from dataclasses import replace from datetime import date from decimal import Decimal as D from arm_statements import StoredColumn, StoredForm, StoredLine, StoredValue, choose_edition, recompute from arm_statements.loader import Rules from arm_statements.model import DocStatus, DocumentType, Edition, FormVariant, LineStatus, PeriodKind from arm_statements.structure import edition_by_date OK, DETAIL = LineStatus.OK, LineStatus.DETAIL COL = StoredColumn("c1", 2, date(2025, 9, 30), PeriodKind.ABSOLUTE) def sl( # noqa: PLR0913 -- конструктор тестовых строк key: str, row: int | None, code: str | None, amount: str | None, *, status: LineStatus = OK, conf: float = 100.0, code_fixed: bool = False, value_conf: float = 100.0, value_fixed: bool = False, recognized: bool = True, ) -> StoredLine: value = StoredValue("c1", None if amount is None else D(amount), amount, value_conf, recognized, value_fixed) return StoredLine(key, row, status, code, conf, None, code_fixed, None, (value,)) def form(*lines: StoredLine, document_type: DocumentType = DocumentType.BALANCE) -> StoredForm: return StoredForm(document_type, date(2025, 9, 30), True, True, (), False, (), (COL,), tuple(lines)) def test_recompute_after_edit(rules: Rules) -> None: result = recompute( form( sl("l1210", 0, "1210", "100"), sl("l1230", 1, "1230", "300", value_conf=60.0, value_fixed=True), sl("l1250", 2, "1250", "50", conf=40.0, code_fixed=True), sl("l1260", None, "1260", "50"), sl("ldet", 3, None, "7", status=DETAIL, value_conf=60.0), sl("l1200", 4, "1200", "500"), ), rules, ) # 1200 = 100 + 300 + 50 + 50 (с 1250 и строкой правки 1260) — сходится; 1600 нет — «пустая цель» assert [(f.relation, f.column_key, f.column_index) for f in result.checks.relations_failed] == [ ("1600 = 1100 + 1200", "c1", 2) ] assert result.value_needs_review[("l1230", "c1")] is False assert result.value_needs_review[("ldet", "c1")] is False assert result.line_needs_review["l1250"] is False assert result.detail_parents == {"ldet": "l1250"} assert (result.edition, result.form_variant, result.status) == ( Edition.FSBU4, FormVariant.FULL, DocStatus.NEEDS_CHECK, ) def test_code_change_switches_variant(rules: Rules) -> None: simplified = form( sl("a", 0, "1150", "50"), sl("b", 1, "1600", "50"), sl("c", 2, "1300", "50"), sl("d", 3, "1700", "50") ) assert recompute(simplified, rules).form_variant is FormVariant.SIMPLIFIED edited = form( sl("a", 0, "1100", "50", code_fixed=True), sl("b", 1, "1600", "50"), sl("c", 2, "1300", "50"), sl("d", 3, "1700", "50"), ) result = recompute(edited, rules) assert result.form_variant is FormVariant.FULL assert result.status is DocStatus.NEEDS_CHECK def test_edition_equals_choose_edition(rules: Rules) -> None: stored = form(sl("a", 0, "1110", "10"), sl("b", 1, "1120", "5", conf=60.0), sl("c", 2, "1130", "5")) pairs = [("1110", True), ("1120", False), ("1130", True)] expected = choose_edition(DocumentType.BALANCE, pairs, date(2025, 9, 30), rules) assert recompute(stored, rules).edition is expected is Edition.RAS_66N def test_accepted_and_empty_edit_line(rules: Rules) -> None: broken = form(sl("a", 0, "1600", "100"), sl("b", 1, "1700", "90"), sl("e", None, "1220", None)) assert recompute(broken, rules).status is DocStatus.NEEDS_CHECK assert recompute(replace(broken, accepted=True), rules).status is DocStatus.DONE def test_disclosure_has_complete_maps(rules: Rules) -> None: """I-3: для расшифровки в словарях ключи всех строк и значений, а не пустые словари.""" stored = form( sl("a", 0, "1110", "10"), sl("d", 1, None, "7", status=DETAIL), sl("e", None, "1120", None), document_type=DocumentType.DISCLOSURE, ) result = recompute(stored, rules) assert result.line_needs_review == {"a": False, "d": False, "e": False} assert result.value_needs_review == {("a", "c1"): False, ("d", "c1"): False, ("e", "c1"): False} assert result.detail_parents == {"d": "a"} assert (result.form_variant, result.checks.relations_failed, result.status) == (None, (), DocStatus.DONE) assert result.edition is edition_by_date(date(2025, 9, 30)) def test_edit_line_without_amount_is_excluded_from_checks_and_variant(rules: Rules) -> None: """T18.6: пустая строка правки не участвует в §7 и не влияет на вариант.""" base = [sl("a", 0, "1150", "50"), sl("b", 1, "1600", "50"), sl("c", 2, "1300", "50"), sl("d", 3, "1700", "50")] with_edit = recompute(form(*base, sl("e", None, "1100", None)), rules) without = recompute(form(*base), rules) assert with_edit.form_variant is without.form_variant is FormVariant.SIMPLIFIED assert with_edit.checks == without.checks def test_detail_without_row_index_has_no_parent(rules: Rules) -> None: stored = form(sl("a", 0, "1210", "10"), sl("d", None, None, "3", status=DETAIL)) assert recompute(stored, rules).detail_parents == {"d": None}