Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BJKMLHdxMwSUeYcdkwqX4W
84 lines
2.9 KiB
Python
84 lines
2.9 KiB
Python
"""Агрегат формы по коду (спека `arm-core` v5.19; Review Focus 2)."""
|
|
|
|
import uuid
|
|
from dataclasses import dataclass
|
|
from decimal import Decimal
|
|
|
|
from src.domain.statement_aggregate import AggLine, aggregate_by_code, checks_input
|
|
from src.models.enums import StatementLineStatus
|
|
|
|
C1 = uuid.UUID(int=1)
|
|
C2 = uuid.UUID(int=2)
|
|
L1, L5 = uuid.UUID(int=101), uuid.UUID(int=105)
|
|
|
|
|
|
def line( # noqa: PLR0913 - поля строки теста
|
|
status: str,
|
|
code: str | None,
|
|
*,
|
|
r: int | None,
|
|
c1: str | None = None,
|
|
c2: str | None = None,
|
|
id_: uuid.UUID | None = None,
|
|
) -> AggLine:
|
|
amounts = {C1: Decimal(c1) if c1 is not None else None, C2: Decimal(c2) if c2 is not None else None}
|
|
return AggLine(id_ or uuid.uuid4(), r, StatementLineStatus(status), code, amounts)
|
|
|
|
|
|
def ok(code: str, *, r: int | None, c1: str | None = None, c2: str | None = None, id_: uuid.UUID | None = None):
|
|
return line("ok", code, r=r, c1=c1, c2=c2, id_=id_)
|
|
|
|
|
|
def test_repeated_code_sums() -> None:
|
|
agg = aggregate_by_code([ok("2410", r=1, c1="100", id_=L1), ok("2410", r=5, c1="649", id_=L5)])
|
|
assert agg["2410"].amounts[C1] == Decimal("749") and agg["2410"].line_ids == (L1, L5)
|
|
|
|
|
|
def test_empty_amounts_stay_null() -> None:
|
|
assert aggregate_by_code([ok("1110", r=0), ok("1110", r=3)])["1110"].amounts[C1] is None
|
|
assert aggregate_by_code([ok("1110", r=0), ok("1110", r=3, c1="5")])["1110"].amounts[C1] == Decimal("5")
|
|
|
|
|
|
def test_non_ok_lines_excluded() -> None:
|
|
agg = aggregate_by_code(
|
|
[
|
|
line("detail", None, r=2, c1="7"),
|
|
line("unreadable", None, r=3, c1="9"),
|
|
line("missing", None, r=4, c1="1"),
|
|
ok("1250", r=5, c1="3"),
|
|
]
|
|
)
|
|
assert list(agg) == ["1250"]
|
|
|
|
|
|
def test_template_line_counts_as_ok() -> None:
|
|
agg = aggregate_by_code([ok("1250", r=None, c1="10")])
|
|
assert agg["1250"].amounts[C1] == Decimal("10") and agg["1250"].has_source_line is False
|
|
|
|
|
|
def test_codes_ascending_and_template_line_ids_last() -> None:
|
|
agg = aggregate_by_code([ok("2400", r=None, id_=L5), ok("1600", r=2), ok("2400", r=7, id_=L1)])
|
|
assert list(agg) == ["1600", "2400"] and agg["2400"].line_ids == (L1, L5)
|
|
assert agg["2400"].has_source_line is True
|
|
|
|
|
|
def test_reference_name_from_callback() -> None:
|
|
agg = aggregate_by_code([ok("1250", r=1)], reference_name=lambda code: f"имя {code}")
|
|
assert agg["1250"].reference_name == "имя 1250"
|
|
|
|
|
|
@dataclass
|
|
class Col:
|
|
id: uuid.UUID
|
|
column_index: int
|
|
|
|
|
|
def test_checks_input_skips_empty_template_codes() -> None:
|
|
agg = aggregate_by_code(
|
|
[ok("1250", r=None), ok("1260", r=None, c1="4"), ok("1270", r=3), ok("1280", r=4, c1="1"), ok("1280", r=None)]
|
|
)
|
|
indexes, lines = checks_input([Col(C2, 1), Col(C1, 0)], agg)
|
|
assert indexes == [0, 1]
|
|
assert [code for code, _ in lines] == ["1260", "1270", "1280"]
|
|
assert dict(lines)["1260"] == {0: Decimal("4"), 1: None}
|