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

GET /api/developer/crm/booking-programs — Программы бронирования

Список программ бронирования застройщика (условия резерва/брони) для ЛК застройщика и как набор опций при бронировании. Read-only проекция booking-program.md: платность, сумма, длительности резерва и брони, правила возврата. Разграничение — по TenantId = DeveloperCompanyId (ADR-0052): застройщик видит только свои программы. Сортировка фиксированная — по SortOrder. Мутаций и событий не производит.

Назначение

Отдать держателю crm.booking-programs.view живые программы застройщика (для управления менеджером застройщика и как список опций при создании брони участниками бронирования — агентом/инвестором). Возврат — Id, Name, Description, IsPaid, Price, ReserveDurationMinutes, BookingDurationHours, RefundPolicy, IsDefault, в порядке SortOrder.

Metadata

ПолеЗначение
Сервис/контекстcrm
API areadeveloper
Feature groupbooking-programs
Статусdraft
Документdocs/06-services/crm/api/developer/list-booking-programs.md

Актор и доступ

ПроверкаЗначение
Actor typeUser — Менеджер застройщика (управление) либо участник бронирования (агент/инвестор — чтение опций)
Auth policy / Permissionscrm.booking-programs.view (JWT identity); crm.booking-programs.manage — не требуется для чтения
Scope (RBAC)организация (застройщик) — TenantId = DeveloperCompanyId
Record-levelне применяется: держатель .view видит все живые программы своего TenantId
Tenant isolationTenantId в каждом запросе (= DeveloperCompanyId); для участника бронирования DeveloperCompanyId резолвится из проекта целевого объекта недвижимости
Auditread (не sensitive): PII отсутствует (справочник условий, booking-program.md)
Доступ

Единственная проверка доступа — permission crm.booking-programs.view; record-level внутри TenantId не применяется — держатель видит все живые программы застройщика целиком. Для менеджера застройщика TenantId — его собственная компания; для участника бронирования (агента/инвестора) TenantId — не его компания, а DeveloperCompanyId, резолвится из проекта целевого объекта недвижимости.

HTTP-контракт

ПолеЗначение
MethodGET
Route/api/developer/crm/booking-programs
Success status200
Idempotency headerне применяется (read-only, безопасный метод)
Correlationсквозной trace через заголовок traceparent

Входные данные / параметры запроса

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
TenantIdidentity claimsGuidДаRequired (DeveloperCompanyId)Tenant isolation
Pagingquery{ Take, Offset }НетTake 1..200 (default 50), Offset >= 0 (default 0)

Сортировка не параметризуется: фиксированная SortOrder asc, tiebreak Name asc → Id asc (детерминизм пагинации). Основной индекс — btree (tenant_id, sort_order) (см. booking-program.md, «Индексы»). Возвращаются только живые программы (deleted_at IS NULL); архивные — исключены. Фильтров нет: список справочника мал (единицы строк на застройщика).

Пустой список — крайний случай

Пустой ответ (200, Items = []) для этого метода — не ожидаемый, а крайний случай: у застройщика после онбординга всегда есть хотя бы одна default-программа (IsDefault = true, INV-BPR-5). Это не 404 — список существует всегда, вопрос только в его наполненности.

Модель ответа — страница списка программ

Ответ — страница списка: массив элементов BookingProgramModel + метаданные пагинации (Total, Take, Offset, HasMore). Поля элемента — проекция booking-program.md (здесь только дельта списка):

BookingProgramModel

ПолеТипОбязательностьИсточникMaskingОписание
IdGuidДаBookingProgramEntity.IdИдентификатор программы (выбор опции при брони).
NamestringДаBookingProgramEntity.NameНазвание («Бесплатная бронь 4 часа», «Платная бронь 10 дней»).
Descriptionstring?НетBookingProgramEntity.DescriptionОписание условий для клиента.
IsPaidboolДаBookingProgramEntity.IsPaidПлатная ли бронь.
PriceMoneyView?НетBookingProgramEntity.Price (Money owned)Стоимость брони; null при IsPaid = false (INV-BPR-1).
ReserveDurationMinutesintДаBookingProgramEntity.ReserveDurationMinutesДлительность фазы «Резерв» (default 60, ТЗ.md:469).
BookingDurationHoursintДаBookingProgramEntity.BookingDurationHoursДлительность фазы «Бронь» после подтверждения.
RefundPolicystring?НетBookingProgramEntity.RefundPolicyПорядок возврата платы за бронь (US-INV-10).
IsDefaultboolДаBookingProgramEntity.IsDefaultПредлагается по умолчанию с витрины (решение 7).

Price наружу — MoneyView (Amount, CurrencyCode, Formatted); дом контракта Moneydeal.md. SortOrder наружу не отдаётся — он лишь задаёт порядок элементов.

Алгоритм

  1. Контекст и доступ. Читает User из JWT — менеджер застройщика (управление) либо участник бронирования (агент/инвестор, чтение опций); permission crm.booking-programs.view (иначе CRM_BOOKING_PROGRAMS_FORBIDDEN); tenant — TenantId = DeveloperCompanyId (для участника бронирования резолвится из проекта целевого объекта). Безопасный GET, идемпотентность не требуется.
  2. Проверка входа. Take ∈ 1..200, Offset >= 0 — иначе VALIDATION_FAILED. Id-фильтров нет; доступ ограничен tenant, record-level не применяется.
  3. Что читаем. Живые программы застройщика (deleted_at IS NULL) с пагинацией и фиксированной сортировкой SortOrder asc → Name asc → Id asc, по индексу (tenant_id, sort_order); проекция в BookingProgramModel (Price → MoneyView). Join не требуется — справочник самодостаточный.
  4. Бизнес-правила и маппинг. Переходов состояний нет. Маппинг поле-в-поле — таблица «Модель ответа»; Price мапится в MoneyView (или null при IsPaid = false, INV-BPR-1). SortOrder в модель не проецируется — используется только как ключ сортировки.
  5. Результат. Страница списка (Items + Total/Take/Offset) → HTTP 200. Пустой результат — 200 с пустым списком (не 404; у застройщика после онбординга всегда есть default-программа, INV-BPR-5). Условия программ распространяются наружу snapshot-ом (VO BookingProgramTerms, booking.md) внутри booking-событий (booking-created.md, booking-confirmed.md); сам список — синхронное чтение.

Диаграмма

Ошибки и failure modes

Ошибки

Метод не возвращает 404 — это список, а не карточка по идентификатору: TenantId резолвится из identity claims/проекта объекта, а не задаётся актором произвольно; пустой результат — валидный 200 Items = [], а не ошибка. Единственный отказ по доступу — 403 CRM_BOOKING_PROGRAMS_FORBIDDEN при отсутствии permission crm.booking-programs.view.

КодHTTP statusУсловиеСообщениеRetry
VALIDATION_FAILED400Take вне 1..200, Offset < 0«Проверьте параметры запроса»no retry
UNAUTHORIZED401нет/просрочен JWT«Требуется авторизация»no retry
CRM_BOOKING_PROGRAMS_FORBIDDEN403нет permission crm.booking-programs.view«Недостаточно прав»no retry

Совместимость и наблюдаемость

  • Новые optional поля ответа — additive (без breaking); удаление/переименование/смена типа поля — breaking (migration + rollout). Фиксированная сортировка SortOrder — часть контракта (изменение порядка — поведенческий breaking для UI опций).
  • Logs: developer booking-programs listed (TenantId, счётчик Total); metrics: crm_developer_booking_programs_list_total{result}, latency p95 (< 1 c, SLO списков); traces — сквозной trace через traceparent; dashboard pin-crm; runbook — docs/06-services/crm/runbooks/ (будущий путь).

Acceptance criteria

  • Given живые программы застройщика, When держатель crm.booking-programs.view запрашивает список, Then 200 со страницей BookingProgramModel в фиксированном порядке SortOrder asc → Name asc → Id asc (архивные — исключены).
  • Given платная программа (IsPaid = true), When GET, Then Price заполнен (MoneyView); при IsPaid = falsePrice = null (INV-BPR-1).
  • Given участник бронирования (агент/инвестор) с .view и целевым объектом, When GET, Then видит опции бронирования того застройщика (TenantId резолвится из проекта целевого объекта).
  • Given застройщик прошёл онбординг, When GET, Then список непуст — всегда есть default-программа (IsDefault = true, INV-BPR-5); пустой список отдаётся как 200 Items = [], а не 404.
  • Given Take вне 1..200 или Offset < 0, When GET, Then 400 VALIDATION_FAILED.
  • Given актор без crm.booking-programs.view, When GET, Then 403 CRM_BOOKING_PROGRAMS_FORBIDDEN.

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

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

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

  • Сущности: booking-program.md (дом полей, инварианты INV-BPR-1..5, индекс tenant_id, sort_order, Money), booking.md (потребитель условий, snapshot BookingProgramTerms), deal.md (контракт Money/MoneyView).
  • События жизненного цикла брони (несут snapshot условий): booking-created.md, booking-confirmed.md.
  • Контекст: README; tenancy — ADR-0052.