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:
@@ -20,6 +20,7 @@ class ParserLoadLog(TimestampMixin, models.Model):
|
||||
INDUSTRIAL = "industrial", _("Промышленное производство")
|
||||
MANUFACTURES = "manufactures", _("Реестр производителей")
|
||||
INSPECTIONS = "inspections", _("Единый реестр проверок")
|
||||
PROCUREMENTS = "procurements", _("Государственные закупки")
|
||||
|
||||
batch_id = models.PositiveIntegerField(
|
||||
_("ID пакета"),
|
||||
@@ -361,3 +362,147 @@ class InspectionRecord(TimestampMixin, models.Model):
|
||||
def __str__(self) -> str:
|
||||
org_name = self.organisation_name[:50] if self.organisation_name else ""
|
||||
return f"{self.registration_number} - {org_name}"
|
||||
|
||||
|
||||
class ProcurementRecord(TimestampMixin, models.Model):
|
||||
"""
|
||||
Государственная закупка из ЕИС zakupki.gov.ru.
|
||||
|
||||
Данные загружаются из Единой информационной системы в сфере закупок.
|
||||
Поддерживает закупки по 44-ФЗ и 223-ФЗ.
|
||||
"""
|
||||
|
||||
load_batch = models.PositiveIntegerField(
|
||||
_("ID пакета загрузки"),
|
||||
db_index=True,
|
||||
help_text=_("Идентификатор пакета загрузки"),
|
||||
)
|
||||
purchase_number = models.CharField(
|
||||
_("реестровый номер"),
|
||||
max_length=100,
|
||||
db_index=True,
|
||||
help_text=_("Реестровый номер закупки"),
|
||||
)
|
||||
purchase_name = models.TextField(
|
||||
_("наименование закупки"),
|
||||
help_text=_("Наименование закупки"),
|
||||
)
|
||||
customer_inn = models.CharField(
|
||||
_("ИНН заказчика"),
|
||||
max_length=20,
|
||||
db_index=True,
|
||||
help_text=_("ИНН заказчика"),
|
||||
)
|
||||
customer_kpp = models.CharField(
|
||||
_("КПП заказчика"),
|
||||
max_length=20,
|
||||
blank=True,
|
||||
help_text=_("КПП заказчика"),
|
||||
)
|
||||
customer_ogrn = models.CharField(
|
||||
_("ОГРН заказчика"),
|
||||
max_length=20,
|
||||
db_index=True,
|
||||
blank=True,
|
||||
help_text=_("ОГРН заказчика"),
|
||||
)
|
||||
customer_name = models.TextField(
|
||||
_("наименование заказчика"),
|
||||
help_text=_("Наименование заказчика"),
|
||||
)
|
||||
max_price = models.CharField(
|
||||
_("НМЦ"),
|
||||
max_length=50,
|
||||
blank=True,
|
||||
help_text=_("Начальная (максимальная) цена контракта"),
|
||||
)
|
||||
currency_code = models.CharField(
|
||||
_("валюта"),
|
||||
max_length=10,
|
||||
default="RUB",
|
||||
help_text=_("Код валюты"),
|
||||
)
|
||||
placement_method = models.CharField(
|
||||
_("способ определения"),
|
||||
max_length=255,
|
||||
blank=True,
|
||||
help_text=_("Способ определения поставщика"),
|
||||
)
|
||||
publish_date = models.CharField(
|
||||
_("дата публикации"),
|
||||
max_length=30,
|
||||
blank=True,
|
||||
help_text=_("Дата публикации извещения"),
|
||||
)
|
||||
end_date = models.CharField(
|
||||
_("дата окончания"),
|
||||
max_length=30,
|
||||
blank=True,
|
||||
help_text=_("Дата окончания подачи заявок"),
|
||||
)
|
||||
status = models.CharField(
|
||||
_("статус"),
|
||||
max_length=100,
|
||||
blank=True,
|
||||
help_text=_("Статус закупки"),
|
||||
)
|
||||
law_type = models.CharField(
|
||||
_("тип закона"),
|
||||
max_length=20,
|
||||
blank=True,
|
||||
db_index=True,
|
||||
help_text=_("Тип закона (44-ФЗ, 223-ФЗ)"),
|
||||
)
|
||||
purchase_object_info = models.TextField(
|
||||
_("объект закупки"),
|
||||
blank=True,
|
||||
help_text=_("Информация об объекте закупки"),
|
||||
)
|
||||
href = models.URLField(
|
||||
_("ссылка"),
|
||||
blank=True,
|
||||
max_length=500,
|
||||
help_text=_("Ссылка на страницу закупки"),
|
||||
)
|
||||
region_code = models.CharField(
|
||||
_("код региона"),
|
||||
max_length=10,
|
||||
blank=True,
|
||||
db_index=True,
|
||||
help_text=_("Код региона"),
|
||||
)
|
||||
data_year = models.PositiveSmallIntegerField(
|
||||
_("год данных"),
|
||||
db_index=True,
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text=_("Год, за который загружены данные"),
|
||||
)
|
||||
data_month = models.PositiveSmallIntegerField(
|
||||
_("месяц данных"),
|
||||
db_index=True,
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text=_("Месяц, за который загружены данные"),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
db_table = "parsers_procurement"
|
||||
verbose_name = _("закупка")
|
||||
verbose_name_plural = _("закупки")
|
||||
ordering = ["-created_at"]
|
||||
indexes = [
|
||||
models.Index(fields=["customer_inn", "purchase_number"]),
|
||||
models.Index(fields=["load_batch", "customer_inn"]),
|
||||
models.Index(fields=["law_type", "data_year", "data_month"]),
|
||||
]
|
||||
constraints = [
|
||||
models.UniqueConstraint(
|
||||
fields=["purchase_number"],
|
||||
name="unique_procurement_purchase_number",
|
||||
),
|
||||
]
|
||||
|
||||
def __str__(self) -> str:
|
||||
name = self.purchase_name[:50] if self.purchase_name else ""
|
||||
return f"{self.purchase_number} - {name}"
|
||||
|
||||
Reference in New Issue
Block a user