101 lines
4.6 KiB
Python
101 lines
4.6 KiB
Python
"""Точные пары «Русского бисквита» на 30.09.2024 из ОСВ и ручного эталона.
|
||
|
||
Решение владельца 08.10.2026: общий итог элиминации с книгой не сравниваем —
|
||
аналитик снимает полную сумму каждой стороны, система использует минимум пары.
|
||
Здесь явно выбраны только точные встречные строки. Для 62.01 это производство
|
||
молочной булочки (строка 21); аренда (строка 17) в эту пару не входит.
|
||
Тест проверяет расчёт выбранных пар, а не автоматический подбор строк из всей ОСВ.
|
||
"""
|
||
|
||
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)
|
||
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
|