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
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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user