правки по ревью 2
This commit is contained in:
parent
abe1d890d0
commit
2c0a2ad939
@ -13,6 +13,7 @@
|
||||
|
||||
import uuid
|
||||
from collections.abc import Collection, Sequence
|
||||
from dataclasses import dataclass
|
||||
from typing import Literal
|
||||
|
||||
from fastapi.concurrency import run_in_threadpool
|
||||
@ -129,6 +130,16 @@ async def _entities(session: AsyncSession, ids: Collection[uuid.UUID]) -> dict[u
|
||||
return {entity.id: _entity(entity) for entity in found.scalars()}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ConsolidatedOsvBuildResult:
|
||||
"""Свод и данные версии, загруженные для его расчёта."""
|
||||
|
||||
consolidated: domain.Consolidated
|
||||
version: ConsolidationVersion
|
||||
consolidation: Consolidation
|
||||
members: dict[uuid.UUID, domain.Entity]
|
||||
|
||||
|
||||
class ConsolidatedOsvService:
|
||||
"""Чтение сводной ОСВ версии: страница с фильтрами и сводка с пробелами (без блокировок и аудита)."""
|
||||
|
||||
@ -136,14 +147,13 @@ class ConsolidatedOsvService:
|
||||
self._session = session
|
||||
self._scope = scope
|
||||
|
||||
async def build(self, version_id: uuid.UUID) -> tuple[domain.Consolidated, ConsolidationVersion, Consolidation]:
|
||||
"""Свод версии (`domain.build`) вместе с версией и консолидацией; версия вне видимости — 404."""
|
||||
consolidated, version, consolidation, _ = await self.build_with_members(version_id)
|
||||
return consolidated, version, consolidation
|
||||
async def build(self, version_id: uuid.UUID) -> ConsolidatedOsvBuildResult:
|
||||
"""Свод версии и данные его расчёта; версия вне видимости — 404."""
|
||||
return await self.build_with_members(version_id)
|
||||
|
||||
async def build_with_members(
|
||||
self, version_id: uuid.UUID
|
||||
) -> tuple[domain.Consolidated, ConsolidationVersion, Consolidation, dict[uuid.UUID, domain.Entity]]:
|
||||
) -> ConsolidatedOsvBuildResult:
|
||||
"""Свод и участники периметра, загруженные при его расчёте."""
|
||||
found = await repo.get_version(self._session, self._scope, version_id)
|
||||
if found is None:
|
||||
@ -174,12 +184,17 @@ class ConsolidatedOsvService:
|
||||
rows=rows,
|
||||
resolver=index,
|
||||
)
|
||||
return consolidated, version, consolidation, members
|
||||
return ConsolidatedOsvBuildResult(
|
||||
consolidated=consolidated,
|
||||
version=version,
|
||||
consolidation=consolidation,
|
||||
members=members,
|
||||
)
|
||||
|
||||
async def page(self, version_id: uuid.UUID, f: OsvFilters, page: OsvPageParams) -> Page[ConsolidatedRowOut]:
|
||||
"""Страница свода после фильтров: `total` — число строк после фильтра, срез — в памяти (порядок D-16)."""
|
||||
consolidated, _, _ = await self.build(version_id)
|
||||
rows = domain.apply_filter(consolidated.rows, domain_filter(f))
|
||||
result = await self.build(version_id)
|
||||
rows = domain.apply_filter(result.consolidated.rows, domain_filter(f))
|
||||
window = rows[page.offset : page.offset + page.limit]
|
||||
return Page[ConsolidatedRowOut](
|
||||
items=[row_out(row) for row in window], total=len(rows), limit=page.limit, offset=page.offset
|
||||
@ -189,20 +204,20 @@ class ConsolidatedOsvService:
|
||||
"""xlsx сводной ОСВ (spec «Экспорт xlsx», D-14): строки после тех же фильтров, что у `page`, без пагинации, в
|
||||
порядке D-16; лист пробелов — по своду целиком. Имя файла — `svod-osv_<ГК>_<год>-<квартал>_v<n>.xlsx` (без
|
||||
percent-кодирования: оно в маршруте). `audit_event(EXPORT)` пишется после сборки книги, в транзакции запроса."""
|
||||
consolidated, version, consolidation = await self.build(version_id)
|
||||
rows = domain.apply_filter(consolidated.rows, domain_filter(f))
|
||||
content = await run_in_threadpool(build_workbook, consolidated, rows)
|
||||
result = await self.build(version_id)
|
||||
rows = domain.apply_filter(result.consolidated.rows, domain_filter(f))
|
||||
content = await run_in_threadpool(build_workbook, result.consolidated, rows)
|
||||
group_name = (
|
||||
await self._session.execute(
|
||||
select(CompanyGroup.name).where(CompanyGroup.id == consolidation.company_group_id)
|
||||
select(CompanyGroup.name).where(CompanyGroup.id == result.consolidation.company_group_id)
|
||||
)
|
||||
).scalar_one()
|
||||
await audit.record(
|
||||
action=AuditAction.EXPORT,
|
||||
section=AuditSection.EXPORT,
|
||||
object_type="consolidation_version",
|
||||
object_id=version.id,
|
||||
consolidation_version_id=version.id,
|
||||
object_id=result.version.id,
|
||||
consolidation_version_id=result.version.id,
|
||||
after_value={
|
||||
"report": "consolidated_osv",
|
||||
"filters": f.model_dump(mode="json", exclude_none=True),
|
||||
@ -210,27 +225,27 @@ class ConsolidatedOsvService:
|
||||
},
|
||||
)
|
||||
filename = (
|
||||
f"svod-osv_{group_name}_{consolidation.year}-{consolidation.quarter.value}_v{version.version_no}.xlsx"
|
||||
f"svod-osv_{group_name}_{result.consolidation.year}-{result.consolidation.quarter.value}_v{result.version.version_no}.xlsx"
|
||||
)
|
||||
return content, filename
|
||||
|
||||
async def summary(self, version_id: uuid.UUID) -> OsvSummaryOut:
|
||||
"""Сводка: строки, несопоставленные, автосопоставленные по имени/алиасу, ОСВ-документы версии и пробелы."""
|
||||
consolidated, _, _ = await self.build(version_id)
|
||||
rows_total, unmapped_total, auto_matched_total = domain.summary(consolidated)
|
||||
result = await self.build(version_id)
|
||||
rows_total, unmapped_total, auto_matched_total = domain.summary(result.consolidated)
|
||||
return OsvSummaryOut(
|
||||
rows_total=rows_total,
|
||||
unmapped_total=unmapped_total,
|
||||
auto_matched_total=auto_matched_total,
|
||||
documents_total=consolidated.documents_total,
|
||||
gaps=[GapOut.model_validate(gap, from_attributes=True) for gap in consolidated.gaps],
|
||||
documents_total=result.consolidated.documents_total,
|
||||
gaps=[GapOut.model_validate(gap, from_attributes=True) for gap in result.consolidated.gaps],
|
||||
)
|
||||
|
||||
async def row_for_entry(self, version_id: uuid.UUID, entry_id: uuid.UUID) -> ConsolidatedRowOut | None:
|
||||
"""Строка свода, в `entry_ids` которой есть `entry_id`; `None` — строка в своде не участвует (пара с пробелом
|
||||
или документ не выбран источником при наличии нарастающего)."""
|
||||
consolidated, _, _ = await self.build(version_id)
|
||||
row = next((row for row in consolidated.rows if entry_id in row.entry_ids), None)
|
||||
result = await self.build(version_id)
|
||||
row = next((row for row in result.consolidated.rows if entry_id in row.entry_ids), None)
|
||||
return None if row is None else row_out(row)
|
||||
|
||||
async def candidates(
|
||||
|
||||
@ -317,8 +317,8 @@ class IntercompanyMatrixService:
|
||||
selection: MatrixSelection,
|
||||
) -> IntercompanyMatrixOut:
|
||||
"""Счета принимает готовыми; выбор режима ALL/MANUAL выполняется вызывающим кодом."""
|
||||
consolidated, _, _, members = await self._osv_service.build_with_members(version_id)
|
||||
return self._calculate(consolidated.rows, members, selection)
|
||||
result = await self._osv_service.build_with_members(version_id)
|
||||
return self._calculate(result.consolidated.rows, result.members, selection)
|
||||
|
||||
def _calculate(
|
||||
self, rows: Iterable[ConsolidatedRow], members: dict[uuid.UUID, Entity], selection: MatrixSelection
|
||||
@ -329,10 +329,10 @@ class IntercompanyMatrixService:
|
||||
|
||||
async def build_result_table(self, version_id: uuid.UUID) -> IntercompanyResultTableOut:
|
||||
"""Статичная таблица по пяти статьям"""
|
||||
consolidated, _, _, members = await self._osv_service.build_with_members(version_id)
|
||||
result = await self._osv_service.build_with_members(version_id)
|
||||
accounts = await directory_repo.list_accounts(self._session)
|
||||
companies = [IntercompanyMatrixCompanyOut(id=entity.id, name=entity.name) for entity in members.values()]
|
||||
return calculate_result_table(consolidated.rows, companies, accounts)
|
||||
companies = [IntercompanyMatrixCompanyOut(id=entity.id, name=entity.name) for entity in result.members.values()]
|
||||
return calculate_result_table(result.consolidated.rows, companies, accounts)
|
||||
|
||||
async def build_accounts(
|
||||
self,
|
||||
@ -510,8 +510,8 @@ class IntercompanyMatrixService:
|
||||
credit_account_ids: Sequence[uuid.UUID],
|
||||
rule_id: uuid.UUID | None = None,
|
||||
) -> MatrixBuildResult:
|
||||
consolidated, version, consolidation, members = await self._osv_service.build_with_members(version_id)
|
||||
rows = consolidated.rows
|
||||
osv_result = await self._osv_service.build_with_members(version_id)
|
||||
rows = osv_result.consolidated.rows
|
||||
accounts = await directory_repo.list_accounts(self._session)
|
||||
by_id, children = _make_account_tree(accounts)
|
||||
rule: EliminationRule | None = None
|
||||
@ -528,16 +528,16 @@ class IntercompanyMatrixService:
|
||||
credit = _select_accounts(credit_account_ids, (), "K", by_id, children)
|
||||
selection = MatrixSelection(debit, credit, "closing")
|
||||
elif mode == "PRESET":
|
||||
selection, rule = await self._resolve_preset_selection(rule_id, version, by_id, children)
|
||||
selection, rule = await self._resolve_preset_selection(rule_id, osv_result.version, by_id, children)
|
||||
else:
|
||||
raise DomainValidationError("INVALID_MODE", "Неизвестный режим выбора счетов")
|
||||
matrix = self._calculate(rows, members, selection)
|
||||
matrix = self._calculate(rows, osv_result.members, selection)
|
||||
if rule is not None:
|
||||
matrix = await self._apply_corrections(matrix, version_id, rule.id)
|
||||
return MatrixBuildResult(
|
||||
matrix=matrix,
|
||||
version=version,
|
||||
consolidation=consolidation,
|
||||
version=osv_result.version,
|
||||
consolidation=osv_result.consolidation,
|
||||
accounts=accounts,
|
||||
selection=selection,
|
||||
rows=rows,
|
||||
|
||||
@ -120,7 +120,11 @@ async def test_preset_applies_exclusions_to_its_own_side_and_rule_measure(monkey
|
||||
version = SimpleNamespace(elimination_rules_version=1, version_no=2)
|
||||
consolidation = SimpleNamespace(company_group_id=uuid.uuid4(), year=2024, quarter=matrix.PeriodQuarter.Q3)
|
||||
osv_service = SimpleNamespace(
|
||||
build_with_members=AsyncMock(return_value=(SimpleNamespace(rows=[]), version, consolidation, {}))
|
||||
build_with_members=AsyncMock(
|
||||
return_value=SimpleNamespace(
|
||||
consolidated=SimpleNamespace(rows=[]), version=version, consolidation=consolidation, members={}
|
||||
)
|
||||
)
|
||||
)
|
||||
monkeypatch.setattr(matrix.directory_repo, "list_accounts", AsyncMock(return_value=[parent, child]))
|
||||
rule = SimpleNamespace(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user