arm-core/api/tests/test_intercompany_matrix_golden.py
2026-10-20 14:45:27 +03:00

146 lines
6.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Точные пары «Русского бисквита» на 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