feat(forms): unify F2-F6 upload contracts and add shared serializers
This commit is contained in:
216
src/apps/core/upload_contracts.py
Normal file
216
src/apps/core/upload_contracts.py
Normal file
@@ -0,0 +1,216 @@
|
||||
"""Shared upload serializers and response helpers for form upload endpoints."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
from uuid import uuid4
|
||||
|
||||
from django.utils import timezone
|
||||
from rest_framework import serializers, status
|
||||
from rest_framework.response import Response
|
||||
|
||||
MAX_UPLOAD_SIZE_BYTES = 50 * 1024 * 1024
|
||||
ALLOWED_UPLOAD_EXTENSIONS = (".xlsx", ".xls")
|
||||
|
||||
_ROMAN_NUMBERS = {
|
||||
1: "I",
|
||||
2: "II",
|
||||
3: "III",
|
||||
4: "IV",
|
||||
}
|
||||
|
||||
|
||||
def _normalize_extension(name: str) -> str:
|
||||
return name.rsplit(".", 1)[-1].lower() if "." in name else ""
|
||||
|
||||
|
||||
def _validate_uploaded_file(value: Any) -> None:
|
||||
if not hasattr(value, "name") or not value.name:
|
||||
raise serializers.ValidationError("Файл не выбран")
|
||||
|
||||
extension = "." + _normalize_extension(value.name)
|
||||
if extension not in ALLOWED_UPLOAD_EXTENSIONS:
|
||||
raise serializers.ValidationError("Неподдерживаемый формат файла")
|
||||
|
||||
if value.size > MAX_UPLOAD_SIZE_BYTES:
|
||||
raise serializers.ValidationError("Размер файла превышает 50MB")
|
||||
|
||||
|
||||
def report_quarter_display(report_year: int, report_quarter: int) -> str:
|
||||
return f"{_ROMAN_NUMBERS.get(report_quarter, report_quarter)} квартал {report_year}"
|
||||
|
||||
|
||||
def report_half_year_display(report_year: int, report_half_year: int) -> str:
|
||||
return f"{_ROMAN_NUMBERS.get(report_half_year, report_half_year)} полугодие {report_year}"
|
||||
|
||||
|
||||
def report_annual_display(report_year: int) -> str:
|
||||
return f"{report_year} год"
|
||||
|
||||
|
||||
def build_upload_success_payload(
|
||||
*,
|
||||
form: str,
|
||||
report_year: int,
|
||||
status: str,
|
||||
report_quarter: int | None = None,
|
||||
report_half_year: int | None = None,
|
||||
result: dict[str, Any] | None = None,
|
||||
job_id: str | None = None,
|
||||
created_at: datetime | None = None,
|
||||
) -> dict[str, Any]:
|
||||
created = created_at or timezone.now()
|
||||
payload: dict[str, Any] = {
|
||||
"upload_id": str(uuid4()),
|
||||
"form": form,
|
||||
"report_year": report_year,
|
||||
"status": status,
|
||||
"created_at": created.isoformat(),
|
||||
}
|
||||
|
||||
if report_half_year is not None:
|
||||
payload["report_half_year"] = report_half_year
|
||||
payload["report_period_display"] = report_half_year_display(
|
||||
report_year=report_year,
|
||||
report_half_year=report_half_year,
|
||||
)
|
||||
elif report_quarter is not None:
|
||||
payload["report_quarter"] = report_quarter
|
||||
payload["report_period_display"] = report_quarter_display(
|
||||
report_year=report_year,
|
||||
report_quarter=report_quarter,
|
||||
)
|
||||
else:
|
||||
payload["report_period_display"] = report_annual_display(report_year=report_year)
|
||||
|
||||
if result is not None:
|
||||
payload["result"] = result
|
||||
|
||||
if job_id is not None:
|
||||
payload["job_id"] = job_id
|
||||
|
||||
return payload
|
||||
|
||||
|
||||
def build_upload_validation_payload(errors: dict[str, Any]) -> dict[str, Any]:
|
||||
details: list[dict[str, Any]] = []
|
||||
|
||||
def _collect(field: str, value: Any) -> None:
|
||||
if isinstance(value, dict):
|
||||
for nested_field, nested_value in value.items():
|
||||
nested_name = f"{field}.{nested_field}" if field else nested_field
|
||||
_collect(nested_name, nested_value)
|
||||
return
|
||||
|
||||
if isinstance(value, list):
|
||||
for item in value:
|
||||
_collect(field, item)
|
||||
return
|
||||
|
||||
code = getattr(value, "code", None) or "invalid"
|
||||
details.append(
|
||||
{
|
||||
"field": field,
|
||||
"code": code,
|
||||
"message": str(value),
|
||||
}
|
||||
)
|
||||
|
||||
for field, value in errors.items():
|
||||
target_field = "non_field_errors" if field == "non_field_errors" else field
|
||||
_collect(target_field, value)
|
||||
|
||||
return {
|
||||
"error_code": "validation_error",
|
||||
"error_message": "Validation failed",
|
||||
"details": details,
|
||||
}
|
||||
|
||||
|
||||
def build_upload_error_payload(
|
||||
*,
|
||||
error_code: str,
|
||||
error_message: str,
|
||||
details: list[dict[str, Any]] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
return {
|
||||
"error_code": error_code,
|
||||
"error_message": error_message,
|
||||
"details": details or [],
|
||||
}
|
||||
|
||||
|
||||
def build_upload_error_response(
|
||||
*,
|
||||
error_code: str,
|
||||
error_message: str,
|
||||
details: list[dict[str, Any]] | None = None,
|
||||
status_code: int = status.HTTP_400_BAD_REQUEST,
|
||||
) -> Response:
|
||||
return Response(
|
||||
build_upload_error_payload(
|
||||
error_code=error_code,
|
||||
error_message=error_message,
|
||||
details=details,
|
||||
),
|
||||
status=status_code,
|
||||
)
|
||||
|
||||
|
||||
def build_upload_validation_response(errors: dict[str, Any]) -> Response:
|
||||
return Response(
|
||||
build_upload_validation_payload(errors),
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
|
||||
class BaseUploadSerializer(serializers.Serializer):
|
||||
file = serializers.FileField(help_text="Excel файл (.xlsx, .xls)")
|
||||
report_year = serializers.IntegerField(min_value=2000, help_text="Отчетный год")
|
||||
|
||||
def validate_file(self, value):
|
||||
_validate_uploaded_file(value)
|
||||
return value
|
||||
|
||||
|
||||
class UploadQuarterSerializer(BaseUploadSerializer):
|
||||
report_quarter = serializers.IntegerField(
|
||||
min_value=1,
|
||||
max_value=4,
|
||||
required=True,
|
||||
help_text="Отчетный квартал от 1 до 4",
|
||||
)
|
||||
|
||||
|
||||
class UploadHalfYearSerializer(BaseUploadSerializer):
|
||||
report_half_year = serializers.IntegerField(
|
||||
min_value=1,
|
||||
max_value=2,
|
||||
required=True,
|
||||
help_text="Полугодие отчета: 1 или 2",
|
||||
)
|
||||
|
||||
|
||||
class UploadAnnualSerializer(BaseUploadSerializer):
|
||||
"""Upload payload without period quarter (годовая форма)."""
|
||||
|
||||
|
||||
class FieldErrorSerializer(serializers.Serializer):
|
||||
field = serializers.CharField()
|
||||
message = serializers.CharField()
|
||||
|
||||
|
||||
class RowValidationErrorSerializer(serializers.Serializer):
|
||||
row = serializers.IntegerField()
|
||||
inn = serializers.CharField(allow_null=True)
|
||||
kpp = serializers.CharField(allow_null=True)
|
||||
organization_name = serializers.CharField(allow_null=True)
|
||||
errors = FieldErrorSerializer(many=True)
|
||||
|
||||
|
||||
class UploadParseResultSerializer(serializers.Serializer):
|
||||
batch_id = serializers.IntegerField()
|
||||
loaded_count = serializers.IntegerField()
|
||||
skipped_count = serializers.IntegerField()
|
||||
errors = RowValidationErrorSerializer(many=True)
|
||||
@@ -8,6 +8,11 @@ API формы Ф-2.
|
||||
|
||||
import logging
|
||||
|
||||
from apps.core.upload_contracts import (
|
||||
build_upload_error_response,
|
||||
build_upload_success_payload,
|
||||
build_upload_validation_response,
|
||||
)
|
||||
from apps.core.viewsets import ReadOnlyViewSet
|
||||
from apps.form_2.models import FormF2Record
|
||||
from apps.form_2.serializers import (
|
||||
@@ -42,7 +47,9 @@ class FormF2UploadView(APIView):
|
||||
def post(self, request):
|
||||
"""Загрузка и обработка файла."""
|
||||
serializer = FormF2UploadSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
if not serializer.is_valid():
|
||||
return build_upload_validation_response(serializer.errors)
|
||||
|
||||
file = serializer.validated_data["file"]
|
||||
report_year = serializer.validated_data["report_year"]
|
||||
@@ -59,11 +66,13 @@ class FormF2UploadView(APIView):
|
||||
)
|
||||
|
||||
return Response(
|
||||
{
|
||||
"success": True,
|
||||
"message": "Файл поставлен в очередь на обработку",
|
||||
"task_id": task.id,
|
||||
},
|
||||
build_upload_success_payload(
|
||||
form="f2",
|
||||
report_year=report_year,
|
||||
report_quarter=report_quarter,
|
||||
status="queued",
|
||||
job_id=task.id,
|
||||
),
|
||||
status=status.HTTP_202_ACCEPTED,
|
||||
)
|
||||
|
||||
@@ -77,20 +86,21 @@ class FormF2UploadView(APIView):
|
||||
result_serializer = FormF2ParseResultSerializer(result)
|
||||
|
||||
return Response(
|
||||
{
|
||||
"success": True,
|
||||
"data": result_serializer.data,
|
||||
},
|
||||
build_upload_success_payload(
|
||||
form="f2",
|
||||
report_year=report_year,
|
||||
report_quarter=report_quarter,
|
||||
status="done",
|
||||
result=result_serializer.data,
|
||||
),
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.exception("Ошибка обработки файла Ф-2")
|
||||
return Response(
|
||||
{
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
return build_upload_error_response(
|
||||
error_code="processing_error",
|
||||
error_message=str(e),
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
"""
|
||||
|
||||
from apps.form_2.models import FormF2Record
|
||||
from apps.core.upload_contracts import (
|
||||
UploadQuarterSerializer,
|
||||
UploadParseResultSerializer,
|
||||
)
|
||||
from apps.organization.serializers import OrganizationSerializer
|
||||
from rest_framework import serializers
|
||||
|
||||
@@ -51,52 +55,9 @@ class FormF2RecordListSerializer(serializers.ModelSerializer):
|
||||
]
|
||||
|
||||
|
||||
class FormF2UploadSerializer(serializers.Serializer):
|
||||
"""Сериализатор загрузки файла формы Ф-2."""
|
||||
|
||||
file = serializers.FileField(help_text="Excel файл формы Ф-2 (.xlsx)")
|
||||
report_year = serializers.IntegerField(min_value=2000, help_text="Отчетный год")
|
||||
report_quarter = serializers.IntegerField(
|
||||
min_value=1,
|
||||
max_value=4,
|
||||
required=False,
|
||||
allow_null=True,
|
||||
help_text="Отчетный квартал от 1 до 4. Пусто для годовой формы.",
|
||||
)
|
||||
|
||||
def validate_file(self, value):
|
||||
"""Валидация загруженного файла."""
|
||||
if not value.name.endswith((".xlsx", ".xls")):
|
||||
raise serializers.ValidationError(
|
||||
"Неподдерживаемый формат файла. Используйте .xlsx или .xls"
|
||||
)
|
||||
# Ограничение размера: 50MB
|
||||
if value.size > 50 * 1024 * 1024:
|
||||
raise serializers.ValidationError("Размер файла превышает 50MB")
|
||||
return value
|
||||
class FormF2UploadSerializer(UploadQuarterSerializer):
|
||||
"""Загрузка файла формы Ф-2 (квартальная отчетность)."""
|
||||
|
||||
|
||||
class FieldErrorSerializer(serializers.Serializer):
|
||||
"""Сериализатор ошибки поля."""
|
||||
|
||||
field = serializers.CharField()
|
||||
message = serializers.CharField()
|
||||
|
||||
|
||||
class RowValidationErrorSerializer(serializers.Serializer):
|
||||
"""Сериализатор ошибки валидации строки."""
|
||||
|
||||
row = serializers.IntegerField()
|
||||
inn = serializers.CharField(allow_null=True)
|
||||
kpp = serializers.CharField(allow_null=True)
|
||||
organization_name = serializers.CharField(allow_null=True)
|
||||
errors = FieldErrorSerializer(many=True)
|
||||
|
||||
|
||||
class FormF2ParseResultSerializer(serializers.Serializer):
|
||||
class FormF2ParseResultSerializer(UploadParseResultSerializer):
|
||||
"""Сериализатор результата парсинга."""
|
||||
|
||||
batch_id = serializers.IntegerField()
|
||||
loaded_count = serializers.IntegerField()
|
||||
skipped_count = serializers.IntegerField()
|
||||
errors = RowValidationErrorSerializer(many=True)
|
||||
|
||||
@@ -8,6 +8,11 @@ API формы Ф-3.
|
||||
|
||||
import logging
|
||||
|
||||
from apps.core.upload_contracts import (
|
||||
build_upload_error_response,
|
||||
build_upload_success_payload,
|
||||
build_upload_validation_response,
|
||||
)
|
||||
from apps.core.viewsets import ReadOnlyViewSet
|
||||
from apps.form_3.models import FormF3Record
|
||||
from apps.form_3.serializers import (
|
||||
@@ -41,7 +46,9 @@ class FormF3UploadView(APIView):
|
||||
def post(self, request):
|
||||
"""Загрузка и обработка файла."""
|
||||
serializer = FormF3UploadSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
if not serializer.is_valid():
|
||||
return build_upload_validation_response(serializer.errors)
|
||||
|
||||
file = serializer.validated_data["file"]
|
||||
report_year = serializer.validated_data["report_year"]
|
||||
@@ -57,11 +64,12 @@ class FormF3UploadView(APIView):
|
||||
)
|
||||
|
||||
return Response(
|
||||
{
|
||||
"success": True,
|
||||
"message": "Файл поставлен в очередь на обработку",
|
||||
"task_id": task.id,
|
||||
},
|
||||
build_upload_success_payload(
|
||||
form="f3",
|
||||
report_year=report_year,
|
||||
status="queued",
|
||||
job_id=task.id,
|
||||
),
|
||||
status=status.HTTP_202_ACCEPTED,
|
||||
)
|
||||
|
||||
@@ -74,20 +82,20 @@ class FormF3UploadView(APIView):
|
||||
result_serializer = FormF3ParseResultSerializer(result)
|
||||
|
||||
return Response(
|
||||
{
|
||||
"success": True,
|
||||
"data": result_serializer.data,
|
||||
},
|
||||
build_upload_success_payload(
|
||||
form="f3",
|
||||
report_year=report_year,
|
||||
status="done",
|
||||
result=result_serializer.data,
|
||||
),
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.exception("Ошибка обработки файла Ф-3")
|
||||
return Response(
|
||||
{
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
return build_upload_error_response(
|
||||
error_code="processing_error",
|
||||
error_message=str(e),
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
"""
|
||||
|
||||
from apps.form_3.models import FormF3Record
|
||||
from apps.core.upload_contracts import (
|
||||
UploadAnnualSerializer,
|
||||
UploadParseResultSerializer,
|
||||
)
|
||||
from apps.organization.serializers import OrganizationSerializer
|
||||
from rest_framework import serializers
|
||||
|
||||
@@ -50,51 +54,9 @@ class FormF3RecordListSerializer(serializers.ModelSerializer):
|
||||
]
|
||||
|
||||
|
||||
class FormF3UploadSerializer(serializers.Serializer):
|
||||
"""Сериализатор загрузки файла формы Ф-3."""
|
||||
|
||||
file = serializers.FileField(help_text="Excel файл формы Ф-3 (.xlsx)")
|
||||
report_year = serializers.IntegerField(min_value=2000, help_text="Отчетный год")
|
||||
report_quarter = serializers.IntegerField(
|
||||
min_value=1,
|
||||
max_value=4,
|
||||
required=False,
|
||||
allow_null=True,
|
||||
help_text="Отчетный квартал от 1 до 4. Пусто для годовой формы.",
|
||||
)
|
||||
|
||||
def validate_file(self, value):
|
||||
"""Валидация загруженного файла."""
|
||||
if not value.name.endswith((".xlsx", ".xls")):
|
||||
raise serializers.ValidationError(
|
||||
"Неподдерживаемый формат файла. Используйте .xlsx или .xls"
|
||||
)
|
||||
if value.size > 50 * 1024 * 1024:
|
||||
raise serializers.ValidationError("Размер файла превышает 50MB")
|
||||
return value
|
||||
class FormF3UploadSerializer(UploadAnnualSerializer):
|
||||
"""Загрузка файла формы Ф-3 (годовая отчетность)."""
|
||||
|
||||
|
||||
class FieldErrorSerializer(serializers.Serializer):
|
||||
"""Сериализатор ошибки поля."""
|
||||
|
||||
field = serializers.CharField()
|
||||
message = serializers.CharField()
|
||||
|
||||
|
||||
class RowValidationErrorSerializer(serializers.Serializer):
|
||||
"""Сериализатор ошибки валидации строки."""
|
||||
|
||||
row = serializers.IntegerField()
|
||||
inn = serializers.CharField(allow_null=True)
|
||||
kpp = serializers.CharField(allow_null=True)
|
||||
organization_name = serializers.CharField(allow_null=True)
|
||||
errors = FieldErrorSerializer(many=True)
|
||||
|
||||
|
||||
class FormF3ParseResultSerializer(serializers.Serializer):
|
||||
class FormF3ParseResultSerializer(UploadParseResultSerializer):
|
||||
"""Сериализатор результата парсинга."""
|
||||
|
||||
batch_id = serializers.IntegerField()
|
||||
loaded_count = serializers.IntegerField()
|
||||
skipped_count = serializers.IntegerField()
|
||||
errors = RowValidationErrorSerializer(many=True)
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
|
||||
import logging
|
||||
|
||||
from apps.core.upload_contracts import (
|
||||
build_upload_error_response,
|
||||
build_upload_success_payload,
|
||||
build_upload_validation_response,
|
||||
)
|
||||
from apps.core.viewsets import ReadOnlyViewSet
|
||||
from apps.form_4.models import FormF4Record
|
||||
from apps.form_4.serializers import (
|
||||
@@ -27,10 +32,13 @@ class FormF4UploadView(APIView):
|
||||
|
||||
def post(self, request):
|
||||
serializer = FormF4UploadSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
if not serializer.is_valid():
|
||||
return build_upload_validation_response(serializer.errors)
|
||||
|
||||
file = serializer.validated_data["file"]
|
||||
report_year = serializer.validated_data["report_year"]
|
||||
report_quarter = serializer.validated_data.get("report_quarter")
|
||||
report_half_year = serializer.validated_data.get("report_half_year")
|
||||
report_quarter = report_half_year
|
||||
|
||||
if file.size > BACKGROUND_THRESHOLD:
|
||||
task = process_form_f4_file.delay(
|
||||
@@ -40,11 +48,13 @@ class FormF4UploadView(APIView):
|
||||
report_quarter,
|
||||
)
|
||||
return Response(
|
||||
{
|
||||
"success": True,
|
||||
"message": "Файл поставлен в очередь",
|
||||
"task_id": task.id,
|
||||
},
|
||||
build_upload_success_payload(
|
||||
form="f4",
|
||||
report_year=report_year,
|
||||
report_half_year=report_half_year,
|
||||
status="queued",
|
||||
job_id=task.id,
|
||||
),
|
||||
status=status.HTTP_202_ACCEPTED,
|
||||
)
|
||||
|
||||
@@ -55,12 +65,21 @@ class FormF4UploadView(APIView):
|
||||
report_quarter=report_quarter,
|
||||
)
|
||||
return Response(
|
||||
{"success": True, "data": FormF4ParseResultSerializer(result).data}
|
||||
build_upload_success_payload(
|
||||
form="f4",
|
||||
report_year=report_year,
|
||||
report_half_year=report_half_year,
|
||||
status="done",
|
||||
result=FormF4ParseResultSerializer(result).data,
|
||||
),
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.exception("Ошибка обработки файла Ф-4")
|
||||
return Response(
|
||||
{"success": False, "error": str(e)}, status=status.HTTP_400_BAD_REQUEST
|
||||
return build_upload_error_response(
|
||||
error_code="processing_error",
|
||||
error_message=str(e),
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
"""Сериализаторы формы Ф-4."""
|
||||
|
||||
from apps.form_4.models import FormF4Record
|
||||
from apps.core.upload_contracts import (
|
||||
UploadHalfYearSerializer,
|
||||
UploadParseResultSerializer,
|
||||
)
|
||||
from apps.organization.serializers import OrganizationSerializer
|
||||
from rest_framework import serializers
|
||||
|
||||
@@ -39,40 +43,9 @@ class FormF4RecordListSerializer(serializers.ModelSerializer):
|
||||
]
|
||||
|
||||
|
||||
class FormF4UploadSerializer(serializers.Serializer):
|
||||
file = serializers.FileField(help_text="Excel файл формы Ф-4 (.xlsx)")
|
||||
report_year = serializers.IntegerField(min_value=2000, help_text="Отчетный год")
|
||||
report_quarter = serializers.IntegerField(
|
||||
min_value=1,
|
||||
max_value=4,
|
||||
required=False,
|
||||
allow_null=True,
|
||||
help_text="Отчетный квартал от 1 до 4. Пусто для годовой формы.",
|
||||
)
|
||||
|
||||
def validate_file(self, value):
|
||||
if not value.name.endswith((".xlsx", ".xls")):
|
||||
raise serializers.ValidationError("Неподдерживаемый формат файла")
|
||||
if value.size > 50 * 1024 * 1024:
|
||||
raise serializers.ValidationError("Размер файла превышает 50MB")
|
||||
return value
|
||||
class FormF4UploadSerializer(UploadHalfYearSerializer):
|
||||
"""Загрузка файла формы Ф-4 (полугодовая отчетность)."""
|
||||
|
||||
|
||||
class FieldErrorSerializer(serializers.Serializer):
|
||||
field = serializers.CharField()
|
||||
message = serializers.CharField()
|
||||
|
||||
|
||||
class RowValidationErrorSerializer(serializers.Serializer):
|
||||
row = serializers.IntegerField()
|
||||
inn = serializers.CharField(allow_null=True)
|
||||
kpp = serializers.CharField(allow_null=True)
|
||||
organization_name = serializers.CharField(allow_null=True)
|
||||
errors = FieldErrorSerializer(many=True)
|
||||
|
||||
|
||||
class FormF4ParseResultSerializer(serializers.Serializer):
|
||||
batch_id = serializers.IntegerField()
|
||||
loaded_count = serializers.IntegerField()
|
||||
skipped_count = serializers.IntegerField()
|
||||
errors = RowValidationErrorSerializer(many=True)
|
||||
class FormF4ParseResultSerializer(UploadParseResultSerializer):
|
||||
"""Сериализатор результата парсинга."""
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
|
||||
import logging
|
||||
|
||||
from apps.core.upload_contracts import (
|
||||
build_upload_error_response,
|
||||
build_upload_success_payload,
|
||||
build_upload_validation_response,
|
||||
)
|
||||
from apps.core.viewsets import ReadOnlyViewSet
|
||||
from apps.form_5.models import FormF5Record
|
||||
from apps.form_5.serializers import (
|
||||
@@ -27,9 +32,12 @@ class FormF5UploadView(APIView):
|
||||
|
||||
def post(self, request):
|
||||
serializer = FormF5UploadSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
if not serializer.is_valid():
|
||||
return build_upload_validation_response(serializer.errors)
|
||||
|
||||
file = serializer.validated_data["file"]
|
||||
report_year = serializer.validated_data["report_year"]
|
||||
|
||||
report_quarter = serializer.validated_data.get("report_quarter")
|
||||
|
||||
if file.size > BACKGROUND_THRESHOLD:
|
||||
@@ -40,11 +48,12 @@ class FormF5UploadView(APIView):
|
||||
report_quarter,
|
||||
)
|
||||
return Response(
|
||||
{
|
||||
"success": True,
|
||||
"message": "Файл поставлен в очередь",
|
||||
"task_id": task.id,
|
||||
},
|
||||
build_upload_success_payload(
|
||||
form="f5",
|
||||
report_year=report_year,
|
||||
status="queued",
|
||||
job_id=task.id,
|
||||
),
|
||||
status=status.HTTP_202_ACCEPTED,
|
||||
)
|
||||
|
||||
@@ -55,12 +64,20 @@ class FormF5UploadView(APIView):
|
||||
report_quarter=report_quarter,
|
||||
)
|
||||
return Response(
|
||||
{"success": True, "data": FormF5ParseResultSerializer(result).data}
|
||||
build_upload_success_payload(
|
||||
form="f5",
|
||||
report_year=report_year,
|
||||
status="done",
|
||||
result=FormF5ParseResultSerializer(result).data,
|
||||
),
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.exception("Ошибка обработки файла Ф-5")
|
||||
return Response(
|
||||
{"success": False, "error": str(e)}, status=status.HTTP_400_BAD_REQUEST
|
||||
return build_upload_error_response(
|
||||
error_code="processing_error",
|
||||
error_message=str(e),
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
"""Сериализаторы формы Ф-5."""
|
||||
|
||||
from apps.form_5.models import FormF5Record
|
||||
from apps.core.upload_contracts import (
|
||||
UploadAnnualSerializer,
|
||||
UploadParseResultSerializer,
|
||||
)
|
||||
from apps.organization.serializers import OrganizationSerializer
|
||||
from rest_framework import serializers
|
||||
|
||||
@@ -41,40 +45,9 @@ class FormF5RecordListSerializer(serializers.ModelSerializer):
|
||||
]
|
||||
|
||||
|
||||
class FormF5UploadSerializer(serializers.Serializer):
|
||||
file = serializers.FileField(help_text="Excel файл формы Ф-5 (.xlsx)")
|
||||
report_year = serializers.IntegerField(min_value=2000, help_text="Отчетный год")
|
||||
report_quarter = serializers.IntegerField(
|
||||
min_value=1,
|
||||
max_value=4,
|
||||
required=False,
|
||||
allow_null=True,
|
||||
help_text="Отчетный квартал от 1 до 4. Пусто для годовой формы.",
|
||||
)
|
||||
|
||||
def validate_file(self, value):
|
||||
if not value.name.endswith((".xlsx", ".xls")):
|
||||
raise serializers.ValidationError("Неподдерживаемый формат файла")
|
||||
if value.size > 50 * 1024 * 1024:
|
||||
raise serializers.ValidationError("Размер файла превышает 50MB")
|
||||
return value
|
||||
class FormF5UploadSerializer(UploadAnnualSerializer):
|
||||
"""Загрузка файла формы Ф-5 (годовая отчетность)."""
|
||||
|
||||
|
||||
class FieldErrorSerializer(serializers.Serializer):
|
||||
field = serializers.CharField()
|
||||
message = serializers.CharField()
|
||||
|
||||
|
||||
class RowValidationErrorSerializer(serializers.Serializer):
|
||||
row = serializers.IntegerField()
|
||||
inn = serializers.CharField(allow_null=True)
|
||||
kpp = serializers.CharField(allow_null=True)
|
||||
organization_name = serializers.CharField(allow_null=True)
|
||||
errors = FieldErrorSerializer(many=True)
|
||||
|
||||
|
||||
class FormF5ParseResultSerializer(serializers.Serializer):
|
||||
batch_id = serializers.IntegerField()
|
||||
loaded_count = serializers.IntegerField()
|
||||
skipped_count = serializers.IntegerField()
|
||||
errors = RowValidationErrorSerializer(many=True)
|
||||
class FormF5ParseResultSerializer(UploadParseResultSerializer):
|
||||
"""Сериализатор результата парсинга."""
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
|
||||
import logging
|
||||
|
||||
from apps.core.upload_contracts import (
|
||||
build_upload_error_response,
|
||||
build_upload_success_payload,
|
||||
build_upload_validation_response,
|
||||
)
|
||||
from apps.core.viewsets import ReadOnlyViewSet
|
||||
from apps.form_6.models import FormF6Record
|
||||
from apps.form_6.serializers import (
|
||||
@@ -27,7 +32,9 @@ class FormF6UploadView(APIView):
|
||||
|
||||
def post(self, request):
|
||||
serializer = FormF6UploadSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
if not serializer.is_valid():
|
||||
return build_upload_validation_response(serializer.errors)
|
||||
|
||||
file = serializer.validated_data["file"]
|
||||
report_year = serializer.validated_data["report_year"]
|
||||
report_quarter = serializer.validated_data.get("report_quarter")
|
||||
@@ -40,11 +47,12 @@ class FormF6UploadView(APIView):
|
||||
report_quarter,
|
||||
)
|
||||
return Response(
|
||||
{
|
||||
"success": True,
|
||||
"message": "Файл поставлен в очередь",
|
||||
"task_id": task.id,
|
||||
},
|
||||
build_upload_success_payload(
|
||||
form="f6",
|
||||
report_year=report_year,
|
||||
status="queued",
|
||||
job_id=task.id,
|
||||
),
|
||||
status=status.HTTP_202_ACCEPTED,
|
||||
)
|
||||
|
||||
@@ -55,12 +63,20 @@ class FormF6UploadView(APIView):
|
||||
report_quarter=report_quarter,
|
||||
)
|
||||
return Response(
|
||||
{"success": True, "data": FormF6ParseResultSerializer(result).data}
|
||||
build_upload_success_payload(
|
||||
form="f6",
|
||||
report_year=report_year,
|
||||
status="done",
|
||||
result=FormF6ParseResultSerializer(result).data,
|
||||
),
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.exception("Ошибка обработки файла Ф-6")
|
||||
return Response(
|
||||
{"success": False, "error": str(e)}, status=status.HTTP_400_BAD_REQUEST
|
||||
return build_upload_error_response(
|
||||
error_code="processing_error",
|
||||
error_message=str(e),
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
"""Сериализаторы формы Ф-6."""
|
||||
|
||||
from apps.form_6.models import FormF6Record
|
||||
from apps.core.upload_contracts import (
|
||||
UploadAnnualSerializer,
|
||||
UploadParseResultSerializer,
|
||||
)
|
||||
from apps.organization.serializers import OrganizationSerializer
|
||||
from rest_framework import serializers
|
||||
|
||||
@@ -40,40 +44,9 @@ class FormF6RecordListSerializer(serializers.ModelSerializer):
|
||||
]
|
||||
|
||||
|
||||
class FormF6UploadSerializer(serializers.Serializer):
|
||||
file = serializers.FileField(help_text="Excel файл формы Ф-6 (.xlsx)")
|
||||
report_year = serializers.IntegerField(min_value=2000, help_text="Отчетный год")
|
||||
report_quarter = serializers.IntegerField(
|
||||
min_value=1,
|
||||
max_value=4,
|
||||
required=False,
|
||||
allow_null=True,
|
||||
help_text="Отчетный квартал от 1 до 4. Пусто для годовой формы.",
|
||||
)
|
||||
|
||||
def validate_file(self, value):
|
||||
if not value.name.endswith((".xlsx", ".xls")):
|
||||
raise serializers.ValidationError("Неподдерживаемый формат файла")
|
||||
if value.size > 50 * 1024 * 1024:
|
||||
raise serializers.ValidationError("Размер файла превышает 50MB")
|
||||
return value
|
||||
class FormF6UploadSerializer(UploadAnnualSerializer):
|
||||
"""Загрузка файла формы Ф-6 (годовая отчетность)."""
|
||||
|
||||
|
||||
class FieldErrorSerializer(serializers.Serializer):
|
||||
field = serializers.CharField()
|
||||
message = serializers.CharField()
|
||||
|
||||
|
||||
class RowValidationErrorSerializer(serializers.Serializer):
|
||||
row = serializers.IntegerField()
|
||||
inn = serializers.CharField(allow_null=True)
|
||||
kpp = serializers.CharField(allow_null=True)
|
||||
organization_name = serializers.CharField(allow_null=True)
|
||||
errors = FieldErrorSerializer(many=True)
|
||||
|
||||
|
||||
class FormF6ParseResultSerializer(serializers.Serializer):
|
||||
batch_id = serializers.IntegerField()
|
||||
loaded_count = serializers.IntegerField()
|
||||
skipped_count = serializers.IntegerField()
|
||||
errors = RowValidationErrorSerializer(many=True)
|
||||
class FormF6ParseResultSerializer(UploadParseResultSerializer):
|
||||
"""Сериализатор результата парсинга."""
|
||||
|
||||
Reference in New Issue
Block a user