UC-CHS-007. Администратор ведёт справочники банков и отельеров
Назначение
Администратор платформы ведёт платформенные справочники BankEntity и HotelierEntity через Admin API
шахматки (create, search/list, get by id, update, delete = архив, lookup list, lookup by id), а менеджеры
застройщика и агенты используют lookup-методы этих справочников. Административный контур — отдельный за
пределами identity (ADR-0053), обращается по m2m. Сценарий относится к контексту chessboard.
Metadata
| Поле | Значение |
|---|---|
| ID | UC-CHS-007 |
| Контекст | chessboard (см. docs/06-services/chessboard/) |
| Тип | основной |
| Статус | draft |
Актор
| Поле | Значение |
|---|---|
| Роль | Администратор платформы (запрос приходит в chessboard по m2m от админ-бэкенда) |
| Permissions / scope | chessboard.banks.manage / chessboard.hoteliers.manage — m2m-клиент админ-бэкенда (client-credentials, ADR-0053); scope = платформа. Lookup для менеджеров застройщика и агентов — chessboard.references.lookup |
| Tenant | Не применяется: справочники платформенные, не tenanted (ADR-0052 п.3) |
| Record-level | Не применяется |
Предусловие
- Админ-бэкенд аутентифицирован m2m-токеном с permission
chessboard.banks.manage/chessboard.hoteliers.manage; конечный администратор авторизован в admin API (его собственная identity, ADR-0053), его id прокидывается в actor-stamping. - Лого предварительно загружено в storage/CDN — в команду передаётся только
LogoFileId.
Триггер
| Поле | Значение |
|---|---|
| Источник | Действие администратора (admin-интерфейс → админ-бэкенд → m2m-вызов в chessboard) |
| Триггерящий элемент | Раздел «Справочники» → «Банки» / «Отельеры» → «Создать» / «Изменить» / «Архивировать» |
Основной Поток
Сценарий одинаков для банка и отельера (парные справочники одной формы); ниже — банк, дельта отельера — в A5. Ведение — простой CRUD над anemic-справочником (bank.md) без доменных событий и Kafka-публикаций (simplicity-first, ADR-0056; потребители читают lookup-API синхронно).
- Список.
GET /admin/banks?search=&page=&pageSize=— постранично активные записи (DeletedAt == null, фильтр по имени, сортировка поName); поляBankModel:Id,Name,LogoFileId,CreatedAt,UpdatedAt,Version,RowVersion. ФлагincludeArchived = trueпоказывает и архивные. - Создание.
POST /admin/banks(Name,LogoFileId). Вход:Name1..256;LogoFileId— валидный Guid или null. Имя уникально среди активных; занято →CHESSBOARD_BANK_NAME_TAKEN(INV-BNK-1; unique-индексux_banks_nameстрахует гонку). Создаётся запись (CreatedByUserId = admin-user,Version = 1). Результат —201 Created. - Правка.
PUT /admin/banks/{id}(Name,LogoFileId,RowVersion) — проверка уникальности нового имени (INV-BNK-1),Version + 1,UpdatedByUserId = admin-user. УстаревшийRowVersion→409. Результат —200 OK. - Архив («delete» в терминах формы).
DELETE /admin/banks/{id}— soft-delete:DeletedAt = now,DeletedByUserId = admin-user; физического удаления нет — на банк ссылаютсяProject.Financing.*BankIdsи сделкиcrm(INV-BNK-2). Результат —204 No Content. Ссылки остаются валидными; lookup-списки и public-проекции архивные исключают. - Карточка.
GET /admin/banks/{id}(включая архивные) →200/404. - Lookup для доменных сценариев.
GET /developers/banks/lookup?search=иGET /developers/banks/lookup/{id}(продублировано в agents area; permissionchessboard.references.lookup) — только активные (DeletedAt == null, сортировка поName), компактная модель{Id, Name, LogoFileId}; используется вкладкой «Финансирование» проекта (UC-CHS-001, INV-PRJ-7) и оформлением сделок вcrm.
Альтернативные Потоки
A1. Дубль названия
- Имя совпало с активной записью (или гонку поймал unique-индекс
ux_banks_name/ux_hoteliers_name) →CHESSBOARD_BANK_NAME_TAKEN/CHESSBOARD_HOTELIER_NAME_TAKEN(INV-BNK-1 / INV-HTL-1),409. Админ меняет название. - Имя совпало с архивной записью — создание допустимо (уникальность — среди
deleted_at is null); при желании админ восстанавливает архивную (A3).
A2. Архив отельера с активными проектами
- У отельера есть активные проекты (
HotelierId == id,DeletedAt == null) →CHESSBOARD_HOTELIER_HAS_PROJECTS(INV-HTL-2),409— FKprojects.hotelier_idфизический, архив оператора с живыми отелями сломал бы INV-PRJ-4. Админ сперва переназначает отельера в проектах. - Для банка такого guard-а нет (ссылки — jsonb без FK): архив всегда проходит, проекции фильтруют (INV-BNK-2).
A3. Восстановление из архива
POST /admin/banks/{id}/restore— guard уникальности имени среди активных (иначеCHESSBOARD_BANK_NAME_TAKEN),DeletedAt = null,DeletedByUserId = null,Version + 1.
A4. Конкурентная правка двумя админами
- Устаревший
RowVersion→409; admin-интерфейс перечитывает карточку и повторяет.
A5. Дельта отельера
- Все шаги идентичны с заменой справочника/кодов: справочник отельеров,
CHESSBOARD_HOTELIER_NAME_TAKEN, guard архива INV-HTL-2 (A2); lookup отельеров используется формой проекта для выбораHotelierId(UC-CHS-001 A2, INV-PRJ-4).
Кросс-сервисный контракт, идемпотентность и наблюдаемость
Синхронное чтение потребителями (без событий). Справочники маленькие и меняются редко, поэтому
интеграция — только синхронный lookup-API, без Kafka-реплики (simplicity-first, ADR-0056). Потребители:
catalog (название+лого банка/отельера в карточке проекта), crm (банк эскроу-счёта сделки) — резолвят
запись по id в момент построения проекции / оформления сделки.
Поведение при архиве и устаревании (что видит потребитель).
- Lookup-списки и lookup-by-id для developers/agents отдают только активные записи; архивная запись
исчезает из выбора и из public-проекции карточки проекта — на месте архивированного банка/отельера поле
просто не отображается, при этом ссылка в
Project.Financing/projects.hotelier_idостаётся валидной (INV-BNK-2 / INV-HTL-2). - Потребитель, которому нужно показать уже сосланную архивную запись (например,
crmдля банка эскроу сделки, начатой до архива), обязан сохранить снапшот названия/лого в момент связывания либо запросить запись admin-путём получения по id (он возвращает и архивные, шаг 5) — устаревание разрешается на стороне потребителя; активные lookup-ответы шахматки архивными записями не «засоряются». - Отельер защищён строже банка: архив запрещён при живых ссылающихся проектах (INV-HTL-2, FK) — «исчезновения» оператора из карточки апарт-отеля не происходит. Банк допускает архив при живых ссылках (jsonb без FK), поэтому ответственность за отображение архивного банка лежит на потребителе.
Идемпотентность записи. Создание естественно идемпотентно по имени: повторный сабмит формы с тем же
названием активной записи → CHESSBOARD_BANK_NAME_TAKEN / CHESSBOARD_HOTELIER_NAME_TAKEN (INV-BNK-1 /
INV-HTL-1; гонку страхует unique-индекс), задвоения не будет. Правка / архив / восстановление защищены
RowVersion (устаревшая версия → 409, A4): побеждает первый коммит, проигравший перечитывает карточку и
повторяет.
Наблюдаемость / НФТ. Записей — десятки, изменения редки, спец-нагрузки нет. Логировать
создание/правку/архив/восстановление с id администратора (actor-stamping) — без PII; аудит обеспечивают
Version + CreatedBy/UpdatedBy/DeletedByUserId. Метрика конфликтов имени и отказов архива
(INV-HTL-2) помогает ловить попытки нарушить целостность справочника.
Постусловие
- Справочник актуален: активные записи уникальны по имени, архивные сохранены со ссылками
(
Project.Financing.*BankIds,projects.hotelier_id, сделкиcrmне ломаются). - Lookup-списки developers/agents отдают только активные записи; public-проекция карточки проекта (название+лого банка/отельера) — тоже.
- Audit:
CreatedBy/UpdatedBy/DeletedByUserId= id администратора,Versionинкрементирован. - Kafka-событий не публиковалось (справочники читаются синхронно).
Возможные Ошибки
| Код | HTTP | Условие | Поведение клиента |
|---|---|---|---|
CHESSBOARD_BANK_NAME_TAKEN / CHESSBOARD_HOTELIER_NAME_TAKEN | 409 | INV-BNK-1 / INV-HTL-1: имя занято среди активных (A1) | Показать конфликт, предложить другое имя |
CHESSBOARD_HOTELIER_HAS_PROJECTS | 409 | INV-HTL-2: архив отельера с активными проектами (A2) | Показать список проектов, требующих переназначения |
CHESSBOARD_BANK_NOT_FOUND / CHESSBOARD_HOTELIER_NOT_FOUND | 404 | Записи нет | Обновить список |
| — (validation) | 400 | Name пустое/длиннее 256; LogoFileId не Guid | Подсветить поля |
| — (forbidden) | 403 | m2m-токен без chessboard.*.manage; lookup без chessboard.references.lookup | Ошибка доступа; не ретраить |
| — (concurrency) | 409 | RowVersion устарел (A4) | Перечитать карточку, повторить |
Доменные События
Не поднимаются — anemic-справочники без DDD-обвязки (bank.md,
hotelier.md); аудит обеспечивают Version + actor-stamping.
Kafka Интеграционные События
Не публикуются (simplicity-first, ADR-0056): банков/отельеров десятки, изменения редки; потребители
(catalog — карточка проекта, crm — банк сделки) резолвят название/лого синхронно через lookup-API.
Если потребуется реплика — BankUpsertedKafkaEvent добавляется отдельным изменением контракта (bank.md
«Доменные и интеграционные события»).
Acceptance Criteria
| # | Критерий | Проверка |
|---|---|---|
| AC1 | Create/Update/Archive/Restore/Get/Search работают для обоих справочников | CRUD-интеграционные тесты |
| AC2 | Дубль имени среди активных отклоняется и под гонкой; с архивным именем — создаётся | параллельный тест + тест архива (A1) |
| AC3 | Архив банка не ломает проекты с его id в Financing; lookup его больше не отдаёт | тест INV-BNK-2 |
| AC4 | Архив отельера с активными проектами отклоняется; после переназначения — проходит | тест INV-HTL-2 |
| AC5 | «Delete» — soft: запись остаётся в БД с DeletedAt, GET by id для админа доступен | тест soft-delete |
| AC6 | Lookup доступен developers/agents c chessboard.references.lookup, недоступен гостю | тест областей и permissions |
| AC7 | CRUD недоступен без m2m-permission chessboard.banks.manage/hoteliers.manage | тест 403 |
| AC8 | Actor-stamping фиксирует id администратора | тест audit-полей |
| AC9 | Архивная запись исчезает из активного lookup и карточки проекта; admin-путь по id всё ещё её возвращает, ссылки не ломаются | тест архивной видимости и разрешения устаревания |
| AC10 | Повторный сабмит создания с тем же именем не задваивает запись (idempotent через INV-BNK-1/INV-HTL-1) | тест повторного создания |
Обратные ссылки
Автоссылки: где используется этот документ.
- registries (1): Реестр use cases и user stories PIN
Связанные Документы
- API методы: ../api/admin/banks-crud.md, ../api/admin/hoteliers-crud.md, ../api/developers/references-lookup.md.
- Domain entities: ../domain/bank.md (INV-BNK-1/2), ../domain/hotelier.md (INV-HTL-1/2), ../domain/project.md (INV-PRJ-4, INV-PRJ-7 — потребители справочников).
- Граница административного контура: ADR-0053 (
docs/02-architecture/adr/0053-admin-identity-separation.md). - Связанные UC: UC-CHS-001 (выбор банков/отельера в проекте).