Фронт в arm-core/web: документация, без sync-web #144

Merged
aep merged 4 commits from feat/143-layout-frontend-in-core into main 2026-10-21 09:30:59 +03:00
16 changed files with 144 additions and 584 deletions

View File

@ -8,7 +8,7 @@ CHANGE ?=
# Текущая версия модели данных — по ней рисуются диаграммы (make diagrams) # Текущая версия модели данных — по ней рисуются диаграммы (make diagrams)
DBML ?= model/data-model-v5.19.dbml DBML ?= model/data-model-v5.19.dbml
.PHONY: venv validate-contracts validate-openspec test-snapshot test-sync-web test-sync-statements sync-web sync-statements statements-check check diagrams .PHONY: venv validate-contracts validate-openspec test-snapshot test-sync-statements sync-statements statements-check check diagrams
# Зависимости скриптов проверки контракта — jsonschema и PyYAML (перечни кодов форм contracts/statement-code-orders.yaml) # Зависимости скриптов проверки контракта — jsonschema и PyYAML (перечни кодов форм contracts/statement-code-orders.yaml)
venv: venv:
@ -27,18 +27,10 @@ validate-openspec:
test-snapshot: test-snapshot:
STATEMENTS_PY=$(PY) scripts/test_prepare_contour_snapshot.sh STATEMENTS_PY=$(PY) scripts/test_prepare_contour_snapshot.sh
# sync-web.sh на синтетических репозиториях: точка синхронизации, перенос набора, отказ при расхождении, пайплайн переноса
test-sync-web:
scripts/test_sync_web.sh
# sync-statements.sh на синтетических репозиториях: помощник проверок, копирование пакета, отказы # sync-statements.sh на синтетических репозиториях: помощник проверок, копирование пакета, отказы
test-sync-statements: test-sync-statements:
STATEMENTS_PY=$(PY) scripts/test_sync_statements.sh STATEMENTS_PY=$(PY) scripts/test_sync_statements.sh
# Синхронизация arm-core/web с arm-web@test одной командой (worktree, issue, PR); SYNC_ARGS="--check" или "--dry-run"
sync-web:
scripts/sync-web.sh $(SYNC_ARGS)
# Копия пакета arm_statements в хост одной командой (worktree, issue, PR); SYNC_ARGS="--host arm-core [--check|--dry-run]" # Копия пакета arm_statements в хост одной командой (worktree, issue, PR); SYNC_ARGS="--host arm-core [--check|--dry-run]"
sync-statements: sync-statements:
STATEMENTS_PY=$(PY) scripts/sync-statements.sh $(SYNC_ARGS) STATEMENTS_PY=$(PY) scripts/sync-statements.sh $(SYNC_ARGS)
@ -50,7 +42,7 @@ statements-check:
STATEMENTS_PY=$(PY) scripts/sync-statements.sh --check --host $$h || { c=$$?; [ $$c -eq 10 ] || rc=1; }; \ STATEMENTS_PY=$(PY) scripts/sync-statements.sh --check --host $$h || { c=$$?; [ $$c -eq 10 ] || rc=1; }; \
done; exit $$rc done; exit $$rc
check: validate-contracts validate-openspec test-snapshot test-sync-web test-sync-statements check: validate-contracts validate-openspec test-snapshot test-sync-statements
# Диаграммы model/diagrams/part01…09.{svg,png} из $(DBML); нужен npx (Node.js), зависимостей Python нет # Диаграммы model/diagrams/part01…09.{svg,png} из $(DBML); нужен npx (Node.js), зависимостей Python нет
diagrams: diagrams:

View File

@ -12,16 +12,24 @@
├── docs/ → git: arm-ka-docs контекст, модель, контракты, экспорт ТЗ, макет ├── docs/ → git: arm-ka-docs контекст, модель, контракты, экспорт ТЗ, макет
├── fixtures/ → git: arm-ka-fixtures реальные данные (ОСВ, PDF, OCR-пары, эталон); отдельный репо, ├── fixtures/ → git: arm-ka-fixtures реальные данные (ОСВ, PDF, OCR-пары, эталон); отдельный репо,
│ чтобы не тащить 50 МБ в сервисы и управлять доступом │ чтобы не тащить 50 МБ в сервисы и управлять доступом
├── arm-core/ → git: arm-core ядро: FastAPI + React (монорепо по шаблону команды) ├── arm-core/ → git: arm-core ядро: FastAPI + React (монорепо по шаблону команды); фронт — `web/`
├── tabular-parser/ → git: arm-tabular-parser сервис разбора XLS/XLSX/XLSB (ОСВ, ББ, ОФР) ├── tabular-parser/ → git: arm-tabular-parser сервис разбора XLS/XLSX/XLSB (ОСВ, ББ, ОФР)
├── scan-gateway/ → git: arm-scan-gateway адаптер ИС Сканирование (PDF/PNG/JPG) ├── scan-gateway/ → git: arm-scan-gateway адаптер ИС Сканирование (PDF/PNG/JPG)
├── arm-statements/ → git: arm-statements пакет общего кода нормализации ББ/ОФР `arm_statements` (копируется в сервисы и ядро; план 1 change unified-statement-normalization) ├── arm-statements/ → git: arm-statements пакет общего кода нормализации ББ/ОФР `arm_statements` (копируется в сервисы и ядро; план 1 change unified-statement-normalization)
├── arm-web/ → git: arm-web прежний отдельный репозиторий фронта, заморожен с 21.10.2026 (см. ниже)
└── .claude/ ← настройки workspace: хуки, разрешения (не в git или в arm-ka-docs/.claude-workspace) └── .claude/ ← настройки workspace: хуки, разрешения (не в git или в arm-ka-docs/.claude-workspace)
``` ```
Контракт между сервисами — `docs/contracts/*.schema.json` в `arm-ka-docs`. Изменение контракта = PR в docs **и** в Контракт между сервисами — `docs/contracts/*.schema.json` в `arm-ka-docs`. Изменение контракта = PR в docs **и** в
каждый затронутый сервис, с одной `schema_version`. каждый затронутый сервис, с одной `schema_version`.
**Фронт ведётся в `arm-core/web`** (решение владельца 21.10.2026): фронтовые задачи — worktree arm-core и PR в
`RSHB/arm-core` → `main`, туда же идут PR фронта от других исполнителей. Репозиторий `RSHB/arm-web` (ветка `test`)
заморожен на `de4e477` — последней синхронизации (arm-core #147, `3157a15`); новых правок туда не вносим. Скрипт
синхронизации `sync-web.sh` удалён, `scripts/prepare-contour-snapshot.sh` берёт `web/` из arm-core как есть
(`dev/contour-transfer-guide.md` раздел 3.1). Спеки экранов (`*-ui`) и открытые issue arm-web
перенесены в arm-core (`openspec/specs/*-ui`, issue #150–#157); дубль `openspec/specs/arm-web` из docs удалён.
## Как агент работает с несколькими репозиториями ## Как агент работает с несколькими репозиториями
- **Один change — один репозиторий.** OpenSpec (`openspec/`) живёт в каждом сервисе. Сквозное изменение (контракт + - **Один change — один репозиторий.** OpenSpec (`openspec/`) живёт в каждом сервисе. Сквозное изменение (контракт +

View File

@ -74,11 +74,11 @@ docker compose -f ../docs/dev/docker-compose.yml --project-name arm-ka run --rm
- **Маркер версии:** `api/arm_statements/VERSION` — полный хэш коммита `arm-statements`, из которого разложена копия; - **Маркер версии:** `api/arm_statements/VERSION` — полный хэш коммита `arm-statements`, из которого разложена копия;
пишет только скрипт. Сервис при старте читает его и пишет в результат `service.statements_version`; ядро сравнивает его пишет только скрипт. Сервис при старте читает его и пишет в результат `service.statements_version`; ядро сравнивает его
с `statements_version` задания при пересчёте после правки (`STATEMENTS_VERSION_MISMATCH`, `info`). с `statements_version` задания при пересчёте после правки (`STATEMENTS_VERSION_MISMATCH`, `info`).
- **Скрипт:** `docs/scripts/sync-statements.sh` (по образцу `sync-web.sh`), запуск из workspace или `make -C docs - **Скрипт:** `docs/scripts/sync-statements.sh`, запуск из workspace или `make -C docs
sync-statements SYNC_ARGS=…`. Источник — локальный checkout `arm-statements` рядом (`../arm-statements`) или sync-statements SYNC_ARGS=…`. Источник — локальный checkout `arm-statements` рядом (`../arm-statements`) или
`--source <dir>`, ссылка — `origin/main` или `--ref <ref>`; хост — `--host arm-core|tabular-parser|scan-gateway` или `--source <dir>`, ссылка — `origin/main` или `--ref <ref>`; хост — `--host arm-core|tabular-parser|scan-gateway` или
`--host-dir <dir>`. Без `--check` — раскладывает копию нужного коммита в новый worktree хоста на ветке фичи и пишет `--host-dir <dir>`. Без `--check` — раскладывает копию нужного коммита в новый worktree хоста на ветке фичи и пишет
`VERSION`; issue и PR — как у `sync-web.sh`; `--dry-run` — то же без issue, проверок, коммита и PR. С `--in-place` — `VERSION`, заводит issue и PR в хосте; `--dry-run` — то же без issue, проверок, коммита и PR. С `--in-place` —
раскладывает копию в рабочее дерево существующего worktree хоста без git-операций (подключение хоста; PR фичи, раскладывает копию в рабочее дерево существующего worktree хоста без git-операций (подключение хоста; PR фичи,
которой нужна новая версия пакета; при прямой правке копии или недостающей зависимости — отказ `20`, ничего не которой нужна новая версия пакета; при прямой правке копии или недостающей зависимости — отказ `20`, ничего не
пишется). Обзор по трём хостам рядом — `make -C docs statements-check`; сборка снимка для контура пишется). Обзор по трём хостам рядом — `make -C docs statements-check`; сборка снимка для контура
@ -112,7 +112,8 @@ docker compose -f ../docs/dev/docker-compose.yml --project-name arm-ka run --rm
поэтому отдельным шагом в `check-full` не гоняется. Для быстрой проверки одних миграций: `make migration-cycle`. поэтому отдельным шагом в `check-full` не гоняется. Для быстрой проверки одних миграций: `make migration-cycle`.
- `check-infra` — TCP-проверка доступности PostgreSQL и NATS из `api/.env.test`; вместо длинного трейсбека pytest - `check-infra` — TCP-проверка доступности PostgreSQL и NATS из `api/.env.test`; вместо длинного трейсбека pytest
подсказывает «поднимите инфраструктуру: `make up`». подсказывает «поднимите инфраструктуру: `make up`».
- `web-check` — `npm install`, `biome check src`, `tsc --noEmit`, `npm run build` в `web/`. - `web-check` — `npm install`, `biome check src`, `tsc --noEmit`, `npm run build` в `web/`. Юнит-тесты и линтер фронта
в `web-check` не входят — их запускают из `web/`: `npm test` (vitest) и `npm run lint` (ESLint + Prettier).
Дополнительные цели репозиториев (не общие): `arm-core` — `migrate`, `seed`, `seed-dev`, `dbml-check`, Дополнительные цели репозиториев (не общие): `arm-core` — `migrate`, `seed`, `seed-dev`, `dbml-check`,
`migration-cycle`, `check-infra`, `docker-build`, `web-check`; `scan-gateway` — `help`, `docker-build`, `emulator`; `migration-cycle`, `check-infra`, `docker-build`, `web-check`; `scan-gateway` — `help`, `docker-build`, `emulator`;
@ -180,7 +181,7 @@ PR убедитесь, что `docs` и `fixtures` доступны (пропу
| `make venv` | `.venv` (Python 3.11, `uv`) с `jsonschema` и `PyYAML` — зависимостями скриптов (после обновления репозитория — перезапустить) | | `make venv` | `.venv` (Python 3.11, `uv`) с `jsonschema` и `PyYAML` — зависимостями скриптов (после обновления репозитория — перезапустить) |
| `make validate-contracts` | `python scripts/validate_contracts.py`: примеры `contracts/examples/` против текущих схем JSON Schema 2020-12, примеры `contracts/examples/v1/` — против схем прежнего MAJOR `*.v1.schema.json` (окно перехода на контракт 2.0), форма упорядоченных перечней кодов `contracts/statement-code-orders.yaml` (коды — 4 цифры формы; в варианте `full` код стоит 1 + (вхождений в `repeats`) раз, в `simplified` — не чаще); код 1 при любой ошибке | | `make validate-contracts` | `python scripts/validate_contracts.py`: примеры `contracts/examples/` против текущих схем JSON Schema 2020-12, примеры `contracts/examples/v1/` — против схем прежнего MAJOR `*.v1.schema.json` (окно перехода на контракт 2.0), форма упорядоченных перечней кодов `contracts/statement-code-orders.yaml` (коды — 4 цифры формы; в варианте `full` код стоит 1 + (вхождений в `repeats`) раз, в `simplified` — не чаще); код 1 при любой ошибке |
| `make validate-openspec` | `openspec validate --all --strict` — все спеки и незаархивированные change'и (один change: `make validate-openspec CHANGE=<id>`) | | `make validate-openspec` | `openspec validate --all --strict` — все спеки и незаархивированные change'и (один change: `make validate-openspec CHANGE=<id>`) |
| `make check` | все проверки выше и `test-snapshot`, `test-sync-web`, `test-sync-statements` | | `make check` | все проверки выше и `test-snapshot`, `test-sync-statements` |
| `make test-sync-statements` | сценарии `sync-statements.sh` на синтетических репозиториях: коды `--check`, `--in-place`, `--dry-run`, отказ при прямой правке | | `make test-sync-statements` | сценарии `sync-statements.sh` на синтетических репозиториях: коды `--check`, `--in-place`, `--dry-run`, отказ при прямой правке |
| `make sync-statements` | копия пакета `arm_statements` в хост одной командой (worktree, issue, PR); `SYNC_ARGS="--host arm-core [--check\|--dry-run]"` | | `make sync-statements` | копия пакета `arm_statements` в хост одной командой (worktree, issue, PR); `SYNC_ARGS="--host arm-core [--check\|--dry-run]"` |
| `make statements-check` | обзор свежести копий по трём хостам рядом (`arm-core`, `tabular-parser`, `scan-gateway`): строка итога на хост; в `check` не входит | | `make statements-check` | обзор свежести копий по трём хостам рядом (`arm-core`, `tabular-parser`, `scan-gateway`): строка итога на хост; в `check` не входит |

View File

@ -36,8 +36,8 @@ Gitea в контуре нет)
- `docs` и `fixtures` рядом с сервисом — тесты идентичности контракта и тесты на реальных данных пропускаются - `docs` и `fixtures` рядом с сервисом — тесты идентичности контракта и тесты на реальных данных пропускаются
(раздел 5), сверка ORM с DBML (`make dbml-check` в arm-core) не выполняется. (раздел 5), сверка ORM с DBML (`make dbml-check` в arm-core) не выполняется.
- `tea`, `codegraph`, `openspec` (CLI), хуки `.claude/hooks` — инструментарий процесса; его конфигурация - `tea`, `codegraph`, `openspec` (CLI), хуки `.claude/hooks` — инструментарий процесса; его конфигурация
(`.claude/`, `.codegraph/`, `CLAUDE.md`, `openspec/config.yaml`) вырезается на любой глубине — фронт в `arm-core/web/` (`.claude/`, `.codegraph/`, `CLAUDE.md`, `openspec/config.yaml`) вырезается на любой глубине, в том числе во вложенном
синхронизируется из arm-web вместе со своими копиями этих файлов, удалять их вручную не нужно. `arm-core/web/`; удалять их вручную не нужно.
- Внешний Gitea `git.aepif.ru` — ссылки вида `https://git.aepif.ru/RSHB/arm-core/pulls/19` в архивах change'ей - Внешний Gitea `git.aepif.ru` — ссылки вида `https://git.aepif.ru/RSHB/arm-core/pulls/19` в архивах change'ей
скрипт превращает в `RSHB/arm-core#19` (номера относятся к внешнему Gitea, в контуре их нет). скрипт превращает в `RSHB/arm-core#19` (номера относятся к внешнему Gitea, в контуре их нет).
@ -64,14 +64,7 @@ docs/scripts/prepare-contour-snapshot.sh scan-gateway /tmp/contour
Что делает скрипт (репозиторий-источник не меняется): Что делает скрипт (репозиторий-источник не меняется):
0. Только arm-core: проверяет, не отстаёт ли встроенный фронт `web/` от `RSHB/arm-web@test` (`sync-web.sh --check`, 0. Для каждого сервиса, где в `<ref>` есть `api/arm_statements/`: проверяет свежесть копии пакета (`sync-statements.sh --check`
раздел 3.1). Если отстаёт — спрашивает «перенести как есть? [y/N]»: `y` — снимок делается, отставание записывается в
`SNAPSHOT.md`; `n` (или пустой ответ) — запускается синхронизация (worktree, issue, PR в arm-core) и перенос
останавливается с кодом 3 до merge PR и закрытия issue («закрывай»), затем снимок повторяется. Без терминала вопрос не
задаётся: нужен флаг `--web-as-is` первым аргументом, иначе код 3. Если `web/` правили напрямую в arm-core мимо arm-web
(точки синхронизации нет) — снимок не делается, разбирать вручную. Если `arm-web` рядом не найден — предупреждение,
снимок делается.
0a. Для каждого сервиса, где в `<ref>` есть `api/arm_statements/`: проверяет свежесть копии пакета (`sync-statements.sh --check`
по дереву `<ref>`). Отстаёт — предупреждение и абзац в `SNAPSHOT.md`; копия правлена напрямую или чужая — снимок не по дереву `<ref>`). Отстаёт — предупреждение и абзац в `SNAPSHOT.md`; копия правлена напрямую или чужая — снимок не
делается (код 1); нет `arm-statements` рядом или подходящего Python — предупреждение, снимок делается. Нет делается (код 1); нет `arm-statements` рядом или подходящего Python — предупреждение, снимок делается. Нет
`api/arm_statements/` — проверка молча пропускается. `api/arm_statements/` — проверка молча пропускается.
@ -92,33 +85,15 @@ docs/scripts/prepare-contour-snapshot.sh scan-gateway /tmp/contour
`.env.test`, `contracts/`). Любая проблема — код возврата 1 и список. `.env.test`, `contracts/`). Любая проблема — код возврата 1 и список.
Тест скрипта — `make test-snapshot` (синтетический репозиторий с артефактами агента в корне и в `web/`); проверка Тест скрипта — `make test-snapshot` (синтетический репозиторий с артефактами агента в корне и в `web/`); проверка
отставания `web/` и остановка переноса — в `make test-sync-web`, а проверка копии `arm_statements` (пропуск, отставание, правка) копии `arm_statements` (пропуск, отставание, правка)
— в `make test-snapshot` (нужен Python ≥ 3.11 с PyYAML, `make venv`). — в `make test-snapshot` (нужен Python ≥ 3.11 с PyYAML, `make venv`).
### 3.1. Синхронизация `arm-core/web` с `arm-web`: `scripts/sync-web.sh` ### 3.1. Фронт arm-core: `web/` ведётся в arm-core
Фронт ведётся в двух местах: отдельный репозиторий `RSHB/arm-web` (ветка `test`, прототип и демо-стенд) и встроенный С 21.10.2026 фронт разрабатывается прямо в `arm-core/web` (PR в `RSHB/arm-core` → `main`), отдельный репозиторий
`arm-core/web`, из которого собирается контур. Правила: `arm-core/web` напрямую не редактируется — все правки фронта идут в `RSHB/arm-web` заморожен на `test` `de4e477` (= arm-core `3157a15`), скрипт синхронизации `sync-web.sh` удалён
arm-web и переносятся скриптом; состояние синхронизации не хранится — точкой считается коммит arm-web, у которого дерево (`docs/context/00-workspace-layout.md`). Снимок arm-core берёт `web/` как есть из `<ref>`; флаг `--web-as-is`,
`src` совпадает с `web/src` в arm-core main (сравнение по хэшу дерева git). нужный раньше для переноса с отставанием от arm-web, игнорируется с предупреждением.
```bash
make -C docs sync-web SYNC_ARGS=--check # 0 — синхронно; 10 — arm-core/web отстаёт (список коммитов); 20 — расхождение
make -C docs sync-web SYNC_ARGS=--dry-run # worktree .wt/web-sync-<sha7>/arm-core с перенесённым набором, без issue/PR
make -C docs sync-web # полный цикл: worktree, issue и PR в RSHB/arm-core, проверки, коммит, push
```
Полный цикл делает то же, что делалось вручную в arm-core #94 и #100: worktree по абсолютному пути на ветке
`feat/<issue>-web-sync-<sha7>` с `.bounded`; перенос набора синхронизации — всё дерево arm-web, кроме служебного
(`CLAUDE.md`, `.claude/`, `openspec/`, `docs/`, `scripts/`, `Makefile`, `ARCHITECTURE.md`, `.gitignore`, `package-lock.json`;
`README.md` входит в набор — решение владельца 10.10.2026); контроль `diff -rq` с деревом arm-web; проверки `vitest`,
`tsc --noEmit`, `biome check src` (предупреждений не больше, чем на main), `rsbuild build`; коммит и PR с диапазоном
коммитов arm-web и рулингами R-1/R-2. Любая красная проверка останавливает скрипт до коммита, worktree остаётся для разбора.
`node_modules` для проверок берётся из `WEB_NODE_MODULES` (по умолчанию `.wt/web-proto/arm-web/node_modules` — стенд демо),
иначе `npm install` в worktree. Скрипт ничего не мержит и не удаляет: ревью перенесённого кода против бэкенда, merge и
чеклист «закрывай» — за исполнителем. Код 20 (расхождение) означает прямые правки `web/` в arm-core: их нужно перенести в
arm-web или откатить, иначе автоперенос их затёр бы.
Проверено 06.10.2026: снимки arm-core@1d7f2c8, tabular-parser@3b32462, scan-gateway@dffc18a проходят `make check` без Проверено 06.10.2026: снимки arm-core@1d7f2c8, tabular-parser@3b32462, scan-gateway@dffc18a проходят `make check` без
`docs` и `fixtures` (1084 / 1087 / 726 passed, пропуски только по причинам из раздела 5). `docs` и `fixtures` (1084 / 1087 / 726 passed, пропуски только по причинам из раздела 5).
@ -149,8 +124,8 @@ arm-web #29), поэтому снимок оставляет только нуж
`WEB_DEV_ALLOW` и повторите снимок. `WEB_DEV_ALLOW` и повторите снимок.
**Правило обратного потока:** `web/package.json` из контура наружу не переносить — он урезан (нет eslint, prettier, vitest, **Правило обратного потока:** `web/package.json` из контура наружу не переносить — он урезан (нет eslint, prettier, vitest,
typescript и скриптов `test`/`lint`/`format`); источник истины — `arm-web`, а `arm-core/web` синхронизируется из него typescript и скриптов `test`/`lint`/`format`); источник истины — `web/package.json` в arm-core `main` снаружи (раздел 3.1).
(раздел 3.1). Правки зависимостей в контуре возвращаются в arm-web вручную, только по существу, а не файлом целиком. Правки зависимостей в контуре возвращаются в arm-core вручную, только по существу, а не файлом целиком.
## 4. Как положить снимок в контур ## 4. Как положить снимок в контур
@ -280,9 +255,7 @@ git diff <коммит sync> HEAD -- . ':!SNAPSHOT.md' > /tmp/contour-arm-core.p
1. Снаружи: нужные PR слиты в `main` трёх сервисов; `make check-full` зелёный в каждом; контракт в `docs/contracts` и копии 1. Снаружи: нужные PR слиты в `main` трёх сервисов; `make check-full` зелёный в каждом; контракт в `docs/contracts` и копии
`api/contracts` идентичны. `api/contracts` идентичны.
2. Правки из контура (раздел 7) возвращены наружу и слиты. 2. Правки из контура (раздел 7) возвращены наружу и слиты.
3. `prepare-contour-snapshot.sh` по каждому сервису; скрипт завершился словами «снимок готов». Для arm-core скрипт сам 3. `prepare-contour-snapshot.sh` по каждому сервису; скрипт завершился словами «снимок готов».
проверяет отставание `web/` от arm-web (раздел 3.1): при остановке с кодом 3 — дождаться merge PR синхронизации и
«закрывай», затем повторить.
4. В контуре: клон, ветка, `rsync --delete`, просмотр `git status`, коммит `sync: <repo>@<hash> (<дата>)`, `make venv`, 4. В контуре: клон, ветка, `rsync --delete`, просмотр `git status`, коммит `sync: <repo>@<hash> (<дата>)`, `make venv`,
`make check` (пропуски — только из раздела 5), для arm-core `make web-check`, merge request по регламенту GitLab контура. `make check` (пропуски — только из раздела 5), для arm-core `make web-check`, merge request по регламенту GitLab контура.
5. Порядок мержа при смене версии контракта: arm-core → tabular-parser → scan-gateway. 5. Порядок мержа при смене версии контракта: arm-core → tabular-parser → scan-gateway.

View File

@ -416,8 +416,8 @@ UI прячет элементы по `GET /me → permissions` (объедин
### 5.6.1 Формы ББ/ОФР после единой нормализации (arm-core `main` `35a24e7a`) ### 5.6.1 Формы ББ/ОФР после единой нормализации (arm-core `main` `35a24e7a`)
> Работает на `main` ядра: change `unified-statement-normalization` (RSHB/arm-ka-docs#116; контракт распознавания v2.0, > Работает на `main` ядра: change `unified-statement-normalization` (RSHB/arm-ka-docs#116; контракт распознавания v2.0,
> DBML v5.19), части А (arm-core #129) и Б (#132). Встроенный фронт `arm-core/web` получает новые типы через `sync-web` > DBML v5.19), части А (arm-core #129) и Б (#132). Фронт `arm-core/web` эти типы уже использует (план 6, сделан в
> после плана 6 (arm-web). Правила — `context/05-domain-rules.md` §7, §9.3, §9.5 пп. 2, 4, 9, §11; экран — спека `arm-web` того же change. > arm-web и перенесён синхронизацией до 21.10.2026; дальше фронт ведётся в arm-core). Правила — `context/05-domain-rules.md` §7, §9.3, §9.5 пп. 2, 4, 9, §11; экран — спеки `document-review-ui` и `primary-files-ui` в `arm-core/openspec/specs/`.
> В `…/statement` поля добавлены, старые не удалены: клиент, написанный по §5.6, продолжит читать ответ. > В `…/statement` поля добавлены, старые не удалены: клиент, написанный по §5.6, продолжит читать ответ.
> **Исключение — карточка файла:** `FileOut.job.warnings` сменил тип со строк на объекты (ниже, «Карточка файла»), это > **Исключение — карточка файла:** `FileOut.job.warnings` сменил тип со строк на объекты (ниже, «Карточка файла»), это
> несовместимо — экран файлов обновляется в той же выкладке, что и ядро. Раздел 5.6 ниже описывает базовые маршруты; где он > несовместимо — экран файлов обновляется в той же выкладке, что и ядро. Раздел 5.6 ниже описывает базовые маршруты; где он
@ -756,7 +756,7 @@ async function download(path: string, fallbackName: string): Promise<void> {
| Задача | Что поменяется для фронта | Как подготовиться | | Задача | Что поменяется для фронта | Как подготовиться |
|---|---|---| |---|---|---|
| OQ-54 — SSO РАИСА | Меняется только получение токена (вместо dev-формы) | держать слой получения токена изолированным (`frontend-auth-guide.md` §9) | | OQ-54 — SSO РАИСА | Меняется только получение токена (вместо dev-формы) | держать слой получения токена изолированным (`frontend-auth-guide.md` §9) |
| arm-ka-docs#116 — единая нормализация ББ/ОФР | Выполнено в `arm-core` `main` (#129, #132) — см. §1 и §5.6.1; ждёт `arm-web` (план 6) и выкладки сервисов tabular-parser / scan-gateway на контракт 2.0 | типы из §5.6.1 | | arm-ka-docs#116 — единая нормализация ББ/ОФР | Выполнено в `arm-core` `main` (#129, #132) — см. §1 и §5.6.1; фронт (план 6) — в `arm-core/web`; выкладка сервисов на контракт 2.0 — `qa-statements-v2.md` | типы из §5.6.1 |
Состояние этих задач — в Gitea (`RSHB/arm-core`, `RSHB/arm-ka-docs`); после слияния каждой этот раздел переезжает в §1. Состояние этих задач — в Gitea (`RSHB/arm-core`, `RSHB/arm-ka-docs`); после слияния каждой этот раздел переезжает в §1.

View File

@ -1,8 +1,9 @@
# Гайд тестировщика: локальный стенд АРМ КА (Срезы 0–2c, 3b, 4 и фронт arm-web) # Гайд тестировщика: локальный стенд АРМ КА (Срезы 0–2c, 3b, 4 и фронт)
Документ для ручного тестирования текущего `main` бэкенда и ветки `test` фронта `arm-web`. Путь проверен 05.10.2026 на Документ для ручного тестирования текущего `main` репозитория `arm-core` — бэкенда и встроенного фронта `web/` (с
macOS (Docker 28, compose 2.34, Node 24, uv 0.10): от пустого каталога до экрана администратора — около 15 минут, из них 21.10.2026 фронт ведётся там; прежний отдельный репозиторий `arm-web` заморожен и для тестирования не нужен). Путь
10 — скачивание образов. Сценарий в UI (раздел 8) проверен 08.10.2026 в Chrome 1600×1000 на данных РБ 2024 Q3. проверен 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 — то же и Как читать: раздел 4 — поднять стенд, раздел 8 — пройти основной путь аналитика в браузере, разделы 6–7 — то же и
глубже через API (Swagger/curl): там проверяются права, коды ошибок и граничные случаи, до которых UI не доходит. глубже через API (Swagger/curl): там проверяются права, коды ошибок и граничные случаи, до которых UI не доходит.
@ -21,7 +22,7 @@ macOS (Docker 28, compose 2.34, Node 24, uv 0.10): от пустого ката
офис видит всё, филиал — только свои ГК. офис видит всё, филиал — только свои ГК.
- Импорт справочников из выгрузки ЕЦП CRM (xlsx/csv) с пробным прогоном и отчётом. - Импорт справочников из выгрузки ЕЦП CRM (xlsx/csv) с пробным прогоном и отчётом.
- Администрирование: пользователи, роли, матрица «роль × право», защита последнего администратора. - Администрирование: пользователи, роли, матрица «роль × право», защита последнего администратора.
- Фронтенд **arm-web** (демо-прототип, раздел 8): группы компаний, консолидации и создание консолидации, панель - Фронтенд `arm-core/web` (раздел 8): группы компаний, консолидации и создание консолидации, панель
«Организации» с чек-листом CSV, загрузка файлов ЮЛ с опросом статуса, просмотр ОСВ-документа с подтверждением, проверка «Организации» с чек-листом CSV, загрузка файлов ЮЛ с опросом статуса, просмотр ОСВ-документа с подтверждением, проверка
форм ББ/ОФР рядом со сканом PDF (правка, акцепт нарушений, подтверждение), сводная ОСВ с фильтрами, пробелами и форм ББ/ОФР рядом со сканом PDF (правка, акцепт нарушений, подтверждение), сводная ОСВ с фильтрами, пробелами и
экспортом xlsx; администрирование `/admin` («Пользователи и роли»). экспортом xlsx; администрирование `/admin` («Пользователи и роли»).
@ -33,7 +34,7 @@ macOS (Docker 28, compose 2.34, Node 24, uv 0.10): от пустого ката
`aep_3.pdf` / `aep_6.pdf` из `fixtures/ocr`. `aep_3.pdf` / `aep_6.pdf` из `fixtures/ocr`.
- Консолидация через API (Срез 4): версии за период, периметр, блокировка «взять в работу», статусы, привязка подтверждённых - Консолидация через API (Срез 4): версии за период, периметр, блокировка «взять в работу», статусы, привязка подтверждённых
документов, чек-лист, сводная ОСВ, ручное сопоставление контрагента, экспорт xlsx (раздел 7; полный прогон на данных РБ и документов, чек-лист, сводная ОСВ, ручное сопоставление контрагента, экспорт xlsx (раздел 7; полный прогон на данных РБ и
контрольные цифры — 7.1–7.3). Основной путь консолидации есть и в UI arm-web — раздел 8. контрольные цифры — 7.1–7.3). Основной путь консолидации есть и в UI — раздел 8.
- План счетов — полная выгрузка 1С заказчика, 488 строк (arm-core #25): субсчета реальных ОСВ (66.01, 76.07.x, забалансовые, - План счетов — полная выгрузка 1С заказчика, 488 строк (arm-core #25): субсчета реальных ОСВ (66.01, 76.07.x, забалансовые,
буквенные `01.К`) резолвятся в `account_id`. Границы входов консолидации: периметр ≤ 500 ЮЛ, комментарий статуса ≤ 1000, поиск буквенные `01.К`) резолвятся в `account_id`. Границы входов консолидации: периметр ≤ 500 ЮЛ, комментарий статуса ≤ 1000, поиск
`q` ≤ 500 символов (arm-core #26, раздел 6.5). `q` ≤ 500 символов (arm-core #26, раздел 6.5).
@ -77,7 +78,7 @@ macOS (Docker 28, compose 2.34, Node 24, uv 0.10): от пустого ката
## 3. Клонирование ## 3. Клонирование
Репозитории должны лежать **рядом**, в одном каталоге, под именами `docs`, `arm-core`, `fixtures`, `arm-web` (в коде пути Репозитории должны лежать **рядом**, в одном каталоге, под именами `docs`, `arm-core`, `fixtures` (в коде пути
вида `../docs/...`, `../fixtures/...`): вида `../docs/...`, `../fixtures/...`):
```bash ```bash
@ -85,11 +86,9 @@ 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-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-core.git arm-core
git clone ssh://git@git.aepif.ru:2222/RSHB/arm-ka-fixtures.git fixtures 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` там — исходная копия Фронт — каталог `web/` в `arm-core`, отдельно его клонировать не нужно. Обновление: `git -C arm-core pull`.
без экранов консолидации). Обновление: `git -C arm-web pull`.
Сервисы распознавания собираются из исходников, поэтому для загрузки файлов (6.6–7, раздел 8) рядом нужен ещё Сервисы распознавания собираются из исходников, поэтому для загрузки файлов (6.6–7, раздел 8) рядом нужен ещё
`tabular-parser`, для PDF (6.8, 8.5) — `scan-gateway`: `tabular-parser`, для PDF (6.8, 8.5) — `scan-gateway`:
@ -141,28 +140,24 @@ docker compose -f ../docs/dev/docker-compose.yml --profile services up -d --buil
curl -s localhost:8001/healthcheck # nats: ok curl -s localhost:8001/healthcheck # nats: ok
``` ```
### 4.3 Фронтенд arm-web (второй терминал, из корня `arm-ka`) ### 4.3 Фронтенд (второй терминал, из корня `arm-ka`)
```bash ```bash
cd arm-web cd arm-core/web
make install # npm install; первый раз — несколько минут npm install # первый раз — несколько минут
make start # dev-сервер rsbuild на :3000, браузер откроется сам REACT_APP_ROOT_PATH=/ npm run start # dev-сервер rsbuild на :3000, браузер откроется сам
``` ```
Откроется <http://localhost:3000/> с формой «Вход для разработки». Запросы `/api` dev-сервер проксирует на ядро Откроется <http://localhost:3000/> с формой «Вход для разработки». Запросы `/api` dev-сервер проксирует на ядро
(`ARM_CORE_URL`, по умолчанию `http://localhost:8000`), поэтому `REACT_APP_API_URL` задавать не нужно, а CORS не мешает (`ARM_CORE_URL`, по умолчанию `http://localhost:8000`), поэтому `REACT_APP_API_URL` задавать не нужно, а CORS не мешает
скачиванию файлов. Ядро на другом адресе: `ARM_CORE_URL=http://host:port make start`. скачиванию файлов. Ядро на другом адресе: `ARM_CORE_URL=http://host:port REACT_APP_ROOT_PATH=/ npm run start`.
Запускайте именно `make start`: он задаёт `REACT_APP_ROOT_PATH=/`. Голый `npm run start` ждёт путь приложения как на Не забывайте `REACT_APP_ROOT_PATH=/`: без него `npm run start` ждёт путь приложения как на
платформе банка (`/aurora/apps/fastapi-dkb-armka`) и показывает белый экран. Не используйте и `make build`/`npm run build` платформе банка (`/aurora/apps/fastapi-dkb-armka`) и показывает белый экран. Не используйте и `npm run build`
для тестирования: в production-сборке dev-формы входа нет, страница показывает «Вход выполняется через РАИСА». для тестирования: в production-сборке dev-формы входа нет, страница показывает «Вход выполняется через РАИСА».
Проверка сборки фронта (линтер, типы, юнит-тесты, сборка) — `make check` в `arm-web`; ручному тестированию она не нужна. Проверка сборки фронта (линтер, типы, юнит-тесты, сборка) — `make web-check` в `arm-core` и `npm test`, `npm run lint` в
`arm-core/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`) Настройки из `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`. для локального стенда менять не нужно: `DEBUG=true` включает документацию API на `/docs`, dev-вход и разрешает `make seed-dev`.
@ -225,10 +220,10 @@ curl -s localhost:8000/api/v1/me -H "Authorization: Bearer $TOKEN"
### 6.1 Администрирование (UI `/admin`, вход `admin@armka.local`) ### 6.1 Администрирование (UI `/admin`, вход `admin@armka.local`)
Пункт меню «Система → Пользователи и роли». **Известный дефект arm-web#7:** в arm-web на этом экране не появляются Пункт меню «Система → Пользователи и роли». **Известный дефект arm-web#7:** на этом экране не появляются
уведомления (ни об успехе, ни об ошибке), в консоли браузера — `message.error is not a function`, модалка создания при ошибке уведомления (ни об успехе, ни об ошибке), в консоли браузера — `message.error is not a function`, модалка создания при ошибке
остаётся открытой без текста. Пока он не исправлен, сценарии с ожиданием «уведомлением» (A3, A6, A8, A10) проходите на старом остаётся открытой без текста. Пока он не исправлен, в сценариях с ожиданием «уведомлением» (A3, A6, A8, A10) проверяйте код ответа во
фронте `arm-core/web` (раздел 4.3) или проверяйте код ответа во вкладке Network. вкладке Network.
| № | Шаги | Ожидание | | № | Шаги | Ожидание |
|---|---|---| |---|---|---|
@ -242,7 +237,7 @@ curl -s localhost:8000/api/v1/me -H "Authorization: Bearer $TOKEN"
| A8 | Деактивировать единственного активного админа | 409 `LAST_ADMIN` | | A8 | Деактивировать единственного активного админа | 409 `LAST_ADMIN` |
| A9 | Матрица прав: роли EXPERT добавить `EXPORT`, сохранить | `/me` эксперта содержит `EXPORT` | | A9 | Матрица прав: роли EXPERT добавить `EXPORT`, сохранить | `/me` эксперта содержит `EXPORT` |
| A10 | Матрица прав: снять `USERS_ADMIN` у ADMIN, когда других админов нет | 409 `LAST_ADMIN` | | A10 | Матрица прав: снять `USERS_ADMIN` у ADMIN, когда других админов нет | 409 `LAST_ADMIN` |
| A11 | Войти `expert@armka.local`, открыть `/admin` напрямую | «Нет доступа» / «Раздел доступен пользователям с правом администрирования», запросов к `/admin/*` нет (вкладка Network). Пункт меню в arm-web виден всем ролям — так в прототипе, в `arm-core/web` он скрыт | | A11 | Войти `expert@armka.local`, открыть `/admin` напрямую | «Нет доступа» / «Раздел доступен пользователям с правом администрирования», запросов к `/admin/*` нет (вкладка Network). Пункт меню виден всем ролям — известное ограничение |
| A12 | После правки своих ролей обновить страницу | профиль в шапке обновился только после перезагрузки (известное ограничение) | | A12 | После правки своих ролей обновить страницу | профиль в шапке обновился только после перезагрузки (известное ограничение) |
### 6.2 Справочники (Swagger `/docs`) ### 6.2 Справочники (Swagger `/docs`)
@ -605,7 +600,7 @@ curl -s "localhost:8000/api/v1/documents/$DOC/statement" -H "$H" | python3 -m js
(раздел 6.6); данные — в `../fixtures/osv/`. Токены: `ka_go` (`CONSOLIDATION_VIEW/EDIT`, видит всё), `ka_rf` (раздел 6.6); данные — в `../fixtures/osv/`. Токены: `ka_go` (`CONSOLIDATION_VIEW/EDIT`, видит всё), `ka_rf`
(`CONSOLIDATION_VIEW/EDIT`, только ГК Ростовского РФ), `expert` (только `CONSOLIDATION_VIEW`), `client_manager` (`CONSOLIDATION_VIEW/EDIT`, только ГК Ростовского РФ), `expert` (только `CONSOLIDATION_VIEW`), `client_manager`
(`CONSOLIDATION_VIEW`, Краснодарский РФ), `admin` (прав на консолидации нет). Подробные схемы запросов и ответов, коды ошибок и (`CONSOLIDATION_VIEW`, Краснодарский РФ), `admin` (прав на консолидации нет). Подробные схемы запросов и ответов, коды ошибок и
нюансы для фронта — `dev/api-contract-slice-4.md`; Swagger — `/docs`. В UI arm-web (раздел 8) есть K1 (W5–W8), загрузка в нюансы для фронта — `dev/api-contract-slice-4.md`; Swagger — `/docs`. В UI (раздел 8) есть K1 (W5–W8), загрузка в
версию (K8, W13), чек-лист CSV (W11), свод, пробелы и экспорт (K11, K12 частично, K17 — W29–W35); блокировки, периметра, версию (K8, W13), чек-лист CSV (W11), свод, пробелы и экспорт (K11, K12 частично, K17 — W29–W35); блокировки, периметра,
статусов и сопоставления (K4–K7, K13–K16, K18–K20) в UI нет. статусов и сопоставления (K4–K7, K13–K16, K18–K20) в UI нет.
@ -776,7 +771,7 @@ Q3-only — `missing_quarters = ["Q1","Q2"]`.
привязаны, свод 1421 строка, суммы равны своду РБ 2025 Q1 из тех же файлов). привязаны, свод 1421 строка, суммы равны своду РБ 2025 Q1 из тех же файлов).
4. Карточка документа по-прежнему показывает период из шапки — это не баг. ББ/ОФР режим не затрагивает. 4. Карточка документа по-прежнему показывает период из шапки — это не баг. ББ/ОФР режим не затрагивает.
## 8. Сквозной сценарий в UI (arm-web) ## 8. Сквозной сценарий в UI
Основной путь аналитика по макету: группа компаний → новая консолидация → файлы ЮЛ → проверка ОСВ и форм ББ/ОФР → Основной путь аналитика по макету: группа компаний → новая консолидация → файлы ЮЛ → проверка ОСВ и форм ББ/ОФР →
сводная ОСВ. Номера в квадратных скобках — экраны макета `docs/mockup/screens/<nn>-*.png` (открывать по одному через сводная ОСВ. Номера в квадратных скобках — экраны макета `docs/mockup/screens/<nn>-*.png` (открывать по одному через
@ -784,7 +779,7 @@ Q3-only — `missing_quarters = ["Q1","Q2"]`.
ошибок и граничные случаи, которых нет в UI, — в разделах 6–7. ошибок и граничные случаи, которых нет в UI, — в разделах 6–7.
**Предусловия.** Стенд по разделу 4: ядро с `NATS_ENABLED=true`, tabular-parser на `:8001`, `make seed-dev` (ГК «Русский **Предусловия.** Стенд по разделу 4: ядро с `NATS_ENABLED=true`, tabular-parser на `:8001`, `make seed-dev` (ГК «Русский
Бисквит» с ГСК из РБ и Счастливочной), фронт — `make start`. Для 8.5 (PDF) — ещё шлюз и эмулятор ИС Сканирование (из Бисквит» с ГСК из РБ и Счастливочной), фронт — раздел 4.3. Для 8.5 (PDF) — ещё шлюз и эмулятор ИС Сканирование (из
каталога `arm-core`, как в 4.2): каталога `arm-core`, как в 4.2):
```bash ```bash
@ -828,12 +823,14 @@ docker compose -f ../docs/dev/docker-compose.yml --profile services up -d --buil
| № | Шаги | Ожидание | | № | Шаги | Ожидание |
|---|---|---| |---|---|---|
| W16 | В панели Счастливочной клик по «ОСВ 60» | экран «Просмотр ОСВ»: крошки «Отчётность › ПЕКАРНЯ СЧАСТЛИВОЧНАЯ, ООО › Проверка документа», подзаголовок «<имя файла> · XLS · <дата>», справа статус «Загружен», кнопка «✓ Подтвердить» неактивна; надпись «Суммы в рублях; строки ОСВ только для чтения»; поиск «Контрагент или ИНН», фильтр «Субсчёт», справа «Строк: 73»; над таблицей «Предупреждения (1)» — «Информация: иерархия строк — по группировке листа (outline)». **Плоская** таблица (arm-web #13): строка на контрагента, колонки «Субсчёт», «Контрагент», «ИНН» и шесть сумм (сальдо на начало, обороты, сальдо на конец — Дебет / Кредит). Строка «Итого по документу» внизу (сумма корневых строк счёта, как «Итого» файла): «—», «27 923 002,09», «107 554 149,33», «99 188 100,32», «—», «19 556 953,08» (сальдо нач. Кт + обороты Кт − обороты Дт = сальдо кон. Кт) | | W16 | В панели Счастливочной клик по «ОСВ 60» (2024 К3) | экран «Просмотр ОСВ»: крошки «Отчётность › ПЕКАРНЯ СЧАСТЛИВОЧНАЯ, ООО › Проверка документа», подзаголовок «<имя файла> · XLS · <дата>», справа выбор «Документ» (список документов ЮЛ) и статус «Загружен», кнопка «✓ Подтвердить» неактивна; надпись «Суммы в рублях; строки ОСВ только для чтения»; поиск «Контрагент или ИНН», фильтр «Счёт / субсчёт», справа «Строк: 77»; над таблицей «Предупреждения (1)» — «Информация: иерархия строк — по группировке листа (outline)» (на документах, загруженных до появления записей предупреждений, блока нет — загрузите файл заново). Таблица «как в 1С» (AURORA-1702/1703): все строки файла в его порядке, 77 = счёт `60` (1) + субсчета `60.01`, `60.02`, `60.31` (3) + 73 контрагента; колонки «Субсчёт», «Контрагент», «ИНН» и шесть сумм (сальдо на начало, обороты, сальдо на конец — Дебет / Кредит). Строки счёта и субсчетов — **жирным** и с суммами (`60`: «—», «27 923 002,09», «107 554 149,33», «99 188 100,32», «—», «19 556 953,08»; `60.01`: «—», «28 220 505,40», «101 313 974,32», «94 382 838,93», «—», «21 289 370,01»; `60.31`: обороты «74 475,84» / «74 475,84»); текст в колонке «Контрагент» сдвинут на 16 px на уровень: `60` — без отступа, `60.01` — 16 px, контрагенты — 32 px (у групп в колонке «Контрагент» повторяется код счёта, как в файле). Колонка «Субсчёт» у контрагента показывает его субсчёт (`60.01`). Строка «Итого по документу» закреплена внизу таблицы (при окне 1600×1000 её край у нижней границы экрана — прокрутите область) и равна строке счёта `60` и «Итого» файла (строка 89): «—», «27 923 002,09», «107 554 149,33», «99 188 100,32», «—», «19 556 953,08» (сальдо нач. Кт + обороты Кт − обороты Дт = сальдо кон. Кт). Итого файла (как и строки счёта и субсчетов) — свёрнутое сальдо 1С, поэтому сумма сальдо контрагентов с дебетовым остатком (начальное Дт «297 503,31») не равна «Итого» — это не ошибка |
| W16а | Фильтр «Счёт / субсчёт»: открыть список; выбрать `60`; затем `60.02`; затем `60.01`, `60.31`; сбросить крестиком | список: `60`, `60.01`, `60.02`, `60.31`; фильтр оставляет счёт и все его субсчета по префиксу (`62` найдёт `62`, `62.01`, но не `620`): `60` — «Строк: 77», `60.01` — «Строк: 50» (группа + 49 контрагентов), `60.02` — «Строк: 23» (группа + 22), `60.31` — «Строк: 3» (группа + 2); при субсчёте первой строкой идёт его жирная группа с отступом 16 px, контрагенты — 32 px; «Итого по документу» от фильтра не меняется; после сброса — «Строк: 77» |
| W16б | Поиск: ввести `бисквит`; затем `3528052718`; затем `60.01`; очистить | и по названию, и по ИНН — «Строк: 2»: «Русский бисквит», ИНН 3528052718, в субсчетах `60.01` и `60.02`; строки-группы при поиске скрываются (жирных строк нет); `60.01` в поле поиска — «Строк: 0» (поиск идёт по контрагенту и ИНН, не по счёту); поиск и фильтр счёта действуют вместе; «Итого по документу» не меняется; после очистки — «Строк: 77» |
| 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] версии этот файл виден только по переключателю «Все документы организации» с подписью «вне консолидации» | | 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 несопоставленных: 1236 пробелов: 7`. Статус версии — «Коррекция данных» (K9) появляется сразу после распознавания (автоподтверждение чистых документов, OQ-157), в «Организации» в колонке «ОСВ» — счета ЮЛ | | W18 | Остальные файлы РБ и Счастливочной 2024 Q1–Q3 (всего 22, список — 7.1): через «Загрузить документы» у каждого ЮЛ и подтверждение каждого ОСВ, либо скриптом из корня `arm-ka`: `docs/scripts/demo-prepare.sh <version_id>` (id — из адреса `?version=`) | скрипт печатает строку на каждый файл (пять файлов из W13 — «уже загружен», это нормально), ждёт распознавания (до 5 мин), подтверждает только неподтверждённые ОСВ (чистые уже «Загружен», автоподтверждение, OQ-157) и в конце печатает `строк: 1385 документов: 22 несопоставленных: 1236 пробелов: 7`. Статус версии — «Коррекция данных» (K9) появляется сразу после распознавания (автоподтверждение чистых документов, OQ-157), в «Организации» в колонке «ОСВ» — счета ЮЛ |
`demo-prepare.sh` берёт пользователя вторым аргументом (по умолчанию `ka_go@armka.local`), адрес ядра — из `API` `demo-prepare.sh` берёт пользователя вторым аргументом (по умолчанию `ka_go@armka.local`), адрес ядра — из `API`
(по умолчанию `localhost:8000/api/v1`), каталог фикстур — из `FIXTURES_DIR` (по умолчанию `fixtures` рядом с `arm-web`). (по умолчанию `localhost:8000/api/v1`), каталог фикстур — из `FIXTURES_DIR` (по умолчанию `fixtures` рядом с `docs`).
### 8.5 Проверка формы ББ/ОФР из PDF [41], [41-02] ### 8.5 Проверка формы ББ/ОФР из PDF [41], [41-02]
@ -904,11 +901,11 @@ PDF из `fixtures/ocr` — отчётность других компаний (
- Предупреждения antd в консоли браузера («Static function can not consume context…», deprecated) ни на что не влияют. - Предупреждения antd в консоли браузера («Static function can not consume context…», deprecated) ни на что не влияют.
- Уведомления в `/admin` не появляются — дефект arm-web#7 (раздел 6.1). - Уведомления в `/admin` не появляются — дефект arm-web#7 (раздел 6.1).
Баг UI — issue в `RSHB/arm-web` (раздел 11): номер сценария W, скриншот, вкладка Network с запросом и ответом. Баг UI — раздел 11: номер сценария W, скриншот, вкладка Network с запросом и ответом.
## 9. Известные ограничения (не заводить как баги) ## 9. Известные ограничения (не заводить как баги)
- Справочники — только через API; в UI arm-web — администрирование и путь консолидации (раздел 8, ограничения прототипа — 8.7). - Справочники — только через API; в UI — администрирование и путь консолидации (раздел 8, ограничения прототипа — 8.7).
- Вход без пароля, открытый `/docs`, CORS `*` — dev-режим. - Вход без пароля, открытый `/docs`, CORS `*` — dev-режим.
- Email `*.local` через API/UI отклоняется, при этом dev-пользователи именно такие (созданы seed напрямую). - Email `*.local` через API/UI отклоняется, при этом dev-пользователи именно такие (созданы seed напрямую).
- Удалять алиасы и пустые ГСК/ГСЗ может только ADMIN (право `DIRECTORY_DELETE`). - Удалять алиасы и пустые ГСК/ГСЗ может только ADMIN (право `DIRECTORY_DELETE`).
@ -952,9 +949,9 @@ make migrate && make seed-dev
## 11. Как заводить баги ## 11. Как заводить баги
Gitea: бэкенд и API — `RSHB/arm-core`, экраны — `RSHB/arm-web`, документация — `RSHB/arm-ka-docs`. Один дефект — одна issue. Gitea: бэкенд, API и экраны — `RSHB/arm-core`, документация — `RSHB/arm-ka-docs`. Один дефект — одна issue.
Не уверены, чей баг, — откройте вкладку Network: ответ сервера не тот, что в гайде, — arm-core; ответ верный, а экран Не уверены, бэкенд это или фронт, — откройте вкладку Network: ответ сервера не тот, что в гайде, — бэкенд; ответ верный, а
показывает другое — arm-web. экран показывает другое — фронт (`web/`); в issue укажите, что именно.
Шаблон: Шаблон:
@ -967,7 +964,7 @@ Gitea: бэкенд и API — `RSHB/arm-core`, экраны — `RSHB/arm-web`,
Запрос: метод, путь, тело (без токена) Запрос: метод, путь, тело (без токена)
Ответ: статус и тело Ответ: статус и тело
Лог бэкенда: строки с трейсбэком, если есть Лог бэкенда: строки с трейсбэком, если есть
Окружение: ОС, браузер, версии docker/node, коммиты arm-core и arm-web (`git -C arm-core rev-parse --short HEAD`) Окружение: ОС, браузер, версии docker/node, коммит arm-core (`git -C arm-core rev-parse --short HEAD`)
Скриншот: для багов UI — экран и вкладка Network с запросом Скриншот: для багов UI — экран и вкладка Network с запросом
``` ```
@ -981,10 +978,10 @@ Gitea: бэкенд и API — `RSHB/arm-core`, экраны — `RSHB/arm-web`,
| `docker compose up --wait` без списка сервисов возвращает 1 | нормально из-за `minio-init`; используйте команду со списком из 4.1 | | `docker compose up --wait` без списка сервисов возвращает 1 | нормально из-за `minio-init`; используйте команду со списком из 4.1 |
| `make migrate` → connection refused | PostgreSQL ещё не `healthy`; подождать и повторить | | `make migrate` → connection refused | PostgreSQL ещё не `healthy`; подождать и повторить |
| `make venv` не находит Python 3.11 | `uv` скачает его сам при наличии интернета; проверьте `uv python list` | | `make venv` не находит Python 3.11 | `uv` скачает его сам при наличии интернета; проверьте `uv python list` |
| Белый экран на `localhost:3000` | фронт запущен `npm run start` без `REACT_APP_ROOT_PATH=/`; запускайте `make start` (4.3) | | Белый экран на `localhost:3000` | фронт запущен `npm run start` без `REACT_APP_ROOT_PATH=/` (4.3) |
| После входа «Нет связи с сервером» или ошибки сети | ядро не запущено на `:8000`; если на другом адресе — `ARM_CORE_URL=… make start`. Для старого `arm-core/web` — забыты `REACT_APP_API_URL`/`REACT_APP_ROOT_PATH` | | После входа «Нет связи с сервером» или ошибки сети | ядро не запущено на `:8000`; если на другом адресе — задайте `ARM_CORE_URL` (4.3) |
| Страница «Вход выполняется через РАИСА» | это production-сборка; запускайте `make start`, а не `make build` | | Страница «Вход выполняется через РАИСА» | это production-сборка; запускайте `npm run start` (4.3), а не `npm run build` |
| `make start`: порт 3000 занят | запущен второй фронт (например, старый `arm-core/web`); остановите его | | `npm run start`: порт 3000 занят | запущен второй фронт (например, из worktree); остановите его |
| Скан PDF не отображается («Не удалось открыть PDF») | старый браузер — обновите его | | Скан PDF не отображается («Не удалось открыть PDF») | старый браузер — обновите его |
| PDF завис в «Обработка» | не запущены `scan-gateway`/`scan-emulator` (раздел 8, предусловия) | | PDF завис в «Обработка» | не запущены `scan-gateway`/`scan-emulator` (раздел 8, предусловия) |
| PDF в статусе «Ошибка» (подсказка `INVALID_DOCUMENT`) | эмулятор знает только PDF из `fixtures/ocr` с JSON-ответом (6.8); свой PDF так и должен падать | | PDF в статусе «Ошибка» (подсказка `INVALID_DOCUMENT`) | эмулятор знает только PDF из `fixtures/ocr` с JSON-ответом (6.8); свой PDF так и должен падать |
@ -993,4 +990,4 @@ Gitea: бэкенд и API — `RSHB/arm-core`, экраны — `RSHB/arm-web`,
| `/healthcheck` показывает `nats: down` | NATS не поднят; на сценарии Среза 1 не влияет, но загрузка ОСВ (6.6) без NATS не дойдёт до парсера | | `/healthcheck` показывает `nats: down` | NATS не поднят; на сценарии Среза 1 не влияет, но загрузка ОСВ (6.6) без NATS не дойдёт до парсера |
| Файл завис в `UPLOADED`/`PROCESSING` | не запущен tabular-parser (`curl localhost:8001/healthcheck`) или ядро запущено без `NATS_ENABLED=true`; без ack файл через ~2 мин станет `ERROR` | | Файл завис в `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`. Подробнее об инфраструктуре — `docs/dev/README.md`, о бэкенде — `arm-core/README.md`, о фронте — `arm-core/web/README.md`.

View File

@ -1,7 +1,7 @@
# Тестирование единой нормализации ББ/ОФР (контракт 2.0) # Тестирование единой нормализации ББ/ОФР (контракт 2.0)
Документ для тестировщиков к выкладке «единой нормализации ББ/ОФР» на тестовый стенд в контуре банка (19.10.2026). Документ для тестировщиков к выкладке «единой нормализации ББ/ОФР» на тестовый стенд в контуре банка (19.10.2026).
Проверка — через экраны АРМ КА (arm-web) на стенде контура. Как поднять стенд локально, войти и проверить API через Проверка — через экраны АРМ КА (фронт `arm-core/web`) на стенде контура. Как поднять стенд локально, войти и проверить API через
Swagger — общий гайд [`qa-guide.md`](qa-guide.md) (разделы 4, 6.9, 8.5); здесь — только то, что изменилось с этой Swagger — общий гайд [`qa-guide.md`](qa-guide.md) (разделы 4, 6.9, 8.5); здесь — только то, что изменилось с этой
выкладкой. выкладкой.
@ -295,7 +295,7 @@ tabular-parser `4d1709a`, scan-gateway `1d05b9e`, общий пакет `arm-sta
| № | Шаги | Ожидание | | № | Шаги | Ожидание |
|---|---|---| |---|---|---|
| N57 | загрузить ОСВ из 1С (любой счёт), открыть | как раньше: «Загружен» для чистой ОСВ, «Требуется проверка» при чужой организации в шапке; экран «Просмотр ОСВ» — **плоская** таблица (колонки «Субсчёт», «Контрагент», «ИНН», шесть сумм, внизу «Итого по документу»), поиск «Контрагент или ИНН», фильтр «Субсчёт», «Строк: N»; над таблицей «Предупреждения (1)» — «Информация: иерархия строк — по группировке листа (outline)»; в панели у каждой ОСВ значок «ⓘ 1» (O-2). Суммы «Итого по документу» равны строке «Итого» файла | | N57 | загрузить ОСВ из 1С (любой счёт), открыть | как раньше: «Загружен» для чистой ОСВ, «Требуется проверка» при чужой организации в шапке; экран «Просмотр ОСВ» — таблица (колонки «Субсчёт», «Контрагент», «ИНН», шесть сумм, внизу «Итого по документу»), поиск «Контрагент или ИНН», фильтр «Субсчёт» (с 21.10.2026, AURORA-1702/1703, — вид «как в 1С» со строками счёта и субсчетов и фильтр «Счёт / субсчёт», см. `qa-guide.md` W16), «Строк: N»; над таблицей «Предупреждения (1)» — «Информация: иерархия строк — по группировке листа (outline)»; в панели у каждой ОСВ значок «ⓘ 1» (O-2). Суммы «Итого по документу» равны строке «Итого» файла |
| N58 | (окно перехода, если tabular-parser или scan-gateway ещё старые) загрузить ОСВ и форму | принимаются, экран работает; у форм все строки обычные, расшифровок и розовых строк нет; в значке документа — «Предупреждения файла» уровня «Информация» со старыми текстами (`LEGACY_WARNING`). Не баг — сообщите владельцу | | N58 | (окно перехода, если tabular-parser или scan-gateway ещё старые) загрузить ОСВ и форму | принимаются, экран работает; у форм все строки обычные, расшифровок и розовых строк нет; в значке документа — «Предупреждения файла» уровня «Информация» со старыми текстами (`LEGACY_WARNING`). Не баг — сообщите владельцу |
| N59 | ОСВ сч. 01 за 2-й или 3-й квартал без оборотов (Счастливочная 2025 Q2 / Q3) | «Требуется проверка» вместо «Загружен» — известное замечание I-5 | | N59 | ОСВ сч. 01 за 2-й или 3-й квартал без оборотов (Счастливочная 2025 Q2 / Q3) | «Требуется проверка» вместо «Загружен» — известное замечание I-5 |
@ -378,7 +378,7 @@ tabular-parser `4d1709a`, scan-gateway `1d05b9e`, общий пакет `arm-sta
## 8. Как заводить баги ## 8. Как заводить баги
На стенде контура баг заводится задачей в Jira проекта AURORA, как остальные замечания тестирования. Шаблон описания — На стенде контура баг заводится задачей в Jira проекта AURORA, как остальные замечания тестирования. Шаблон описания —
`qa-guide.md` раздел 11; в задаче укажите, чей он, если понятно (экран — arm-web, API и данные — arm-core, распознавание `qa-guide.md` раздел 11; в задаче укажите, чей он, если понятно (экран и API, данные — arm-core, распознавание
Excel — tabular-parser, PDF — scan-gateway; не уверены — по вкладке Network). Для форм ББ/ОФР дополнительно приложите: Excel — tabular-parser, PDF — scan-gateway; не уверены — по вкладке Network). Для форм ББ/ОФР дополнительно приложите:
1. **Файл** (или обезличенную копию) и где он лежит на стенде: ЮЛ, имя файла, дата загрузки. 1. **Файл** (или обезличенную копию) и где он лежит на стенде: ЮЛ, имя файла, дата загрузки.

View File

@ -1,66 +0,0 @@
# arm-web Specification
## Purpose
Требования к экрану проверки формы ББ/ОФР в arm-web по контракту 2.0: раскладка строк со статусами (`ok` / `detail` / `unreadable` / `missing`), подсветка «требует внимания», предупреждения у места и правка кодов строк, сумм, дат колонок и шапки.
## Requirements
### Requirement: Раскладка строк формы со статусами
Экран проверки формы ББ/ОФР ([41]) SHALL сохранять раскладку «как на бумаге» (Р-14): разделы, внутри — строки `ok` по
возрастанию кода, несколько строк одного кода — в порядке файла (`row_index`), шаблонные строки на своих местах,
пятизначные подстроки под родителем. Строка `unreadable` / `missing` SHALL стоять под ближайшей предыдущей по
`row_index` строкой, размещённой по коду, а без такой — в начале первого раздела; она подсвечена целиком, в ячейке
кода серым — сырой текст `line_code_text` с подсказкой. Строки `detail` SHALL быть скрыты под переключателем
«Показать расшифровки (N)» и показываться под родителем (`parent_row_index`) с отступом. Экран показывает справочное
имя строки (`reference_name`), файловое (`line_name`) — рядом, если отличается. Переключателя «как в файле» нет.
#### Scenario: Код-мусор на своём месте
- **WHEN** строка `unreadable` «ИЧ,250» в файле стоит после строки 1240
- **THEN** на экране она стоит сразу под 1240, подсвечена, в ячейке кода серым «ИЧ,250»
#### Scenario: Расшифровки скрыты
- **WHEN** у формы три строки `detail`
- **THEN** по умолчанию их не видно, переключатель «Показать расшифровки (3)»; включённый показывает их под родителями
### Requirement: Подсветка и предупреждения у места
Экран SHALL подсвечивать ячейки только по `needs_review` (в подсказке — `text` и уверенность), строки — по
`needs_review` строки, поля шапки — по `needs_review_fields` (в подсказке у поля — `header.source.<поле>`: сырой текст и
уверенность). Предупреждения и нарушения (`issues`, `checks`) SHALL показываться у места: у строк (`line_ids`), ячеек
(`line_ids` + `column_id`), полей шапки (`field`) или у документа (в том числе запись с `column_index` без `column_id` —
столбец вне колонок формы); уровень `error` отличается от `info`. Подсветки по `confidence < 100` и `is_recognized =
false` напрямую нет. У значений строк `detail` `needs_review` всегда `false` (Р-23): при показанных расшифровках их
сомнительные ячейки не подсвечиваются, но подсказка с `text` и уверенностью есть.
#### Scenario: Догадка подтверждена проверкой
- **WHEN** значение `is_recognized = false`, `needs_review = false`
- **THEN** ячейка не подсвечена; в подсказке сырой текст и уверенность
### Requirement: Правка строк, колонок и подтверждение в режиме «Редактировать»
В режиме «Редактировать» экран SHALL давать: у `unreadable` / `missing` — поле ввода кода с подсказкой из справочника
(код → справочное имя) и пункт меню строки «Это расшифровка»; у `detail` — «Назначить код»; у строки `ok` — «Сделать
расшифровкой», смену кода и «Подтвердить код» у строки с `needs_review`; подтверждение ячейки без правки; ввод даты у
колонки без даты (`period_kind = none`) или с предупреждением о дате (в том числе подтверждение той же даты); снятие подтверждения ячейки и кода (`confirmed:
false`); правка наименования организации в шапке; подтверждение даты отчёта из `needs_review_fields` без изменения (тем
же значением). «Сохранить» отправляет один `PATCH …/statement` (`header`, `values`, `columns`, `lines`). Кнопка «Подтвердить данные» при строках `unreadable` / `missing` с суммами
SHALL быть отключена с подсказкой «Разберите строки без кода: N» и переходом к первой такой строке; ответ сервера 409
`LINES_UNRESOLVED` показывается тем же текстом, 409 `DUPLICATE_FORM` — со ссылкой на подтверждённую форму, 409
`LINE_CODE_TAKEN` — у поля кода. Кнопка «Подтвердить данные» учитывает строки `unreadable` / `missing` с суммой или
нераспознанным текстом в ячейках.
#### Scenario: Назначить код
- **WHEN** аналитик вводит 1250 в ячейку кода строки `unreadable` и сохраняет
- **THEN** уходит `PATCH` с `lines: [{line_id, line_code: "1250"}]`; после ответа строка встаёт на место 1250
#### Scenario: Подтверждение заблокировано
- **WHEN** у формы две строки `missing` с суммами
- **THEN** «Подтвердить данные» отключена, подсказка «Разберите строки без кода: 2», клик по подсказке ведёт к первой
### Requirement: Предупреждения файла и документа в карточке файла
Карточка файла (панель файлов) SHALL читать `job.warnings` как список структур `{code, severity, message, locator}`
(после выкладки ядра v5.19 это не строки; у старых заданий ядро отдаёт строки, обёрнутые в `LEGACY_WARNING`), показывать
`message` и `locator`, отличая `error` от `info`; предупреждения листа ОСВ и документа — из `DocumentOut.issues[]`.
`job.mapping_warnings` остаётся списком строк.
#### Scenario: Потерянный ОФР в карточке
- **WHEN** у задания `job.warnings = [{code: "JSON_FAILED", severity: "error", message: "…", locator: "file:x_ofr.json"}]`
- **THEN** карточка показывает предупреждение уровня «ошибка» с текстом и местом; экран не падает на объекте вместо строки

View File

@ -35,10 +35,10 @@ json dataclass-ы), шаги 2–8, общие разборы текста (дв
только скрипт, сервис читает при старте и пишет в результат как `service.statements_version`. Прямые правки копии MUST только скрипт, сервис читает при старте и пишет в результат как `service.statements_version`. Прямые правки копии MUST
NOT делаться. NOT делаться.
Копию SHALL раскладывать и проверять скрипт `docs/scripts/sync-statements.sh` (по образцу `sync-web.sh`; цель `make -C Копию SHALL раскладывать и проверять скрипт `docs/scripts/sync-statements.sh` (цель `make -C
docs sync-statements`). Источник — локальный checkout `arm-statements` рядом с workspace или `--source <dir>`, ссылка — docs sync-statements`). Источник — локальный checkout `arm-statements` рядом с workspace или `--source <dir>`, ссылка —
`origin/main` или `--ref`; хост — `--host <имя>` или `--host-dir <dir>`. Без `--check` скрипт раскладывает копию коммита `origin/main` или `--ref`; хост — `--host <имя>` или `--host-dir <dir>`. Без `--check` скрипт раскладывает копию коммита
`--ref` в новый worktree хоста на ветке фичи, пишет `VERSION` и готовит issue и PR (как `sync-web.sh`). С `--in-place` скрипт SHALL раскладывать `--ref` в новый worktree хоста на ветке фичи, пишет `VERSION` и готовит issue и PR. С `--in-place` скрипт SHALL раскладывать
копию коммита `--ref` в рабочее дерево указанного хоста (существующий worktree) без issue, коммита и PR — для копию коммита `--ref` в рабочее дерево указанного хоста (существующий worktree) без issue, коммита и PR — для
подключения хоста и для PR фичи, которой нужна новая версия пакета; при прямой правке копии или недостающей зависимости — подключения хоста и для PR фичи, которой нужна новая версия пакета; при прямой правке копии или недостающей зависимости —
отказ с кодом `20`, ничего не пишется. Копия отказ с кодом `20`, ничего не пишется. Копия

50
scripts/demo-prepare.sh Executable file
View File

@ -0,0 +1,50 @@
#!/usr/bin/env bash
# Подготовка демо: загрузить квартальные ОСВ РБ и Счастливочной за 2024 Q1–Q3 в версию и подтвердить ОСВ-документы.
# Использование: docs/scripts/demo-prepare.sh <version_id> [email] (стенд arm-core на localhost:8000, DEBUG=true)
# Каталог фикстур: FIXTURES_DIR (по умолчанию ../../fixtures относительно этого скрипта, т.е. workspace/fixtures); перенесён из arm-web 21.10.2026.
set -euo pipefail
V="${1:?version_id}"
EMAIL="${2:-ka_go@armka.local}"
API="${API:-localhost:8000/api/v1}"
F="${FIXTURES_DIR:-$(cd "$(dirname "$0")/../.." && pwd)/fixtures}/osv"
H="Authorization: Bearer $(curl -sf -X POST "$API/dev/token" -H 'Content-Type: application/json' -d "{\"email\":\"$EMAIL\"}" | python3 -c 'import sys,json;print(json.load(sys.stdin)["access_token"])')"
members=$(curl -sf "$API/consolidations/versions/$V" -H "$H" | python3 -c '
import sys,json
for m in json.load(sys.stdin)["members"]: print(m["id"], m["inn"], m["name"])')
# ЮЛ ищем по ИНН (в справочнике имена могут быть в верхнем регистре)
le_by_inn() { echo "$members" | awk -v inn="$1" '$2 == inn { print $1; exit }'; }
LE_RB=$(le_by_inn 3528052718)
LE_SCH=$(le_by_inn 3528104476)
[ -n "$LE_RB" ] && [ -n "$LE_SCH" ] || { echo "В периметре версии нет РБ или Счастливочной:"; echo "$members"; exit 1; }
upload() { for f in "$F/$2"/2024-Q{1,2,3}/*; do
curl -s -X POST "$API/legal-entities/$1/files?consolidation_version_id=$V" -H "$H" -F "files=@$f" \
| python3 -c 'import sys,json;d=json.load(sys.stdin);print("принят" if d.get("accepted") else d.get("rejected") or d)'
done; }
upload "$LE_RB" rb
upload "$LE_SCH" schastl
echo "Жду распознавания…"
for _ in $(seq 1 60); do
pending=0
for LE in $LE_RB $LE_SCH; do
n=$(curl -sf "$API/legal-entities/$LE/files?limit=100" -H "$H" | python3 -c 'import sys,json;print(sum(f["status"] in ("UPLOADED","PROCESSING") for f in json.load(sys.stdin)["items"]))')
pending=$((pending + n))
done
[ "$pending" -eq 0 ] && break
sleep 5
done
for LE in $LE_RB $LE_SCH; do
curl -sf "$API/legal-entities/$LE/files?limit=100" -H "$H" | python3 -c '
import sys,json
for f in json.load(sys.stdin)["items"]:
for d in f["documents"]:
if d["document_type"] == "OSV" and d["status"] in ("RECOGNIZED","NEEDS_CHECK"): print(d["id"])' \
| while read -r DOC; do curl -s -o /dev/null -w "%{http_code} $DOC\n" -X POST "$API/documents/$DOC/confirm" -H "$H"; done
done
curl -sf "$API/consolidations/versions/$V/osv/summary" -H "$H" | python3 -c '
import sys,json
s=json.load(sys.stdin); print("строк:", s["rows_total"], "документов:", s["documents_total"], "несопоставленных:", s["unmapped_total"], "пробелов:", len(s["gaps"]))'

View File

@ -6,17 +6,12 @@
# Nexus (как `setup/setup.sh` шаблона), убирает цель docker-build, заменяет ссылки на внешний Gitea, пишет SNAPSHOT.md с # Nexus (как `setup/setup.sh` шаблона), убирает цель docker-build, заменяет ссылки на внешний Gitea, пишет SNAPSHOT.md с
# провенансом и проверяет результат. Репозиторий-источник не меняется. # провенансом и проверяет результат. Репозиторий-источник не меняется.
# #
# Запуск из workspace: docs/scripts/prepare-contour-snapshot.sh [--web-as-is] <каталог репозитория> <каталог вывода> [ref] # Запуск из workspace: docs/scripts/prepare-contour-snapshot.sh <каталог репозитория> <каталог вывода> [ref]
# ref по умолчанию — origin/main (снимок всегда с сервера, не с рабочего дерева). # ref по умолчанию — origin/main (снимок всегда с сервера, не с рабочего дерева).
# Пример: docs/scripts/prepare-contour-snapshot.sh tabular-parser /tmp/contour # Пример: docs/scripts/prepare-contour-snapshot.sh tabular-parser /tmp/contour
# Результат: <каталог вывода>/<имя репозитория>/ — готовое дерево для rsync в клон репозитория контура. # Результат: <каталог вывода>/<имя репозитория>/ — готовое дерево для rsync в клон репозитория контура.
# #
# Для arm-core перед снимком проверяется, не отстаёт ли встроенный фронт web/ от RSHB/arm-web (sync-web.sh --check). # Флаг --web-as-is остался от синхронизации с arm-web (до 21.10.2026) и игнорируется: фронт ведётся в arm-core/web.
# Если отстаёт — вопрос «перенести как есть?»: «y» — снимок с отставанием (оно записывается в SNAPSHOT.md); «n» —
# запускается синхронизация (sync-web.sh: worktree, issue, PR) и перенос останавливается (код 3) до merge PR и закрытия
# issue. Без терминала вопрос не задаётся: нужен флаг --web-as-is, иначе код 3. Расхождение (прямые правки web/ в arm-core) —
# всегда стоп. arm-web ищется рядом с репозиторием (../arm-web) или в $ARM_WEB; если его нет — предупреждение, снимок делается.
# $SYNC_WEB_ARGS — дополнительные аргументы sync-web.sh (для теста: --dry-run --wt-root …).
# Для каждого сервиса, где в $REF есть api/arm_statements, проверяется свежесть копии пакета (sync-statements.sh --check # Для каждого сервиса, где в $REF есть api/arm_statements, проверяется свежесть копии пакета (sync-statements.sh --check
# по дереву $REF; Р-42): отставание — предупреждение и абзац в SNAPSHOT.md, прямая правка копии — стоп (код 1), нет # по дереву $REF; Р-42): отставание — предупреждение и абзац в SNAPSHOT.md, прямая правка копии — стоп (код 1), нет
# arm-statements или Python — предупреждение. Нет api/arm_statements — проверка пропускается. $SYNC_STATEMENTS_ARGS — # arm-statements или Python — предупреждение. Нет api/arm_statements — проверка пропускается. $SYNC_STATEMENTS_ARGS —
@ -32,9 +27,10 @@ WEB_OPTIONAL_KEEP_PREFIX="raisa-" # opt
WEB_SCRIPTS_KEEP=(build start preview) # scripts, работающие без инструментов разработки WEB_SCRIPTS_KEEP=(build start preview) # scripts, работающие без инструментов разработки
set -euo pipefail set -euo pipefail
usage() { echo "usage: $0 [--web-as-is] <repo-dir> <out-dir> [ref]" >&2; exit 2; } usage() { echo "usage: $0 <repo-dir> <out-dir> [ref]" >&2; exit 2; }
WEB_AS_IS=0 if [ "${1:-}" = "--web-as-is" ]; then
if [ "${1:-}" = "--web-as-is" ]; then WEB_AS_IS=1; shift; fi echo "предупреждение: --web-as-is больше не нужен (фронт ведётся в arm-core/web) — флаг игнорируется" >&2; shift
fi
[ $# -ge 2 ] || usage [ $# -ge 2 ] || usage
REPO=$(cd "$1" && pwd) REPO=$(cd "$1" && pwd)
@ -53,42 +49,6 @@ if [ -e "$DEST" ]; then
exit 2 exit 2
fi fi
# --- 0. arm-core: встроенный фронт web/ не должен отставать от arm-web -----------------------------------------------
WEB_LAG=""
if [ "$NAME" = arm-core ]; then
SYNC_WEB="$(cd "$(dirname "$0")" && pwd)/sync-web.sh"
ARM_WEB_DIR=${ARM_WEB:-$(dirname "$REPO")/arm-web}
# shellcheck disable=SC2206
SYNC_ARGS=(--arm-core "$REPO" --arm-web "$ARM_WEB_DIR" --core-ref "$REF" ${SYNC_WEB_ARGS:-})
if [ ! -e "$ARM_WEB_DIR/.git" ]; then
echo "предупреждение: arm-web не найден ($ARM_WEB_DIR) — отставание web/ от arm-web не проверено" >&2
else
rc=0; "$SYNC_WEB" --check "${SYNC_ARGS[@]}" || rc=$?
case "$rc" in
0) ;;
10)
if [ "$WEB_AS_IS" = 1 ]; then
answer=y
elif [ -t 0 ] || [ -p /dev/stdin ] || [ -f /dev/stdin ]; then
read -r -p "web/ в arm-core отстаёт от arm-web. Перенести в контур как есть? [y/N] " answer || answer=n
else
answer=n
echo "нет терминала и нет --web-as-is: перенос как есть не подтверждён" >&2
fi
case "$answer" in
y|Y|д|Д) WEB_LAG="отстаёт от arm-web (перенесён как есть по решению оператора, см. sync-web.sh --check)" ;;
*)
echo "запускается синхронизация arm-core/web с arm-web (sync-web.sh)…" >&2
"$SYNC_WEB" "${SYNC_ARGS[@]}" || true
echo "перенос в контур остановлен до полного завершения синхронизации: merge PR и закрытие issue (чеклист «закрывай»), затем повторите снимок." >&2
exit 3 ;;
esac ;;
20) echo "ошибка: web/ в arm-core расходится с arm-web (прямые правки) — снимок не делается, разберитесь вручную" >&2; exit 1 ;;
*) echo "ошибка: sync-web.sh --check завершился с кодом $rc" >&2; exit 1 ;;
esac
fi
fi
# --- 0a. Копия пакета arm_statements в api/arm_statements не должна отставать от arm-statements --------------------------- # --- 0a. Копия пакета arm_statements в api/arm_statements не должна отставать от arm-statements ---------------------------
# Проверяется дерево $REF хоста (git archive api → временный каталог), не рабочее. Нет api/arm_statements в $REF — пакет в # Проверяется дерево $REF хоста (git archive api → временный каталог), не рабочее. Нет api/arm_statements в $REF — пакет в
# хост ещё не подключён, проверка молча пропускается. Коды sync-statements.sh --check: 0 — тихо; 10 — предупреждение в # хост ещё не подключён, проверка молча пропускается. Коды sync-statements.sh --check: 0 — тихо; 10 — предупреждение в
@ -134,8 +94,8 @@ git -C "$REPO" archive "$REF" | tar -x -C "$DEST"
# ../docs и ../fixtures; openspec CLI в контуре нет, specs/ и design.md читаются без него); docs/superpowers/ — планы и # ../docs и ../fixtures; openspec CLI в контуре нет, specs/ и design.md читаются без него); docs/superpowers/ — планы и
# ledger (в них локальные пути); Dockerfile/.dockerignore — локальная сборка образа, платформа собирает образ своим CI. # ledger (в них локальные пути); Dockerfile/.dockerignore — локальная сборка образа, платформа собирает образ своим CI.
# openspec/specs/ и openspec/changes/archive/ остаются: это спецификация проекта и решения (design.md). # openspec/specs/ и openspec/changes/archive/ остаются: это спецификация проекта и решения (design.md).
# Артефакты агента вырезаются на любой глубине: фронт перенесён в arm-core/web/ из arm-web со своими .claude/, CLAUDE.md, # Артефакты агента вырезаются на любой глубине (вложенный проект вроде arm-core/web/ может нести свои .claude/, CLAUDE.md,
# openspec/config.yaml, docs/superpowers/ и .codegraph/. Dockerfile/.dockerignore — только в корне (образ сервиса). # openspec/config.yaml, docs/superpowers/ и .codegraph/). Dockerfile/.dockerignore — только в корне (образ сервиса).
rm -rf "$DEST/Dockerfile" "$DEST/.dockerignore" rm -rf "$DEST/Dockerfile" "$DEST/.dockerignore"
find "$DEST" \( \( -type d \( -name .claude -o -name .codegraph -o -path '*/docs/superpowers' \) \) \ find "$DEST" \( \( -type d \( -name .claude -o -name .codegraph -o -path '*/docs/superpowers' \) \) \
-o \( -type f \( -name CLAUDE.md -o -path '*/openspec/config.yaml' \) \) \) -prune -exec rm -rf {} + -o \( -type f \( -name CLAUDE.md -o -path '*/openspec/config.yaml' \) \) \) -prune -exec rm -rf {} +
@ -297,9 +257,6 @@ fi
if [ -n "$STATEMENTS_LAG" ]; then if [ -n "$STATEMENTS_LAG" ]; then
printf '\nКопия пакета arm_statements (`api/arm_statements/`) на момент снимка требует внимания: %s\n' "$STATEMENTS_LAG" >> "$DEST/SNAPSHOT.md" printf '\nКопия пакета arm_statements (`api/arm_statements/`) на момент снимка требует внимания: %s\n' "$STATEMENTS_LAG" >> "$DEST/SNAPSHOT.md"
fi fi
if [ -n "$WEB_LAG" ]; then
printf '\nВстроенный фронт \`web/\` на момент снимка %s.\n' "$WEB_LAG" >> "$DEST/SNAPSHOT.md"
fi
# --- 5. Проверки ---------------------------------------------------------------------------------------------------- # --- 5. Проверки ----------------------------------------------------------------------------------------------------
fail=0 fail=0

View File

@ -1,221 +0,0 @@
#!/usr/bin/env bash
# Синхронизация встроенного фронта arm-core/web с отдельным репозиторием фронта RSHB/arm-web (ветка test).
# Делает одной командой то, что делалось вручную в arm-core #94 и #100: worktree, issue, перенос набора файлов,
# проверки фронта, коммит, PR. Гайд: dev/contour-transfer-guide.md §3.1.
#
# Точка синхронизации не хранится: это коммит arm-web@<ref>, у которого хэш дерева `src` равен хэшу `web/src`
# в arm-core@<core-ref>. Если такого коммита нет — arm-core/web правили напрямую, мимо arm-web; перенос затёр бы
# эту работу, поэтому скрипт останавливается (код 20).
#
# Набор синхронизации — всё дерево arm-web, кроме служебного (контекст агента, спеки UI, Makefile и скрипты
# демо-стенда): CLAUDE.md, .claude/, openspec/, docs/, scripts/, Makefile, ARCHITECTURE.md, .gitignore, package-lock.json.
#
# Запуск из workspace: docs/scripts/sync-web.sh [--check] [--dry-run] [--ref <ref>] [--core-ref <ref>]
# [--arm-web <dir>] [--arm-core <dir>] [--wt-root <dir>]
# --check только сравнить: 0 — синхронно, 10 — arm-core/web отстаёт, 20 — расхождение (правки напрямую)
# --dry-run worktree, перенос и контроль идентичности без issue, PR, push и проверок npm (посмотреть, что перенесётся)
# --ref ветка arm-web, по умолчанию origin/test; --core-ref — база arm-core, по умолчанию origin/main
# WEB_NODE_MODULES — откуда взять node_modules для проверок (по умолчанию <wt-root>/web-proto/arm-web/node_modules);
# если каталога нет — npm install в worktree
# Скрипт пишет только в новый worktree arm-core на ветке фичи; ничего не мержит, не удаляет и не закрывает —
# закрытие после merge идёт обычным чеклистом «закрывай».
set -euo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)"
# Корень workspace — где рядом лежат arm-core и arm-web. Скрипт запускают и из docs/ (корень = ../..), и из worktree
# .wt/<задача>/docs (корень = ../../../..); внешний кандидат проверяется первым — там основные checkout'ы.
WS="$(cd "$HERE/../.." && pwd)"
for cand in "$HERE/../../../.." "$HERE/../.."; do
if [ -e "$cand/arm-core/.git" ] && [ -e "$cand/arm-web/.git" ]; then WS="$(cd "$cand" && pwd)"; break; fi
done
ARM_WEB="$WS/arm-web"
ARM_CORE="$WS/arm-core"
WT_ROOT="$WS/.wt"
REF=origin/test
CORE_REF=origin/main
MODE=sync
CHECK=0
while [ $# -gt 0 ]; do
case "$1" in
--check) CHECK=1 ;; # имеет приоритет над --dry-run независимо от порядка (пайплайн добавляет $SYNC_WEB_ARGS в конец)
--dry-run) MODE=dry ;;
--ref) REF=$2; shift ;;
--core-ref) CORE_REF=$2; shift ;;
--arm-web) ARM_WEB=$2; shift ;;
--arm-core) ARM_CORE=$2; shift ;;
--wt-root) WT_ROOT=$2; shift ;;
-h|--help) sed -n '2,24p' "$0"; exit 0 ;;
*) echo "sync-web: неизвестный аргумент $1" >&2; exit 2 ;;
esac
shift
done
[ "$CHECK" = 0 ] || MODE=check
# Исключения набора синхронизации: pathspec для git diff и имена для diff -rq
EXCLUDE_NAMES=(CLAUDE.md .claude openspec docs scripts Makefile ARCHITECTURE.md .gitignore package-lock.json)
PATHSPEC=(.)
DIFF_EXCLUDES=(--exclude=node_modules --exclude=dist --exclude=.codegraph --exclude=.superpowers --exclude=.bounded)
for n in "${EXCLUDE_NAMES[@]}"; do PATHSPEC+=(":(exclude,top)$n"); DIFF_EXCLUDES+=("--exclude=$n"); done
die() { echo "sync-web: $1" >&2; exit "${2:-1}"; } # die <сообщение> [код]
[ -d "$ARM_WEB/.git" ] || [ -f "$ARM_WEB/.git" ] || die "arm-web не найден: $ARM_WEB (--arm-web <dir>)" 2
[ -d "$ARM_CORE/.git" ] || [ -f "$ARM_CORE/.git" ] || die "arm-core не найден: $ARM_CORE (--arm-core <dir>)" 2
ARM_WEB=$(cd "$ARM_WEB" && pwd); ARM_CORE=$(cd "$ARM_CORE" && pwd)
git -C "$ARM_WEB" fetch -q origin
git -C "$ARM_CORE" fetch -q origin
git -C "$ARM_WEB" rev-parse -q --verify "$REF^{commit}" >/dev/null || die "в arm-web нет $REF" 2
git -C "$ARM_CORE" rev-parse -q --verify "$CORE_REF^{commit}" >/dev/null || die "в arm-core нет $CORE_REF" 2
# --- 1. Точка синхронизации -------------------------------------------------------------------------------------------
CORE_SRC=$(git -C "$ARM_CORE" rev-parse -q --verify "$CORE_REF:web/src") || die "в arm-core@$CORE_REF нет web/src" 2
HEAD_SHA=$(git -C "$ARM_WEB" rev-parse "$REF")
HEAD7=${HEAD_SHA:0:7}
POINT=""
while IFS= read -r c; do
t=$(git -C "$ARM_WEB" rev-parse -q --verify "$c:src" 2>/dev/null || true)
if [ "$t" = "$CORE_SRC" ]; then POINT=$c; break; fi
done < <(git -C "$ARM_WEB" rev-list "$REF")
if [ -z "$POINT" ]; then
echo "sync-web: расхождение — ни у одного коммита arm-web@$REF дерево src не совпадает с arm-core@$CORE_REF:web/src." >&2
echo "arm-core/web правили напрямую, мимо arm-web; автоперенос затёр бы эти правки. Коммиты arm-core по web/:" >&2
git -C "$ARM_CORE" log --format=' %h %s' -10 "$CORE_REF" -- web/ >&2
echo "Разберитесь вручную: перенесите прямые правки в arm-web (или откатите их), затем повторите." >&2
exit 20
fi
if [ "$POINT" = "$HEAD_SHA" ]; then
echo "sync-web: arm-core@$CORE_REF web/ синхронен с arm-web@$REF ($HEAD7) — переносить нечего"
exit 0
fi
COMMITS=$(git -C "$ARM_WEB" log --format='%h %s' --no-merges "$POINT..$REF")
N_COMMITS=$(printf '%s\n' "$COMMITS" | grep -c . || true)
FILES=$(git -C "$ARM_WEB" diff --name-only "$POINT" "$REF" -- "${PATHSPEC[@]}")
N_FILES=$(printf '%s\n' "$FILES" | grep -c . || true)
echo "sync-web: arm-core@$CORE_REF web/ отстаёт от arm-web@$REF ($HEAD7)."
echo "точка синхронизации: arm-web ${POINT:0:7}; непереносённых коммитов: $N_COMMITS, файлов в наборе: $N_FILES"
printf '%s\n' "$COMMITS" | sed 's/^/ /'
[ "$MODE" != check ] || exit 10
# --- 2. Worktree -----------------------------------------------------------------------------------------------------
TMP_BRANCH="tmp/web-sync-$HEAD7"
WT="$WT_ROOT/web-sync-$HEAD7/arm-core"
[ ! -e "$WT" ] || die "worktree уже существует: $WT — завершите или удалите предыдущую синхронизацию" 2
git -C "$ARM_CORE" rev-parse -q --verify "$TMP_BRANCH" >/dev/null && die "ветка $TMP_BRANCH уже есть в arm-core" 2
mkdir -p "$(dirname "$WT")"
git -C "$ARM_CORE" worktree add -q -b "$TMP_BRANCH" "$WT" "$CORE_REF"
touch "$WT/.bounded"
echo "worktree: $WT ($TMP_BRANCH от $CORE_REF)"
# --- 3. Issue и ветка (не в dry-run) ---------------------------------------------------------------------------------
# tea 0.16+ принимает --login/--repo только после подкоманды: tea issues create --login … (arm-ka-docs#110)
TEA_FLAGS=(--login home --repo RSHB/arm-core)
ISSUE=""
BRANCH="$TMP_BRANCH"
if [ "$MODE" = sync ]; then
TITLE="web: синхронизация встроенного фронта с arm-web@${REF#origin/} ($HEAD7)"
BODY="Встроенный фронт \`arm-core/web\` отстаёт от \`RSHB/arm-web@${REF#origin/}\` ($HEAD7); точка синхронизации — arm-web ${POINT:0:7}.
Непереносённые коммиты arm-web ($N_COMMITS):
$(printf '%s\n' "$COMMITS" | sed 's/^/- /')
Bounded-задача, выполнена \`docs/scripts/sync-web.sh\`: перенос набора синхронизации ($N_FILES файлов) в \`web/\`,
проверки vitest / tsc / biome / build, PR в main. Ревью перенесённого кода против бэкенда — за исполнителем."
ISSUE_URL=$(tea issues create "${TEA_FLAGS[@]}" --title "$TITLE" --description "$BODY" | grep -o 'https://[^ ]*/issues/[0-9]*' | tail -1)
ISSUE=${ISSUE_URL##*/}
[ -n "$ISSUE" ] || die "не удалось создать issue через tea"
BRANCH="feat/$ISSUE-web-sync-$HEAD7"
git -C "$WT" branch -m "$TMP_BRANCH" "$BRANCH"
echo "issue: $ISSUE_URL; ветка: $BRANCH"
fi
# --- 4. Перенос набора и контроль идентичности ------------------------------------------------------------------------
git -C "$ARM_WEB" diff --binary "$POINT" "$REF" -- "${PATHSPEC[@]}" | git -C "$WT" apply --directory=web
REF_TREE=$(mktemp -d); trap 'rm -rf "$REF_TREE"' EXIT
git -C "$ARM_WEB" archive "$REF" | tar -x -C "$REF_TREE"
if ! diff -rq "${DIFF_EXCLUDES[@]}" "$REF_TREE" "$WT/web" >/dev/null; then
echo "sync-web: после переноса web/ не совпадает с arm-web@$REF:" >&2
diff -rq "${DIFF_EXCLUDES[@]}" "$REF_TREE" "$WT/web" >&2 || true
exit 1
fi
echo "перенесено: $N_FILES файлов; web/ идентичен arm-web@$REF по набору синхронизации"
if [ "$MODE" = dry ]; then
echo "dry-run: issue, проверки, коммит и PR не выполнялись. Посмотреть: git -C $WT status; убрать:"
echo " git -C $ARM_CORE worktree remove --force $WT && git -C $ARM_CORE branch -D $TMP_BRANCH"
exit 0
fi
# --- 5. Проверки фронта ---------------------------------------------------------------------------------------------
NM=${WEB_NODE_MODULES:-$WT_ROOT/web-proto/arm-web/node_modules}
if [ -d "$NM" ]; then
ln -s "$NM" "$WT/web/node_modules"
echo "node_modules: симлинк на $NM"
else
echo "node_modules: $NM нет — npm install в worktree"
(cd "$WT/web" && npm install --no-audit --no-fund >/dev/null)
fi
run_check() { # имя, команда... — печатает хвост вывода при ошибке и останавливает скрипт
local name=$1; shift
local log; log=$(mktemp)
if (cd "$WT/web" && "$@") >"$log" 2>&1; then rm -f "$log"; return 0; fi
echo "sync-web: проверка «$name» не прошла; worktree оставлен для разбора: $WT" >&2
tail -40 "$log" >&2; rm -f "$log"; exit 1
}
run_check vitest npx vitest run
TESTS=$(cd "$WT/web" && npx vitest run 2>&1 | grep -E '^\s*Tests\s' | sed 's/^ *//' || true)
run_check tsc npx tsc --noEmit
biome_warnings() { (cd "$WT/web" && npx biome check "$1" 2>&1 | grep -oE 'Found [0-9]+ warning' | grep -oE '[0-9]+') || echo 0; }
W_MAIN=$(biome_warnings "$ARM_CORE/web/src")
W_NEW=$(biome_warnings src)
[ "$W_NEW" -le "$W_MAIN" ] || { echo "sync-web: biome — предупреждений стало больше: $W_NEW против $W_MAIN на $CORE_REF; worktree: $WT" >&2; exit 1; }
run_check build npm run build
echo "проверки: $TESTS; tsc чисто; biome $W_NEW предупреждений (на $CORE_REF — $W_MAIN); build ок"
# --- 6. Коммит, push, PR -------------------------------------------------------------------------------------------
# web/dist игнорируется .gitignore arm-core; исключать его pathspec'ом нельзя — git 2.50 на явно названный
# игнорируемый путь отвечает «paths are ignored» и кодом 1 (arm-ka-docs#110). Симлинк node_modules .gitignore не ловит.
git -C "$WT" add -A web -- ':!web/node_modules'
git -C "$WT" -c commit.gpgsign=false commit -q -F - <<EOF
fix(web): синхронизация встроенного фронта с arm-web@${REF#origin/} ($HEAD7) (#$ISSUE)
Перенесён диапазон arm-web ${POINT:0:7}..$HEAD7 ($N_COMMITS коммитов, $N_FILES файлов набора
синхронизации) скриптом docs/scripts/sync-web.sh. После переноса web/ и
arm-web@${REF#origin/} идентичны по набору. Коммиты arm-web:
$(printf '%s\n' "$COMMITS" | sed 's/^/- /')
Co-Authored-By: sync-web.sh <noreply@aepif.ru>
EOF
COMMIT=$(git -C "$WT" rev-parse --short=7 HEAD)
git -C "$WT" push -q -u origin "$BRANCH"
PR_BODY="Закрывает #$ISSUE.
Встроенный фронт \`arm-core/web\` отставал от \`RSHB/arm-web@${REF#origin/}\` ($HEAD7). Диапазон arm-web ${POINT:0:7}..$HEAD7
перенесён скриптом \`docs/scripts/sync-web.sh\` без правок; после переноса \`web/\` и arm-web идентичны по набору синхронизации.
Коммиты arm-web ($N_COMMITS):
$(printf '%s\n' "$COMMITS" | sed 's/^/- /')
Файлы ($N_FILES):
$(printf '%s\n' "$FILES" | sed 's/^/- web\//')
Проверки в worktree: $TESTS; \`tsc --noEmit\` чисто; \`biome check src\` — $W_NEW предупреждений (на ${CORE_REF#origin/} — $W_MAIN); \`rsbuild build\` собирается.
## Rulings I made
- R-1 — служебные файлы arm-web (CLAUDE.md, .claude/, openspec/, docs/, scripts/, Makefile, ARCHITECTURE.md, .gitignore, lockfile) не переносятся: UI-спеки и демо-стенд живут в arm-web.
- R-2 — bounded-задача без OpenSpec change: перенос готового, проревьюенного в arm-web кода.
Ревью перенесённого кода против бэкенда — за исполнителем, находки с маркерами в комментариях к PR.
🤖 Generated with [Claude Code](https://claude.com/claude-code)"
PR_URL=$(tea pulls create "${TEA_FLAGS[@]}" --head "$BRANCH" --base "${CORE_REF#origin/}" --title "fix(web): синхронизация встроенного фронта с arm-web@${REF#origin/} ($HEAD7) (#$ISSUE)" \
--description "$PR_BODY" | grep -o 'https://[^ ]*/pulls/[0-9]*' | tail -1)
echo
echo "готово: коммит $COMMIT, ветка $BRANCH"
echo "issue: $ISSUE_URL"
echo "PR: ${PR_URL:-создать вручную: tea pulls create ${TEA_FLAGS[*]} --head $BRANCH --base ${CORE_REF#origin/}}"
echo "исполнителю: проверить перенесённый код против бэкенда (коммиты arm-web выше), затем — merge и «закрывай»."
echo "worktree $WT, симлинк web/node_modules и .bounded остаются до закрытия."

View File

@ -22,7 +22,7 @@ for p in .claude/skills/a/SKILL.md CLAUDE.md openspec/config.yaml docs/superpowe
put "$p" "/Users/someone/projects" put "$p" "/Users/someone/projects"
done done
put openspec/specs/osv/spec.md put openspec/specs/osv/spec.md
# вложенный фронт (как web/ из arm-web): те же артефакты на второй глубине # вложенный проект (как фронт arm-core/web/): те же артефакты на второй глубине
for p in web/.claude/commands/opsx/apply.md web/.codegraph/.gitignore web/CLAUDE.md web/openspec/config.yaml \ for p in web/.claude/commands/opsx/apply.md web/.codegraph/.gitignore web/CLAUDE.md web/openspec/config.yaml \
web/docs/superpowers/plans/2026-10-08-demo.md; do web/docs/superpowers/plans/2026-10-08-demo.md; do
put "$p" "/Users/someone/projects" put "$p" "/Users/someone/projects"

View File

@ -1,135 +0,0 @@
#!/usr/bin/env bash
# Тест sync-web.sh на синтетических репозиториях arm-web и arm-core (bare origin + клон), без tea, npm и сети.
# Сценарии: синхронно; arm-core отстаёт (--check и --dry-run); arm-core правили напрямую (отказ);
# пайплайн переноса в контур (prepare-contour-snapshot.sh спрашивает / останавливается / идёт «как есть»).
# Запуск: make test-sync-web (или напрямую из корня docs).
set -euo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)"
SYNC="$HERE/sync-web.sh"
SNAPSHOT="$HERE/prepare-contour-snapshot.sh"
WORK=$(mktemp -d)
trap 'rm -rf "$WORK"' EXIT
export GIT_AUTHOR_NAME=t GIT_AUTHOR_EMAIL=t@t GIT_COMMITTER_NAME=t GIT_COMMITTER_EMAIL=t@t
fail=0
check() { if [ "$1" = 0 ]; then echo "ok: $2"; else echo "FAIL: $2" >&2; fail=1; fi; }
put() { mkdir -p "$(dirname "$1")"; printf '%s\n' "${2:-x}" > "$1"; }
commit_push() { git -C "$1" add -A; git -C "$1" commit -qm "$2"; git -C "$1" push -q origin "HEAD:$3" 2>/dev/null; git -C "$1" fetch -q origin; }
# --- синтетический arm-web: ветка test ---------------------------------------------------------------------------
WEB="$WORK/arm-web"
git init -q --bare "$WORK/arm-web.git"
git clone -q "$WORK/arm-web.git" "$WEB" 2>/dev/null
git -C "$WEB" checkout -q -b test
put "$WEB/src/a.ts" "export const a = 1;"
put "$WEB/README.md" "arm-web v1"
put "$WEB/package.json" '{"name":"web","devDependencies":{"@rsbuild/core":"1","@rsbuild/plugin-react":"1","tailwindcss":"3","daisyui":"4"}}'
for p in CLAUDE.md .claude/skills/s/SKILL.md openspec/config.yaml openspec/specs/ui/spec.md docs/superpowers/plans/p.md \
scripts/demo-prepare.sh Makefile ARCHITECTURE.md .gitignore; do put "$WEB/$p" "/Users/someone/arm-web"; done
commit_push "$WEB" "web v1" test
POINT=$(git -C "$WEB" rev-parse HEAD)
# --- синтетический arm-core: web/ = снимок arm-web v1 без служебных файлов, плюс платформенные файлы ----------------
CORE="$WORK/arm-core"
git init -q --bare "$WORK/arm-core.git"
git clone -q "$WORK/arm-core.git" "$CORE" 2>/dev/null
git -C "$CORE" checkout -q -b main
put "$CORE/web/src/a.ts" "export const a = 1;"
put "$CORE/web/README.md" "arm-web v1"
put "$CORE/web/package.json" '{"name":"web","devDependencies":{"@rsbuild/core":"1","@rsbuild/plugin-react":"1","tailwindcss":"3","daisyui":"4"}}'
printf 'venv:\n\tuv venv api/.venv\n' > "$CORE/Makefile"
for p in raisa_config.yml setup/setup.sh setup/pip.conf api/run_server.py api/requirements.txt api/.env.test \
api/contracts/schema.json; do put "$CORE/$p"; done
commit_push "$CORE" "core v1" main
COMMON=(--arm-web "$WEB" --arm-core "$CORE" --wt-root "$WORK/wt")
# --- 1. синхронно ---------------------------------------------------------------------------------------------------
rc=0; "$SYNC" --check "${COMMON[@]}" >/dev/null || rc=$?
check "$([ "$rc" = 0 ] && echo 0 || echo 1)" "синхронно: --check возвращает 0 (rc=$rc)"
rc=0; out=$("$SYNC" --dry-run "${COMMON[@]}") || rc=$?
check "$([ "$rc" = 0 ] && [ ! -d "$WORK/wt" ] && echo 0 || echo 1)" "синхронно: --dry-run ничего не делает (rc=$rc)"
# --- arm-web уходит вперёд: правка src, новый файл, README; служебные файлы тоже меняются ---------------------------
put "$WEB/src/a.ts" "export const a = 2;"
put "$WEB/src/b.ts" "export const b = 1;"
put "$WEB/README.md" "arm-web v2"
put "$WEB/CLAUDE.md" "/Users/someone/arm-web v2"
put "$WEB/scripts/demo-prepare.sh" "v2"
commit_push "$WEB" "web v2" test
HEAD7=$(git -C "$WEB" rev-parse --short=7 HEAD)
# --- 2. отстаёт: --check ----------------------------------------------------------------------------------------------
rc=0; out=$("$SYNC" --check "${COMMON[@]}" 2>&1) || rc=$?
check "$([ "$rc" = 10 ] && echo 0 || echo 1)" "отстаёт: --check возвращает 10 (rc=$rc)"
check "$(grep -q "web v2" <<<"$out" && echo 0 || echo 1)" "отстаёт: --check перечисляет непереносённые коммиты"
check "$(grep -q "${POINT:0:7}" <<<"$out" && echo 0 || echo 1)" "отстаёт: --check называет точку синхронизации"
# --- 3. отстаёт: --dry-run переносит набор в worktree -------------------------------------------------------------------
rc=0; "$SYNC" --dry-run "${COMMON[@]}" >/dev/null || rc=$?
WT="$WORK/wt/web-sync-$HEAD7/arm-core"
check "$([ "$rc" = 0 ] && [ -d "$WT" ] && echo 0 || echo 1)" "dry-run: worktree $WT создан (rc=$rc)"
check "$([ "$(git -C "$WT" rev-parse --abbrev-ref HEAD)" = "tmp/web-sync-$HEAD7" ] && echo 0 || echo 1)" "dry-run: временная ветка tmp/web-sync-$HEAD7"
check "$([ -f "$WT/.bounded" ] && echo 0 || echo 1)" "dry-run: .bounded создан"
check "$([ "$(cat "$WT/web/src/a.ts")" = "export const a = 2;" ] && echo 0 || echo 1)" "dry-run: изменённый src/a.ts перенесён"
check "$([ -f "$WT/web/src/b.ts" ] && echo 0 || echo 1)" "dry-run: новый src/b.ts перенесён"
check "$([ "$(cat "$WT/web/README.md")" = "arm-web v2" ] && echo 0 || echo 1)" "dry-run: README перенесён"
check "$([ ! -e "$WT/web/CLAUDE.md" ] && [ ! -e "$WT/web/scripts" ] && echo 0 || echo 1)" "dry-run: служебные файлы arm-web не перенесены"
git -C "$WT" add -A web >/dev/null
TREE_WT=$(git -C "$WT" write-tree)
check "$([ "$(git -C "$WT" rev-parse "$TREE_WT:web/src")" = "$(git -C "$WEB" rev-parse test:src)" ] && echo 0 || echo 1)" "dry-run: дерево web/src совпадает с arm-web test:src"
git -C "$WT" reset -q
rc=0; "$SYNC" --dry-run "${COMMON[@]}" >/dev/null 2>&1 || rc=$?
check "$([ "$rc" != 0 ] && echo 0 || echo 1)" "dry-run повторно: отказ, worktree уже существует (rc=$rc)"
git -C "$CORE" worktree remove --force "$WT"; git -C "$CORE" branch -q -D "tmp/web-sync-$HEAD7"
# --- 4. пайплайн переноса: prepare-contour-snapshot.sh видит отставание ---------------------------------------------
# сети в тесте нет: проверку сборки web/ в снимке отключаем (она покрыта test_prepare_contour_snapshot.sh)
export SNAPSHOT_SKIP_WEB_BUILD=1
export ARM_WEB="$WEB" SYNC_WEB_ARGS="--dry-run --wt-root $WORK/wt"
rc=0; out=$("$SNAPSHOT" "$CORE" "$WORK/out1" origin/main </dev/null 2>&1) || rc=$?
check "$([ "$rc" = 3 ] && echo 0 || echo 1)" "пайплайн без терминала: перенос остановлен, rc=3 (rc=$rc)"
check "$(grep -qi "остановлен" <<<"$out" && echo 0 || echo 1)" "пайплайн: сообщение «перенос остановлен до завершения синхронизации»"
check "$([ -d "$WT" ] && [ -f "$WT/web/src/b.ts" ] && echo 0 || echo 1)" "пайплайн: синхронизация запущена, worktree создан"
check "$([ ! -d "$WORK/out1/arm-core" ] && echo 0 || echo 1)" "пайплайн: снимок не создан"
git -C "$CORE" worktree remove --force "$WT"; git -C "$CORE" branch -q -D "tmp/web-sync-$HEAD7"
rc=0; out=$(printf 'y\n' | "$SNAPSHOT" "$CORE" "$WORK/out2" origin/main 2>&1) || rc=$?
check "$([ "$rc" = 0 ] && [ -f "$WORK/out2/arm-core/web/src/a.ts" ] && [ ! -e "$WORK/out2/arm-core/web/src/b.ts" ] && echo 0 || echo 1)" \
"пайплайн, ответ «y»: снимок как есть (rc=$rc)"
rc=0; out=$(printf 'n\n' | "$SNAPSHOT" "$CORE" "$WORK/out3" origin/main 2>&1) || rc=$?
check "$([ "$rc" = 3 ] && [ -d "$WT" ] && echo 0 || echo 1)" "пайплайн, ответ «n»: синхронизация запущена, перенос остановлен (rc=$rc)"
git -C "$CORE" worktree remove --force "$WT"; git -C "$CORE" branch -q -D "tmp/web-sync-$HEAD7"
rc=0; out=$("$SNAPSHOT" --web-as-is "$CORE" "$WORK/out4" origin/main </dev/null 2>&1) || rc=$?
check "$([ "$rc" = 0 ] && [ -f "$WORK/out4/arm-core/SNAPSHOT.md" ] && echo 0 || echo 1)" "пайплайн, --web-as-is: снимок без вопроса (rc=$rc)"
check "$(grep -q "отстаёт" "$WORK/out4/arm-core/SNAPSHOT.md" && echo 0 || echo 1)" "пайплайн, --web-as-is: отставание записано в SNAPSHOT.md"
unset SYNC_WEB_ARGS
# arm-web рядом нет (в тесте он есть как сосед $CORE, поэтому путь задаётся явно несуществующим)
export ARM_WEB="$WORK/no-such-arm-web"
rc=0; out=$("$SNAPSHOT" "$CORE" "$WORK/out5" origin/main </dev/null 2>&1) || rc=$?
check "$([ "$rc" = 0 ] && grep -q "arm-web не найден" <<<"$out" && echo 0 || echo 1)" "пайплайн без arm-web рядом: предупреждение, снимок сделан (rc=$rc)"
unset ARM_WEB
# --- 5. arm-core правили напрямую: точки синхронизации нет ----------------------------------------------------------
put "$CORE/web/src/a.ts" "export const a = 'direct';"
commit_push "$CORE" "core: прямая правка web" main
rc=0; out=$("$SYNC" --check "${COMMON[@]}" 2>&1) || rc=$?
check "$([ "$rc" = 20 ] && echo 0 || echo 1)" "прямая правка arm-core: --check возвращает 20 (rc=$rc)"
check "$(grep -q "прямая правка web" <<<"$out" && echo 0 || echo 1)" "прямая правка arm-core: названы коммиты arm-core по web/"
export ARM_WEB="$WEB"
rc=0; out=$("$SNAPSHOT" "$CORE" "$WORK/out6" origin/main </dev/null 2>&1) || rc=$?
check "$([ "$rc" != 0 ] && [ ! -d "$WORK/out6/arm-core" ] && echo 0 || echo 1)" "пайплайн при расхождении: снимок не делается (rc=$rc)"
unset ARM_WEB
# --- 6. вызовы tea: флаги после подкоманды ---------------------------------------------------------------------------
# tea 0.16 не принимает --login/--repo перед подкомандой («flag provided but not defined: -login», arm-ka-docs#110);
# сам tea тест не вызывает (нет сети), поэтому проверяется текст скриптов
bad=$(grep -nE '(^|[^[:alnum:]_-])tea[[:space:]]+-' "$SYNC" "$SNAPSHOT" || true)
check "$([ -z "$bad" ] && echo 0 || echo 1)" "tea: флаги идут после подкоманды${bad:+ — нарушения: $bad}"
[ "$fail" = 0 ] || exit 1
echo "ok: sync-web.sh — точка синхронизации, перенос набора, отказ при расхождении, пайплайн переноса"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 168 KiB

View File

@ -5,7 +5,7 @@
экранов приведены демонстрационные данные группы компаний «Русский Бисквит»; названия организаций, даты и суммы в вашем экранов приведены демонстрационные данные группы компаний «Русский Бисквит»; названия организаций, даты и суммы в вашем
интерфейсе могут отличаться. интерфейсе могут отличаться.
Инструкция описывает фронт `arm-web` (ветка `test`) и ядро `arm-core` (`main`) по состоянию на 20.10.2026. Как поднять Инструкция описывает АРМ КА из `arm-core` (`main`: ядро и встроенный фронт `web/`) по состоянию на 20.10.2026. Как поднять
стенд, какие учётные записи есть и как заводить дефекты — в гайде тестировщика стенд, какие учётные записи есть и как заводить дефекты — в гайде тестировщика
[`../dev/qa-guide.md`](../dev/qa-guide.md). [`../dev/qa-guide.md`](../dev/qa-guide.md).
@ -356,13 +356,17 @@ PDF» и «ОФР · PDF». PDF с одной формой не обязан с
![Просмотр ОСВ](img/17-osv-document.png) ![Просмотр ОСВ](img/17-osv-document.png)
- Строки идут в порядке файла, как в 1С: строка счёта и строки субсчетов выделены жирным и показывают свои сальдо и
обороты, строки контрагентов (аналитики) сдвинуты вправо под своим субсчётом. Строки количества и налогового учёта
(«Кол.», «НУ») из ОСВ с колонкой «Показатели» не показываются — только суммы в рублях.
- Строки ОСВ только для чтения, суммы в рублях. Колонки показываются по содержимому файла: «Субсчёт»; колонка аналитики - Строки ОСВ только для чтения, суммы в рублях. Колонки показываются по содержимому файла: «Субсчёт»; колонка аналитики
— «Контрагент», название вида аналитики из файла (например «Основные средства») или «Аналитика», если видов несколько; — «Контрагент», название вида аналитики из файла (например «Основные средства») или «Аналитика», если видов несколько;
«ИНН» — только если в ОСВ есть контрагенты; «Подразделение» — если оно есть в файле; далее сальдо на начало периода «ИНН» — только если в ОСВ есть контрагенты; «Подразделение» — если оно есть в файле; далее сальдо на начало периода
(Дебет / Кредит), обороты за период (Дебет / Кредит), сальдо на конец периода (Дебет / Кредит). Ноль показывается как (Дебет / Кредит), обороты за период (Дебет / Кредит), сальдо на конец периода (Дебет / Кредит). Ноль показывается как
«—». «—».
- Поле «Контрагент или ИНН» (или «Поиск по аналитике», если контрагентов нет) фильтрует строки, «Субсчёт» — выбор - Поле «Контрагент или ИНН» (или «Поиск по аналитике», если контрагентов нет) фильтрует строки (строки счёта и
субсчёта; «Строк: N» — число строк после фильтра. субсчетов при поиске скрываются), «Счёт / субсчёт» — выбор счёта или субсчёта: остаются он и все его субсчета;
«Строк: N» — число строк после фильтра.
- Таблица прокручивается внутри экрана, внизу закреплена строка «Итого по документу». - Таблица прокручивается внутри экрана, внизу закреплена строка «Итого по документу».
- Если у документа есть предупреждения, над таблицей показывается блок «Предупреждения (N)» (раздел 7.4). - Если у документа есть предупреждения, над таблицей показывается блок «Предупреждения (N)» (раздел 7.4).
- «Подтвердить» активна только для документа в статусе «Требуется проверка»; у автоматически подтверждённых ОСВ - «Подтвердить» активна только для документа в статусе «Требуется проверка»; у автоматически подтверждённых ОСВ
@ -801,5 +805,5 @@ USERS_ADMIN», роли не меняются.
- Документ в статусе «Ошибка» — распознавание не удалось; для PDF на локальном стенде это ожидаемо для файлов вне - Документ в статусе «Ошибка» — распознавание не удалось; для PDF на локальном стенде это ожидаемо для файлов вне
тестового набора. тестового набора.
- Кнопка «Загрузить документы» неактивна — версия не «в работе» (раздел 7.1). - Кнопка «Загрузить документы» неактивна — версия не «в работе» (раздел 7.1).
- Дефект — issue в Gitea: экраны — `RSHB/arm-web`, ответы сервера — `RSHB/arm-core`. Шаблон и как понять, чей баг, — - Дефект — issue в Gitea `RSHB/arm-core` (экраны и ответы сервера). Шаблон и как понять, чей баг, —
`qa-guide.md`, раздел 11. Приложите скриншот экрана и запрос с ответом из вкладки Network. `qa-guide.md`, раздел 11. Приложите скриншот экрана и запрос с ответом из вкладки Network.