""" Сериализаторы для приложения парсеров. Все сериализаторы read-only, так как данные загружаются только через парсеры. """ from apps.parsers.models import ( FinancialReport, FinancialReportLine, IndustrialCertificateRecord, IndustrialProductRecord, InspectionRecord, ManufacturerRecord, ParserLoadLog, ProcurementRecord, Proxy, ) from rest_framework import serializers # ============================================================================= # Минпромторг - Сертификаты промышленного производства # ============================================================================= class IndustrialCertificateSerializer(serializers.ModelSerializer): """ Сертификат промышленного производства РФ. Данные загружаются из Минпромторга. """ class Meta: model = IndustrialCertificateRecord fields = [ "id", "load_batch", "issue_date", "issue_date_normalized", "certificate_number", "expiry_date", "expiry_date_normalized", "certificate_file_url", "organisation_name", "inn", "ogrn", "registry_organization", "created_at", "updated_at", ] read_only_fields = fields # ============================================================================= # Минпромторг - Реестр производителей # ============================================================================= class ManufacturerSerializer(serializers.ModelSerializer): """ Производитель из реестра Минпромторга. Данные загружаются из Минпромторга. """ class Meta: model = ManufacturerRecord fields = [ "id", "load_batch", "full_legal_name", "inn", "ogrn", "address", "registry_organization", "created_at", "updated_at", ] read_only_fields = fields class IndustrialProductSerializer(serializers.ModelSerializer): """ Промышленная продукция из реестра Минпромторга. Данные загружаются из Минпромторга. """ class Meta: model = IndustrialProductRecord fields = [ "id", "load_batch", "full_organisation_name", "ogrn", "inn", "registry_number", "product_name", "product_model", "okpd2_code", "tnved_code", "regulatory_document", "registry_organization", "created_at", "updated_at", ] read_only_fields = fields # ============================================================================= # Единый реестр проверок (proverki.gov.ru) # ============================================================================= class InspectionSerializer(serializers.ModelSerializer): """ Проверка из Единого реестра проверок. Поддерживает ФЗ-294 и ФЗ-248. """ class Meta: model = InspectionRecord fields = [ "id", "load_batch", "registration_number", "inn", "ogrn", "organisation_name", "control_authority", "inspection_type", "inspection_form", "start_date", "start_date_normalized", "end_date", "end_date_normalized", "status", "legal_basis", "result", "is_federal_law_248", "data_year", "data_month", "registry_organization", "created_at", "updated_at", ] read_only_fields = fields # ============================================================================= # Государственные закупки (zakupki.gov.ru) # ============================================================================= class ProcurementSerializer(serializers.ModelSerializer): """ Государственная закупка из ЕИС zakupki.gov.ru. Поддерживает 44-ФЗ и 223-ФЗ. """ class Meta: model = ProcurementRecord fields = [ "id", "load_batch", "purchase_number", "purchase_name", "customer_inn", "customer_kpp", "customer_ogrn", "customer_name", "max_price", "max_price_amount", "currency_code", "placement_method", "publish_date", "publish_date_normalized", "end_date", "end_date_normalized", "status", "law_type", "purchase_object_info", "href", "region_code", "data_year", "data_month", "registry_organization", "created_at", "updated_at", ] read_only_fields = fields # ============================================================================= # ФНС - Бухгалтерская отчетность # ============================================================================= class FinancialReportLineSerializer(serializers.ModelSerializer): """Строка финансового отчета.""" class Meta: model = FinancialReportLine fields = [ "id", "form_code", "line_code", "line_name", "year", "period_start", "period_end", ] class FinancialReportSerializer(serializers.ModelSerializer): """ Финансовый отчет ФНС. Данные загружаются из Excel файлов. """ lines_count = serializers.SerializerMethodField() class Meta: model = FinancialReport fields = [ "id", "external_id", "ogrn", "registry_organization", "file_name", "file_hash", "load_batch", "status", "source", "error_message", "created_at", "updated_at", "lines_count", ] read_only_fields = fields def get_lines_count(self, obj) -> int: return getattr(obj, "lines_count", obj.lines.count()) class FinancialReportDetailSerializer(FinancialReportSerializer): """Финансовый отчет с детализацией строк.""" lines = FinancialReportLineSerializer(many=True, read_only=True) class Meta(FinancialReportSerializer.Meta): fields = FinancialReportSerializer.Meta.fields + ["lines"] class FNSFileUploadSerializer(serializers.Serializer): """ Сериализатор для загрузки файлов FNS. Принимает список Excel файлов в формате fin_{id}_{ogrn}.xlsx """ files = serializers.ListField( child=serializers.FileField(), allow_empty=False, help_text="Список файлов для загрузки (fin_*.xlsx)", ) def validate_files(self, value): """Валидация файлов.""" import re pattern = re.compile(r"^fin_\d+_\d{13,15}\.xlsx$") for file in value: if not pattern.match(file.name): raise serializers.ValidationError( f"Неверный формат имени файла: {file.name}. " "Ожидается: fin_{{id}}_{{ogrn}}.xlsx" ) return value # ============================================================================= # Служебные модели # ============================================================================= class ParserLoadLogSerializer(serializers.ModelSerializer): """ Лог загрузки парсера. Информация о каждой загрузке данных из внешнего источника. """ source_display = serializers.CharField(source="get_source_display", read_only=True) class Meta: model = ParserLoadLog fields = [ "id", "batch_id", "source", "source_display", "records_count", "status", "error_message", "created_at", "updated_at", ] read_only_fields = fields class ProxySerializer(serializers.ModelSerializer): """ Прокси-сервер для парсеров. Используется для обхода блокировок при парсинге. """ class Meta: model = Proxy fields = [ "id", "address", "is_active", "last_used_at", "fail_count", "description", "created_at", "updated_at", ] read_only_fields = fields class SourceCardRefreshParamSerializer(serializers.Serializer): """Описание параметра ручного обновления карточки источника.""" name = serializers.CharField(read_only=True) label = serializers.CharField(read_only=True) description = serializers.CharField(read_only=True) required = serializers.BooleanField(read_only=True) type = serializers.CharField(read_only=True) default = serializers.JSONField(read_only=True) class SourceCardLoadSerializer(serializers.Serializer): """Сводка по последней загрузке источника.""" batch_id = serializers.IntegerField(read_only=True) source = serializers.CharField(read_only=True) source_display = serializers.CharField(read_only=True) records_count = serializers.IntegerField(read_only=True) status = serializers.CharField(read_only=True) error_message = serializers.CharField(read_only=True) created_at = serializers.DateTimeField(read_only=True) updated_at = serializers.DateTimeField(read_only=True) class SourceCardTaskSerializer(serializers.Serializer): """Краткая информация по фоновой задаче карточки.""" task_id = serializers.CharField(read_only=True) task_name = serializers.CharField(read_only=True) status = serializers.CharField(read_only=True) progress = serializers.IntegerField(read_only=True) progress_message = serializers.CharField(read_only=True) started_at = serializers.DateTimeField(read_only=True) created_at = serializers.DateTimeField(read_only=True) meta = serializers.JSONField(read_only=True) class SourceCardItemSerializer(serializers.Serializer): """Подисточник внутри агрегированной карточки.""" code = serializers.CharField(read_only=True) title = serializers.CharField(read_only=True) description = serializers.CharField(read_only=True) parser_source = serializers.CharField(read_only=True, allow_null=True) parser_source_display = serializers.CharField(read_only=True, allow_null=True) records_count = serializers.IntegerField(read_only=True) organizations_count = serializers.IntegerField(read_only=True) last_updated_at = serializers.DateTimeField(read_only=True, allow_null=True) latest_load = SourceCardLoadSerializer(read_only=True, allow_null=True) latest_success_load = SourceCardLoadSerializer(read_only=True, allow_null=True) class SourceCardSerializer(serializers.Serializer): """Serializer for frontend source cards list.""" slug = serializers.CharField(read_only=True) title = serializers.CharField(read_only=True) description = serializers.CharField(read_only=True) order = serializers.IntegerField(read_only=True) is_available = serializers.BooleanField(read_only=True) status = serializers.CharField(read_only=True) status_label = serializers.CharField(read_only=True) progress = serializers.IntegerField(read_only=True) records_count = serializers.IntegerField(read_only=True) organizations_count = serializers.IntegerField(read_only=True) last_updated_at = serializers.DateTimeField(read_only=True, allow_null=True) next_update_at = serializers.DateTimeField(read_only=True, allow_null=True) error_message = serializers.CharField(read_only=True) task_names = serializers.ListField(child=serializers.CharField(), read_only=True) refresh_requires_params = serializers.BooleanField(read_only=True) refresh_params = SourceCardRefreshParamSerializer(many=True, read_only=True) class SourceTaskStatusSerializer(serializers.Serializer): """Табличная строка статуса источника для экрана задач парсинга.""" row_number = serializers.IntegerField(read_only=True) slug = serializers.CharField(read_only=True) source = serializers.CharField(read_only=True) status = serializers.CharField(read_only=True) status_label = serializers.CharField(read_only=True) actualized_at = serializers.DateTimeField(read_only=True, allow_null=True) next_update_at = serializers.DateTimeField(read_only=True, allow_null=True) records_count = serializers.IntegerField(read_only=True) organizations_count = serializers.IntegerField(read_only=True) progress = serializers.IntegerField(read_only=True) error_message = serializers.CharField(read_only=True) active_tasks = SourceCardTaskSerializer(many=True, read_only=True) class SourceCardDetailSerializer(SourceCardSerializer): """Detailed serializer for a single frontend source card.""" active_tasks = SourceCardTaskSerializer(many=True, read_only=True) source_items = SourceCardItemSerializer(many=True, read_only=True) latest_load = SourceCardLoadSerializer(read_only=True, allow_null=True) latest_success_load = SourceCardLoadSerializer(read_only=True, allow_null=True) class SourceCardRefreshRequestSerializer(serializers.Serializer): """Request body for manual card refresh.""" params = serializers.DictField(required=False) class SourceCardRefreshTaskSerializer(serializers.Serializer): """Queued task info after refresh start.""" task_id = serializers.CharField(read_only=True) task_name = serializers.CharField(read_only=True) class SourceCardRefreshResponseSerializer(serializers.Serializer): """Response serializer for manual card refresh.""" source_card = serializers.CharField(read_only=True) status = serializers.CharField(read_only=True) requested_at = serializers.DateTimeField(read_only=True) tasks = SourceCardRefreshTaskSerializer(many=True, read_only=True)