fix(audit): сохранять номер и версию правила в экспорте пресета
This commit is contained in:
parent
3eb5362e58
commit
b078933428
@ -876,7 +876,8 @@ API: список счетов `GET /directories/accounts`, правила ве
|
||||
`GET …/elimination-rules` отдаёт объект `{version_no, is_version_fixed, rules}` (у правила есть `id`): при незаданной
|
||||
версии правил — открытые правила (`valid_to IS NULL`) с максимальным `version_no`; ответ сообщает этот номер. Ошибки PRESET: 422 `RULE_REQUIRED`, `INVALID_RULE`,
|
||||
`ACCOUNTS_REQUIRED`; правило недействительное для версии или несуществующее — 404. В шапке xlsx — строка «Показатель»,
|
||||
audit `EXPORT` пишет `mode` (`ALL`/`MANUAL`/`PRESET`) и `rule_id`. Открытые вопросы по пресетам —
|
||||
audit `EXPORT` пишет `mode` (`ALL`/`MANUAL`/`PRESET`), для PRESET — `rule_id`, `rule_no` и фактический
|
||||
`rule_version_no`. Открытые вопросы по пресетам —
|
||||
`openspec/changes/intercompany-matrix-presets/design.md`. Контракт для фронта —
|
||||
`../docs/dev/frontend-api-guide.md` §5.8; требования — в существующих спецификациях
|
||||
`openspec/specs/{directories-api,consolidations-api,consolidated-osv,audit-trail}/spec.md`.
|
||||
|
||||
@ -52,6 +52,8 @@ async def _export_response(
|
||||
*,
|
||||
selection: IntercompanyMatrixManualIn | None = None,
|
||||
) -> Response:
|
||||
if info.mode == "PRESET" and (info.rule_id is None or info.rule_no is None or info.rule_version_no is None):
|
||||
raise ValueError("Для аудита PRESET нужны ID, номер и версия правила")
|
||||
response = await _xlsx_response(matrix, info)
|
||||
await audit.record(
|
||||
action=AuditAction.EXPORT,
|
||||
@ -64,7 +66,15 @@ async def _export_response(
|
||||
"mode": info.mode,
|
||||
"debit_account_ids": [str(account_id) for account_id in selection.debit_account_ids] if selection else [],
|
||||
"credit_account_ids": [str(account_id) for account_id in selection.credit_account_ids] if selection else [],
|
||||
**({"rule_id": str(info.rule_id)} if info.rule_id is not None else {}),
|
||||
**(
|
||||
{
|
||||
"rule_id": str(info.rule_id),
|
||||
"rule_no": info.rule_no,
|
||||
"rule_version_no": info.rule_version_no,
|
||||
}
|
||||
if info.mode == "PRESET"
|
||||
else {}
|
||||
),
|
||||
"cells": len(matrix.cells),
|
||||
},
|
||||
)
|
||||
|
||||
@ -87,6 +87,7 @@ class MatrixBuildResult:
|
||||
accounts: list[ChartOfAccount]
|
||||
selection: MatrixSelection
|
||||
rows: tuple[ConsolidatedRow, ...]
|
||||
rule: EliminationRule | None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@ -100,6 +101,8 @@ class MatrixExportInfo:
|
||||
credit_account_codes: tuple[str, ...]
|
||||
measure: Measure = "closing"
|
||||
rule_id: uuid.UUID | None = None
|
||||
rule_no: int | None = None
|
||||
rule_version_no: int | None = None
|
||||
|
||||
@property
|
||||
def filename(self) -> str:
|
||||
@ -394,7 +397,9 @@ class IntercompanyMatrixService:
|
||||
debit_account_codes=debit_codes,
|
||||
credit_account_codes=credit_codes,
|
||||
measure=result.selection.measure,
|
||||
rule_id=rule_id,
|
||||
rule_id=result.rule.id if result.rule is not None else None,
|
||||
rule_no=result.rule.rule_no if result.rule is not None else None,
|
||||
rule_version_no=result.rule.version_no if result.rule is not None else None,
|
||||
)
|
||||
return result.matrix, info
|
||||
|
||||
@ -462,6 +467,7 @@ class IntercompanyMatrixService:
|
||||
rows = osv_result.consolidated.rows
|
||||
accounts = await directory_repo.list_accounts(self._session)
|
||||
by_id, children = _make_account_tree(accounts)
|
||||
rule: EliminationRule | None = None
|
||||
if mode == "ALL":
|
||||
if debit_account_ids or credit_account_ids:
|
||||
raise DomainValidationError("ACCOUNTS_NOT_ALLOWED", "Для режима ALL не нужно указывать счета")
|
||||
@ -475,7 +481,7 @@ class IntercompanyMatrixService:
|
||||
credit = _select_accounts(credit_account_ids, (), "K", by_id, children)
|
||||
selection = MatrixSelection(debit, credit, "closing")
|
||||
elif mode == "PRESET":
|
||||
selection, _ = await self._resolve_preset_selection(rule_id, osv_result.version, by_id, children)
|
||||
selection, rule = await self._resolve_preset_selection(rule_id, osv_result.version, by_id, children)
|
||||
else:
|
||||
raise DomainValidationError("INVALID_MODE", "Неизвестный режим выбора счетов")
|
||||
matrix = self._calculate(rows, osv_result.members, selection)
|
||||
@ -486,4 +492,5 @@ class IntercompanyMatrixService:
|
||||
accounts=accounts,
|
||||
selection=selection,
|
||||
rows=rows,
|
||||
rule=rule,
|
||||
)
|
||||
|
||||
@ -177,6 +177,8 @@ async def test_preset_uses_rule_sides_and_exclusions(auth_client: AuthClientFact
|
||||
audit = (await seeded.scalars(select(AuditEvent).where(AuditEvent.action == AuditAction.EXPORT))).one()
|
||||
assert audit.after_value["mode"] == "PRESET"
|
||||
assert audit.after_value["rule_id"] == str(rule.id)
|
||||
assert audit.after_value["rule_no"] == rule.rule_no
|
||||
assert audit.after_value["rule_version_no"] == rule.version_no
|
||||
|
||||
|
||||
@pytest.mark.parametrize("suffix", ["", "/export"])
|
||||
@ -241,6 +243,9 @@ async def test_preset_uses_current_rules_when_version_unfixed(
|
||||
response = await client.get(url)
|
||||
assert response.status_code == OK, response.text
|
||||
assert (await client.get(f"{url}/export")).status_code == OK
|
||||
audit = (await seeded.scalars(select(AuditEvent).where(AuditEvent.action == AuditAction.EXPORT))).one()
|
||||
assert audit.after_value["rule_no"] == rule.rule_no
|
||||
assert audit.after_value["rule_version_no"] == rule.version_no
|
||||
assert world.version.elimination_rules_version is None
|
||||
world.version.elimination_rules_version = rule.version_no + 1
|
||||
await seeded.flush()
|
||||
|
||||
@ -129,6 +129,7 @@ async def test_preset_applies_exclusions_to_its_own_side_and_rule_measure(monkey
|
||||
monkeypatch.setattr(matrix.directory_repo, "list_accounts", AsyncMock(return_value=[parent, child]))
|
||||
rule = SimpleNamespace(
|
||||
id=uuid.uuid4(),
|
||||
rule_no=6,
|
||||
statement_type=StatementType.balance,
|
||||
version_no=1,
|
||||
side_1="D",
|
||||
@ -165,6 +166,8 @@ async def test_preset_applies_exclusions_to_its_own_side_and_rule_measure(monkey
|
||||
assert info.debit_account_codes == ("76",)
|
||||
assert info.credit_account_codes == ("76", "76.01")
|
||||
assert info.measure == "turnover"
|
||||
assert info.rule_no == rule.rule_no
|
||||
assert info.rule_version_no == rule.version_no
|
||||
|
||||
|
||||
async def test_preset_rejects_income_rule_before_selecting_accounts(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
|
||||
@ -37,6 +37,8 @@ def _info(*, mode: str = "MANUAL", rule_id: UUID | None = None) -> MatrixExportI
|
||||
credit_account_codes=("60.01",) if mode != "ALL" else (),
|
||||
measure="turnover" if mode == "PRESET" else "closing",
|
||||
rule_id=rule_id,
|
||||
rule_no=6 if mode == "PRESET" else None,
|
||||
rule_version_no=3 if mode == "PRESET" else None,
|
||||
)
|
||||
|
||||
|
||||
@ -135,7 +137,6 @@ def test_matrix_export_has_three_rows_per_debit_company_and_highlights_threshold
|
||||
)
|
||||
|
||||
|
||||
|
||||
async def test_export_routes_use_matrix_calculation() -> None:
|
||||
version_id = UUID("11111111-1111-1111-1111-111111111111")
|
||||
account_id = UUID("22222222-2222-2222-2222-222222222222")
|
||||
@ -234,6 +235,8 @@ async def test_export_routes_use_matrix_calculation() -> None:
|
||||
"debit_account_ids": [],
|
||||
"credit_account_ids": [],
|
||||
"rule_id": str(account_id),
|
||||
"rule_no": 6,
|
||||
"rule_version_no": 3,
|
||||
"cells": 1,
|
||||
},
|
||||
},
|
||||
|
||||
@ -51,8 +51,7 @@ change не входит. Шахматка — чистая функция от
|
||||
|
||||
## Known issues
|
||||
|
||||
1. Имя файла содержит UUID правила, шапка xlsx не содержит номер правила и версию; audit EXPORT не пишет номер и
|
||||
версию правил.
|
||||
1. Имя файла содержит UUID правила, шапка xlsx не содержит номер правила и версию.
|
||||
|
||||
## Decisions made without discussion
|
||||
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
`id`. Для версии без зафиксированного `elimination_rules_version` отдаются открытые правила (`valid_to IS NULL`)
|
||||
с максимальным `version_no`; ответ содержит этот номер.
|
||||
- xlsx: для PRESET имя файла с `_preset-<rule_id>`, в шапке строка «Показатель»; audit `EXPORT` пишет `mode`
|
||||
`ALL`/`MANUAL`/`PRESET` и `rule_id`.
|
||||
`ALL`/`MANUAL`/`PRESET`, а для PRESET также `rule_id`, `rule_no` и фактический `rule_version_no`.
|
||||
|
||||
## Capabilities
|
||||
|
||||
|
||||
@ -6,7 +6,8 @@
|
||||
SHALL добавлять отдельное неизменяемое событие `audit_event` с `action = EXPORT`, `section = EXPORT`,
|
||||
`object_type = consolidation_version`, `object_id = consolidation_version_id`, `user_id` инициатора,
|
||||
`before_value = null` и `after_value = {report: "intercompany_matrix", mode, debit_account_ids,
|
||||
credit_account_ids, cells}`; для `PRESET` в `after_value` SHALL быть дополнительно `rule_id`. `mode` — `ALL`,
|
||||
credit_account_ids, cells}`; для `PRESET` в `after_value` SHALL быть дополнительно `rule_id`, `rule_no` и
|
||||
`rule_version_no` (фактическая версия применённого правила, в том числе при незафиксированной версии правил). `mode` — `ALL`,
|
||||
`MANUAL` или `PRESET`, массивы счетов — ID из запроса (в ALL и PRESET пустые), `cells` — число ячеек. Выгрузка
|
||||
сводной ОСВ продолжает писать собственное событие `EXPORT`.
|
||||
|
||||
@ -16,4 +17,4 @@ credit_account_ids, cells}`; для `PRESET` в `after_value` SHALL быть д
|
||||
|
||||
#### Scenario: Выгрузка пресета
|
||||
- **WHEN** пользователь выгружает `GET …/intercompany-matrix/preset/{rule_id}/export`
|
||||
- **THEN** событие `EXPORT` содержит `mode = PRESET`, `rule_id` правила, пустые массивы счетов и число ячеек
|
||||
- **THEN** событие `EXPORT` содержит `mode = PRESET`, `rule_id`, `rule_no`, `rule_version_no`, пустые массивы счетов и число ячеек
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user