47 Commits

Author SHA1 Message Date
Andrey Epifancev
67c2d457c5 docs: ledger SDD Среза 1 (находки, рулинги, итоги check-full) перед удалением рабочих каталогов
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 02:39:36 +04:00
Andrey Epifancev
7b75981d0f docs: README/CLAUDE.md Среза 1, тест маршрутов OpenAPI, tasks.md
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 01:45:13 +04:00
Andrey Epifancev
d3c2f8ab1f feat(web): страница администратора — пользователи, роли, матрица прав
Маршрут /admin (вкладки «Пользователи» и «Матрица прав») и пункт «Система → Пользователи и роли» в меню;
без USERS_ADMIN в /me — экран «Нет доступа», таблицы не монтируются и запросов к /admin/* нет, пункт меню скрыт.

Почему без Redux: данные экрана нужны только ему, после каждой правки перечитываются с сервера — хватает
локального state и маленького хука useAsyncData (отбрасывает устаревшие ответы и ответы после ухода со страницы).
Клиент один — api/client.ts (Bearer из sessionStorage, ошибки сервера -> ApiError с кодом и текстом); ошибки
показываются через App.useApp().message (корень обёрнут в antd App). Экран показывает только серверное состояние:
при отказе (409 LAST_ADMIN и др.) — уведомление и перечитывание, матрица сохраняет роли по очереди.
РАИСА появится в auth/: токен даст SSO вместо dev-входа; этот экран и клиент не меняются.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 01:04:32 +04:00
Andrey Epifancev
498450b763 feat(web): API-клиент с Bearer, dev-вход по email, загрузка /me
Токен хранится в sessionStorage (design D-9; без Redux): живёт до закрытия вкладки, все обращения к хранилищу в try/catch с запасным токеном в памяти.
apiFetch подставляет Bearer, на 401 сбрасывает токен и шлёт событие armka:unauthorized, ошибки разбирает по трём формам detail (строка / {code,message} / массив 422).
useMe не ходит в /me без токена и отбрасывает устаревшие ответы (флаг cancelled), AuthGate показывает вход, пока профиля нет.
Форма DevLogin вырезается из production-сборки по литералу process.env.NODE_ENV; через Config это не работает: process.env там заменён объектом REACT_APP_*.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 00:23:56 +04:00
Andrey Epifancev
800f3a49c4 feat(admin): пользователи, роли, матрица прав — API с аудитом и защитой LAST_ADMIN
/api/v1/admin/* под правом USERS_ADMIN (403 на всё без него): список пользователей
(поиск по email/ФИО, is_active, Page), создание (email уникален без учёта регистра →
409 EMAIL_EXISTS, роли — коды активных ролей → 422 UNKNOWN_ROLE), PATCH полей и
деактивация, PUT ролей (замена набора с diff), GET ролей с правами, GET справочника
прав (16 кодов), PUT прав роли (422 UNKNOWN_PERMISSION, granted_by/granted_at).
Аудит USER_CHANGE / USER_ROLE_CHANGE (USERS) и ROLE_PERMISSION_CHANGE (ACCESS);
повтор того же состояния аудит не пишет.

LAST_ADMIN: деактивация (в том числе себя), замена ролей и снятие USERS_ADMIN с роли
запрещены, если у активных пользователей через активные роли не останется USERS_ADMIN.
Гонки: строка пользователя/роли и строка права USERS_ADMIN блокируются FOR NO KEY
UPDATE (FOR UPDATE давал deadlock на FK-проверке вставки audit_event); тест двух
параллельных транзакций с барьером.

Задача плана Task 12, tasks.md 4.1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 00:07:07 +04:00
Andrey Epifancev
c9cd839e59 test(import): dry_run полной выгрузки ЕЦП (8400 строк) укладывается в 60 с
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-04 23:37:54 +04:00
Andrey Epifancev
c9c3b69076 feat(directories): импорт выгрузки ЕЦП — сервис, эндпоинт, аудит, файл в S3
POST /api/v1/directories/import (DIRECTORY_ADMIN, multipart: file, dry_run=true, on_existing=skip):
снимок справочника тремя запросами (load_snapshot), план ядра §13.2, применение пакетами по типу
операции; UpdateGroup/UpdateEntity — apply_group_update/apply_entity_update (R-14) с description
«импорт ЕЦП». dry_run — явный session.rollback(), без аудита и S3. Боевой прогон —
audit_event(DIRECTORY_IMPORT, after_value = отчёт) и файл uploads/imports/{event_id}/{имя} +
s3_object(uploads). IMPORT_MAX_BYTES (20 МБ) → 413; ошибки файла → 422 MISSING_COLUMNS/UNSUPPORTED_FILE.
Тест dry_run — на отдельной БД с сессией запроса как в проде (SAVEPOINT-обёртка db_session
при rollback снимает и seed теста).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-04 23:15:26 +04:00
Andrey Epifancev
7f14e70aef chore(openspec): рулинги R-14…R-16 (design #25–27), спека/план приведены к факту фикстур
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-04 22:41:41 +04:00
Andrey Epifancev
df8a5285bb feat(domain): ядро импорта справочников из выгрузки ЕЦП (§13.1–13.2)
- domain/directory_import.py: read_rows (xlsx первый лист / csv UTF-8 `;`/`,`,
  заголовки по нормализованному тексту, MISSING_COLUMNS / UNSUPPORTED_FILE),
  plan_import — чистая функция: операции плана и отчёт с кодами §13.2,
  ASSUMPTION(OQ-87..91); ИНН — через domain/inn.py.
- openpyxl==3.1.5 (+ et_xmlfile==2.0.0) в requirements.txt,
  types-openpyxl в requirements-dev.txt.
- Тесты без БД: нормализация, чтение csv/xlsx (Review Focus 1–3), каждый код
  §13.2, skip/update, фикстура «Русский Бисквит» (ФЛ в строке 12 → UNSUPPORTED_TYPE).
- План фазы 2: Task 9 отмечена; tasks.md 3.1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-04 22:29:52 +04:00
Andrey Epifancev
a04d9db478 chore(directories): отметить Task 8 (2.5) и завершение фазы 1 в плане и tasks.md
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-04 22:08:04 +04:00
Andrey Epifancev
a19875dbe3 chore(directories): отметить Task 7 (2.4) в плане и tasks.md
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-04 21:53:13 +04:00
Andrey Epifancev
347b46e32c feat(directories): ЮЛ/ИП — CRUD, перевод, вывод из состава, история, аудит
- domain/inn.py: нормализация и проверка ИНН (ЮЛ — 10, ИП — 12 цифр 0–9), ASSUMPTION(OQ-88)
- репозиторий: список ЮЛ с видимостью и фильтрами (company_group_id, outside_group, inn точно, q по name/inn),
  карточка, история (по убыванию valid_from), текущие членства, close_memberships (SCD2), алиасы
- LegalEntityService: create (INN_EXISTS, CRM_ID_EXISTS, ГК через scope → 404, филиал по умолчанию — филиал ГК),
  update (no-op без истории; снимок UPDATE), move (MOVE_TO_GROUP, закрытие членств, MEMBERSHIP_CHANGE),
  withdraw (CLOSE / REMOVE_FROM_GROUP), effective_date ≥ сегодня — ASSUMPTION(OQ-21)
- эндпоинты /directories/legal-entities: GET/POST, GET/PATCH /{id}, /{id}/move, /{id}/withdraw, /{id}/history
- тесты: unit ИНН, интеграционные сценарии спеки, Review Focus 1–3, откат мутации

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-04 21:41:17 +04:00
Andrey Epifancev
80e8905e68 feat(directories): филиалы и ГК — API, история, аудит, видимость
Репозиторий, сервис и эндпоинты /api/v1/directories/{regional-offices,company-groups}:
код GK-NNNNNN из последовательности, снимки в company_group_history, no-op PATCH
без истории и аудита, поиск q по name/crm_id/code с экранированием LIKE, видимость
по филиалу (чужая ГК = 404 как несуществующая), аудит GROUP_CHANGE/ASSIGNMENT_CHANGE.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-04 21:28:06 +04:00
Andrey Epifancev
d142c6f69e feat(domain): policy видимости справочников (§10.1, ASSUMPTION(OQ-86))
Первый модуль src/domain/ (чистые функции, без I/O, mypy strict): VisibilityScope,
scope_for/scope_of, SQL-фильтры company_groups_visible/legal_entities_visible,
проверки company_group_is_visible/legal_entity_is_visible, office_for_create
(чужой филиал → 422 OFFICE_NOT_ALLOWED). Код роли не читается; business_segment
не фильтрует (ASSUMPTION(OQ-86)). Принадлежность ЮЛ ГК филиала — подзапросом IN,
а не JOIN (число строк не меняется, нет конфликта с JOIN вызывающего).

Тесты: unit без БД (компиляция Select под PostgreSQL, все ветки) + один
integration-тест семантики фильтров и их согласованности с проверками объектов.
Отмечены шаги Task 4 в плане и 2.1 в tasks.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-04 21:17:58 +04:00
Andrey Epifancev
8eb8592452 test: фабрики справочников, фикстуры app/auth_client/seeded
Фабрики ГК/ЮЛ/ГСК/членства/пользователя (flush, без commit), фикстуры seeded/app/auth_client
в conftest (override сессии через begin_nested), test_me_and_dev_token переведён на общие
фикстуры (сочетания флагов остались на локальном _build_app), test_factories.py проверяет фабрики.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-04 21:10:41 +04:00
Andrey Epifancev
5976986e1b feat(api): транзакция на запрос, RequestAudit, доменные ошибки 404/409/422, Page
- get_async_session: одна транзакция на запрос (commit при успехе, rollback при исключении), R-1;
  объявляется только через SessionDep (scope="function") — commit до отправки ответа, одна сессия
  на все зависимости запроса; current_user и dev-эмитент переведены на SessionDep.
- DomainError/NotFoundError/ConflictError/DomainValidationError + обработчик
  {"detail": {"code", "message", ...}}; ответы HTTPException и ошибок БД не меняются.
- RequestAudit/get_request_audit: AuditService.record с user_id, ip_address, user_agent запроса.
- schemas/common.py: Page[T], PageParams (limit 1..200, по умолчанию 50; offset >= 0), ErrorDetail.
- tests/test_uow_and_errors.py: формат ошибок, пагинация, аудит, scope сессии во всех маршрутах,
  настоящий commit/rollback на временной БД, commit до http.response.start, ошибка commit -> 409.
- План: Task 2 отмечена; tasks.md: 1.2 отмечен.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-04 21:03:07 +04:00
Andrey Epifancev
458392e94d feat(model): DBML v5.14 — related_group_member.kind, audit_action +2, миграция 0002, последовательности кодов
- RelatedGroupMember.kind (related_group_kind, NOT NULL) между legal_entity_id и valid_from, как в DBML;
  частичный уникальный индекс uq_related_group_member_entity_kind_current (legal_entity_id, kind) WHERE is_current —
  ЮЛ одновременно в одной ГСК и одной ГСЗ.
- AuditAction: LEGAL_ENTITY_CHANGE (после GROUP_CHANGE), USER_CHANGE (после USER_ROLE_CHANGE).
- Последовательности company_group_code_seq / related_group_code_seq в metadata (design D-5).
- Миграция 0002_dbml_v5_14: related_group_member пересоздаётся с переносом строк (kind из группы), чтобы порядок
  колонок совпал с DBML и create_all; ALTER TYPE ADD VALUE ×2; downgrade пересоздаёт audit_action v5.13.
- DBML_VERSION = 5.14, пути к data-model-v5.14.dbml (Makefile, dbml_parity), тесты: откат на шаг, перенос строк,
  уникальность gsk+gsz, офлайн-SQL до head.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-04 20:49:32 +04:00
Andrey Epifancev
44806c8403 chore: план исполнения Среза 1 (две фазы) и рулинг R-1 в design.md
docs/superpowers/plans/2026-10-04-slice-1-directories-phase{1,2}.md — 15 задач из tasks.md change'а
slice-1-directories: модель v5.14 и инфраструктура запросов, policy, API справочников (фаза 1); импорт ЕЦП,
администрирование, экран /admin, документы и check-full (фаза 2).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-04 20:37:47 +04:00
Andrey Epifancev
f7bde3f82f chore: отметка задачи 27
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-04 01:18:12 +04:00
Andrey Epifancev
18126eeb8f docs: команды make и фактическое устройство в CLAUDE.md, выправлены формулировки tasks/plan/spec
CLAUDE.md: цели make из корня, NATS_ENABLED vs DEBUG, обмен recognition_job, аудит, аутентификация, seed,
сверка ORM-DBML, тесты; Dockerfile для dev и check-full; skip reason у fixtures.
tasks.md/plan: required_document_rule в audit_ex.py (R-4); spec data-model: 43 таблицы модели + alembic_version.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-04 01:16:56 +04:00
Andrey Epifancev
b3f7deae22 chore: отметка задачи 26
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-04 01:12:24 +04:00
Andrey Epifancev
d4a29748f5 chore: отметка задачи 25
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-04 00:59:24 +04:00
Andrey Epifancev
0b4fa3a819 chore: отметка задачи 24
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-04 00:46:54 +04:00
Andrey Epifancev
c5a5c0bf29 chore: отметка задачи 23
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-04 00:20:12 +04:00
Andrey Epifancev
a0de203d88 chore: отметка задачи 22
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 23:48:55 +04:00
Andrey Epifancev
1c51fb5459 chore: отметка задачи 21
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 23:21:30 +04:00
Andrey Epifancev
0837e18a0a chore: отметка задачи 20
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 23:01:34 +04:00
Andrey Epifancev
0c1e1b94b2 chore: отметка задачи 19
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 22:39:56 +04:00
Andrey Epifancev
64668085ab chore: отметка задачи 18
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 22:29:02 +04:00
Andrey Epifancev
aaf9490a8d chore: отметка задачи 17
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 22:14:29 +04:00
Andrey Epifancev
f4699ff86a chore: отметка задачи 16
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 21:51:55 +04:00
Andrey Epifancev
7429240f01 chore: отметка задачи 15
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 21:41:19 +04:00
Andrey Epifancev
0f800a1bd0 chore: отметка задачи 14
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 21:29:37 +04:00
Andrey Epifancev
54e072947c chore: отметка задачи 13
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 21:17:22 +04:00
Andrey Epifancev
81af5e7dd7 chore: отметка задачи 12
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 21:07:20 +04:00
Andrey Epifancev
b1f76d0a7d chore: отметка задачи 11
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 20:47:03 +04:00
Andrey Epifancev
3a057b2591 chore: отметка задачи 10
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 20:27:29 +04:00
Andrey Epifancev
dd551faf17 chore: отметка задачи 9
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 19:57:07 +04:00
Andrey Epifancev
f30494e7cf chore: отметка задачи 8
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 19:33:11 +04:00
Andrey Epifancev
842bb584ee chore: отметка задачи 7
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 19:22:33 +04:00
Andrey Epifancev
9936b0a59f chore: отметка задачи 6
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 19:05:09 +04:00
Andrey Epifancev
a272d6873a chore: отметка задачи 5
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 18:47:29 +04:00
Andrey Epifancev
12ebb515c2 chore: отметка задачи 4
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 18:37:05 +04:00
Andrey Epifancev
56ab8df3fd chore: отметка задачи 3
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 18:29:19 +04:00
Andrey Epifancev
3d1755b7fa chore: отметка задачи 2
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 18:17:39 +04:00
Andrey Epifancev
25a594503a chore: отметка задачи 1
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 18:02:43 +04:00
Andrey Epifancev
9fcc59ddea chore: план исполнения SDD из tasks.md change'а, служебные каталоги в .gitignore
docs/superpowers/plans/2026-10-03-slice-0-skeleton*.md сгенерирован 1:1 из openspec/changes/slice-0-skeleton/tasks.md
(заголовки «Task N» для скриптов subagent-driven-development, Global Constraints из спеки и CLAUDE.md).
.gitignore: .codegraph/, .superpowers/, .bounded, .DS_Store.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 17:53:56 +04:00