- 07-open-questions: OQ-117 — индексы osv_entry(parent_entry_id),
osv_entry(recognition_job_id), stored_file(current_recognition_job_id); без них
DELETE /files/{id} делает seq scan на проверке self-FK (I-1 final review, R-24);
нужны DBML v5.16 и миграция 0004, блокер до прода / до подсреза 2d.
- 09-delivery-plan: итог make check-full приведён к фактическому прогону
(e2e skipped в check-full, отдельный e2e 1 passed, fixtures 13 + 1 skipped);
решения без обсуждения №1–52 (T12.6).
- qa-guide U16: до ERROR файл остаётся UPLOADED (ack нет) (T12.7).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
369 lines
32 KiB
Markdown
369 lines
32 KiB
Markdown
# Гайд тестировщика: локальный стенд АРМ КА (Срезы 0–2c)
|
||
|
||
Документ для ручного тестирования текущего `main`. Путь проверен 05.10.2026 на macOS (Docker 28, compose 2.34,
|
||
Node 24, uv 0.10): от пустого каталога до экрана администратора — около 15 минут, из них 10 — скачивание образов.
|
||
|
||
## 1. Что сейчас есть и чего нет
|
||
|
||
**Есть (Срез 0 + Срез 1 + Срез 2c):**
|
||
|
||
- Бэкенд `arm-core` (FastAPI, `/api/v1`): вход по email в dev-режиме, профиль пользователя, права как данные.
|
||
- API справочников: группы компаний (ГК), юрлица и ИП (ЮЛ) с историей, переводом между ГК и закрытием, алиасы
|
||
контрагентов, группы связанных компаний и заёмщиков (ГСК/ГСЗ) с составом, филиалы. Видимость по филиалу: головной
|
||
офис видит всё, филиал — только свои ГК.
|
||
- Импорт справочников из выгрузки ЕЦП CRM (xlsx/csv) с пробным прогоном и отчётом.
|
||
- Администрирование: пользователи, роли, матрица «роль × право», защита последнего администратора.
|
||
- Фронтенд: страница `/admin` («Пользователи и роли»). Другого UI нет.
|
||
- Журнал аудита в БД (`dkb.audit_event`).
|
||
- Загрузка файлов ОСВ через API (`POST /legal-entities/{id}/files`), распознавание реальным `tabular-parser`, разбор
|
||
результата в документы и строки ОСВ, статусы файла и документа, автозамена версий, подтверждение документа, удаление
|
||
файла (раздел 6.6). Всё — через Swagger/curl, UI «Первичные документы» — подсрез 2d.
|
||
|
||
**Нет (это не баги):**
|
||
|
||
- Экранов справочников ГК/ЮЛ/ГСК — проверяются только через Swagger (`/docs`) или curl.
|
||
- Экрана загрузки и таблицы ОСВ (подсрез 2d), ББ/ОФР, распознавания PDF, консолидации, КФО — следующие срезы.
|
||
- Настоящего входа через 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 (бэкенд), 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).
|
||
|
||
## 3. Клонирование
|
||
|
||
Три репозитория должны лежать **рядом**, в одном каталоге, под именами `docs`, `arm-core`, `fixtures` (в коде пути
|
||
вида `../docs/...`):
|
||
|
||
```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
|
||
```
|
||
|
||
Без 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-пользователей
|
||
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**; поднимайте
|
||
только его, без второго экземпляра ядра (он забирает часть сообщений `recognition.result`):
|
||
|
||
```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 Фронтенд (второй терминал)
|
||
|
||
```bash
|
||
cd arm-core/web
|
||
npm install
|
||
REACT_APP_API_URL=http://localhost:8000 REACT_APP_ROOT_PATH=/ npm run start
|
||
```
|
||
|
||
Откроется <http://localhost:3000/> с формой «Вход для разработки». Две переменные окружения обязательны: без них
|
||
фронт ищет API на своём порту и путь приложения как на платформе банка.
|
||
|
||
Не используйте `npm run build` для тестирования: в production-сборке dev-формы входа нет, страница показывает
|
||
«Вход выполняется через РАИСА».
|
||
|
||
Настройки из `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`.
|
||
|
||
### 4.4 Вход
|
||
|
||
В форме введите **email** dev-пользователя (не имя и не логин) и нажмите «Войти (dev)». Пароля нет. Это единственная форма
|
||
входа АРМ: страница с полями «Имя пользователя / пароль» — это Gitea, а не стенд. Токен живёт в текущей вкладке
|
||
(`sessionStorage`), новая вкладка — новый вход. Токен действует 8 часов.
|
||
|
||
| 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
|
||
|
||
```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`)
|
||
|
||
| № | Шаги | Ожидание |
|
||
|---|---|---|
|
||
| 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) |
|
||
| 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 |
|
||
|
||
### 6.3 Импорт из ЕЦП CRM (пользователь `admin`, curl или Swagger)
|
||
|
||
Файл: `fixtures/directories/ecp-export-russkiy-biskvit.xlsx` (21 строка). Выполняйте на свежем стенде или после
|
||
сброса (раздел 8), иначе счётчики будут другими.
|
||
|
||
| № | Шаги | Ожидание |
|
||
|---|---|---|
|
||
| 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 |
|
||
|
||
### 6.6 Загрузка ОСВ и просмотр строк через API (Срез 2c)
|
||
|
||
Предусловия: раздел 4.2 (ядро с `NATS_ENABLED=true`, tabular-parser на `:8001`), файлы из `../fixtures/osv/`. Выполняйте на
|
||
свежем стенде (раздел 8) или с другим ЮЛ: дедуп и автозамена зависят от уже загруженного. Ожидаемое время от загрузки до
|
||
`PROCESSED` — до 15 секунд (опрос публикатора и цикла маппинга по 5 с, парсер — доли секунды).
|
||
|
||
Подготовка: пользователь `ka_go` (права `DOCUMENT_EDIT`, `DOCUMENT_VIEW`, видит все ЮЛ). ЮЛ должно называться как в шапке ОСВ —
|
||
`АО "Русский бисквит"`, ИНН `3528052718` (если вы делали импорт I2 — найдите его в `GET /directories/legal-entities?q=бисквит`
|
||
и используйте существующее, иначе создайте как в D9). Для проверки «чужого филиала» понадобится ГК с филиалом.
|
||
|
||
```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 -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 = RECOGNIZED`, `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` | 200, `status = CONFIRMED`, `confirmed_by`/`confirmed_at` заполнены; повторный вызов — 409 `ALREADY_CONFIRMED` |
|
||
| U9 | ka_go | `DELETE /files/{file_id}` файла с подтверждённым документом | 409 `FILE_HAS_CONFIRMED_DOCUMENTS`, файл и строки на месте |
|
||
| 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 |
|
||
|
||
Аудит (6.4 — запрос из psql): после U1 — `FILE_UPLOAD`; U8 — `DOCUMENT_CONFIRM`; U10 — `FILE_REPLACE` с пустым `user_id`
|
||
(системное событие) и `description`, где перечислены документы заменённого файла; U12 — `FILE_DELETE` с карточкой файла
|
||
в `before_value`. Смены статусов обмена (`PROCESSING`, `PROCESSED`, `ERROR`) аудитом не фиксируются — так задумано.
|
||
|
||
## 7. Известные ограничения (не заводить как баги)
|
||
|
||
- UI только для администрирования; справочники — через API.
|
||
- Вход без пароля, открытый `/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` — каркас, не влияет.
|
||
- Таблица пользователей показывает первые 200.
|
||
- При импорте ИНН, записанный в Excel числом с ведущим нулём, теряет ноль и даёт `INVALID_INN`.
|
||
|
||
Полный список отложенных находок — Gitea `RSHB/arm-core`, issue «Follow-ups from #5».
|
||
|
||
## 8. Сброс стенда
|
||
|
||
```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` стирает и их.
|
||
|
||
## 9. Как заводить баги
|
||
|
||
Gitea, репозиторий `RSHB/arm-core` (для документации — `RSHB/arm-ka-docs`). Один дефект — одна issue.
|
||
|
||
Шаблон:
|
||
|
||
```
|
||
Срез: 1 / Сценарий: D16 (номер из гайда, если есть)
|
||
Пользователь: ka_go@armka.local (роль KA_GO)
|
||
Шаги: 1. … 2. …
|
||
Ожидание: 409 MEMBER_OF_OTHER_GROUP
|
||
Факт: 500 (тело ответа ниже)
|
||
Запрос: метод, путь, тело (без токена)
|
||
Ответ: статус и тело
|
||
Лог бэкенда: строки с трейсбэком, если есть
|
||
Окружение: ОС, версии docker/node, коммит arm-core (`git rev-parse --short HEAD`)
|
||
```
|
||
|
||
Вопросы по ожидаемому поведению (не ясно, баг ли) — тоже issue, с пометкой «вопрос» в заголовке.
|
||
|
||
## 10. Типичные проблемы при запуске
|
||
|
||
| Симптом | Причина и решение |
|
||
|---|---|
|
||
| `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` |
|
||
| Фронт открылся, но после входа пусто или ошибки сети | забыты `REACT_APP_API_URL`/`REACT_APP_ROOT_PATH` при `npm run start` |
|
||
| Страница «Вход выполняется через РАИСА» | это production-сборка; запускайте `npm run start`, а не `build` |
|
||
| Импорт падает на записи файла в S3 | MinIO не поднят или не создан бакет: `docker compose logs minio-init` |
|
||
| `/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`.
|