"""Выбор счетов и расчёт шахматки без обращения к БД.""" import datetime import uuid from decimal import Decimal from pathlib import Path from types import SimpleNamespace from unittest.mock import AsyncMock, Mock import pytest import yaml from fastapi import status as http_status from src.api.endpoints.consolidations.intercompany_matrix import build_intercompany_matrix, router from src.models.enums import AccountKind, PeriodQuarter, StatementType from src.schemas.consolidations import ( IntercompanyMatrixCellOut, IntercompanyMatrixCompanyOut, IntercompanyMatrixOut, IntercompanyMatrixSelectionIn, IntercompanyResultTableOut, ) from src.services import intercompany_matrix as matrix def _account(kind: AccountKind, parent_id: uuid.UUID | None = None) -> SimpleNamespace: return SimpleNamespace(id=uuid.uuid4(), kind=kind, parent_id=parent_id) def _row( owner: uuid.UUID, counterparty: uuid.UUID, account_id: uuid.UUID | None, *, debit: str = "0", credit: str = "0", ) -> SimpleNamespace: return SimpleNamespace( legal_entity_id=owner, counterparty_id=counterparty, account_id=account_id, closing_debit=Decimal(debit), closing_credit=Decimal(credit), turnover_debit=Decimal("0"), turnover_credit=Decimal("0"), entry_ids=(), ) def test_routes_and_matrix_expands_manual_accounts() -> None: paths = {(route.path, tuple(sorted(route.methods))) for route in router.routes} assert ("/consolidations/versions/{version_id}/intercompany-matrix", ("POST",)) in paths assert ("/consolidations/versions/{version_id}/intercompany-matrix/export", ("POST",)) in paths assert not any(path.endswith(("/all", "/manual", "/all/export", "/manual/export")) for path, _ in paths) assert ("/consolidations/versions/{version_id}/intercompany-matrix/preset/{rule_id}", ("GET",)) in paths assert not any(path.endswith("/unpaired-osv") for path, _ in paths) assert ("/consolidations/versions/{version_id}/intercompany-matrix/result-table", ("GET",)) in paths debit_parent = _account(AccountKind.A) debit_child = _account(AccountKind.A, debit_parent.id) credit_parent = _account(AccountKind.P) credit_child = _account(AccountKind.P, credit_parent.id) by_id, children = matrix._make_account_tree([debit_parent, debit_child, credit_parent, credit_child]) debit = matrix._select_accounts([debit_parent.id], [], "D", by_id, children) credit = matrix._select_accounts([credit_parent.id], [], "K", by_id, children) assert debit == {debit_parent.id, debit_child.id} assert credit == {credit_parent.id, credit_child.id} company_a, company_b = uuid.uuid4(), uuid.uuid4() result = matrix.calculate_matrix( [ _row(company_a, company_b, debit_child.id, debit="100"), _row(company_b, company_a, credit_child.id, credit="90"), ], [ IntercompanyMatrixCompanyOut(id=company_a, name="Компания А"), IntercompanyMatrixCompanyOut(id=company_b, name="Компания Б"), ], matrix.MatrixSelection(debit, credit, "closing"), ) assert [(company.id, company.name) for company in result.companies] == [ (company_a, "Компания А"), (company_b, "Компания Б"), ] assert result.cells[0].amount_a == Decimal("100") assert result.cells[0].amount_b == Decimal("90") assert result.cells[0].variance_percent == Decimal("11.11") assert isinstance(result, IntercompanyMatrixOut) response = result assert response.model_dump(mode="json")["companies"] == [ {"id": str(company_a), "name": "Компания А"}, {"id": str(company_b), "name": "Компания Б"}, ] def test_matrix_totals_accumulate_each_company_and_side() -> None: company_a, company_b, company_c = (uuid.uuid4() for _ in range(3)) debit_account, credit_account = uuid.uuid4(), uuid.uuid4() companies = [ IntercompanyMatrixCompanyOut(id=company_id, name=str(index)) for index, company_id in enumerate((company_a, company_b, company_c)) ] result = matrix.calculate_matrix( [ _row(company_a, company_b, debit_account, debit="100"), _row(company_a, company_c, debit_account, debit="20"), _row(company_c, company_a, debit_account, debit="5"), _row(company_b, company_a, credit_account, credit="70"), _row(company_c, company_a, credit_account, credit="10"), _row(company_a, company_b, credit_account, credit="3"), ], companies, matrix.MatrixSelection(frozenset({debit_account}), frozenset({credit_account}), "closing"), ) assert result.total_debit == {company_a: Decimal("120"), company_b: Decimal("0"), company_c: Decimal("5")} assert result.total_credit == {company_a: Decimal("3"), company_b: Decimal("70"), company_c: Decimal("10")} assert result.model_dump(mode="json")["total_debit"][str(company_a)] == "120.00" @pytest.mark.parametrize( "payload", [ None, {}, {"debit_account_ids": []}, {"credit_account_ids": [uuid.uuid4()]}, {"legal_entity_ids": [uuid.uuid4()]}, ], ) async def test_unified_matrix_route_passes_selection(payload: dict | None) -> None: version_id = uuid.uuid4() selection = IntercompanyMatrixSelectionIn(**payload) if payload is not None else None result = IntercompanyMatrixOut(companies=[], cells=[]) service = SimpleNamespace(build_accounts=AsyncMock(return_value=result)) assert await build_intercompany_matrix(version_id, service, selection) is result expected_selection = selection or IntercompanyMatrixSelectionIn() service.build_accounts.assert_awaited_once_with( version_id, debit_account_ids=expected_selection.debit_account_ids, credit_account_ids=expected_selection.credit_account_ids, legal_entity_ids=expected_selection.legal_entity_ids, ) async def test_export_mode_is_derived_from_account_lists(monkeypatch: pytest.MonkeyPatch) -> None: debit_id = uuid.uuid4() built = SimpleNamespace( matrix=IntercompanyMatrixOut(companies=[], cells=[]), consolidation=SimpleNamespace(year=2024, quarter=PeriodQuarter.Q1), version=SimpleNamespace(version_no=1), accounts=[SimpleNamespace(id=debit_id, code="62.01")], selection=matrix.MatrixSelection(frozenset(), frozenset(), "closing"), rule=None, ) service = matrix.IntercompanyMatrixService(SimpleNamespace(), None, SimpleNamespace()) monkeypatch.setattr(service, "_build_accounts", AsyncMock(return_value=built)) monkeypatch.setattr(service, "_group_name", AsyncMock(return_value="ГК")) _, all_info = await service.build_export(uuid.uuid4()) _, manual_info = await service.build_export(uuid.uuid4(), debit_account_ids=[debit_id]) assert (all_info.mode, manual_info.mode) == ("ALL", "MANUAL") assert manual_info.debit_account_codes == ("62.01",) assert manual_info.credit_account_codes == () async def test_build_pairs_uses_supplied_rows_and_prebuilt_osv(monkeypatch: pytest.MonkeyPatch) -> None: version_id, rule_id, company_a, company_b = (uuid.uuid4() for _ in range(4)) full_rows = [object()] selected_rows: list[object] = [] osv_result = SimpleNamespace( version=SimpleNamespace(id=version_id), members={company_a: object(), company_b: object()}, consolidated=SimpleNamespace(rows=full_rows), ) osv_service = SimpleNamespace(build_with_members=AsyncMock(side_effect=AssertionError("свод собран повторно"))) service = matrix.IntercompanyMatrixService(AsyncMock(), None, osv_service) rule = SimpleNamespace(id=rule_id, rule_no=1, version_no=2, measure="closing") service._resolve_preset_selection = AsyncMock( # type: ignore[method-assign] return_value=(matrix.MatrixSelection(frozenset(), frozenset(), "closing"), rule) ) monkeypatch.setattr(matrix.directory_repo, "list_accounts", AsyncMock(return_value=[])) totals = matrix.RuleTotals(Decimal("0"), Decimal("0"), Decimal("0"), None, Decimal("0"), False) pack_rows = Mock(return_value=matrix.RulePacking([], [], [], totals)) monkeypatch.setattr(matrix, "_pack_preset_rows", pack_rows) response = await service.build_pairs( version_id, rule_id, company_a, company_b, selected_rows, osv_result=osv_result ) assert response.rule_id == rule_id assert pack_rows.call_args.args[0] is selected_rows osv_service.build_with_members.assert_not_awaited() async def test_preset_applies_exclusions_to_its_own_side_and_rule_measure(monkeypatch: pytest.MonkeyPatch) -> None: parent = _account(AccountKind.AP) child = _account(AccountKind.AP, parent.id) parent.code = "76" child.code = "76.01" 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( consolidated=SimpleNamespace(rows=[]), version=version, consolidation=consolidation, members={} ) ) ) monkeypatch.setattr(matrix.directory_repo, "list_accounts", AsyncMock(return_value=[parent, child])) rule = SimpleNamespace( id=uuid.uuid4(), rule_no=6, statement_type=StatementType.balance, article_1_line="1230", article_2_line="1520", version_no=1, side_1="D", measure="turnover", valid_from=datetime.date(2024, 1, 1), valid_to=None, ) rule_accounts = [ SimpleNamespace(company_no=1, account_id=parent.id, is_excluded=False), SimpleNamespace(company_no=1, account_id=child.id, is_excluded=True), SimpleNamespace(company_no=2, account_id=parent.id, is_excluded=False), ] monkeypatch.setattr( matrix.elimination_rules_repo, "rule_with_account", AsyncMock(return_value=(rule, rule_accounts)) ) session = SimpleNamespace(execute=AsyncMock(return_value=SimpleNamespace(scalar_one=lambda: "ГК"))) service = matrix.IntercompanyMatrixService(session, None, osv_service) captured = [] monkeypatch.setattr(service, "_calculate", lambda rows, members, selection: captured.append(selection)) await service.build_accounts(uuid.uuid4(), rule_id=uuid.uuid4()) assert len(captured) == 1 assert captured[0].company_1_account_ids == {parent.id} assert captured[0].company_2_account_ids == {parent.id, child.id} assert captured[0].measure == "turnover" _, info = await service.build_export(uuid.uuid4(), rule_id=uuid.uuid4()) assert info.mode == "PRESET" assert info.debit_account_codes == ("76",) assert info.credit_account_codes == ("76", "76.01") assert info.measure == "turnover" assert info.rule_no == rule.rule_no assert info.rule_version_no == rule.version_no assert info.article_1_line == rule.article_1_line assert info.article_2_line == rule.article_2_line async def test_preset_credit_first_uses_second_side_for_debit(monkeypatch: pytest.MonkeyPatch) -> None: credit_parent = _account(AccountKind.P) credit_child = _account(AccountKind.P, credit_parent.id) debit_parent = _account(AccountKind.A) debit_child = _account(AccountKind.A, debit_parent.id) accounts = [credit_parent, credit_child, debit_parent, debit_child] by_id, children = matrix._make_account_tree(accounts) rule = SimpleNamespace( version_no=1, valid_to=None, statement_type=StatementType.balance, side_1="K", measure="turnover", ) rule_accounts = [ SimpleNamespace(company_no=1, account_id=credit_parent.id, is_excluded=False), SimpleNamespace(company_no=1, account_id=credit_child.id, is_excluded=True), SimpleNamespace(company_no=2, account_id=debit_parent.id, is_excluded=False), ] monkeypatch.setattr( matrix.elimination_rules_repo, "rule_with_account", AsyncMock(return_value=(rule, rule_accounts)) ) service = matrix.IntercompanyMatrixService(object(), None, object()) selection, selected_rule = await service._resolve_preset_selection( uuid.uuid4(), SimpleNamespace(elimination_rules_version=1), by_id, children ) assert selected_rule is rule assert selection.company_1_account_ids == {debit_parent.id, debit_child.id} assert selection.company_2_account_ids == {credit_parent.id} assert selection.measure == "turnover" async def test_preset_rejects_income_rule_before_selecting_accounts(monkeypatch: pytest.MonkeyPatch) -> None: rule = SimpleNamespace( statement_type=StatementType.income, version_no=1, valid_to=None, ) monkeypatch.setattr(matrix.elimination_rules_repo, "rule_with_account", AsyncMock(return_value=(rule, []))) service = matrix.IntercompanyMatrixService(object(), None, object()) with pytest.raises(matrix.DomainValidationError) as error: await service._resolve_preset_selection(uuid.uuid4(), SimpleNamespace(elimination_rules_version=1), {}, {}) assert error.value.status == http_status.HTTP_422_UNPROCESSABLE_CONTENT assert error.value.code == "INVALID_RULE" async def test_all_excludes_off_balance_accounts_only(monkeypatch: pytest.MonkeyPatch) -> None: company_a, company_b = uuid.uuid4(), uuid.uuid4() off_balance = SimpleNamespace(id=uuid.uuid4(), kind=AccountKind.A, parent_id=None, is_off_balance=True) off_balance_credit = SimpleNamespace(id=uuid.uuid4(), kind=AccountKind.P, parent_id=None, is_off_balance=True) debit = SimpleNamespace(id=uuid.uuid4(), kind=AccountKind.A, parent_id=None, is_off_balance=False) credit = SimpleNamespace(id=uuid.uuid4(), kind=AccountKind.P, parent_id=None, is_off_balance=False) accounts = [off_balance, off_balance_credit, debit, credit] monkeypatch.setattr(matrix.directory_repo, "list_accounts", AsyncMock(return_value=accounts)) rows = [ _row(company_a, company_b, off_balance.id, debit="100"), _row(company_a, company_b, debit.id, debit="30"), _row(company_b, company_a, off_balance_credit.id, credit="40"), _row(company_b, company_a, credit.id, credit="20"), ] members = { company_a: SimpleNamespace(id=company_a, name="Компания А"), company_b: SimpleNamespace(id=company_b, name="Компания Б"), } osv_service = SimpleNamespace( build_with_members=AsyncMock( return_value=SimpleNamespace( consolidated=SimpleNamespace(rows=rows), version=None, consolidation=None, members=members ) ) ) service = matrix.IntercompanyMatrixService(SimpleNamespace(), None, osv_service) all_matrix = await service.build_accounts(uuid.uuid4()) manual_matrix = await service.build_accounts( uuid.uuid4(), debit_account_ids=[off_balance.id], credit_account_ids=[off_balance_credit.id] ) debit_only = await service.build_accounts(uuid.uuid4(), debit_account_ids=[off_balance.id]) credit_only = await service.build_accounts(uuid.uuid4(), credit_account_ids=[off_balance_credit.id]) filtered = await service.build_accounts(uuid.uuid4(), legal_entity_ids=[company_a]) assert all_matrix.cells[0].amount_a == Decimal("30") assert all_matrix.cells[0].amount_b == Decimal("20") assert manual_matrix.cells[0].amount_a == Decimal("100") assert manual_matrix.cells[0].amount_b == Decimal("40") assert (debit_only.cells[0].amount_a, debit_only.cells[0].amount_b) == (Decimal("100"), Decimal("20")) assert (credit_only.cells[0].amount_a, credit_only.cells[0].amount_b) == (Decimal("30"), Decimal("40")) assert [company.id for company in filtered.companies] == [company_a] assert filtered.cells == [] assert filtered.total_debit == filtered.total_credit == {company_a: Decimal("0")} assert filtered.model_dump(mode="json")["total_debit"] == {str(company_a): "0.00"} with pytest.raises(matrix.DomainValidationError) as error: await service.build_accounts(uuid.uuid4(), legal_entity_ids=[uuid.uuid4()]) assert error.value.code == "ENTITY_NOT_IN_PERIMETER" @pytest.mark.parametrize( ("debit_amount", "credit_amount", "one_sided", "exceeds"), [ ("100000000", "0", True, False), ("0", "100000000", True, False), ("0", "0", False, False), ("100", "100", False, False), ("100", "50", False, True), ("-50", "0", True, False), ("0", "-50", True, False), ], ) def test_one_sided_uses_amounts_even_with_zero_balance_rows( debit_amount: str, credit_amount: str, one_sided: bool, exceeds: bool ) -> None: company_a, company_b = uuid.uuid4(), uuid.uuid4() debit_account, credit_account = uuid.uuid4(), uuid.uuid4() companies = [ IntercompanyMatrixCompanyOut(id=company_a, name="Компания А"), IntercompanyMatrixCompanyOut(id=company_b, name="Компания Б"), ] selection = matrix.MatrixSelection(frozenset({debit_account}), frozenset({credit_account}), "closing") debit = _row(company_a, company_b, debit_account, debit=debit_amount) credit = _row(company_b, company_a, credit_account, credit=credit_amount) debit.entry_ids = (uuid.uuid4(),) credit.entry_ids = (uuid.uuid4(),) cell = matrix.calculate_matrix([debit, credit], companies, selection).cells[0] assert cell.one_sided is one_sided assert cell.exceeds_threshold is exceeds assert cell.model_dump(mode="json")["one_sided"] is one_sided def test_unmapped_account_entries_are_counted_for_both_directions_of_pair() -> None: company_a, company_b, outsider = uuid.uuid4(), uuid.uuid4(), uuid.uuid4() account_id = uuid.uuid4() missing_a = _row(company_a, company_b, None, debit="100") missing_a.entry_ids = (uuid.uuid4(), uuid.uuid4()) missing_b = _row(company_b, company_a, None, credit="90") missing_b.entry_ids = (uuid.uuid4(),) outside = _row(company_a, outsider, None, debit="500") outside.entry_ids = (uuid.uuid4(),) matched = _row(company_a, company_b, account_id, debit="20") matched.entry_ids = (uuid.uuid4(),) companies = [ IntercompanyMatrixCompanyOut(id=company_a, name="Компания А"), IntercompanyMatrixCompanyOut(id=company_b, name="Компания Б"), ] result = matrix.calculate_matrix( [missing_a, missing_b, outside, matched], companies, matrix.MatrixSelection(frozenset({account_id}), frozenset({account_id}), "closing"), ) expected_skipped = len(missing_a.entry_ids) + len(missing_b.entry_ids) assert [cell.skipped_osv_entry_count for cell in result.cells] == [expected_skipped, expected_skipped] assert result.cells[0].model_dump(mode="json")["skipped_osv_entry_count"] == expected_skipped assert result.cells[0].amount_a == Decimal("20") assert result.cells[0].amount_b == Decimal("0") @pytest.mark.parametrize( ("debit", "expected", "exceeds"), [ ("111.125", "11.13", True), ("111.124", "11.12", True), ("105", "5.00", False), ("105.004", "5.00", True), ("104.999", "5.00", False), ("100", "0.00", False), ], ) def test_percent_rounding_preserves_threshold(debit: str, expected: str, exceeds: bool) -> None: company_a, company_b, account = uuid.uuid4(), uuid.uuid4(), uuid.uuid4() result = matrix.calculate_matrix( [_row(company_a, company_b, account, debit=debit), _row(company_b, company_a, account, credit="100")], [ IntercompanyMatrixCompanyOut(id=company_a, name="Компания А"), IntercompanyMatrixCompanyOut(id=company_b, name="Компания Б"), ], matrix.MatrixSelection(frozenset({account}), frozenset({account}), "closing"), ) assert result.cells[0].variance_percent == Decimal(expected) assert result.cells[0].model_dump(mode="json")["variance_percent"] == expected assert result.cells[0].exceeds_threshold is exceeds @pytest.mark.parametrize( ("value", "expected"), [("0", "0.00"), ("1.005", "1.01"), ("-1.005", "-1.01"), ("3.873533480903112697872235289", "3.87")], ) def test_cell_numbers_have_two_decimals(value: str, expected: str) -> None: fields = ("amount_a", "amount_b", "variance_amount", "variance_percent") cell = IntercompanyMatrixCellOut( company_a_id=uuid.uuid4(), company_b_id=uuid.uuid4(), **dict.fromkeys(fields, value), exceeds_threshold=False, one_sided=False, skipped_osv_entry_count=0, osv_entry_ids_debit=[], osv_entry_ids_credit=[], ) assert {field: cell.model_dump(mode="json")[field] for field in fields} == dict.fromkeys(fields, expected) def test_result_table_uses_fixed_articles_and_group_totals() -> None: company_a, company_b, external = uuid.uuid4(), uuid.uuid4(), uuid.uuid4() required_codes = { code for article in matrix.RESULT_ARTICLES for code in (*article.account_codes, *article.excluded_codes) } kinds = { code: AccountKind.A if article.side == "D" else AccountKind.P for article in matrix.RESULT_ARTICLES for code in article.account_codes } kinds.update({"76": AccountKind.AP, "76.07.1": AccountKind.P, "76.07.5": AccountKind.A, "76.07.9": AccountKind.A}) for code in required_codes: kinds.setdefault(code, AccountKind.AP) ids = {code: uuid.uuid4() for code in required_codes} parents = {"67.03": "67", "66.03": "66", "66.04": "66"} parents.update({code: "76" for code in required_codes if code.startswith("76.")}) accounts = [ SimpleNamespace(id=ids[code], code=code, kind=kinds[code], parent_id=ids.get(parents.get(code))) for code in required_codes ] rows = [ _row(company_a, company_b, ids["60.02"], debit="100"), _row(company_a, company_b, ids["76.07.5"], debit="50"), _row(company_a, company_b, ids["76.07.1"], credit="20"), # субсчета 76 из списка AURORA-2034 не входят ни в дебиторку, ни в кредиторку _row(company_a, company_b, ids["76.АВ"], debit="5"), _row(company_b, company_a, ids["76.К"], credit="3"), _row(company_b, company_a, ids["76.27.1"], credit="4"), _row(company_a, company_b, ids["58.01"], debit="30"), _row(company_b, company_a, ids["60.01"], credit="70"), _row(company_b, company_a, ids["67.03"], credit="40"), _row(company_a, company_b, ids["66.04"], credit="10"), _row(company_a, external, ids["60.02"], debit="999"), ] result = matrix.calculate_result_table( rows, [ IntercompanyMatrixCompanyOut(id=company_a, name="Компания А"), IntercompanyMatrixCompanyOut(id=company_b, name="Компания Б"), ], accounts, ) articles = {article.code: article for article in result.articles} assert list(articles) == [article.code for article in matrix.RESULT_ARTICLES] assert [item.amount for item in articles["receivables"].amounts] == [Decimal("100"), Decimal("0")] assert articles["receivables"].group_total == Decimal("100") assert articles["payables"].group_total == Decimal("70") assert articles["financial_investments"].group_total == Decimal("30") assert articles["long_term_loans"].group_total == Decimal("40") assert articles["short_term_loans"].group_total == Decimal("10") assert isinstance(result, IntercompanyResultTableOut) response = result assert response.articles[0].amounts[0].company_id == company_a assert {"company_totals", "grand_total"}.isdisjoint(result.model_dump(mode="json")) def test_pairs_use_signed_sums_and_leave_negative_rows_unpaired() -> None: """§5: у A по B строки 150 и −50, у B по A 100 — суммы со знаком, как в шахматке; −50 в упаковку не идёт.""" company_a, company_b = uuid.uuid4(), uuid.uuid4() debit_account, credit_account = uuid.uuid4(), uuid.uuid4() selection = matrix.MatrixSelection(frozenset({debit_account}), frozenset({credit_account}), "closing") positive = _row(company_a, company_b, debit_account, debit="150") negative = _row(company_a, company_b, debit_account, debit="-50") counter = _row(company_b, company_a, credit_account, credit="100") for row in (positive, negative, counter): row.entry_ids = (uuid.uuid4(),) row.account_code = "62.01" rows = [positive, negative, counter] rule = SimpleNamespace(id=uuid.uuid4(), rule_no=6, side_1="D", elimination_mode="min") packed = matrix._pack_preset_rows(rows, selection, matrix.PairContext(rule, company_a, company_b)) assert (packed.totals.amount_a, packed.totals.amount_b) == (Decimal("100"), Decimal("100")) assert packed.totals.eliminated_amount == Decimal("100") assert packed.matched == packed.with_discrepancy == [] # 150 против 100 — больше 5 % reasons = {item.row.entry_ids[0]: item.reason for item in packed.unpaired} assert reasons == { positive.entry_ids[0]: "NO_MATCH", negative.entry_ids[0]: "NEGATIVE_AMOUNT", counter.entry_ids[0]: "NO_MATCH", } [item] = matrix.calculate_rule_total_items(rows, [(rule, selection)], [company_a, company_b]) assert (item.amount_a, item.amount_b, item.eliminated_amount, item.status) == ( Decimal("100.00"), Decimal("100.00"), Decimal("100.00"), "AUTO", ) assert set(item.osv_entry_ids_a) == {positive.entry_ids[0], negative.entry_ids[0]} def test_negative_smaller_side_is_discrepancy_without_elimination() -> None: company_a, company_b = uuid.uuid4(), uuid.uuid4() debit_account, credit_account = uuid.uuid4(), uuid.uuid4() selection = matrix.MatrixSelection(frozenset({debit_account}), frozenset({credit_account}), "closing") debit = _row(company_a, company_b, debit_account, debit="-50") credit = _row(company_b, company_a, credit_account, credit="-50") debit.entry_ids, credit.entry_ids = (uuid.uuid4(),), (uuid.uuid4(),) rule = SimpleNamespace(id=uuid.uuid4(), rule_no=6, side_1="D", elimination_mode="min") [item] = matrix.calculate_rule_total_items([debit, credit], [(rule, selection)], [company_a, company_b]) assert (item.deviation_amount, item.deviation_percent, item.eliminated_amount) == (Decimal("0.00"), None, 0) assert item.status == "DISCREPANCY" def test_result_articles_76_exclusions_match_seed_rules() -> None: """Исключения субсчетов 76 в результирующей таблице §6 те же, что у маски 76 в правилах 5–8 seed (AURORA-2034).""" rules = yaml.safe_load((Path(__file__).resolve().parent.parent / "seed/core/elimination_rules_v1.yaml").read_text()) by_no = {rule["rule_no"]: rule for rule in rules["rules"]} seed_excluded = {frozenset(by_no[no]["company_1"]["excluded"]) for no in (5, 6, 7, 8)} seed_excluded.add(frozenset(by_no[6]["company_2"]["excluded"])) articles = {article.code: article for article in matrix.RESULT_ARTICLES} assert seed_excluded == {frozenset(articles["receivables"].excluded_codes)} assert frozenset(articles["payables"].excluded_codes) == frozenset(articles["receivables"].excluded_codes) assert "76.АВ" in articles["receivables"].excluded_codes