2026-10-04 22:08:04 +04:00

11 KiB
Raw Blame History

Tasks: Срез 1 — arm-core

Каждая задача ≤ половины дня, начинается с тестов/фикстур, заканчивается зелёным make check (интеграционные — make test-integration). Пути — от корня репозитория. Допущения в коде помечаются # ASSUMPTION(OQ-nn).

1. Модель v5.14 и инфраструктура запросов

  • 1.1 ORM и миграция 0002. api/src/models/directories.py: RelatedGroupMember.kind (enum related_group_kind, NOT NULL), индекс uq_related_group_member_entity_kind_current (legal_entity_id, kind) WHERE is_current вместо …_entity_current; enums.py: AuditAction.LEGAL_ENTITY_CHANGE (после GROUP_CHANGE), USER_CHANGE (после USER_ROLE_CHANGE); alembic/versions/0002_dbml_v5_14.py (явный DDL: колонка с заполнением из related_group, индексы, ALTER TYPE … ADD VALUE, последовательности company_group_code_seq, related_group_code_seq; downgrade пересоздаёт enum); DBML_VERSION = "5.14", Makefile DBML, dbml_parity.DBML_FILE_NAME, tests/test_migrations.HEAD, test_enums, test_check_dbml_script (фиктивная версия 5.15), _check_related_group_member_uniqueness. Проверка: make dbml-check пуст; make migration-cycle зелёный; тест «ЮЛ в ГСК и ГСЗ одновременно, вторая ГСК — IntegrityError».
  • 1.2 Единица работы, аудит, исключения, пагинация. api/src/api/dependencies/db.py:get_uow_session (commit при успехе, rollback при исключении); dependencies/audit.py:get_audit_service (ip/user-agent/user_id); common/exceptions.py: NotFoundError, ConflictError(code, message, extra), DomainValidationError(code, message) + обработчики 404/409/422 в core/exception_handlers.py (формат {"detail": {"code", "message", …}}); schemas/common.py: Page[T] {items,total,limit,offset}, параметры limit ≤ 200. Проверка: unit-тесты обработчиков; интеграционный тест через scratch_db_url: эндпоинт-заглушка с get_uow_session коммитит при 2xx и откатывает при исключении.
  • 1.3 Фабрики и клиент тестов. tests/factories.py: make_regional_office, make_company_group, make_legal_entity, make_related_group, make_membership, make_user(email, office, roles); tests/conftest.py: seeded, app, auth_client(email) -> httpx.AsyncClient с dev-токеном. Переписать test_me_and_dev_token.py на новые фикстуры. Проверка: существующие тесты зелёные.

2. Policy и справочники

  • 2.1 Policy. api/src/domain/policy.py (strict mypy): VisibilityScope, scope_for(principal), apply_scope_company_groups(stmt, scope), apply_scope_legal_entities(stmt, scope), assert_office_allowed(scope, office_id); unit-тесты без БД на все ветки (ГО, филиал, монозаёмщик, чужой филиал → OFFICE_NOT_ALLOWED).
  • 2.2 Филиалы и ГК. repositories/directories.py (запросы с scope), services/company_groups.py (создание с code из последовательности, изменение со снимком company_group_history, архив, аудит GROUP_CHANGE/ASSIGNMENT_CHANGE), schemas/directories.py, api/endpoints/directories/regional_offices.py, company_groups.py. Интеграционные тесты: сценарии спеки directories-api (ГК) и directory-policy (видимость филиала/ГО, 404 вне scope, создание из филиала).
  • 2.3 ЮЛ/ИП: CRUD, перевод, вывод, история. domain/inn.py (длина 10/12 по типу; ASSUMPTION(OQ-88)), services/legal_entities.py (создание, изменение со снимком legal_entity_history(UPDATE), move (MOVE_TO_GROUP, закрытие членств), withdraw (CLOSE/REMOVE_FROM_GROUP), effective_date ≥ today), эндпоинты legal_entities.py, аудит LEGAL_ENTITY_CHANGE + MEMBERSHIP_CHANGE. Тесты: сценарии спеки (ИНН, монозаёмщик, перевод закрывает членства, закрытие, откат).
  • 2.4 Алиасы. domain/aliases.py:normalize_alias по §3 (unit-тесты на кавычки, ОПФ, пробелы, ё), сервис + эндпоинты POST/DELETE …/aliases, конфликты ALIAS_EXISTS / ALIAS_TAKEN (с владельцем). Тесты: сценарий «Нормализация».
  • 2.5 ГСК/ГСЗ и состав. services/related_groups.py (создание с code GSK-/GSZ-, изменение, удаление пустой ASSUMPTION(OQ-34), добавление члена: та же ГК, kind группы, одна группа каждого вида → MEMBER_OF_OTHER_GROUP, повтор → MEMBER_EXISTS; исключение — SCD2), эндпоинты related_groups.py, аудит MEMBERSHIP_CHANGE. Тесты: сценарии спеки (ГСК+ГСЗ одновременно, вторая ГСК, повторное включение, непустая группа).

3. Импорт из ЕЦП CRM

  • 3.1 Чтение файла и ядро решений. openpyxl в api/requirements.txt (пин); domain/directory_import.py: read_rows(file) -> (headers, rows) для xlsx/csv, нормализация заголовков (MISSING_COLUMNS), ImportCode (StrEnum = §13.2), plan_import(rows, snapshot, on_existing) -> ImportPlan(operations, report) — чистая функция; ASSUMPTION(OQ-87..91) в коде. Unit-тесты без БД: синтетический csv, покрывающий каждый код §13.2 ровно один раз; xlsx-фикстура ../fixtures/directories/ecp-export-russkiy-biskvit.xlsx → ожидаемый план (1 ГК, 1 РФ, 18 ЮЛ/ИП, 1 ГСК с 2 членами, коды ФЛ/дубля по фактическому порядку строк).
  • 3.2 Сервис и эндпоинт. services/directory_import.py (снимок справочника одним запросом на таблицу, применение плана, dry_run → rollback, audit_event(DIRECTORY_IMPORT, after_value = отчёт), файл в S3 uploads/imports/{audit_event_id}/{name} + s3_object), POST /api/v1/directories/import (multipart, IMPORT_MAX_BYTES = 20 МБ → 413, DIRECTORY_ADMIN), schemas/directory_import.py. Интеграционные тесты: сценарии спеки directory-import (срез РБ, идемпотентность, update, LE_IN_OTHER_GROUP, dry_run без следов, MISSING_COLUMNS); S3 — через app.state.storage с заглушкой в тестах.
  • 3.3 Полная выгрузка (fixtures-тест). Тест с маркером fixtures: ARM_KA_DOCS/source/biq/ecp-export-lagal-entity.xlsx, dry_run, rows = 8400, RF_CREATED = 53, присутствие кодов, время ≤ 60 с; пропуск с причиной, если файла нет. Профилирование: при превышении — пакетные вставки (insert().values([...])).

4. Администрирование

  • 4.1 Пользователи и роли (API). services/users_admin.py (создание с проверкой lower(email), изменение, деактивация, PUT roles с diff и LAST_ADMIN), repositories/users.py (+ запросы списка), эндпоинты api/endpoints/admin/users.py, roles.py (GET roles, GET permissions, PUT roles/{code}/permissions с LAST_ADMIN и защитой is_system), аудит USER_CHANGE / USER_ROLE_CHANGE / ROLE_PERMISSION_CHANGE. Тесты: сценарии спеки users-admin (создание с ролью → /me, email в другом регистре, замена набора ролей, последний администратор, матрица → /me видит новое право).
  • 4.2 Фронт: API-клиент и dev-вход. web/src/api/client.ts (apiFetch<T>(path, init): базовый URL, Bearer из sessionStorage, 401 → очистка токена и переход на вход, ошибка {detail} → Error с текстом), web/src/api/types.ts (DTO /me, /admin/*), web/src/auth/DevLogin.tsx (поле email → POST /dev/token, только при process.env.NODE_ENV !== 'production'), web/src/auth/useMe.ts (загрузка /me, права). Проверка: make web-check; ручной сценарий в README.
  • 4.3 Фронт: страница администратора. web/src/layouts/Admin.tsx: antd Table пользователей (email, ФИО, филиал, роли — Select mode="multiple" с сохранением по PUT roles, переключатель is_active), Modal создания (email, ФИО, филиал из /directories/regional-offices, роли), таблица «роль × право» с Checkbox и кнопкой «Сохранить» (PUT roles/{code}/permissions), message.error на ошибки API, экран «нет доступа» без USERS_ADMIN; маршрут /admin в App.tsx, пункт «Система → Пользователи и роли» в Sidebar. Проверка: make web-check; самостоятельное ревью TS/React с объяснением решений в PR.

5. Завершение

  • 5.1 Документы и проверка. README.md (разделы: API справочников, импорт из ЕЦП — пошагово с dry_run, администрирование и экран /admin, ограничение «повторный seed не отзывает права»), CLAUDE.md (DBML v5.14, domain/policy.py, get_uow_session, формат ошибок, openpyxl), api/.env.example (IMPORT_MAX_BYTES), спеки openspec/specs/* после merge (archive); make check-full зелёный, вывод — в описание PR. Проверка: OpenAPI (/docs) содержит все маршруты спек.