From 4401fbb510aa0d0c4eb3a90e9e8b735cbdc8ccb6 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Fri, 23 Oct 2026 00:05:52 +0400 Subject: [PATCH] =?UTF-8?q?=D0=90=D1=80=D1=85=D0=B8=D0=B2=20OpenSpec=20int?= =?UTF-8?q?ercompany-matrix-one-sided=20(PR=20#165)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .../.openspec.yaml | 0 .../design.md | 0 .../proposal.md | 0 .../specs/consolidated-osv/spec.md | 0 .../tasks.md | 2 +- openspec/specs/consolidated-osv/spec.md | 43 ++++++++++++++----- 6 files changed, 34 insertions(+), 11 deletions(-) rename openspec/changes/{intercompany-matrix-one-sided => archive/2026-10-23-intercompany-matrix-one-sided}/.openspec.yaml (100%) rename openspec/changes/{intercompany-matrix-one-sided => archive/2026-10-23-intercompany-matrix-one-sided}/design.md (100%) rename openspec/changes/{intercompany-matrix-one-sided => archive/2026-10-23-intercompany-matrix-one-sided}/proposal.md (100%) rename openspec/changes/{intercompany-matrix-one-sided => archive/2026-10-23-intercompany-matrix-one-sided}/specs/consolidated-osv/spec.md (100%) rename openspec/changes/{intercompany-matrix-one-sided => archive/2026-10-23-intercompany-matrix-one-sided}/tasks.md (93%) diff --git a/openspec/changes/intercompany-matrix-one-sided/.openspec.yaml b/openspec/changes/archive/2026-10-23-intercompany-matrix-one-sided/.openspec.yaml similarity index 100% rename from openspec/changes/intercompany-matrix-one-sided/.openspec.yaml rename to openspec/changes/archive/2026-10-23-intercompany-matrix-one-sided/.openspec.yaml diff --git a/openspec/changes/intercompany-matrix-one-sided/design.md b/openspec/changes/archive/2026-10-23-intercompany-matrix-one-sided/design.md similarity index 100% rename from openspec/changes/intercompany-matrix-one-sided/design.md rename to openspec/changes/archive/2026-10-23-intercompany-matrix-one-sided/design.md diff --git a/openspec/changes/intercompany-matrix-one-sided/proposal.md b/openspec/changes/archive/2026-10-23-intercompany-matrix-one-sided/proposal.md similarity index 100% rename from openspec/changes/intercompany-matrix-one-sided/proposal.md rename to openspec/changes/archive/2026-10-23-intercompany-matrix-one-sided/proposal.md diff --git a/openspec/changes/intercompany-matrix-one-sided/specs/consolidated-osv/spec.md b/openspec/changes/archive/2026-10-23-intercompany-matrix-one-sided/specs/consolidated-osv/spec.md similarity index 100% rename from openspec/changes/intercompany-matrix-one-sided/specs/consolidated-osv/spec.md rename to openspec/changes/archive/2026-10-23-intercompany-matrix-one-sided/specs/consolidated-osv/spec.md diff --git a/openspec/changes/intercompany-matrix-one-sided/tasks.md b/openspec/changes/archive/2026-10-23-intercompany-matrix-one-sided/tasks.md similarity index 93% rename from openspec/changes/intercompany-matrix-one-sided/tasks.md rename to openspec/changes/archive/2026-10-23-intercompany-matrix-one-sided/tasks.md index d1864ca..a4b0038 100644 --- a/openspec/changes/intercompany-matrix-one-sided/tasks.md +++ b/openspec/changes/archive/2026-10-23-intercompany-matrix-one-sided/tasks.md @@ -5,4 +5,4 @@ фильтр `/directories/accounts` убран; тесты (суммы, нули, отрицательные, xlsx, ALL/MANUAL с 001). - [x] 1.3 Ревьюер: правила §6 и OQ в `../docs` (docs-PR вместе с этим change), гайд фронта §5.7.1. - [x] 1.4 Ревьюер: подсветка `one_sided` на экране — PR #158, change `intercompany-matrix-ui`. -- [ ] 1.5 После слияния: архив change. +- [x] 1.5 После слияния: архив change. diff --git a/openspec/specs/consolidated-osv/spec.md b/openspec/specs/consolidated-osv/spec.md index 5ad071f..2033bd5 100644 --- a/openspec/specs/consolidated-osv/spec.md +++ b/openspec/specs/consolidated-osv/spec.md @@ -167,10 +167,11 @@ filename="svod-osv.xlsx"; filename*=UTF-8''svod-osv_<ГК>_<год>-<кварт `GET /api/v1/consolidations/versions/{version_id}/intercompany-matrix/all` и `POST /api/v1/consolidations/versions/{version_id}/intercompany-matrix/manual` (`CONSOLIDATION_VIEW`, policy) SHALL рассчитывать матрицу без сохранения в БД, из строк сводной ОСВ версии -и её периметра. ALL включает все счета вида `A`/`AP` для дебета и `P`/`AP` для кредита. MANUAL принимает -обязательные непустые `debit_account_ids: UUID[]` и `credit_account_ids: UUID[]`, раскрывает выбранные счета -по иерархии и оставляет подходящий вид. Неизвестный счёт SHALL давать 422 `ACCOUNT_NOT_FOUND`, неверный вид — -422 `ACCOUNT_KIND_MISMATCH`; пустой массив — 422 валидации тела. Невидимая и несуществующая версия SHALL +и её периметра. ALL включает балансовые счета (`is_off_balance = false`) вида `A`/`AP` для дебета и `P`/`AP` для +кредита; забалансовые счета (001–012) в ALL MUST NOT входить. MANUAL принимает обязательные непустые +`debit_account_ids: UUID[]` и `credit_account_ids: UUID[]`, раскрывает выбранные счета по иерархии и оставляет +подходящий вид; забалансовые счета в MANUAL допускаются. Неизвестный счёт SHALL давать 422 `ACCOUNT_NOT_FOUND`, неверный +вид — 422 `ACCOUNT_KIND_MISMATCH`; пустой массив — 422 валидации тела. Невидимая и несуществующая версия SHALL отвечать одинаковым 404 `NOT_FOUND`. Ответ SHALL содержать `companies[] {id, name}` для всего периметра и `cells[]` для каждой упорядоченной пары @@ -180,19 +181,36 @@ filename="svod-osv.xlsx"; filename*=UTF-8''svod-osv_<ГК>_<год>-<кварт `amount_b` — сумма `closing_credit` строк B с контрагентом A по кредитовым. Расхождение — модуль разности; процент — расхождение / меньшую сумму × 100 или null при нуле. `exceeds_threshold` SHALL быть true при точном проценте > 5, до округления; суммы и процент в JSON SHALL быть десятичными строками с двумя знаками после -запятой (`ROUND_HALF_UP`). `one_sided` SHALL быть true при наличии исходных записей ОСВ только с одной стороны, -в том числе при нулевой сумме. `skipped_osv_entry_count` SHALL считать исходные строки ОСВ пары с -`account_id = null` на обеих сторонах; они не входят в суммы. Массивы `osv_entry_ids_debit/credit` SHALL -содержать ID исходных записей соответствующей стороны. +запятой (`ROUND_HALF_UP`). `one_sided` (пара без встречной стороны) SHALL быть true тогда и только тогда, когда +ровно одна из сумм `amount_a`, `amount_b` равна нулю (знак ненулевой суммы не важен); наличие строк ОСВ с нулевым +сальдо на признак не влияет. Пара подсвечивается, если `exceeds_threshold` или `one_sided`. `skipped_osv_entry_count` +SHALL считать исходные строки ОСВ пары с `account_id = null` на обеих сторонах; они не входят в суммы. Массивы +`osv_entry_ids_debit/credit` SHALL содержать ID исходных записей соответствующей стороны. #### Scenario: Пустой периметр и отсутствие ОСВ - **WHEN** у версии нет членов периметра - **THEN** `companies = []`, `cells = []`; если компании есть, но ОСВ по выбранным счетам нет, ячейки присутствуют с нулевыми суммами #### Scenario: Только одна сторона пары -- **WHEN** у A есть строка ОСВ с контрагентом B, а у B встречной строки нет +- **WHEN** у A есть строка ОСВ с контрагентом B и ненулевым сальдо, а у B встречной строки нет - **THEN** для `(A, B)` `one_sided = true`, `variance_percent = null`, соответствующий массив `osv_entry_ids_*` пуст +#### Scenario: Встречная строка с нулевым сальдо +- **WHEN** у A по B сальдо 100 000 000, а у B по A есть строка ОСВ с нулевым сальдо (одни обороты) +- **THEN** `one_sided = true`, `exceeds_threshold = false`, пара подсвечивается + +#### Scenario: Обе стороны нулевые +- **WHEN** у A и у B есть строки ОСВ друг на друга, но обе суммы равны 0 +- **THEN** `one_sided = false`, `exceeds_threshold = false`, пара не подсвечивается + +#### Scenario: Отрицательное сальдо +- **WHEN** у A по B сальдо −50, у B по A — 0 +- **THEN** `one_sided = true` + +#### Scenario: Забалансовые счета +- **WHEN** у A по B есть сальдо на счёте 001 (`is_off_balance = true`) +- **THEN** в режиме ALL оно в суммы не входит; в MANUAL с выбранным счётом 001 — входит + #### Scenario: Счёт не сопоставлен - **WHEN** у пары есть строка свода с `account_id = null` и двумя `entry_ids` - **THEN** она не входит в суммы, `skipped_osv_entry_count` обеих направленных ячеек пары увеличивается на 2 @@ -205,12 +223,17 @@ filename="svod-osv.xlsx"; filename*=UTF-8''svod-osv_<ГК>_<год>-<кварт `GET …/intercompany-matrix/all/export` и `POST …/intercompany-matrix/manual/export` SHALL использовать те же режимы, тело и права, что и соответствующие JSON-маршруты. Ответ — XLSX с именем `intercompany-matrix_<ГК>_<год>-<квартал>_v.xlsx`, шапкой (ГК, период, версия, единицы, режим, -выбранные счета) и строками разницы, дебета, кредита и процента расхождения для каждой пары. +выбранные счета; для ALL — «Все балансовые счета») и строками разницы, дебета, кредита и процента расхождения для +каждой пары. Пара SHALL заливаться цветом порога, если `exceeds_threshold` или `one_sided` — так же, как в API. #### Scenario: Экспорт выбранных счетов - **WHEN** пользователь выгружает MANUAL для ГК «Русский бисквит» за 2024 Q1, версия 1 - **THEN** имя содержит `Русский бисквит_2024-Q1_v1.xlsx`, шапка содержит выбранные счета, ячейки процента показывают два знака и символ `%` без повторного масштабирования +#### Scenario: Подсветка пары без встречной стороны +- **WHEN** выгружается ALL, у пары `amount_a = 100000000`, `amount_b = 0` +- **THEN** все строки пары залиты цветом порога, в шапке «Режим» и «Счета дебета/кредита» — «Все балансовые счета» + ### Requirement: Шахматка по правилу-пресету `GET /api/v1/consolidations/versions/{version_id}/intercompany-matrix/preset/{rule_id}` (`CONSOLIDATION_VIEW`, policy) SHALL рассчитывать шахматку в режиме PRESET с тем же ответом, что ALL и MANUAL.