OpenSpec change intercompany-matrix: требования шахматки из specs/ в дельты (#114)
Требования AURORA-1537, дописанные в openspec/specs/{audit-trail,consolidated-osv,
consolidations-api,directories-api}, перенесены в дельты change'а; specs/ возвращены к main.
Известные дефекты (AURORA-1651/1652/1653) зафиксированы в design.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
4228b94a7f
commit
5682897ed0
2
openspec/changes/intercompany-matrix/.openspec.yaml
Normal file
2
openspec/changes/intercompany-matrix/.openspec.yaml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
created: 2026-10-16
|
||||||
50
openspec/changes/intercompany-matrix/design.md
Normal file
50
openspec/changes/intercompany-matrix/design.md
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
## Context
|
||||||
|
|
||||||
|
Реализация — PR RSHB/arm-core#114 (AURORA-1537). Шахматка — чистая функция от сводной ОСВ версии, периметра и выбранных
|
||||||
|
счетов; в БД не хранится (инвариант «производные консолидации не хранятся», DECISION-5).
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
1. Матрица считается поверх `ConsolidatedOsvService.build_with_members` (бывший приватный `_build`): строки свода и
|
||||||
|
участники периметра берутся из того же расчёта, что и сводная ОСВ; в `SourceRow`/`ConsolidatedRow` добавлено
|
||||||
|
`account_id`.
|
||||||
|
2. Пара `(A, B)`: сторона 1 — дебет ОСВ A по контрагенту B, сторона 2 — кредит ОСВ B по контрагенту A. Расчёт
|
||||||
|
поддерживает показатели `closing`/`turnover`, но в режимах ALL и MANUAL всегда используется конечное сальдо
|
||||||
|
(`closing`).
|
||||||
|
3. Режим ALL берёт все счета вида `A`/`AP` (дебет) и `P`/`AP` (кредит); MANUAL раскрывает выбранные счета по иерархии
|
||||||
|
`chart_of_account` и фильтрует по виду.
|
||||||
|
4. Порог расхождения 5 % проверяется по точному значению до округления; суммы в JSON — десятичные строки с двумя
|
||||||
|
знаками (`ROUND_HALF_UP`).
|
||||||
|
5. Строки пары без `account_id` в суммы не входят и считаются в `skipped_osv_entry_count`.
|
||||||
|
6. Результирующая таблица — пять фиксированных статей (`RESULT_ARTICLES`) со списками кодов счетов и исключений;
|
||||||
|
отсутствие любого кода в плане счетов — 422 `RESULT_ACCOUNTS_MISSING`.
|
||||||
|
7. xlsx: имя файла `intercompany-matrix_<ГК>_<год>-<квартал>_v<n>.xlsx`; каждая выгрузка пишет
|
||||||
|
собственное событие `audit_event(EXPORT)`.
|
||||||
|
8. Сверка с эталоном «Русский бисквит» — только по точным парам счетов и компаний; общая сумма элиминации эталона не
|
||||||
|
приравнивается к минимуму по паре (решение владельца 08.10.2026).
|
||||||
|
|
||||||
|
## Known issues
|
||||||
|
|
||||||
|
Требования ниже описывают текущее поведение; дефекты вынесены в задачи Jira и в этом change не исправляются.
|
||||||
|
|
||||||
|
- AURORA-1651: `one_sided` считается по наличию исходных записей, а не по суммам и строкам-основаниям — пара «100 млн
|
||||||
|
против строки с нулевым сальдо» не подсвечивается, «0 против 0» подсвечивается ложно; подсветка таких пар в xlsx;
|
||||||
|
режим ALL захватывает забалансовые счета 001–012.
|
||||||
|
- AURORA-1652: `GET …/elimination-rules` пуст до задачи 1536, хотя требование описывает непустой ответ; формат сумм
|
||||||
|
результирующей таблицы `"0"` вместо `"0.00"` (расхождение с форматом матрицы); ссылки на §§ в docstring; ссылка README
|
||||||
|
на гайд фронта.
|
||||||
|
- AURORA-1653: переход «ячейка → сводная ОСВ» — фильтр по паре в `GET …/osv`; `osv_entry_ids_*` содержат ID нулевых
|
||||||
|
строк и не отсортированы.
|
||||||
|
|
||||||
|
## Decisions made without discussion
|
||||||
|
|
||||||
|
- 16.10.2026. Требования автора перенесены из `openspec/specs/*` в дельты `ADDED` дословно, без правки содержания.
|
||||||
|
Обоснование: владелец поручил оформить change процессно, а содержательные замечания ревью уже вынесены в задачи.
|
||||||
|
Цена ошибки: низкая — текст можно поправить в этом же change'е до архивации.
|
||||||
|
- 16.10.2026. Спека фиксирует текущее поведение, включая семантику `one_sided` и формат `"0"` результирующей таблицы;
|
||||||
|
исправление требований идёт вместе с кодом в AURORA-1651/1652/1653 (там же MODIFIED-дельты). Обоснование: иначе спека
|
||||||
|
разойдётся с кодом PR. Цена ошибки: до исправления спека описывает известные дефекты (отмечены в `## Known issues`).
|
||||||
|
- 16.10.2026. Все требования оформлены как `ADDED` в существующих capability, новая capability не заводится.
|
||||||
|
Обоснование: шахматка — отчёт по сводной ОСВ (consolidated-osv), API версий (consolidations-api), справочников и
|
||||||
|
аудита; отдельная capability дробила бы уже согласованную структуру. Цена ошибки: низкая, перенос между capability —
|
||||||
|
правка путей дельт.
|
||||||
32
openspec/changes/intercompany-matrix/proposal.md
Normal file
32
openspec/changes/intercompany-matrix/proposal.md
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
## Why
|
||||||
|
|
||||||
|
Отчёт «Шахматка ВГО» (ТЗ §6): аналитик сверяет внутригрупповые обороты пар компаний периметра по строкам сводной ОСВ
|
||||||
|
версии, видит расхождения сверх порога, получает результирующую таблицу по пяти статьям и выгрузку в xlsx. Jira
|
||||||
|
AURORA-1537, реализация — PR RSHB/arm-core#114 (автор — Потапова). Требования автор дописала прямо в
|
||||||
|
`openspec/specs/`; при подготовке к слиянию они оформлены как дельты этого change'а (решение владельца 16.10.2026).
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- Расчёт шахматки без хранения в БД (`GET …/intercompany-matrix/all`, `POST …/intercompany-matrix/manual`) по строкам
|
||||||
|
сводной ОСВ версии и её периметру; режимы ALL и MANUAL.
|
||||||
|
- Результирующая таблица ВГО из пяти фиксированных статей (`GET …/intercompany-matrix/result-table`).
|
||||||
|
- Выгрузка xlsx шахматки (`…/all/export`, `…/manual/export`) и аудит `EXPORT` каждой выгрузки.
|
||||||
|
- Правила элиминации версии (`GET …/elimination-rules`).
|
||||||
|
- Плоский список счетов для фильтров (`GET /api/v1/directories/accounts`).
|
||||||
|
- В сводную ОСВ добавлен `account_id` строки (внутреннее поле домена, в API свода не выходит).
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
### New Capabilities
|
||||||
|
Нет.
|
||||||
|
|
||||||
|
### Modified Capabilities
|
||||||
|
- `consolidated-osv`: шахматка по сводной ОСВ и её xlsx.
|
||||||
|
- `consolidations-api`: правила элиминации версии, результирующая таблица ВГО.
|
||||||
|
- `directories-api`: плоский список счетов.
|
||||||
|
- `audit-trail`: аудит экспорта шахматки.
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
Только `arm-core` (API, сервисы, схемы), таблицы и DBML не меняются. Код реализован и протестирован в PR #114; этот
|
||||||
|
change фиксирует требования к текущему поведению ветки, включая известные дефекты (см. `design.md`, `## Known issues`).
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Аудит экспорта шахматки
|
||||||
|
Каждый успешный `GET …/intercompany-matrix/all/export` и `POST …/intercompany-matrix/manual/export`
|
||||||
|
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}`. `mode` — `ALL` или `MANUAL`, массивы счетов — ID из запроса (в ALL пустые),
|
||||||
|
`cells` — число ячеек. Выгрузка сводной ОСВ продолжает писать собственное событие `EXPORT`.
|
||||||
|
|
||||||
|
#### Scenario: Две выгрузки
|
||||||
|
- **WHEN** пользователь дважды выгружает ту же шахматку
|
||||||
|
- **THEN** в `audit_event` две разные записи `EXPORT`, каждая со своим payload и числом ячеек
|
||||||
@ -0,0 +1,49 @@
|
|||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Шахматка по сводной ОСВ версии
|
||||||
|
`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
|
||||||
|
отвечать одинаковым 404 `NOT_FOUND`.
|
||||||
|
|
||||||
|
Ответ SHALL содержать `companies[] {id, name}` для всего периметра и `cells[]` для каждой упорядоченной пары
|
||||||
|
разных компаний: `company_a_id`, `company_b_id`, `amount_a`, `amount_b`, `variance_amount`, `variance_percent`,
|
||||||
|
`exceeds_threshold`, `one_sided`, `skipped_osv_entry_count`, `osv_entry_ids_debit`, `osv_entry_ids_credit`.
|
||||||
|
Для пары `(A, B)` `amount_a` — сумма `closing_debit` строк A с контрагентом B по дебетовым счетам,
|
||||||
|
`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 исходных записей соответствующей стороны.
|
||||||
|
|
||||||
|
#### Scenario: Пустой периметр и отсутствие ОСВ
|
||||||
|
- **WHEN** у версии нет членов периметра
|
||||||
|
- **THEN** `companies = []`, `cells = []`; если компании есть, но ОСВ по выбранным счетам нет, ячейки присутствуют с нулевыми суммами
|
||||||
|
|
||||||
|
#### Scenario: Только одна сторона пары
|
||||||
|
- **WHEN** у A есть строка ОСВ с контрагентом B, а у B встречной строки нет
|
||||||
|
- **THEN** для `(A, B)` `one_sided = true`, `variance_percent = null`, соответствующий массив `osv_entry_ids_*` пуст
|
||||||
|
|
||||||
|
#### Scenario: Счёт не сопоставлен
|
||||||
|
- **WHEN** у пары есть строка свода с `account_id = null` и двумя `entry_ids`
|
||||||
|
- **THEN** она не входит в суммы, `skipped_osv_entry_count` обеих направленных ячеек пары увеличивается на 2
|
||||||
|
|
||||||
|
#### Scenario: Эталон «Русский бисквит»
|
||||||
|
- **WHEN** матрица строится по ОСВ РБ и Пекарни Счастливочной из эталонных фикстур
|
||||||
|
- **THEN** суммы сравниваются с эталоном только для точных пар счетов и компаний; общая сумма элиминации эталона не приравнивается к минимуму по паре в API (решение владельца 08.10.2026)
|
||||||
|
|
||||||
|
### Requirement: XLSX шахматки
|
||||||
|
`GET …/intercompany-matrix/all/export` и `POST …/intercompany-matrix/manual/export` SHALL использовать
|
||||||
|
те же режимы, тело и права, что и соответствующие JSON-маршруты. Ответ — XLSX с именем
|
||||||
|
`intercompany-matrix_<ГК>_<год>-<квартал>_v<n>.xlsx`, шапкой (ГК, период, версия, единицы, режим,
|
||||||
|
выбранные счета) и строками разницы, дебета, кредита и процента расхождения для каждой пары.
|
||||||
|
|
||||||
|
#### Scenario: Экспорт выбранных счетов
|
||||||
|
- **WHEN** пользователь выгружает MANUAL для ГК «Русский бисквит» за 2024 Q1, версия 1
|
||||||
|
- **THEN** имя содержит `Русский бисквит_2024-Q1_v1.xlsx`, шапка содержит выбранные счета, ячейки процента показывают два знака и символ `%` без повторного масштабирования
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Правила элиминации версии
|
||||||
|
`GET /api/v1/consolidations/versions/{version_id}/elimination-rules` (`CONSOLIDATION_VIEW`, policy по ГК)
|
||||||
|
SHALL возвращать все правила версии массивом. Каждое правило SHALL содержать `rule_no`, `version_no`,
|
||||||
|
`statement_type`, `side_1` (`D`/`K`), `measure` (`closing`/`turnover`), `company_1`, `company_2`;
|
||||||
|
каждая сторона SHALL содержать `accounts: UUID[]` и `excluded: UUID[]` до раскрытия иерархии счетов.
|
||||||
|
Невидимая и несуществующая версия SHALL отвечать одинаковым 404 `NOT_FOUND`.
|
||||||
|
|
||||||
|
#### Scenario: Правило с исключением
|
||||||
|
- **WHEN** у правила первая сторона включает счёт 76 и исключает 76.07.5
|
||||||
|
- **THEN** ID 76 находится в `company_1.accounts`, ID 76.07.5 — в `company_1.excluded`
|
||||||
|
|
||||||
|
### Requirement: Результирующая таблица ВГО
|
||||||
|
`GET /api/v1/consolidations/versions/{version_id}/intercompany-matrix/result-table`
|
||||||
|
(`CONSOLIDATION_VIEW`, policy) SHALL возвращать `{companies[], articles[]}`. Компании — все члены периметра
|
||||||
|
по имени; статья содержит `code`, `name`, `amounts[] {company_id, amount}` и `group_total`.
|
||||||
|
Пять фиксированных статей §6: дебиторская и кредиторская задолженность, финансовые вложения,
|
||||||
|
долгосрочные и краткосрочные займы. Сумма компании SHALL считаться по всем остальным компаниям периметра
|
||||||
|
из конечного сальдо нужной стороны и выбранных счетов статьи; `group_total` — сумма по компаниям.
|
||||||
|
Отсутствие любого необходимого кода счёта SHALL давать 422 `RESULT_ACCOUNTS_MISSING` с перечнем кодов.
|
||||||
|
|
||||||
|
#### Scenario: Компания без ОСВ
|
||||||
|
- **WHEN** в периметре есть компания без подходящих строк ОСВ
|
||||||
|
- **THEN** она есть в `companies`, её сумма по каждой статье — строка `"0"`
|
||||||
|
|
||||||
|
#### Scenario: Нет обязательного счёта
|
||||||
|
- **WHEN** в плане счетов отсутствует счёт 58.03
|
||||||
|
- **THEN** ответ 422 `RESULT_ACCOUNTS_MISSING`, сообщение содержит `58.03`
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Плоский список счетов для фильтров шахматки
|
||||||
|
`GET /api/v1/directories/accounts` SHALL возвращать пользователю с `DIRECTORY_VIEW` все строки
|
||||||
|
`chart_of_account` одним массивом в естественном порядке кодов. Каждая строка SHALL содержать `id`, `code`,
|
||||||
|
`parent_id`, `name`, `kind` (`A`/`P`/`AP`/null), `row_type`, `is_off_balance`, `is_debit`, `is_credit`;
|
||||||
|
корневые счета имеют `parent_id = null`. `is_debit = true` для `A` и `AP`, `is_credit = true` для `P` и `AP`;
|
||||||
|
при `kind = null` оба признака ложны.
|
||||||
|
|
||||||
|
#### Scenario: Счета разных уровней
|
||||||
|
- **WHEN** в плане счетов есть счёт, субсчёт и аналитика
|
||||||
|
- **THEN** ответ содержит все три строки без вложенности, а `parent_id` указывает на родительскую строку
|
||||||
|
|
||||||
|
#### Scenario: Нет права на справочник
|
||||||
|
- **WHEN** пользователь без `DIRECTORY_VIEW` запрашивает список счетов
|
||||||
|
- **THEN** ответ 403
|
||||||
4
openspec/changes/intercompany-matrix/tasks.md
Normal file
4
openspec/changes/intercompany-matrix/tasks.md
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
## 1. Реализация
|
||||||
|
|
||||||
|
Реализация уже выполнена автором в PR RSHB/arm-core#114 (AURORA-1537); задач плана нет. Доработки по замечаниям ревью —
|
||||||
|
AURORA-1651, AURORA-1652, AURORA-1653.
|
||||||
@ -145,15 +145,3 @@ ORM-модель `audit_event` MUST NOT использоваться для `UPD
|
|||||||
#### Scenario: Автоматический переход
|
#### Scenario: Автоматический переход
|
||||||
- **WHEN** подтверждение документа перевело версию в `DATA_CORRECTION`
|
- **WHEN** подтверждение документа перевело версию в `DATA_CORRECTION`
|
||||||
- **THEN** событие `CONSOLIDATION_STATUS_CHANGE` с `user_id` подтвердившего, `before_value.status = INITIATED`
|
- **THEN** событие `CONSOLIDATION_STATUS_CHANGE` с `user_id` подтвердившего, `before_value.status = INITIATED`
|
||||||
|
|
||||||
### Requirement: Аудит экспорта шахматки
|
|
||||||
Каждый успешный `GET …/intercompany-matrix/all/export` и `POST …/intercompany-matrix/manual/export`
|
|
||||||
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}`. `mode` — `ALL` или `MANUAL`, массивы счетов — ID из запроса (в ALL пустые),
|
|
||||||
`cells` — число ячеек. Выгрузка сводной ОСВ продолжает писать собственное событие `EXPORT`.
|
|
||||||
|
|
||||||
#### Scenario: Две выгрузки
|
|
||||||
- **WHEN** пользователь дважды выгружает ту же шахматку
|
|
||||||
- **THEN** в `audit_event` две разные записи `EXPORT`, каждая со своим payload и числом ячеек
|
|
||||||
|
|||||||
@ -162,51 +162,3 @@ filename="svod-osv.xlsx"; filename*=UTF-8''svod-osv_<ГК>_<год>-<кварт
|
|||||||
- **WHEN** экспорт версии с 3 строками и 1 пробелом открыт `openpyxl`
|
- **WHEN** экспорт версии с 3 строками и 1 пробелом открыт `openpyxl`
|
||||||
- **THEN** лист «Сводная ОСВ» содержит заголовок и 3 строки, суммы — числа с двумя знаками, равные API; лист «Пробелы»
|
- **THEN** лист «Сводная ОСВ» содержит заголовок и 3 строки, суммы — числа с двумя знаками, равные API; лист «Пробелы»
|
||||||
— одну строку с `Q2`; `audit_event(EXPORT)` с `after_value.rows = 3`
|
— одну строку с `Q2`; `audit_event(EXPORT)` с `after_value.rows = 3`
|
||||||
|
|
||||||
### Requirement: Шахматка по сводной ОСВ версии
|
|
||||||
`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
|
|
||||||
отвечать одинаковым 404 `NOT_FOUND`.
|
|
||||||
|
|
||||||
Ответ SHALL содержать `companies[] {id, name}` для всего периметра и `cells[]` для каждой упорядоченной пары
|
|
||||||
разных компаний: `company_a_id`, `company_b_id`, `amount_a`, `amount_b`, `variance_amount`, `variance_percent`,
|
|
||||||
`exceeds_threshold`, `one_sided`, `skipped_osv_entry_count`, `osv_entry_ids_debit`, `osv_entry_ids_credit`.
|
|
||||||
Для пары `(A, B)` `amount_a` — сумма `closing_debit` строк A с контрагентом B по дебетовым счетам,
|
|
||||||
`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 исходных записей соответствующей стороны.
|
|
||||||
|
|
||||||
#### Scenario: Пустой периметр и отсутствие ОСВ
|
|
||||||
- **WHEN** у версии нет членов периметра
|
|
||||||
- **THEN** `companies = []`, `cells = []`; если компании есть, но ОСВ по выбранным счетам нет, ячейки присутствуют с нулевыми суммами
|
|
||||||
|
|
||||||
#### Scenario: Только одна сторона пары
|
|
||||||
- **WHEN** у A есть строка ОСВ с контрагентом B, а у B встречной строки нет
|
|
||||||
- **THEN** для `(A, B)` `one_sided = true`, `variance_percent = null`, соответствующий массив `osv_entry_ids_*` пуст
|
|
||||||
|
|
||||||
#### Scenario: Счёт не сопоставлен
|
|
||||||
- **WHEN** у пары есть строка свода с `account_id = null` и двумя `entry_ids`
|
|
||||||
- **THEN** она не входит в суммы, `skipped_osv_entry_count` обеих направленных ячеек пары увеличивается на 2
|
|
||||||
|
|
||||||
#### Scenario: Эталон «Русский бисквит»
|
|
||||||
- **WHEN** матрица строится по ОСВ РБ и Пекарни Счастливочной из эталонных фикстур
|
|
||||||
- **THEN** суммы сравниваются с эталоном только для точных пар счетов и компаний; общая сумма элиминации эталона не приравнивается к минимуму по паре в API (решение владельца 08.10.2026)
|
|
||||||
|
|
||||||
### Requirement: XLSX шахматки
|
|
||||||
`GET …/intercompany-matrix/all/export` и `POST …/intercompany-matrix/manual/export` SHALL использовать
|
|
||||||
те же режимы, тело и права, что и соответствующие JSON-маршруты. Ответ — XLSX с именем
|
|
||||||
`intercompany-matrix_<ГК>_<год>-<квартал>_v<n>.xlsx`, шапкой (ГК, период, версия, единицы, режим,
|
|
||||||
выбранные счета) и строками разницы, дебета, кредита и процента расхождения для каждой пары.
|
|
||||||
|
|
||||||
#### Scenario: Экспорт выбранных счетов
|
|
||||||
- **WHEN** пользователь выгружает MANUAL для ГК «Русский бисквит» за 2024 Q1, версия 1
|
|
||||||
- **THEN** имя содержит `Русский бисквит_2024-Q1_v1.xlsx`, шапка содержит выбранные счета, ячейки процента показывают два знака и символ `%` без повторного масштабирования
|
|
||||||
|
|||||||
@ -223,31 +223,3 @@ account, status, missing_quarters, document_ids}`: `document_type` и `account`
|
|||||||
#### Scenario: Невидимая версия при занятой строке
|
#### Scenario: Невидимая версия при занятой строке
|
||||||
- **WHEN** версию чужого филиала держит `FOR UPDATE` другая транзакция, пользователь без видимости вызывает `POST …/lock`
|
- **WHEN** версию чужого филиала держит `FOR UPDATE` другая транзакция, пользователь без видимости вызывает `POST …/lock`
|
||||||
- **THEN** 404 `VERSION_NOT_FOUND` без ожидания
|
- **THEN** 404 `VERSION_NOT_FOUND` без ожидания
|
||||||
|
|
||||||
### Requirement: Правила элиминации версии
|
|
||||||
`GET /api/v1/consolidations/versions/{version_id}/elimination-rules` (`CONSOLIDATION_VIEW`, policy по ГК)
|
|
||||||
SHALL возвращать все правила версии массивом. Каждое правило SHALL содержать `rule_no`, `version_no`,
|
|
||||||
`statement_type`, `side_1` (`D`/`K`), `measure` (`closing`/`turnover`), `company_1`, `company_2`;
|
|
||||||
каждая сторона SHALL содержать `accounts: UUID[]` и `excluded: UUID[]` до раскрытия иерархии счетов.
|
|
||||||
Невидимая и несуществующая версия SHALL отвечать одинаковым 404 `NOT_FOUND`.
|
|
||||||
|
|
||||||
#### Scenario: Правило с исключением
|
|
||||||
- **WHEN** у правила первая сторона включает счёт 76 и исключает 76.07.5
|
|
||||||
- **THEN** ID 76 находится в `company_1.accounts`, ID 76.07.5 — в `company_1.excluded`
|
|
||||||
|
|
||||||
### Requirement: Результирующая таблица ВГО
|
|
||||||
`GET /api/v1/consolidations/versions/{version_id}/intercompany-matrix/result-table`
|
|
||||||
(`CONSOLIDATION_VIEW`, policy) SHALL возвращать `{companies[], articles[]}`. Компании — все члены периметра
|
|
||||||
по имени; статья содержит `code`, `name`, `amounts[] {company_id, amount}` и `group_total`.
|
|
||||||
Пять фиксированных статей §6: дебиторская и кредиторская задолженность, финансовые вложения,
|
|
||||||
долгосрочные и краткосрочные займы. Сумма компании SHALL считаться по всем остальным компаниям периметра
|
|
||||||
из конечного сальдо нужной стороны и выбранных счетов статьи; `group_total` — сумма по компаниям.
|
|
||||||
Отсутствие любого необходимого кода счёта SHALL давать 422 `RESULT_ACCOUNTS_MISSING` с перечнем кодов.
|
|
||||||
|
|
||||||
#### Scenario: Компания без ОСВ
|
|
||||||
- **WHEN** в периметре есть компания без подходящих строк ОСВ
|
|
||||||
- **THEN** она есть в `companies`, её сумма по каждой статье — строка `"0"`
|
|
||||||
|
|
||||||
#### Scenario: Нет обязательного счёта
|
|
||||||
- **WHEN** в плане счетов отсутствует счёт 58.03
|
|
||||||
- **THEN** ответ 422 `RESULT_ACCOUNTS_MISSING`, сообщение содержит `58.03`
|
|
||||||
|
|||||||
@ -189,18 +189,3 @@ SHALL выполняться в одной транзакции с запись
|
|||||||
#### Scenario: Ошибка внутри мутации
|
#### Scenario: Ошибка внутри мутации
|
||||||
- **WHEN** при переводе ЮЛ закрытие членства завершается ошибкой БД
|
- **WHEN** при переводе ЮЛ закрытие членства завершается ошибкой БД
|
||||||
- **THEN** ЮЛ остаётся в прежней ГК, строк истории и аудита нет
|
- **THEN** ЮЛ остаётся в прежней ГК, строк истории и аудита нет
|
||||||
|
|
||||||
### Requirement: Плоский список счетов для фильтров шахматки
|
|
||||||
`GET /api/v1/directories/accounts` SHALL возвращать пользователю с `DIRECTORY_VIEW` все строки
|
|
||||||
`chart_of_account` одним массивом в естественном порядке кодов. Каждая строка SHALL содержать `id`, `code`,
|
|
||||||
`parent_id`, `name`, `kind` (`A`/`P`/`AP`/null), `row_type`, `is_off_balance`, `is_debit`, `is_credit`;
|
|
||||||
корневые счета имеют `parent_id = null`. `is_debit = true` для `A` и `AP`, `is_credit = true` для `P` и `AP`;
|
|
||||||
при `kind = null` оба признака ложны.
|
|
||||||
|
|
||||||
#### Scenario: Счета разных уровней
|
|
||||||
- **WHEN** в плане счетов есть счёт, субсчёт и аналитика
|
|
||||||
- **THEN** ответ содержит все три строки без вложенности, а `parent_id` указывает на родительскую строку
|
|
||||||
|
|
||||||
#### Scenario: Нет права на справочник
|
|
||||||
- **WHEN** пользователь без `DIRECTORY_VIEW` запрашивает список счетов
|
|
||||||
- **THEN** ответ 403
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user