157 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
7f891ddb48 fix: extra=forbid у создания ГК/ЮЛ, имя файла без Cf, поправки README (M-1, M-2, M-4, M-8, T11.3)
- M-8: CompanyGroupCreate и LegalEntityCreate отвергают неизвестные поля (422, объект не создаётся); тесты.
- T11.3: safe_filename отбрасывает и категорию Cf (U+202E, U+200B) до обрезки длины; тесты.
- M-2: README «Ошибки API» — пример с ALIAS_TAKEN, INN_EXISTS без дополнительных полей.
- M-1: README — требование к ingress по размеру тела для /directories/import.
- M-4: README — зависимость формы пользователя от DIRECTORY_VIEW, /me не перечитывается, ASSIGNMENT_CHANGE (№35).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 02:25:12 +04:00
Andrey Epifancev
bea295d222 chore(openspec): рулинги R-23, R-24 (design #34–35), спека users-admin уточнена (I-1), поправки design #22/#28
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 02:23:31 +04:00
Andrey Epifancev
811a1d2e90 chore(openspec): рулинг R-22 (общий маппинг уникальных ограничений) в design.md
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 02:02:43 +04:00
Andrey Epifancev
070a398c65 fix(api): гонка уникальных ограничений отдаёт тот же 409 с кодом, что и pre-check (T6.5, T7.1, T8.2, T12.7, T15.3, T15.4)
Реестр имя ограничения PostgreSQL -> (код, постоянный текст) в common/constraint_conflicts.py;
integrity_error_handler для unique_violation (23505) по известному ограничению отдаёт
{"detail": {"code", "message"}}, иначе прежняя общая строка. Имя берётся из полей драйвера
(asyncpg __cause__.constraint_name, psycopg diag), не из текста ошибки; resolved_code пишется в лог.
Тесты: unit обработчика/реестра (ключи есть в Base.metadata), интеграционные гонки (ГК/ЮЛ crm_id,
ИНН, алиас, email, членство, откат и следующий запрос, FK остаётся общим 409).
T15.3: README шаг 4 импорта; T15.4: test_components_have_no_duplicated_models.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 02:02:14 +04:00
Andrey Epifancev
def3d72935 chore(openspec): design.md — поведение повторного seed приведено к факту; tasks.md 5.1/5.2 (T15.1, T15.2)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 01:56:48 +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
d241c07f54 test(admin): ветка kept_roles, различающий тест экранирования, код LAST_ADMIN (T12.1, T12.3, T12.4)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 01:29:32 +04:00
Andrey Epifancev
fda907ba2e chore(openspec): рулинг R-21 (решения Task 14) в design.md
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 01:28:13 +04:00
Andrey Epifancev
4d719ecc0e fix(web): /admin — снятие неактивной роли, слияние ответов матрицы, защита от двойной отправки (T14.1–T14.6)
T14.1: неактивная роль, уже назначенная пользователю, больше не disabled в его Select — её можно снять
(новое назначение неактивной роли по-прежнему запрещено). T14.2: матрица подставляет роли из ответов
успешных PUT до перечитывания — при неудачном перечитывании на экране сохранённое, а не прежнее.
T14.3: исправлен комментарий useAsyncData (неудачный reload оставляет прежние данные). T14.4: ref-замок
от двойного POST в окне создания. T14.5: черновик матрицы — Map вместо объекта (ключи прототипа).
T14.6: группы меню в useMemo, чтобы memo(Section) работал.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 01:22:30 +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
a7502f6315 chore(openspec): рулинг R-20 (решения Task 13) в design.md
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 00:53:56 +04:00
Andrey Epifancev
d76ca570d8 fix(web): клиент — сетевые/не-JSON ошибки, ошибка входа в prod, мелочи (T13.1–T13.3, T13.5–T13.10)
apiFetch оборачивает сбой сети в ApiError(0, NETWORK) и не-JSON тело 2xx в ApiError(BAD_RESPONSE); AbortError пробрасывается как есть, пустое тело даёт undefined.
В production-ветке входа показываются ошибка загрузки профиля и кнопка «Повторить», форма dev-входа по-прежнему вырезается из бандла литералом process.env.NODE_ENV.
Мелочи: «Сессия истекла» при 401, trim и whitespace-правило для email, центрирование по 100svh, union-типы прав, срез хвостовых слэшей в API_BASE.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 00:39:59 +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
6217e4e123 chore(openspec): рулинг R-19 (решения Task 12) в design.md
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 00:20:33 +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
4dd966b0b7 refactor(schemas): одна модель филиала в schemas/directories.py (R-13)
RegionalOfficeOut больше не дублируется в schemas/me.py: в schemas/directories.py
определены RegionalOfficeBrief (id, name, is_head_office — форма /me Среза 0) и
RegionalOfficeOut(RegionalOfficeBrief) с is_active. /me импортирует RegionalOfficeBrief,
тело ответа /me не меняется; в OpenAPI нет двух одноимённых компонентов.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-04 23:54:59 +04:00
Andrey Epifancev
83746e694e chore(openspec): рулинг R-18 (решения Task 11) в design.md
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-04 23:50:01 +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
c793b42fba test(import): вынести committed_client в conftest, добиты проверки dry_run, безопасное имя файла (T10.1–T10.4, T10.6)
- T10.6: committed_sessionmaker/committed_app/committed_client/committed_storage и FakeStorage — в conftest.py,
  приложение собирается общей _build_application (как и app).
- T10.1: dry_run не пишет в S3 (objects == {}), боевой прогон — ровно один объект.
- T10.2: dry_run на реальном xlsx РБ (21 строка, таблицы не изменились).
- T10.3: dry_run с on_existing=update не оставляет истории/аудита, имена прежние.
- T10.4: safe_filename отбрасывает управляющие символы и ограничивает длину (200) с сохранением расширения.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-04 23:35:06 +04:00
Andrey Epifancev
58075e9445 chore(openspec): рулинг R-17 (решения Task 10) в design.md
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-04 23:32:21 +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
e373dfc187 refactor(services): apply_group_update/apply_entity_update — общая логика изменения ГК и ЮЛ (R-14)
Логика изменения со снимком истории и аудитом вынесена из CompanyGroupService.update и
LegalEntityService.update в функции модуля, чтобы импорт ЕЦП (Task 10) применял UpdateGroup/
UpdateEntity той же функцией. Снимок и «новая версия» ЮЛ — snapshot_entity/touch_entity
(используются также в move/withdraw). Поведение PATCH не меняется.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-04 23:06:16 +04:00
Andrey Epifancev
1364f43c61 fix(domain): импорт — устойчивое чтение повреждённых xlsx, лимиты строк и размера (T9.1, T9.2, T9.4)
- T9.1: ошибки разбора xlsx (XML, ссылки ячеек, книга без листов, zlib) и при
  ленивой итерации листа → UNSUPPORTED_FILE «файл xlsx не читается».
- T9.2: строки читаются лениво; MAX_IMPORT_ROWS = 100 000 сырых строк (с пустыми)
  и MAX_UNCOMPRESSED_BYTES = 200 MiB распакованного zip → UNSUPPORTED_FILE.
- T9.4: номер строки csv — строка файла, с которой начинается запись.
- Тесты: перепакованные xlsx с повреждёнными частями, r="2000000", zip-бомба,
  предел строк csv, многострочное поле csv.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-04 22:44:48 +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
dd176a3c69 chore(openspec): рулинги R-12, R-13 (design #23–24)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-04 22:20:42 +04:00
Andrey Epifancev
b5a83245e7 chore(openspec): рулинг R-11 (решения Task 8), tasks.md 1.2/2.1 приведены к фактическим именам
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-04 22:14:46 +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
88a4b6d7a2 feat(directories): ГСК/ГСЗ и состав (SCD2, одна группа каждого вида на ЮЛ)
Создание с кодом GSK-/GSZ-NNNNNN, изменение name/created_date, удаление только пустой группы
(ASSUMPTION(OQ-34), закрытые строки состава удаляются вместе с группой), добавление члена
(та же ГК, kind группы, MEMBER_OF_OTHER_GROUP / MEMBER_EXISTS), исключение SCD2. Видимость
группы — через явный JOIN ГК и company_groups_visible (T4.1). Аудит MEMBERSHIP_CHANGE.

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
e3ea074e4d refactor(services): общий utc_now вместо локальных _now в сервисах справочников
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
69d641c14e chore(openspec): рулинг R-10 (решения Task 7) в design.md
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-04 21:54:52 +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
7007c51f63 feat(directories): алиасы ЮЛ с нормализацией §3
normalize_alias (lower, ё→е, без пунктуации и ОПФ), POST/DELETE
/directories/legal-entities/{id}/aliases, конфликты ALIAS_EXISTS /
ALIAS_TAKEN (с legal_entity_id владельца), EMPTY_ALIAS, аудит
LEGAL_ENTITY_CHANGE по legal_entity_alias.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-04 21:52:47 +04:00
Andrey Epifancev
e03f97946f chore(openspec): рулинг R-9 (решения Task 6) в design.md
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-04 21:47:57 +04:00
Andrey Epifancev
462a30fcf6 fix(directories): PATCH ЮЛ с прежним чужим филиалом — не 422 (I-1)
`office_for_create` применяется к `regional_office_id` только при фактической смене филиала: тело с тем же
значением — no-op (Review Focus 1), правка `name` в том же запросе проходит. Тест на ЮЛ в ГК своего филиала
с чужим `regional_office_id`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-04 21:46:57 +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
69769cc302 chore(openspec): рулинг R-8 (решения Task 5) в design.md
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-04 21:30:43 +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
d85ecd22e4 test: подавить шум build_verifier в общей фикстуре app
Закрывает T3.1: предупреждение «Включён dev-эмитент…» из src.core.auth.logger
подавляется через monkeypatch в фикстуре app. Локальный autouse в
test_me_and_dev_token.py оставлен: его _build_app вызывает build_verifier без
фикстуры app.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-04 21:22:32 +04:00
Andrey Epifancev
01187b550d chore(openspec): рулинг R-7 (policy: подзапрос IN вместо outerjoin) в design.md
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-04 21:21:12 +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
cd6cca32f2 chore(openspec): рулинг R-6 (SessionDep, scope=function) в design.md
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-04 21:04:26 +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
c80e029b96 chore(openspec): D-1 приведён к фактической миграции 0002
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-04 20:53:24 +04:00
Andrey Epifancev
3a0ee80504 chore(openspec): рулинг R-5 (миграция 0002 пересоздаёт related_group_member) в design.md
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-04 20:50:55 +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
13d11c074f chore(openspec): рулинги R-2..R-4 (SDD фаза 1, pre-flight) в design.md
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-04 20:41:12 +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