feat(fns): парсер ФНС бухгалтерской отчетности
- Модели FinancialReport и FinancialReportLine
- FNSExcelParser для файлов fin_{id}_{ogrn}.xlsx
- FNSReportService с дедупликацией по хешу файла
- Celery задачи для мониторинга папки (каждые 5 мин)
- API: POST /fns/upload/, GET /fns/reports/
- Django admin интеграция
- 25 unit-тестов
This commit is contained in:
79
src/apps/parsers/migrations/0007_add_fns_models.py
Normal file
79
src/apps/parsers/migrations/0007_add_fns_models.py
Normal file
@@ -0,0 +1,79 @@
|
||||
# Generated by Django 3.2.25 on 2026-02-01 12:59
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('parsers', '0006_add_procurement_model'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='FinancialReport',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True, help_text='Дата и время создания записи', verbose_name='создано')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, help_text='Дата и время последнего обновления', verbose_name='обновлено')),
|
||||
('external_id', models.CharField(db_index=True, help_text='Уникальный идентификатор из имени файла', max_length=50, unique=True, verbose_name='внешний ID')),
|
||||
('ogrn', models.CharField(db_index=True, help_text='ОГРН организации', max_length=15, verbose_name='ОГРН')),
|
||||
('file_name', models.CharField(help_text='Оригинальное имя загруженного файла', max_length=255, verbose_name='имя файла')),
|
||||
('file_hash', models.CharField(help_text='SHA256 хеш файла для дедупликации', max_length=64, unique=True, verbose_name='хеш файла')),
|
||||
('load_batch', models.PositiveIntegerField(db_index=True, help_text='Идентификатор пакета загрузки', verbose_name='ID пакета загрузки')),
|
||||
('status', models.CharField(choices=[('pending', 'Ожидает обработки'), ('processing', 'Обрабатывается'), ('success', 'Успешно'), ('failed', 'Ошибка')], db_index=True, default='pending', help_text='Статус обработки файла', max_length=20, verbose_name='статус')),
|
||||
('error_message', models.TextField(blank=True, help_text='Текст ошибки при неудачной обработке', verbose_name='сообщение об ошибке')),
|
||||
('source', models.CharField(choices=[('file_watch', 'Мониторинг папки'), ('api', 'API загрузка')], help_text='Источник загрузки файла', max_length=20, verbose_name='источник')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'финансовый отчет',
|
||||
'verbose_name_plural': 'финансовые отчеты',
|
||||
'db_table': 'parsers_financial_report',
|
||||
'ordering': ['-created_at'],
|
||||
},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='parserloadlog',
|
||||
name='source',
|
||||
field=models.CharField(choices=[('industrial', 'Промышленное производство'), ('manufactures', 'Реестр производителей'), ('inspections', 'Единый реестр проверок'), ('procurements', 'Государственные закупки'), ('fns_reports', 'Бухгалтерская отчетность ФНС')], db_index=True, help_text='Источник данных', max_length=50, verbose_name='источник'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='FinancialReportLine',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('form_code', models.CharField(db_index=True, help_text='Номер формы отчетности (1, 2, 3, 4, 6)', max_length=10, verbose_name='код формы')),
|
||||
('line_code', models.CharField(db_index=True, help_text='Код строки отчетности (например: 1100, 2110)', max_length=10, verbose_name='код строки')),
|
||||
('line_name', models.CharField(help_text='Наименование строки отчетности', max_length=255, verbose_name='наименование строки')),
|
||||
('year', models.PositiveSmallIntegerField(db_index=True, help_text='Отчетный год', verbose_name='год')),
|
||||
('period_start', models.BigIntegerField(blank=True, help_text='Значение на начало периода (тыс. руб.)', null=True, verbose_name='на начало периода')),
|
||||
('period_end', models.BigIntegerField(blank=True, help_text='Значение на конец периода (тыс. руб.)', null=True, verbose_name='на конец периода')),
|
||||
('report', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='lines', to='parsers.financialreport', verbose_name='отчет')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'строка финансового отчета',
|
||||
'verbose_name_plural': 'строки финансовых отчетов',
|
||||
'db_table': 'parsers_financial_report_line',
|
||||
},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='financialreport',
|
||||
index=models.Index(fields=['ogrn', 'status'], name='parsers_fin_ogrn_defc61_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='financialreport',
|
||||
index=models.Index(fields=['load_batch', 'status'], name='parsers_fin_load_ba_c3516a_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='financialreportline',
|
||||
index=models.Index(fields=['report', 'form_code', 'line_code'], name='parsers_fin_report__867450_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='financialreportline',
|
||||
index=models.Index(fields=['year', 'line_code'], name='parsers_fin_year_b93f1e_idx'),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='financialreportline',
|
||||
constraint=models.UniqueConstraint(fields=('report', 'form_code', 'line_code', 'year'), name='unique_report_line_year'),
|
||||
),
|
||||
]
|
||||
Reference in New Issue
Block a user