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

Entity / Aggregate: BookingProgramEntity

Дом полей сущности «Программа бронирования застройщика» контекста crm (глоссарий «Программа бронирования»; решение 7 ТЗ: «таймеры — параметры программы бронирования»). Snapshot условий в брони (booking.md, VO BookingProgramTerms) ссылается сюда.

Назначение

BookingProgramEntity — настройки условий бронирования конкретного застройщика: платность и сумма, длительности резерва и брони, правила возврата. Примеры: «бесплатная бронь на 4 часа», «платная бронь 10 т.р./50 т.р. на 10 дней». Клиент/агент выбирает программу из списка опций при бронировании; бронь снимает иммутабельный snapshot условий, поэтому правка программы не влияет на уже созданные брони.

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

ПолеЗначение
Контекстcrm
KindAggregateRoot (настраиваемый справочник)
Source of truthcrm
Таблицаcrm.booking_programs
Tenant isolationTenantId = DeveloperCompanyId

Поля

ПолеТипОбязательностьОграничения (PK/FK/index/constraint)ИсточникОписание
IdGuidДаPKappИдентификатор программы.
TenantIdGuidДаindex ix_booking_programs_tenant_ididentity (actor застройщика)Застройщик-владелец.
NamestringДаvarchar(128), не пустое; unique ux_booking_programs_tenant_name on (TenantId, lower(Name)) where deleted_at is nullappНазвание («Бесплатная бронь 4 часа», «Платная бронь 10 дней»).
Descriptionstring?Нетvarchar(2000)appОписание условий для клиента (показывается при выборе опции).
IsPaidboolДаdefault falseappПлатная ли бронь.
PriceMoney? (owned)Нет (обязателен при IsPaid = true, check ck_booking_programs_price)контракт VO — deal.md (Amount + CurrencyId); Amount > 0; колонки price_amount/price_currency_idappСтоимость бронирования (10 000 – 50 000 ₽ в примерах ТЗ; жёстких границ платформа не навязывает — только > 0).
ReserveDurationMinutesintДа> 0 and <= 10080 (неделя), default 60appДлительность фазы «Резерв» (по умолчанию 1 час; параметр программы по решению 7).
BookingDurationHoursintДа> 0 and <= 8760 (год)appДлительность фазы «Бронь» после подтверждения (4 часа бесплатная; 240 часов = 10 дней платная).
RefundPolicystring?Нетvarchar(2000)appПорядок возврата платы за бронь (US-INV-10: «срок блокировки, порядок возврата»).
IsDefaultboolДаdefault false; unique partial ux_booking_programs_tenant_default on (TenantId) where is_default and deleted_at is nullappПрограмма, предлагаемая по умолчанию при бронировании с витрины (точка входа инвестора, решение 7).
SortOrderintДа>= 0, default 0appПорядок в списке опций бронирования.
CreatedAtDateTimeOffsetДаappСоздание.
CreatedByUserIdGuid?НетappМенеджер застройщика.
UpdatedAtDateTimeOffsetДаappДата последнего изменения.
UpdatedByUserIdGuid?НетappПоследний изменивший.
DeletedAtDateTimeOffset?НетappАрхив программы.
DeletedByUserIdGuid?НетappКто заархивировал.

Value objects

Money (owned Price) — контракт в deal.md.

Модели JSONB (versioned)

JSONB-модели не используются.

Связи

СвязьТипКлючПравило загрузкиDelete behavior
BookingEntityreference (children)bookings.program_idзагрузка при создании брони (условия — snapshot)restrict (архив вместо удаления при наличии броней)

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

ИнвариантУсловиеКогда проверяетсяОшибка
INV-BPR-1IsPaid = truePrice задан и > 0; IsPaid = falsePrice = nullпри создании/изменении программы (check ck_booking_programs_price)CRM_BOOKING_PROGRAM_PRICE_INVALID
INV-BPR-2Не более одной живой default-программы на застройщика; установка default снимает флаг с прежней в одном согласованном изменениипри установке программы по умолчанию (unique partial ux_booking_programs_tenant_default)CRM_BOOKING_PROGRAM_DEFAULT_CONFLICT
INV-BPR-3Имя уникально в рамках TenantId среди живыхunique ux_booking_programs_tenant_nameCRM_BOOKING_PROGRAM_NAME_DUPLICATE
INV-BPR-4Правка условий не затрагивает существующие брони (иммутабельный snapshot BookingProgramTerms в booking.md)правило snapshot при создании брони
INV-BPR-5У застройщика всегда есть хотя бы одна живая программа (SEED при онбординге: «Бесплатная бронь 4 часа», default)при архивировании (должна остаться ≥ 1 живая программа)CRM_BOOKING_PROGRAM_LAST

Диаграмма состояний неприменима — статусной машины нет (живая ↔ архив через soft-delete).

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

СобытиеТипКогдаSnapshot/поля
BookingProgramChangedдоменноеcreate/update/archive/set-defaultId, TenantId

Интеграционные события не публикуются: программы читаются синхронно (список опций бронирования — API CRM), условия распространяются snapshot-ом внутри booking-событий.

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

АспектЗначение
ИндексыPK; ix_booking_programs_tenant_id; ux_booking_programs_tenant_name unique partial; ux_booking_programs_tenant_default unique partial; btree (tenant_id,sort_order)
Партиционирование / retentionnone (единицы строк на застройщика)
Concurrencyоптимистическая блокировка (правка не должна затирать чужую)
Permissions / PIIcrm.booking-programs.view (участники бронирования — чтение списка опций), crm.booking-programs.manage (менеджеры застройщика). PII отсутствует.
Auditотметки создания/изменения/удаления (кто и когда), soft-delete

Целостность, конкуренция и snapshot-семантика

  • Snapshot защищает существующие брони. Правка/архивирование программы не влияет на уже созданные брони — каждая держит иммутабельный snapshot условий BookingProgramTerms (INV-BPR-4). Downstream-события брони разносят именно snapshot, а не текущее состояние программы, поэтому изменение цены/длительности не «догоняет» активные резервы.
  • Единственный default. Установка программы по умолчанию снимает флаг с прежней в одном согласованном изменении (INV-BPR-2, unique partial ux_booking_programs_tenant_default); гонка двух одновременных «сделать default» разрешается оптимистической блокировкой — побеждает первый коммит, второй перечитывает и получает CRM_BOOKING_PROGRAM_DEFAULT_CONFLICT.
  • Уникальность имени. Живые программы уникальны по имени (case-insensitive) в рамках застройщика (INV-BPR-3) → CRM_BOOKING_PROGRAM_NAME_DUPLICATE.
  • Минимум одна живая программа. При онбординге застройщика SEED заводит «Бесплатная бронь 4 часа» (default); архивирование последней живой программы запрещено (INV-BPR-5) → CRM_BOOKING_PROGRAM_LAST.
  • Список опций читается синхронно. API CRM отдаёт список живых программ для формы бронирования; при точке входа инвестора с витрины предвыбирается IsDefault. Интеграционных событий программа не публикует.

Acceptance-критерии

  • Платная без цены. Дано: IsPaid = true, Price не задан. Когда программа сохраняется. Тогда отказ CRM_BOOKING_PROGRAM_PRICE_INVALID; при IsPaid = false заданная Price — тоже нарушение INV-BPR-1.
  • Смена default. Дано: default-программа A. Когда застройщик делает default программу B. Тогда флаг у A снят, у B установлен — в одном изменении; ровно одна default.
  • Дубль имени. Дано: живая программа «Платная бронь 10 дней». Когда создаётся вторая с тем же именем. Тогда отказ CRM_BOOKING_PROGRAM_NAME_DUPLICATE.
  • Архив последней. Дано: у застройщика одна живая программа. Когда её архивируют. Тогда отказ CRM_BOOKING_PROGRAM_LAST.
  • Snapshot неизменен. Дано: бронь создана по программе. Когда программу правят/архивируют. Тогда условия этой брони (BookingProgramTerms) не меняются.

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

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

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

  • booking.md — потребитель условий (snapshot BookingProgramTerms).
  • Решение 7 ТЗ (расхождение «1 час vs 72 часа vs 4 часа/10 дней» закрыто параметрами программы) — technical-assignment.md.