feat/goodwillback: бэкенд гудвилл #222

Merged
aep merged 7 commits from feat/goodwill-back into main 2026-11-06 18:07:16 +03:00
4 changed files with 42 additions and 1 deletions
Showing only changes of commit eeb0ce48da - Show all commits

View File

@ -13,6 +13,9 @@ from src.domain.goodwill import Investment
from src.domain.statement_aggregate import AggLine, aggregate_by_code
from src.repositories import goodwill as repo
from src.services.consolidated_osv import ConsolidatedOsvBuildResult, ConsolidatedOsvService
from src.services.statements_engine import default_engine
UNIT_FACTORS: dict[str | None, Decimal] = {"руб.": Decimal(1), "тыс. руб.": Decimal(1000), "млн руб.": Decimal(1000000)}
@dataclass
@ -26,7 +29,7 @@ class Sources:
def capital_amounts(meta_id, units, columns, values, date) -> tuple[Decimal | None, Decimal | None]:
selected = [c.id for c in columns if c.meta_id == meta_id and c.period_date == date]
factor = {"руб.": Decimal(1), "тыс. руб.": Decimal(1000), "млн руб.": Decimal(1000000)}.get(units)
factor = UNIT_FACTORS.get(default_engine().canonical_units(units))
if len(selected) != 1 or factor is None:
return None, None
lines = {}

View File

@ -29,6 +29,7 @@ from arm_statements import (
statements_version,
status_input_from_contract,
)
from arm_statements.text import normalize_caption
__all__ = [
"ArmStatementsEngine",
@ -62,6 +63,7 @@ class StatementsEngine(Protocol):
def editions(self) -> tuple[Edition, ...]: ...
def resolve_relative(self, kind: PeriodKind, effective_date: datetime.date | None) -> datetime.date | None: ...
def tolerance(self) -> Decimal: ...
def canonical_units(self, text: str | None) -> str | None: ...
class ArmStatementsEngine:
@ -105,6 +107,13 @@ class ArmStatementsEngine:
def tolerance(self) -> Decimal:
return self._rules.checks.tolerance
def canonical_units(self, text: str | None) -> str | None:
"""Единица формы по словарю пакета («тыс.руб.», «в тысячах рублей» → «тыс. руб.»); не узнали — None."""
if text is None:
return None
caption = normalize_caption(text)
return next((canonical for pattern, canonical in self._rules.units.spellings if pattern.search(caption)), None)
@functools.cache
def default_engine() -> StatementsEngine:

View File

@ -58,6 +58,9 @@ class FakeEngine:
def version(self) -> str | None:
return self._version
def canonical_units(self, text: str | None) -> str | None:
return text
def recompute(self, form: StoredForm) -> RecomputeResult:
self.forms.append(form)
return self.result

View File

@ -57,3 +57,29 @@ def test_capital_aggregates_duplicates_at_period_end() -> None:
assert capital_amounts(mid, "тыс. руб.", columns, values, dt.date(2025, 9, 30)) == (Decimal("120000"), None)
assert capital_amounts(mid, "тыс. руб.", columns, values, dt.date(2025, 6, 30)) == (None, None)
assert capital_amounts(mid, None, columns, values, dt.date(2025, 9, 30)) == (None, None)
@pytest.mark.parametrize(
("units", "factor"),
[
("тыс. руб.", Decimal(1000)),
("тыс.руб.", Decimal(1000)),
("в тысячах рублей", Decimal(1000)),
("руб", Decimal(1)),
("млн. руб.", Decimal(1000000)),
],
)
def test_capital_units_spellings(units: str, factor: Decimal) -> None:
"""Единицы, исправленные аналитиком вручную, читаются словарём единиц arm_statements."""
mid, col = UUID(int=1), UUID(int=2)
columns = [N(id=col, meta_id=mid, period_date=dt.date(2025, 9, 30), is_current=True)]
line = N(id=UUID(int=4), meta_id=mid, line_code="1310", row_index=0, line_status=StatementLineStatus.ok)
values = [(line, col, Decimal("7"))]
assert capital_amounts(mid, units, columns, values, dt.date(2025, 9, 30)) == (Decimal("7") * factor, None)
def test_capital_unknown_units() -> None:
mid, col = UUID(int=1), UUID(int=2)
columns = [N(id=col, meta_id=mid, period_date=dt.date(2025, 9, 30), is_current=True)]
line = N(id=UUID(int=4), meta_id=mid, line_code="1310", row_index=0, line_status=StatementLineStatus.ok)
assert capital_amounts(mid, "штук", columns, [(line, col, Decimal("7"))], dt.date(2025, 9, 30)) == (None, None)