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 area | developer |
| Feature group | booking-programs |
| Статус | draft |
| Документ | docs/06-services/crm/api/developer/list-booking-programs.md |
Актор и доступ
| Проверка | Значение |
|---|---|
| Actor type | User — Менеджер застройщика (управление) либо участник бронирования (агент/инвестор — чтение опций) |
| Auth policy / Permissions | crm.booking-programs.view (JWT identity); crm.booking-programs.manage — не требуется для чтения |
| Scope (RBAC) | организация (застройщик) — TenantId = DeveloperCompanyId |
| Record-level | не применяется: держатель .view видит все живые программы своего TenantId |
| Tenant isolation | TenantId в каждом запросе (= DeveloperCompanyId); для участника бронирования DeveloperCompanyId резолвится из проекта целевого объекта недвижимости |
| Audit | read (не sensitive): PII отсутствует (справочник условий, booking-program.md) |
Единственная проверка доступа — permission crm.booking-programs.view; record-level внутри TenantId не
применяется — держатель видит все живые программы застройщика целиком. Для менеджера застройщика TenantId
— его собственная компания; для участника бронирования (агента/инвестора) TenantId — не его компания, а
DeveloperCompanyId, резолвится из проекта целевого объекта недвижимости.
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | GET |
| Route | /api/developer/crm/booking-programs |
| Success status | 200 |
| Idempotency header | не применяется (read-only, безопасный метод) |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|---|---|---|---|---|
TenantId | identity claims | Guid | Да | Required (DeveloperCompanyId) | Tenant isolation |
Paging | query | { 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 | Описание |
|---|---|---|---|---|---|
Id | Guid | Да | BookingProgramEntity.Id | — | Идентификатор программы (выбор опции при брони). |
Name | string | Да | BookingProgramEntity.Name | — | Название («Бесплатная бронь 4 часа», «Платная бронь 10 дней»). |
Description | string? | Нет | BookingProgramEntity.Description | — | Описание условий для клиента. |
IsPaid | bool | Да | BookingProgramEntity.IsPaid | — | Платная ли бронь. |
Price | MoneyView? | Нет | BookingProgramEntity.Price (Money owned) | — | Стоимость брони; null при IsPaid = false (INV-BPR-1). |
ReserveDurationMinutes | int | Да | BookingProgramEntity.ReserveDurationMinutes | — | Длительность фазы «Резерв» (default 60, ТЗ.md:469). |
BookingDurationHours | int | Да | BookingProgramEntity.BookingDurationHours | — | Длительность фазы «Бронь» после подтверждения. |
RefundPolicy | string? | Нет | BookingProgramEntity.RefundPolicy | — | Порядок возврата платы за бронь (US-INV-10). |
IsDefault | bool | Да | BookingProgramEntity.IsDefault | — | Предлагается по умолчанию с витрины (решение 7). |
Price наружу — MoneyView (Amount, CurrencyCode, Formatted); дом контракта Money —
deal.md. SortOrder наружу не отдаётся — он лишь задаёт порядок элементов.
Алгоритм
- Контекст и доступ. Читает
Userиз JWT — менеджер застройщика (управление) либо участник бронирования (агент/инвестор, чтение опций); permissioncrm.booking-programs.view(иначеCRM_BOOKING_PROGRAMS_FORBIDDEN); tenant —TenantId = DeveloperCompanyId(для участника бронирования резолвится из проекта целевого объекта). БезопасныйGET, идемпотентность не требуется. - Проверка входа.
Take ∈ 1..200,Offset >= 0— иначеVALIDATION_FAILED. Id-фильтров нет; доступ ограничен tenant, record-level не применяется. - Что читаем. Живые программы застройщика (
deleted_at IS NULL) с пагинацией и фиксированной сортировкойSortOrder asc → Name asc → Id asc, по индексу (tenant_id,sort_order); проекция вBookingProgramModel(Price → MoneyView). Join не требуется — справочник самодостаточный. - Бизнес-правила и маппинг. Переходов состояний нет. Маппинг поле-в-поле — таблица «Модель ответа»;
Priceмапится вMoneyView(илиnullприIsPaid = false, INV-BPR-1).SortOrderв модель не проецируется — используется только как ключ сортировки. - Результат. Страница списка (
Items+Total/Take/Offset) → HTTP200. Пустой результат —200с пустым списком (не 404; у застройщика после онбординга всегда есть default-программа, INV-BPR-5). Условия программ распространяются наружу snapshot-ом (VOBookingProgramTerms, 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_FAILED | 400 | Take вне 1..200, Offset < 0 | «Проверьте параметры запроса» | no retry |
UNAUTHORIZED | 401 | нет/просрочен JWT | «Требуется авторизация» | no retry |
CRM_BOOKING_PROGRAMS_FORBIDDEN | 403 | нет 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; dashboardpin-crm; runbook —docs/06-services/crm/runbooks/(будущий путь).
Acceptance criteria
- Given живые программы застройщика, When держатель
crm.booking-programs.viewзапрашивает список, Then200со страницейBookingProgramModelв фиксированном порядкеSortOrder asc → Name asc → Id asc(архивные — исключены). - Given платная программа (
IsPaid = true), When GET, ThenPriceзаполнен (MoneyView); приIsPaid = false—Price = 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, Then400 VALIDATION_FAILED. - Given актор без
crm.booking-programs.view, When GET, Then403 CRM_BOOKING_PROGRAMS_FORBIDDEN.
Обратные ссылки
Автоссылки: где используется этот документ.
- registries (1): Реестр API-методов PIN
Связанные документы
- Сущности: booking-program.md (дом полей, инварианты INV-BPR-1..5,
индекс
tenant_id, sort_order,Money), booking.md (потребитель условий, snapshotBookingProgramTerms), deal.md (контрактMoney/MoneyView). - События жизненного цикла брони (несут snapshot условий): booking-created.md, booking-confirmed.md.
- Контекст: README; tenancy — ADR-0052.