clear from agent files in web #73
@ -1,26 +0,0 @@
|
||||
# Архитектура arm-web
|
||||
|
||||
SPA поверх API arm-core (`/api/v1`). Своих данных и бизнес-вычислений нет: всё производное (чек-лист, сводная ОСВ,
|
||||
статусы) считает сервер, фронт показывает и отправляет действия пользователя.
|
||||
|
||||
## Модули
|
||||
|
||||
| Модуль | Ответственность | Зависит от |
|
||||
|---|---|---|
|
||||
| `src/api/` | HTTP-клиент, токен, разбор ошибок | `conf/`, `auth/token` |
|
||||
| `src/auth/` | профиль `/me`, dev-вход, права для меню | `api/` |
|
||||
| `src/services/api/` | вызовы ресурсов arm-core | `api/`, `types/` |
|
||||
| `src/store/` | состояние экранов (Redux Toolkit) | `services/api/` |
|
||||
| `src/utils/` | чистые функции: деньги, подписи, скачивание | `types/` |
|
||||
| `src/types/` | DTO API (повторяют `arm-core/api/src/schemas/`) | — |
|
||||
| `src/components/` | примитивы UI (таблица, теги, заголовок) | antd |
|
||||
| `src/widgets/` | блоки экранов (`DocumentReview` — проверка документа со сканом, `ConsolidatedOsv` — сводная ОСВ, `FilesDrawer`) | `components/`, `store/`, `services/api/`, `utils/` |
|
||||
| `src/layouts/` | страницы-маршруты | `widgets/` |
|
||||
|
||||
## Границы
|
||||
- Сетевые запросы — только через `src/api/client.ts`; `fetch` напрямую не вызывается.
|
||||
- `components/` не знают про store и API; `types/` ни от чего не зависят.
|
||||
- `utils/` не импортирует React, store и API.
|
||||
|
||||
## Открытые вопросы
|
||||
- Судьба `arm-core/web` (дубль основы этого репозитория) — решает владелец.
|
||||
@ -1,65 +0,0 @@
|
||||
# CLAUDE.md — arm-web (фронт АРМ КА)
|
||||
|
||||
Общий контекст системы — `../CLAUDE.md` и `../docs/` (workspace). Этот файл — только про этот репозиторий.
|
||||
Язык общения, коммитов, комментариев и документации — русский; идентификаторы — английские, как в DBML и API.
|
||||
|
||||
## Что это
|
||||
SPA кредитного аналитика АРМ КА: справочники групп, консолидации ГК по периодам, карточка версии с чек-листом
|
||||
документов, загрузка и проверка первичных файлов (ОСВ, ББ/ОФР из PDF), дальше — сводная ОСВ и шаги консолидации.
|
||||
Бэкенд — `arm-core` (`/api/v1`, единственный источник данных). Репозиторий выделен из `arm-core/web`
|
||||
(06.10.2026, автор основы — команда фронта); `arm-core/web` пока не удалён — его судьба не решена.
|
||||
|
||||
## Ветки
|
||||
- `main` — не трогаем до решения владельца.
|
||||
- `test` — интеграционная ветка прототипа: все PR (`feat/<issue>-<slug>`) вливаются в `test`, не в `main`.
|
||||
|
||||
## Источники истины для фронта
|
||||
1. OpenAPI arm-core и гайд `../docs/dev/frontend-api-guide.md` — эндпоинты, тела, коды ошибок. Типы в `src/types/`
|
||||
повторяют схемы бэка (`arm-core/api/src/schemas/`), поля не придумываем.
|
||||
2. Пользовательский путь — `../docs/context/10-user-journey.md`; макет — `../docs/mockup/` (по одному экрану через
|
||||
`../docs/mockup/INDEX.md`).
|
||||
3. Права — данные: доступ решает сервер (403). Меню пока не скрывается по правам (`Sidebar` не применяет permission);
|
||||
коды прав из `/me` лежат в `src/auth/permissions.ts`.
|
||||
- `../reference-old-arm/` — только чтение: инфраструктурные идеи (PDFResultViewer) можно смотреть, эндпоинты старого
|
||||
АРМ (`/fsl/...`, `/osv/by-file-id`, `POST /files`) в arm-core не существуют.
|
||||
|
||||
## Стек и ограничения
|
||||
- React 18 + TypeScript strict, rsbuild, antd 6 (основной UI-кит; daisyui/tailwind — из шаблона), react-router 7,
|
||||
Redux Toolkit, pdfjs-dist; линтер — biome (ширина 140, одинарные кавычки).
|
||||
- Пакеты `raisa-*` — `optionalDependencies` (вне банка недоступны); `package-lock.json` в git не хранится (как в шаблоне:
|
||||
в банке зависимости ставятся через Nexus).
|
||||
- env только `REACT_APP_*` через `src/conf/config.ts`; `REACT_APP_API_URL` — адрес arm-core, `REACT_APP_ROOT_PATH` =
|
||||
`ROOT_PATH` бэка.
|
||||
|
||||
## Архитектура
|
||||
- `src/api/` — единый HTTP-клиент `apiFetch` / `api.get|post|put|patch|del`: префикс `${API_URL}/api/v1`, Bearer-токен,
|
||||
`ApiError {status, code, message}` по трём формам `detail`, событие `armka:unauthorized` на 401. **Все запросы — только
|
||||
через него** (прямой `fetch` теряет токен и разбор ошибок).
|
||||
- `src/api/client.ts` также даёт `apiFetchBlob(path) → {blob, filename}` для бинарных ответов (скан PDF, экспорт xlsx) с той же обработкой токена/401/ошибок.
|
||||
- `src/services/api/` — функции по ресурсам arm-core (consolidations, files, documents, osv, directories/…).
|
||||
- `src/utils/` — чистые функции без React/store/API: деньги (`money.ts`, суммы — строки), подписи статусов/типов/периодов (`labels.ts`), скачивание и имя файла (`download.ts`).
|
||||
- `src/auth/` — `AuthGate` (профиль `/me`), `DevLogin` (`POST /dev/token`, только не в production), `token.ts`.
|
||||
- `src/store/` — Redux Toolkit: `store.ts`, слайсы `companyGroupsSlice` и `consolidations` (+ `thunks.ts`).
|
||||
- `src/layouts/` — страницы (маршруты в `src/App.tsx`, в т.ч. `/documents/:document_id` и `/groups/:group_id/versions/:version_id/osv`), `src/widgets/` — блоки экранов (`DocumentReview` — проверка ББ/ОФР/ОСВ со сканом, `ConsolidatedOsv` — сводная ОСВ, `FilesDrawer` — файлы ЮЛ), `src/components/` — примитивы
|
||||
(`GenericTable`, `TableTag`, `PageTitle`), `src/types/` — DTO API.
|
||||
|
||||
## Команды
|
||||
- `make install` — `npm install`.
|
||||
- `make test` — vitest (`npm test`).
|
||||
- `make check` — install + biome + `tsc --noEmit` + vitest + `rsbuild build`. Зелёным считается только всё вместе; **перед PR**, вывод —
|
||||
в описание PR.
|
||||
- `make start` — dev-сервер (`rsbuild dev`, цель сама задаёт `REACT_APP_ROOT_PATH=/`: без него приложение ждёт префикс
|
||||
`/aurora/apps/fastapi-dkb-armka` и открывается белый экран; `qa-guide` §4.3) с proxy `/api` → `ARM_CORE_URL` (по умолчанию `http://localhost:8000`); бэкенд — локальный стенд arm-core по `../docs/dev/qa-guide.md`.
|
||||
|
||||
## Тесты
|
||||
Unit-тесты — vitest на чистых функциях (`src/**/*.test.ts`, `npm test` / `make test`); компоненты не тестируются — ручной демо-сценарий по стенду arm-core с фикстурами из `../fixtures` (README, `scripts/demo-prepare.sh`).
|
||||
|
||||
## Для владельца
|
||||
Пользователь слаб во фронте: на TS/React-изменения агент делает код-ревью сам и объясняет решения простым языком.
|
||||
|
||||
## OpenSpec и superpowers
|
||||
- Вход в любую задачу — superpowers:brainstorming. Skills openspec-propose и openspec-explore сами по себе не запускать.
|
||||
- Когда brainstorming классифицировал задачу как architectural, структуру change создать через `openspec new change <id>` (или `/opsx:propose <id>`), а содержимое proposal.md, specs/ и design.md написать по итогам brainstorming. tasks.md из OpenSpec не заполнять — задачи живут в плане superpowers.
|
||||
- `/opsx:apply` не использовать: исполнение идёт через writing-plans и SDD/executing-plans.
|
||||
- `openspec archive <id>` — после merge PR, как часть чеклиста закрытия.
|
||||
- Хук require-plan защищает любой каталог `src/` на любой глубине. Если код лежит иначе — перечислить каталоги (префиксы от корня, по одному в строке) в `.claude/code-paths`.
|
||||
23
web/Makefile
23
web/Makefile
@ -1,23 +0,0 @@
|
||||
.PHONY: install lint typecheck test build check start
|
||||
|
||||
install:
|
||||
npm install
|
||||
|
||||
lint:
|
||||
npx biome check src
|
||||
|
||||
typecheck:
|
||||
npx tsc --noEmit
|
||||
|
||||
test:
|
||||
npm test
|
||||
|
||||
build:
|
||||
npm run build
|
||||
|
||||
# Перед PR: всё вместе, вывод — в описание PR
|
||||
check: install lint typecheck test build
|
||||
|
||||
# REACT_APP_ROOT_PATH=/ — иначе приложение ждёт префикс /aurora/apps/fastapi-dkb-armka и показывает белый экран
|
||||
start:
|
||||
REACT_APP_ROOT_PATH=/ npm run start
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,2 +0,0 @@
|
||||
schema: spec-driven
|
||||
created: 2026-10-08
|
||||
@ -1,158 +0,0 @@
|
||||
## Context
|
||||
|
||||
arm-web выделен из `arm-core/web` командой фронта (3 коммита 06–07.10.2026) и дополнен экранами групп, консолидаций и
|
||||
загрузки. Обзор 08.10.2026: список консолидаций и карточка версии работают с arm-core; загрузка теряет ошибки (нет
|
||||
`await`, лишние поля формы); проверка документов (`PDFResultViewer`, `FileViewer`, `FileUploaderModel`) скопирована из
|
||||
старого АРМ и ходит в `/fsl/...`, `/osv/by-file-id`, `POST /files` мимо клиента API; сводной ОСВ нет; `tsc` — 185 ошибок,
|
||||
biome — 276.
|
||||
|
||||
Демо — 09.10.2026 на локальном стенде; затем прототип уходит в контур на тестирование заказчику и переезжает в
|
||||
`arm-core/web`, где развивается дальше. Поэтому код держится соглашений шаблона `arm-core/web`.
|
||||
|
||||
Источник контракта — `../docs/dev/frontend-api-guide.md` (§5.5–§5.7) и схемы `arm-core/api/src/schemas/`.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:** аналитик на стенде проходит путь «создание консолидации → загрузка ОСВ и PDF → проверка и подтверждение →
|
||||
сводная ОСВ» без сбоев и консоли разработчика; `make check` зелёный; все запросы — к API arm-core.
|
||||
|
||||
**Non-Goals:** см. proposal.md («Non-goals»).
|
||||
|
||||
## Decisions
|
||||
|
||||
### D-1. Чинить на месте, новое — по образцу arm-core/web (согласовано 08.10.2026)
|
||||
Оставляем `api/client.ts`, `auth/*`, список консолидаций, карточку версии, модалку создания (с правками), Redux-слайсы
|
||||
(типизируем). Новые экраны держат состояние в локальных хуках (`useStatement`, `useDocumentEntries`,
|
||||
`useConsolidatedOsv`) как `arm-core/web/src/widgets/Admin/hooks.ts`. Альтернативы — генерация типов из OpenAPI (новая
|
||||
dev-зависимость, доступность в Nexus не проверена) и переписывание с нуля (не успеть) — отклонены владельцем.
|
||||
|
||||
### D-2. Типы DTO руками
|
||||
`src/types/` повторяют схемы arm-core только для используемых эндпоинтов: `Page<T>`, `UploadResult`, `FileOut`,
|
||||
`DocumentBrief`, `DocumentOut`, `StatementOut` (`HeaderOut`, `ColumnOut`, `LineOut`, `ValueOut`, `ChecksOut`),
|
||||
`EntryOut`, `ConsolidatedRowOut`, `OsvSummaryOut`, `VersionCard` (исправления: `is_head_entity: boolean`,
|
||||
`entity_type: 'ЮЛ' | 'ИП' | null`, `osv_accounts: string[]`, `changed_by` — UUID). `Decimal` и даты приходят строками —
|
||||
в типах `string`.
|
||||
|
||||
### D-3. Деньги — строки
|
||||
Суммы не превращаются в `number`. Чистые функции `src/utils/money.ts`: `formatAmount(s)` — разделители разрядов и два
|
||||
знака без арифметики над float; `parseAmountInput(s)` — нормализация ввода (пробелы, запятая → точка) и проверка
|
||||
`^-?\d+(\.\d{1,2})?$`, пустое — `null`. Единицы ББ/ОФР — из `header.units`, ОСВ — рубли.
|
||||
|
||||
### D-4. Бинарные ответы — `apiFetchBlob`
|
||||
`src/api/client.ts` получает `apiFetchBlob(path) → {blob, filename | null}` с той же обработкой токена, 401 и ошибок
|
||||
(тело ошибки — JSON). Используется для скана (`blob.arrayBuffer()` → pdf.js) и экспорта (сохранение через
|
||||
`URL.createObjectURL` + `<a download>`). Имя экспорта — из `Content-Disposition` (`filename*=UTF-8''…` или `filename=`),
|
||||
иначе `exportFilename(group, year, quarter)`.
|
||||
|
||||
### D-5. Dev-proxy вместо CORS
|
||||
`rsbuild.config.ts`: `server.proxy['/api'] → process.env.ARM_CORE_URL ?? 'http://localhost:8000'`; в dev
|
||||
`REACT_APP_API_URL` пустой. Запросы идут с того же источника: нет CORS и читается `Content-Disposition` (follow-up
|
||||
docs#58 O-1 на фронте обходится). В контуре SPA отдаёт сам arm-core — тот же источник, код не меняется. Гайд фронта §2
|
||||
(где `REACT_APP_API_URL=http://localhost:8000`) остаётся верным для варианта без proxy.
|
||||
|
||||
### D-6. Просмотр PDF
|
||||
Из `widgets/PDFResultViewer/IFrameBlock.tsx` сохраняется только постраничный рендер pdf.js (canvas, масштаб, листание);
|
||||
загрузка — `apiFetchBlob('/files/{id}/content?disposition=inline')` → `arrayBuffer`. Компонент переименовывается в
|
||||
`widgets/DocumentReview/PdfPages.tsx`. Worker pdf.js подключается из пакета `pdfjs-dist` сборкой (без CDN — в контуре
|
||||
внешнего интернета нет). `PDFDataViewer.tsx`, `types.ts`, `styles.module.css` старого АРМ удаляются.
|
||||
|
||||
### D-7. Экран документа
|
||||
`layouts/DocumentPage.tsx` (`/documents/:document_id`) → `GET /documents/{id}` → `widgets/DocumentReview/StatementReview`
|
||||
(BALANCE/INCOME) или `widgets/DocumentReview/OsvDocument` (OSV). Чистые функции: `buildStatementGrid(statement)` —
|
||||
матрица строк × колонок по `column_id` (не по индексу), `buildEntryTree(entries)` — дерево по `parent_entry_id` с
|
||||
сохранением `row_number`. Правка ячейки — локальный черновик ячеек и «Сохранить» одним `PATCH` со всеми изменёнными
|
||||
ячейками; ответ сервера заменяет состояние (сервер пересчитывает проверки и сбрасывает акцепт).
|
||||
|
||||
### D-8. Панель файлов
|
||||
`widgets/FilesDrawer` переписывается: `UploadDocumentsModal` → один запрос со всеми файлами, итог из
|
||||
`summarizeUpload(result)`; список — `FilesList` с вложенными документами; подписи — `fileStatusLabel`,
|
||||
`documentTypeLabel`, `periodLabel`, `documentStatusLabel` (чистые функции). Опрос — `useDocumentsPolling` с остановкой,
|
||||
когда нет `UPLOADED`/`PROCESSING`. Слайс `files` типизируется или заменяется локальным хуком (решает исполнитель по
|
||||
месту, без смены поведения).
|
||||
|
||||
### D-9. Сводная ОСВ
|
||||
`layouts/ConsolidatedOsvPage.tsx` + `widgets/ConsolidatedOsv/` (`useConsolidatedOsv`, `OsvFilters`, `OsvTable`,
|
||||
`OsvGaps`). Таблица — antd `Table` с серверной пагинацией (`limit` 500). Фильтры — query-параметры как в API; ЮЛ —
|
||||
из `VersionCard.members`.
|
||||
|
||||
### D-10. Тесты — vitest на чистых функциях
|
||||
`vitest` (devDependency), `npm test` → `vitest run`, цель `make test` входит в `make check`. Покрытие по TDD: money,
|
||||
`summarizeUpload`, подписи статусов/типов/периодов, `normalizeData` карточки версии, `exportFilename` и разбор
|
||||
`Content-Disposition`, `buildStatementGrid`, `buildEntryTree`. Компоненты — ручной демо-сценарий.
|
||||
|
||||
## Демо-сценарий (приёмка)
|
||||
|
||||
Стенд по `../docs/dev/qa-guide.md` §4 и §6.8 (профиль `services`: tabular-parser, scan-gateway, scan-emulator),
|
||||
`make seed-dev`, вход `ka_rf@armka.local` (или пользователь из гайда с правами EDIT на ГК «Русский бисквит»).
|
||||
|
||||
Данные стенда уже подготовлены: консолидация РБ ГСК 2024 Q3 создана (версия `0437f8be-019c-4dbb-9776-08c306aa38cc`),
|
||||
`aep_6.pdf` уже загружен Счастливочной; повторы дают штатные 409 `CONSOLIDATION_EXISTS` / `DUPLICATE_FILE`.
|
||||
|
||||
1. Кнопка «Новая консолидация» — в боковом меню (действует на всё приложение); ГК «Русский бисквит» выбирается в модалке:
|
||||
ГСК, период 2024 Q2 или 2025 Q3 (для 2024 Q3 — показать 409 как штатную ошибку) → члены ГСК отмечены → «Создать».
|
||||
2. Шаги 2–4 — на подготовленной версии `0437f8be-…`. Панель «Организации» → РБ → «Файлы»: ОСВ
|
||||
`fixtures/osv/rb/2024-Q{1,2,3}/*` и Счастливочной (`fixtures/osv/schastl/2024-Q{1,2,3}/*`) загружены, документы
|
||||
подтверждены (на новой версии — загрузить и подтвердить на экране ОСВ).
|
||||
3. ББ/ОФР: открыть уже загруженный ББ Счастливочной (или загрузить `fixtures/ocr/aep_6.pdf` РБ) → скан слева, форма справа,
|
||||
исправить ячейку; ИНН и дата отчёта не распознаны — ввести в шапке дату `2024-09-30` (ГГГГ-ММ-ДД) и «Сохранить», иначе
|
||||
409 `PERIOD_REQUIRED`; принять нарушения с комментарием (если есть), подтвердить.
|
||||
4. «К отчётности» → сводная ОСВ: 1385 строк, 22 документа, 7 пробелов; фильтр Счастливочная + «бисквит» → строка `60.01`
|
||||
по `qa-guide` §7.2; «Скачать xlsx».
|
||||
|
||||
Шаги API (2–4) перед браузерным прогоном проверяются curl-скриптом; браузерный прогон — владелец.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- [Круг «ядро + scan-gateway + эмулятор» на стенде целиком не прогонялся (`qa-guide` §6.8: «сервис проверяется
|
||||
отдельно»)] → Задача 1 плана — прогон до кода. Если сломан: issue в arm-core/scan-gateway, а форма ББ/ОФР на демо —
|
||||
из xlsx (`fixtures/statements-xlsx/mezheninovskaya`, `qa-guide` §6.9), скан — заглушка «превью недоступно».
|
||||
- [Объём к завтра] → порядок задач от критичного к желательному; экспорт и пробелы — последними.
|
||||
- [Ручные типы расходятся с API] → типы только по используемым полям, сверка со схемами при ревью.
|
||||
- [Подтверждение 22 ОСВ кликами долго на демо] → в README — скрипт подтверждения из `qa-guide` §7.1; на демо можно
|
||||
показать подтверждение одного документа, остальные подготовить заранее.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
Один PR `feat/3-demo-prototype` → `test`. Откат — revert PR. Перенос в `arm-core/web` — отдельная задача после демо.
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Какой ЮЛ «владеет» `aep_6.pdf` в фикстурах (ИНН в шапке) — влияет только на `counterparty_check` (не блокирует
|
||||
подтверждение); выясняется в задаче 1.
|
||||
|
||||
## Decisions made without discussion
|
||||
|
||||
- 08.10.2026 — D-5 dev-proxy `/api` вместо `REACT_APP_API_URL=http://localhost:8000`. Обоснование: снимает CORS и
|
||||
нечитаемый `Content-Disposition` без правок arm-core. Цена ошибки: низкая — переменная `REACT_APP_API_URL` продолжает
|
||||
работать, proxy только в dev.
|
||||
- 08.10.2026 — D-6 worker pdf.js из пакета, без CDN. Обоснование: в контуре нет внешнего интернета. Цена ошибки: низкая.
|
||||
- 08.10.2026 — D-7 правка ячеек пакетом по «Сохранить», а не по каждой ячейке. Обоснование: меньше запросов, сервер
|
||||
принимает до 5000 ячеек и пересчитывает проверки один раз. Цена ошибки: низкая — поведение меняется в одном хуке.
|
||||
- 08.10.2026 — D-8: слайс Redux `files` заменён локальным хуком `useLegalEntityFiles` (список нужен только открытой панели). Цена ошибки: низкая.
|
||||
- 08.10.2026 — R-1: Task 1 (проверка стенда, без записи в worktree) и Task 2 запущены параллельно. Обоснование: срок демо. Цена ошибки: нулевая.
|
||||
- 08.10.2026 — R-2: замечание T4.I1 (частичный успех загрузки не обновляет список) отложено в Task 6, где модалка переписывалась; закрыто в Task 6. Цена ошибки: нулевая.
|
||||
- 08.10.2026 — R-3, R-4: scoped re-review однострочного фикса T5.I1 и фикса T7.I1 (чистая функция с тестами) заменены проверкой контроллера по коду. Цена ошибки: низкая.
|
||||
- 08.10.2026 — R-5: все находки финального ревью ветки исправлены одной fix-волной (384301e, 0f734e3, 67bc2c8), M-5 (тосты после 401) отложена. Цена ошибки: низкая.
|
||||
- 08.10.2026 — R-6: `make start` задаёт `REACT_APP_ROOT_PATH=/` (иначе белый экран на dev-сервере, C-1). Цена ошибки: низкая — в контуре переменную задаёт платформа.
|
||||
- 08.10.2026 — R-7: по замечаниям владельца (H-1, H-2) и сверке всех экранов с PNG макета (`docs/mockup/screens/`) экраны
|
||||
[03], [30], [34], [39], [40], [41], [42] приведены к макету в той же ветке. Цена ошибки: низкая — только вёрстка.
|
||||
- 08.10.2026 — R-8: суммы — запятая и разделители разрядов (`12 275,68`); в своде и ОСВ ноль показывается «—», в формах
|
||||
ББ/ОФР — без копеек и «-» для пустых (как в макете). Цена ошибки: низкая — одна функция `formatAmount`.
|
||||
- 08.10.2026 — R-9: даты загрузки и создания показываются по Москве (`Europe/Moscow`), а не по UTC-строке. Цена: низкая.
|
||||
- 08.10.2026 — R-10: [34] «Дата верификации» — «—» (в `VersionBrief` нет поля, до маршрута согласования), «ГСК» — «—»
|
||||
(П-3). Цена: низкая.
|
||||
- 08.10.2026 — R-11: [30] вкладки Шахматка/КФО/… показаны неактивными (Срезы 5–6); фильтры свода — в заголовках колонок,
|
||||
признак контрагента — в фильтре «Контрагенты». Цена: низкая.
|
||||
- 08.10.2026 — R-12: [41] заголовок колонки формы без распознанной подписи — «На <дата периода>» или «Колонка N»; длинный
|
||||
список нарушений — с прокруткой (120 px). Цена: низкая.
|
||||
- 08.10.2026 — R-13 (согласовано с владельцем в чате): форма ББ/ОФР [41] раскладывается «как на бумаге» на фронте —
|
||||
порядок строк и заголовки разделов восстанавливаются по соотношениям §7 (`formLayout.ts`), итоги XX00 жирные (кроме 2900),
|
||||
строки вне формы — «Прочие строки»; приклеенные OCR заголовки разделов срезаются только при показе (полное имя — в
|
||||
подсказке). ИНН — только в рамке кодов; ОКУД — по типу формы, если не распознан; длинное название — 2 строки. Причина:
|
||||
сервер отдаёт строки по коду и без строк-разделов (§12, OQ-36). Цена ошибки: низкая — только показ, данные не меняются;
|
||||
при появлении порядка строк в API (issue arm-core) раскладку можно упростить.
|
||||
- 08.10.2026 — R-14 (по замечанию владельца H-5): шапка формы [41] — как в старом АРМ (`reference-old-arm`
|
||||
`PDFResultViewer`): компактная таблица ИНН / Дата отчёта / Единица измерения (подсветка уверенности и правки, правка в
|
||||
режиме «Редактировать»), затем «Бухгалтерский баланс на …». Реквизиты бумажной шапки макета (ОКПО, ОКОПФ, ОКФС, адрес,
|
||||
название организации) не показываются: распознавание их не отдаёт или отдаёт мусор (arm-scan-gateway#15). Заменяет
|
||||
шапку из R-13. Цена ошибки: низкая — один компонент `StatementHeaderBlock`.
|
||||
@ -1,57 +0,0 @@
|
||||
## Why
|
||||
|
||||
В пятницу 09.10.2026 — демо владельцу и бизнесу, затем прототип уходит в контур банка на тестирование заказчику
|
||||
(и переезжает внутрь `arm-core/web`). Сейчас arm-web не проводит аналитика по пути «консолидация → первичка →
|
||||
проверка → сводная ОСВ»: загрузка теряет ошибки, проверка документов ходит в эндпоинты старого АРМ, экрана сводной ОСВ
|
||||
нет, `make check` красный (tsc — 185 ошибок, biome — 276).
|
||||
|
||||
## What Changes
|
||||
|
||||
- Модалка «Новая консолидация» [42]: предотметка членов ГСК, догрузка всех ЮЛ ГК, ошибки API, переход к новой версии.
|
||||
- Панель «Файлы ЮЛ» [39]/[40] переписана: загрузка в контексте версии с итогом «принято / отклонено», файлы со
|
||||
вложенными документами, переход к документу, опрос до окончания распознавания.
|
||||
- Новый экран документа `/documents/:document_id`: для ББ/ОФР [41]/[41-02] — скан PDF рядом с формой, правка ячеек,
|
||||
проверки и их акцепт, подтверждение; для ОСВ — дерево строк только для чтения и подтверждение.
|
||||
- Новый экран «Сводная ОСВ» [30] `/groups/:group_id/versions/:version_id/osv`: таблица, фильтры, пробелы, счётчик
|
||||
несопоставленных, экспорт xlsx.
|
||||
- `api/client.ts`: `apiFetchBlob` для бинарных ответов (скан, xlsx); dev-proxy `/api` в `rsbuild.config.ts`.
|
||||
- **BREAKING (внутреннее):** удалены `layouts/FileViewer.tsx`, `components/FileUploaderModel.tsx`,
|
||||
`widgets/PDFResultViewer/PDFDataViewer.tsx`, `services/api/mocks.ts`; маршрут `/files/:file_id` заменён на
|
||||
`/documents/:document_id`.
|
||||
- `make check` зелёный: biome + tsc + vitest + build; добавлен `vitest` (dev) для чистых функций.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `consolidation-setup`: создание консолидации ГК за период из модалки и переход к её версии.
|
||||
- `primary-files-ui`: загрузка первичных файлов ЮЛ в контексте версии и список файлов/документов со статусами.
|
||||
- `document-review-ui`: проверка и подтверждение документа — форма ББ/ОФР со сканом PDF, ОСВ-документ.
|
||||
- `consolidated-osv-ui`: сводная ОСВ версии — просмотр, фильтры, пробелы, экспорт.
|
||||
|
||||
### Modified Capabilities
|
||||
(нет — `openspec/specs/` пуст)
|
||||
|
||||
## Impact
|
||||
|
||||
- Экраны макета: [03], [34], [39], [40], [41], [41-02], [42], [30].
|
||||
- Эндпоинты arm-core (`/api/v1`), только существующие:
|
||||
`GET /directories/company-groups`, `GET /directories/company-groups/{id}/related-groups`,
|
||||
`GET /directories/related-groups/{id}`, `GET /directories/legal-entities`, `POST /consolidations`,
|
||||
`GET /company-groups/{id}/consolidations`, `GET /consolidations/versions/{id}`,
|
||||
`POST /legal-entities/{id}/files`, `GET /legal-entities/{id}/files`, `GET /files/{id}/content`,
|
||||
`GET /documents/{id}`, `GET /documents/{id}/entries`, `POST /documents/{id}/confirm`,
|
||||
`GET|PATCH /documents/{id}/statement`, `POST /documents/{id}/statement/checks/accept`,
|
||||
`GET /consolidations/versions/{id}/osv`, `GET …/osv/summary`, `GET …/osv/export`.
|
||||
- Зависимости: `vitest` (devDependency).
|
||||
- Код: `src/api/`, `src/services/api/`, `src/types/`, `src/widgets/{NewConsoleModal,FilesDrawer,ConsoleTable,…}`,
|
||||
новые `src/widgets/DocumentReview/`, `src/widgets/ConsolidatedOsv/`, `src/layouts/`, `src/App.tsx`, `rsbuild.config.ts`,
|
||||
`Makefile`, `README.md`.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Блокировка версии («Взять в работу»), сопоставление контрагентов [32-*], экран чек-листа [37], комментарии,
|
||||
удаление файлов, смена периметра и ручные переходы статусов.
|
||||
- Правка строк ОСВ (в API нет), картинки страниц и координаты ячеек в скане (в API нет).
|
||||
- Генерация типов из OpenAPI; автотесты компонентов.
|
||||
- Перенос в `arm-core/web` и сборка под контур — отдельной задачей после демо.
|
||||
- Изменения arm-core / scan-gateway: найденный пробел — отдельная issue в том репозитории.
|
||||
@ -1,39 +0,0 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Сводка версии
|
||||
Экран «Сводная ОСВ» [30] SHALL показывать шапку версии (ГК, период, номер, статус) и сводку
|
||||
`GET /consolidations/versions/{id}/osv/summary`: строк всего, документов, несопоставленных (`unmapped_total` — счётчик
|
||||
«Осталось N» без перехода к сопоставлению), автоматически сопоставленных.
|
||||
|
||||
#### Scenario: Контрольные цифры РБ 2024 Q3
|
||||
- **WHEN** открыт свод версии РБ 2024 Q3 после подтверждения 22 ОСВ из `fixtures/osv/{rb,schastl}/2024-Q{1,2,3}`
|
||||
- **THEN** «строк» = 1385, «документов» = 22, «несопоставленных» = 1381
|
||||
|
||||
### Requirement: Таблица свода и фильтры
|
||||
Экран SHALL показывать строки `GET /consolidations/versions/{id}/osv` постранично (размер страницы ≤ 2000): ЮЛ, счёт,
|
||||
контрагент, ИНН контрагента, признак контрагента (ГСК/ГК/Внешний), способ сопоставления, сальдо на начало Дт/Кт,
|
||||
обороты Дт/Кт, сальдо на конец Дт/Кт (суммы — строками с разделителями разрядов). Фильтры: ЮЛ (`legal_entity_id`),
|
||||
корень счёта (`account_root`), признак контрагента (`counterparty_type`), только несопоставленные (`unmapped`), поиск по
|
||||
контрагенту (`q`, ≤ 500 символов). Смена фильтра SHALL перечитывать первую страницу.
|
||||
|
||||
#### Scenario: Строка из контрольных цифр
|
||||
- **WHEN** фильтр ЮЛ «Пекарня Счастливочная» и поиск «бисквит»
|
||||
- **THEN** строка `60.01` показывает сальдо нач. Кт 4 454 151.93, обороты Дт 128 058 143.65, Кт 125 950 450.77,
|
||||
сальдо кон. Кт 2 346 459.05, признак «ГСК»
|
||||
|
||||
### Requirement: Пробелы
|
||||
Экран SHALL показывать `gaps[]` сводки списком «ЮЛ — счёт — недостающие кварталы» с пояснением, что строки этих пар в
|
||||
своде отсутствуют.
|
||||
|
||||
#### Scenario: Пробелы РБ 2024 Q3
|
||||
- **WHEN** открыт свод версии РБ 2024 Q3
|
||||
- **THEN** показано 7 пробелов, у каждого недостающие кварталы «Q1, Q2»
|
||||
|
||||
### Requirement: Экспорт
|
||||
Кнопка «Скачать xlsx» SHALL получать `GET /consolidations/versions/{id}/osv/export` с текущими фильтрами через клиент API
|
||||
с токеном и сохранять файл; имя — из `Content-Disposition`, а если заголовок недоступен —
|
||||
`Сводная_ОСВ_<ГК>_<год>_<квартал>.xlsx`.
|
||||
|
||||
#### Scenario: Экспорт с фильтром
|
||||
- **WHEN** аналитик скачивает свод с фильтром по ЮЛ
|
||||
- **THEN** сохраняется xlsx, а ошибка сервера показывается текстом, а не скачивается
|
||||
@ -1,39 +0,0 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Состав модалки «Новая консолидация»
|
||||
Модалка [42] SHALL предлагать выбор ГК (`GET /directories/company-groups`), ГСК этой ГК с `kind = gsk`
|
||||
(`GET /directories/company-groups/{id}/related-groups`), года и квартала, и показывать **все** незакрытые ЮЛ ГК
|
||||
(`GET /directories/legal-entities?company_group_id=&closed=false`, страницы дочитываются по `offset`, пока не получены
|
||||
все `total`). Члены выбранной ГСК (`GET /directories/related-groups/{id}` → `members[]`) SHALL быть отмечены
|
||||
автоматически; пользователь может снять или добавить отметки.
|
||||
|
||||
#### Scenario: Выбор ГСК отмечает её членов
|
||||
- **WHEN** аналитик выбирает ГК «Русский бисквит» и её ГСК
|
||||
- **THEN** в списке ЮЛ видны все ЮЛ ГК, а члены ГСК («АО "Русский бисквит"», «ООО "Пекарня Счастливочная"») отмечены
|
||||
|
||||
#### Scenario: ЮЛ больше одной страницы
|
||||
- **WHEN** у ГК больше ЮЛ, чем `limit` одной страницы
|
||||
- **THEN** модалка показывает все ЮЛ ГК, а не первую страницу
|
||||
|
||||
### Requirement: Создание и переход к версии
|
||||
По «Создать» модалка SHALL отправить `POST /consolidations {company_group_id, related_group_id, year, quarter,
|
||||
legal_entity_ids}` с отмеченными ЮЛ. При 201 модалка SHALL закрыться, список консолидаций ГК — перечитаться, а созданная
|
||||
версия (`VersionCard.id`) — открыться в карточке. При ошибке модалка SHALL остаться открытой и показать `message` из
|
||||
`ApiError` (коды `CONSOLIDATION_EXISTS`, `RELATED_GROUP_MISMATCH`, `ENTITY_NOT_IN_GROUP`, `PERIMETER_TOO_SMALL` и 422
|
||||
валидации).
|
||||
|
||||
#### Scenario: Успешное создание
|
||||
- **WHEN** аналитик создаёт консолидацию 2024 Q3 с периметром из ГСК
|
||||
- **THEN** открыта карточка версии №1 со статусом «Инициация» и двумя ЮЛ в панели «Организации»
|
||||
|
||||
#### Scenario: Консолидация за период уже есть
|
||||
- **WHEN** сервер отвечает 409 `CONSOLIDATION_EXISTS`
|
||||
- **THEN** модалка не закрывается и показывает текст ошибки сервера
|
||||
|
||||
### Requirement: Переход к сводной ОСВ из карточки
|
||||
Кнопка «К отчётности» карточки версии SHALL вести на `/groups/:group_id/versions/:version_id/osv` и быть доступна в
|
||||
любом статусе версии.
|
||||
|
||||
#### Scenario: Переход к своду
|
||||
- **WHEN** аналитик нажимает «К отчётности» у версии
|
||||
- **THEN** открывается экран сводной ОСВ этой версии
|
||||
@ -1,64 +0,0 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Экран документа
|
||||
Маршрут `/documents/:document_id` SHALL загрузить `GET /documents/{id}` и показать вид по `document_type`: `BALANCE` и
|
||||
`INCOME` — проверка формы, `OSV` — просмотр ОСВ; для прочих типов — карточку документа без данных. Шапка экрана SHALL
|
||||
показывать тип, ЮЛ-владельца, период и статус документа, кнопку «Назад».
|
||||
|
||||
#### Scenario: Открытие формы баланса
|
||||
- **WHEN** аналитик открывает документ `BALANCE`
|
||||
- **THEN** показан экран проверки формы
|
||||
|
||||
### Requirement: Скан рядом с формой
|
||||
Для ББ/ОФР экран SHALL показывать исходный файл документа: PDF — постранично через pdf.js из байт
|
||||
`GET /files/{file_id}/content?disposition=inline`, полученных клиентом API с токеном (не через `<iframe>` и не прямой
|
||||
ссылкой); изображение (png/jpg) — тегом `<img>` из blob; xlsx — без превью, с кнопкой «Скачать исходник».
|
||||
|
||||
#### Scenario: PDF под CSP sandbox
|
||||
- **WHEN** сервер отдаёт PDF с `Content-Security-Policy: sandbox`
|
||||
- **THEN** страницы PDF отрисованы на canvas и листаются
|
||||
|
||||
### Requirement: Форма ББ/ОФР — просмотр и правка
|
||||
Экран SHALL показывать `GET /documents/{id}/statement`: шапку (ИНН, дата отчёта, единицы — значение и исходное при
|
||||
правке; наименование, ОКУД/ОКПО) и таблицу «строка (код, наименование) × колонки» с суммами из `values[]`. Ячейки с
|
||||
`confidence < 100` или `is_recognized = false` SHALL подсвечиваться как «требует внимания», с `is_modified = true` — как
|
||||
«исправлено» с исходным значением `original_amount` в подсказке. Единицы (`header.units`) SHALL выводиться над таблицей.
|
||||
Пока документ не `CONFIRMED`, ячейка SHALL быть редактируемой: ввод проверяется по формату суммы (до 2 знаков после
|
||||
точки, знак минус допустим, пустое — `null`), сохранение отправляет `PATCH /documents/{id}/statement {values:
|
||||
[{line_id, column_id, amount}]}` суммой-строкой, ответ заменяет состояние экрана. Правка шапки (ИНН, дата отчёта,
|
||||
единицы) SHALL отправляться тем же `PATCH` в `header`; `inn: null` не отправляется.
|
||||
|
||||
#### Scenario: Исправление ячейки
|
||||
- **WHEN** аналитик меняет значение ячейки и сохраняет
|
||||
- **THEN** ячейка помечена «исправлено», проверки пересчитаны по ответу сервера, акцепт снят
|
||||
|
||||
#### Scenario: Неверный формат суммы
|
||||
- **WHEN** аналитик вводит «12,3,4»
|
||||
- **THEN** запрос не отправляется, поле показывает ошибку формата
|
||||
|
||||
### Requirement: Проверки и их акцепт
|
||||
Экран SHALL показывать `checks`: жёсткие (`failed`), мягкие (`soft_failed`), межформенную (`cross_form`) и акцепт
|
||||
(`accepted`: кто, когда, комментарий). При непустом `failed` и `accepted = null` SHALL быть доступно «Принять с
|
||||
комментарием»: комментарий 1–2000 символов → `POST /documents/{id}/statement/checks/accept {comment}`.
|
||||
|
||||
#### Scenario: Акцепт нарушений
|
||||
- **WHEN** есть жёсткие нарушения и аналитик принимает их с комментарием
|
||||
- **THEN** блок проверок показывает акцепт, нарушения остаются видимыми
|
||||
|
||||
### Requirement: Подтверждение документа
|
||||
Кнопка «Подтвердить» SHALL отправлять `POST /documents/{id}/confirm` и быть недоступна для `CONFIRMED` и `ERROR`. При 409
|
||||
`CHECKS_NOT_PASSED` экран SHALL показать `detail.failures[]` и предложить акцепт; при 409 `PERIOD_REQUIRED` — подсказать
|
||||
исправить дату отчёта; прочие ошибки — `message`. После успеха экран SHALL перейти в режим только чтения.
|
||||
|
||||
#### Scenario: Подтверждение с неакцептованными нарушениями
|
||||
- **WHEN** аналитик подтверждает форму с жёсткими нарушениями без акцепта
|
||||
- **THEN** показан список нарушений из ответа и кнопка «Принять с комментарием»
|
||||
|
||||
### Requirement: ОСВ-документ
|
||||
Для `OSV` экран SHALL показывать строки `GET /documents/{id}/entries` (все страницы, `limit` ≤ 2000) деревом по
|
||||
`parent_entry_id` с колонками: счёт/субсчёт, контрагент, ИНН, сальдо на начало Дт/Кт, обороты Дт/Кт, сальдо на конец
|
||||
Дт/Кт (суммы — строками с разделителями разрядов, рубли), только чтение. Кнопка «Подтвердить» — как для форм.
|
||||
|
||||
#### Scenario: Подтверждение ОСВ
|
||||
- **WHEN** аналитик подтверждает ОСВ-документ `NEEDS_CHECK`
|
||||
- **THEN** документ становится «Загружен», версия, к которой он подходит, переходит в `DATA_CORRECTION`
|
||||
@ -1,38 +0,0 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Загрузка файлов ЮЛ в контексте версии
|
||||
Панель «Файлы ЮЛ» [39]/[40], открытая из строки ЮЛ карточки версии, SHALL загружать выбранные файлы одним запросом
|
||||
`POST /legal-entities/{id}/files?consolidation_version_id={version_id}` (multipart, поле `files` повторяется), без
|
||||
других полей формы. Тип и период документа не спрашиваются. После ответа панель SHALL показать итог: принятые файлы
|
||||
(`accepted[].name`) и отклонённые с причиной (`rejected[].name` + `message`). Ответ 422 с телом `UploadResult` SHALL
|
||||
трактоваться как «ни один файл не принят» с тем же показом `rejected[]`; ответ 422 с `detail` (`ENTITY_NOT_IN_PERIMETER`)
|
||||
и прочие ошибки — показом `message`.
|
||||
|
||||
#### Scenario: Часть файлов отклонена
|
||||
- **WHEN** аналитик загружает ОСВ-файл и файл, уже загруженный ранее у этого ЮЛ
|
||||
- **THEN** первый показан принятым, второй — отклонённым с текстом причины `DUPLICATE_FILE` от сервера
|
||||
|
||||
#### Scenario: Все файлы отклонены
|
||||
- **WHEN** сервер отвечает 422 с телом `UploadResult`
|
||||
- **THEN** панель показывает каждый отклонённый файл с причиной, а не общую ошибку
|
||||
|
||||
### Requirement: Список файлов и документов ЮЛ
|
||||
Панель SHALL показывать файлы ЮЛ из `GET /legal-entities/{id}/files` (новые первыми): имя, статус файла по-русски
|
||||
(«Обработка» для `UPLOADED`/`PROCESSING`, «Ошибка» с причиной из `job` для `ERROR`) и вложенные `documents[]` — тип
|
||||
(«ОСВ <счёт>», «Бух. баланс», «ОФР», «Расшифровка», «Прочее»), период («2024 К1», «2024 · 9 мес.», «период не
|
||||
определён»), статус документа («Требуется проверка», «Загружен» для `CONFIRMED`, «Ошибка»). Документы `OSV`, `BALANCE`,
|
||||
`INCOME` SHALL быть ссылками на `/documents/:document_id`.
|
||||
|
||||
#### Scenario: PDF с двумя формами
|
||||
- **WHEN** файл `aep_6.pdf` распознан
|
||||
- **THEN** под файлом видны два документа — «Бух. баланс» и «ОФР» со статусом «Требуется проверка», каждый открывает
|
||||
экран проверки
|
||||
|
||||
### Requirement: Опрос до окончания распознавания
|
||||
Пока панель открыта и среди файлов есть `UPLOADED` или `PROCESSING`, панель SHALL перечитывать список раз в 5 секунд и
|
||||
SHALL прекратить опрос, когда таких файлов нет или панель закрыта. При закрытии панели карточка версии SHALL
|
||||
перечитываться (обновить ячейки чек-листа).
|
||||
|
||||
#### Scenario: Опрос останавливается
|
||||
- **WHEN** все файлы ЮЛ перешли в `PROCESSED` или `ERROR`
|
||||
- **THEN** запросы списка файлов прекращаются
|
||||
@ -1,28 +0,0 @@
|
||||
schema: spec-driven
|
||||
|
||||
context: |
|
||||
Репозиторий arm-web — фронт системы АРМ КА (ДКБ, Россельхозбанк): SPA кредитного аналитика поверх API arm-core (/api/v1).
|
||||
Workspace из нескольких репозиториев; общий контекст — ../CLAUDE.md и ../docs/ (репозиторий arm-ka-docs).
|
||||
Источники истины: OpenAPI arm-core и ../docs/dev/frontend-api-guide.md (контракт), ../docs/context/10-user-journey.md
|
||||
(путь пользователя), ../docs/mockup/ (макет, по одному экрану через INDEX.md), ../docs/context/05-domain-rules.md (правила).
|
||||
Стек: React 18 + TypeScript strict, rsbuild, antd 6, Redux Toolkit, react-router 7, pdfjs-dist, biome.
|
||||
Интеграционная ветка — test (PR вливаются в неё, main не трогаем). Язык документов и коммитов — русский.
|
||||
|
||||
rules:
|
||||
proposal:
|
||||
- Перечислять экраны (номера макета) и эндпоинты arm-core, которые затрагиваются
|
||||
- Чего нет в API arm-core — не придумывать на фронте; пробел API — отдельный change/issue в arm-core
|
||||
- Раздел «Non-goals» обязателен
|
||||
specs:
|
||||
- Сценарии — по шагам пользовательского пути на локальном стенде arm-core с фикстурами ../fixtures
|
||||
design:
|
||||
- Производные консолидации считает сервер; фронт не дублирует вычисления
|
||||
- Все запросы — через src/api/client.ts
|
||||
tasks:
|
||||
- Задачи не больше половины дня, с указанием файлов
|
||||
|
||||
operations:
|
||||
apply:
|
||||
guidance:
|
||||
- Работать в ветке feat/<issue>-<slug> от test; PR в test
|
||||
- После изменений — make check (biome, tsc, build)
|
||||
@ -1,72 +0,0 @@
|
||||
# consolidated-osv-ui Specification
|
||||
|
||||
## Purpose
|
||||
Аналитик видит сводную ОСВ версии [30]: сводку с несопоставленными строками, пробелы по кварталам, таблицу с фильтрами в заголовках колонок и экспорт в xlsx; сопоставляет контрагента строки с ЮЛ справочника [32].
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: Сводка версии
|
||||
Экран «Сводная ОСВ» [30] SHALL показывать шапку версии (ГК, период, номер, статус) и сводку
|
||||
`GET /consolidations/versions/{id}/osv/summary`: строк всего, документов, несопоставленных (`unmapped_total` — счётчик
|
||||
«Осталось N»; сопоставление — «Указать» в строке), автоматически сопоставленных.
|
||||
|
||||
#### Scenario: Контрольные цифры РБ 2024 Q3
|
||||
- **WHEN** открыт свод версии РБ 2024 Q3 после подтверждения 22 ОСВ из `fixtures/osv/{rb,schastl}/2024-Q{1,2,3}`
|
||||
- **THEN** «строк» = 1385, «документов» = 22, «несопоставленных» = 1381
|
||||
|
||||
### Requirement: Таблица свода и фильтры
|
||||
Экран SHALL показывать строки `GET /consolidations/versions/{id}/osv` постранично (размер страницы ≤ 2000): ЮЛ, счёт,
|
||||
контрагент, ИНН контрагента, сальдо на начало Дт/Кт, обороты Дт/Кт, сальдо на конец Дт/Кт (суммы — строками с
|
||||
разделителями разрядов) и последней колонкой «Тип контрагента» — бейдж ГСК (синий) / ГК (зелёный) / Внешний (серый), как
|
||||
в макете [30]. Фильтры — в заголовках колонок: ЮЛ (`legal_entity_id`), корень счёта (`account_root`), поиск по
|
||||
контрагенту (`q`, ≤ 500 символов), тип контрагента (`counterparty_type`, одно значение — на колонке «Тип контрагента»);
|
||||
только несопоставленные (`unmapped`) — кнопкой над таблицей. «Применить» и «Сбросить» SHALL закрывать окно фильтра.
|
||||
Смена фильтра SHALL перечитывать первую страницу.
|
||||
|
||||
#### Scenario: Строка из контрольных цифр
|
||||
- **WHEN** фильтр ЮЛ «Пекарня Счастливочная» и поиск «бисквит»
|
||||
- **THEN** строка `60.01` показывает сальдо нач. Кт 4 454 151.93, обороты Дт 128 058 143.65, Кт 125 950 450.77,
|
||||
сальдо кон. Кт 2 346 459.05, признак «ГСК»
|
||||
|
||||
### Requirement: Ручное сопоставление контрагента
|
||||
У строки без сопоставленного ЮЛ (`counterparty_id = null`) ячейка ИНН SHALL быть подсвечена и содержать ссылку
|
||||
«Указать» (рядом с ИНН из файла, если он есть), как в макете [32-osv-no-inn]. «Указать» SHALL открывать модалку
|
||||
«Выбор организации для связи с <имя контрагента из файла>» [32-osv-map-counterparty]: поиск, таблица ИНН / Организация /
|
||||
ГК / ГСК / ГСЗ / «Связать», рекомендуемые (`recommended`) — первыми под заголовком «Рекомендуемые» с зелёной подсветкой.
|
||||
Кандидаты — `GET …/counterparty-candidates?scope=&entry_id=&q=` с паузой 300 мс после ввода: по умолчанию `scope=group`
|
||||
(периметр и ГК); галочка «Искать во всём справочнике» — `scope=all`, при поиске короче 3 символов запрос не отправляется
|
||||
и показывается подсказка.
|
||||
«Связать» SHALL отправлять `POST …/osv/entries/{entry_ids.at(-1)}/counterparty {legal_entity_id}`, после успеха —
|
||||
сообщение (с кем связано, сколько записей ОСВ сопоставлено автоприменением, создан или перенесён алиас), закрывать
|
||||
модалку и перечитывать страницу свода и сводку («Осталось N» на клиенте не вычисляется). Ответы 409:
|
||||
- `ALIAS_TAKEN` — подтверждение «Имя уже закреплено за <владельцы>. Перенести?» → повтор с `move_alias = true`;
|
||||
- `LOCK_REQUIRED` — подтверждение «Взять версию в работу?» → `POST …/lock` → повтор; отказ — ничего не меняется;
|
||||
- `LOCKED_BY_OTHER` (от правки или от `POST …/lock`) — «Версию держит <имя>» (имя — из ответа `POST …/lock` или из
|
||||
`lock` карточки версии), без перехвата;
|
||||
- `VERSION_NOT_EDITABLE` и прочие ошибки — текстом сервера.
|
||||
|
||||
#### Scenario: Сопоставление «Пекарня Счастливочная ООО» в ОСВ РБ
|
||||
- **WHEN** в своде РБ 2024 Q3 у строки контрагента «Пекарня Счастливочная ООО» без сопоставления нажато «Указать» и у
|
||||
рекомендуемой «Пекарни Счастливочной» — «Связать» (версия не взята в работу → «Взять в работу»)
|
||||
- **THEN** строка показывает ЮЛ и тип «ГСК», «Осталось N» уменьшается на число сопоставленных строк, у ЮЛ появляется алиас
|
||||
|
||||
#### Scenario: Алиас занят
|
||||
- **WHEN** имя строки уже алиас другого ЮЛ и аналитик выбирает третье ЮЛ
|
||||
- **THEN** показан вопрос о переносе с именами владельцев; «Перенести» повторяет запрос с `move_alias = true`
|
||||
|
||||
### Requirement: Пробелы
|
||||
Экран SHALL показывать `gaps[]` сводки списком «ЮЛ — счёт — недостающие кварталы» с пояснением, что строки этих пар в
|
||||
своде отсутствуют.
|
||||
|
||||
#### Scenario: Пробелы РБ 2024 Q3
|
||||
- **WHEN** открыт свод версии РБ 2024 Q3
|
||||
- **THEN** показано 7 пробелов, у каждого недостающие кварталы «Q1, Q2»
|
||||
|
||||
### Requirement: Экспорт
|
||||
Кнопка «Скачать xlsx» SHALL получать `GET /consolidations/versions/{id}/osv/export` с текущими фильтрами через клиент API
|
||||
с токеном и сохранять файл; имя — из `Content-Disposition`, а если заголовок недоступен —
|
||||
`Сводная_ОСВ_<ГК>_<год>_<квартал>.xlsx`.
|
||||
|
||||
#### Scenario: Экспорт с фильтром
|
||||
- **WHEN** аналитик скачивает свод с фильтром по ЮЛ
|
||||
- **THEN** сохраняется xlsx, а ошибка сервера показывается текстом, а не скачивается
|
||||
@ -1,44 +0,0 @@
|
||||
# consolidation-setup Specification
|
||||
|
||||
## Purpose
|
||||
Аналитик создаёт консолидацию ГК за период из модалки [42] (периметр — члены ГСК с возможностью правки), сразу попадает в карточку новой версии и из любой версии переходит к её сводной ОСВ.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: Состав модалки «Новая консолидация»
|
||||
Модалка [42] SHALL предлагать выбор ГК (`GET /directories/company-groups`), ГСК этой ГК с `kind = gsk`
|
||||
(`GET /directories/company-groups/{id}/related-groups`), года и квартала, и показывать **все** незакрытые ЮЛ ГК
|
||||
(`GET /directories/legal-entities?company_group_id=&closed=false`, страницы дочитываются по `offset`, пока не получены
|
||||
все `total`). Члены выбранной ГСК (`GET /directories/related-groups/{id}` → `members[]`) SHALL быть отмечены
|
||||
автоматически; пользователь может снять или добавить отметки.
|
||||
|
||||
#### Scenario: Выбор ГСК отмечает её членов
|
||||
- **WHEN** аналитик выбирает ГК «Русский бисквит» и её ГСК
|
||||
- **THEN** в списке ЮЛ видны все ЮЛ ГК, а члены ГСК («АО "Русский бисквит"», «ООО "Пекарня Счастливочная"») отмечены
|
||||
|
||||
#### Scenario: ЮЛ больше одной страницы
|
||||
- **WHEN** у ГК больше ЮЛ, чем `limit` одной страницы
|
||||
- **THEN** модалка показывает все ЮЛ ГК, а не первую страницу
|
||||
|
||||
### Requirement: Создание и переход к версии
|
||||
По «Создать» модалка SHALL отправить `POST /consolidations {company_group_id, related_group_id, year, quarter,
|
||||
legal_entity_ids}` с отмеченными ЮЛ. При 201 модалка SHALL закрыться, список консолидаций ГК — перечитаться, а созданная
|
||||
версия (`VersionCard.id`) — открыться в карточке. При ошибке модалка SHALL остаться открытой и показать `message` из
|
||||
`ApiError` (коды `CONSOLIDATION_EXISTS`, `RELATED_GROUP_MISMATCH`, `ENTITY_NOT_IN_GROUP`, `PERIMETER_TOO_SMALL` и 422
|
||||
валидации).
|
||||
|
||||
#### Scenario: Успешное создание
|
||||
- **WHEN** аналитик создаёт консолидацию 2024 Q3 с периметром из ГСК
|
||||
- **THEN** открыта карточка версии №1 со статусом «Инициация» и двумя ЮЛ в панели «Организации»
|
||||
|
||||
#### Scenario: Консолидация за период уже есть
|
||||
- **WHEN** сервер отвечает 409 `CONSOLIDATION_EXISTS`
|
||||
- **THEN** модалка не закрывается и показывает текст ошибки сервера
|
||||
|
||||
### Requirement: Переход к сводной ОСВ из карточки
|
||||
Кнопка «К отчётности» карточки версии SHALL вести на `/groups/:group_id/versions/:version_id/osv` и быть доступна в
|
||||
любом статусе версии.
|
||||
|
||||
#### Scenario: Переход к своду
|
||||
- **WHEN** аналитик нажимает «К отчётности» у версии
|
||||
- **THEN** открывается экран сводной ОСВ этой версии
|
||||
@ -1,69 +0,0 @@
|
||||
# document-review-ui Specification
|
||||
|
||||
## Purpose
|
||||
Аналитик проверяет распознанный документ и подтверждает его: форма ББ/ОФР рядом со сканом PDF (правка значений и шапки, проверки и их акцепт) [41]/[41-02], ОСВ-документ — деревом строк только для чтения.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: Экран документа
|
||||
Маршрут `/documents/:document_id` SHALL загрузить `GET /documents/{id}` и показать вид по `document_type`: `BALANCE` и
|
||||
`INCOME` — проверка формы, `OSV` — просмотр ОСВ; для прочих типов — карточку документа без данных. Шапка экрана SHALL
|
||||
показывать тип, ЮЛ-владельца, период и статус документа, кнопку «Назад».
|
||||
|
||||
#### Scenario: Открытие формы баланса
|
||||
- **WHEN** аналитик открывает документ `BALANCE`
|
||||
- **THEN** показан экран проверки формы
|
||||
|
||||
### Requirement: Скан рядом с формой
|
||||
Для ББ/ОФР экран SHALL показывать исходный файл документа: PDF — постранично через pdf.js из байт
|
||||
`GET /files/{file_id}/content?disposition=inline`, полученных клиентом API с токеном (не через `<iframe>` и не прямой
|
||||
ссылкой); изображение (png/jpg) — тегом `<img>` из blob; xlsx — без превью, с кнопкой «Скачать исходник».
|
||||
|
||||
#### Scenario: PDF под CSP sandbox
|
||||
- **WHEN** сервер отдаёт PDF с `Content-Security-Policy: sandbox`
|
||||
- **THEN** страницы PDF отрисованы на canvas и листаются
|
||||
|
||||
### Requirement: Форма ББ/ОФР — просмотр и правка
|
||||
Экран SHALL показывать `GET /documents/{id}/statement`: шапку (ИНН, дата отчёта, единицы — значение и исходное при
|
||||
правке; наименование, ОКУД/ОКПО) и таблицу «строка (код, наименование) × колонки» с суммами из `values[]`. Ячейки с
|
||||
`confidence < 100` или `is_recognized = false` SHALL подсвечиваться как «требует внимания», с `is_modified = true` — как
|
||||
«исправлено» с исходным значением `original_amount` в подсказке. Единицы (`header.units`) SHALL выводиться над таблицей.
|
||||
Пока документ не `CONFIRMED`, ячейка SHALL быть редактируемой: ввод проверяется по формату суммы (до 2 знаков после
|
||||
точки, знак минус допустим, пустое — `null`), сохранение отправляет `PATCH /documents/{id}/statement {values:
|
||||
[{line_id, column_id, amount}]}` суммой-строкой, ответ заменяет состояние экрана. Правка шапки (ИНН, дата отчёта,
|
||||
единицы) SHALL отправляться тем же `PATCH` в `header`; `inn: null` не отправляется.
|
||||
|
||||
#### Scenario: Исправление ячейки
|
||||
- **WHEN** аналитик меняет значение ячейки и сохраняет
|
||||
- **THEN** ячейка помечена «исправлено», проверки пересчитаны по ответу сервера, акцепт снят
|
||||
|
||||
#### Scenario: Неверный формат суммы
|
||||
- **WHEN** аналитик вводит «12,3,4»
|
||||
- **THEN** запрос не отправляется, поле показывает ошибку формата
|
||||
|
||||
### Requirement: Проверки и их акцепт
|
||||
Экран SHALL показывать `checks`: жёсткие (`failed`), мягкие (`soft_failed`), межформенную (`cross_form`) и акцепт
|
||||
(`accepted`: кто, когда, комментарий). При непустом `failed` и `accepted = null` SHALL быть доступно «Принять с
|
||||
комментарием»: комментарий 1–2000 символов → `POST /documents/{id}/statement/checks/accept {comment}`.
|
||||
|
||||
#### Scenario: Акцепт нарушений
|
||||
- **WHEN** есть жёсткие нарушения и аналитик принимает их с комментарием
|
||||
- **THEN** блок проверок показывает акцепт, нарушения остаются видимыми
|
||||
|
||||
### Requirement: Подтверждение документа
|
||||
Кнопка «Подтвердить» SHALL отправлять `POST /documents/{id}/confirm` и быть недоступна для `CONFIRMED` и `ERROR`. При 409
|
||||
`CHECKS_NOT_PASSED` экран SHALL показать `detail.failures[]` и предложить акцепт; при 409 `PERIOD_REQUIRED` — подсказать
|
||||
исправить дату отчёта; прочие ошибки — `message`. После успеха экран SHALL перейти в режим только чтения.
|
||||
|
||||
#### Scenario: Подтверждение с неакцептованными нарушениями
|
||||
- **WHEN** аналитик подтверждает форму с жёсткими нарушениями без акцепта
|
||||
- **THEN** показан список нарушений из ответа и кнопка «Принять с комментарием»
|
||||
|
||||
### Requirement: ОСВ-документ
|
||||
Для `OSV` экран SHALL показывать строки `GET /documents/{id}/entries` (все страницы, `limit` ≤ 2000) деревом по
|
||||
`parent_entry_id` с колонками: счёт/субсчёт, контрагент, ИНН, сальдо на начало Дт/Кт, обороты Дт/Кт, сальдо на конец
|
||||
Дт/Кт (суммы — строками с разделителями разрядов, рубли), только чтение. Кнопка «Подтвердить» — как для форм.
|
||||
|
||||
#### Scenario: Подтверждение ОСВ
|
||||
- **WHEN** аналитик подтверждает ОСВ-документ `NEEDS_CHECK`
|
||||
- **THEN** документ становится «Загружен», версия, к которой он подходит, переходит в `DATA_CORRECTION`
|
||||
@ -1,43 +0,0 @@
|
||||
# primary-files-ui Specification
|
||||
|
||||
## Purpose
|
||||
Аналитик загружает первичные файлы ЮЛ в контексте версии и видит в панели «Первичные документы» [39]/[40] файлы и их документы со статусами распознавания; список обновляется сам, пока идёт распознавание.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: Загрузка файлов ЮЛ в контексте версии
|
||||
Панель «Файлы ЮЛ» [39]/[40], открытая из строки ЮЛ карточки версии, SHALL загружать выбранные файлы одним запросом
|
||||
`POST /legal-entities/{id}/files?consolidation_version_id={version_id}` (multipart, поле `files` повторяется), без
|
||||
других полей формы. Тип и период документа не спрашиваются. После ответа панель SHALL показать итог: принятые файлы
|
||||
(`accepted[].name`) и отклонённые с причиной (`rejected[].name` + `message`). Ответ 422 с телом `UploadResult` SHALL
|
||||
трактоваться как «ни один файл не принят» с тем же показом `rejected[]`; ответ 422 с `detail` (`ENTITY_NOT_IN_PERIMETER`)
|
||||
и прочие ошибки — показом `message`.
|
||||
|
||||
#### Scenario: Часть файлов отклонена
|
||||
- **WHEN** аналитик загружает ОСВ-файл и файл, уже загруженный ранее у этого ЮЛ
|
||||
- **THEN** первый показан принятым, второй — отклонённым с текстом причины `DUPLICATE_FILE` от сервера
|
||||
|
||||
#### Scenario: Все файлы отклонены
|
||||
- **WHEN** сервер отвечает 422 с телом `UploadResult`
|
||||
- **THEN** панель показывает каждый отклонённый файл с причиной, а не общую ошибку
|
||||
|
||||
### Requirement: Список файлов и документов ЮЛ
|
||||
Панель SHALL показывать файлы ЮЛ из `GET /legal-entities/{id}/files` (новые первыми): имя, статус файла по-русски
|
||||
(«Обработка» для `UPLOADED`/`PROCESSING`, «Ошибка» с причиной из `job` для `ERROR`) и вложенные `documents[]` — тип
|
||||
(«ОСВ <счёт>», «Бух. баланс», «ОФР», «Расшифровка», «Прочее»), период («2024 К1», «2024 · 9 мес.», «период не
|
||||
определён»), статус документа («Требуется проверка», «Загружен» для `CONFIRMED`, «Ошибка»). Документы `OSV`, `BALANCE`,
|
||||
`INCOME` SHALL быть ссылками на `/documents/:document_id`.
|
||||
|
||||
#### Scenario: PDF с двумя формами
|
||||
- **WHEN** файл `aep_6.pdf` распознан
|
||||
- **THEN** под файлом видны два документа — «Бух. баланс» и «ОФР» со статусом «Требуется проверка», каждый открывает
|
||||
экран проверки
|
||||
|
||||
### Requirement: Опрос до окончания распознавания
|
||||
Пока панель открыта и среди файлов есть `UPLOADED` или `PROCESSING`, панель SHALL перечитывать список раз в 5 секунд и
|
||||
SHALL прекратить опрос, когда таких файлов нет или панель закрыта. При закрытии панели карточка версии SHALL
|
||||
перечитываться (обновить ячейки чек-листа).
|
||||
|
||||
#### Scenario: Опрос останавливается
|
||||
- **WHEN** все файлы ЮЛ перешли в `PROCESSED` или `ERROR`
|
||||
- **THEN** запросы списка файлов прекращаются
|
||||
@ -1,50 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Подготовка демо: загрузить квартальные ОСВ РБ и Счастливочной за 2024 Q1–Q3 в версию и подтвердить ОСВ-документы.
|
||||
# Использование: scripts/demo-prepare.sh <version_id> [email] (стенд arm-core на localhost:8000, DEBUG=true)
|
||||
# Каталог фикстур: FIXTURES_DIR (по умолчанию ../../fixtures относительно репозитория arm-web, т.е. workspace/fixtures).
|
||||
set -euo pipefail
|
||||
V="${1:?version_id}"
|
||||
EMAIL="${2:-ka_go@armka.local}"
|
||||
API="${API:-localhost:8000/api/v1}"
|
||||
F="${FIXTURES_DIR:-$(cd "$(dirname "$0")/../.." && pwd)/fixtures}/osv"
|
||||
H="Authorization: Bearer $(curl -sf -X POST "$API/dev/token" -H 'Content-Type: application/json' -d "{\"email\":\"$EMAIL\"}" | python3 -c 'import sys,json;print(json.load(sys.stdin)["access_token"])')"
|
||||
|
||||
members=$(curl -sf "$API/consolidations/versions/$V" -H "$H" | python3 -c '
|
||||
import sys,json
|
||||
for m in json.load(sys.stdin)["members"]: print(m["id"], m["inn"], m["name"])')
|
||||
# ЮЛ ищем по ИНН (в справочнике имена могут быть в верхнем регистре)
|
||||
le_by_inn() { echo "$members" | awk -v inn="$1" '$2 == inn { print $1; exit }'; }
|
||||
LE_RB=$(le_by_inn 3528052718)
|
||||
LE_SCH=$(le_by_inn 3528104476)
|
||||
[ -n "$LE_RB" ] && [ -n "$LE_SCH" ] || { echo "В периметре версии нет РБ или Счастливочной:"; echo "$members"; exit 1; }
|
||||
|
||||
upload() { for f in "$F/$2"/2024-Q{1,2,3}/*; do
|
||||
curl -s -X POST "$API/legal-entities/$1/files?consolidation_version_id=$V" -H "$H" -F "files=@$f" \
|
||||
| python3 -c 'import sys,json;d=json.load(sys.stdin);print("принят" if d.get("accepted") else d.get("rejected") or d)'
|
||||
done; }
|
||||
upload "$LE_RB" rb
|
||||
upload "$LE_SCH" schastl
|
||||
|
||||
echo "Жду распознавания…"
|
||||
for _ in $(seq 1 60); do
|
||||
pending=0
|
||||
for LE in $LE_RB $LE_SCH; do
|
||||
n=$(curl -sf "$API/legal-entities/$LE/files?limit=100" -H "$H" | python3 -c 'import sys,json;print(sum(f["status"] in ("UPLOADED","PROCESSING") for f in json.load(sys.stdin)["items"]))')
|
||||
pending=$((pending + n))
|
||||
done
|
||||
[ "$pending" -eq 0 ] && break
|
||||
sleep 5
|
||||
done
|
||||
|
||||
for LE in $LE_RB $LE_SCH; do
|
||||
curl -sf "$API/legal-entities/$LE/files?limit=100" -H "$H" | python3 -c '
|
||||
import sys,json
|
||||
for f in json.load(sys.stdin)["items"]:
|
||||
for d in f["documents"]:
|
||||
if d["document_type"] == "OSV" and d["status"] in ("RECOGNIZED","NEEDS_CHECK"): print(d["id"])' \
|
||||
| while read -r DOC; do curl -s -o /dev/null -w "%{http_code} $DOC\n" -X POST "$API/documents/$DOC/confirm" -H "$H"; done
|
||||
done
|
||||
|
||||
curl -sf "$API/consolidations/versions/$V/osv/summary" -H "$H" | python3 -c '
|
||||
import sys,json
|
||||
s=json.load(sys.stdin); print("строк:", s["rows_total"], "документов:", s["documents_total"], "несопоставленных:", s["unmapped_total"], "пробелов:", len(s["gaps"]))'
|
||||
Loading…
x
Reference in New Issue
Block a user