Some checks failed
CI/CD Pipeline / Code Quality Checks (push) Failing after 3m50s
CI/CD Pipeline / Run Tests (push) Successful in 3m57s
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
165 lines
6.1 KiB
Python
165 lines
6.1 KiB
Python
"""
|
||
Сервисы для работы с формой Ф-6.
|
||
|
||
Содержит:
|
||
- FormF6Service - CRUD операции
|
||
- FormF6Parser - парсинг Excel
|
||
"""
|
||
|
||
import logging
|
||
from typing import Any
|
||
|
||
from django.db import transaction
|
||
from django.db.models import Count, Max
|
||
|
||
from apps.core.excel import (
|
||
BaseExcelParser,
|
||
ColumnMapping,
|
||
ParseResult,
|
||
RowData,
|
||
)
|
||
from apps.core.reporting import ReportingPeriodParserMixin, VersionedReportServiceMixin
|
||
from apps.core.services import BaseService, BulkOperationsMixin
|
||
from apps.form_6.models import FormF6Record
|
||
from apps.organization.services import OrganizationService
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class FormF6Service(
|
||
BulkOperationsMixin,
|
||
VersionedReportServiceMixin[FormF6Record],
|
||
BaseService[FormF6Record],
|
||
):
|
||
"""Сервис для работы с записями формы Ф-6."""
|
||
|
||
model = FormF6Record
|
||
|
||
@classmethod
|
||
def get_by_organization(cls, organization_id):
|
||
"""Получить записи организации."""
|
||
return cls.get_queryset().filter(organization_id=organization_id)
|
||
|
||
@classmethod
|
||
def get_by_batch(cls, batch_id: int):
|
||
return cls.get_queryset().filter(load_batch=batch_id)
|
||
|
||
@classmethod
|
||
def get_by_load_batch(cls, batch_id: int):
|
||
"""Совместимость со старым API сервиса."""
|
||
return cls.get_by_batch(batch_id)
|
||
|
||
@classmethod
|
||
def delete_by_load_batch(cls, batch_id: int) -> int:
|
||
"""Удалить записи по номеру загрузки."""
|
||
deleted_count, _ = cls.get_queryset().filter(load_batch=batch_id).delete()
|
||
return deleted_count
|
||
|
||
@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 FormF6Parser(ReportingPeriodParserMixin, BaseExcelParser[FormF6Record]):
|
||
"""Парсер Excel файла формы Ф-6 (Возрастная структура оборудования)."""
|
||
|
||
ORG_NAME_COLUMN = 0
|
||
OKPO_COLUMN = 1
|
||
OGRN_COLUMN = 2
|
||
INN_COLUMN = 3
|
||
|
||
def get_column_mappings(self) -> list[ColumnMapping]:
|
||
return [
|
||
# Категоризация
|
||
ColumnMapping(4, "Код строки", "row_code", field_type="str"),
|
||
ColumnMapping(5, "Категория оборудования", "category", field_type="str"),
|
||
# Общие данные
|
||
ColumnMapping(6, "Всего оборудования", "total_equipment", field_type="int"),
|
||
ColumnMapping(
|
||
7, "Отечественное оборудование", "domestic_equipment", field_type="int"
|
||
),
|
||
ColumnMapping(
|
||
8, "Импортное оборудование", "imported_equipment", field_type="int"
|
||
),
|
||
# Возрастная структура
|
||
ColumnMapping(9, "До 5 лет", "age_under_5", field_type="int"),
|
||
ColumnMapping(10, "5-10 лет", "age_5_10", field_type="int"),
|
||
ColumnMapping(11, "10-15 лет", "age_10_15", field_type="int"),
|
||
ColumnMapping(12, "15-20 лет", "age_15_20", field_type="int"),
|
||
ColumnMapping(13, "Свыше 20 лет", "age_over_20", field_type="int"),
|
||
# С ЧПУ
|
||
ColumnMapping(14, "С ЧПУ всего", "cnc_total", field_type="int"),
|
||
ColumnMapping(15, "С ЧПУ до 5 лет", "cnc_under_5", field_type="int"),
|
||
ColumnMapping(16, "С ЧПУ 5-10 лет", "cnc_5_10", field_type="int"),
|
||
ColumnMapping(17, "С ЧПУ 10-15 лет", "cnc_10_15", field_type="int"),
|
||
ColumnMapping(18, "С ЧПУ 15-20 лет", "cnc_15_20", field_type="int"),
|
||
ColumnMapping(19, "С ЧПУ свыше 20 лет", "cnc_over_20", field_type="int"),
|
||
# Показатели
|
||
ColumnMapping(
|
||
20, "Средняя сменность работы", "avg_shift_work", field_type="decimal"
|
||
),
|
||
ColumnMapping(
|
||
21, "Коэффициент загрузки", "utilization_rate", field_type="decimal"
|
||
),
|
||
ColumnMapping(
|
||
22, "Физический износ, %", "physical_wear_percent", field_type="decimal"
|
||
),
|
||
# Потребности
|
||
ColumnMapping(
|
||
23,
|
||
"Рабочие места без оборудования",
|
||
"workplaces_without_equipment",
|
||
field_type="int",
|
||
),
|
||
ColumnMapping(
|
||
24, "Оборудование к замене", "equipment_to_replace", field_type="int"
|
||
),
|
||
]
|
||
|
||
def get_next_batch_id(self) -> int:
|
||
return FormF6Service.get_next_batch_id()
|
||
|
||
@transaction.atomic
|
||
def create_record(
|
||
self,
|
||
row_data: RowData | dict[str, Any],
|
||
batch_id: int | None = None,
|
||
) -> FormF6Record:
|
||
row_data = self._normalize_row_data(row_data)
|
||
batch_id = batch_id or getattr(self, "load_batch", self.get_next_batch_id())
|
||
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 "",
|
||
},
|
||
)
|
||
return FormF6Service.create_versioned_record(
|
||
organization=org,
|
||
load_batch=batch_id,
|
||
report_year=self.report_year,
|
||
report_quarter=self.report_quarter,
|
||
**row_data.fields,
|
||
)
|
||
|
||
|
||
def parse_form_f6_file(
|
||
file,
|
||
*,
|
||
report_year: int,
|
||
report_quarter: int | None = None,
|
||
) -> ParseResult:
|
||
parser = FormF6Parser(report_year=report_year, report_quarter=report_quarter)
|
||
return parser.parse(file)
|