feat(admin): expand exchange admin and unify admin UX
Some checks failed
CI/CD Pipeline / Code Quality Checks (pull_request) Failing after 2m39s
CI/CD Pipeline / Run Tests (pull_request) Successful in 3m0s
CI/CD Pipeline / Run API Inventory E2E Tests (pull_request) Successful in 35s
CI/CD Pipeline / Telegram Notify Success (pull_request) Has been skipped

This commit is contained in:
2026-03-24 13:58:24 +01:00
parent 559b9bc5ef
commit c98ba76081
33 changed files with 2915 additions and 209 deletions

View File

@@ -171,41 +171,52 @@ class ParserLoadLogAdmin(admin.ModelAdmin):
list_display = [
"id",
"source",
"batch_id",
"source_title",
"status_badge",
"records_count",
"created_at",
]
list_filter = ["source", "status", "created_at"]
search_fields = ["batch_id", "error_message"]
search_fields = ["error_message"]
readonly_fields = ["created_at", "updated_at"]
ordering = ["-created_at"]
list_per_page = 50
date_hierarchy = "created_at"
fieldsets = (
("Основное", {"fields": ("source", "batch_id", "status")}),
("Основное", {"fields": ("source", "status")}),
("Результат", {"fields": ("records_count", "error_message")}),
("Даты", {"fields": ("created_at", "updated_at"), "classes": ("collapse",)}),
(
"Служебное",
{
"fields": ("batch_id", "created_at", "updated_at"),
"classes": ("collapse",),
},
),
)
def source_title(self, obj):
"""Полное название источника."""
return obj.get_source_display()
source_title.short_description = "Источник"
source_title.admin_order_field = "source"
def status_badge(self, obj):
"""Цветной бейдж статуса."""
colors = {
"success": "#28a745",
"failed": "#dc3545",
"in_progress": "#ffc107",
"pending": "#6c757d",
ParserLoadLog.Status.SUCCESS: "#28a745",
ParserLoadLog.Status.FAILED: "#dc3545",
ParserLoadLog.Status.IN_PROGRESS: "#ffc107",
ParserLoadLog.Status.PENDING: "#6c757d",
ParserLoadLog.Status.SKIPPED: "#17a2b8",
}
color = colors.get(obj.status, "#6c757d")
return format_html(
'<span style="color: white; background: {}; padding: 3px 10px; '
'border-radius: 3px;">{}</span>',
color,
obj.get_status_display()
if hasattr(obj, "get_status_display")
else obj.status,
obj.get_status_display(),
)
status_badge.short_description = "Статус"
@@ -917,7 +928,7 @@ class FinancialReportAdmin(admin.ModelAdmin):
context = {
**self.admin_site.each_context(request),
"opts": self.model._meta,
"title": "Загрузка Excel отчетности ФНС",
"title": "Загрузка Excel бухгалтерской отчетности ФНС",
"changelist_url": changelist_url,
}
return TemplateResponse(
@@ -961,7 +972,7 @@ class FinancialReportAdmin(admin.ModelAdmin):
context = {
**self.admin_site.each_context(request),
"opts": self.model._meta,
"title": "Загрузка ZIP отчетности ФНС",
"title": "Загрузка ZIP бухгалтерской отчетности ФНС",
"changelist_url": changelist_url,
}
return TemplateResponse(

View File

@@ -0,0 +1,51 @@
# Generated by Django 3.2.25 on 2026-03-24 11:20
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('parsers', '0015_add_proxy_metadata'),
]
operations = [
migrations.AlterModelOptions(
name='financialreport',
options={'ordering': ['-created_at'], 'verbose_name': 'запись бухгалтерской отчетности ФНС', 'verbose_name_plural': 'Бухгалтерская отчетность ФНС'},
),
migrations.AlterModelOptions(
name='industrialcertificaterecord',
options={'ordering': ['-created_at'], 'verbose_name': 'сертификат промышленного производства', 'verbose_name_plural': 'Сертификаты промышленного производства'},
),
migrations.AlterModelOptions(
name='industrialproductrecord',
options={'ordering': ['-created_at'], 'verbose_name': 'запись реестра промышленной продукции', 'verbose_name_plural': 'Реестр промышленной продукции'},
),
migrations.AlterModelOptions(
name='inspectionrecord',
options={'ordering': ['-created_at'], 'verbose_name': 'запись единого реестра проверок', 'verbose_name_plural': 'Единый реестр проверок'},
),
migrations.AlterModelOptions(
name='manufacturerrecord',
options={'ordering': ['-created_at'], 'verbose_name': 'запись реестра производителей', 'verbose_name_plural': 'Реестр производителей'},
),
migrations.AlterModelOptions(
name='parserloadlog',
options={'ordering': ['-created_at'], 'verbose_name': 'обновление источника', 'verbose_name_plural': 'история обновлений'},
),
migrations.AlterModelOptions(
name='procurementrecord',
options={'ordering': ['-created_at'], 'verbose_name': 'запись ЕИС закупок', 'verbose_name_plural': 'Единая информационная система закупок'},
),
migrations.AlterField(
model_name='parserloadlog',
name='source',
field=models.CharField(choices=[('industrial', 'Сертификаты промышленного производства'), ('industrial_products', 'Реестр промышленной продукции'), ('manufactures', 'Реестр производителей'), ('inspections', 'Единый реестр проверок'), ('procurements', 'Единая информационная система закупок'), ('fns_reports', 'Бухгалтерская отчетность ФНС')], db_index=True, help_text='Источник данных', max_length=50, verbose_name='источник'),
),
migrations.AlterField(
model_name='parserloadlog',
name='status',
field=models.CharField(choices=[('success', 'Успешно'), ('failed', 'Ошибка'), ('in_progress', 'В процессе'), ('pending', 'В очереди'), ('skipped', 'Пропущено')], default='success', help_text='Статус загрузки', max_length=20, verbose_name='статус'),
),
]

View File

@@ -17,13 +17,20 @@ class ParserLoadLog(TimestampMixin, models.Model):
"""
class Source(models.TextChoices):
INDUSTRIAL = "industrial", _("Промышленное производство")
INDUSTRIAL = "industrial", _("Сертификаты промышленного производства")
INDUSTRIAL_PRODUCTS = "industrial_products", _("Реестр промышленной продукции")
MANUFACTURES = "manufactures", _("Реестр производителей")
INSPECTIONS = "inspections", _("Единый реестр проверок")
PROCUREMENTS = "procurements", _("Государственные закупки")
PROCUREMENTS = "procurements", _("Единая информационная система закупок")
FNS_REPORTS = "fns_reports", _("Бухгалтерская отчетность ФНС")
class Status(models.TextChoices):
SUCCESS = "success", _("Успешно")
FAILED = "failed", _("Ошибка")
IN_PROGRESS = "in_progress", _("В процессе")
PENDING = "pending", _("В очереди")
SKIPPED = "skipped", _("Пропущено")
batch_id = models.PositiveIntegerField(
_("ID пакета"),
db_index=True,
@@ -44,7 +51,8 @@ class ParserLoadLog(TimestampMixin, models.Model):
status = models.CharField(
_("статус"),
max_length=20,
default="success",
choices=Status.choices,
default=Status.SUCCESS,
help_text=_("Статус загрузки"),
)
error_message = models.TextField(
@@ -55,8 +63,8 @@ class ParserLoadLog(TimestampMixin, models.Model):
class Meta:
db_table = "parsers_load_log"
verbose_name = _("лог загрузки")
verbose_name_plural = _("логи загрузок")
verbose_name = _("обновление источника")
verbose_name_plural = _("история обновлений")
ordering = ["-created_at"]
indexes = [
models.Index(fields=["source", "batch_id"]),
@@ -149,8 +157,8 @@ class IndustrialCertificateRecord(TimestampMixin, models.Model):
class Meta:
db_table = "parsers_industrial_certificate"
verbose_name = _("сертификат промпроизводства")
verbose_name_plural = _("сертификаты промпроизводства")
verbose_name = _("сертификат промышленного производства")
verbose_name_plural = _("Сертификаты промышленного производства")
ordering = ["-created_at"]
indexes = [
models.Index(fields=["inn", "certificate_number"]),
@@ -212,8 +220,8 @@ class ManufacturerRecord(TimestampMixin, models.Model):
class Meta:
db_table = "parsers_manufacturer"
verbose_name = _("производитель")
verbose_name_plural = _("производители")
verbose_name = _("запись реестра производителей")
verbose_name_plural = _("Реестр производителей")
ordering = ["-created_at"]
indexes = [
models.Index(fields=["load_batch", "inn"]),
@@ -303,8 +311,8 @@ class IndustrialProductRecord(TimestampMixin, models.Model):
class Meta:
db_table = "parsers_industrial_product"
verbose_name = _("промышленная продукция")
verbose_name_plural = _("промышленная продукция")
verbose_name = _("запись реестра промышленной продукции")
verbose_name_plural = _("Реестр промышленной продукции")
ordering = ["-created_at"]
indexes = [
models.Index(fields=["load_batch", "inn"]),
@@ -507,8 +515,8 @@ class InspectionRecord(TimestampMixin, models.Model):
class Meta:
db_table = "parsers_inspection"
verbose_name = _("проверка")
verbose_name_plural = _("проверки")
verbose_name = _("запись единого реестра проверок")
verbose_name_plural = _("Единый реестр проверок")
ordering = ["-created_at"]
indexes = [
models.Index(fields=["inn", "registration_number"]),
@@ -683,8 +691,8 @@ class ProcurementRecord(TimestampMixin, models.Model):
class Meta:
db_table = "parsers_procurement"
verbose_name = _("закупка")
verbose_name_plural = _("закупки")
verbose_name = _("запись ЕИС закупок")
verbose_name_plural = _("Единая информационная система закупок")
ordering = ["-created_at"]
indexes = [
models.Index(fields=["customer_inn", "purchase_number"]),
@@ -781,8 +789,8 @@ class FinancialReport(TimestampMixin, models.Model):
class Meta:
db_table = "parsers_financial_report"
verbose_name = _("финансовый отчет")
verbose_name_plural = _("финансовые отчеты")
verbose_name = _("запись бухгалтерской отчетности ФНС")
verbose_name_plural = _("Бухгалтерская отчетность ФНС")
ordering = ["-created_at"]
indexes = [
models.Index(fields=["ogrn", "status"]),