Files
mostovik-backend/src/apps/parsers/serializers.py
Aleksandr Meshchriakov 3d298ce352
Some checks failed
CI/CD Pipeline / Run Tests (pull_request) Successful in 1m53s
CI/CD Pipeline / Telegram Notify Success (push) Has been cancelled
CI/CD Pipeline / Run Tests (push) Has been cancelled
CI/CD Pipeline / Code Quality Checks (push) Has been cancelled
CI/CD Pipeline / Code Quality Checks (pull_request) Failing after 2m54s
CI/CD Pipeline / Telegram Notify Success (pull_request) Has been skipped
feat: expand platform APIs, sources, and test coverage
2026-03-17 12:56:48 +01:00

457 lines
15 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Сериализаторы для приложения парсеров.
Все сериализаторы 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)