aurora-1761 #223

Merged
aep merged 5 commits from aurora-1761 into main 2026-11-12 09:21:04 +03:00
5 changed files with 70 additions and 10 deletions
Showing only changes of commit e5b998df20 - Show all commits

View File

@ -90,6 +90,10 @@ class MatrixSelection:
company_1_account_ids: frozenset[uuid.UUID]
company_2_account_ids: frozenset[uuid.UUID]
measure: Measure
debit_rule_codes: tuple[str, ...] = ()
credit_rule_codes: tuple[str, ...] = ()
debit_excluded_codes: tuple[str, ...] = ()
credit_excluded_codes: tuple[str, ...] = ()
@dataclass(frozen=True)
@ -129,6 +133,9 @@ class MatrixExportInfo(VersionExportInfo):
rule_statement_type: StatementType | None = None
article_1_line: str | None = None
article_2_line: str | None = None
rule_side_1: str | None = None
debit_excluded_codes: tuple[str, ...] = ()
credit_excluded_codes: tuple[str, ...] = ()
@property
def preset_details(self) -> tuple[StatementType, int, int, str, str]:
@ -704,8 +711,8 @@ class IntercompanyMatrixService:
group_name = await self._group_name(result.consolidation)
by_id = {account.id: account.code for account in result.accounts}
if mode == "PRESET":
debit_codes = tuple(sorted(by_id[account_id] for account_id in result.selection.company_1_account_ids))
credit_codes = tuple(sorted(by_id[account_id] for account_id in result.selection.company_2_account_ids))
debit_codes = result.selection.debit_rule_codes
credit_codes = result.selection.credit_rule_codes
else:
debit_codes = tuple(by_id[account_id] for account_id in debit_account_ids)
credit_codes = tuple(by_id[account_id] for account_id in credit_account_ids)
@ -724,6 +731,9 @@ class IntercompanyMatrixService:
rule_statement_type=result.rule.statement_type if result.rule is not None else None,
article_1_line=result.rule.article_1_line if result.rule is not None else None,
article_2_line=result.rule.article_2_line if result.rule is not None else None,
rule_side_1=result.rule.side_1 if result.rule is not None else None,
debit_excluded_codes=result.selection.debit_excluded_codes,
credit_excluded_codes=result.selection.credit_excluded_codes,
)
return result.matrix, info
@ -764,7 +774,16 @@ class IntercompanyMatrixService:
credit = _select_accounts(credit_ids, credit_excluded, "K", by_id, children)
measure: Measure = "closing" if rule.measure == "closing" else "turnover"
return MatrixSelection(debit, credit, measure), rule
def codes(ids: Sequence[uuid.UUID]) -> tuple[str, ...]:
return tuple(sorted(by_id[account_id].code for account_id in ids))
return MatrixSelection(
debit, credit, measure,
debit_rule_codes=codes(debit_ids),
credit_rule_codes=codes(credit_ids),
debit_excluded_codes=codes(debit_excluded),
credit_excluded_codes=codes(credit_excluded),
), rule
async def _build_accounts(
self,

View File

@ -57,17 +57,21 @@ def _write_context(sheet: Worksheet, info: MatrixExportInfo) -> int:
extra = [
("Режим", mode_name),
("Счета дебета", ", ".join(info.debit_account_codes) if info.mode != "ALL" else all_accounts),
("Счета кредита", ", ".join(info.credit_account_codes) if info.mode != "ALL" else all_accounts),
("Показатель", "Сальдо" if info.measure == "closing" else "Оборот"),
]
if info.mode == "PRESET" and info.debit_excluded_codes:
extra.append(("кроме", ", ".join(info.debit_excluded_codes)))
extra.append(("Счета кредита", ", ".join(info.credit_account_codes) if info.mode != "ALL" else all_accounts))
if info.mode == "PRESET" and info.credit_excluded_codes:
extra.append(("кроме", ", ".join(info.credit_excluded_codes)))
extra.append(("Показатель", "Сальдо" if info.measure == "closing" else "Оборот"))
if info.mode == "PRESET":
statement_type, rule_no, rule_version_no, article_1_line, article_2_line = info.preset_details
rule_type = "ББ" if statement_type.value == "balance" else "ОФР"
extra.extend(
(
("Правило", f"{rule_type} № {rule_no}"),
("Статья А", article_1_line),
("Статья Б", article_2_line),
("Статья А", article_1_line if info.rule_side_1 != "K" else article_2_line),
("Статья Б", article_2_line if info.rule_side_1 != "K" else article_1_line),
("Версия правил", f"v{rule_version_no}"),
)
)

View File

@ -168,7 +168,10 @@ async def test_preset_applies_exclusions_to_its_own_side_and_rule_measure(monkey
_, info = await service.build_export(uuid.uuid4(), mode="PRESET", rule_id=uuid.uuid4())
assert info.mode == "PRESET"
assert info.debit_account_codes == ("76",)
assert info.credit_account_codes == ("76", "76.01")
assert info.credit_account_codes == ("76",)
assert info.debit_excluded_codes == ("76.01",)
assert info.credit_excluded_codes == ()
assert info.rule_side_1 == "D"
assert info.measure == "turnover"
assert info.rule_no == rule.rule_no
assert info.rule_version_no == rule.version_no
@ -181,6 +184,10 @@ async def test_preset_credit_first_uses_second_side_for_debit(monkeypatch: pytes
credit_child = _account(AccountKind.P, credit_parent.id)
debit_parent = _account(AccountKind.A)
debit_child = _account(AccountKind.A, debit_parent.id)
credit_parent.code = "76"
credit_child.code = "76.07.1"
debit_parent.code = "62"
debit_child.code = "62.01"
accounts = [credit_parent, credit_child, debit_parent, debit_child]
by_id, children = matrix._make_account_tree(accounts)
rule = SimpleNamespace(
@ -208,6 +215,9 @@ async def test_preset_credit_first_uses_second_side_for_debit(monkeypatch: pytes
assert selection.company_1_account_ids == {debit_parent.id, debit_child.id}
assert selection.company_2_account_ids == {credit_parent.id}
assert selection.measure == "turnover"
assert selection.debit_rule_codes == ("62",)
assert selection.credit_rule_codes == ("76",)
assert selection.credit_excluded_codes == ("76.07.1",)
async def test_preset_rejects_income_rule_before_selecting_accounts(monkeypatch: pytest.MonkeyPatch) -> None:

View File

@ -1,5 +1,6 @@
"""XLSX-выгрузка шахматки из готового результата расчёта."""
from dataclasses import replace
from io import BytesIO
from types import SimpleNamespace
from unittest.mock import AsyncMock
@ -52,9 +53,35 @@ def _info(*, mode: str = "MANUAL", rule_id: UUID | None = None) -> MatrixExportI
rule_statement_type=StatementType.balance if mode == "PRESET" else None,
article_1_line="1230" if mode == "PRESET" else None,
article_2_line="1520" if mode == "PRESET" else None,
rule_side_1="D" if mode == "PRESET" else None,
)
def test_preset_header_shows_rule_codes_exclusions_and_credit_first_articles() -> None:
matrix = IntercompanyMatrixOut(companies=[], cells=[])
info = replace(
_info(mode="PRESET"),
debit_account_codes=("60.02", "76"),
credit_account_codes=("60.01", "76"),
debit_excluded_codes=("76.07.5", "76.07.9"),
credit_excluded_codes=("76.07.1",),
rule_side_1="K",
)
sheet = load_workbook(BytesIO(build_matrix_workbook(matrix, info))).active
assert [(sheet.cell(row, 1).value, sheet.cell(row, 2).value) for row in range(7, 16)] == [
("Счета дебета", "60.02, 76"),
("кроме", "76.07.5, 76.07.9"),
("Счета кредита", "60.01, 76"),
("кроме", "76.07.1"),
("Показатель", "Оборот"),
("Правило", "ББ № 6"),
("Статья А", "1520"),
("Статья Б", "1230"),
("Версия правил", "v3"),
]
def test_matrix_export_has_three_rows_per_debit_company_and_highlights_threshold() -> None:
company_a = UUID("11111111-1111-1111-1111-111111111111")
company_b = UUID("22222222-2222-2222-2222-222222222222")

View File

@ -280,8 +280,8 @@ SHALL считать исходные строки ОСВ пары с `account_i
### Requirement: XLSX шахматки пресета
`GET …/intercompany-matrix/preset/{rule_id}/export` SHALL возвращать XLSX с теми же строками, что JSON-расчёт
пресета. Имя файла SHALL иметь вид `intercompany-matrix_<ГК>_<год>-<квартал>_v<n>_rule-balance-<rule_no>.xlsx`. Шапка SHALL
содержать режим «Пресет», коды счетов дебета и кредита (раскрытые по иерархии, с учётом исключений правила), номер
правила, статьи А и Б (`article_1_line` / `article_2_line`), фактическую версию правил и строку «Показатель» со значением «Сальдо» для `closing` или «Оборот» для `turnover`. Строка «Показатель» SHALL присутствовать и в выгрузках ALL и MANUAL (значение
содержать режим «Пресет», исходные коды счетов дебета и кредита из правила и отдельную строку «кроме» для исключений каждой стороны, если они есть, номер
правила, статьи А и Б по дебетовой и кредитовой сторонам соответственно (`article_1_line` / `article_2_line` при `side_1 = D`, наоборот при `side_1 = K`), фактическую версию правил и строку «Показатель» со значением «Сальдо» для `closing` или «Оборот» для `turnover`. Строка «Показатель» SHALL присутствовать и в выгрузках ALL и MANUAL (значение
«Сальдо»).
#### Scenario: Выгрузка по оборотам