Entity / Aggregate: BookingEntity
Дом полей агрегата «Бронирование объекта» контекста crm — модуль бронирования (решение 7 ТЗ: единый flow
с двумя точками входа — инвестор с формы «Забронировать» и агент из сделки; US-INV-06/10/21). Заявка на
бронирование становится событием ленты лида (lead.md BookingRequested) и этим агрегатом. Все
проекции (блок брони в карточке сделки, payload booking-событий) ссылаются сюда и описывают только дельту.
Назначение
BookingEntity — процесс закрепления объекта за сделкой: Резерв (короткая блокировка с таймером) →
Бронь (по программе бронирования застройщика booking-program.md, с оплатой для
платных программ — US-INV-21) → завершение (переход сделки в продажу) или истечение/отмена. Смена статусов
объекта в шахматке (Свободен → Резерв → Бронь → Продан) выполняется консюмером chessboard по
интеграционным событиям брони — CRM чужие таблицы не пишет (правила границ,
service-boundaries).
Таймеры — параметры программы бронирования (решение 7); истечение обрабатывается фоновым polling-worker-ом (ADR-0056, без TTL-магии), при истечении — сообщение-карточка в чат сделки через событие.
Классификация
| Поле | Значение |
|---|---|
| Контекст | crm |
| Kind | AggregateRoot |
| Source of truth | crm (процесс брони); статус объекта — chessboard (проекция по событиям) |
| Таблица | crm.bookings |
| Tenant isolation | TenantId = DeveloperCompanyId (бронь — процесс на объекте застройщика) |
Поля
| Поле | Тип | Обязательность | Ограничения (PK/FK/index/constraint) | Источник | Описание |
|---|---|---|---|---|---|
Id | Guid | Да | PK | app | Идентификатор бронирования. |
TenantId | Guid | Да | index ix_bookings_tenant_id | из сделки | Застройщик объекта. |
DealId | Guid | Да | FK fk_bookings_deal_id → crm.deals(id), index; unique partial ux_bookings_active_deal on (DealId) where status in ('Reserved','AwaitingPayment','Booked') | app | Сделка-владелец (deal.md). Одна активная бронь на сделку. |
PropertyObjectId | Guid | Да | index; unique partial ux_bookings_active_property_object on (PropertyObjectId) where status in ('Reserved','AwaitingPayment','Booked') | из сделки | Объект (INV-BKG-001: один активный процесс брони на объект недвижимости). |
ProgramId | Guid | Да | FK fk_bookings_program_id → crm.booking_programs(id) | app (выбор клиента/агента из опций, ТЗ.md:453) | Программа бронирования застройщика. |
ProgramTerms | BookingProgramTerms (owned) | Да | см. VO ниже | snapshot из BookingProgramEntity на момент создания | Иммутабельный снимок условий (программа могла измениться после). Источник блока «условия бронирования» US-INV-10. |
Status | BookingStatus | Да | enum как text, varchar(32), index ix_bookings_status | app / worker | См. enum и stateDiagram. |
InitiatedBy | BookingInitiator | Да | enum как text, varchar(32) | app | Точка входа (решение 7): инвестор с витрины или агент из сделки. |
RequisiteSetId | Guid? | Нет | логическая ссылка на profile (шаблон реквизитов) | app (инвестор при брони, ТЗ.md:453) | Паспортные данные/реквизиты, выбранные для брони; сам состав данных — контекст profile (docs/06-services/profile/). |
ReserveExpiresAt | DateTimeOffset | Да | index ix_bookings_reserve_expires_at (worker) | app: CreatedAt + ProgramTerms.ReserveDurationMinutes | Дедлайн резерва (ТЗ.md:469: 1 час по умолчанию). |
BookingExpiresAt | DateTimeOffset? | Нет | index where not null (worker) | app: BookedAt + ProgramTerms.BookingDuration | Дедлайн брони (после подтверждения). |
PaymentStatus | BookingPaymentStatus | Да | enum как text, varchar(32), default NotRequired | app | Оплата брони (US-INV-06/21). Эквайринг отложен (решение 9 ТЗ): подтверждение оплаты — вручную менеджером застройщика по чеку (ТЗ.md:401). |
PaymentAmount | Money? (owned) | Нет (обязателен при ProgramTerms.IsPaid) | контракт VO — deal.md, колонки payment_amount_* | snapshot ProgramTerms.Price | Сумма платного бронирования. |
PaidAt | DateTimeOffset? | Нет | — | app (подтверждение менеджером) | Дата оплаты (US-INV-06: «Оплачено» + дата). |
PaymentConfirmedByUserId | Guid? | Нет | — | app | Менеджер застройщика, подтвердивший оплату. |
ContractDocumentId | Guid? | Нет | FK fk_bookings_contract_document_id → crm.deal_documents(id) | app (застройщик, ТЗ.md:399) | Файл договора бронирования, загруженный застройщиком. |
BookedAt | DateTimeOffset? | Нет | — | app | Момент подтверждения брони (→ Booked). |
CompletedAt | DateTimeOffset? | Нет | — | app | Бронь конвертирована в продажу (Deal.StartSale). |
CancelledAt | DateTimeOffset? | Нет | — | app | Отмена. |
CancelReason | string? | Нет | varchar(1000); обязателен при Status = Cancelled (check) | app | Причина отмены. |
ExpiredAt | DateTimeOffset? | Нет | — | worker | Фактический момент истечения (резерва или брони). |
ExtensionsCount | int | Да | >= 0, default 0 | app | Сколько раз менеджер продлевал бронь (ТЗ.md:471: «подтвердите/продлите — решает менеджер застройщика»). |
CreatedAt | DateTimeOffset | Да | index ix_bookings_created_at desc | app | Создание заявки на бронирование (= старт резерва). |
CreatedByUserId | Guid? | Нет | — | app | Инициатор (инвестор/агент/Менеджер ПИН). |
UpdatedAt | DateTimeOffset | Да | — | app | Дата последнего изменения. |
UpdatedByUserId | Guid? | Нет | — | app | Последний изменивший. |
DeletedAt | DateTimeOffset? | Нет | — | app | Soft-delete (административно). |
DeletedByUserId | Guid? | Нет | — | app | Кто скрыл. |
Enum BookingStatus
| Значение | Описание |
|---|---|
Reserved | Резерв: объект временно заблокирован (chessboard ставит «Резерв» по событию), идёт отсчёт ReserveExpiresAt; клиент выбирает/заполняет реквизиты, застройщик готовит договор брони. |
AwaitingPayment | Договор брони загружен застройщиком; для платной программы клиент скачивает/подписывает/оплачивает и прикрепляет чек (ТЗ.md:455). Для бесплатной программы статус пропускается. |
Booked | Бронь подтверждена (для платной — после подтверждения оплаты менеджером, ТЗ.md:401); chessboard ставит «Бронь»; отсчёт BookingExpiresAt. |
Completed | Бронь сыграла: сделка перешла в продажу (Deal.StartSale). Терминальный. |
ReserveExpired | Резерв истёк (worker); карточка в чат «время резерва истекло, подтвердите бронь» (ТЗ.md:469). Терминальный (новая попытка — новый агрегат). |
BookingExpired | Бронь истекла (worker); карточка в чат «подтвердите/продлите бронь» (ТЗ.md:471). Терминальный, либо менеджер успел Extend до истечения. |
Cancelled | Отменена (клиент передумал / смена объекта недвижимости / отказ сделки). Терминальный. |
Enum BookingInitiator
| Значение | Описание |
|---|---|
Investor | Кнопка «Забронировать» на витрине (UC-3; prototype: форма ФИО+телефон). |
Agent | Агент из карточки сделки/лида (ТЗ.md:583). |
PinManager | Менеджер ПИН вручную (US-MGR). |
Enum BookingPaymentStatus
| Значение | Описание |
|---|---|
NotRequired | Бесплатная программа. |
Pending | Ожидается оплата/чек («Не оплачено» + кнопка «Оплатить», US-INV-06/21). |
Paid | Оплата подтверждена менеджером застройщика (PaidAt, PaymentConfirmedByUserId). |
Value objects
BookingProgramTerms (owned, snapshot программы)
Контракт полей повторяет booking-program.md; префикс колонок program_terms_.
| Поле | Тип | Обязательность | Ограничения | Колонка |
|---|---|---|---|---|
Name | string | Да | varchar(128) | program_terms_name |
IsPaid | bool | Да | — | program_terms_is_paid |
PriceAmount | decimal? | Нет (обязателен при IsPaid) | numeric(18,2) > 0 | program_terms_price_amount |
PriceCurrencyId | short | Да | ссылка на справочник валют handbook; default — валюта RUB; display-код — из CurrencySnapshot событий / lookup справочника (snapshots.md) | program_terms_price_currency_id |
ReserveDurationMinutes | int | Да | > 0 (default программы 60) | program_terms_reserve_duration_minutes |
BookingDurationHours | int | Да | > 0 (например 4 часа или 240 = 10 дней) | program_terms_booking_duration_hours |
RefundPolicy | string? | Нет | varchar(2000) | program_terms_refund_policy |
Money (owned PaymentAmount) — контракт в deal.md.
Модели JSONB (versioned)
JSONB-модели не используются.
Связи
| Связь | Тип | Ключ | Правило загрузки | Delete behavior |
|---|---|---|---|---|
DealEntity | reference (родитель процесса) | DealId | загрузка при командах (проверка статуса сделки) | restrict |
BookingProgramEntity | reference | ProgramId | загрузка при создании (snapshot) | restrict |
DealDocumentEntity (договор брони, чек) | reference | ContractDocumentId; документы типов BookingContract/SignedBookingContract/BookingPaymentProof c deal_documents.booking_id | выборка по BookingId | restrict |
Объект недвижимости (chessboard) | внешняя логическая | PropertyObjectId | статус меняет консюмер chessboard по событиям | — |
Инварианты и переходы состояний
| Инвариант | Условие | Когда проверяется | Ошибка |
|---|---|---|---|
| INV-BKG-001 | На PropertyObjectId — не более одного активного процесса брони (Reserved/AwaitingPayment/Booked); на DealId — тоже не более одного | при создании брони (unique partial ux_bookings_active_property_object, ux_bookings_active_deal) | CRM_BOOKING_PROPERTY_OBJECT_BUSY / CRM_BOOKING_DEAL_ACTIVE_EXISTS |
| INV-BKG-2 | Создание брони — только у сделки Status ∈ {Application, Booking}; сделка переводится в Booking в одном согласованном изменении | при создании брони | CRM_DEAL_INVALID_STATE |
| INV-BKG-3 | ConfirmBooking (→ Booked): для платной программы — только после PaymentStatus = Paid; для бесплатной — сразу из Reserved до ReserveExpiresAt | при подтверждении брони | CRM_BOOKING_PAYMENT_REQUIRED / CRM_BOOKING_RESERVE_EXPIRED |
| INV-BKG-4 | ConfirmPayment — только менеджер TenantId, только из AwaitingPayment, при наличии документа-чека (BookingPaymentProof со статусом Uploaded+) | при подтверждении оплаты | CRM_BOOKING_INVALID_STATE / CRM_BOOKING_PROOF_REQUIRED |
| INV-BKG-5 | Истечение — только worker-ом: Reserved/AwaitingPayment при now > ReserveExpiresAt → ReserveExpired; Booked при now > BookingExpiresAt → BookingExpired; идемпотентно (ADR-0056) | фоновая задача booking-expiration | — |
| INV-BKG-6 | Extend (продление брони): только Booked, только менеджер TenantId; BookingExpiresAt += ProgramTerms.BookingDurationHours, ExtensionsCount += 1 | при продлении брони | CRM_BOOKING_INVALID_STATE |
| INV-BKG-7 | Complete — только из Booked, в одном согласованном изменении с Deal.StartSale | при старте продажи сделки | CRM_BOOKING_INVALID_STATE |
| INV-BKG-8 | Терминальные статусы иммутабельны | все операции над бронью | CRM_BOOKING_ALREADY_TERMINAL |
Доменные и интеграционные события
| Событие | Тип | Когда | Snapshot/поля |
|---|---|---|---|
BookingCreated / BookingConfirmed / BookingExpired / BookingCancelled | доменное | соответствующие переходы | внутренние подписчики (таймлайн сделки) |
BookingCreatedEvent | интеграционное, topic pin.crm.booking-created.v1, ContractVersion = 1 | [*] → Reserved | BookingId, DealId, TenantId, PropertyObjectId, ReserveExpiresAt, ProgramTerms {Name, IsPaid, PriceAmount?, PriceCurrencyId, PriceCurrency (CurrencySnapshot), ReserveDurationMinutes, BookingDurationHours}, InitiatedBy; консюмеры: chessboard (объект недвижимости → «Резерв» + таймер), catalog, messenger, notifications; контракт — ../events/booking-created.md |
BookingPaidEvent | интеграционное, topic pin.crm.booking-paid.v1, ContractVersion = 1 | ConfirmPayment (PaymentStatus → Paid) | BookingId, DealId, TenantId, PropertyObjectId, PaymentAmount {Amount, CurrencyId} + Currency (CurrencySnapshot), PaidAt; консюмеры: messenger (карточка «договор оплачен», US-INV-16), notifications, rewards; контракт — ../events/booking-paid.md |
BookingConfirmedEvent | интеграционное, topic pin.crm.booking-confirmed.v1, ContractVersion = 1 | → Booked | BookingId, DealId, TenantId, PropertyObjectId, BookingExpiresAt; консюмеры: chessboard (объект недвижимости → «Бронь» + таймер), catalog, notifications |
BookingExpiredEvent | интеграционное, topic pin.crm.booking-expired.v1, ContractVersion = 1 | → ReserveExpired / → BookingExpired (worker) | BookingId, DealId, TenantId, PropertyObjectId, Phase (Reserve/Booking), ExpiredAt; консюмеры: chessboard (объект недвижимости → «Свободен»), messenger (карточка «время истекло, подтвердите/продлите», ТЗ.md:469/471), notifications; контракт — ../events/booking-expired.md |
BookingCancelledEvent | интеграционное, topic pin.crm.booking-cancelled.v1, ContractVersion = 1 | → Cancelled | BookingId, DealId, TenantId, PropertyObjectId, CancelReason; консюмеры: chessboard (объект недвижимости → «Свободен»), notifications |
Интеграционные события публикуются атомарно со сменой состояния агрегата.
Индексы, хранение, безопасность
| Аспект | Значение |
|---|---|
| Индексы | PK; ix_bookings_tenant_id; ix_bookings_deal_id; ux_bookings_active_deal, ux_bookings_active_property_object unique partial; ix_bookings_status; partial btree (reserve_expires_at) where status in ('Reserved','AwaitingPayment') и (booking_expires_at) where status = 'Booked' — выборки worker-а; ix_bookings_created_at desc |
| Партиционирование / retention | none (ADR-0056) |
| Concurrency | оптимистическая блокировка: гонка worker ↔ менеджер (ConfirmBooking vs Expire) — проигравший повторяет с перечиткой |
| Permissions / PII | crm.bookings.view (участники сделки), crm.bookings.manage (агент/инвестор — создание, US-INV-21 оплата), crm.bookings.decide (менеджер застройщика: договор/оплата/продление). Record-level и tenant-правила — как у сделки (deal.md). RequisiteSetId — только ссылка; PII реквизитов живёт в profile (шифрование там). |
| Audit | отметки создания/изменения/удаления (кто и когда); фазовые метки (BookedAt,PaidAt,ExpiredAt…) — иммутабельные факты; полная история — таймлайн сделки |
Межсервисные последовательности и промежуточные состояния
Бронь — источник истины процесса (crm); статусы объекта недвижимости в шахматке — проекция, которую по
booking-событиям ведёт консюмер chessboard (CRM чужие таблицы не пишет). Все цепочки — асинхронные,
at-least-once; порядок событий одной брони гарантирован ключом партиционирования BookingId.
Старт брони
В одном согласованном изменении фиксируются: запись брони Reserved, перевод сделки в Booking, строка
таймлайна BookingRequested, исходящее BookingCreatedEvent. Далее асинхронно: chessboard → объект
недвижимости «Резерв» + таймер до ReserveExpiresAt; messenger → карточка брони в чат сделки;
notifications → уведомление участникам.
:::info Окно eventual-consistency
Между коммитом в crm и обработкой события консюмером chessboard витрина/шахматка ещё показывают объект
недвижимости «Свободен» (обычно < 1 мин). Это не допускает двойного бронирования: единственность
держится на источнике (crm, INV-BKG-001, unique partial ux_bookings_active_property_object), а не на
проекции шахматки — вторая попытка в этом окне отклоняется сразу с CRM_BOOKING_PROPERTY_OBJECT_BUSY.
Что видит пользователь: в карточке сделки бронь и таймлайн-событие появляются сразу (данные crm); карточка
в чате и смена цвета объекта недвижимости в шахматке — с задержкой до обработки события.
:::
Подтверждение/оплата
ConfirmPayment → BookingPaidEvent (→ messenger карточка «договор оплачен», US-INV-16; notifications;
rewards); ConfirmBooking → BookingConfirmedEvent (→ chessboard объект недвижимости «Бронь» + таймер
BookingExpiresAt; catalog; notifications). Таймлайн-строки BookingPaid/BookingConfirmed пишутся в
тех же согласованных изменениях, поэтому в карточке сделки они видны раньше, чем downstream-эффекты
консюмеров.
Истечение/отмена
BookingExpiredEvent (worker) и BookingCancelledEvent (актор либо каскад отмены сделки) оба приводят
объект недвижимости в шахматке к «Свободен». Кросс-топик порядок между ними не гарантирован; консюмер
chessboard идемпотентен и fail-safe — повторное освобождение уже свободного объекта недвижимости = no-op.
Завершение
Переход брони → Completed происходит в одном согласованном изменении со Deal.StartSale; отдельного
освобождения объекта недвижимости нет — по завершении сделки chessboard ставит «Продан» по
DealCompletedEvent (deal.md).
Отказы, идемпотентность и конкуренция
- Идемпотентность создания. Повтор запроса на бронирование того же объекта недвижимости/сделки в
активной фазе не создаёт второй брони: структурная защита — unique partial
ux_bookings_active_property_objectиux_bookings_active_deal; исход повтора —CRM_BOOKING_PROPERTY_OBJECT_BUSYлибоCRM_BOOKING_DEAL_ACTIVE_EXISTS. Гонка двух одновременных создателей на один объект недвижимости → ровно одна броньReserved, второй запрос — busy-ошибка. - Гонка worker ↔ менеджер.
ConfirmBooking/ConfirmPayment/Extendменеджера и истечение worker-а конкурируют за одну бронь; арбитраж — оптимистическая блокировка по версии. Победитель фиксирует переход, проигравший перечитывает состояние: если истечение выиграло — менеджер видит терминальныйReserveExpired/BookingExpiredи получаетCRM_BOOKING_RESERVE_EXPIRED/CRM_BOOKING_ALREADY_TERMINAL; если выиграл менеджер — очередной проход worker-а видит уже неподходящий статус и не делает ничего (INV-BKG-5, идемпотентно). - Идемпотентность worker-а. Проход
booking-expirationнад уже терминальной бронью не порождает второгоBookingExpiredEventи не меняет статус (ADR-0056). - Частичный отказ консюмеров. Бронь валидна независимо от downstream: если
chessboardвременно недоступен, объект недвижимости в шахматке отстаёт по цвету, но процесс вcrmкорректен — событие переотправляется (at-least-once), проекция статуса — идемпотентный upsert. Еслиmessengerнедоступен, карточка в чате появляется с задержкой; прогресс всё равно виден в таймлайне сделки (данныеcrm). - Компенсации. Многошаговых распределённых транзакций нет: каждый переход брони атомарен в
crmи публикует ровно одно интеграционное событие; занятие/освобождение объекта недвижимости — следствие-эффект консюмера, а не часть транзакцииcrm(eventual). Откат резерва при отказе от брони — не компенсация, а штатный переход→ Cancelled/→ ReserveExpiredсо своим событием.
Acceptance-критерии
- Happy (создание). Дано: объект недвижимости свободен, сделка в
Application, выбрана живая программа. Когда участник сделки создаёт бронь. Тогда броньReservedсо snapshotProgramTerms, сделка →Booking, в таймлайнеBookingRequested, опубликованBookingCreatedEvent. - Занятый объект недвижимости. Дано: на объекте недвижимости уже есть активная бронь. Когда создаётся
вторая. Тогда отказ
CRM_BOOKING_PROPERTY_OBJECT_BUSY, второй записи нет. - Сделка в недопустимом статусе. Дано: сделка
Sale/Completed/Cancelled. Когда создаётся бронь. Тогда отказCRM_DEAL_INVALID_STATE. - Платная программа без оплаты. Дано: бронь платной программы,
PaymentStatus ≠ Paid. Когда пытаются подтвердить бронь. Тогда отказCRM_BOOKING_PAYMENT_REQUIRED. - Подтверждение оплаты без чека. Дано: бронь
AwaitingPaymentбез документаBookingPaymentProof. Когда менеджер подтверждает оплату. Тогда отказCRM_BOOKING_PROOF_REQUIRED. - Просроченный резерв. Дано:
now > ReserveExpiresAt, worker перевёл бронь вReserveExpired. Когда менеджер подтверждает бронь. Тогда отказCRM_BOOKING_RESERVE_EXPIRED/CRM_BOOKING_ALREADY_TERMINAL. - Конкурентное создание. Дано: два запроса на бронь одного объекта недвижимости одновременно. Когда оба
выполняются. Тогда ровно одна бронь
Reserved, второй — busy-ошибка. - Идемпотентное истечение. Дано: бронь уже
ReserveExpired. Когда worker проходит повторно. Тогда статус и число событий не меняются. - Отмена освобождает объект недвижимости. Дано: активная бронь. Когда актор отменяет с причиной. Тогда
бронь
Cancelled, опубликованBookingCancelledEvent,chessboardвозвращает объект недвижимости «Свободен».
Обратные ссылки
Автоссылки: где используется этот документ.
- Домен (CQRS) (6): Entity / Aggregate: BookingProgramEntity, Entity / Aggregate: CalendarEventEntity, Entity / Aggregate: DealDocumentEntity, Entity / Aggregate: DealEntity, Entity / Aggregate: DealTimelineEventEntity (TPH), Entity / Aggregate: PurchaseProgramEntity (+ InstallmentPresetEntity)
- API (23): GET /api/agent/crm/deals — Сделки агента (список/поиск), GET /api/agent/crm/deals/{dealId} — Карточка сделки (агент), GET /api/developer/crm/booking-programs — Программы бронирования, GET /api/developer/crm/deals/{dealId} — Карточка сделки (застройщик), GET /api/investor/crm/deals/{dealId} — Карточка сделки (инвестор), GET /api/investor/crm/deals/{dealId}/timeline — Таймлайн сделки (инвестор), GET /api/pin-manager/crm/deals — Сквозной список всех сделок платформы, GET /api/pin-manager/crm/deals/{dealId} — Карточка любой сделки (Менеджер ПИН), POST /api/agent/crm/bookings/{bookingId}/cancel — Отменить бронь, POST /api/agent/crm/bookings/{bookingId}/confirm — Подтвердить бронь (бесплатная программа), POST /api/agent/crm/deals/{dealId}/bookings — Создать бронь (Резерв) от имени клиента, POST /api/developer/crm/booking-programs — Создать программу бронирования (+11)
- Use Cases (8): UC-BKG-001. Инвестор инициирует бронирование с витрины, UC-BKG-002. Бронирование Резерв → Бронь, UC-BKG-003. Истечение резерва/брони и продление, UC-BKG-004. Оплата платного бронирования (чек + подтверждение), UC-CAL-007. Дедлайны брони CRM в календаре агента, UC-CRM-003. Движение сделки по канбан-доске, UC-CRM-005. Оформление сделки: договоры → эскроу → Росреестр → завершение, US-реестр блока «Сделка» (US-INV / US-AGT / US-DEV / US-MGR)
- События (5): Event Contract: pin.crm.booking-cancelled (BookingCancelledEvent), Event Contract: pin.crm.booking-confirmed (BookingConfirmedEvent), Event Contract: pin.crm.booking-created (BookingCreatedEvent), Event Contract: pin.crm.booking-expired (BookingExpiredEvent), Event Contract: pin.crm.booking-paid (BookingPaidEvent)
- Консюмеры (2): Consumer: Calendar CRM Booking Sync (calendar-crm-booking-events), Consumer: CRM PropertyObject Status (pin-chessboard-crm-property-object-status)
- Фоновые задачи (1): Background Job: BookingExpirationJob
- Саги (1): Saga «Booking» (Резерв → Бронь → передача в продажу)
- index.md (1): CRM Service
- registries (2): Реестр инвариантов PIN, Реестр сущностей PIN
Связанные документы
- booking-program.md — источник условий; deal.md — родительский процесс; deal-document.md — договор брони/чек; purchase-program.md — способ оплаты сделки (не путать с оплатой брони).
- Статусы объекта недвижимости —
docs/06-services/chessboard/(консюмер booking-событий, будущий путь по конвенции). - ADR-0056 (polling-worker), решение 7 и 9 ТЗ (две точки входа; эквайринг отложен).