feat(api): полное API для всех парсеров с документацией

This commit is contained in:
2026-02-01 15:19:21 +01:00
parent eacb1527c4
commit efa5c3ae34
8 changed files with 840 additions and 141 deletions

View File

@@ -1,13 +1,164 @@
"""
Сериализаторы для приложения парсеров.
Все сериализаторы read-only, так как данные загружаются только через парсеры.
"""
from apps.parsers.models import FinancialReport, FinancialReportLine
from apps.parsers.models import (
FinancialReport,
FinancialReportLine,
IndustrialCertificateRecord,
InspectionRecord,
ManufacturerRecord,
ParserLoadLog,
ProcurementRecord,
Proxy,
)
from rest_framework import serializers
# =============================================================================
# Минпромторг - Сертификаты промышленного производства
# =============================================================================
class IndustrialCertificateSerializer(serializers.ModelSerializer):
"""
Сертификат промышленного производства РФ.
Данные загружаются из Минпромторга.
"""
class Meta:
model = IndustrialCertificateRecord
fields = [
"id",
"load_batch",
"issue_date",
"certificate_number",
"expiry_date",
"certificate_file_url",
"organisation_name",
"inn",
"ogrn",
"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",
"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",
"end_date",
"status",
"legal_basis",
"result",
"is_federal_law_248",
"data_year",
"data_month",
"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",
"currency_code",
"placement_method",
"publish_date",
"end_date",
"status",
"law_type",
"purchase_object_info",
"href",
"region_code",
"data_year",
"data_month",
"created_at",
"updated_at",
]
read_only_fields = fields
# =============================================================================
# ФНС - Бухгалтерская отчетность
# =============================================================================
class FinancialReportLineSerializer(serializers.ModelSerializer):
"""Сериализатор строки финансового отчета."""
"""Строка финансового отчета."""
class Meta:
model = FinancialReportLine
@@ -23,7 +174,11 @@ class FinancialReportLineSerializer(serializers.ModelSerializer):
class FinancialReportSerializer(serializers.ModelSerializer):
"""Сериализатор финансового отчета."""
"""
Финансовый отчет ФНС.
Данные загружаются из Excel файлов.
"""
lines_count = serializers.SerializerMethodField()
@@ -50,7 +205,7 @@ class FinancialReportSerializer(serializers.ModelSerializer):
class FinancialReportDetailSerializer(FinancialReportSerializer):
"""Сериализатор финансового отчета с детализацией строк."""
"""Финансовый отчет с детализацией строк."""
lines = FinancialReportLineSerializer(many=True, read_only=True)
@@ -59,7 +214,11 @@ class FinancialReportDetailSerializer(FinancialReportSerializer):
class FNSFileUploadSerializer(serializers.Serializer):
"""Сериализатор для загрузки файлов FNS."""
"""
Сериализатор для загрузки файлов FNS.
Принимает список Excel файлов в формате fin_{id}_{ogrn}.xlsx
"""
files = serializers.ListField(
child=serializers.FileField(),
@@ -81,3 +240,55 @@ class FNSFileUploadSerializer(serializers.Serializer):
)
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

View File

@@ -1,10 +1,66 @@
from apps.parsers.views import FinancialReportViewSet, FNSReportUploadView
"""
URL конфигурация для приложения парсеров.
Все эндпоинты только для чтения (GET, GET list).
"""
from apps.parsers.views import (
FinancialReportViewSet,
FNSReportUploadView,
IndustrialCertificateViewSet,
InspectionViewSet,
ManufacturerViewSet,
ParserLoadLogViewSet,
ProcurementViewSet,
ProxyViewSet,
)
from django.urls import include, path
from rest_framework.routers import DefaultRouter
app_name = "parsers"
# FNS router
# =============================================================================
# Минпромторг: /api/v1/minpromtorg/
# =============================================================================
minpromtorg_router = DefaultRouter()
minpromtorg_router.register(
r"certificates", IndustrialCertificateViewSet, basename="certificates"
)
minpromtorg_router.register(
r"manufacturers", ManufacturerViewSet, basename="manufacturers"
)
minpromtorg_urlpatterns = [
path("", include(minpromtorg_router.urls)),
]
# =============================================================================
# Единый реестр проверок: /api/v1/proverki/
# =============================================================================
proverki_router = DefaultRouter()
proverki_router.register(r"", InspectionViewSet, basename="inspections")
proverki_urlpatterns = [
path("", include(proverki_router.urls)),
]
# =============================================================================
# Государственные закупки: /api/v1/zakupki/
# =============================================================================
zakupki_router = DefaultRouter()
zakupki_router.register(r"", ProcurementViewSet, basename="procurements")
zakupki_urlpatterns = [
path("", include(zakupki_router.urls)),
]
# =============================================================================
# ФНС - Бухгалтерская отчетность: /api/v1/fns/
# =============================================================================
fns_router = DefaultRouter()
fns_router.register(r"reports", FinancialReportViewSet, basename="fns-reports")
@@ -13,4 +69,20 @@ fns_urlpatterns = [
path("", include(fns_router.urls)),
]
# =============================================================================
# Системные (логи, прокси): /api/v1/system/
# =============================================================================
system_router = DefaultRouter()
system_router.register(r"logs", ParserLoadLogViewSet, basename="parser-logs")
system_router.register(r"proxies", ProxyViewSet, basename="proxies")
system_urlpatterns = [
path("", include(system_router.urls)),
]
# =============================================================================
# Legacy urlpatterns (пусто, используется app_name)
# =============================================================================
urlpatterns = []

View File

@@ -1,53 +1,296 @@
"""
Views для приложения парсеров.
Все ViewSets только для чтения (GET, GET list).
Добавление и удаление данных - через парсеры и админку.
"""
import hashlib
from pathlib import Path
from apps.parsers.models import FinancialReport
from apps.parsers.models import (
FinancialReport,
IndustrialCertificateRecord,
InspectionRecord,
ManufacturerRecord,
ParserLoadLog,
ProcurementRecord,
Proxy,
)
from apps.parsers.serializers import (
FinancialReportDetailSerializer,
FinancialReportSerializer,
FNSFileUploadSerializer,
IndustrialCertificateSerializer,
InspectionSerializer,
ManufacturerSerializer,
ParserLoadLogSerializer,
ProcurementSerializer,
ProxySerializer,
)
from apps.parsers.tasks import process_fns_file
from django.conf import settings
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
from rest_framework import status
from rest_framework.parsers import MultiPartParser
from rest_framework.permissions import IsAdminUser, IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.viewsets import ReadOnlyModelViewSet
# =============================================================================
# Swagger Tags (для группировки в документации)
# =============================================================================
MINPROMTORG_TAG = "Минпромторг"
PROVERKI_TAG = "Единый реестр проверок"
ZAKUPKI_TAG = "Государственные закупки"
FNS_TAG = "ФНС - Бухгалтерская отчетность"
SYSTEM_TAG = "Системные"
# =============================================================================
# Минпромторг - Сертификаты промышленного производства
# =============================================================================
class IndustrialCertificateViewSet(ReadOnlyModelViewSet):
"""
API для просмотра сертификатов промышленного производства.
Данные загружаются из Минпромторга.
Только чтение - добавление через парсер/админку.
"""
queryset = IndustrialCertificateRecord.objects.all().order_by("-created_at")
serializer_class = IndustrialCertificateSerializer
permission_classes = [IsAuthenticated]
filterset_fields = ["inn", "ogrn", "certificate_number", "load_batch"]
search_fields = ["organisation_name", "certificate_number", "inn", "ogrn"]
@swagger_auto_schema(
tags=[MINPROMTORG_TAG],
operation_summary="Список сертификатов",
operation_description=(
"Возвращает список сертификатов промышленного производства.\n"
"Поддерживает фильтрацию по: inn, ogrn, certificate_number, load_batch.\n"
"Поддерживает поиск по: organisation_name, certificate_number, inn, ogrn."
),
)
def list(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs)
@swagger_auto_schema(
tags=[MINPROMTORG_TAG],
operation_summary="Детали сертификата",
operation_description="Возвращает информацию о конкретном сертификате.",
)
def retrieve(self, request, *args, **kwargs):
return super().retrieve(request, *args, **kwargs)
# =============================================================================
# Минпромторг - Реестр производителей
# =============================================================================
class ManufacturerViewSet(ReadOnlyModelViewSet):
"""
API для просмотра реестра производителей.
Данные загружаются из Минпромторга.
Только чтение - добавление через парсер/админку.
"""
queryset = ManufacturerRecord.objects.all().order_by("-created_at")
serializer_class = ManufacturerSerializer
permission_classes = [IsAuthenticated]
filterset_fields = ["inn", "ogrn", "load_batch"]
search_fields = ["full_legal_name", "inn", "ogrn", "address"]
@swagger_auto_schema(
tags=[MINPROMTORG_TAG],
operation_summary="Список производителей",
operation_description=(
"Возвращает список производителей из реестра Минпромторга.\n"
"Поддерживает фильтрацию по: inn, ogrn, load_batch.\n"
"Поддерживает поиск по: full_legal_name, inn, ogrn, address."
),
)
def list(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs)
@swagger_auto_schema(
tags=[MINPROMTORG_TAG],
operation_summary="Детали производителя",
operation_description="Возвращает информацию о конкретном производителе.",
)
def retrieve(self, request, *args, **kwargs):
return super().retrieve(request, *args, **kwargs)
# =============================================================================
# Единый реестр проверок (proverki.gov.ru)
# =============================================================================
class InspectionViewSet(ReadOnlyModelViewSet):
"""
API для просмотра проверок из Единого реестра проверок.
Данные из ФГИС "Единый реестр проверок" (Генпрокуратура РФ).
Поддерживает ФЗ-294 (традиционные) и ФЗ-248 (новые с 2021).
Только чтение - добавление через парсер/админку.
"""
queryset = InspectionRecord.objects.all().order_by("-created_at")
serializer_class = InspectionSerializer
permission_classes = [IsAuthenticated]
filterset_fields = [
"inn",
"ogrn",
"registration_number",
"is_federal_law_248",
"data_year",
"data_month",
"load_batch",
]
search_fields = [
"organisation_name",
"registration_number",
"inn",
"ogrn",
"control_authority",
]
@swagger_auto_schema(
tags=[PROVERKI_TAG],
operation_summary="Список проверок",
operation_description=(
"Возвращает список проверок из Единого реестра.\n"
"Поддерживает фильтрацию по: inn, ogrn, registration_number, "
"is_federal_law_248, data_year, data_month, load_batch.\n"
"Поддерживает поиск по: organisation_name, registration_number, "
"inn, ogrn, control_authority."
),
)
def list(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs)
@swagger_auto_schema(
tags=[PROVERKI_TAG],
operation_summary="Детали проверки",
operation_description="Возвращает информацию о конкретной проверке.",
)
def retrieve(self, request, *args, **kwargs):
return super().retrieve(request, *args, **kwargs)
# =============================================================================
# Государственные закупки (zakupki.gov.ru)
# =============================================================================
class ProcurementViewSet(ReadOnlyModelViewSet):
"""
API для просмотра государственных закупок.
Данные из ЕИС zakupki.gov.ru.
Поддерживает 44-ФЗ и 223-ФЗ.
Только чтение - добавление через парсер/админку.
"""
queryset = ProcurementRecord.objects.all().order_by("-created_at")
serializer_class = ProcurementSerializer
permission_classes = [IsAuthenticated]
filterset_fields = [
"customer_inn",
"customer_ogrn",
"purchase_number",
"law_type",
"status",
"region_code",
"data_year",
"data_month",
"load_batch",
]
search_fields = [
"purchase_name",
"purchase_number",
"customer_name",
"customer_inn",
"customer_ogrn",
]
@swagger_auto_schema(
tags=[ZAKUPKI_TAG],
operation_summary="Список закупок",
operation_description=(
"Возвращает список государственных закупок.\n"
"Поддерживает фильтрацию по: customer_inn, customer_ogrn, "
"purchase_number, law_type, status, region_code, "
"data_year, data_month, load_batch.\n"
"Поддерживает поиск по: purchase_name, purchase_number, "
"customer_name, customer_inn, customer_ogrn."
),
)
def list(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs)
@swagger_auto_schema(
tags=[ZAKUPKI_TAG],
operation_summary="Детали закупки",
operation_description="Возвращает информацию о конкретной закупке.",
)
def retrieve(self, request, *args, **kwargs):
return super().retrieve(request, *args, **kwargs)
# =============================================================================
# ФНС - Бухгалтерская отчетность
# =============================================================================
class FinancialReportViewSet(ReadOnlyModelViewSet):
"""
API для просмотра финансовых отчетов ФНС.
list:
Получить список всех отчетов.
Поддерживает фильтрацию по: ogrn, external_id, status.
retrieve:
Получить детальную информацию об отчете, включая все строки.
Данные загружаются из Excel файлов (fin_{id}_{ogrn}.xlsx).
Только чтение - добавление через загрузку файлов.
"""
queryset = FinancialReport.objects.all().order_by("-created_at")
filterset_fields = ["ogrn", "external_id", "status", "source"]
permission_classes = [IsAuthenticated]
filterset_fields = ["ogrn", "external_id", "status", "source", "load_batch"]
search_fields = ["ogrn", "external_id", "file_name"]
def get_serializer_class(self):
if self.action == "retrieve":
return FinancialReportDetailSerializer
return FinancialReportSerializer
@swagger_auto_schema(tags=[FNS_TAG])
@swagger_auto_schema(
tags=[FNS_TAG],
operation_summary="Список отчетов",
operation_description=(
"Возвращает список финансовых отчетов ФНС.\n"
"Поддерживает фильтрацию по: ogrn, external_id, status, "
"source, load_batch.\n"
"Поддерживает поиск по: ogrn, external_id, file_name."
),
)
def list(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs)
@swagger_auto_schema(tags=[FNS_TAG])
@swagger_auto_schema(
tags=[FNS_TAG],
operation_summary="Детали отчета",
operation_description=(
"Возвращает детальную информацию об отчете, "
"включая все строки бухгалтерской отчетности."
),
)
def retrieve(self, request, *args, **kwargs):
return super().retrieve(request, *args, **kwargs)
@@ -56,25 +299,59 @@ class FNSReportUploadView(APIView):
"""
API для загрузки файлов бухгалтерской отчетности ФНС.
POST:
Пакетная загрузка файлов.
Файлы сохраняются во временную директорию и ставятся в очередь
на обработку через Celery.
Request:
multipart/form-data с полем 'files' (можно несколько файлов)
Response:
{
"queued": 3,
"skipped": 1,
"task_ids": ["uuid1", "uuid2", "uuid3"]
}
Файлы сохраняются во временную директорию и ставятся в очередь
на обработку через Celery.
"""
parser_classes = [MultiPartParser]
permission_classes = [IsAuthenticated]
@swagger_auto_schema(tags=[FNS_TAG], request_body=FNSFileUploadSerializer)
@swagger_auto_schema(
tags=[FNS_TAG],
operation_summary="Загрузка файлов",
operation_description=(
"Пакетная загрузка файлов бухгалтерской отчетности.\n\n"
"**Формат файла:** fin_{id}_{ogrn}.xlsx\n\n"
"**Ответ:**\n"
"- queued: количество файлов в очереди\n"
"- skipped: количество пропущенных (дубликаты)\n"
"- task_ids: ID задач для отслеживания статуса"
),
manual_parameters=[
openapi.Parameter(
name="files",
in_=openapi.IN_FORM,
type=openapi.TYPE_FILE,
required=True,
description="Файл(ы) для загрузки (fin_*.xlsx)",
),
],
consumes=["multipart/form-data"],
responses={
202: openapi.Response(
description="Файлы приняты в обработку",
schema=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
"queued": openapi.Schema(
type=openapi.TYPE_INTEGER,
description="Количество файлов в очереди",
),
"skipped": openapi.Schema(
type=openapi.TYPE_INTEGER,
description="Количество пропущенных",
),
"task_ids": openapi.Schema(
type=openapi.TYPE_ARRAY,
items=openapi.Schema(type=openapi.TYPE_STRING),
description="ID задач Celery",
),
},
),
),
400: "Ошибка валидации файлов",
},
)
def post(self, request):
serializer = FNSFileUploadSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
@@ -120,3 +397,76 @@ class FNSReportUploadView(APIView):
},
status=status.HTTP_202_ACCEPTED,
)
# =============================================================================
# Системные (логи загрузок, прокси)
# =============================================================================
class ParserLoadLogViewSet(ReadOnlyModelViewSet):
"""
API для просмотра логов загрузок парсеров.
Информация о каждой загрузке данных из внешних источников.
Только для администраторов.
"""
queryset = ParserLoadLog.objects.all().order_by("-created_at")
serializer_class = ParserLoadLogSerializer
permission_classes = [IsAdminUser]
filterset_fields = ["source", "status", "batch_id"]
@swagger_auto_schema(
tags=[SYSTEM_TAG],
operation_summary="Список логов загрузок",
operation_description=(
"Возвращает историю загрузок данных парсерами.\n"
"Доступно только администраторам.\n"
"Поддерживает фильтрацию по: source, status, batch_id."
),
)
def list(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs)
@swagger_auto_schema(
tags=[SYSTEM_TAG],
operation_summary="Детали загрузки",
operation_description="Возвращает информацию о конкретной загрузке.",
)
def retrieve(self, request, *args, **kwargs):
return super().retrieve(request, *args, **kwargs)
class ProxyViewSet(ReadOnlyModelViewSet):
"""
API для просмотра списка прокси-серверов.
Используется для отладки и мониторинга парсеров.
Только для администраторов.
"""
queryset = Proxy.objects.all().order_by("-last_used_at")
serializer_class = ProxySerializer
permission_classes = [IsAdminUser]
filterset_fields = ["is_active"]
@swagger_auto_schema(
tags=[SYSTEM_TAG],
operation_summary="Список прокси",
operation_description=(
"Возвращает список прокси-серверов для парсеров.\n"
"Доступно только администраторам.\n"
"Поддерживает фильтрацию по: is_active."
),
)
def list(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs)
@swagger_auto_schema(
tags=[SYSTEM_TAG],
operation_summary="Детали прокси",
operation_description="Возвращает информацию о конкретном прокси.",
)
def retrieve(self, request, *args, **kwargs):
return super().retrieve(request, *args, **kwargs)