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

POST /api/developer/crm/booking-programs/{programId}/set-default — Программа бронирования по умолчанию

Менеджер застройщика назначает живую программу бронирования предлагаемой по умолчанию при бронировании с витрины (точка входа инвестора «Забронировать» без явного выбора условий; см. create-booking-request.md). Реализует инвариант INV-BPR-2 (booking-program.md): не более одной живой default-программы на застройщика — установка новой снимает флаг с прежней в том же изменении, а гарантия БД — unique partial ux_booking_programs_tenant_default. Дом полей и инвариантов — booking-program.md.

Назначение

Помечает одну программу бронирования застройщика (TenantId) флагом IsDefault = true, атомарно сняв его с прежней default-программы. Вызывает менеджер застройщика; тела ответа нет.

Идемпотентность

Отдельного ключа идемпотентности нет — операция естественно идемпотентна: если целевая программа уже default, повторный вызов — no-op (мутаций и факта BookingProgramChanged нет), ответ 204.

Metadata

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

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

ПроверкаЗначение
Actor typeUser (Менеджер застройщика в рамках своего TenantId = DeveloperCompanyId)
Auth policy / Permissionscrm.booking-programs.manage (управление справочником программ застройщика)
Scope (RBAC)организация (застройщик): менеджер оперирует программами своего TenantId
Record-levelBookingProgramEntity.TenantId == actor.DeveloperCompanyId (программа принадлежит застройщику)
Tenant isolationTenantId = DeveloperCompanyId в каждом запросе (ADR-0052)
Auditupdate (UpdatedByUserId = actor на обеих затронутых программах, инкремент Version)
Доступ

Tenant isolation обязателен — TenantId = DeveloperCompanyId в каждом запросе (ADR-0052). Менеджер оперирует только программами своего застройщика; программа чужого tenant → 403 CRM_BOOKING_PROGRAM_FORBIDDEN.

HTTP-контракт

ПолеЗначение
MethodPOST
Route/api/developer/crm/booking-programs/{programId}/set-default
Success status204 (флаг установлен; тело пустое) / 204 на идемпотентный повтор (программа уже default, шаг 4)
Idempotency headerне применяется — операция естественно идемпотентна (повторная установка той же default — no-op)
Correlationсквозной trace через заголовок traceparent

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

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
TenantIdidentity claimsGuidДаRequired; = DeveloperCompanyIdTenant isolation
ProgramIdrouteGuidДа!= Guid.Empty; существующая живая программа в TenantId (шаг 2)
ActorUserIdidentity claimsGuidДаJWT менеджера; атрибуция UpdatedByUserId на обеих программах

Тело запроса пустое: действие детерминировано (IsDefault = true на ProgramId).

Модель ответа

Тело отсутствует — по успеху HTTP 204 No Content. Актуальный список программ (с новой отметкой default) клиент перечитывает отдельным list-методом; дом полей программы — booking-program.md.

Алгоритм

  1. Контекст и доступ. Выполняет менеджер застройщика (User, permission crm.booking-programs.manage); tenant — TenantId = DeveloperCompanyId. Операция естественно идемпотентна — повторная установка той же default обрабатывается как no-op (шаг 4).
  2. Проверка входа и предусловий.
    • ProgramId != Guid.Empty — иначе VALIDATION_FAILED;
    • программа существует среди живых в TenantId (архивные скрыты soft-delete-фильтром) — иначе CRM_BOOKING_PROGRAM_NOT_FOUND (default нельзя назначить неживой программе, INV-BPR-2 «живая default»);
    • scope: program.TenantId == TenantId (застройщик оперирует своими программами) — иначе CRM_BOOKING_PROGRAM_FORBIDDEN.
  3. Что читаем. Целевую программу под блокировкой строки. Затем текущую default того же tenant (partial-index ux_booking_programs_tenant_default гарантирует ≤ 1 такой строки) — тоже под блокировкой, чтобы сериализовать параллельные set-default одного застройщика. Порядок: сперва target, затем прежняя default.
  4. Бизнес-правила и переходы. Ветвление по target.IsDefault:
    • уже true → идемпотентный no-op: мутаций и событий нет, сразу результат (шаг 8);
    • false → снять флаг с прежней default (если она есть и это не target): IsDefault = false, UpdatedByUserId = ActorUserId, факт BookingProgramChanged; затем назначить целевую: IsDefault = true, UpdatedByUserId = ActorUserId, факт BookingProgramChanged (INV-BPR-2). Порядок «снять → назначить» исключает промежуточное состояние с двумя is_default = true.
  5. Что меняется. Прежняя default (если была) теряет флаг, целевая получает IsDefault = true. Конкурентный доступ — по версии строки; параллельный set-default, пропустивший заблокированную строку, упирается в unique partial ux_booking_programs_tenant_default при попытке второго is_default = trueCRM_BOOKING_PROGRAM_DEFAULT_CONFLICT.
  6. События. Интеграционных Kafka-событий нет: программы читаются синхронно (список опций бронирования — API CRM), условия распространяются snapshot-ом внутри booking-событий; топика pin.crm.booking-program-* не существует. Внутренние доменные факты BookingProgramChanged (на target и, если менялась, на прежнюю default) инвалидируют кэш списка опций.
  7. Согласованность. Снятие прежней default и назначение целевой фиксируются в одном согласованном изменении (атомарно, обе строки одного TenantId).
  8. Результат. Тела ответа нет → HTTP 204 No Content; идемпотентный повтор (target уже default) — тоже 204.

Диаграмма

Побочные эффекты

ТипДетали
Integration eventsnone (программы читаются синхронно; kafka-топика программ нет)
External callsnone
Cache / projections / searchновая default попадает в список опций бронирования (query-метод программ); прежняя теряет отметку
Notificationsnone (внутренняя настройка справочника; клиента/агента не уведомляем)

Ошибки и failure modes

Ошибки
КодHTTP statusУсловиеСообщениеRetry
VALIDATION_FAILED400ProgramId = Guid.Empty«Некорректный идентификатор программы»no retry
CRM_BOOKING_PROGRAM_NOT_FOUND404программа не найдена или архивна в TenantId«Программа бронирования не найдена»no retry
CRM_BOOKING_PROGRAM_FORBIDDEN403чужой tenant / нет crm.booking-programs.manage«Нет доступа к программе бронирования»no retry
CRM_BOOKING_PROGRAM_DEFAULT_CONFLICT409гонка параллельных set-default (нарушение unique ux_booking_programs_tenant_default)«Не удалось назначить программу по умолчанию, повторите»retry (перечитать и повторить)

Acceptance Criteria

  • Given живая программа P застройщика T с IsDefault = false и текущая default P0, When менеджер T вызывает set-default для P, Then P получает IsDefault = true, P0 теряет флаг в том же изменении, ответ 204, инвариант INV-BPR-2 соблюдён (ровно одна default у T).
  • Given у застройщика ещё нет ни одной default-программы, When set-default для живой P, Then P становится default, ответ 204, снимать флаг не с кого.
  • Given P уже является default, When повторный set-default для P, Then no-op — мутаций и факта BookingProgramChanged нет, ответ 204.
  • Given ProgramId не существует либо архивирована в T, When set-default, Then CRM_BOOKING_PROGRAM_NOT_FOUND (404), default не изменена (неживой программе default не назначается).
  • Given программа другого застройщика либо нет crm.booking-programs.manage, When set-default, Then CRM_BOOKING_PROGRAM_FORBIDDEN (403).
  • Given два параллельных set-default одного T на разные программы, When оба коммитятся, Then один выигрывает, второй нарушает unique ux_booking_programs_tenant_defaultCRM_BOOKING_PROGRAM_DEFAULT_CONFLICT (409); промежуточное состояние с двумя is_default = true не наблюдается.
  • Given ProgramId = Guid.Empty, When set-default, Then VALIDATION_FAILED (400).

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

  • Route/пустое тело стабильны; ответ 204 без тела — добавление тела было бы breaking (сейчас клиент перечитывает список). Enum-полей на входе/выходе нет. Удаление/переименование route — breaking (rollout).
  • Logs: booking-program set-default requested/succeeded/conflict/noop (ProgramId, TenantId, PreviousDefaultId; PII отсутствует); metrics: crm_booking_program_set_default_total{result}; traces — сквозной trace через traceparent; dashboard pin-crm; runbook — docs/06-services/crm/runbooks/ (будущий путь по конвенции).

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

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

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