"""Точные пары «Русского бисквита» на 30.09.2024 из ОСВ и ручного эталона. Решение владельца 08.10.2026: общий итог элиминации с книгой не сравниваем — аналитик снимает полную сумму каждой стороны, система использует минимум пары. С листом «01.10.2024» сверяются только точные встречные строки; расхождения по остальным строкам ожидаемы. Для правила № 6: 60.01 АО РБ ↔ 62.01 Счастливочной — производство молочной булочки (строка 21), не аренда (строка 17). По займу 42 млн есть 66.03 АО РБ, но в фикстурах нет встречной ОСВ 58.03 Счастливочной; её присутствие проверяется отдельно на модельной строке. """ import json import uuid from decimal import ROUND_HALF_UP, Decimal from pathlib import Path from types import SimpleNamespace import pytest from openpyxl import load_workbook from src.schemas.consolidations import IntercompanyMatrixCompanyOut from src.services.intercompany_matrix import MatrixSelection, calculate_matrix pytestmark = pytest.mark.fixtures GOLDEN = Path("golden/expected_osv_parse") RB = uuid.UUID("11111111-1111-1111-1111-111111111111") BAKERY = uuid.UUID("22222222-2222-2222-2222-222222222222") def _source(fixtures_dir: Path, folder: str, account: str, number: int) -> dict: matches = [ row for path in sorted((fixtures_dir / GOLDEN / folder / "2024-Q3").glob("*.json")) for document in json.loads(path.read_text("utf-8"))["documents"] if document["document_type"] == "OSV" and document["header"]["account_code_raw"] == account for row in document["rows"] if row["row_number"] == number and row["group_type"] == "COUNTERPARTY" and row["data_type"] == "BU" ] assert len(matches) == 1, (folder, account, number) return matches[0] @pytest.mark.parametrize( ("debit_root", "debit_row", "credit_root", "credit_row", "amount", "golden_cells"), [ ("62", 21, "60", 409, "106311116.46", ("F51",)), # F50 содержит 42 млн основного долга + 350 тыс. процентов; J29 — основной долг. ("76", 20, "66", 28, "350000.00", ("F50", "J29")), ], ) def test_exact_pairs_against_russian_biscuit_golden( # noqa: PLR0913 - описание пары и ячеек эталона fixtures_dir: Path, *, debit_root: str, debit_row: int, credit_root: str, credit_row: int, amount: str, golden_cells: tuple[str, ...], ) -> None: debit = _source(fixtures_dir, "schastl", debit_root, debit_row) credit = _source(fixtures_dir, "rb", credit_root, credit_row) if amount == "106311116.46": assert (debit["account_code_raw"], credit["account_code_raw"]) == ("62.01", "60.01") # правило № 6 assert debit["counterparty_inn"] == "3528052718" assert "СЧАСТЛИВОЧНАЯ" in credit["counterparty_name"] debit_account, credit_account = uuid.uuid4(), uuid.uuid4() rows = [ SimpleNamespace( legal_entity_id=owner, counterparty_id=counterparty, account_id=account_id, closing_debit=Decimal(source["closing_debit"] or "0"), closing_credit=Decimal(source["closing_credit"] or "0"), entry_ids=(uuid.uuid4(),), ) for owner, counterparty, account_id, source in ( (BAKERY, RB, debit_account, debit), (RB, BAKERY, credit_account, credit), ) ] result = calculate_matrix( rows, [ IntercompanyMatrixCompanyOut(id=BAKERY, name="Пекарня Счастливочная"), IntercompanyMatrixCompanyOut(id=RB, name="Русский бисквит"), ], MatrixSelection(frozenset({debit_account}), frozenset({credit_account}), "closing"), ) cell = result.cells[0] assert cell.amount_a == cell.amount_b == Decimal(amount) assert cell.variance_amount == cell.variance_percent == Decimal(0) assert cell.one_sided is False and cell.exceeds_threshold is False with (fixtures_dir / "golden/Консолидация_Группа РБ.xlsx").open("rb") as source: workbook = load_workbook(source, read_only=True, data_only=True) try: sheet = workbook["01.10.2024"] expected = Decimal(str(sheet[golden_cells[0]].value)) for address in golden_cells[1:]: expected -= Decimal(str(sheet[address].value)) finally: workbook.close() # В книге тысячи рублей с округлением до целого; входные ОСВ содержат рубли и копейки. assert (cell.amount_a / 1000).quantize(Decimal("1"), rounding=ROUND_HALF_UP) == expected def test_loan_66_03_against_58_03_when_counterpart_exists(fixtures_dir: Path) -> None: """42 млн на 66.03 РБ: без ОСВ 58.03 пара односторонняя, со встречной строкой — точная.""" credit = _source(fixtures_dir, "rb", "66", 25) assert credit["account_code_raw"] == "66.03" amount = Decimal("42000000.00") assert Decimal(credit["closing_credit"]) == amount debit_account, credit_account = uuid.uuid4(), uuid.uuid4() credit_row = SimpleNamespace( legal_entity_id=RB, counterparty_id=BAKERY, account_id=credit_account, closing_debit=Decimal(0), closing_credit=amount, entry_ids=(uuid.uuid4(),), ) companies = [ IntercompanyMatrixCompanyOut(id=BAKERY, name="Пекарня Счастливочная"), IntercompanyMatrixCompanyOut(id=RB, name="Русский бисквит"), ] selection = MatrixSelection(frozenset({debit_account}), frozenset({credit_account}), "closing") # правило № 4 one_sided = calculate_matrix([credit_row], companies, selection).cells[0] assert one_sided.amount_a == 0 assert one_sided.amount_b == amount assert one_sided.one_sided is True # В наборе нет ОСВ 58.03 Счастливочной; модельная строка проверяет встречную сторону правила № 4. debit_row = SimpleNamespace( legal_entity_id=BAKERY, counterparty_id=RB, account_id=debit_account, closing_debit=amount, closing_credit=Decimal(0), entry_ids=(uuid.uuid4(),), ) paired = calculate_matrix([debit_row, credit_row], companies, selection).cells[0] assert paired.amount_a == paired.amount_b == amount assert paired.variance_amount == 0 assert paired.one_sided is False