""" Сервисы для работы с формой Ф-2. Содержит: - FormF2Service - CRUD операции - FormF2Parser - парсинг Excel """ import logging from typing import Any from apps.core.excel import ( BaseExcelParser, ColumnMapping, ParseResult, RowData, ) from apps.core.services import BaseService, BulkOperationsMixin from apps.form_2.models import FormF2Record from apps.organization.services import OrganizationService from django.db import transaction from django.db.models import Count, Max logger = logging.getLogger(__name__) class FormF2Service(BulkOperationsMixin, BaseService[FormF2Record]): """ Сервис для работы с записями формы Ф-2. Методы: get_by_batch: Получить записи по номеру загрузки get_next_batch_id: Получить следующий номер загрузки get_batches: Получить список загрузок """ model = FormF2Record @classmethod def get_by_batch(cls, batch_id: int): """Получить записи по номеру загрузки.""" return cls.get_queryset().filter(load_batch=batch_id) @classmethod def get_next_batch_id(cls) -> int: """Получить следующий номер загрузки.""" max_batch = cls.model.objects.aggregate(max_batch=Max("load_batch")) return (max_batch["max_batch"] or 0) + 1 @classmethod def get_batches(cls) -> list[dict[str, Any]]: """Получить список загрузок с количеством записей.""" return list( cls.model.objects.values("load_batch") .annotate( count=Count("id"), created_at=Max("created_at"), ) .order_by("-load_batch") ) class FormF2Parser(BaseExcelParser[FormF2Record]): """ Парсер Excel файла формы Ф-2 (Бухгалтерский баланс). Колонки: 0: Наименование организации 1: ОКПО 2: ОГРН 3: ИНН 4+: Данные баланса """ ORG_NAME_COLUMN = 0 OKPO_COLUMN = 1 OGRN_COLUMN = 2 INN_COLUMN = 3 def get_column_mappings(self) -> list[ColumnMapping]: """Маппинг колонок Excel на поля модели.""" return [ # I. Внеоборотные активы ColumnMapping(4, "Нематериальные активы", "intangible_assets", field_type="decimal"), ColumnMapping(5, "Результаты исследований и разработок", "rd_results", field_type="decimal"), ColumnMapping(6, "Нематериальные поисковые активы", "intangible_search_assets", field_type="decimal"), ColumnMapping(7, "Материальные поисковые активы", "tangible_search_assets", field_type="decimal"), ColumnMapping(8, "Основные средства", "fixed_assets", field_type="decimal"), ColumnMapping(9, "Доходные вложения в материальные ценности", "profitable_investments", field_type="decimal"), ColumnMapping(10, "Финансовые вложения (внеоборотные)", "financial_investments_non_current", field_type="decimal"), ColumnMapping(11, "Отложенные налоговые активы", "deferred_tax_assets", field_type="decimal"), ColumnMapping(12, "Прочие внеоборотные активы", "other_non_current_assets", field_type="decimal"), ColumnMapping(13, "Итого внеоборотные активы", "total_non_current_assets", field_type="decimal"), # II. Оборотные активы ColumnMapping(14, "Запасы", "inventories", field_type="decimal"), ColumnMapping(15, "НДС по приобретённым ценностям", "vat_on_acquired_assets", field_type="decimal"), ColumnMapping(16, "Дебиторская задолженность", "receivables", field_type="decimal"), ColumnMapping(17, "Финансовые вложения (оборотные)", "financial_investments_current", field_type="decimal"), ColumnMapping(18, "Денежные средства и эквиваленты", "cash_and_equivalents", field_type="decimal"), ColumnMapping(19, "Прочие оборотные активы", "other_current_assets", field_type="decimal"), ColumnMapping(20, "Итого оборотные активы", "total_current_assets", field_type="decimal"), ColumnMapping(21, "Баланс (актив)", "total_assets", field_type="decimal"), # III. Капитал и резервы ColumnMapping(22, "Уставный капитал", "authorized_capital", field_type="decimal"), ColumnMapping(23, "Собственные акции, выкупленные у акционеров", "own_shares_bought_back", field_type="decimal"), ColumnMapping(24, "Переоценка внеоборотных активов", "revaluation_of_non_current_assets", field_type="decimal"), ColumnMapping(25, "Добавочный капитал", "additional_capital", field_type="decimal"), ColumnMapping(26, "Резервный капитал", "reserve_capital", field_type="decimal"), ColumnMapping(27, "Нераспределённая прибыль", "retained_earnings", field_type="decimal"), ColumnMapping(28, "Итого капитал и резервы", "total_equity", field_type="decimal"), # IV. Долгосрочные обязательства ColumnMapping(29, "Заёмные средства (долгосрочные)", "borrowings_non_current", field_type="decimal"), ColumnMapping(30, "Отложенные налоговые обязательства", "deferred_tax_liabilities", field_type="decimal"), ColumnMapping(31, "Оценочные обязательства (долгосрочные)", "estimated_liabilities_non_current", field_type="decimal"), ColumnMapping(32, "Прочие обязательства (долгосрочные)", "other_liabilities_non_current", field_type="decimal"), ColumnMapping(33, "Итого долгосрочные обязательства", "total_non_current_liabilities", field_type="decimal"), # V. Краткосрочные обязательства ColumnMapping(34, "Заёмные средства (краткосрочные)", "borrowings_current", field_type="decimal"), ColumnMapping(35, "Кредиторская задолженность", "payables", field_type="decimal"), ColumnMapping(36, "Доходы будущих периодов", "deferred_income", field_type="decimal"), ColumnMapping(37, "Оценочные обязательства (краткосрочные)", "estimated_liabilities_current", field_type="decimal"), ColumnMapping(38, "Прочие обязательства (краткосрочные)", "other_liabilities_current", field_type="decimal"), ColumnMapping(39, "Итого краткосрочные обязательства", "total_current_liabilities", field_type="decimal"), ColumnMapping(40, "Баланс (пассив)", "total_liabilities", field_type="decimal"), # Отчёт о финансовых результатах ColumnMapping(41, "Выручка", "revenue", field_type="decimal"), ColumnMapping(42, "Себестоимость продаж", "cost_of_sales", field_type="decimal"), ColumnMapping(43, "Валовая прибыль", "gross_profit", field_type="decimal"), ColumnMapping(44, "Коммерческие расходы", "selling_expenses", field_type="decimal"), ColumnMapping(45, "Управленческие расходы", "administrative_expenses", field_type="decimal"), ColumnMapping(46, "Прибыль от продаж", "profit_from_sales", field_type="decimal"), ColumnMapping(47, "Проценты к получению", "interest_receivable", field_type="decimal"), ColumnMapping(48, "Проценты к уплате", "interest_payable", field_type="decimal"), ColumnMapping(49, "Прочие доходы", "other_income", field_type="decimal"), ColumnMapping(50, "Прочие расходы", "other_expenses", field_type="decimal"), ColumnMapping(51, "Прибыль до налогообложения", "profit_before_tax", field_type="decimal"), ColumnMapping(52, "Текущий налог на прибыль", "income_tax", field_type="decimal"), ColumnMapping(53, "Чистая прибыль", "net_profit", field_type="decimal"), # Дополнительные показатели ColumnMapping(54, "EBITDA", "ebitda", field_type="decimal"), ColumnMapping(55, "Амортизация", "depreciation", field_type="decimal"), ColumnMapping(56, "Оборотный капитал", "working_capital", field_type="decimal"), ColumnMapping(57, "Чистый долг", "net_debt", field_type="decimal"), # Прошлый период ColumnMapping(58, "Баланс (актив) - прошлый период", "total_assets_prev", field_type="decimal"), ColumnMapping(59, "Баланс (пассив) - прошлый период", "total_liabilities_prev", field_type="decimal"), ColumnMapping(60, "Выручка - прошлый период", "revenue_prev", field_type="decimal"), ColumnMapping(61, "Чистая прибыль - прошлый период", "net_profit_prev", field_type="decimal"), ] def get_next_batch_id(self) -> int: """Получить следующий номер загрузки.""" return FormF2Service.get_next_batch_id() @transaction.atomic def create_record(self, row_data: RowData, batch_id: int) -> FormF2Record: """Создать запись формы Ф-2.""" org, _ = OrganizationService.get_or_create_by_inn( inn=row_data.inn, defaults={ "name": row_data.organization_name, "ogrn": row_data.ogrn or "", "okpo": row_data.okpo or "", "kpp": row_data.kpp or "", }, ) record = FormF2Record.objects.create( organization=org, load_batch=batch_id, **row_data.fields, ) return record def parse_form_f2_file(file) -> ParseResult: """ Парсит Excel файл формы Ф-2. Args: file: Загруженный файл Returns: ParseResult с результатами парсинга """ parser = FormF2Parser() return parser.parse(file)