diff --git a/api/src/domain/vgo_distribution.py b/api/src/domain/vgo_distribution.py index a793947..ebab240 100644 --- a/api/src/domain/vgo_distribution.py +++ b/api/src/domain/vgo_distribution.py @@ -100,17 +100,23 @@ def distribute_vgo_rows(data: DistributionInput) -> Distribution: for row in data.rows: if not used.isdisjoint(row.entry_ids): continue + manual = row.entry_ids in data.manual_keys + # §5 п. 2a: разносятся только строки между разными компаниями периметра; строка с контрагентом вне периметра + # или «сам на себя» в таблицу не попадает, если по ней нет ручного решения аналитика (AURORA-2034) + in_perimeter = ( + row.legal_entity_id in data.members + and row.counterparty_id in data.members + and row.legal_entity_id != row.counterparty_id + ) + if not manual and not in_perimeter: + continue rule_ids = rules_for(data.owners, row.account_id, row_side(row.closing_debit, row.closing_credit)) if ( - row.entry_ids not in data.manual_keys + not manual and len(rule_ids) == 1 - and ( - row.legal_entity_id in data.members - and row.counterparty_id in data.members - and (row.closing_debit not in (None, ZERO) or row.closing_credit not in (None, ZERO)) - ) + and (row.closing_debit not in (None, ZERO) or row.closing_credit not in (None, ZERO)) ): auto_rules[row.entry_ids] = next(iter(rule_ids)) - elif rule_ids or row.entry_ids in data.manual_keys: + elif rule_ids or manual: unassigned.add(row.entry_ids) return Distribution(selected, auto_rules, frozenset(unassigned), frozenset(used)) diff --git a/api/tests/test_vgo.py b/api/tests/test_vgo.py index 25bd82c..fe3d54e 100644 --- a/api/tests/test_vgo.py +++ b/api/tests/test_vgo.py @@ -131,6 +131,45 @@ def test_credit_row_is_matched_by_credit_side_rules_only() -> None: assert distribution.unassigned == {debit_row.entry_ids} +def test_rows_outside_perimeter_or_to_itself_are_not_unallocated() -> None: + """Разносятся только строки между компаниями периметра (§5 п. 2a): контрагент вне периметра и «сам на себя» + в нераспределённые не попадают; ручное решение аналитика по такой строке сохраняется (AURORA-2034).""" + rule_5, rule_6, account, company_a, company_b, outsider = (uuid.uuid4() for _ in range(6)) + inside = _row(uuid.uuid4(), company_a, company_b, 1) + outside = _row(uuid.uuid4(), company_a, outsider, 2) + to_itself = _row(uuid.uuid4(), company_a, company_a, 3) + to_itself_single = _row(uuid.uuid4(), company_b, company_b, 4) + outside_manual = _row(uuid.uuid4(), company_a, outsider, 5) + for row in (inside, outside, to_itself, to_itself_single, outside_manual): + row.account_id = account + owners = account_owners([(rule_5, {account}, set()), (rule_6, {account}, {account})]) + single = account_owners([(rule_6, {account}, set())]) + distribution = distribute_vgo_rows( + DistributionInput( + rows=[inside, outside, to_itself, outside_manual], + exact_ids=set(), + diff_ids=[], + manual_keys={outside_manual.entry_ids}, + owners=owners, + members={company_a, company_b}, + ) + ) + assert distribution.unassigned == {inside.entry_ids, outside_manual.entry_ids} + assert distribution.auto_rules == {} + single_distribution = distribute_vgo_rows( + DistributionInput( + rows=[to_itself_single], + exact_ids=set(), + diff_ids=[], + manual_keys=set(), + owners=single, + members={company_a, company_b}, + ) + ) + assert single_distribution.auto_rules == {} + assert single_distribution.unassigned == frozenset() + + async def test_unallocated_credit_row_offers_only_credit_side_rules(monkeypatch: pytest.MonkeyPatch) -> None: """Меню «Отнести к правилу» Кт-строки — только правила, где счёт на стороне кредита (AURORA-2034).""" version_id, company_a, company_b, rule_1, rule_2, rule_3, account = (uuid.uuid4() for _ in range(7))