Files
state-corp-backend/src/apps/form_2/services.py
Aleksandr Meshchriakov 8ed3e1175c
Some checks failed
CI/CD Pipeline / Code Quality Checks (push) Failing after 5m5s
CI/CD Pipeline / Run Tests (push) Failing after 5m5s
CI/CD Pipeline / Build Docker Images (push) Has been skipped
CI/CD Pipeline / Push to Gitea Registry (push) Has been skipped
CI/CD Pipeline / Deploy to Server (push) Has been skipped
Add initial implementations for forms and organization apps with serializers, factories, and admin configurations
2026-02-17 09:26:08 +01:00

189 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Сервисы для работы с формой Ф-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)