Add initial implementations for forms and organization apps with serializers, factories, and admin configurations
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
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
This commit is contained in:
188
src/apps/form_2/services.py
Normal file
188
src/apps/form_2/services.py
Normal file
@@ -0,0 +1,188 @@
|
||||
"""
|
||||
Сервисы для работы с формой Ф-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)
|
||||
Reference in New Issue
Block a user