Add initial implementations for forms and organization apps with serializers, factories, and admin configurations
Some checks failed
CI/CD Pipeline / Run Tests (push) Failing after 45s
CI/CD Pipeline / Code Quality Checks (push) Failing after 48s
CI/CD Pipeline / Build Docker Images (push) Has been skipped
CI/CD Pipeline / Push to Gitea Registry (push) Has been skipped
CI/CD Pipeline / Deploy to Server (push) Has been skipped

This commit is contained in:
2026-03-28 18:23:06 +01:00
parent 8ed3e1175c
commit 345b1d0cc8
201 changed files with 15097 additions and 6691 deletions

View File

@@ -35,6 +35,7 @@ class FieldError:
field: str
message: str
value: Any = None
@dataclass
@@ -86,6 +87,12 @@ class ColumnMapping:
model_field: str # Название поля модели
required: bool = False
field_type: str = "str" # str, int, decimal, bool, date
validator: Any = None
@property
def field_name(self) -> str:
"""Backward-compatible alias for legacy tests."""
return self.model_field
@dataclass
@@ -352,10 +359,7 @@ class BaseExcelParser(ABC, Generic[T]):
def _load_workbook(self, file: UploadedFile | BytesIO) -> None:
"""Загружает Excel файл."""
if isinstance(file, UploadedFile):
content = BytesIO(file.read())
else:
content = file
content = BytesIO(file.read()) if isinstance(file, UploadedFile) else file
self._workbook = openpyxl.load_workbook(content, read_only=True, data_only=True)
self._sheet = self._workbook.active
@@ -398,6 +402,27 @@ class BaseExcelParser(ABC, Generic[T]):
cell = self._sheet.cell(row=row, column=col + 1)
return cell.value
def _normalize_row_data(self, row_data: RowData | dict[str, Any]) -> RowData:
"""Support legacy tests that still pass plain dict payloads."""
if isinstance(row_data, RowData):
return row_data
payload = dict(row_data)
organization_name = payload.pop("organization_name", payload.pop("name", None))
equipment_name = payload.pop("equipment_name", None)
if equipment_name is not None and "name" not in payload:
payload["name"] = equipment_name
return RowData(
row_number=0,
organization_name=organization_name,
inn=payload.pop("inn", None),
ogrn=payload.pop("ogrn", None),
kpp=payload.pop("kpp", None),
okpo=payload.pop("okpo", None),
fields=payload,
)
def _convert_value(self, value: Any, field_type: str) -> Any:
"""Конвертирует значение в нужный тип."""
if value is None:
@@ -407,11 +432,11 @@ class BaseExcelParser(ABC, Generic[T]):
if field_type == "str":
return str(value).strip() if value else None
elif field_type == "int":
if isinstance(value, (int, float)):
if isinstance(value, int | float):
return int(value)
return int(float(str(value).replace(",", ".").replace(" ", "")))
elif field_type == "decimal":
if isinstance(value, (int, float, Decimal)):
if isinstance(value, int | float | Decimal):
return Decimal(str(value))
cleaned = str(value).replace(",", ".").replace(" ", "")
return Decimal(cleaned) if cleaned else None
@@ -423,7 +448,7 @@ class BaseExcelParser(ABC, Generic[T]):
elif field_type == "date":
from datetime import date, datetime
if isinstance(value, (date, datetime)):
if isinstance(value, date | datetime):
return value.date() if isinstance(value, datetime) else value
return None
else:
@@ -437,7 +462,12 @@ class BaseExcelParser(ABC, Generic[T]):
# Валидация обязательных полей организации
if not row_data.organization_name:
errors.append(FieldError(field="organization_name", message="Наименование организации обязательно"))
errors.append(
FieldError(
field="organization_name",
message="Наименование организации обязательно",
)
)
# Валидация ИНН
valid, msg = validate_inn(row_data.inn)
@@ -474,13 +504,16 @@ class BaseExcelParser(ABC, Generic[T]):
)
# Валидация числовых полей (должны быть >= 0)
if mapping.field_type in ("int", "decimal") and value is not None:
if value < 0:
errors.append(
FieldError(
field=mapping.model_field,
message=f"Значение должно быть >= 0",
)
if (
mapping.field_type in ("int", "decimal")
and value is not None
and value < 0
):
errors.append(
FieldError(
field=mapping.model_field,
message="Значение должно быть >= 0",
)
)
return errors