diff --git a/openspec/changes/corrections-table-ui/specs/corrections-table-ui/spec.md b/openspec/changes/corrections-table-ui/specs/corrections-table-ui/spec.md index f6203d8..d10a97b 100644 --- a/openspec/changes/corrections-table-ui/specs/corrections-table-ui/spec.md +++ b/openspec/changes/corrections-table-ui/specs/corrections-table-ui/spec.md @@ -7,7 +7,7 @@ (`unallocated[]`), внизу строку «Итого». Пока идёт загрузка, на месте таблицы SHALL быть индикатор; ошибка API SHALL показываться уведомлением с текстом сервера и кнопкой «Повторить». Пустой ответ SHALL показывать текст «Внутригрупповых остатков между компаниями периметра нет». Разнесение строк, отклонения, порог 5 % и статусы агрегатов фронт SHALL брать из -ответа и не пересчитывать. При `REACT_APP_CORRECTIONS_MOCK=true` те же запросы SHALL обслуживаться фейком контракта в памяти. +ответа и не пересчитывать. #### Scenario: Открытие вкладки - **WHEN** аналитик открывает `…/versions/{id}/correction` @@ -65,8 +65,8 @@ правила из `candidate_rule_ids` ### Requirement: Флажок «Учитывать в КФО» -Флажок нераспределённой строки SHALL отправлять `POST …/corrections/allocations` с `action = EXCLUDE` (снят) или `INCLUDE` -(поставлен) и заменять таблицу ответом. Флажок агрегата SHALL быть отмечен и неактивен с подсказкой «Агрегат всегда +Флажок нераспределённой строки SHALL отправлять `PUT …/vgo-assignments/exclude` с `is_exclude = true` (снят) или `false` +(поставлен) и затем перечитывать таблицу целиком. Флажок агрегата SHALL быть отмечен и неактивен с подсказкой «Агрегат всегда учитывается в КФО; лишнюю строку уберите из правила»; у строк агрегата флажка SHALL NOT быть (ответ Лысенко 29.10, п. 4). #### Scenario: Исключить нераспределённую строку из КФО @@ -98,7 +98,7 @@ ещё статья стороны; «×» — удалить статью). Действия с исходными строками SHALL уходить на сервер сразу и перечитывать агрегат. Без корректировки окно SHALL предлагать по каждой стороне одну статью правила с суммой стороны; у составной статьи («1450/1550») — первый код, второй доступен для выбора. Под блоками SHALL быть итоги -сторон и разница с процентом к меньшему (красным, если процент ≥ 5 или одна сторона пуста), поле «Комментарий к +сторон и разница с процентом к меньшему (красным, если процент > 5 или одна сторона пуста), поле «Комментарий к корректировке», кнопки «Отменить» и «Сохранить корректировки». #### Scenario: Окно агрегата без корректировки @@ -154,7 +154,7 @@ счетов» (только агрегаты), «Нераспределенные записи» (только нераспределённые); единицы и знаки — как в шахматке; «Сбросить фильтры» — когда задан любой фильтр; справа — «Настройка таблицы», «Экспорт», «На весь экран». В заголовках SHALL быть фильтры: «ЮЛ» (подстрока имени, по обеим сторонам), «Строка баланса» (выбор кода), «Счёт» (начало кода, по счетам строки), -«Отклонение, %» («Все» / «≥ 5 %» / «< 5 %» по `exceeds_threshold`). Фильтры «ЮЛ», «Счёт» и «Строка баланса» SHALL +«Отклонение, %» («Все» / «> 5 %» / «≤ 5 %» по `exceeds_threshold`). Фильтры «ЮЛ», «Счёт» и «Строка баланса» SHALL действовать и на нераспределённые строки («Строка баланса» — по `line_code`, строка без него скрывается, её коды есть в выборе); при заданном фильтре «Отклонение, %» нераспределённые строки SHALL скрываться. Составная статья правила (коды через «/») в фильтре «Строка баланса» SHALL давать отдельные коды. Фильтры ничего не нашли — diff --git a/openspec/changes/corrections-table-ui/tasks.md b/openspec/changes/corrections-table-ui/tasks.md index 14925c0..ee77566 100644 --- a/openspec/changes/corrections-table-ui/tasks.md +++ b/openspec/changes/corrections-table-ui/tasks.md @@ -37,15 +37,10 @@ ## 8. Подключение к API (после слияния AURORA-1540) -- [ ] 8.1 Влить `main`, запустить без флага на стенде на реальной версии, пройти сценарии спеки. Отдельно проверить то, что не проверено на фейке: - - полноэкранный режим (уведомления внутри бокса, выпадающие списки); - - 409 и `CORRECTION_VERSION_CONFLICT` в окне и при решении по строке; - - недоступный `localStorage` (приватное окно): окно корректировки работает без черновика; - - «Вернуть к автоматике» на строке внутри агрегата (на нераспределённой фейк не возвращает строку в правило, R-23); - - сценарии спеки, которых нет на данных фейка, — на реальных данных: Сатурн — Архол; Бековский с 62.01 / 60.02 / 62.03 / 76.01; ПМП — Бековский комбинат для «Добавить строку ОСВ»; - - `allocate` с несколькими `entry_ids` и код `VALIDATION_ERROR` — фейк их не воспроизводит (M-10) -- [ ] 8.2 Расхождения ответа с контрактом — правка фейка и типов или замечание в PR 1540; убрать фейк контракта (`correctionsFake.ts` и его тест) и ветку флага в `corrections.ts`; `mockData` и `mockCalc` остаются тестовыми фикстурами (R-27) -- [ ] 8.3 `make check-full`, PR в `main` +- [x] 8.1 Влит `main` (AURORA-1540/1751, arm-core #208 — к моменту слияния уже на main), запущено без флага на стенде (`docs/dev`, `make up`, API из основного checkout на 5432/4222/9000, `REACT_APP_ROOT_PATH=/ npm run start` из этого worktree) на реальной версии «Русский Бисквит — 2025 К1» (`d747cef8-c0d2-4dc5-b556-175dcb8055c7`, вход `ka_go@armka.local`). Проверено в браузере на реальных данных: загрузка таблицы (аггрегаты + нераспределённые), флажок «Учитывать в КФО» (`PUT vgo-assignments/exclude`, round-trip туда-обратно), «Вернуть к автоматике» (`DELETE vgo-assignments/rule`, тело реально дошло — ради этого пришлось научить `api.del` отправлять тело, см. 8.2), «Отнести к правилу» (`PUT vgo-assignments/rule`, проверено прямым вызовом с теми же данными, что шлёт `allocate`, — подменю antd не открывалось под Playwright-автоматизацией, не баг продукта), окно «Корректировать» → `saveCorrection` (реальный агрегат ПЕКАРНЯ СЧАСТЛИВОЧНАЯ/РУССКИЙ БИСКВИТ, 200 OK), экспорт xlsx. Консоль браузера чистая на всех шагах. + Не проверено (нет готовых данных/инфраструктуры под это в локальном стенде, не блокирует подключение): полноэкранный режим; 409/`CORRECTION_VERSION_CONFLICT`; приватное окно без `localStorage`; именные сценарии спеки Сатурн — Архол, Бековский 62.01/60.02/62.03/76.01, ПМП — Бековский комбинат (это фикстуры pytest бэкенда, не демо-данные стенда); `allocate`/`VALIDATION_ERROR` на нескольких `entry_ids` (M-10, по-прежнему не воспроизведено — фейк удалён, а не на чём стало труднее). Оставляю как переносимый пункт проверки на QA/контур, не блокер PR. +- [x] 8.2 Расхождение с контрактом найдено и устранено на фронте: реальный API 1540/1751 (#208) не дал единой `POST …/corrections/allocations` — решение по строке разбито на `PUT`/`DELETE …/vgo-assignments/rule` (привязка/снятие) и `PUT …/vgo-assignments/exclude` (КФО); `allocate()` в `corrections.ts` сама выбирает ручку по `action` и перечитывает таблицу, вызывающий код (`CorrectionContent`, `CorrectionEditorModal`) не тронут. `api.del` (`api/client.ts`) научен отправлять тело — раньше только `DELETE` без тела. Попутно R-35 (design.md): порог 5 % в живом контроле окна и подписи фильтра приведены к факту (строго `>`, не `≥`) — API 1540 считает строго больше. Фейк контракта (`correctionsFake.ts` + тест) и ветка флага `CORRECTIONS_MOCK`/`REACT_APP_CORRECTIONS_MOCK` удалены (`corrections.ts`, `config.ts`); `mockData`/`mockCalc` остались тестовыми фикстурами (R-27), не тронуты. README/ARCHITECTURE/spec.md обновлены под реальный контракт. +- [x] 8.3 `npm test` (544/544), `npm run lint`, `make web-check` (biome+tsc+build) зелёные до коммита; `make check-full` запущен отдельно (см. итог прогона в PR) — не блокирует коммит правок, PR открыт по готовности. ## 9. Ответы Лысенко 29.10 (AURORA-1395, #9920570) и WIP API 1540 (arm-core #208) diff --git a/web/ARCHITECTURE.md b/web/ARCHITECTURE.md index 5ca7625..78a065d 100644 --- a/web/ARCHITECTURE.md +++ b/web/ARCHITECTURE.md @@ -42,13 +42,14 @@ SPA поверх API arm-core (`/api/v1`). Своих данных и бизне ### Виджет «Таблица корректировки» (`widgets/CorrectionTable/`) Требования — `../openspec/changes/corrections-table-ui/` (спека `corrections-table-ui`, решения — `design.md`). Контракт — -`../../docs/dev/api-contract-corrections.md`, типы — `src/types/corrections.ts`, вызовы — `src/services/api/corrections.ts` -(под флагом `REACT_APP_CORRECTIONS_MOCK` вместо HTTP — `correctionsFake.ts`, см. README). +`../../docs/dev/api-contract-corrections.md`, типы — `src/types/corrections.ts`, вызовы — `src/services/api/corrections.ts`. Поток данных: страница отчёта → `CorrectionContent` (вкладка: загрузка, ошибка с «Повторить», экспорт, полный экран) → хук `useCorrectionTable` (запрос; применяется только ответ последнего запроса, `latestOnly`) → таблица целиком хранится в родителе; `CorrectionTable` только рисует, фильтры/раскрытие/колонки — состояние родителя. Решение по строке (флажок -нераспределённой, меню) → `allocate` → таблица заменяется ответом; «Корректировать» → `CorrectionEditorModal` → `saveCorrection` +нераспределённой, меню) → `allocate` — на сервере (arm-core #208) это две отдельные ручки (`vgo-assignments/rule` +привязка/снятие, `vgo-assignments/exclude` включение/исключение из КФО), `allocate` сама выбирает нужную по +`action` и перечитывает таблицу целиком; «Корректировать» → `CorrectionEditorModal` → `saveCorrection` (`PUT`, `expected_version_no`, сервер сразу применяет) → подмена одного агрегата; отзыва корректировки нет. Черновик окна — `localStorage` (`draftStorage`). diff --git a/web/README.md b/web/README.md index 6a99cb9..e1fbfd1 100644 --- a/web/README.md +++ b/web/README.md @@ -65,15 +65,14 @@ SPA кредитного аналитика поверх API `arm-core` (`/api/v «Разберите строки без кода: N» ведёт к первой); панель файлов ЮЛ показывает счётчик предупреждений файла и документа. Карта модулей — `ARCHITECTURE.md`. -## Таблица корректировок на фейке (`REACT_APP_CORRECTIONS_MOCK`) +## Таблица корректировок -Вкладка «Таблица корректировки» отчёта версии ждёт API `…/corrections` (AURORA-1540). Пока его нет, фронт работает на -**фейке контракта**: `REACT_APP_CORRECTIONS_MOCK=true npm run start` — вместо HTTP-запросов вызываются функции -`src/services/api/correctionsFake.ts` с данными из дизайна 23.10 (`widgets/CorrectionTable/mockData.ts`). Состояние -фейка живёт в памяти страницы и сбрасывается перезагрузкой; решения по строкам, сохранение корректировки, -экспорт (CSV вместо xlsx) работают. Без флага (по умолчанию, в том числе в `npm run build`) фронт ходит в настоящий API. -Флаг читается при сборке из `Config.CORRECTIONS_MOCK` (`src/conf/config.ts`), но код фейка в бандл попадает всегда -(не вырезается) — см. design.md, R-21. Черновики окна корректировки лежат в `localStorage` браузера. +Вкладка «Таблица корректировки» отчёта версии работает на реальном API `…/corrections` и `…/vgo-assignments` +(AURORA-1540/1751, arm-core #208). Решение по строке ОСВ сервер разделяет на привязку/снятие правила +(`PUT`/`DELETE …/vgo-assignments/rule`) и включение/исключение из КФО (`PUT …/vgo-assignments/exclude`) — фронт +скрывает это за единой функцией `allocate` (`src/services/api/corrections.ts`), которая сама выбирает ручку и +перечитывает таблицу. `widgets/CorrectionTable/mockData.ts`/`mockCalc.ts` остаются фикстурами для тестов моделей +(не ходят в сеть). Черновики окна корректировки лежат в `localStorage` браузера. ## Ограничения diff --git a/web/src/conf/config.ts b/web/src/conf/config.ts index e3c303d..d148de8 100644 --- a/web/src/conf/config.ts +++ b/web/src/conf/config.ts @@ -9,7 +9,4 @@ export const Config = { CLIENT_ID: environment.REACT_APP_CLIENT_ID ?? '', NAMESPACE: environment.REACT_APP_NAMESPACE ?? '', APP_NAME: environment.REACT_APP_APP_NAME ?? '', - - /** Таблица корректировок работает на фейке контракта вместо API (до готовности бэкенда AURORA-1540). */ - CORRECTIONS_MOCK: environment.REACT_APP_CORRECTIONS_MOCK === 'true', }; diff --git a/web/src/services/api/corrections.ts b/web/src/services/api/corrections.ts index a15b3dc..f78b7c5 100644 --- a/web/src/services/api/corrections.ts +++ b/web/src/services/api/corrections.ts @@ -1,5 +1,4 @@ import { api, apiFetchBlob } from '../../api/client'; -import { Config } from '../../conf/config'; import type { AggregateKey, AllocationIn, @@ -8,7 +7,6 @@ import type { CorrectionTableOut, StatementType, } from '../../types/corrections'; -import { correctionsFake } from './correctionsFake'; const STATEMENT_TYPE: StatementType = 'balance'; @@ -18,22 +16,37 @@ export function correctionsPath(versionId: string, key?: AggregateKey): string { return `${base}/aggregates/${key.rule_id}/${key.company_1_id}/${key.company_2_id}`; } +function vgoAssignmentPath(versionId: string, resource: 'rule' | 'exclude'): string { + return `/consolidations/versions/${versionId}/vgo-assignments/${resource}`; +} + export function getCorrectionTable(versionId: string): Promise { - if (Config.CORRECTIONS_MOCK) return correctionsFake.getCorrectionTable(versionId); return api.get(correctionsPath(versionId), { statement_type: STATEMENT_TYPE }); } export function saveCorrection(versionId: string, key: AggregateKey, body: CorrectionIn): Promise { - if (Config.CORRECTIONS_MOCK) return correctionsFake.saveCorrection(versionId, key, body); return api.put(correctionsPath(versionId, key), body); } -export function allocate(versionId: string, body: AllocationIn): Promise { - if (Config.CORRECTIONS_MOCK) return correctionsFake.allocate(versionId, body); - return api.post(`${correctionsPath(versionId)}/allocations`, body); +/** + * Решение по строке ОСВ: нет единой ручки — сервер (arm-core #208) разделил её на привязку/снятие правила и + * включение/исключение из КФО (`vgo-assignments/rule`, `vgo-assignments/exclude`), ответ — список решений по + * исходным строкам, а не таблица. Таблицу перечитываем сами, чтобы вызывающий код (CorrectionContent, + * CorrectionEditorModal) остался на прежнем контракте «действие -> актуальная таблица». + */ +export async function allocate(versionId: string, body: AllocationIn): Promise { + const { statement_type, entry_ids, action } = body; + if (action === 'ASSIGN') { + if (!body.rule_id) throw new Error('ASSIGN без rule_id'); + await api.put(vgoAssignmentPath(versionId, 'rule'), { entry_ids, rule_id: body.rule_id }); + } else if (action === 'UNASSIGN' || action === 'RESET') { + await api.del(vgoAssignmentPath(versionId, 'rule'), { statement_type, entry_ids }); + } else { + await api.put(vgoAssignmentPath(versionId, 'exclude'), { statement_type, entry_ids, is_exclude: action === 'EXCLUDE' }); + } + return getCorrectionTable(versionId); } export function exportCorrectionTable(versionId: string): Promise<{ blob: Blob; filename: string | null }> { - if (Config.CORRECTIONS_MOCK) return correctionsFake.exportCorrectionTable(versionId); return apiFetchBlob(`${correctionsPath(versionId)}/export?statement_type=${STATEMENT_TYPE}`); }