feat(parsers): добавлен парсер zakupki.gov.ru с SOAP API интеграцией
Реализована полная интеграция с ЕИС Закупки через SOAP API (FTP доступ закрыт с 01.01.2025). Добавлено: - ZakupkiClient с поддержкой SOAP методов getDocsByOrgRegionRequest и getDocsByReestrNumberRequest - Модель ProcurementRecord (18 полей, 3 индекса) - ProcurementService и ParserLoadLogService для бизнес-логики - Celery задачи parse_procurements и sync_procurements - Админка с цветовой индикацией статусов и фильтрами - 71 тест (unit + E2E с RUN_E2E_TESTS=1) Требования: токен SOAP API через Госуслуги 🤖 Generated with [Qoder][https://qoder.com]
This commit is contained in:
@@ -7,6 +7,7 @@ from apps.parsers.models import (
|
||||
InspectionRecord,
|
||||
ManufacturerRecord,
|
||||
ParserLoadLog,
|
||||
ProcurementRecord,
|
||||
Proxy,
|
||||
)
|
||||
from django.contrib import admin
|
||||
@@ -385,3 +386,137 @@ class InspectionRecordAdmin(admin.ModelAdmin):
|
||||
def has_change_permission(self, request, obj=None):
|
||||
"""Запретить редактирование записей."""
|
||||
return False
|
||||
|
||||
|
||||
@admin.register(ProcurementRecord)
|
||||
class ProcurementRecordAdmin(admin.ModelAdmin):
|
||||
"""Admin для государственных закупок."""
|
||||
|
||||
list_display = [
|
||||
"purchase_number",
|
||||
"purchase_name_short",
|
||||
"customer_inn",
|
||||
"customer_name_short",
|
||||
"max_price",
|
||||
"law_type",
|
||||
"status_badge",
|
||||
"publish_date",
|
||||
"load_batch",
|
||||
]
|
||||
list_filter = [
|
||||
"law_type",
|
||||
"status",
|
||||
"region_code",
|
||||
"load_batch",
|
||||
"created_at",
|
||||
]
|
||||
search_fields = [
|
||||
"purchase_number",
|
||||
"purchase_name",
|
||||
"customer_inn",
|
||||
"customer_ogrn",
|
||||
"customer_name",
|
||||
]
|
||||
readonly_fields = ["created_at", "updated_at", "load_batch"]
|
||||
ordering = ["-created_at"]
|
||||
list_per_page = 100
|
||||
date_hierarchy = "created_at"
|
||||
|
||||
fieldsets = (
|
||||
(
|
||||
"Закупка",
|
||||
{
|
||||
"fields": (
|
||||
"purchase_number",
|
||||
"purchase_name",
|
||||
"purchase_object_info",
|
||||
"law_type",
|
||||
"status",
|
||||
)
|
||||
},
|
||||
),
|
||||
(
|
||||
"Заказчик",
|
||||
{
|
||||
"fields": (
|
||||
"customer_name",
|
||||
"customer_inn",
|
||||
"customer_kpp",
|
||||
"customer_ogrn",
|
||||
)
|
||||
},
|
||||
),
|
||||
(
|
||||
"Финансы",
|
||||
{"fields": ("max_price", "currency_code", "placement_method")},
|
||||
),
|
||||
(
|
||||
"Сроки",
|
||||
{"fields": ("publish_date", "end_date")},
|
||||
),
|
||||
(
|
||||
"Дополнительно",
|
||||
{"fields": ("region_code", "href"), "classes": ("collapse",)},
|
||||
),
|
||||
(
|
||||
"Системное",
|
||||
{
|
||||
"fields": (
|
||||
"load_batch",
|
||||
"data_year",
|
||||
"data_month",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
),
|
||||
"classes": ("collapse",),
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
def purchase_name_short(self, obj):
|
||||
"""Сокращённое наименование закупки."""
|
||||
name = obj.purchase_name or ""
|
||||
return name[:50] + "..." if len(name) > 50 else name
|
||||
|
||||
purchase_name_short.short_description = "Наименование"
|
||||
purchase_name_short.admin_order_field = "purchase_name"
|
||||
|
||||
def customer_name_short(self, obj):
|
||||
"""Сокращённое наименование заказчика."""
|
||||
name = obj.customer_name or ""
|
||||
return name[:30] + "..." if len(name) > 30 else name
|
||||
|
||||
customer_name_short.short_description = "Заказчик"
|
||||
customer_name_short.admin_order_field = "customer_name"
|
||||
|
||||
def status_badge(self, obj):
|
||||
"""Цветной бейдж статуса."""
|
||||
status = obj.status or ""
|
||||
status_lower = status.lower()
|
||||
|
||||
if "опублик" in status_lower or "подача" in status_lower:
|
||||
color = "#28a745"
|
||||
elif "завершен" in status_lower or "состоял" in status_lower:
|
||||
color = "#17a2b8"
|
||||
elif "отменен" in status_lower or "не состоял" in status_lower:
|
||||
color = "#dc3545"
|
||||
else:
|
||||
color = "#6c757d"
|
||||
|
||||
return format_html(
|
||||
'<span style="color: white; background: {}; padding: 2px 8px; '
|
||||
'border-radius: 3px; font-size: 11px;">{}</span>',
|
||||
color,
|
||||
status[:20] if len(status) > 20 else status,
|
||||
)
|
||||
|
||||
status_badge.short_description = "Статус"
|
||||
status_badge.admin_order_field = "status"
|
||||
|
||||
def has_add_permission(self, request):
|
||||
"""Запретить создание записей вручную."""
|
||||
return False
|
||||
|
||||
def has_change_permission(self, request, obj=None):
|
||||
"""Запретить редактирование записей."""
|
||||
return False
|
||||
|
||||
Reference in New Issue
Block a user