test: шапка XLSX пресета со строками «кроме» в интеграционном тесте (AURORA-1761, PR #223)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Andrey Epifancev 2026-11-12 01:37:06 +04:00
parent e9a4f8e8a8
commit 6b948debc0
2 changed files with 30 additions and 9 deletions

View File

@ -13,7 +13,7 @@ from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from src.models.access import AppUser from src.models.access import AppUser
from src.models.accounting import ChartOfAccount, EliminationRule from src.models.accounting import ChartOfAccount, EliminationRule, EliminationRuleAccount
from src.models.audit_ex import AuditEvent from src.models.audit_ex import AuditEvent
from src.models.consolidation import ConsolidationDocument, ConsolidationVersion from src.models.consolidation import ConsolidationDocument, ConsolidationVersion
from src.models.directories import CompanyGroup, LegalEntity, RegionalOffice from src.models.directories import CompanyGroup, LegalEntity, RegionalOffice
@ -67,6 +67,19 @@ async def _request(client: AsyncClient, version_id: uuid.UUID, suffix: str, sele
return await client.get(f"{base}/{suffix}") return await client.get(f"{base}/{suffix}")
async def _rule_codes(session: AsyncSession, rule: EliminationRule) -> dict[tuple[int, bool], str]:
"""Коды сторон правила как в шапке XLSX пресета: (company_no, is_excluded) → «код, код»."""
rows = await session.execute(
select(EliminationRuleAccount.company_no, EliminationRuleAccount.is_excluded, ChartOfAccount.code)
.join(ChartOfAccount, ChartOfAccount.id == EliminationRuleAccount.account_id)
.where(EliminationRuleAccount.rule_id == rule.id)
)
grouped: dict[tuple[int, bool], list[str]] = {}
for company_no, is_excluded, code in rows:
grouped.setdefault((company_no, is_excluded), []).append(code)
return {key: ", ".join(sorted(values)) for key, values in grouped.items()}
async def _source( async def _source(
session: AsyncSession, world: World, *, credit: bool, amount: str, account: str | None = None session: AsyncSession, world: World, *, credit: bool, amount: str, account: str | None = None
) -> uuid.UUID: ) -> uuid.UUID:
@ -204,16 +217,22 @@ async def test_preset_uses_rule_sides_and_exclusions(auth_client: AuthClientFact
assert exported.status_code == OK, exported.text assert exported.status_code == OK, exported.text
assert f"_rule-balance-{rule.rule_no}.xlsx" in unquote(exported.headers["content-disposition"]) assert f"_rule-balance-{rule.rule_no}.xlsx" in unquote(exported.headers["content-disposition"])
sheet = load_workbook(BytesIO(exported.content))["Шахматка"] sheet = load_workbook(BytesIO(exported.content))["Шахматка"]
codes = await _rule_codes(seeded, rule)
assert sheet["B6"].value == "Пресет" assert sheet["B6"].value == "Пресет"
assert (sheet["A7"].value, sheet["B7"].value) == ("Счета дебета", codes[(1, False)])
assert (sheet["A8"].value, sheet["B8"].value) == ("кроме", codes[(1, True)])
assert (sheet["A9"].value, sheet["B9"].value) == ("Счета кредита", codes[(2, False)])
assert (sheet["A10"].value, sheet["B10"].value) == ("кроме", codes[(2, True)])
assert "76" in codes[(1, False)].split(", ")
assert "76.07.5" not in sheet["B7"].value assert "76.07.5" not in sheet["B7"].value
assert "76.07.1" not in sheet["B8"].value assert "76.07.1" in sheet["B10"].value
assert sheet["B9"].value == "Сальдо" assert sheet["B11"].value == "Сальдо"
assert sheet["B10"].value == f"ББ № {rule.rule_no}" assert sheet["B12"].value == f"ББ № {rule.rule_no}"
assert sheet["B11"].value == rule.article_1_line assert sheet["B13"].value == rule.article_1_line
assert sheet["B12"].value == rule.article_2_line assert sheet["B14"].value == rule.article_2_line
assert sheet["B13"].value == f"v{rule.version_no}" assert sheet["B15"].value == f"v{rule.version_no}"
assert Decimal(str(sheet["D16"].value)) == Decimal("100") assert Decimal(str(sheet["D18"].value)) == Decimal("100")
assert Decimal(str(sheet["D17"].value)) == Decimal("90") assert Decimal(str(sheet["D19"].value)) == Decimal("90")
audit = (await seeded.scalars(select(AuditEvent).where(AuditEvent.action == AuditAction.EXPORT))).one() audit = (await seeded.scalars(select(AuditEvent).where(AuditEvent.action == AuditAction.EXPORT))).one()
assert audit.after_value["mode"] == "PRESET" assert audit.after_value["mode"] == "PRESET"
assert audit.after_value["rule_id"] == str(rule.id) assert audit.after_value["rule_id"] == str(rule.id)

View File

@ -8,3 +8,5 @@
- [x] 1.4 Тесты: шапка правила № 6 с исключениями, `side_1 = K`, описания ошибок маршрутов preset (PR #223, автор) - [x] 1.4 Тесты: шапка правила № 6 с исключениями, `side_1 = K`, описания ошибок маршрутов preset (PR #223, автор)
- [x] 1.5 Ревью: влит main (объединённые ALL/MANUAL, AURORA-2034), пустая сторона MANUAL в шапке — «Все балансовые счета»; - [x] 1.5 Ревью: влит main (объединённые ALL/MANUAL, AURORA-2034), пустая сторона MANUAL в шапке — «Все балансовые счета»;
правка спеки перенесена из `openspec/specs` в этот change правка спеки перенесена из `openspec/specs` в этот change
- [x] 1.6 Ревью: интеграционный `test_preset_uses_rule_sides_and_exclusions` переведён на новую шапку (строки «кроме»
под сторонами, коды правила сверяются с `elimination_rule_account` из seed, данные матрицы сдвинуты на две строки)