Перейти к основному содержимому

Entity / Aggregate: HotelierEntity

Дом полей сущности «Отельер» контекста chessboard. Проекции (карточка апарт-отеля в catalog, эконом-модель доходности апартаментов в invest-analytics) ссылаются сюда и описывают только дельту.

Назначение

HotelierEntity — платформенный справочник отельеров, операторов апарт-отелей (идентификатор, название, логотип). Проект типа ApartHotel/Hotel ссылается на отельера (Project.HotelierId, INV-PRJ-4) — оператор показывается в карточке проекта и участвует в модели доходности (управление арендой). Справочником управляет администратор платформы (admin API: создание, список/поиск, получение по id, изменение, удаление, lookup); застройщикам и агентам доступны только lookup-методы.

Классификация

ПолеЗначение
Контекстchessboard
KindСправочник (Entity — CRUD, доменной логики нет)
Source of truthchessboard
Таблицаchessboard.hoteliers
Tenant isolationНе tenanted — платформенный справочник (ADR-0052 п.3)

Поля

ПолеТипОбязательностьОграничения (PK/FK/index/constraint)ИсточникОписание
IdGuidДаPKappИдентификатор отельера.
NamestringДаvarchar(256), min 1; unique ux_hoteliers_name (lower(name)) where deleted_at is nullapp (admin API)Название оператора. Уникально среди активных (INV-HTL-1).
LogoFileIdGuid?Нетбез FK (внешний storage/CDN)app (admin API)Файл логотипа.
CreatedAtDateTimeOffsetДаindex ix_hoteliers_created_atappДата создания.
CreatedByUserIdGuid?НетappКто создал (администратор).
UpdatedAtDateTimeOffsetДаappДата изменения.
UpdatedByUserIdGuid?НетappПоследний изменивший.
DeletedAtDateTimeOffset?Нетpartial-index filterappАрхив — операция «удаление» выполняется как soft-delete (на отельера ссылаются проекты).
DeletedByUserIdGuid?НетappКто архивировал.
VersionlongДа>= 1appВерсия записи.

Связи

СвязьТипКлючDelete behavior
ProjectEntityreference (обратная)projects.hotelier_id → hoteliers.id (FK)restrict — архив запрещён при активных ссылающихся проектах (INV-HTL-2)

Инварианты и переходы состояний

ИнвариантУсловиеКогда проверяетсяОшибка
INV-HTL-1 UniqueNameName уникально среди не удалённых отельеровпри создании и переименованииCHESSBOARD_HOTELIER_NAME_TAKEN
INV-HTL-2 ArchiveGuardАрхив запрещён, пока существуют не архивированные проекты с HotelierId = Idпри архивированииCHESSBOARD_HOTELIER_HAS_PROJECTS
Статусной state-машины нет

Активен → архив через soft-delete. В отличие от bank.md (логическая ссылка без FK — архив свободен), на отельера ссылаются проекты физическим FK projects.hotelier_id, поэтому архив защищён INV-HTL-2: пока есть не архивированные проекты-владельцы, архивировать нельзя.

Жизненный цикл и операции

ОперацияАкторПредусловиеЭффектОтказ
CreateадминистраторName свободно среди активныхновая запись, Version = 1CHESSBOARD_HOTELIER_NAME_TAKEN (INV-HTL-1)
Renameадминистраторновое Name свободно среди активных (кроме себя)Name обновлён, Version += 1CHESSBOARD_HOTELIER_NAME_TAKEN; конкурентный rename проигравший — тот же код (гонка сериализуется ux_hoteliers_name)
Update logoадминистраторзапись активнаLogoFileId обновлён, Version += 1
Archive (soft-delete)администраторнет не архивированных проектов с HotelierId = Id (INV-HTL-2)DeletedAt/DeletedByUserId проставлены; исчезает из lookup и public-проекцийCHESSBOARD_HOTELIER_HAS_PROJECTS (есть ссылающиеся проекты)
Restore (возврат из архива)администраторName снова свободно среди активныхDeletedAt = null, снова в lookupCHESSBOARD_HOTELIER_NAME_TAKEN
Lookup (list/search/by-id)застройщик / агент / public-карточкаактивные отельеры в списках; by-id резолвит и архивные — для исторической карточки проекта, уже ссылающегося на отельера

Постусловие любой мутации: Version монотонно растёт, UpdatedAt/UpdatedByUserId обновлены; событий в шину нет (потребители читают синхронно, см. ниже).

Доменные и интеграционные события

Событий нет

Справочник маленький, читается lookup-API синхронно — как у bank.md. В snapshot ProjectUpsertedKafkaEvent входит только HotelierId; название/лого отельера catalog резолвит через lookup-API при построении витрины.

Кросс-контекстный контракт (lookup)

  • Что предоставляет. По HotelierId — пара Name + LogoFileId через lookup-API; актуальность read-time, реплики/события нет.
  • Кто ожидает. catalog — карточка проекта Type in (ApartHotel, Hotel) (оператор апарт-отеля); invest-analytics — эконом-модель доходности апартаментов (оператор управления арендой). Оба хранят только HotelierId и резолвят название/лого при построении проекции.
  • Обработка архивных. by-id резолвит и архивные (для исторической карточки уже ссылающегося проекта); в пикерах отельера при заведении/публикации проекта архивные исключены. Архив с живыми ссылками невозможен (INV-HTL-2), поэтому «висячих» HotelierId на активных проектах не возникает.
  • Валидация ссылки. Обязательность HotelierId для Type in (ApartHotel, Hotel) при публикации и существование отельера проверяются публикующей операцией проекта (INV-PRJ-4, project.md).

Индексы, хранение, безопасность

АспектЗначение
ИндексыPK pk_hoteliers; ux_hoteliers_name unique (lower(name)) where deleted_at is null; ix_hoteliers_created_at
Партиционирование / retentionnone — десятки строк; retention бессрочный
Concurrencyоптимистическая блокировка (правка не должна затирать чужую)
Permissions / PIICRUD — только admin API (chessboard.hoteliers.manage, ADR-0053); lookup — Developers/Agents API (chessboard.references.lookup) и public-проекция карточки проекта. PII нет
Auditверсия записи + отметки кто/когда; soft-delete

Обратные ссылки

Автоссылки: где используется этот документ.

Связанные документы