963 lines
137 KiB
Markdown
963 lines
137 KiB
Markdown
# Гайд тестировщика: локальный стенд АРМ КА (Срезы 0–2c, 3b, 4 и фронт arm-web)
|
||
|
||
Документ для ручного тестирования текущего `main` бэкенда и ветки `test` фронта `arm-web`. Путь проверен 05.10.2026 на
|
||
macOS (Docker 28, compose 2.34, Node 24, uv 0.10): от пустого каталога до экрана администратора — около 15 минут, из них
|
||
10 — скачивание образов. Сценарий в UI (раздел 8) проверен 08.10.2026 в Chrome 1600×1000 на данных РБ 2024 Q3.
|
||
|
||
Как читать: раздел 4 — поднять стенд, раздел 8 — пройти основной путь аналитика в браузере, разделы 6–7 — то же и
|
||
глубже через API (Swagger/curl): там проверяются права, коды ошибок и граничные случаи, до которых UI не доходит.
|
||
Инструкция пользователя по экранам со скриншотами (без стенда и API) — [`../user/user-guide.md`](../user/user-guide.md).
|
||
|
||
## 1. Что сейчас есть и чего нет
|
||
|
||
**Есть (Срез 0 + Срез 1 + Срез 2c):**
|
||
|
||
- Бэкенд `arm-core` (FastAPI, `/api/v1`): вход по email в dev-режиме, профиль пользователя, права как данные.
|
||
- API справочников: группы компаний (ГК), юрлица и ИП (ЮЛ) с историей, переводом между ГК и закрытием, алиасы
|
||
контрагентов, группы связанных компаний и заёмщиков (ГСК/ГСЗ) с составом, филиалы. Видимость по филиалу: головной
|
||
офис видит всё, филиал — только свои ГК.
|
||
- Импорт справочников из выгрузки ЕЦП CRM (xlsx/csv) с пробным прогоном и отчётом.
|
||
- Администрирование: пользователи, роли, матрица «роль × право», защита последнего администратора.
|
||
- Фронтенд **arm-web** (демо-прототип, раздел 8): группы компаний, консолидации и создание консолидации, панель
|
||
«Организации» с чек-листом CSV, загрузка файлов ЮЛ с опросом статуса, просмотр ОСВ-документа с подтверждением, проверка
|
||
форм ББ/ОФР рядом со сканом PDF (правка, акцепт нарушений, подтверждение), сводная ОСВ с фильтрами, пробелами и
|
||
экспортом xlsx; администрирование `/admin` («Пользователи и роли»).
|
||
- Журнал аудита в БД (`dkb.audit_event`).
|
||
- Загрузка файлов ОСВ через API (`POST /legal-entities/{id}/files`), распознавание реальным `tabular-parser`, разбор
|
||
результата в документы и строки ОСВ, статусы файла и документа, автозамена версий, подтверждение документа, удаление
|
||
файла (раздел 6.6); проверка парсера на произвольных форматах ОСВ и формах ББ/ОФР — раздел 6.7; формы ББ/ОФР в ядре (просмотр, правка, акцепт проверок, подтверждение, комментарии, исходный файл, CSV чек-листа; Срез 3b) — раздел 6.9. Всё — через Swagger/curl, UI «Первичные документы» — подсрез 2d.
|
||
- Распознавание PDF в `scan-gateway` с эмулятором ИС Сканирование (Срез 3a, раздел 6.8): отдельно от ядра, без UI; загружать
|
||
`aep_3.pdf` / `aep_6.pdf` из `fixtures/ocr`.
|
||
- Консолидация через API (Срез 4): версии за период, периметр, блокировка «взять в работу», статусы, привязка подтверждённых
|
||
документов, чек-лист, сводная ОСВ, ручное сопоставление контрагента, экспорт xlsx (раздел 7; полный прогон на данных РБ и
|
||
контрольные цифры — 7.1–7.3). Основной путь консолидации есть и в UI arm-web — раздел 8.
|
||
- План счетов — полная выгрузка 1С заказчика, 488 строк (arm-core #25): субсчета реальных ОСВ (66.01, 76.07.x, забалансовые,
|
||
буквенные `01.К`) резолвятся в `account_id`. Границы входов консолидации: периметр ≤ 500 ЮЛ, комментарий статуса ≤ 1000, поиск
|
||
`q` ≤ 500 символов (arm-core #26, раздел 6.5).
|
||
- Парсер ОСВ после пакета ДКБ (arm-tabular-parser #13, #17): скользящие периоды, несколько корней без строки счёта, «Итого
|
||
развернутое», шапка без меток, ИНН в тексте названия и строкой выше (#15); тесты на 25 файлах `osv-dkb/**` по манифесту
|
||
ожиданий, golden — 113 файлов (раздел 6.7).
|
||
|
||
**Нет (это не баги):**
|
||
|
||
- Экранов справочников ГК/ЮЛ/ГСК (пункты меню «ГК и организации», «ЮЛ вне ГК» — заглушки) — проверяются только через
|
||
Swagger (`/docs`) или curl.
|
||
- В UI нет: блокировки версии («Взять в работу»), смены периметра и ручных переходов статусов, сопоставления
|
||
контрагентов в сводной ОСВ, экрана чек-листа (только CSV), комментариев к документам, удаления файлов, правки строк
|
||
ОСВ. Это проверяется через API (разделы 6.6, 6.9, 7). Шахматка, КФО, «Единое хранилище», «Журнал действий» —
|
||
следующие срезы (в UI — заглушки или неактивные вкладки).
|
||
- Настоящего входа через SSO банка. Вход — dev-форма по email без пароля.
|
||
|
||
## 2. Что установить и проверить из ПО
|
||
|
||
| Что | Версия | Проверка | Если нет |
|
||
|---|---|---|---|
|
||
| ОС | macOS или Linux. Windows — только через WSL2 (Ubuntu); нативно не проверялось | — | — |
|
||
| Git + SSH-ключ в Gitea | любая свежая | `ssh -T -p 2222 git@git.aepif.ru` отвечает приветствием | добавить публичный ключ в профиль Gitea `git.aepif.ru` |
|
||
| Docker + compose v2 | Docker ≥ 24, compose ≥ 2.20 (проверено 28.0 / 2.34) | `docker compose version` | Docker Desktop (macOS) или docker-ce + docker-compose-plugin (Linux) |
|
||
| `uv` | ≥ 0.4 (проверено 0.10) | `uv --version` | `curl -LsSf https://astral.sh/uv/install.sh \| sh`; Python 3.11 `uv` скачает сам |
|
||
| Node.js + npm | Node ≥ 20 LTS (проверено 24.7) | `node --version && npm --version` | nodejs.org или `nvm install --lts` |
|
||
| `make`, `curl` | любые | `make --version`, `curl --version` | macOS: Xcode Command Line Tools (`xcode-select --install`); Linux: `build-essential` |
|
||
| Свободные порты | 5432, 4222, 8222, 9000, 9001 (compose), 8000 (бэкенд), 8001 (tabular-parser), 3000 (фронт) | `lsof -i :5432` и т. д. пусто | закрыть локальный PostgreSQL/MinIO или сменить порты в `docs/dev/.env` и `arm-core/api/.env` |
|
||
| Ресурсы | Docker: ≥ 4 ГБ RAM, ≥ 3 ГБ диска под образы и тома | — | — |
|
||
| Доступ в интернет | нужен один раз: образы Docker Hub, PyPI, npm | — | Nexus банка не требуется |
|
||
|
||
Необязательно, но удобно: клиент PostgreSQL (DBeaver или `psql`) для просмотра аудита; Postman или аналог — Swagger
|
||
на `/docs` достаточно.
|
||
|
||
Браузер — свежий Chrome, Firefox, Safari или Edge (просмотр PDF использует современный pdf.js; в старых браузерах скан
|
||
не откроется). Удобный размер окна — от 1600×1000: на нём сверялся макет.
|
||
|
||
## 3. Клонирование
|
||
|
||
Репозитории должны лежать **рядом**, в одном каталоге, под именами `docs`, `arm-core`, `fixtures`, `arm-web` (в коде пути
|
||
вида `../docs/...`, `../fixtures/...`):
|
||
|
||
```bash
|
||
mkdir arm-ka && cd arm-ka
|
||
git clone ssh://git@git.aepif.ru:2222/RSHB/arm-ka-docs.git docs
|
||
git clone ssh://git@git.aepif.ru:2222/RSHB/arm-core.git arm-core
|
||
git clone ssh://git@git.aepif.ru:2222/RSHB/arm-ka-fixtures.git fixtures
|
||
git clone -b test ssh://git@git.aepif.ru:2222/RSHB/arm-web.git arm-web
|
||
```
|
||
|
||
Фронт тестируется с ветки **`test`** репозитория `arm-web` (интеграционная ветка прототипа; `main` там — исходная копия
|
||
без экранов консолидации). Обновление: `git -C arm-web pull`.
|
||
|
||
Сервисы распознавания собираются из исходников, поэтому для загрузки файлов (6.6–7, раздел 8) рядом нужен ещё
|
||
`tabular-parser`, для PDF (6.8, 8.5) — `scan-gateway`:
|
||
|
||
```bash
|
||
git clone ssh://git@git.aepif.ru:2222/RSHB/arm-tabular-parser.git tabular-parser
|
||
git clone ssh://git@git.aepif.ru:2222/RSHB/arm-scan-gateway.git scan-gateway
|
||
```
|
||
|
||
Без SSH-ключа можно клонировать по HTTPS с логином и паролем Gitea: `https://git.aepif.ru/RSHB/arm-core.git` и т. д.
|
||
|
||
`fixtures` содержит реальные данные (внутренний доступ) — нужен для файла импорта. Если доступа нет, пункт 6.3
|
||
чек-листа можно выполнить на полной выгрузке из `docs/source/biq/`.
|
||
|
||
## 4. Запуск стенда
|
||
|
||
Все команды — из каталога `arm-core`, если не сказано иное.
|
||
|
||
### 4.1 Инфраструктура (PostgreSQL, NATS, MinIO)
|
||
|
||
```bash
|
||
cp ../docs/dev/.env.example ../docs/dev/.env
|
||
make up
|
||
docker compose -f ../docs/dev/docker-compose.yml up -d --wait postgres nats minio
|
||
docker compose -f ../docs/dev/docker-compose.yml ps
|
||
```
|
||
|
||
Ожидание: `postgres`, `nats`, `minio` — `Up (healthy)`; `minio-init` — `Exited (0)` (это нормально: он создаёт бакет и
|
||
завершается). Первый запуск скачивает образы, это самая долгая часть.
|
||
|
||
### 4.2 Бэкенд
|
||
|
||
```bash
|
||
make venv # api/.venv на Python 3.11, зависимости из PyPI
|
||
cp api/.env.example api/.env # dev-настройки: DEBUG=true, AUTH_DEV_ISSUER=true
|
||
make migrate # схема БД
|
||
make seed-dev # справочники + 7 dev-пользователей + dev-ГК под документы fixtures (см. ниже)
|
||
cd api && .venv/bin/python run_server.py
|
||
```
|
||
|
||
Ожидание: в логе нет трейсбэков; `curl -s localhost:8000/healthcheck` отвечает `{"uptime": ..., "nats": "ok"}`.
|
||
Swagger: <http://localhost:8000/docs>. Сервер слушает только `127.0.0.1`, так и оставьте.
|
||
|
||
Для сценариев загрузки ОСВ (6.6) нужны ещё `NATS_ENABLED=true` (так в `api/.env.example`) и **tabular-parser**; поднимайте
|
||
только его, без второго экземпляра ядра (он забирает часть сообщений `files.tabular_parsed`):
|
||
|
||
```bash
|
||
docker compose -f ../docs/dev/docker-compose.yml --profile services up -d --build --no-deps tabular-parser
|
||
curl -s localhost:8001/healthcheck # nats: ok
|
||
```
|
||
|
||
### 4.3 Фронтенд arm-web (второй терминал, из корня `arm-ka`)
|
||
|
||
```bash
|
||
cd arm-web
|
||
make install # npm install; первый раз — несколько минут
|
||
make start # dev-сервер rsbuild на :3000, браузер откроется сам
|
||
```
|
||
|
||
Откроется <http://localhost:3000/> с формой «Вход для разработки». Запросы `/api` dev-сервер проксирует на ядро
|
||
(`ARM_CORE_URL`, по умолчанию `http://localhost:8000`), поэтому `REACT_APP_API_URL` задавать не нужно, а CORS не мешает
|
||
скачиванию файлов. Ядро на другом адресе: `ARM_CORE_URL=http://host:port make start`.
|
||
|
||
Запускайте именно `make start`: он задаёт `REACT_APP_ROOT_PATH=/`. Голый `npm run start` ждёт путь приложения как на
|
||
платформе банка (`/aurora/apps/fastapi-dkb-armka`) и показывает белый экран. Не используйте и `make build`/`npm run build`
|
||
для тестирования: в production-сборке dev-формы входа нет, страница показывает «Вход выполняется через РАИСА».
|
||
|
||
Проверка сборки фронта (линтер, типы, юнит-тесты, сборка) — `make check` в `arm-web`; ручному тестированию она не нужна.
|
||
|
||
Старый фронт `arm-core/web` (только `/admin`) по-прежнему запускается
|
||
`cd arm-core/web && npm install && REACT_APP_API_URL=http://localhost:8000 REACT_APP_ROOT_PATH=/ npm run start` — на том же
|
||
порту 3000, поэтому одновременно с arm-web не поднимайте. Он нужен, пока в arm-web не исправлены уведомления
|
||
администрирования (arm-web#7, раздел 6.1).
|
||
|
||
Настройки из `api/.env.example` (`DEBUG=true`, `AUTH_DEV_ISSUER=true`, `CORS_ORIGINS=`, `FORWARDED_ALLOW_IPS=127.0.0.1`)
|
||
для локального стенда менять не нужно: `DEBUG=true` включает документацию API на `/docs`, dev-вход и разрешает `make seed-dev`.
|
||
|
||
`make seed-dev` (arm-core #31) создаёт, кроме пользователей, **dev-ГК под документы из `fixtures`**: ГК «Русский Бисквит» ровно как в
|
||
выгрузке ЕЦП CRM (20 ЮЛ/ИП, филиал «Санкт-Петербургский РФ», ГСК из АО «Русский бисквит» `3528052718` и ООО «Пекарня
|
||
Счастливочная» `3528104476`, алиасы под имена контрагентов из ОСВ) и ГК владельцев форм пакета ДКБ («Унагранде», «Новороссийский
|
||
прокатный завод», «Агрофирма Межениновская»). Поэтому ОСВ и формы из `../fixtures` загружаются сразу на «свои» ЮЛ (6.6, 7.1), а
|
||
импорт выгрузки ЕЦП (6.3) на таком стенде отвечает `GK_EXISTS`/`LE_EXISTS`. Стенд без dev-ГК (пустые справочники, как в тестах
|
||
ядра): `cd api && .venv/bin/python -m scripts.seed --dev --no-dev-groups`.
|
||
|
||
### 4.4 Вход
|
||
|
||
В форме введите **email** dev-пользователя (не имя и не логин) и нажмите «Войти (dev)». Пароля нет. Это единственная форма
|
||
входа АРМ: страница с полями «Имя пользователя / пароль» — это Gitea, а не стенд. Токен живёт в текущей вкладке
|
||
(`sessionStorage`), новая вкладка — новый вход. Токен действует 8 часов; после истечения форма входа появится снова с
|
||
сообщением «Сессия истекла». Кнопки «Выйти» нет: чтобы сменить пользователя, откройте новую вкладку (или закройте текущую).
|
||
Главная `/` после входа показывает ФИО dev-пользователя («Dev: Аналитик ГО») и его роли («Роли: KA_GO»).
|
||
|
||
| Email | Роль | Филиал | Что видит |
|
||
|---|---|---|---|
|
||
| `admin@armka.local` | ADMIN | Головной офис | всё; единственный с правом администрирования и импорта |
|
||
| `ka_go@armka.local` | KA_GO | Головной офис | все ГК и ЮЛ, может создавать и менять |
|
||
| `curator_go@armka.local` | CURATOR_GO | Головной офис | все ГК и ЮЛ, может создавать и менять |
|
||
| `expert@armka.local` | EXPERT | Головной офис | всё, только чтение |
|
||
| `ka_rf@armka.local` | KA_RF | Ростовский РФ | только ГК своего филиала, может создавать и менять |
|
||
| `rka@armka.local` | RKA | Ростовский РФ | только ГК своего филиала |
|
||
| `client_manager@armka.local` | CLIENT_MANAGER | Краснодарский РФ | только ГК своего филиала, чтение |
|
||
|
||
### 4.5 Токен для Swagger и curl
|
||
|
||
В Swagger (`/docs`) токен подставляется кнопкой **Authorize** (замок справа вверху): вставьте значение `access_token` без слова
|
||
`Bearer`, дальше все защищённые ручки отправляют заголовок сами. Кнопка появилась с arm-core #29; если её нет — бэкенд старше, используйте curl:
|
||
|
||
```bash
|
||
TOKEN=$(curl -s -X POST localhost:8000/api/v1/dev/token -H 'Content-Type: application/json' \
|
||
-d '{"email":"ka_go@armka.local"}' | python3 -c 'import sys,json;print(json.load(sys.stdin)["access_token"])')
|
||
curl -s localhost:8000/api/v1/me -H "Authorization: Bearer $TOKEN"
|
||
```
|
||
|
||
В Swagger: кнопка **Authorize** → вставить токен (без слова Bearer) → все запросы пойдут от этого пользователя.
|
||
Чтобы проверить видимость разными ролями, получите второй токен другим email и переавторизуйтесь.
|
||
|
||
## 5. Как читать ответы
|
||
|
||
- Успех: 200/201, тело JSON. Удаление — 204 без тела.
|
||
- Нет токена или он плохой — 401. Нет права — 403 с текстом `недостаточно прав: требуется <КОД>`.
|
||
- Доменные ошибки — объект `{"detail": {"code": "...", "message": "..."}}`: 404 `NOT_FOUND` (в том числе для **чужой**
|
||
записи — так задумано, чужое неотличимо от несуществующего), 409 конфликты (`INN_EXISTS`, `LAST_ADMIN`, …),
|
||
422 валидация (`INVALID_INN`, `OFFICE_NOT_ALLOWED`, …).
|
||
- Ошибки валидации тела (не тот тип поля) — 422 со списком от pydantic.
|
||
- Списки — `{"items": [...], "total": N, "limit": 50, "offset": 0}`, `limit` ≤ 200.
|
||
|
||
Полный каталог кодов — `arm-core/README.md`, раздел «Ошибки API».
|
||
|
||
## 6. Чек-лист сценариев
|
||
|
||
Отмечайте: ✅ ожидание совпало / ❌ баг (с номером issue) / ⚠️ вопрос. Пустые поля ожиданий — проверьте сами и
|
||
зафиксируйте результат.
|
||
|
||
### 6.1 Администрирование (UI `/admin`, вход `admin@armka.local`)
|
||
|
||
Пункт меню «Система → Пользователи и роли». **Известный дефект arm-web#7:** в arm-web на этом экране не появляются
|
||
уведомления (ни об успехе, ни об ошибке), в консоли браузера — `message.error is not a function`, модалка создания при ошибке
|
||
остаётся открытой без текста. Пока он не исправлен, сценарии с ожиданием «уведомлением» (A3, A6, A8, A10) проходите на старом
|
||
фронте `arm-core/web` (раздел 4.3) или проверяйте код ответа во вкладке Network.
|
||
|
||
| № | Шаги | Ожидание |
|
||
|---|---|---|
|
||
| A1 | Открыть «Система → Пользователи и роли» | таблица из 7 пользователей, вкладки «Пользователи» и «Матрица прав» |
|
||
| A2 | «Создать пользователя»: email `qa1@example.com`, ФИО, филиал, роль KA_RF | пользователь появился; войти им в новой вкладке — получится, роли KA_RF |
|
||
| A3 | Создать пользователя с email `qa1@EXAMPLE.com` | ошибка 409 `EMAIL_EXISTS` уведомлением |
|
||
| A4 | Создать пользователя с email `x@corp.local` | ошибка 422 (домен `.local` отклоняется — известное ограничение) |
|
||
| A5 | У `qa1@example.com` сменить роли на KA_GO + EXPERT | сохранилось; `/me` этого пользователя (новая вкладка) показывает обе роли |
|
||
| A6 | Снять роль ADMIN у `admin@armka.local` (у себя), пока он единственный админ | 409 `LAST_ADMIN`, роли не изменились |
|
||
| A7 | Выдать роль ADMIN пользователю `qa1@example.com`, затем снять ADMIN у себя | теперь разрешено |
|
||
| A8 | Деактивировать единственного активного админа | 409 `LAST_ADMIN` |
|
||
| A9 | Матрица прав: роли EXPERT добавить `EXPORT`, сохранить | `/me` эксперта содержит `EXPORT` |
|
||
| A10 | Матрица прав: снять `USERS_ADMIN` у ADMIN, когда других админов нет | 409 `LAST_ADMIN` |
|
||
| A11 | Войти `expert@armka.local`, открыть `/admin` напрямую | «Нет доступа» / «Раздел доступен пользователям с правом администрирования», запросов к `/admin/*` нет (вкладка Network). Пункт меню в arm-web виден всем ролям — так в прототипе, в `arm-core/web` он скрыт |
|
||
| A12 | После правки своих ролей обновить страницу | профиль в шапке обновился только после перезагрузки (известное ограничение) |
|
||
|
||
### 6.2 Справочники (Swagger `/docs`)
|
||
|
||
| № | Пользователь | Шаги | Ожидание |
|
||
|---|---|---|---|
|
||
| D1 | ka_go | `POST /directories/company-groups` `{"name":"ГК Тест ГО"}` | 201, `code` вида `GK-000001`, `base_regional_office_id` пустой |
|
||
| D2 | ka_rf | `POST /directories/company-groups` `{"name":"ГК Тест РФ"}` без филиала | 201, филиал подставлен Ростовский |
|
||
| D3 | ka_rf | то же с `base_regional_office_id` Краснодарского РФ (id из `GET /directories/regional-offices`) | 422 `OFFICE_NOT_ALLOWED` |
|
||
| D4 | ka_rf | `GET /directories/company-groups` | только «ГК Тест РФ», `total` = 1 (ГК ГО не видна) |
|
||
| D5 | ka_rf | `GET /directories/company-groups/{id ГК Тест ГО}` | 404, тело такое же, как для случайного uuid |
|
||
| D6 | ka_go | `GET /directories/company-groups` | обе ГК |
|
||
| D7 | ka_go | `PATCH` ГК: сменить имя | 200; `GET .../history` — одна запись с прежним именем и `valid_to` |
|
||
| D8 | ka_go | `POST /directories/legal-entities` ИП с ИНН из 10 цифр | 422 `INVALID_INN` |
|
||
| D9 | ka_go | ЮЛ `{"inn":"7700000001","entity_type":"ЮЛ","name":"ООО Альфа","company_group_id":<ГК ГО>}` | 201 |
|
||
| D10 | ka_go | повторно тот же ИНН | 409 `INN_EXISTS` |
|
||
| D11 | ka_go | ЮЛ без `company_group_id` | 201, в списке `outside_group = true` |
|
||
| D12 | ka_go | `POST .../legal-entities/{id}/aliases` `{"alias":"ООО «Альфа»"}` | 201, `normalized_alias` = `альфа` |
|
||
| D13 | ka_go | тот же алиас другому ЮЛ | 409 `ALIAS_TAKEN` с `legal_entity_id` владельца |
|
||
| D14 | ka_go | `POST /directories/company-groups/{ГК}/related-groups` kind `gsk`, затем `gsz` | 201 оба, коды `GSK-…`, `GSZ-…` |
|
||
| D15 | ka_go | добавить ЮЛ «Альфа» в ГСК и в ГСЗ той же ГК | оба 201 |
|
||
| D16 | ka_go | создать вторую ГСК в той же ГК и добавить туда «Альфа» | 409 `MEMBER_OF_OTHER_GROUP` |
|
||
| D17 | ka_go | добавить в ГСК ЮЛ из другой ГК | 422 `DIFFERENT_COMPANY_GROUP` |
|
||
| D18 | ka_go | `POST .../legal-entities/{Альфа}/move` в другую ГК, `effective_date` сегодня | 200; членства закрыты (`GET .../legal-entities/{id}` — `memberships` пуст); история `MOVE_TO_GROUP` |
|
||
| D19 | ka_go | `move` с `effective_date` вчера | 422 |
|
||
| D20 | ka_go | `POST .../withdraw` `{"event":"CLOSE","effective_date":сегодня}` | `close_date` заполнен, `company_group_id` пуст |
|
||
| D21 | admin | `DELETE /directories/related-groups/{id непустой}` | 409 `GROUP_NOT_EMPTY`; пустой — 204 |
|
||
| D22 | ka_go | `DELETE /directories/related-groups/{id пустой}` | 403 (право `DIRECTORY_DELETE` только у ADMIN — так задумано) |
|
||
| D23 | expert | любой `POST` в справочники | 403 |
|
||
| D24 | любой | `GET /directories/company-groups?limit=201` | 422 |
|
||
| D25 | ka_go | `POST /directories/legal-entities` в ГК из D14 с `"aliases":["ООО «Бета»","Бета-Юг"]`, `gsk_id` и `gsz_id` — ГСК и ГСЗ из D14 | 201, в ответе два `aliases` и два `memberships`; в журнале — создание ЮЛ, два события по алиасам и два `MEMBERSHIP_CHANGE` |
|
||
| D26 | ka_go | то же с новым ИНН, но в `aliases` алиас «Альфа» (занят в D12) | 409 `ALIAS_TAKEN`; ЮЛ с этим ИНН не появилось |
|
||
| D27 | ka_go | `POST` ЮЛ с `gsk_id` = id ГСЗ | 422 `WRONG_GROUP_KIND` |
|
||
| D28 | ka_go | `PATCH` ЮЛ из D25 `{"aliases":["ООО «Бета»","Гамма"]}` | 403 «недостаточно прав: требуется DIRECTORY_DELETE» (удаляется «Бета-Юг»); алиасы не изменились |
|
||
| D29 | admin | тот же `PATCH` | 200: «ООО «Бета»» с прежним `id`, «Бета-Юг» удалён, «Гамма» добавлен; истории ЮЛ (`…/history`) не прибавилось |
|
||
| D30 | ka_go | `PATCH` ЮЛ из D25 `{"gsz_id":null}`, затем повтор | 200, в `memberships` только ГСК; повтор — без новых событий в журнале |
|
||
|
||
### 6.3 Импорт из ЕЦП CRM (пользователь `admin`, curl или Swagger)
|
||
|
||
Файл: `fixtures/directories/ecp-export-russkiy-biskvit.xlsx` (21 строка). Выполняйте на стенде **без dev-ГК**: после сброса
|
||
(раздел 10) засейте `python -m scripts.seed --dev --no-dev-groups` вместо `make seed-dev`, иначе ГК «Русский Бисквит» и её ЮЛ уже
|
||
есть и счётчики будут другими (I2 даст `GK_EXISTS`/`LE_EXISTS`, `offices_created` 0).
|
||
|
||
| № | Шаги | Ожидание |
|
||
|---|---|---|
|
||
| I1 | `POST /directories/import` с `dry_run=true` | 200; `rows` 21, `groups_created` 1, `offices_created` 1, `entities_created` 20, `memberships_created` 2, `errors` 1 (строка типа ФЛ → `UNSUPPORTED_TYPE`); в БД и справочниках ничего не появилось |
|
||
| I2 | тот же файл `dry_run=false` | 200, тот же отчёт; появились ГК «Русский Бисквит», филиал «Санкт-Петербургский РФ», 20 ЮЛ/ИП, одна ГСК с двумя членами |
|
||
| I3 | ещё раз `dry_run=false`, `on_existing=skip` | все строки `LE_EXISTS`/`GK_EXISTS`, число записей не изменилось |
|
||
| I4 | csv без колонки «Тип» | 422 `MISSING_COLUMNS`, в ответе список недостающих колонок |
|
||
| I5 | файл в кодировке cp1251 (сохранить csv из Excel «CSV (разделители — запятые)») | 422 `UNSUPPORTED_FILE` |
|
||
| I6 | пользователем `ka_go` | 403 |
|
||
| I7 | полная выгрузка `docs/source/biq/ecp-export-lagal-entity.xlsx`, `dry_run=true` | 200 за несколько секунд; `rows` 8400, `offices_created` 53; есть коды `UNSUPPORTED_TYPE`, `INVALID_INN`, `DUPLICATE_IN_FILE` |
|
||
|
||
```bash
|
||
ADMIN=$(curl -s -X POST localhost:8000/api/v1/dev/token -H 'Content-Type: application/json' \
|
||
-d '{"email":"admin@armka.local"}' | python3 -c 'import sys,json;print(json.load(sys.stdin)["access_token"])')
|
||
curl -s -X POST localhost:8000/api/v1/directories/import -H "Authorization: Bearer $ADMIN" \
|
||
-F file=@../fixtures/directories/ecp-export-russkiy-biskvit.xlsx -F dry_run=true | python3 -m json.tool | head -40
|
||
```
|
||
|
||
### 6.4 Аудит (БД)
|
||
|
||
```bash
|
||
docker compose -f ../docs/dev/docker-compose.yml exec postgres \
|
||
psql -U armka -d armka -c "select occurred_at, action, section, object_type, changed_field from dkb.audit_event order by occurred_at desc limit 20"
|
||
```
|
||
|
||
| № | После чего | Ожидание |
|
||
|---|---|---|
|
||
| L1 | A2, A5, A9 | записи `USER_CHANGE`, `USER_ROLE_CHANGE`, `ROLE_PERMISSION_CHANGE` с `before_value`/`after_value` |
|
||
| L2 | D1, D7 | `GROUP_CHANGE`; смена филиала ГК — `ASSIGNMENT_CHANGE` |
|
||
| L3 | D9, D12, D18 | `LEGAL_ENTITY_CHANGE`, членства — `MEMBERSHIP_CHANGE` |
|
||
| L4 | I2 | одна запись `DIRECTORY_IMPORT` с отчётом в `after_value`; после I1 (`dry_run`) записи нет |
|
||
| L5 | A6, D10 (отказы) | записей аудита нет |
|
||
|
||
### 6.5 Негатив и границы
|
||
|
||
| № | Шаги | Ожидание |
|
||
|---|---|---|
|
||
| N1 | запрос без заголовка `Authorization` | 401 |
|
||
| N2 | токен для несуществующего email (`nobody@armka.local`) через `POST /dev/token` | 404 `пользователь не найден или отключён`, токен не выдан |
|
||
| N3 | тело `PATCH` ГК с неизвестным полем | 422 (поля вне схемы запрещены) |
|
||
| N4 | импорт файла больше 20 МБ | 413 |
|
||
| N5 | `PUT /consolidations/versions/{id}/members` с 501 `legal_entity_ids` (можно повторить один id) | 422, тело — массив pydantic `detail[] {loc: ["body", "legal_entity_ids"], …}`, без `code` |
|
||
| N6 | `POST …/status` с `comment` длиннее 1000 символов | 422, массив pydantic с `loc: ["body", "comment"]` |
|
||
| N7 | `GET …/osv?q=<501 символ>` и `GET …/osv/export?q=<501 символ>` | 422, массив pydantic с `loc: ["query", "q"]`; 500 символов — 200 |
|
||
| N8 | `DELETE /directories/legal-entities/{id}/aliases/{alias_id}` для уже перенесённого алиаса | 404 `NOT_FOUND`, записи аудита об удалении нет |
|
||
|
||
### 6.6 Загрузка ОСВ и просмотр строк через API (Срез 2c)
|
||
|
||
Предусловия: раздел 4.2 (ядро с `NATS_ENABLED=true`, tabular-parser на `:8001`), файлы из `../fixtures/osv/`. Выполняйте на
|
||
свежем стенде (раздел 10) или с другим ЮЛ: дедуп и автозамена зависят от уже загруженного. Ожидаемое время от загрузки до
|
||
`PROCESSED` — до 15 секунд (опрос публикатора и цикла маппинга по 5 с, парсер — доли секунды). В UI загрузка, опрос статуса,
|
||
дубликат и подтверждение ОСВ — W12–W17 (раздел 8); удаления файлов и автозамены в UI нет.
|
||
|
||
Подготовка: пользователь `ka_go` (права `DOCUMENT_EDIT`, `DOCUMENT_VIEW`, видит все ЮЛ). ЮЛ должно называться как в шапке ОСВ —
|
||
`АО "Русский бисквит"`, ИНН `3528052718`. После `make seed-dev` оно уже есть («РУССКИЙ БИСКВИТ, АО» с алиасом под шапку):
|
||
`GET /directories/legal-entities?q=бисквит` — берите его `id`; создавать как в D9 нужно только на стенде без dev-ГК.
|
||
Для проверки «чужого филиала» понадобится ГК с филиалом (dev-ГК «Унагранде» — Головной офис, «Агрофирма Межениновская» —
|
||
Ростовский РФ).
|
||
|
||
```bash
|
||
TOKEN=$(curl -s -X POST localhost:8000/api/v1/dev/token -H 'Content-Type: application/json' \
|
||
-d '{"email":"ka_go@armka.local"}' | python3 -c 'import sys,json;print(json.load(sys.stdin)["access_token"])')
|
||
H="Authorization: Bearer $TOKEN"
|
||
# ЮЛ — только на стенде без dev-ГК (после make seed-dev оно есть: возьмите id из GET …/legal-entities?q=бисквит)
|
||
LE=$(curl -s -X POST localhost:8000/api/v1/directories/legal-entities -H "$H" -H 'Content-Type: application/json' \
|
||
-d '{"inn":"3528052718","entity_type":"ЮЛ","name":"АО \"Русский бисквит\""}' | python3 -c 'import sys,json;print(json.load(sys.stdin)["id"])')
|
||
# загрузка (U1)
|
||
curl -s -X POST "localhost:8000/api/v1/legal-entities/$LE/files" -H "$H" -F 'files=@../fixtures/osv/rb/2024-FY/ОСВ_66 за 2024.xls'
|
||
FILE=<file_id из ответа>
|
||
# опрос карточки файла (U2): повторяйте до status = PROCESSED
|
||
curl -s "localhost:8000/api/v1/files/$FILE" -H "$H" | python3 -m json.tool
|
||
DOC=<documents[0].id из карточки>
|
||
curl -s "localhost:8000/api/v1/documents/$DOC" -H "$H" | python3 -m json.tool
|
||
curl -s "localhost:8000/api/v1/documents/$DOC/entries" -H "$H" | python3 -m json.tool
|
||
curl -s -X POST "localhost:8000/api/v1/documents/$DOC/confirm" -H "$H"
|
||
curl -s -X DELETE "localhost:8000/api/v1/files/$FILE" -H "$H" -w '%{http_code}\n'
|
||
```
|
||
|
||
| № | Пользователь | Шаги | Ожидание |
|
||
|---|---|---|---|
|
||
| U1 | ka_go | загрузить `ОСВ_66 за 2024.xls` для ЮЛ «Русский бисквит» | 201, `accepted` из одного элемента (`file_id`, `job_id`, `name`), `rejected` пуст; сразу после загрузки `GET /files/{id}` — `status` `UPLOADED` или `PROCESSING` |
|
||
| U2 | ka_go | опрашивать `GET /files/{file_id}` (раз в 2–3 с) | не позднее чем через ~15 с `status = PROCESSED`, `job.status = done`, `job.error_code` пуст; `documents` — один: `document_type = OSV`, `osv_account_code = "66"`, `period_year = 2024`, `period_quarter = Q4`, `is_cumulative = true`, `status = CONFIRMED` (автоподтверждение, OQ-157), `counterparty_check = MATCH`, `period_check = UNKNOWN` |
|
||
| U3 | ka_go | `GET /documents/{id}` | `locator = sheet:Лист_1`, `file_id` = файл из U1, `group_schema` с группировками листа, `warnings` пуст, `confirmed_by` пуст (документ подтверждён системой) |
|
||
| U4 | ka_go | `GET /documents/{id}/entries` | `total = 9`; первая строка — `account_code = "66"`, `level = 0`, `parent_entry_id` пуст; строка «Юг-Финанс / ПЕКАРНЯ СЧАСТЛИВОЧНАЯ» под `66.03` имеет `parent_entry_id` = id строки `66.03`; строки «Итого» нет; суммы — десятичные строки как в Excel (сальдо на конец счёта 66: кредит `54.64`) |
|
||
| U5 | ka_go | загрузить тот же файл ещё раз | 422, `rejected[0].code = DUPLICATE_FILE`, `rejected[0].file_id` — файл из U1; новых файлов нет |
|
||
| U6 | ka_go | загрузить `.docx` (или `.xlsx`, переименованный из `.xls`) | 422, `rejected[0].code = UNSUPPORTED_FILE` |
|
||
| U7 | ka_go | загрузить `ОСВ_67 за 2024.xls` и `.docx` одним запросом (`-F 'files=@…' -F 'files=@…'`) | 201, один в `accepted`, один в `rejected` с `UNSUPPORTED_FILE` |
|
||
| U8 | ka_go | `POST /documents/{id}/confirm` для документа из U1 (уже `CONFIRMED` автоматически) | 409 `ALREADY_CONFIRMED`; ручное подтверждение `NEEDS_CHECK`-документа — в W17 (API) |
|
||
| U9 | ka_go | `DELETE /files/{file_id}` файла из U1 (документ автоподтверждён, к версии не привязан) | 204; `GET /files/{id}` — 404; документы и строки удалены |
|
||
| U10 | ka_go | для того же ЮЛ загрузить `ОСВ_60 за 2024.xls` (rb) и дождаться `PROCESSED`; затем `../fixtures/osv/schastl/2024-FY/Оборотносальдовая ведомость по счету 60 за 2024 г..xlsx` | второй файл — `PROCESSED`, `version_no = 2` (если оба документа 60 / 2024 / Q4); первый — `SUPERSEDED`, `superseded_by_file_id` = второй; у документа второго файла `counterparty_check = MISMATCH`, `status = NEEDS_CHECK` (шапка — другая организация) |
|
||
| U11 | ka_go | `GET /legal-entities/{id}/files`, затем с `include_superseded=true` | в первом — только актуальные файлы; во втором — и заменённый `SUPERSEDED` |
|
||
| U12 | ka_go | `DELETE` второго файла из U10 (заменил первый) | 409 `FILE_SUPERSEDES_OTHERS`; `DELETE` первого (`SUPERSEDED`) — 204, далее `GET /files/{id}` — 404 |
|
||
| U13 | ka_go | `POST /documents/{id}/confirm` для документа первого файла из U10 (`SUPERSEDED`), до удаления в U12 | 409 `FILE_SUPERSEDED` |
|
||
| U14 | expert | `POST .../files` | 403 (нет `DOCUMENT_EDIT`); `GET /files/{id}` — 200 (есть `DOCUMENT_VIEW`) |
|
||
| U15 | client_manager | `GET /files/{id}` файла ЮЛ другого филиала | 404, тело как у случайного uuid |
|
||
| U16 | ka_go | остановить tabular-parser, загрузить новый файл | `status` остаётся `UPLOADED` (ack нет — в `PROCESSING` файл не переходит) ~2 мин (3 повтора по 30 с), затем `ERROR`, `job.error_code` заполнен; `DELETE` такого файла — 204 |
|
||
| U17 | ka_go | загрузить ОСВ ЮЛ в консолидацию `C` (`POST /consolidations/$C/legal-entities/$LE/files`), у которой версия `V` в статусе «Корректировка данных» (`DATA_CORRECTION`); после `PROCESSED` (документ автоподтверждён) — `DELETE /files/{file_id}` | 204; `FILE_DELETE.after_value.unbound_version_ids` содержит `V`; статус версии `V` не меняется; `GET /files/{id}` — 404 |
|
||
| U18 | ka_go | то же, но версия `V` переведена на согласование (`CONSOLIDATION_RF` и далее, см. §9.1) | 409 `FILE_HAS_CONFIRMED_DOCUMENTS`; файл и привязка на месте |
|
||
|
||
Аудит (6.4 — запрос из psql): после U1 — `FILE_UPLOAD` и `DOCUMENT_CONFIRM` с пустым `user_id` (автоподтверждение); W17 — `DOCUMENT_CONFIRM` с пользователем; U10 — `FILE_REPLACE` с пустым `user_id`
|
||
(системное событие) и `description`, где перечислены документы заменённого файла; U12 — `FILE_DELETE` с карточкой файла
|
||
в `before_value`. Смены статусов обмена (`PROCESSING`, `PROCESSED`, `ERROR`) аудитом не фиксируются — так задумано.
|
||
|
||
### 6.7 Проверка парсинга ОСВ (xlsx/xls/xlsb) на произвольных файлах
|
||
|
||
Парсер (`tabular-parser`) не привязан к координатам: лист ОСВ, заголовок таблицы, колонки сумм, колонка ИНН и иерархия
|
||
строк ищутся по содержимому (`05-domain-rules.md` §1.0). Поэтому фикстуры РБ и Счастливочной — только примеры, и
|
||
основная задача тестирования — **файлы других форматов**: иные конфигурации 1С, другие учётные системы, файлы после
|
||
ручной правки бухгалтером. Два способа прогнать файл.
|
||
|
||
**Способ А — через стенд** (как в 6.6): загрузить файл на ЮЛ, дождаться `PROCESSED`, смотреть `GET /documents/{id}` и
|
||
`…/entries`. Так проверяется весь путь, включая маппинг в ядре.
|
||
|
||
**Способ Б — парсер без стенда** (быстрее, виден «сырой» результат парсера до ядра; нужен клон `tabular-parser` и
|
||
`make venv` в нём):
|
||
|
||
```bash
|
||
cd ../tabular-parser/api
|
||
ENV_FILE=.env.test .venv/bin/python -m src.parser.cli "/путь/к/файлу.xlsx" > result.json; echo "exit=$?"
|
||
```
|
||
|
||
Код возврата: `0` — `done`, `1` — `needs_check` (контрольные суммы не сошлись, причины в `metrics.warnings`), `2` — ошибка
|
||
(`error.code`: `INVALID_DOCUMENT` — не найден лист ОСВ, `INVALID_FORMAT` — нет заголовка таблицы или обязательных групп
|
||
сумм, `UNREADABLE`, `UNSUPPORTED_FORMAT`, `TIMEOUT`), `3` — файл не найден. Для расширения не из `.xls/.xlsx/.xlsb`
|
||
добавьте `--file-type XLSX`. В `result.json` — то же, что ядро кладёт в `document`/`osv_entry`: `documents[].header`
|
||
(организация, ИНН, счёт, период, нарастающий итог, БУ/НУ, валюта), `group_schema`, `rows[]` (уровень, тип строки,
|
||
счёт, контрагент, ИНН, шесть сумм, `extra`), `checks`, `metrics.warnings`.
|
||
|
||
**Что сверять с Excel (чек-лист на каждый файл).** Откройте файл рядом с результатом и проверьте:
|
||
|
||
| № | Что | Ожидание |
|
||
|---|---|---|
|
||
| P1 | Лист | найден каждый лист с текстом «Оборотно-сальдовая ведомость» (один `documents[]` на лист, `locator = sheet:<имя>`); листы без ОСВ (справки) пропущены с `warnings` `skipped: …`, а не ошибка; листы форм ББ/ОФР — отдельные документы, см. «Формы ББ/ОФР» ниже |
|
||
| P2 | Шапка | `organization_name`, `organization_inn` (если есть в шапке), `account_code_raw`, `period_text` → `period_year`, `period_quarter`, `is_cumulative` соответствуют тексту над таблицей. Поле не найдено → `null` + предупреждение, не ошибка |
|
||
| P3 | Период | «1 квартал» → `Q1`, нарастающий; «2 квартал» → `Q2`, квартальный; «полугодие», «6 месяцев», «январь – июнь» → `Q2`, нарастающий; «9 месяцев», «3 квартал» → `Q3`; «2024 г.», «12 месяцев» → `Q4`, нарастающий; римские кварталы («I кв.») тоже. Диапазон месяцев: «Апрель – Июнь 2025 г.» → `Q2`, квартальный; **скользящий** «Октябрь 2024 г. – Сентябрь 2025 г.» → `period_year = 2025`, `period_quarter` и `is_cumulative` — `null`, в `warnings` «не с начала года» (ядро такой документ к версии не привяжет, OQ-131). Период отдельной ячейкой без «за» («9 месяцев 2025 г.» в строке «Бухгалтерский \| …») тоже распознаётся |
|
||
| P4 | Колонки сумм | шесть сумм разнесены верно: сальдо на начало Дт/Кт, обороты Дт/Кт, сальдо на конец Дт/Кт. Проверьте 2–3 строки с ненулевыми значениями во всех шести колонках и строку «Итого» |
|
||
| P5 | Иерархия | уровни строк (`level`, `parent_row_number`) повторяют дерево в Excel: счёт → субсчёт → контрагент → договор. Стратегия — в `warnings` `hierarchy=outline|indent|account_pattern|sums`; если `sums` — дерево восстановлено по равенству сумм, посмотрите внимательнее |
|
||
| P6 | Типы строк | `group_type`: `ACCOUNT` (код счёта), `COUNTERPARTY`, `CONTRACT`, `DEPARTMENT`, `OTHER`, `TOTAL` (строка «Итого»; в 1С бывает вторая — «Итого развернутое», обе `TOTAL` уровня 0, первая — основная). Строки «Вал.», «Кол.», «USD» → `data_type` `CURRENCY`/`QUANTITY`; «НУ» → `NU` |
|
||
| P7 | ИНН | если в таблице есть колонка ИНН (по заголовку или по содержимому, когда ≥ 80 % значений — 10/12 цифр), `counterparty_inn` заполнен у контрагентов и **не** заполнен у счетов и договоров; КПП и номера договоров за ИНН не принимаются. Без колонки ИНН: ИНН берётся из **хвоста названия** («Полиграф, 3528315710», «(ИНН 7700000000)», «ИНН: …») — `counterparty_name` без ИНН и разделителя, `row_value` как в файле; **строка только с ИНН над названием** склеивается со следующей строкой того же уровня и из `rows` исчезает (нумерация `row_number` с пропуском — это нормально); ячейка «ИНН/КПП» даёт ИНН. Приоритет: колонка → строка выше → хвост названия |
|
||
| P8 | Числа | суммы — строки с двумя знаками (`"1234567.89"`); текстовые числа «1 234 567,89», «(1 234,00)», «—» разобраны как число, отрицательное, `null`; пустая ячейка — `null`, не `0` |
|
||
| P9 | Контрольные суммы | `status = done`: сальдо строки сходится (начало + обороты = конец), «сумма детей = родитель», «Итого» = корню **или** сумме строк уровня 0 (несколько корней 58.01/58.03 без строки «58» — вариант 2, Новоросметалл/Агрокомплекс). `needs_check` при расхождении, в `checks` — какие строки; «подгонки» под сходимость быть не должно |
|
||
| P10 | Лишние колонки | «КПП», «Код», «Количество» и прочие попали в `extra` строки, суммы не сдвинулись |
|
||
| P11 | Шапка без меток | ОСВ без строки «по счету …» и с колонками «Счет: Код / Наименование» (ОСВ 63 Новоросметалл): счёт берётся из первой строки-счёта таблицы (в `warnings` об этом сказано), период и тип данных («Бухгалтерский») — из отдельных ячеек; строка сумм без подписи принимается за «Итого» с предупреждением |
|
||
|
||
**Реальные нестандартные файлы** — `../fixtures/osv-dkb/` (пакет ДКБ 06.10.2026: шесть организаций, многолистовые книги,
|
||
вариант 2 «сумма», скользящий период, 50 тыс. строк). Ожидаемый статус каждого листа — манифест
|
||
`tabular-parser/api/tests/osv_dkb_expectations.yaml` (данные с причиной: чего там нет, должно быть `done`); golden-снимки есть у 22 файлов
|
||
до 1 МиБ (`golden/expected_osv_parse/osv-dkb/`), три книги Агрокомплекса по 9–14 МБ проверяются только инвариантами.
|
||
`../fixtures/analysis-reports/` («Анализ счета») — ожидается `INVALID_DOCUMENT`. **Готовые кейсы синтетики** — `../fixtures/osv/synthetic/s01…s19` с описанием каждого файла и
|
||
ожиданием в `../fixtures/osv/synthetic/README.md`: шапка варианта 2 («Период: …», «Субконто»), уровни без outline (только
|
||
отступами, только по кодам счетов), строки валюты и количества, числа текстом, два листа ОСВ и посторонний лист, нет
|
||
сальдо на начало и свёрнутое сальдо на конец, колонка КПП, названия субсчетов, сломанная сумма (`needs_check`), повтор
|
||
заголовка внутри таблицы, лист ББ рядом с ОСВ, xlsb, синонимы заголовков, ИНН в названии / строкой выше / «ИНН/КПП»
|
||
(s17–s19). Прогоните их способом Б и сверьте с README.
|
||
|
||
**Формы ББ/ОФР (Срез 2b).** Та же команда CLI (способ Б) разбирает и формы: в `documents[]` лист формы — отдельный документ
|
||
(`document_type` = `BALANCE` / `INCOME` / `DISCLOSURE`, ОСВ идут первыми), с `header`, `columns[]`, `lines[]`, `checks`.
|
||
Что смотреть: `document_type` соответствует форме на листе; `columns[].period_date` — даты колонок слева направо (для ОФР —
|
||
конец периода); `checks.relations_failed` (жёсткие соотношения §7 с `expected`/`actual` по колонкам — причина `needs_check`),
|
||
`checks.soft_relations_failed` и `duplicate_line_codes`; в `metrics.warnings` — предупреждение об единицах («значений с
|
||
дробной частью при единице тыс. руб.: N из M»: значения ББ/ОФР из Excel берутся как показывает числовой формат ячейки, OQ-138, #42; для xlsb — как в файле плюс предупреждение «формат чисел xlsb не читается», для неразобранных форматов — «формат чисел не разобран»), «колонка кодов не найдена»,
|
||
«страница-продолжение без шапки». Ожидания по реальным файлам — `../tabular-parser/api/tests/statements_manifest.yaml` и
|
||
таблица «файл → статус» в README `tabular-parser`; файлы — `../fixtures/statements-xlsx/` (синтетика `st01…st07`).
|
||
Постраничные xls из 1С (страница 2 отдельным файлом, ББ только актив) и формы xlsb в рублях при шапке «тыс. руб.» (формат не читается) дают
|
||
`needs_check` — это штатно (OQ-138, OQ-140), не баг; `done` ожидается только у форм, где §7 сходится (Межениновская).
|
||
|
||
**Как делать свои файлы.** Возьмите любую реальную ОСВ и меняйте по одному признаку, после каждого — прогон и чек-лист:
|
||
|
||
- сдвинуть таблицу: вставить 5–10 пустых строк и 2–3 колонки слева/сверху; перенести шапку организации ниже или выше;
|
||
- переименовать лист; добавить лист «Справка» перед листом ОСВ; положить две ОСВ по разным счетам на разные листы;
|
||
- убрать колонку ИНН совсем; вставить колонку «КПП» между ИНН и суммами; переставить блоки «Обороты» и «Сальдо на конец»;
|
||
- разбить заголовок таблицы на 1–4 строки с объединёнными ячейками; заменить «Сальдо на начало периода» на «Нач. сальдо»,
|
||
«Дебет/Кредит» на «Дт/Кт»;
|
||
- убрать группировку outline (скопировать значения в новую книгу) — уровни должны восстановиться по отступам, затем по кодам
|
||
счетов; убрать и отступы — по шаблону кодов и по суммам;
|
||
- суммы текстом с пробелами и запятой, отрицательные в скобках, прочерки вместо нулей;
|
||
- сохранить как `.xls`, `.xlsx`, `.xlsb` (xlsb — обязательно из Excel, см. ниже);
|
||
- сломать одну сумму на 1 рубль — ожидается `needs_check` с указанием строки, не `done`.
|
||
|
||
**Что не считать багом (известные границы):**
|
||
|
||
- Заголовок таблицы без групп «Обороты» или «Сальдо на конец» → `INVALID_FORMAT`: без них ОСВ не ОСВ. Отсутствовать может
|
||
только «Сальдо на начало».
|
||
- Заголовок с незнакомым синонимом («Нач. остаток») даёт `needs_check`, суммы уходят в `extra`. Это не ошибка кода, а
|
||
словарь: присылайте файл, синоним добавляется строкой в `rules/osv_headers.yaml`.
|
||
- ИНН внутри текста контрагента («Полиграф, 3528315710»), склеенный «ИНН/КПП» и отдельная строка ИНН рядом с названием (любая из
|
||
восьми раскладок: над / под, в дереве / соседом, с суммами / без; `s20`–`s28`, OQ-163, arm-tabular-parser#35) парсер
|
||
извлекает: `counterparty_inn` заполнен, `counterparty_name` без ИНН, исходный текст — в `row_value`. Без подсказки в шапке
|
||
строка считается строкой ИНН только при верной контрольной сумме; неверная — остаётся строкой, `needs_check`.
|
||
- ИНН в колонке и ИНН в хвосте названия различаются — берётся колонка, предупреждения нет; строка-ИНН над счётом или договором
|
||
(не контрагентом) склеивается, но с предупреждением «ИНН … отнесён к строке M, но она не контрагент — проверьте» и `needs_check`
|
||
(arm-tabular-parser#18, A9-2/A9-3; #35).
|
||
- Счёт 91 (и 99): строки аналитики несут только обороты, сальдо закрыто на 91.09 → `needs_check` на каждой ОСВ 91. Решение
|
||
владельца по OQ-132 — исключить 91 и 99 из тождества строк (список в `rules/*.yaml`); до реализации (arm-tabular-parser#11
|
||
п. 2) `needs_check` — ожидаемое поведение, не баг. Аналитика без сальдо на конец (магазины Агрокомплекса в 62/76) — тоже `needs_check` по данным файла.
|
||
- Скользящий период даёт `period_quarter = null` (см. P3) — это не ошибка парсера; что с ним делать, решает ядро (OQ-131).
|
||
- xlsb читается без outline level и форматирования: уровни только по отступам пробелами и кодам счетов. Настоящего xlsb из
|
||
Excel в фикстурах нет (OQ-102) — если у вас есть Excel, сохраните `s02_no_outline_indent.xlsx` как xlsb и прогоните.
|
||
- Строка «Итого» нужна: без неё `needs_check`.
|
||
- Файл больше 50 МиБ, распакованный xlsx больше 500 МиБ или разбор дольше 120 с → `UNSUPPORTED_FORMAT` / `TIMEOUT`.
|
||
- Парсер не знает ядра: признак «нарастающая» для ОСВ за Q2–Q4 ядро перепроверяет по данным Q1 (6.6, `is_cumulative`), ОСВ за
|
||
Q1 ядро считает и нарастающей, и квартальной (OQ-124).
|
||
|
||
Баг парсера — это: суммы в не тех колонках, потерянные или задвоенные строки, неверный уровень при наличии outline или
|
||
отступов, `done` при реально не сходящихся суммах, падение (`RECOGNITION_FAILED`) на любом файле. К багу прикладывайте
|
||
файл (или его обезличенную копию) и `result.json`.
|
||
|
||
### 6.8 Распознавание PDF: scan-gateway и эмулятор ИС Сканирование (Срез 3a)
|
||
|
||
PDF и изображения обрабатывает `scan-gateway`; ядро с Среза 3b маппит формы из PDF (`statementDocument`) в документы так же, как из Excel
|
||
(раздел 6.9), но на стенде это требует профиля `services` с эмулятором, поэтому здесь сервис проверяется отдельно. Сервис и нормализатор проверяются отдельно: поднимите профиль `services`
|
||
(`scan-gateway` и `scan-emulator` в `docs/dev/docker-compose.yml`) и пройдите сценарий из README `scan-gateway` («Полный круг против compose и эмулятора»:
|
||
файл в MinIO → `files.img_uploaded` в NATS → ack и итоговый конверт).
|
||
|
||
Что загружать на стенде: **PDF из `fixtures/ocr` — `aep_3.pdf` и `aep_6.pdf`**. Эмулятор узнаёт их **по содержимому**
|
||
(sha256), а не по имени: шлюз кладёт файл в горячую папку как `<uuid>.<ext>`, и имя фикстуры в нём не сохраняется.
|
||
Файл без пары по содержимому получает ответ-ошибку `INVALID_DOCUMENT` («нет пары по содержимому»), если не задан
|
||
`SCAN_EMULATOR_DEFAULT` (в compose по умолчанию пуст); при заданном `SCAN_EMULATOR_DEFAULT` такой файл получает набор по
|
||
умолчанию. Исключение — PDF из `fixtures/ocr`, для которых нет JSON-набора (`aep_4`, `aep_7`, `aep_9`, `aep_30`–`aep_34`):
|
||
они совпадают по хешу, но «нет пары» и **остаются `INVALID_DOCUMENT` даже при заданном default** (OQ-151).
|
||
|
||
| Файл | Ожидание |
|
||
|---|---|
|
||
| `aep_3.pdf` | `needs_check`, два документа (`BALANCE`, `INCOME`). В `metrics.warnings` (по эталону `fixtures/ocr/expected_normalize/aep_3.json`): файл — `ИНН распознан неуверенно`; таблица 0 — `шапка синтезирована: колонки без дат`, `строк с нечисловым кодом пропущено: 8`, `строк без кода с суммами пропущено: 2`, повторы кодов 1320/1350/1360/1370; таблица 1 — шапка синтезирована, пропущено 2 и 2; строка `<job_id>_ofr.json: INVALID_DOCUMENT: …` (второй файл набора — ответ-ошибка, штатно; понижает статус до `needs_check`) |
|
||
| `aep_6.pdf` | `needs_check`, два документа. Предупреждения: файл — `ИНН не распознан`, `дата отчёта не разобрана`; таблица 0 — пропущено 2 (нечисловой код) и 19 (без кода с суммами), `редакция fsbu4 не заполнена` (форма 2025 года, `fsbu4` пока пуста — OQ-150), повторы кодов 1220/1520; таблица 1 — пропущено 1 и 2, `fsbu4` не заполнена, повтор кода 2410 (OQ-152) |
|
||
| `aep_5.PDF`, `aep_8.pdf` | `error`, `INVALID_DOCUMENT` с сообщением ИС (в фикстурах лежат ответы-ошибки) |
|
||
| любой другой файл | `error`, `INVALID_DOCUMENT` эмулятора («нет пары по содержимому»), если не задан `SCAN_EMULATOR_DEFAULT`; PDF без JSON-набора — всегда (OQ-151) |
|
||
|
||
Что смотреть: `raw/<job_id>.json` (склейка всех файлов набора) и `results/<file_id>/<job_id>/result.json` в MinIO; подписи
|
||
колонок разобраны в даты, коды строк, суммы со скобками (минус), `is_recognized`, `string_value` только у неразобранных сумм.
|
||
`done` на реальных сканах почти не бывает — это ожидаемо. В dev-compose у шлюза `SCAN_CLEANUP=true`: после ответа
|
||
`Output/<job_id>/` и входящий файл удаляются. Что не считать багом: строки с мусорным или пустым кодом не попадают в документ, но есть предупреждения
|
||
`строк с нечисловым кодом пропущено` / `строк без кода с суммами пропущено` (OQ-149); при повторе кода остаётся первая
|
||
строка (OQ-152); минус не поставлен при `}` вместо `)` (OQ-147); `is_recognized = false` у прочерка «—» (OQ-148); сумма
|
||
с тремя знаками после запятой (`1,234`) — `amount = null`, текст в `string_value`.
|
||
|
||
### 6.9 Формы ББ/ОФР: просмотр, правка, акцепт, подтверждение (Срез 3b)
|
||
|
||
Предусловие (OQ-157): чистая форма после маппинга сразу `CONFIRMED` (`confirmed_by` пуст, `reviewed_by` NULL) и правке не поддаётся (`PATCH` — 409 `DOCUMENT_READONLY`). Поэтому F1–F2 — на чистой форме `1_Форма1_бух_09.xlsx`; затем удалите её (`DELETE /files/{file_id}`, 204) и загрузите копию с нарушением: в Excel сумма строки 1600 колонки 0 увеличена на 100 (`1600 = 1100 + 1200` нарушено). F3–F10 ведутся на этой копии (`file_id` новой формы); F3 увеличивает сумму от значения копии, F7 возвращает её на верную сумму `1100 + 1200`.
|
||
|
||
Предусловия: раздел 4.2 (ядро с `NATS_ENABLED=true`, tabular-parser на `:8001`), `make seed-dev` (dev-ГК «Агрофирма Межениновская»,
|
||
ЮЛ с ИНН `7016006635`, Ростовский РФ — ИНН совпадает с ИНН шапок форм), пользователь `ka_go` (`DOCUMENT_EDIT`, `DOCUMENT_VIEW`,
|
||
видит всё) и для негатива `expert` (только `DOCUMENT_VIEW`), `client_manager` (чужой филиал). В UI правка, акцепт и подтверждение
|
||
формы из PDF — W19–W28 (раздел 8); комментариев и CSV чек-листа по ЮЛ там нет. Файлы — книги Excel
|
||
`../fixtures/statements-xlsx/mezheninovskaya/2025-Q3/1_Форма1_бух_09.xlsx` (ББ, 3 колонки: 30.09.2025, 31.12.2024, 31.12.2023, 37 строк
|
||
с кодом) и `1_Форма2_бух_09.xlsx` (ОФР, 2 колонки). Подробные схемы и коды ошибок — `dev/api-contract-slice-3b.md`; правила —
|
||
`context/05-domain-rules.md` §7, §9.3, §9.5. Новый ЮЛ с чужим ИНН для сценария «не совпал контрагент» — создайте через API
|
||
справочников (раздел 6.2). Ожидаемое время от загрузки до `PROCESSED` — до 15 секунд.
|
||
|
||
```bash
|
||
TOKEN=$(curl -s -X POST localhost:8000/api/v1/dev/token -H 'Content-Type: application/json' \
|
||
-d '{"email":"ka_go@armka.local"}' | python3 -c 'import sys,json;print(json.load(sys.stdin)["access_token"])')
|
||
H="Authorization: Bearer $TOKEN"
|
||
LE=$(curl -s "localhost:8000/api/v1/directories/legal-entities?q=7016006635" -H "$H" | python3 -c 'import sys,json;print(json.load(sys.stdin)["items"][0]["id"])')
|
||
curl -s -X POST "localhost:8000/api/v1/legal-entities/$LE/files" -H "$H" \
|
||
-F 'files=@../fixtures/statements-xlsx/mezheninovskaya/2025-Q3/1_Форма1_бух_09.xlsx'
|
||
FILE=<file_id из ответа>
|
||
curl -s "localhost:8000/api/v1/files/$FILE" -H "$H" | python3 -m json.tool # опрос до status = PROCESSED
|
||
DOC=<documents[0].id из карточки>
|
||
curl -s "localhost:8000/api/v1/documents/$DOC/statement" -H "$H" | python3 -m json.tool
|
||
```
|
||
|
||
| № | Пользователь | Шаги | Ожидание |
|
||
|---|---|---|---|
|
||
| F1 | ka_go | загрузить `1_Форма1_бух_09.xlsx` для ЮЛ с ИНН 7016006635, дождаться `PROCESSED` | один документ: `document_type = BALANCE`, `osv_account_code` пуст, `period_year = 2025`, `period_quarter = Q3`, `counterparty_check = MATCH`, `comments_count = 0`, `status = CONFIRMED` (автоподтверждение: `confirmed_by` пуст, `reviewed_by` NULL) |
|
||
| F2 | ka_go | `GET /documents/{id}/statement` | 200; `edition = fsbu4`; `document.reporting_date = 2025-09-30`; `columns` — 3 (у колонки 0 `is_current = true`); `lines` — 37, отсортированы по `line_code`, у каждой `values` на все колонки; суммы — строки; `header.inn.value = "7016006635"`, `confidence = "100.00"`, `modified = false`; `checks.failed`, `soft_failed` пусты, `accepted = null` |
|
||
| F3 | ka_go | `PATCH …/statement` `{"values":[{"line_id":…,"column_id":…,"amount":"<сумма строки 1600, колонки 0, увеличенная на 100>"}]}` (`line_id`/`column_id` — из `GET …/statement` файла-копии, не из F2) | 200; у ячейки `amount` новая, `original_amount` = прежняя, `is_modified = true`, `modified_by` = пользователь; `checks.failed` непуст (нарушено `1600 = 1100 + 1200` и/или `1600 = 1700`), `document.status = NEEDS_CHECK`; в `audit_event` одно `RECOGNIZED_DATA_EDIT` (раздел `RECOGNITION`, `object_type = document`) с `before`/`after` ячейки (6.4) |
|
||
| F4 | ka_go | `POST …/confirm` | 409 `CHECKS_NOT_PASSED`, `detail.failures[]` — нарушения (`column_index`, `relation`, `expected`, `actual`) |
|
||
| F5 | ka_go | `POST …/statement/checks/accept` `{"comment":"округление в форме клиента"}` | 200; `checks.accepted = {by, at, comment}`, `checks.failed` по-прежнему непуст; статус документа пересчитан (`RECOGNIZED`, если других причин `NEEDS_CHECK` нет); `POST …/confirm` теперь проходит (но F11 делайте после возврата значений) |
|
||
| F6 | ka_go | снова `PATCH` той же ячейки другой суммой | 200; `checks.accepted = null` (акцепт сброшен правкой) |
|
||
| F7 | ka_go | `PATCH` той же ячейки на верную сумму (`1100 + 1200` по строкам 1100 и 1200 этой формы) | 200; `checks.failed` пуст; `document.status = RECOGNIZED` (ручное подтверждение — F11); `original_amount` остаётся сломанной суммой из копии, `is_modified = true`; `PATCH` с пустым телом `{}` — 200, новых записей `RECOGNIZED_DATA_EDIT` нет |
|
||
| F8 | ka_go | `PATCH` `{"header":{"inn":"123"}}` и `{"header":{"inn":null}}`; `{"values":[{"line_id":"<случайный uuid>","column_id":"<случайный uuid>","amount":"1"}]}` | 422 `INVALID_INN` (оба случая); 422 `UNKNOWN_CELL`; данные не изменились |
|
||
| F8a | ka_go | `PATCH` `{"values":[{"line_code":"1600","column_id":"<реальный column_id из F2>","amount":"1"}]}` (строка 1600 в форме есть; `column_id` должен быть настоящей колонкой формы, иначе сработает проверка колонок с другим текстом); затем то же с `"line_code":"9999"` (вне справочника) | 422 `UNKNOWN_CELL` в обоих случаях: для 1600 `detail.message` — «строка уже есть в форме — передайте line_id», для 9999 — «строки с таким кодом нет в форме этой редакции»; данные не изменились |
|
||
| F8b | ka_go | Новая строка: в API нет операции удаления строки формы — проверять на документе без 1700 (файл «РБ 1 кв. 2025» из прогона q1-2025-run, OQ-156; не фикстура Межениновской) или вручную: `GET …/statement` — строка 1700 есть, `origin = template`, `id = null`; `PATCH` `line_code = "1700"` с суммой | 200; строка получила `id`, `origin = template` (в БД `line_code_original_value IS NULL`; в `StatementOut` это поле не отдаётся), проверки пересчитаны; очистка (`amount: null`) — строка снова пустая, нарушений по ней нет |
|
||
| F9 | ka_go | `PATCH` `{"header":{"report_date":"2025-06-30"}}`, затем вернуть `"2025-09-30"` | период пересчитывается: `period_quarter = Q2` (`reporting_date = 2025-06-30`), `header.report_date.modified = true`, `original_value` не меняется (после возврата на `2025-09-30` — `Q3`, `modified` остаётся `true`); дата не на конец квартала (`2025-09-15`) — 200, `period_quarter = null`, статус `NEEDS_CHECK`, `POST …/confirm` — 409 `PERIOD_REQUIRED` (поправьте дату — подтверждение проходит) |
|
||
| F10 | ka_go | `POST …/accept` без нарушений | 409 `NOTHING_TO_ACCEPT` |
|
||
| F11 | ka_go | `POST …/confirm` для формы в `RECOGNIZED` после F7 и F9 (ручное подтверждение) | 200 `DocumentOut`, `status = CONFIRMED`, `confirmed_by`/`confirmed_at`; повтор — 409 `ALREADY_CONFIRMED`; `PATCH`/`accept` — 409 `DOCUMENT_READONLY`; в БД у шапки `reviewed_by/at` заполнены. Для автоподтверждённой чистой формы (F1, до удаления) `POST …/confirm` — 409 `ALREADY_CONFIRMED`, `confirmed_by` пуст |
|
||
| F12 | ka_go | загрузить `1_Форма2_бух_09.xlsx` тому же ЮЛ, дождаться `PROCESSED`; `GET …/statement` | `document_type = INCOME`, `period_quarter = Q3`, 2 колонки, `edition = fsbu4`; у ББ и ОФР поле `checks.cross_form` — либо `null`, либо нарушение `1370 − 1370(начало года) = 2400` (мягкое, не блокирует подтверждение); статус ОФР — `CONFIRMED` (автоподтверждение; мягкие проверки его не отменяют) |
|
||
| F13 | ka_go | `GET /files/{file_id}/content?disposition=inline` и без параметра | 200, тело байт в байт равно загруженному `.xlsx`, `Content-Type` xlsx, заголовки `X-Content-Type-Options: nosniff` и `Cache-Control: private, no-store`; `inline` — ещё `Content-Security-Policy: sandbox`, `Content-Disposition: inline; filename="file.xlsx"; filename*=UTF-8''<имя>` (кириллица в percent-кодировке); `disposition=foo` — 422 |
|
||
| F14 | ka_go | `POST /documents/{id}/comments` `{"text":"нет подписи руководителя"}`; `{"text":" "}`; затем `GET …/comments` и `GET /files/{file_id}` | 201 с `author.full_name`; пустой после обрезки текст — 422; в списке один комментарий; в карточке файла и `GET /documents/{id}` у документа `comments_count = 1`; комментарий к `CONFIRMED` документу разрешён |
|
||
| F15 | ka_go | `GET /users/brief?ids=<uploaded_by файла>,<случайный uuid>`; с 201 id; с `ids=abc` | 200, один элемент `{id, full_name, is_active}`; 422 `TOO_MANY_IDS`; 422 `INVALID_IDS`; то же под `expert` (без `USERS_ADMIN`) — 200 |
|
||
| F16 | ka_go | `GET /directories/legal-entities?q=7016006635` после F1 и F12 | `documents_count = 2` (ББ и ОФР) |
|
||
| F17 | ka_go | в ГК «Агрофирма Межениновская» завести второе ЮЛ и ГСК из двух ЮЛ (раздел 7, «Подготовка»), создать версию за 2025 Q3; убедиться, что ББ и ОФР ЮЛ с ИНН 7016006635 (файлы F1/F12) `CONFIRMED` (чистые формы автоподтверждаются; при `NEEDS_CHECK` — проверить значения и подтвердить вручную, F11); `GET …/checklist` и `GET …/checklist?format=csv` | ячейки «Бух. баланс» и «ОФР» ЮЛ — `CONFIRMED`; CSV: файл начинается с BOM (`EF BB BF`), первая строка «Организация;ИНН;Бух. баланс;ОФР;ОСВ …», разделитель `;`, у ЮЛ «Загружен»; `Content-Disposition` с `filename="checklist.csv"` и `filename*=UTF-8''checklist-<ГК>-2025-Q3.csv` |
|
||
| F18 | expert | `PATCH …/statement`, `POST …/comments`, `POST …/accept` | 403 (нет `DOCUMENT_EDIT`); `GET …/statement` и `GET …/comments` — 200 |
|
||
| F19 | client_manager | `GET …/statement`, `GET /files/{id}/content` документа ЮЛ другого филиала | 404 `NOT_FOUND`, тело как у случайного uuid |
|
||
| F20 | ka_go | `GET …/statement` документа `OSV` (раздел 6.6) | 409 `DOCUMENT_NOT_STATEMENT` |
|
||
| F21 | ka_go | загрузить второй файл с формой ББ за тот же период (книга Form1, пересохранённая в Excel — другой sha256); `PATCH` по документу первого файла (это копия с нарушением из F3–F10; чистая F1 удалена по предусловию §6.9) | старый файл `SUPERSEDED`; в коде (`statements.py`) проверка `FILE_SUPERSEDED` идёт раньше `DOCUMENT_READONLY`, поэтому ответ — 409 `FILE_SUPERSEDED` даже для подтверждённой формы; `PATCH`/`accept` по его документу — 409 `FILE_SUPERSEDED`; чтение `GET …/statement` и комментарии работают |
|
||
|
||
Что не считать багом: формы из OCR (PDF) почти всегда `NEEDS_CHECK` — уверенность значений меньше 100, ИНН распознан неуверенно
|
||
(`aep_3.pdf`, `aep_6.pdf`, раздел 6.8), аналитик правит значения и подтверждает; у форм без колонки кодов документ создаётся без
|
||
строк (OQ-139); повтор одной формы в одном файле — вторая не создаётся, первая `NEEDS_CHECK`; страницы одной формы из разных файлов
|
||
не склеиваются (OQ-140); строк без кода в ответе нет (OQ-36); скачивание файла и CSV в журнал не пишутся (OQ-153); формулы §7 одни
|
||
для редакций `66n` и `fsbu4` (OQ-146); уже смапленные до Среза 3b файлы форм не получают — загрузите файл заново (раздел 10 — сброс
|
||
стенда).
|
||
|
||
## 7. Консолидация через API (Срез 4)
|
||
|
||
Предусловия: ядро запущено (раздел 4.2); для сценариев с файлами — ещё `NATS_ENABLED=true` и tabular-parser на `:8001`
|
||
(раздел 6.6); данные — в `../fixtures/osv/`. Токены: `ka_go` (`CONSOLIDATION_VIEW/EDIT`, видит всё), `ka_rf`
|
||
(`CONSOLIDATION_VIEW/EDIT`, только ГК Ростовского РФ), `expert` (только `CONSOLIDATION_VIEW`), `client_manager`
|
||
(`CONSOLIDATION_VIEW`, Краснодарский РФ), `admin` (прав на консолидации нет). Подробные схемы запросов и ответов, коды ошибок и
|
||
нюансы для фронта — `dev/api-contract-slice-4.md`; Swagger — `/docs`. В UI arm-web (раздел 8) есть K1 (W5–W8), загрузка в
|
||
версию (K8, W13), чек-лист CSV (W11), свод, пробелы и экспорт (K11, K12 частично, K17 — W29–W35); блокировки, периметра,
|
||
статусов и сопоставления (K4–K7, K13–K16, K18–K20) в UI нет.
|
||
|
||
**Подготовка.** Нужна ГК с ГСК не менее чем из двух ЮЛ (ГК — в Ростовском РФ, чтобы её видел `ka_rf`): создайте через API
|
||
справочников (раздел 6.2; ГСК — `POST /directories/company-groups/{id}/related-groups`, участники — `POST
|
||
/directories/related-groups/{id}/members`) либо импортом ЕЦП (I2). Для данных РБ: ЮЛ «АО "Русский бисквит"» (ИНН 3528052718) и
|
||
«ООО "Пекарня Счастливочная"» (в ГСК обе). Ниже `V` — id версии, `G` — id ГК, `S` — id ГСК.
|
||
|
||
```bash
|
||
curl -s -X POST localhost:8000/api/v1/consolidations -H "$H" -H 'Content-Type: application/json' \
|
||
-d '{"company_group_id":"'$G'","related_group_id":"'$S'","year":2024,"quarter":"Q3"}'
|
||
# 201: VersionCard; V = ответ.id, C = ответ.consolidation_id, status = INITIATED, reporting_date = 2024-10-01
|
||
curl -s -X POST localhost:8000/api/v1/consolidations/versions/$V/lock -H "$H" # 201 LockOut
|
||
curl -s -X POST "localhost:8000/api/v1/consolidations/$C/legal-entities/$LE/files" -H "$H" -F 'files=@…'
|
||
curl -s localhost:8000/api/v1/consolidations/versions/$V/checklist -H "$H"
|
||
curl -s "localhost:8000/api/v1/consolidations/versions/$V/osv?limit=50" -H "$H"
|
||
curl -s localhost:8000/api/v1/consolidations/versions/$V/osv/summary -H "$H"
|
||
curl -s -o svod.xlsx -D - "localhost:8000/api/v1/consolidations/versions/$V/osv/export" -H "$H" # Content-Disposition
|
||
```
|
||
|
||
| № | Пользователь | Шаги | Ожидание |
|
||
|---|---|---|---|
|
||
| K1 | ka_go | создать консолидацию ГК/ГСК за 2024 Q3 (команда выше) | 201, `status = INITIATED`, `version_no = 1`, `scenario = CREATE`, `approach = BASIC`, `goodwill_enabled = false`, `members` = текущие члены ГСК, `lock = null`; повтор — 409 `CONSOLIDATION_EXISTS` |
|
||
| K2 | ka_go | то же с `year = 1999`; с ГСК другой ГК; с ГСК из одного ЮЛ; с чужим/несуществующим id ГК | 422 (валидация года); 422 `RELATED_GROUP_MISMATCH`; 422 `PERIMETER_TOO_SMALL`; 404 `NOT_FOUND` |
|
||
| K3 | ka_go | `GET /company-groups/{G}/consolidations`, затем с `?in_work_only=true`; `GET …/versions/{V}` | период с версией; карточка с `members[]` (у каждого `checklist[]`), `history[0].from_status = null`; `ka_rf` для ГК вне своего филиала — 404 |
|
||
| K4 | ka_go | `POST …/lock`; повтор | 201 `LockOut` (`locked_by`, `locked_by_name`); повтор — 200, блокировка та же |
|
||
| K5 | ka_rf (ГК его филиала) | `POST …/lock` на версию, занятую `ka_go` | 409 `LOCKED_BY_OTHER` (`locked_by`, `locked_at`, `locked_by_name`); с телом `{"takeover_reason":"…"}` — 201, `taken_over_from` = id `ka_go`, `ka_go` теряет блокировку; в аудите `CONSOLIDATION_LOCK_TAKEOVER` |
|
||
| K6 | ka_go | после K5 `PUT …/members` | 409 `LOCKED_BY_OTHER` (чужая блокировка), без блокировки вообще — 409 `LOCK_REQUIRED` |
|
||
| K7 | владелец блокировки | `PUT …/members` с составом ≤ 1 ЮЛ, с ЮЛ другой ГК, с тем же составом | 422 `PERIMETER_TOO_SMALL`; 422 `ENTITY_NOT_IN_GROUP` (`legal_entity_ids`); 200 без изменений и без события `MEMBERSHIP_CHANGE` |
|
||
| K8 | владелец блокировки | загрузить ОСВ ЮЛ периметра в консолидацию `C` (раздел 6.6) | 201; версия `INITIATED` → `DOCUMENTS_PROCESSING`; ЮЛ вне периметра — 422 `ENTITY_NOT_IN_PERIMETER`; несуществующая консолидация — 404; `GET /consolidations/$C/legal-entities/$LE/files` — загруженный файл в списке, `consolidation_id = C` |
|
||
| K9 | ka_go | после `PROCESSED` и `NEEDS_CHECK` документа — `GET …/versions/{V}`; затем `POST /documents/{id}/confirm` | `NEEDS_CHECK` → версия `DATA_CORRECTION` (если файл загружал пользователь); подтверждение переводит `INITIATED`/`DOCUMENTS_PROCESSING` → `DATA_CORRECTION`; документ привязан к версии (виден в чек-листе: ячейка `CONFIRMED`) |
|
||
| K10 | ka_go | `GET …/checklist` до и после подтверждения; для квартальных ОСВ — только Q3 | ячейка ОСВ: `MISSING` → `NEEDS_CHECK`/`RECOGNIZED` → `CONFIRMED`; при неполных кварталах — `missing_quarters`; ОСВ по субсчёту (58.03) не закрывает «ОСВ 58» |
|
||
| K11 | ka_go | `GET …/osv`, `…/osv/summary` по данным РБ (раздел «Данные для K11») | строки свода по парам, где покрыты все кварталы `Q1..Qn`; пары без квартала — в `gaps[]` с `missing_quarters`; деньги — строки с двумя знаками; `unmapped_total` > 0 |
|
||
| K12 | ka_go | фильтры: `legal_entity_id`, `account_root=60`, `counterparty_type=EXTERNAL`, `unmapped=true`, `match_kind=MANUAL&match_kind=PROPAGATED`, `q=бисквит`, `limit=2001` | отфильтрованные страницы с `total`; `limit=2001` — 422 |
|
||
| K13 | ka_go | `GET …/counterparty-candidates?q=счастл&entry_id=<entry_ids[0]>`; `scope=all&q=ab` | список ЮЛ с `counterparty_type`; `recommended` — только при точном совпадении нормализованного имени; `scope=all` с `q` короче 3 — 422 `QUERY_TOO_SHORT` |
|
||
| K14 | владелец блокировки | `POST …/osv/entries/{entry_id}/counterparty {legal_entity_id}` для строки «Юг-Финанс / ПЕКАРНЯ СЧАСТЛИВОЧНАЯ» | 200: `row.match_kind = MANUAL` (слать **последнюю** `entry_id` строки свода), `propagated` ≥ 0, `alias_created = true`; в сводке `unmapped_total` уменьшился, у других строк с тем же именем — `PROPAGATED`; в аудите `OSV_COUNTERPARTY_MAP` и `LEGAL_ENTITY_CHANGE` |
|
||
| K15 | владелец блокировки | сопоставить другое имя с алиасом, уже закреплённым за другим ЮЛ; затем с `"move_alias": true` | 409 `ALIAS_TAKEN` (`owners[]`); с `move_alias` — 200, `alias_moved_from` = прежний владелец |
|
||
| K16 | ka_go | `entry_id` строки `NU`/`ACCOUNT` или чужой версии | 404 `NOT_FOUND`; без блокировки — 409 `LOCK_REQUIRED` |
|
||
| K17 | ka_go | `GET …/osv/export` (в т. ч. с фильтрами) | 200, `Content-Disposition: attachment; filename="svod-osv.xlsx"; filename*=UTF-8''…`; файл открывается в Excel: лист «Сводная ОСВ» (суммы — числа, формат `#,##0.00`, число строк как в API) и лист «Пробелы» (от фильтров не зависит); в аудите `EXPORT` |
|
||
| K18 | владелец блокировки | `POST …/status {"to_status":"CANCELLED"}` без комментария; с комментарием | 422 `COMMENT_REQUIRED`; 200, `status = CANCELLED`, блокировка снята (`lock = null`), `history` пополнена |
|
||
| K19 | ka_go | после K18: `PUT …/members`, `POST …/lock`, `POST …/status`, сопоставление | 409 `VERSION_NOT_EDITABLE`; чтение (`GET …/osv`, экспорт, чек-лист) — 200 |
|
||
| K19а | ka_go | после K18 (версия `CANCELLED`, другой версии «в работе» нет): загрузить ОСВ ЮЛ периметра в консолидацию `C` (`POST /consolidations/$C/legal-entities/$LE/files`) | 409 `CONSOLIDATION_NOT_EDITABLE` |
|
||
| K20 | владелец блокировки | `POST …/status {"to_status":"INITIATED"}` из `DATA_CORRECTION`; затем `{"to_status":"DATA_CORRECTION"}` и `{"to_status":"VERIFIED"}` | 200, привязки документов сохраняются; ручные `DATA_CORRECTION`/`VERIFIED` — 409 `TRANSITION_NOT_ALLOWED` (`from_status`, `to_status`) |
|
||
| K21 | expert, client_manager | `GET …/osv`, `…/checklist`; `POST …/lock`, `PUT …/members` | чтение — 200 (client_manager — только ГК своего филиала, иначе 404); правки — 403 `недостаточно прав: требуется CONSOLIDATION_EDIT` |
|
||
| K22 | ka_go | заменить файл (загрузить исправленную ОСВ того же счёта и периода) после подтверждения документа | старый файл `SUPERSEDED`, привязка его документов к версии снята (`FILE_REPLACE.after_value.unbound_version_ids` содержит `V`); новый документ привязывается только после подтверждения |
|
||
|
||
Данные для K11 (РБ 2024, как в сквозном тесте `test_consolidation_fixtures`): ОСВ 60 и 62 РБ и Счастливочной, ОСВ 76
|
||
Счастливочной за Q1, Q2 и Q3 2024 (`../fixtures/osv/…`, подтвердить документы), плюс по одному документу за Q3 (РБ: 01, 58.03,
|
||
66, 67, 76; Счастливочная: 01, 02). Для версии Q3 пары с одним Q3 попадают в `gaps` (пропущены Q1–Q2); ОСВ за Q1 (нарастающая
|
||
по признаку парсера) подходит версии любого квартала (R-14).
|
||
|
||
Аудит (6.4): K1 — `CONSOLIDATION_CREATE`; K4 — `CONSOLIDATION_LOCK`; K5 — `CONSOLIDATION_LOCK_TAKEOVER`; K7 — `MEMBERSHIP_CHANGE`
|
||
(только при изменении состава); переходы статуса — `CONSOLIDATION_STATUS_CHANGE`; K14 — `OSV_COUNTERPARTY_MAP` и `LEGAL_ENTITY_CHANGE`;
|
||
K17 — `EXPORT`; во всех — `consolidation_version_id`. Блокировка не истекает по времени — освободить её можно только `DELETE …/lock`,
|
||
перехватом или аннулированием версии (OQ-119).
|
||
|
||
### 7.1 Полный прогон на данных РБ за 2024 (скрипт)
|
||
|
||
Для сводной ОСВ нужны все квартальные файлы двух ЮЛ. Их 22: РБ (`../fixtures/osv/rb/2024-Q{1,2,3}/`, 2 + 2 + 7) и
|
||
Счастливочная (`../fixtures/osv/schastl/2024-Q{1,2,3}/`, 3 + 3 + 5). Загружать по одному через Swagger долго, поэтому цикл
|
||
(`ka_go`, `$H` из 6.6; `LE_RB` и `LE_SCH` — id ЮЛ «АО "Русский бисквит"» и «ООО "Пекарня Счастливочная"», обе в одной ГСК
|
||
ГК из раздела 7; `V` — версия за 2024 Q3, созданная и взятая в работу по K1/K4 **до** загрузки; `C` — её консолидация (`consolidation_id` из ответа K1) — тогда переходы статусов
|
||
видны на K8/K9):
|
||
|
||
```bash
|
||
F=../fixtures/osv
|
||
for f in "$F"/rb/2024-Q1/* "$F"/rb/2024-Q2/* "$F"/rb/2024-Q3/*; do
|
||
curl -s -X POST "localhost:8000/api/v1/consolidations/$C/legal-entities/$LE_RB/files" -H "$H" -F "files=@$f" | python3 -c 'import sys,json;d=json.load(sys.stdin);print(d["accepted"][0]["name"] if d["accepted"] else d["rejected"])'
|
||
done
|
||
for f in "$F"/schastl/2024-Q1/* "$F"/schastl/2024-Q2/* "$F"/schastl/2024-Q3/*; do
|
||
curl -s -X POST "localhost:8000/api/v1/consolidations/$C/legal-entities/$LE_SCH/files" -H "$H" -F "files=@$f" | python3 -c 'import sys,json;d=json.load(sys.stdin);print(d["accepted"][0]["name"] if d["accepted"] else d["rejected"])'
|
||
done
|
||
# подождать ~30 с, затем убедиться, что все файлы PROCESSED, и подтвердить все документы
|
||
for LE in $LE_RB $LE_SCH; do
|
||
curl -s "localhost:8000/api/v1/legal-entities/$LE/files?limit=50" -H "$H" | python3 -c '
|
||
import sys,json
|
||
for f in json.load(sys.stdin)["items"]:
|
||
print(f["status"], f["original_name"], [ (d["id"], d["osv_account_code"], d["period_quarter"], d["status"]) for d in f["documents"]])'
|
||
done
|
||
# подтверждение: для каждого document id из вывода выше
|
||
for DOC in <id1> <id2> …; do curl -s -o /dev/null -w "%{http_code} $DOC\n" -X POST "localhost:8000/api/v1/documents/$DOC/confirm" -H "$H"; done
|
||
```
|
||
|
||
Ожидание после распознавания: чистые документы уже `CONFIRMED` (автоподтверждение, OQ-157); `confirm` для них вернёт 409 `ALREADY_CONFIRMED` — в цикле это ожидаемо; `GET …/versions/{V}` — `status = DATA_CORRECTION`; в чек-листе у РБ ОСВ 60 и 62 `CONFIRMED` с
|
||
тремя `document_ids`, у Счастливочной — 60, 62, 76; ОСВ 58 у РБ не `CONFIRMED` (есть только 58.03); остальные ячейки ОСВ
|
||
Q3-only — `missing_quarters = ["Q1","Q2"]`.
|
||
|
||
Если файл загружен **до** создания версии — не страшно: подтверждённые документы подхватываются при создании версии
|
||
(«подхват»), только переходы `DOCUMENTS_PROCESSING`/`DATA_CORRECTION` по загрузке тогда не увидеть.
|
||
|
||
### 7.2 Контрольные цифры сводной ОСВ (версия РБ 2024 Q3)
|
||
|
||
Значения ниже посчитаны вручную по файлам фикстур и совпадают со сквозным тестом `test_consolidation_fixtures`. Они
|
||
проверяют самое важное — **приведение квартальных ОСВ к 9 месяцам** (`05-domain-rules.md` §2): сальдо на начало берётся из
|
||
ОСВ за Q1, обороты складываются за Q1 + Q2 + Q3, сальдо на конец — из ОСВ за Q3.
|
||
|
||
`GET …/osv/summary`:
|
||
|
||
| Поле | Ожидание |
|
||
|---|---|
|
||
| `rows_total` | 1385 |
|
||
| `unmapped_total` | 1260 до ручного сопоставления — строки без ЮЛ **и** без ИНН из файла (отмена П-1, arm-core #74): строки с ИНН, которого нет в справочнике, сюда не входят; 4 строки Счастливочной с ИНН 3528052718 (`60.01`, `60.02`, `62.01`, `62.02`) сопоставлены автоматически |
|
||
| `documents_total` | 22 |
|
||
| `gaps` | ровно 7: РБ — `01`, `58.03`, `66`, `67`, `76`; Счастливочная — `01`, `02`; у всех `missing_quarters = ["Q1","Q2"]` |
|
||
|
||
Строки свода (`GET …/osv?legal_entity_id=…&q=…`), суммы в рублях:
|
||
|
||
| Компания, счёт, контрагент | Сальдо нач. Дт | Сальдо нач. Кт | Обороты Дт | Обороты Кт | Сальдо кон. Дт | Сальдо кон. Кт | Признак |
|
||
|---|---|---|---|---|---|---|---|
|
||
| Счастливочная, `60.01`, «Русский бисквит», ИНН 3528052718 | — | 4 454 151.93 | 128 058 143.65 | 125 950 450.77 | — | 2 346 459.05 | `GSK`, `match_kind = INN` |
|
||
| Счастливочная, `60.02`, «Русский бисквит» | 0 | 0 | 9 745 019.62 | 9 745 019.62 | 0 | 0 | `GSK`; контрагента нет в Q2 — квартал считается нулями |
|
||
| РБ, `60.01`, «Юг-Финанс / ПЕКАРНЯ СЧАСТЛИВОЧНАЯ», без ИНН | — | 38 806 758.00 | 307 986 284.00 | 375 490 642.46 | — | 106 311 116.46 | `EXTERNAL` до сопоставления, после K14 — `GSK` |
|
||
| Счастливочная, `62.01`, «Русский бисквит» | 40 211 938.00 | — | 398 018 012.46 | 330 517 504.00 | 107 712 446.46 | — | `GSK`; в каждом квартальном файле под `62.01` **две** строки с этим контрагентом — свод их суммирует, `entry_ids` = 6 |
|
||
| Счастливочная, `62.02`, «Русский бисквит» | — | 24 100 000.00 | 9 900 000.00 | 0 | — | 14 200 000.00 | `GSK` |
|
||
|
||
Как проверить любую строку самому: откройте три квартальных файла этого ЮЛ по этому счёту, найдите контрагента под тем же
|
||
субсчётом и сложите обороты по трём файлам; сальдо на начало сравните с первым файлом, на конец — с третьим. Тождество
|
||
«начало + обороты Кт − обороты Дт = конец» (для кредитового сальдо) должно сходиться: 4 454 151.93 + 125 950 450.77 −
|
||
128 058 143.65 = 2 346 459.05. Строка свода содержит `entry_ids` всех трёх исходных записей и `is_cumulative = false`;
|
||
`period = "2024 Q3"`.
|
||
|
||
Строка РБ по `60.01` зеркальна одной из двух позиций Счастливочной под `62.01` (в файлах: обороты 375 490 642.46 /
|
||
307 986 284.00, сальдо на конец 106 311 116.46) — внутригрупповые обороты сходятся копейка в копейку. В своде эта позиция
|
||
видна только в сумме с соседней (строка `62.01` выше); разнесение по позициям и сопоставление ВГО — Срез 5.
|
||
|
||
Что ещё проверить руками:
|
||
|
||
- контрагент, которого нет в одном из кварталов (как «Русский бисквит» на `60.02`): в своде одна строка, пропущенный квартал
|
||
дал нули, `entry_ids` — две записи;
|
||
- `GET …/osv?match_kind=INN` — только строки с ИНН в файле (у Счастливочной есть колонка ИНН, у РБ нет), `match_stored = true`;
|
||
- `GET …/osv?unmapped=true&legal_entity_id=$LE_RB&q=счастлив` — две строки (`60.01`, `62.…`) до сопоставления; после K14 —
|
||
ни одной, а `summary.unmapped_total` уменьшился ровно на число изменившихся строк свода (на этих данных — на 4, при
|
||
`propagated = 17` записей `osv_entry`: записи кварталов и пар-пробелов 66/76 считаются отдельно);
|
||
- пробелы не зависят от фильтров свода: `gaps` одинаковы при любом `GET …/osv/summary`.
|
||
|
||
### 7.3 Сверка экспорта с API и Excel
|
||
|
||
1. `GET …/osv/export` без фильтров → `svod.xlsx`; лист «Сводная ОСВ»: число строк без заголовка = `rows_total`, порядок — как
|
||
в API (ЮЛ по имени, счёт, номер строки). Суммы — числа с двумя знаками (не текст): выделите колонку «Обороты Дт» и
|
||
посмотрите сумму в строке состояния Excel; формат ячейки `#,##0.00`.
|
||
2. Найдите строку «Русский бисквит» `60.01` Счастливочной и сверьте шесть сумм с таблицей из 7.2 и с ответом `GET …/osv`.
|
||
3. Колонки «Сопоставлено с» и «Способ сопоставления»: для сопоставленных — имя ЮЛ из справочника и `INN`/`MANUAL`/`PROPAGATED`,
|
||
для несопоставленных — пусто.
|
||
4. Лист «Пробелы»: 7 строк, как в `summary.gaps`; с фильтром `?legal_entity_id=` в экспорте лист «Пробелы» всё равно полный.
|
||
5. Экспорт с `?unmapped=true` после полного сопоставления — лист «Сводная ОСВ» только с заголовком, лист «Пробелы» заполнен.
|
||
6. Текст контрагента, начинающийся с `=`, `+`, `-`, `@` (сделайте такой в своём файле), в Excel показывается как текст, а не
|
||
вычисляется как формула.
|
||
|
||
### 7.4 Произвольные ОСВ в консолидации другого периода (временный режим)
|
||
|
||
Документ привязывается к версии, только если период из **шапки ОСВ** подходит периоду консолидации: ОСВ за 1 кв. 2025,
|
||
загруженная в консолидацию 2026 Q1, после подтверждения **молча** не попадает ни в версию, ни в свод (OQ-161). Для тестов на
|
||
произвольных ОСВ есть временный режим `CONSOLIDATION_PERIOD_FROM_VERSION=true` (arm-core#103): в `api/.env` локально, в контуре —
|
||
`OPENBAO__APP__CONSOLIDATION_PERIOD_FROM_VERSION=true`; после смены — перезапуск ядра.
|
||
|
||
В режиме ОСВ, **загруженная в консолидацию** (через версию, а не с рабочего стола), период шапки которой консолидации **не
|
||
подходит**, считается нарастающей за год и квартал консолидации: подтверждение привязывает её, свод, чек-лист и колонка «ОСВ»
|
||
периметра её видят. ОСВ «своего» периода режим не трогает — квартальные склеиваются как обычно. Что учесть:
|
||
|
||
1. **Один файл «чужого» периода на счёт ЮЛ.** У подменённых ОСВ склейки кварталов нет: все они по одному счёту одного ЮЛ —
|
||
один слот, в своде будет подтверждённая последней. Грузите одну ОСВ на счёт (лучше нарастающую за весь период).
|
||
2. **Подтвердить всё нужное.** Документы `NEEDS_CHECK` (в т. ч. из-за чужого ЮЛ в шапке, `counterparty_check = MISMATCH`) в свод
|
||
не идут, пока их не подтвердили.
|
||
3. **Документы, подтверждённые до включения режима**, сами не перепривязываются: уберите ЮЛ из периметра и верните (подхват
|
||
`PUT …/members` под своей блокировкой версии) либо создайте консолидацию заново. Периметр — не меньше двух ЮЛ
|
||
(`PERIMETER_TOO_SMALL`), поэтому при двух участниках меняйте ЮЛ на временное третье той же ГК и обратно:
|
||
`{А, Б} → {Б, В} → {А, Б} → {А, В} → {А, Б}` (проверено на стенде 12.10.2026: РБ 2026 Q1 из ОСВ за 1 кв. 2025 — 16 документов
|
||
привязаны, свод 1421 строка, суммы равны своду РБ 2025 Q1 из тех же файлов).
|
||
4. Карточка документа по-прежнему показывает период из шапки — это не баг. ББ/ОФР режим не затрагивает.
|
||
|
||
## 8. Сквозной сценарий в UI (arm-web)
|
||
|
||
Основной путь аналитика по макету: группа компаний → новая консолидация → файлы ЮЛ → проверка ОСВ и форм ББ/ОФР →
|
||
сводная ОСВ. Номера в квадратных скобках — экраны макета `docs/mockup/screens/<nn>-*.png` (открывать по одному через
|
||
`docs/mockup/INDEX.md`): если не ясно, «так ли должно выглядеть», сравнивайте с ними при окне 1600×1000. Права, коды
|
||
ошибок и граничные случаи, которых нет в UI, — в разделах 6–7.
|
||
|
||
**Предусловия.** Стенд по разделу 4: ядро с `NATS_ENABLED=true`, tabular-parser на `:8001`, `make seed-dev` (ГК «Русский
|
||
Бисквит» с ГСК из РБ и Счастливочной), фронт — `make start`. Для 8.5 (PDF) — ещё шлюз и эмулятор ИС Сканирование (из
|
||
каталога `arm-core`, как в 4.2):
|
||
|
||
```bash
|
||
docker compose -f ../docs/dev/docker-compose.yml --profile services up -d --build --no-deps scan-gateway scan-emulator
|
||
```
|
||
|
||
Пользователь — `ka_go@armka.local`. Начинайте со свежего стенда (раздел 10): тогда цифры совпадут с 7.2 без поправок.
|
||
Подписи кнопок, колонок и сообщений ниже — дословно как в UI.
|
||
|
||
### 8.1 Навигация и группы компаний [03]
|
||
|
||
| № | Шаги | Ожидание |
|
||
|---|---|---|
|
||
| W1 | Войти `ka_go`, посмотреть боковое меню | логотип «АРМ КА», кнопка «+ Новая консолидация»; разделы «Отчётность» (Группы компаний, Монозаемщики), «Справочники» (ГК и организации, ЮЛ вне ГК), «Документы» (Единое хранилище), «Система» (Журнал действий, Пользователи и роли); кнопка «‹» сворачивает меню до иконок. Всё, кроме «Группы компаний» и «Пользователи и роли», — заглушки |
|
||
| W2 | «Группы компаний» | крошки «Отчётность › Группы компаний», подзаголовок «4 групп компаний» (после `make seed-dev`: Русский Бисквит, Унагранде, Новороссийский прокатный завод, Агрофирма Межениновская); колонки «Группа компаний», «CRM ID», «Основной РФ», «Организаций» |
|
||
| W3 | Поиск «бисквит»; затем фильтр в заголовке «Группа компаний» (сортировка, отметки, «OK») | остаётся «Русский Бисквит»; появляется «Сбросить фильтры»; «Настройка таблицы» скрывает и показывает колонки |
|
||
| W4 | В строке «Русский Бисквит» — кнопка «→ Консолидации» | страница группы [34]: «Русский Бисквит», «20 организаций», блок «Консолидации 0 шт.»; блок «Организации» пуст, пока версия не выбрана; «← Назад» возвращает к списку |
|
||
|
||
### 8.2 Новая консолидация [42]
|
||
|
||
| № | Шаги | Ожидание |
|
||
|---|---|---|
|
||
| W5 | «+ Новая консолидация» в меню | модалка «Новая консолидация»: «Группа компаний», «ГСК» (неактивна до выбора ГК), «Год» (текущий и три предыдущих), «Квартал»; «Создать» неактивна |
|
||
| W6 | ГК «Русский Бисквит», ГСК «ГСК Русский Бисквит», 2024, «3 квартал» | блок «ЮЛ консолидации»: все открытые ЮЛ ГК, РБ (3528052718) и Счастливочная (3528104476) отмечены и стоят первыми, счётчик «Выбрано: 2 / 20»; поиск «Поиск ЮЛ...» фильтрует список |
|
||
| W7 | «Создать» | уведомление «Создана консолидация 2024, 3 квартал»; открывается страница группы с выбранной версией (`?version=<id>` в адресе): строка «2024 · К3 · 1 · Инициация», «Дата создания» — сегодня по Москве, «ГСК» и «Дата верификации» — «—» (так в прототипе); в «Организации» — 2 строки, «Бух.баланс» и «ОФР» — «Отсутствует», «Ответственный: Dev: Аналитик ГО» |
|
||
| W8 | Повторить W5–W7 с тем же периодом | уведомление «Консолидация ГК за этот период уже создана» (409 `CONSOLIDATION_EXISTS`), модалка остаётся открытой |
|
||
| W9 | В модалке оставить отмеченным одно ЮЛ; снять все | одно ЮЛ — уведомление с текстом сервера (422 `PERIMETER_TOO_SMALL`, K2); ни одного — «Создать» неактивна |
|
||
|
||
### 8.3 Организации, чек-лист, загрузка файлов [34], [39], [40]
|
||
|
||
| № | Шаги | Ожидание |
|
||
|---|---|---|
|
||
| W10 | Кликнуть строку версии в «Консолидации» | строка подсвечена зелёным, адрес `?version=<id>`; блок «Организации»: колонки «Наименование», «Документы», «ОФР», «Бух.баланс», «ОСВ», «ИНН», «Тип»; меню «⋮» строки версии: «Добавить версию» → «Будет позже», «История изменений» → заглушка журнала |
|
||
| W11 | «Скачать чек-лист» | файл `checklist-Русский Бисквит-2024-Q3.csv`; содержимое — как в F17 (BOM, разделитель `;`, открывается в Excel) |
|
||
| W12 | Клик по строке «ПЕКАРНЯ СЧАСТЛИВОЧНАЯ, ООО» (или по кнопке «Документы») | справа панель «ПЕРВИЧНЫЕ ДОКУМЕНТЫ» [39]: имя ЮЛ, ИНН 3528104476, кнопка «Загрузить документы», «Документов нет»; крестик справа закрывает панель |
|
||
| W13 | «Загрузить документы» → «Выбрать документы» → пять файлов `../fixtures/osv/schastl/2024-Q3/*` → «Загрузить» | модалка «Загрузка документов» [40]: зелёный блок «Принято: 5» с именами, кнопка «Загрузить» сменилась на «Готово». В панели файлы сначала «Обработка», через 5–15 с у каждого документ: «ОСВ 60 · XLS · <дата загрузки>», период «2024 К3», проверка «Период —», «Контрагент ✓», статус «Загружен» (чистые ОСВ сразу, автоподтверждение, OQ-157; «Требуется проверка» — только с замечаниями, например шапка другой организации). Панель опрашивает сервер раз в 5 с, пока есть файлы в обработке |
|
||
| W14 | Загрузить ещё раз любой из этих файлов | красный блок «Отклонено: 1» со строкой «<имя> — файл «<имя>» уже загружен для этого ЮЛ» (`DUPLICATE_FILE`), уведомление «Ни один файл не принят» |
|
||
| W15 | Закрыть панель, обновить страницу (F5) | статус версии в таблице — «Обработка документов» (K8) |
|
||
|
||
### 8.4 Просмотр ОСВ-документа
|
||
|
||
| № | Шаги | Ожидание |
|
||
|---|---|---|
|
||
| W16 | В панели Счастливочной клик по «ОСВ 60» | экран «Просмотр ОСВ»: крошки «Отчётность › ПЕКАРНЯ СЧАСТЛИВОЧНАЯ, ООО › Проверка документа», подзаголовок «<имя файла> · XLS · <дата>», справа статус «Загружен», кнопка «✓ Подтвердить» неактивна; надпись «Суммы в рублях; строки ОСВ только для чтения»; дерево счёт → субсчёт → контрагент, колонки «Счёт / контрагент», «ИНН» и шесть сумм. Корневая строка `60`: «—», «27 923 002,09», «107 554 149,33», «99 188 100,32», «—», «19 556 953,08» (сальдо нач. Кт + обороты Кт − обороты Дт = сальдо кон. Кт) |
|
||
| W17 | ka_go, через API (в UI не воспроизводится: загрузка только из панели ЮЛ внутри версии, см. раздел 8.7). Загрузить один файл из `../fixtures/osv/schastl/2024-Q3/` в QA-ЮЛ `$LE_QA` (id ЮЛ, заведённого для QA, не входящего в ГСК демо; без консолидации — маршрутом ЮЛ): `curl -s -X POST "localhost:8000/api/v1/legal-entities/$LE_QA/files" -H "$H" -F "files=@<файл>"`; дождаться `PROCESSED` (`GET /files/{file_id}`); `POST /documents/{id}/confirm` | документ `NEEDS_CHECK` («Контрагент ✗», шапка другой организации) до подтверждения; после — 200 `CONFIRMED`, `confirmed_by` заполнен; повтор — 409 `ALREADY_CONFIRMED`. Файл остаётся на стенде вне версий демо: счётчики W18 не затрагивает; `DELETE` после ручного подтверждения даёт 409; в панели [39] версии этот файл виден только по переключателю «Все документы организации» с подписью «вне консолидации» |
|
||
| W18 | Остальные файлы РБ и Счастливочной 2024 Q1–Q3 (всего 22, список — 7.1): через «Загрузить документы» у каждого ЮЛ и подтверждение каждого ОСВ, либо скриптом из каталога `arm-web`: `scripts/demo-prepare.sh <version_id>` (id — из адреса `?version=`) | скрипт печатает строку на каждый файл (пять файлов из W13 — «уже загружен», это нормально), ждёт распознавания (до 5 мин), подтверждает только неподтверждённые ОСВ (чистые уже «Загружен», автоподтверждение, OQ-157) и в конце печатает `строк: 1385 документов: 22 несопоставленных: 1260 пробелов: 7`. Статус версии — «Коррекция данных» (K9) появляется сразу после распознавания (автоподтверждение чистых документов, OQ-157), в «Организации» в колонке «ОСВ» — счета ЮЛ |
|
||
|
||
`demo-prepare.sh` берёт пользователя вторым аргументом (по умолчанию `ka_go@armka.local`), адрес ядра — из `API`
|
||
(по умолчанию `localhost:8000/api/v1`), каталог фикстур — из `FIXTURES_DIR` (по умолчанию `fixtures` рядом с `arm-web`).
|
||
|
||
### 8.5 Проверка формы ББ/ОФР из PDF [41], [41-02]
|
||
|
||
PDF из `fixtures/ocr` — отчётность других компаний (`aep_6.pdf` — ООО «Дорстроймонтаж», на 30.06.2025), поэтому ячейки
|
||
ББ/ОФР чек-листа версии 2024 Q3 они не заполняют; для проверки экрана подойдёт любое ЮЛ версии. Распознаются только PDF,
|
||
для которых в эмуляторе есть ответ (раздел 6.8).
|
||
|
||
| № | Шаги | Ожидание |
|
||
|---|---|---|
|
||
| W19 | В панели Счастливочной загрузить `../fixtures/ocr/aep_6.pdf` | через 10–60 с два документа: «Бух. баланс · PDF» и «ОФР · PDF», статус «Требуется проверка», «Контрагент ✗» (в скане другая организация) |
|
||
| W20 | Открыть «Бух. баланс» | экран «Проверка распознавания»: слева «ИСХОДНЫЙ ДОКУМЕНТ» — страницы PDF «Страница 1…8», масштаб «−», «100%», «+» и сброс; справа «РЕЗУЛЬТАТ РАСПОЗНАВАНИЯ»: шапка «ИНН —», «Дата отчёта —» (оранжевый фон — не распознано), «Единица измерения тыс. руб.»; заголовок «Бухгалтерский баланс»; колонки «На 30 июня 2025 г», «На 31 декабря 2024 г.», «На 31 декабря 2023 г.»; разделы «АКТИВ», «I. Внеоборотные активы» … «ПАССИВ»; итоги разделов жирным; суммы без копеек, пустые — «-»; строка 1600 «БАЛАНС» — 293 709 / 179 791 / 189 847. Ячейки с уверенностью распознавания ниже 100 % — оранжевые, подсказка «Уверенность: N%» |
|
||
| W21 | «✎ Редактировать» → в строке 1600 первой колонки прибавить 100 → «✓ Сохранить» | уведомление «Изменения сохранены»; ячейка голубая, подсказка «Исходное: 293 709»; блок «Нарушения (N)» со строками «Колонка N: <соотношение> — ожидалось X, получено Y» (F3) |
|
||
| W22 | «✓ Подтвердить данные» | красный блок в правой части с текстом сервера и списком нарушений (409 `CHECKS_NOT_PASSED`, F4); документ не подтверждён |
|
||
| W23 | «Принять с комментарием» → в окне «Принять нарушения» сначала пустой комментарий, затем «округление в форме клиента» → «Принять» | с пустым комментарием «Принять» неактивна; после — синий блок «Нарушения приняты» с датой и комментарием (F5) |
|
||
| W24 | «✎ Редактировать», ввести в ячейку `abc` или `1.234` → «✓ Сохранить» | ячейка в красной рамке, подсказка «Неверный формат суммы»; уведомление «Исправьте суммы с неверным форматом», запрос не уходит. «Отменить» сбрасывает правки |
|
||
| W25 | Вернуть в 1600 исходную сумму, сохранить | «Проверки пройдены», принятие нарушений сброшено правкой (F6) |
|
||
| W26 | В шапке «Дата отчёта» ввести `2025-09-15` → «✓ Сохранить» → «✓ Подтвердить данные» | красный блок «<текст сервера>. Исправьте дату отчёта в шапке и сохраните.» (409 `PERIOD_REQUIRED`: дата не на конец квартала, F9). Дата вводится строго `ГГГГ-ММ-ДД` |
|
||
| W27 | Дата `2025-06-30` → «✓ Сохранить» → «✓ Подтвердить данные» | уведомление «Документ подтверждён», статус «Загружен», «Редактировать» и «Подтвердить данные» неактивны |
|
||
| W28 | Открыть «ОФР» того же файла [41-02] | заголовок «Отчёт о финансовых результатах», колонки периодов ОФР; остальное — как W20 |
|
||
| W28a | В «Бух. балансе» посмотреть строки 1600 и 1700 | строки выделены фиолетовым фоном и жирным шрифтом |
|
||
| W28b | «✎ Редактировать» на форме, где есть достроенные (в источнике отсутствующие) строки | пустые достроенные строки видны, редактируются и не подсвечены как ошибки; ввод суммы → «✓ Сохранить» создаёт строку, проверки пересчитываются |
|
||
|
||
Формы из Excel (`fixtures/statements-xlsx`, раздел 6.9) открываются тем же экраном, но слева вместо скана — «Превью
|
||
недоступно для этого формата» и кнопка «Скачать исходник». Документы «Прочий документ» (расшифровки) в панели не
|
||
открываются.
|
||
|
||
### 8.6 Сводная ОСВ [30]
|
||
|
||
Предусловие — W18: все 22 ОСВ загружены и подтверждены.
|
||
|
||
| № | Шаги | Ожидание |
|
||
|---|---|---|
|
||
| W29 | В строке версии «К отчётности →» | экран «Русский Бисквит — 2024 К3», крошки «Отчётность › Русский Бисквит › 2024 · К3», справа «Статус отчёта» «Коррекция данных»; активна вкладка «ОСВ»; «Шахматка», «Таблица корректировки», «Гудвилл», «КФО», «КФО Общий», «Подтверждение КФО клиента» неактивны (подсказка «Будет в следующих срезах») |
|
||
| W30 | Сводка | под таблицей «Строк: 1385» (= `rows_total`, 7.2); жёлтый баннер «Требуется указать соответствия … Осталось: 1260.» (= `unmapped_total`); свёрнутый блок «Пробелы: 7 — по этим счетам нет ОСВ за часть кварталов, их строк в своде нет», раскрыть — 7 строк «<ЮЛ> — счёт <код> — нет кварталов: Q1, Q2»: РБ 01, 58.03, 66, 67, 76; Счастливочная 01, 02 |
|
||
| W31 | Колонка «Период», суммы | у всех строк «9 мес. 2024 г.»; суммы с запятой и пробелами, ноль — «—»; по 500 строк на страницу |
|
||
| W32 | Фильтр «Компания ГК» (воронка в заголовке) = «ПЕКАРНЯ СЧАСТЛИВОЧНАЯ, ООО» → «Применить»; фильтр «Контрагенты» = `бисквит` → «Применить» | «Строк: 6» — `60.01`, `60.02`, `62.01`, `62.02`, `76.06`, `76.АВ`, контрагент «РУССКИЙ БИСКВИТ, АО», ИНН 3528052718. Суммы — как в 7.2, на экране с запятой: `60.01` — «—», «4 454 151,93», «128 058 143,65», «125 950 450,77», «—», «2 346 459,05»; `60.02` — «—» везде, кроме оборотов «9 745 019,62» / «9 745 019,62»; `62.01` — «40 211 938,00», «—», «398 018 012,46», «330 517 504,00», «107 712 446,46», «—»; `62.02` — «—», «24 100 000,00», «9 900 000,00», «—», «—», «14 200 000,00»; в последней колонке «Тип контрагента» у всех шести — синий бейдж «ГСК». Окно фильтра закрывается после «Применить», иконка активного фильтра зелёная |
|
||
| W32a | Сбросить фильтры; прокрутить таблицу вправо; фильтр на колонке «Тип контрагента» по очереди «ГСК», «ГК», «Внешний» → «Применить» | у каждой строки бейдж: «ГСК» — синий, «ГК» — зелёный, «Внешний» — серый (как в макете [30]); «Строк» при «ГСК» + «ГК» + «Внешний» = 1385, «Строк» при «Внешний» = «Осталось» в баннере. «Сбросить» в окне фильтра возвращает все строки и закрывает окно |
|
||
| W33 | Сбросить фильтры; «Счёт» = `62` | «Строк: 387»; `60.01` — 0 строк (фильтр принимает только корень счёта, ограничение прототипа) |
|
||
| W34 | «Показать ЮЛ без ИНН» | только строки без связи с ЮЛ и без ИНН, «Строк» = «Осталось»; в ячейке ИНН — только красное «Указать» на подсвеченном фоне (макет [32-osv-no-inn]); воронка колонки «ИНН» зелёная; кнопка сменилась на «Показать все строки» |
|
||
| W34a | Ячейка ИНН без фильтров (arm-web #17) | строка с ИНН в файле — просто ИНН моноширинным шрифтом, «Указать» нет (даже если такой организации нет в справочнике); строка, связанная без ИНН в файле (например, РБ → «Юг-Финанс / ПЕКАРНЯ СЧАСТЛИВОЧНАЯ» на данных 2025 К1), — серая «таблетка» с ИНН организации справочника; клик по ней открывает «Выбор организации для связи с …», у текущей организации — неактивное «Связано», у остальных — «Связать» |
|
||
| W35 | «Экспорт» (с фильтрами и без) | файл `svod-osv_Русский Бисквит_2024-Q3_v1.xlsx` по текущим фильтрам; содержимое — по 7.3 |
|
||
|
||
Если на стенде раньше делали ручное сопоставление контрагентов (K14), «Осталось» меньше 1260: алиасы переживают версии.
|
||
Остальные цифры от этого не меняются.
|
||
|
||
### 8.7 Что в UI не считать багом (прототип)
|
||
|
||
- Нет «Взять в работу», смены периметра, ручных переходов статусов, сопоставления контрагентов, экрана чек-листа,
|
||
комментариев, удаления файлов, «Добавить версию» — это проверяется через API (разделы 6.6, 6.9, 7).
|
||
- В своде не показываются «документов: 22» и колонка «Способ сопоставления» (её нет и в макете [30]; значение — в
|
||
экспорте); фильтр «Тип контрагента» — одно значение, а не галочки, как в макете (API принимает один признак); фильтр
|
||
«Счёт» принимает только корень (`60`, не `60.01`).
|
||
- Таблица консолидаций и статус версии обновляются по F5: после создания консолидации на странице той же ГК новой строки
|
||
может не быть до обновления. Панель «Организации» перечитывается при закрытии панели документов.
|
||
- «Распознан» (старые `RECOGNIZED` и формы после правки) и «Требуется проверка» в панели документов выглядят одинаково — «Требуется проверка».
|
||
- «ГСК» и «Дата верификации» в таблице консолидаций, «Утверждён в РФ» и «Верифицирован в ГО» — всегда «—».
|
||
- Панель документов показывает первые 100 файлов рабочего стола консоли (или ЮЛ — по переключателю); загрузка — только из панели ЮЛ внутри версии «в работе».
|
||
- Шапку формы (ИНН, дату, единицу) нельзя очистить, только заменить; время принятия нарушений — в часовом поясе
|
||
браузера, остальные даты — по Москве.
|
||
- Ссылка `?version=` с несуществующим id «выбирает» пустую версию; при 404 на своде может прийти до трёх сообщений;
|
||
после истечения токена кроме «Сессия истекла» бывают лишние уведомления.
|
||
- Предупреждения antd в консоли браузера («Static function can not consume context…», deprecated) ни на что не влияют.
|
||
- Уведомления в `/admin` не появляются — дефект arm-web#7 (раздел 6.1).
|
||
|
||
Баг UI — issue в `RSHB/arm-web` (раздел 11): номер сценария W, скриншот, вкладка Network с запросом и ответом.
|
||
|
||
## 9. Известные ограничения (не заводить как баги)
|
||
|
||
- Справочники — только через API; в UI arm-web — администрирование и путь консолидации (раздел 8, ограничения прототипа — 8.7).
|
||
- Вход без пароля, открытый `/docs`, CORS `*` — dev-режим.
|
||
- Email `*.local` через API/UI отклоняется, при этом dev-пользователи именно такие (созданы seed напрямую).
|
||
- Удалять алиасы и пустые ГСК/ГСЗ может только ADMIN (право `DIRECTORY_DELETE`).
|
||
- Граница «дата перевода не раньше сегодня» считается по UTC: до 03:00 МСК «сегодня» ещё вчера. Будет исправлено.
|
||
- Повторный `make seed-dev` возвращает права, снятые администратором в матрице.
|
||
- Профиль в шапке не перечитывается после правки собственных ролей.
|
||
- В консоли браузера сообщение `Store does not have a valid reducer` и предупреждения antd — каркас, не влияют.
|
||
- Таблица пользователей показывает первые 200.
|
||
- При импорте ИНН, записанный в Excel числом с ведущим нулём, теряет ноль и даёт `INVALID_INN`.
|
||
- Консолидация (Срез 4): блокировка, периметр, статусы и сопоставление — только API (раздел 8.7); маршрут согласования (`CONSOLIDATION_RF` и далее), «Добавить версию», гудвилл, шахматка — следующие
|
||
срезы; формы ББ/ОФР в сводной ОСВ не участвуют (только в чек-листе); блокировка версии не истекает по времени; `period_check` документов остаётся `UNKNOWN`;
|
||
`PROCESSING` в чек-листе ставится по всем недостающим столбцам ЮЛ, пока файл не смаплен (OQ-119, OQ-120, OQ-122).
|
||
- ОСВ, период которой (из шапки) не подходит периоду консолидации, после подтверждения молча не попадает в свод (OQ-161);
|
||
для тестов на произвольных ОСВ — временный режим из раздела 7.4.
|
||
- Ручное сопоставление контрагента меняет строку ОСВ для всех версий, куда входит документ (OQ-123); рекомендация кандидата — только
|
||
при точном совпадении нормализованного имени (OQ-127).
|
||
- PDF-путь (Срез 3a): эмулятор отвечает только на PDF из `fixtures/ocr`, остальные —
|
||
`INVALID_DOCUMENT` (раздел 6.8, OQ-151); редакция `fsbu4` не заполнена (OQ-150).
|
||
|
||
- Парсер ОСВ: границы и «не баги» — в конце раздела 6.7 (счёт 91 — OQ-132; скользящий период — OQ-131).
|
||
- Удаление алиаса одновременно с его переносом ручным сопоставлением даёт 404 вместо 204 (arm-core #26) — так задумано.
|
||
- Код `60.АВ` (синтетика s09) и буквенные забалансовые вида `РВ.1` в план счетов не резолвятся (arm-core #28, C23-1/C23-2).
|
||
|
||
Полный список отложенных находок — Gitea: `RSHB/arm-core` issues «Follow-ups from #5», «Follow-ups from #18» (#21), #28;
|
||
`RSHB/arm-tabular-parser` #18.
|
||
|
||
## 10. Сброс стенда
|
||
|
||
```bash
|
||
docker compose -f ../docs/dev/docker-compose.yml down -v # БД и бакет с нуля
|
||
make up && docker compose -f ../docs/dev/docker-compose.yml up -d --wait postgres nats minio
|
||
make migrate && make seed-dev
|
||
```
|
||
|
||
Бэкенд и фронт перезапускать не нужно, но сессии в браузере лучше начать заново.
|
||
|
||
Файлы, загруженные в 6.6, удаляются из БД командой `DELETE /files/{id}`, но объекты в MinIO остаются (им ставится только
|
||
`expires_at`) — `down -v` стирает и их.
|
||
|
||
## 11. Как заводить баги
|
||
|
||
Gitea: бэкенд и API — `RSHB/arm-core`, экраны — `RSHB/arm-web`, документация — `RSHB/arm-ka-docs`. Один дефект — одна issue.
|
||
Не уверены, чей баг, — откройте вкладку Network: ответ сервера не тот, что в гайде, — arm-core; ответ верный, а экран
|
||
показывает другое — arm-web.
|
||
|
||
Шаблон:
|
||
|
||
```
|
||
Срез: 1 / Сценарий: D16 (номер из гайда, если есть)
|
||
Пользователь: ka_go@armka.local (роль KA_GO)
|
||
Шаги: 1. … 2. …
|
||
Ожидание: 409 MEMBER_OF_OTHER_GROUP
|
||
Факт: 500 (тело ответа ниже)
|
||
Запрос: метод, путь, тело (без токена)
|
||
Ответ: статус и тело
|
||
Лог бэкенда: строки с трейсбэком, если есть
|
||
Окружение: ОС, браузер, версии docker/node, коммиты arm-core и arm-web (`git -C arm-core rev-parse --short HEAD`)
|
||
Скриншот: для багов UI — экран и вкладка Network с запросом
|
||
```
|
||
|
||
Вопросы по ожидаемому поведению (не ясно, баг ли) — тоже issue, с пометкой «вопрос» в заголовке.
|
||
|
||
## 12. Типичные проблемы при запуске
|
||
|
||
| Симптом | Причина и решение |
|
||
|---|---|
|
||
| `make up` → порт занят | локальный PostgreSQL/MinIO; остановить или сменить `*_PORT` в `docs/dev/.env` и те же порты в `arm-core/api/.env` |
|
||
| `docker compose up --wait` без списка сервисов возвращает 1 | нормально из-за `minio-init`; используйте команду со списком из 4.1 |
|
||
| `make migrate` → connection refused | PostgreSQL ещё не `healthy`; подождать и повторить |
|
||
| `make venv` не находит Python 3.11 | `uv` скачает его сам при наличии интернета; проверьте `uv python list` |
|
||
| Белый экран на `localhost:3000` | фронт запущен `npm run start` без `REACT_APP_ROOT_PATH=/`; запускайте `make start` (4.3) |
|
||
| После входа «Нет связи с сервером» или ошибки сети | ядро не запущено на `:8000`; если на другом адресе — `ARM_CORE_URL=… make start`. Для старого `arm-core/web` — забыты `REACT_APP_API_URL`/`REACT_APP_ROOT_PATH` |
|
||
| Страница «Вход выполняется через РАИСА» | это production-сборка; запускайте `make start`, а не `make build` |
|
||
| `make start`: порт 3000 занят | запущен второй фронт (например, старый `arm-core/web`); остановите его |
|
||
| Скан PDF не отображается («Не удалось открыть PDF») | старый браузер — обновите его |
|
||
| PDF завис в «Обработка» | не запущены `scan-gateway`/`scan-emulator` (раздел 8, предусловия) |
|
||
| PDF в статусе «Ошибка» (подсказка `INVALID_DOCUMENT`) | эмулятор знает только PDF из `fixtures/ocr` с JSON-ответом (6.8); свой PDF так и должен падать |
|
||
| Импорт падает на записи файла в S3 | MinIO не поднят или не создан бакет: `docker compose logs minio-init` |
|
||
| `/healthcheck.subjects` ядра и сервиса различаются (или файл при `nats: ok` стоит в `UPLOADED`, а через ~2 мин становится `ERROR` — ack не пришёл) | у сервисов разные `OPENBAO__NATS__NATS_*_SUBJECT`; выровняйте настройки у всех трёх |
|
||
| `/healthcheck` показывает `nats: down` | NATS не поднят; на сценарии Среза 1 не влияет, но загрузка ОСВ (6.6) без NATS не дойдёт до парсера |
|
||
| Файл завис в `UPLOADED`/`PROCESSING` | не запущен tabular-parser (`curl localhost:8001/healthcheck`) или ядро запущено без `NATS_ENABLED=true`; без ack файл через ~2 мин станет `ERROR` |
|
||
|
||
Подробнее об инфраструктуре — `docs/dev/README.md`, о бэкенде — `arm-core/README.md`, о фронте — `arm-web/README.md`.
|