diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 7453842..1d7272c 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -11,15 +11,16 @@ SPA поверх API arm-core (`/api/v1`). Своих данных и бизне | `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/` | блоки экранов | `components/`, `store/`, `services/api/` | +| `src/widgets/` | блоки экранов (`DocumentReview` — проверка документа со сканом, `ConsolidatedOsv` — сводная ОСВ, `FilesDrawer`) | `components/`, `store/`, `services/api/`, `utils/` | | `src/layouts/` | страницы-маршруты | `widgets/` | ## Границы - Сетевые запросы — только через `src/api/client.ts`; `fetch` напрямую не вызывается. - `components/` не знают про store и API; `types/` ни от чего не зависят. -- `services/api/mocks.ts` не импортируется из рабочих путей. +- `utils/` не импортирует React, store и API. ## Открытые вопросы - Судьба `arm-core/web` (дубль основы этого репозитория) — решает владелец. diff --git a/CLAUDE.md b/CLAUDE.md index 8eaeeba..1eaf294 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -34,20 +34,23 @@ SPA кредитного аналитика АРМ КА: справочники - `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/services/api/` — функции по ресурсам arm-core (consolidations, files, directories/…). +- `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`, `files` (+ `thunks.ts`). -- `src/layouts/` — страницы (маршруты в `src/App.tsx`), `src/widgets/` — блоки экранов, `src/components/` — примитивы +- `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`); бэкенд — локальный стенд arm-core по `../docs/dev/qa-guide.md`. +- `make start` — dev-сервер (`rsbuild dev`) с proxy `/api` → `ARM_CORE_URL` (по умолчанию `http://localhost:8000`); бэкенд — локальный стенд arm-core по `../docs/dev/qa-guide.md`. ## Тесты -Unit-тесты — vitest (`npm test`, `make test`); дополнительно — `make check` и ручной прогон пути по стенду arm-core с фикстурами из `../fixtures`. +Unit-тесты — vitest на чистых функциях (`src/**/*.test.ts`, `npm test` / `make test`); компоненты не тестируются — ручной демо-сценарий по стенду arm-core с фикстурами из `../fixtures` (README, `scripts/demo-prepare.sh`). ## Для владельца Пользователь слаб во фронте: на TS/React-изменения агент делает код-ревью сам и объясняет решения простым языком. diff --git a/README.md b/README.md new file mode 100644 index 0000000..902f344 --- /dev/null +++ b/README.md @@ -0,0 +1,44 @@ +# arm-web — фронт АРМ КА (демо-прототип) + +SPA кредитного аналитика поверх API `arm-core` (`/api/v1`). Контекст системы — `../CLAUDE.md`, архитектура — +`ARCHITECTURE.md`, соглашения репозитория — `CLAUDE.md`. + +## Запуск + +1. `make install`. +2. Стенд arm-core — по `../docs/dev/qa-guide.md` §4 (ядро на `localhost:8000`, `DEBUG=true`, `make seed-dev`); для + распознавания PDF — профиль `services` (§6.8: tabular-parser, scan-gateway, scan-emulator). +3. `make start` — dev-сервер rsbuild. Запросы `/api` проксируются на `ARM_CORE_URL` (по умолчанию + `http://localhost:8000`), `REACT_APP_API_URL` в dev не нужен. +4. Вход — через dev-форму (`POST /dev/token`), например `ka_go@armka.local`. + +## Проверка + +`make check` — install, biome, `tsc --noEmit`, vitest, `rsbuild build`. Зелёным считается всё вместе. + +## Демо-сценарий + +Пользователь с правами на ГК «Русский бисквит» (`ka_go@armka.local`). + +1. Группы → «Русский бисквит» → «Новая консолидация»: ГСК, 2024, Q3 → члены ГСК отмечены → «Создать» → открыта версия №1. +2. Загрузить квартальные ОСВ РБ и Счастливочной за 2024 Q1–Q3 (`../fixtures/osv/{rb,schastl}/2024-Q{1,2,3}/*`) и подтвердить + ОСВ-документы. Вручную — панель «Организации» → ЮЛ → «Файлы», подтверждение на экране документа; быстро — скрипт: + `scripts/demo-prepare.sh [email]` (каталог фикстур — `FIXTURES_DIR`, по умолчанию `../../fixtures` + относительно репозитория). В конце скрипт печатает сводку. +3. Загрузить `../fixtures/ocr/aep_6.pdf` одному из ЮЛ → ББ и ОФР «Требуется проверка» → открыть ББ: скан слева, форма + справа, исправить ячейку, принять нарушения с комментарием (если есть), подтвердить. +4. «К отчётности» → сводная ОСВ; фильтр «Счастливочная» + «бисквит» → строка `60.01` (сальдо нач. Кт 4 454 151.93, + обороты Дт 128 058 143.65, Кт 125 950 450.77, сальдо кон. Кт 2 346 459.05, `GSK`); «Скачать xlsx». + +Ожидаемая сводка на чистой версии (`qa-guide` §7.2): `строк: 1385 документов: 22 несопоставленных: 1381 пробелов: 7`. +Если на стенде раньше вручную сопоставляли контрагентов (K14), алиасы сохраняются между версиями, и +«несопоставленных» будет меньше (на стенде прогона — 1349); остальные цифры не меняются. + +## Ограничения прототипа + +- Нет блокировки версии («Взять в работу»), сопоставления контрагентов, экрана чек-листа, комментариев, удаления файлов, + смены периметра и ручных переходов статусов. +- Нет правки строк ОСВ (в API нет), картинок страниц и координат ячеек в скане (в API нет). +- Нет генерации типов из OpenAPI и автотестов компонентов (vitest — только чистые функции). +- Нет сборки под контур и переноса в `arm-core/web` — отдельной задачей после демо. +- Изменений arm-core / scan-gateway нет: найденные пробелы — отдельные issue в тех репозиториях. diff --git a/scripts/demo-prepare.sh b/scripts/demo-prepare.sh new file mode 100755 index 0000000..aca0dd2 --- /dev/null +++ b/scripts/demo-prepare.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +# Подготовка демо: загрузить квартальные ОСВ РБ и Счастливочной за 2024 Q1–Q3 в версию и подтвердить ОСВ-документы. +# Использование: scripts/demo-prepare.sh [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"]))'