From 9e2e9f55df6596112338afd1e5c9f58297017fd5 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Thu, 29 Oct 2026 23:20:36 +0400 Subject: [PATCH] =?UTF-8?q?=D0=9A=D0=BE=D0=BD=D1=82=D1=80=D0=B0=D0=BA?= =?UTF-8?q?=D1=82=20=D0=BA=D0=BE=D1=80=D1=80=D0=B5=D0=BA=D1=82=D0=B8=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=D0=BE=D0=BA:=20=D0=BE=D1=82=D0=B2=D0=B5=D1=82?= =?UTF-8?q?=D1=8B=20=D0=9B=D1=8B=D1=81=D0=B5=D0=BD=D0=BA=D0=BE=2029.10=20?= =?UTF-8?q?=D0=B8=20=D1=80=D0=B0=D0=B7=D0=B4=D0=B5=D0=BB=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D1=81=201751=20(#186)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - отзыв корректировки и status в PUT убраны: сохранение сразу APPLIED (ответ п. 9); - «не учитывать в КФО» — только у нераспределённой строки, у строк агрегата excluded нет; EXCLUDE/INCLUDE строки агрегата — 422 ENTRY_ALLOCATED, ASSIGN исключённой — 422 ENTRY_EXCLUDED; UNASSIGN уводит строку в нераспределённые (ответ п. 4); - у нераспределённой строки — line_code, строка баланса владельца (ответ п. 11); - vgo_assignment — только ручные решения (action ASSIGN | UNASSIGNED, is_exclude, side), автоматика — чистая функция 1751; кто что делает 1540/1751 ([allocation-split]); - экспорт: один лист, имя Корректировки_<ГК>_<год>_<квартал>.xlsx, как в WIP #208; - снято «ждёт п. N» там, где ответ есть; граница ровно 5 % — открытый вопрос. Co-Authored-By: Claude Opus 5.5 --- dev/api-contract-corrections.md | 122 ++++++++++++---------- dev/api-contract-corrections.openapi.yaml | 66 +++--------- 2 files changed, 81 insertions(+), 107 deletions(-) diff --git a/dev/api-contract-corrections.md b/dev/api-contract-corrections.md index eec73f3..f28f62e 100644 --- a/dev/api-contract-corrections.md +++ b/dev/api-contract-corrections.md @@ -5,12 +5,14 @@ Источники, в порядке приоритета: 1. Аналитика Лысенко: DOC-272613 «Корректировка данных агрегатом», ответы на вопросы 1–7 (комментарий от 27.10.2026 в - AURORA-1395), DOC-274230 «Правила пар счетов». + AURORA-1395), ответы на `[design-23-10-check]` (комментарий #9920570 от 29.10.2026 там же), DOC-274230 «Правила пар + счетов». 2. Дизайн таблицы корректировок от 23.10.2026 (вложение к DOC-272613). 3. ТЗ 6.8, Прил. 9 — только там, где аналитика молчит. -Открытые вопросы к аналитику — комментарий `[design-23-10-check]` в AURORA-1395 (12 пунктов + 3 по ТЗ) и в AURORA-1396 -(ОФР). Места контракта, которые от них зависят, помечены **(ждёт п. N)**. +На `[design-23-10-check]` (12 пунктов + 3 по ТЗ) Лысенко ответил 29.10; ссылки вида «ответ п. N» — на этот комментарий. +Открыты: ОФР (AURORA-1396) и граница ровно 5 % (§2). Разделение работ с бэкендом — комментарии `[allocation-split]` в +AURORA-1540 и AURORA-1751 (29.10): разнесение строк и решения по строкам — 1751, корректировка и таблица — 1540. ## 1. Общее @@ -24,7 +26,7 @@ - проценты — строки с двумя знаками (`"394.27"`), `null` — процент не определён. **Правки.** Порядок проверок: право (403) → видимость (404) → версия редактируема (409) → операция. Какие статусы версии -разрешают корректировки, решает сервер (по §9; статус «Верифицирован» и закрытые — только чтение); фронт не повторяет +разрешают корректировки, решает сервер (по §9; статус «Верифицирован» и закрытые — только чтение, подтверждено ответом по ТЗ); фронт не повторяет это правило, а читает `editable` / `read_only_reason` из ответа таблицы. Ошибки правок версии — те же коды, что везде: 409 `VERSION_NOT_EDITABLE`, `LOCK_REQUIRED`, `LOCKED_BY_OTHER`. @@ -36,7 +38,8 @@ кредитовым сальдо одновременно даёт две позиции — `side = "D"` и `side = "K"`. - **Агрегат** — правило × компания 1 × компания 2 (× тип отчётности, он следует из правила). Отдельно не хранится, считается при каждом запросе (DOC-272613 §3). Ключ агрегата — `(rule_id, company_1_id, company_2_id)`. -- **Разнесение** (DOC-272613 «Распределение строк по правилам», ответы 1(б), 2(а)): +- **Разнесение** (DOC-272613 «Распределение строк по правилам», ответы 1(б), 2(а)) — чистая функция от версии консолидации, + считается на каждый запрос и не хранится (DECISION-5); хранятся только ручные решения аналитика (`vgo_assignment`, §5): 1. ручное решение по строке (`vgo_assignment`) важнее автоматики; 2. строка подходит под одно правило — в это правило, даже без встречной стороны; 3. под несколько правил и у второй компании есть строки по счетам другой стороны ровно одного из них — в это правило; @@ -45,14 +48,19 @@ Каждая строка учитывается не более чем в одном агрегате своего типа отчётности. - **Сторона агрегата.** Сторона 1 — строки компании 1 по счетам стороны 1 правила, сторона 2 — строки компании 2 по счетам - стороны 2. Сумма стороны — сумма строк, не отмеченных «не включать в КФО». + стороны 2. Сумма стороны — сумма всех её строк: агрегат учитывается в КФО целиком, лишнюю строку убирают из правила, и она + становится нераспределённой (ответ п. 4). +- **«Не учитывать в КФО»** — отметка только у нераспределённой строки (ответ п. 4: «функционал нужен для нераспределённых + строк, чтобы суммы совпадали в КФО»). У строк агрегата отметки нет. - **Отклонение** (DOC-272613 «Расчёт»): `deviation_amount = |amount_1 − amount_2|`; `deviation_percent = deviation_amount / min(amount_1, amount_2) × 100`, если меньшая сторона > 0, иначе `null`. - **Порог: строго меньше 5 % — в допуске, от 5 % включительно — на проверку** (`exceeds_threshold`). Внимание: нынешний - `calculate_rule_totals` считает `> 5`; по аналитике нужно `≥ 5`. + **Порог: строго меньше 5 % — в допуске, от 5 % включительно — на проверку** (`exceeds_threshold`, DOC-272613). + **Открытый вопрос:** в описании AURORA-1540 и в WIP API (#208) ровно 5 % — в допуске (`> 5`); аналитик на это не отвечал. + Отклонение у отдельных строк агрегата не считается (ответ п. 3). - **Корректировка** — решение аналитика по агрегату: какие суммы и из каких статей ББ (ОФР) исключить по каждой стороне, - комментарий. Делается **только на уровне агрегата** (DOC-272613); на строку и на нераспределённую строку корректировка не - вводится **(ждёт п. 2)**. Сохранённая корректировка не меняет суммы сторон — они всегда считаются по строкам. + комментарий. Делается **только на уровне агрегата** (DOC-272613, ответ п. 2); на строку и на нераспределённую строку + корректировка не вводится. Можно корректировать и агрегат в допуске 5 %, и с отклонением больше 5 % — с обязательным + комментарием (ответ п. 8 и по ТЗ). Сохранённая корректировка не меняет суммы сторон — они всегда считаются по строкам. ## 3. Эндпоинты @@ -60,11 +68,10 @@ |---|---|---|---|---| | `GET /consolidations/versions/{version_id}/corrections` | VIEW | query `statement_type` (`balance` по умолчанию / `income`) | 200 `CorrectionTableOut` | 404 `NOT_FOUND` | | `PUT /consolidations/versions/{version_id}/corrections/aggregates/{rule_id}/{company_1_id}/{company_2_id}` | EDIT | `CorrectionIn` | 200 `CorrectionAggregateOut` | 404 `NOT_FOUND` (версия, правило или агрегата нет в версии), 409 правок версии, 409 `CORRECTION_VERSION_CONFLICT`, 422 `COMMENT_REQUIRED`, 422 `EMPTY_CORRECTION`, 422 `INVALID_LINE_CODE` | -| `POST /consolidations/versions/{version_id}/corrections/aggregates/{rule_id}/{company_1_id}/{company_2_id}/revoke` | EDIT | `CorrectionRevokeIn {comment}` | 200 `CorrectionAggregateOut` | 404 `NOT_FOUND`, 409 правок версии, 409 `CORRECTION_NOT_APPLIED`, 422 `COMMENT_REQUIRED` | -| `POST /consolidations/versions/{version_id}/corrections/allocations` | EDIT | `AllocationIn` | 200 `CorrectionTableOut` | 404 `NOT_FOUND` (версия или правило), 409 правок версии, 422 `ENTRY_NOT_IN_VERSION`, 422 `RULE_NOT_APPLICABLE`, 422 `RULE_REQUIRED` | +| `POST /consolidations/versions/{version_id}/corrections/allocations` | EDIT | `AllocationIn` | 200 `CorrectionTableOut` | 404 `NOT_FOUND` (версия или правило), 409 правок версии, 422 `ENTRY_NOT_IN_VERSION`, 422 `RULE_NOT_APPLICABLE`, 422 `RULE_REQUIRED`, 422 `ENTRY_ALLOCATED`, 422 `ENTRY_EXCLUDED` | | `GET /consolidations/versions/{version_id}/corrections/export` | VIEW | query `statement_type` | файл xlsx | 404 `NOT_FOUND` | -Импорта xlsx в контракте нет — ждёт ответа по ТЗ 6.8 (третий пункт по ТЗ в `[design-23-10-check]`). +Импорта xlsx нет — не нужен (ответ по ТЗ). Отзыва корректировки (`REVOKED`) нет — её меняют повторным сохранением (ответ п. 9). ### 3.1 `GET …/corrections` — таблица @@ -86,42 +93,36 @@ - `line_code` — код строки формы ББ (ОФР) этой компании; не из формы — 422 `INVALID_LINE_CODE` с `detail.line_code`. Сумма — строка ≥ 0, не больше 16 цифр до запятой и двух после (`numeric(18,2)`); это уменьшение статьи. Иное — 422 массивом pydantic. -- `comment` обязателен, пустой после обрезки — 422 `COMMENT_REQUIRED` (DOC-272613, ТЗ 6.8) **(ждёт п. 6)**; не длиннее - 1000 символов (как комментарий смены статуса), длиннее — 422 массивом pydantic. То же у `revoke`. -- `status` — `APPLIED` (по умолчанию) или `DRAFT`. В дизайне одна кнопка «Сохранить корректировки», фронт шлёт `APPLIED` - **(ждёт п. 9)**. +- `comment` обязателен всегда, пустой после обрезки — 422 `COMMENT_REQUIRED` (DOC-272613, ответ п. 6); не длиннее + 1000 символов (как комментарий смены статуса), длиннее — 422 массивом pydantic. +- Сохранение сразу применяет корректировку — `status = APPLIED`; поля `status` в теле нет (ответ п. 9). `DRAFT` есть в модели, + сервер его пока не выставляет. - `expected_version_no` — номер редакции действующей корректировки, которую аналитик видел (`correction.version_no`). - Действующей (`DRAFT` / `APPLIED`) нет — в том числе после отзыва — `null`. Не совпал с текущим, или `null`, а действующая - есть — 409 `CORRECTION_VERSION_CONFLICT` (кто-то сохранил раньше; фронт перечитывает таблицу). Новая корректировка после - отзыва — новые записи `correction`; отозванные остаются в истории. + Действующей нет — `null`. Не совпал с текущим, или `null`, а действующая есть — 409 `CORRECTION_VERSION_CONFLICT` (кто-то + сохранил раньше; фронт перечитывает таблицу). - `line_code` — один код. У правил 1 и 2 статья стороны 2 составная (`article_2_line = "1450/1550"`, `"1310/1350"`, коды через «/»); в корректировку пишется один из кодов. -- Корректировку можно сохранить, даже если стороны не сошлись; ответ показывает `balance` (контроль распределения) - **(ждёт п. 8)**. Ограничения «только при отклонении > 5 %» нет — в аналитике его нет (вопрос по ТЗ открыт). +- Корректировку можно сохранить, даже если стороны не сошлись; ответ показывает `balance` (контроль распределения). В КФО по + выбранным статьям уходит меньшая сумма (ответ п. 8). Ограничения «только при отклонении > 5 %» нет (ответ по ТЗ). - Каждое сохранение увеличивает `version_no`; аудит `CORRECTION_CREATE` (первая) / `CORRECTION_EDIT` (следующие) с `before` / `after`; результат КФО помечается устаревшим (AURORA-1417). Ответ — пересчитанный агрегат. -### 3.3 `POST …/revoke` — отозвать корректировку - -Действующая корректировка агрегата получает статус `REVOKED` и остаётся в истории; комментарий обязателен. Действующей -нет — 409 `CORRECTION_NOT_APPLIED`. Аудит `CORRECTION_REVOKE`. **(ждёт п. 9 — как отзывать в интерфейсе)** - -### 3.4 `POST …/allocations` — решение по строке +### 3.3 `POST …/allocations` — решение по строке Одно действие над одной позицией: строка сводной ОСВ (`entry_ids` — как в `CorrectionEntryOut`) **и сторона** (`side`). Сторона нужна потому, что строка с дебетовым и кредитовым сальдо одновременно даёт две позиции (§2), и решение по одной не -должно задевать другую. Хранение — `vgo_assignment` (DOC-272613 «Ручное добавление и перенос строк»), одно действующее -решение на `osv_entry` и сторону в версии и типе отчётности, повторное решение обновляет запись. Позиции с такой стороной у -строки нет — 422 `ENTRY_NOT_IN_VERSION`. +должно задевать другую. Хранение — `vgo_assignment` (DOC-272613 «Ручное добавление и перенос строк», §5): только ручные +решения, одно действующее на `osv_entry` и сторону в версии и типе отчётности, повторное решение обновляет запись. Позиции с +такой стороной у строки нет — 422 `ENTRY_NOT_IN_VERSION`. | `action` | Что делает | `vgo_assignment` | |---|---|---| -| `ASSIGN` | отнести к правилу `rule_id` или перенести в него из другого агрегата | `action = ASSIGN`, `rule_id` | -| `UNASSIGN` | убрать из агрегата; автоматика её обратно не вернёт | `action = UNASSIGNED`, `rule_id = NULL` | -| `EXCLUDE` | «Не включать в КФО»: строка не входит в суммы сторон и элиминацию, остаётся видна с отметкой | `action = EXCLUDE`, прежний `rule_id` сохраняется | -| `INCLUDE` | снять «Не включать в КФО»: вернуть прежнее ручное отнесение, если оно было, иначе — автоматика | `action = ASSIGN` с прежним `rule_id` или запись удаляется | +| `ASSIGN` | отнести к правилу `rule_id` или перенести в него из другого агрегата; исключённую из КФО нераспределённую строку — нельзя, 422 `ENTRY_EXCLUDED` (сначала `INCLUDE`) | `action = ASSIGN`, `rule_id` | +| `UNASSIGN` | убрать из агрегата — строка становится нераспределённой (`reason = UNASSIGNED`), автоматика её обратно не вернёт (ответ п. 4) | `action = UNASSIGNED`, `rule_id = NULL` | +| `EXCLUDE` | «Не учитывать в КФО» — **только у нераспределённой строки**; строка остаётся видна с отметкой. У строки агрегата — 422 `ENTRY_ALLOCATED` (ответ п. 4) | `is_exclude = true`; записи не было — она создаётся ради отметки, `reason` строки не меняется | +| `INCLUDE` | снять «Не учитывать в КФО»; у строки агрегата — 422 `ENTRY_ALLOCATED` | `is_exclude = false`; запись, созданная только ради отметки, удаляется | | `RESET` | снять ручное решение — строка снова разносится автоматически | запись удаляется | Проверки `ASSIGN` (DOC-272613, шаг 2; ответ 3(а)): правило той же версии правил и того же типа отчётности; счёт и сторона @@ -133,12 +134,14 @@ `composition_changed = true` (ответ 5). Ответ — вся таблица (решение меняет и агрегаты, и нераспределённые строки). Аудит: в `audit_action` DBML v5.23 подходящего действия нет — нужно новое (например, `VGO_ASSIGNMENT_CHANGE`) вместе с -таблицей `vgo_assignment` в новой версии DBML. +таблицей `vgo_assignment` в DBML v5.24 (AURORA-1751). -### 3.5 `GET …/export` — xlsx +### 3.4 `GET …/export` — xlsx -Таблица выбранного типа отчётности, как на экране: агрегаты с колонками Дебет / Кредит / Контроль / Корректировка, затем -нераспределённые строки; суммы в рублях. Выбор колонок и фильтры в первой версии не передаются. Аудит `EXPORT`. +Таблица выбранного типа отчётности одним листом, как на экране: строка агрегата с колонками Дебет / Кредит / Контроль / +Корректировка, под ней его строки ОСВ (группировка, колонка «Правило» у них пустая), затем нераспределённые строки и итог; +суммы в рублях. Имя файла — `Корректировки_<название ГК>_<год>_<квартал>.xlsx` в `Content-Disposition` (`filename*`, ASCII +запасное `corrections.xlsx`), как в WIP API (#208). Выбор колонок и фильтры в первой версии не передаются. Аудит `EXPORT`. ## 4. Схемы @@ -174,8 +177,8 @@ deviation_percent percent | null к меньшей стороне; nu exceeds_threshold bool deviation_percent ≥ 5, или одна сторона пуста при ненулевой другой status "AUTO" | "DISCREPANCY" | "ONE_SIDED" | "MANUAL" как EliminationTotalItemOut eliminated_amount money автоматическая сумма элиминации агрегата (§5), справочно -composition_changed bool состав или суммы изменились после применённой корректировки (ответ 5) -correction CorrectionOut | null действующая (APPLIED) или черновик (DRAFT); отозванные не показываются +composition_changed bool состав или суммы изменились после применённой корректировки (ответ 5; ответ п. 10 — подсвечивать) +correction CorrectionOut | null действующая корректировка (APPLIED; DRAFT — зарезервирован) ``` **`AggregateSideOut`** `{article_line (статья правила), accounts[] (коды счетов строк, вошедших в сторону; не весь перечень @@ -190,22 +193,21 @@ account_code string side "D" | "K" amount money сальдо (оборот) строки по этой стороне decision "AUTO" | "ASSIGN" как попала в агрегат -excluded bool «Не включать в КФО»; в сумму стороны не входит candidate_rule_ids[] uuid другие правила, под которые строка подходит по счёту и стороне (для «Перенести в правило…») ``` **`UnallocatedEntryOut`** — нераспределённая строка: ``` entry_ids[], owner {id, name}, counterparty {id, name}, account_code, side ("D"|"K"), amount +line_code string | null строка баланса владельца, к которой привязан счёт (ответ п. 11); null — не определена candidate_rule_ids[] uuid правила, под которые строка подходит по счёту и стороне reason "MULTIPLE_RULES" | "UNASSIGNED" несколько правил / убрана аналитиком -excluded bool +excluded bool «Не учитывать в КФО» (только у нераспределённой) ``` -Строка баланса владельца у нераспределённой строки не передаётся (DOC-272613 §4.3) **(ждёт п. 11)**. **`CorrectionOut`** ``` -status "DRAFT" | "APPLIED" +status "DRAFT" | "APPLIED" сохранение даёт APPLIED version_no int debit[] CorrectionLineOut статьи стороны 1, первая — основная credit[] CorrectionLineOut статьи стороны 2, первая — основная @@ -226,9 +228,7 @@ remainder_1 (сумма стороны 1 − debit_total), remainder_2 (сумм correction_debit, correction_credit}`. При фильтрах на экране фронт считает итоги видимых строк сам. **`CorrectionIn`** `{debit[] (CorrectionLineIn), credit[] (CorrectionLineIn), comment (string), reason_code? (string|null), -status? ("APPLIED" по умолчанию | "DRAFT"), expected_version_no (int|null)}`; `CorrectionLineIn {line_code, amount}`. - -**`CorrectionRevokeIn`** `{comment}`. +expected_version_no (int|null)}`; `CorrectionLineIn {line_code, amount}`. **`AllocationIn`** `{statement_type, entry_ids[] (≥ 1), side ("D"|"K"), action ("ASSIGN"|"UNASSIGN"|"EXCLUDE"|"INCLUDE"|"RESET"), rule_id? (uuid, для ASSIGN)}`. @@ -239,14 +239,20 @@ rule_id? (uuid, для ASSIGN)}`. статьи; поля `debit_*` заполняются у записей стороны 1, `credit_*` — у стороны 2; `legal_entity_id` — компания стороны, `counterparty_id` — другая компания агрегата. `deviation_*` записываются на момент сохранения — по ним считается `composition_changed`. -- `vgo_assignment` — новая таблица (DOC-272613): `consolidation_version_id, statement_type, osv_entry_id, side (D | K), - rule_id (NULL), action (ASSIGN | UNASSIGNED | EXCLUDE), created_by/at, updated_by/at`, уникальность - `(consolidation_version_id, statement_type, osv_entry_id, side)`. Поля `side` в DOC-272613 нет — добавлено контрактом, - иначе у строки с дебетовым и кредитовым сальдо решения по двум позициям неразличимы. Нужна новая версия DBML и - `audit_action` для решений по строкам. -- Разнесение — чистая функция в `api/src/domain/` (как `elimination_packing`), без хранения агрегатов. Нынешняя упаковка - считает каждое правило отдельно (TODO в `intercompany_matrix.build_pairs`) — для таблицы корректировок строка должна - попадать не более чем в одно правило. +- **Кто что делает** (`[allocation-split]`, 29.10): AURORA-1751 — разнесение строк (чистая функция), ручные решения + (`POST …/allocations`, `vgo_assignment`, аудит, DBML v5.24); AURORA-1540 — таблица, корректировка, экспорт поверх функции + 1751 (источник агрегатов в WIP #208 — `CorrectionAggregateSource`, его реализация вызывает функцию 1751). +- `vgo_assignment` — **только ручные решения**, автоматическое разнесение не хранится (DECISION-5): иначе ручное решение не + отличить от автоматического и при пересчёте после замены ОСВ оно затрётся. Поля (миграция AURORA-1751 + сторона): + `consolidation_version_id, statement_type, osv_entry_id, side (D | K), rule_id (NULL), action (ASSIGN | UNASSIGNED), + is_exclude (bool), created_by/at, updated_by/at`; `ASSIGN` — с `rule_id`, `UNASSIGNED` — без; уникальность + `(consolidation_version_id, statement_type, osv_entry_id, side)`. Поля `side` в DOC-272613 и в миграции нет — добавлено + контрактом, иначе у строки с дебетовым и кредитовым сальдо решения по двум позициям неразличимы. Нужны DBML v5.24, ORM-модель + в `src/models` и `audit_action` для решений по строкам. +- Разнесение — чистая функция в `api/src/domain/` (как `elimination_packing`): строки свода × правила × ручные решения → + агрегаты и нераспределённые строки (с `line_code` и `candidate_rule_ids`). Нынешняя упаковка считает каждое правило + отдельно (TODO в `intercompany_matrix.build_pairs`) — для таблицы корректировок строка должна попадать не более чем в одно + правило. - Правила ББ 1 и 2 (DOC-274230) имеют свой расчёт и не наследуют min; что идёт в `eliminated_amount` по ним — по DOC-274230. - ОФР: тот же ответ с `statement_type = income` **(ждёт ответа в AURORA-1396)**; до него сервер может отдавать пустые `aggregates` и `unallocated` для `income`. @@ -270,7 +276,7 @@ rule_id? (uuid, для ASSIGN)}`. "company_1": {"id": "a…", "name": "ЭПТ"}, "company_2": {"id": "b…", "name": "ИЛС"}, "side_1": {"article_line": "1230", "accounts": ["62.01"], "amount": "1000000.00", "entries": [{"entry_ids": ["e1…"], "legal_entity_id": "a…", "counterparty_id": "b…", "account_code": "62.01", - "side": "D", "amount": "1000000.00", "decision": "AUTO", "excluded": false}]}, + "side": "D", "amount": "1000000.00", "decision": "AUTO"}]}, "side_2": {"article_line": "1520", "accounts": ["60.02"], "amount": "202320.00", "entries": ["…"]}, "deviation_amount": "797680.00", "deviation_percent": "394.27", "exceeds_threshold": true, "status": "DISCREPANCY", "eliminated_amount": "0.00", "composition_changed": false, @@ -283,7 +289,7 @@ rule_id? (uuid, для ASSIGN)}`. "updated_by": {"id": "u…", "name": "Иванов И. И."}, "updated_at": "2026-10-28T12:00:00+03:00"} }], "unallocated": [{"entry_ids": ["e9…"], "owner": {"id": "c…", "name": "ООО «БСК»"}, - "counterparty": {"id": "d…", "name": "ПМП ООО"}, "account_code": "76.05", "side": "K", + "counterparty": {"id": "d…", "name": "ПМП ООО"}, "account_code": "76.05", "line_code": "1520", "side": "K", "amount": "142878.00", "candidate_rule_ids": ["r6…", "r7…"], "reason": "MULTIPLE_RULES", "excluded": false}], "totals": {"amount_1": "1000000.00", "amount_2": "202320.00", "deviation_amount": "797680.00", "correction_debit": "1000000.00", "correction_credit": "202320.00"} diff --git a/dev/api-contract-corrections.openapi.yaml b/dev/api-contract-corrections.openapi.yaml index 444705d..31f890d 100644 --- a/dev/api-contract-corrections.openapi.yaml +++ b/dev/api-contract-corrections.openapi.yaml @@ -1,7 +1,7 @@ openapi: 3.1.0 info: title: АРМ КА — таблица корректировок - version: 0.1.0-draft + version: 0.2.0-draft description: > Черновик контракта AURORA-1540 (бэкенд) / AURORA-1541 (фронт). Текстовое описание, источники и открытые вопросы — dev/api-contract-corrections.md; оба файла держатся одинаковыми. Деньги — строки с двумя знаками в рублях, проценты — @@ -61,44 +61,16 @@ paths: '409': { $ref: '#/components/responses/DomainError' } '422': { $ref: '#/components/responses/DomainOrValidationError' } - /consolidations/versions/{version_id}/corrections/aggregates/{rule_id}/{company_1_id}/{company_2_id}/revoke: - post: - operationId: revokeCorrection - summary: Отозвать действующую корректировку агрегата - description: > - Статус REVOKED, запись остаётся в истории; аудит CORRECTION_REVOKE. - 409 — правки версии, CORRECTION_NOT_APPLIED; 422 — COMMENT_REQUIRED. - x-permission: CONSOLIDATION_EDIT - parameters: - - $ref: '#/components/parameters/VersionId' - - $ref: '#/components/parameters/RuleId' - - $ref: '#/components/parameters/Company1Id' - - $ref: '#/components/parameters/Company2Id' - requestBody: - required: true - content: - application/json: - schema: { $ref: '#/components/schemas/CorrectionRevokeIn' } - responses: - '200': - description: Пересчитанный агрегат - content: - application/json: - schema: { $ref: '#/components/schemas/CorrectionAggregateOut' } - '401': { $ref: '#/components/responses/Unauthorized' } - '403': { $ref: '#/components/responses/Forbidden' } - '404': { $ref: '#/components/responses/DomainError' } - '409': { $ref: '#/components/responses/DomainError' } - '422': { $ref: '#/components/responses/DomainOrValidationError' } - /consolidations/versions/{version_id}/corrections/allocations: post: operationId: allocateEntry summary: Решение по строке сводной ОСВ (vgo_assignment) description: > - ASSIGN — отнести/перенести в правило; UNASSIGN — убрать из агрегата; EXCLUDE / INCLUDE — «Не включать в КФО» и снятие; - RESET — вернуть к автоматике. 409 — правки версии; 422 — ENTRY_NOT_IN_VERSION, RULE_NOT_APPLICABLE (detail.reason), - RULE_REQUIRED. Ответ — вся таблица. + Только ручные решения (vgo_assignment). ASSIGN — отнести/перенести в правило (исключённую нераспределённую — + ENTRY_EXCLUDED); UNASSIGN — убрать из агрегата в нераспределённые; EXCLUDE / INCLUDE — «Не учитывать в КФО» и + снятие, только у нераспределённой строки (у строки агрегата — ENTRY_ALLOCATED); RESET — вернуть к автоматике. + 409 — правки версии; 422 — ENTRY_NOT_IN_VERSION, RULE_NOT_APPLICABLE (detail.reason), RULE_REQUIRED, ENTRY_ALLOCATED, + ENTRY_EXCLUDED. Ответ — вся таблица. x-permission: CONSOLIDATION_EDIT parameters: - $ref: '#/components/parameters/VersionId' @@ -248,13 +220,14 @@ components: - LOCK_REQUIRED - LOCKED_BY_OTHER - CORRECTION_VERSION_CONFLICT - - CORRECTION_NOT_APPLIED - COMMENT_REQUIRED - EMPTY_CORRECTION - INVALID_LINE_CODE - ENTRY_NOT_IN_VERSION - RULE_NOT_APPLICABLE - RULE_REQUIRED + - ENTRY_ALLOCATED + - ENTRY_EXCLUDED message: { type: string, description: Текст по-русски } additionalProperties: true @@ -331,7 +304,7 @@ components: CorrectionEntryOut: type: object - required: [entry_ids, legal_entity_id, counterparty_id, account_code, side, amount, decision, excluded, candidate_rule_ids] + required: [entry_ids, legal_entity_id, counterparty_id, account_code, side, amount, decision, candidate_rule_ids] properties: entry_ids: { type: array, items: { type: string, format: uuid }, minItems: 1 } legal_entity_id: { type: string, format: uuid } @@ -340,7 +313,6 @@ components: side: { $ref: '#/components/schemas/Side' } amount: { $ref: '#/components/schemas/Money' } decision: { type: string, enum: [AUTO, ASSIGN] } - excluded: { type: boolean } candidate_rule_ids: type: array items: { type: string, format: uuid } @@ -348,23 +320,26 @@ components: UnallocatedEntryOut: type: object - required: [entry_ids, owner, counterparty, account_code, side, amount, candidate_rule_ids, reason, excluded] + required: [entry_ids, owner, counterparty, account_code, line_code, side, amount, candidate_rule_ids, reason, excluded] properties: entry_ids: { type: array, items: { type: string, format: uuid }, minItems: 1 } owner: { $ref: '#/components/schemas/Ref' } counterparty: { $ref: '#/components/schemas/Ref' } account_code: { type: string } + line_code: + type: [string, 'null'] + description: Строка баланса владельца, к которой привязан счёт; null — не определена side: { $ref: '#/components/schemas/Side' } amount: { $ref: '#/components/schemas/Money' } candidate_rule_ids: { type: array, items: { type: string, format: uuid } } reason: { type: string, enum: [MULTIPLE_RULES, UNASSIGNED] } - excluded: { type: boolean } + excluded: { type: boolean, description: «Не учитывать в КФО» — есть только у нераспределённой строки } CorrectionOut: type: object required: [status, version_no, debit, credit, comment, reason_code, balance, updated_by, updated_at] properties: - status: { type: string, enum: [DRAFT, APPLIED] } + status: { type: string, enum: [DRAFT, APPLIED], description: Сохранение даёт APPLIED; DRAFT зарезервирован } version_no: { type: integer } debit: type: array @@ -426,18 +401,11 @@ components: credit: { type: array, items: { $ref: '#/components/schemas/CorrectionLineIn' } } comment: { type: string, maxLength: 1000 } reason_code: { type: [string, 'null'] } - status: { type: string, enum: [APPLIED, DRAFT], default: APPLIED } expected_version_no: type: [integer, 'null'] description: > - Редакция действующей (DRAFT/APPLIED) корректировки, которую видел аналитик; null — действующей нет - (в том числе после отзыва). Несовпадение — 409 CORRECTION_VERSION_CONFLICT. - - CorrectionRevokeIn: - type: object - required: [comment] - properties: - comment: { type: string, maxLength: 1000 } + Редакция действующей корректировки, которую видел аналитик; null — действующей нет. + Несовпадение — 409 CORRECTION_VERSION_CONFLICT. Сохранение сразу применяет корректировку (APPLIED). AllocationIn: type: object