POST /api/developer/crm/booking-programs/{programId}/archive — Архивировать программу бронирования
Менеджер застройщика архивирует (soft-delete) программу бронирования: она исчезает из списка опций при
новом бронировании, но уже созданные брони продолжают жить на своём иммутабельном snapshot условий
(BookingProgramTerms, INV-BPR-4). Ключевое ограничение — INV-BPR-5: у застройщика всегда остаётся хотя
бы одна живая программа, поэтому последнюю архивировать нельзя. Дом полей, инвариантов и переходов —
booking-program.md.
Архивировать нельзя: последнюю живую программу застройщика (INV-BPR-5 → 409 CRM_BOOKING_PROGRAM_LAST) и
программу, назначенную default (INV-BPR-2 → 409 CRM_BOOKING_PROGRAM_IS_DEFAULT; сначала переназначьте
default другой программой).
Назначение
Помечает программу бронирования архивной (DeletedAt) в TenantId = DeveloperCompanyId, не затрагивая ни
существующие брони (их условия — snapshot), ни ссылку bookings.program_id (связь restrict, архив вместо
физического удаления — booking-program.md, раздел «Связи»). Вызывает
менеджер застройщика; по успеху программа выпадает из списков опций и управления.
Metadata
| Поле | Значение |
|---|---|
| Сервис/контекст | crm |
| API area | developer |
| Feature group | booking-programs |
| Статус | draft |
| Документ | docs/06-services/crm/api/developer/archive-booking-program.md |
Актор и доступ
| Проверка | Значение |
|---|---|
| Actor type | User (Менеджер застройщика в рамках своего TenantId = DeveloperCompanyId) |
| Auth policy / Permissions | crm.booking-programs.manage (Менеджер ПИН — platform-scope, тот же permission) |
| Scope (RBAC) | организация (застройщик): менеджер управляет программами своей компании |
| Record-level | BookingProgramEntity.TenantId == actor.DeveloperCompanyId (программа — справочник застройщика) |
| Tenant isolation | TenantId = DeveloperCompanyId в каждом запросе (ADR-0052) |
| Audit | delete (soft-delete: DeletedAt, DeletedByUserId = actor, инкремент Version) |
Tenant isolation обязателен — TenantId = DeveloperCompanyId в каждом запросе (ADR-0052). Программа
чужого tenant неотличима от отсутствия: 403 CRM_BOOKING_PROGRAM_FORBIDDEN.
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | POST |
| Route | /api/developer/crm/booking-programs/{programId}/archive |
| Success status | 204 (программа заархивирована; тело пустое) |
| Idempotency header | не применяется — повторный архив уже недоступной (архивной) программы → 404 (soft-delete-фильтр исключает её из живых) |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|---|---|---|---|---|
TenantId | identity claims | Guid | Да | Required; = DeveloperCompanyId | Tenant isolation |
ProgramId | route | Guid | Да | != Guid.Empty; существующая живая программа в TenantId (шаг 2) | — |
ActorUserId | identity claims | Guid | Да | JWT менеджера; используется для record-level и DeletedByUserId | — |
Тело запроса пустое: действие детерминировано (пометить программу архивной). Параметров нет.
Результат операции
Тела ответа нет — по успеху возвращается HTTP 204 No Content. Наружу никакая проекция не отдаётся: клиент по
успеху убирает программу из списка опций бронирования и списков управления. Именованной response-модели нет
(мутация без данных).
Алгоритм
- Контекст и доступ. Выполняет менеджер застройщика (
User, permissioncrm.booking-programs.manage; менеджер ПИН — platform-scope); tenant —TenantId = DeveloperCompanyId. Идемпотентность не требуется — повтор архивной программы отсекается soft-delete-фильтром (шаг 2) как404. - Проверка входа и предусловий.
ProgramId != Guid.Empty— иначеVALIDATION_FAILED;- программа существует среди живых в
TenantId(архивные исключены soft-delete-фильтромdeleted_at is null) — иначеCRM_BOOKING_PROGRAM_NOT_FOUND(повторный архив тоже даёт404); - scope:
program.TenantId == TenantId(застройщик оперирует своими программами) — иначеCRM_BOOKING_PROGRAM_FORBIDDEN; - default-guard (INV-BPR-2): программа не default (
IsDefault == false) — иначеCRM_BOOKING_PROGRAM_IS_DEFAULT(у витрины должен остаться живой default; менеджер сперва переназначает default другой программой командойSetDefault, затем архивирует эту).
- Что читаем. Берём транзакционную блокировку на уровне застройщика (по
TenantId) — она сериализует конкурентные архив/назначение default одного застройщика и детерминированно закрывает гонку INV-BPR-5 (см. шаг 7) без внешней инфраструктуры (ADR-0056: PostgreSQL-only). Затем целевую программу под блокировкой строки и счётчик живых программ застройщика (deleted_at is null) в том же снапшоте — для INV-BPR-5. - Бизнес-правила и переходы.
- INV-BPR-5 (последняя живая): живых программ должно остаться больше одной — иначе
CRM_BOOKING_PROGRAM_LAST(нельзя оставить застройщика без единственной программы; SEED «Бесплатная бронь 4 часа» — минимум); - soft-delete программы. Маппинг:
DeletedAt = now,DeletedByUserId = ActorUserId,UpdatedAt = now,UpdatedByUserId = ActorUserId,Version += 1; - существующие брони с
program_id = ProgramIdне трогаются: их условия — иммутабельный snapshotBookingProgramTerms(INV-BPR-4, booking.md); FK остаётся валидным (архив, не физическое удаление — связьrestrict); - фиксируется доменный факт
BookingProgramChanged.
- INV-BPR-5 (последняя живая): живых программ должно остаться больше одной — иначе
- Что меняется. Программа помечается архивной (
DeletedAt) и исчезает из живых списков; дочерние брони не удаляются (restrict). Конкурентный доступ — по версии строки: при параллельном архиве/назначении default той же строки запись проигрывает → перечитка иCRM_BOOKING_PROGRAM_NOT_FOUND(уже архивна) либоCRM_BOOKING_PROGRAM_LAST. - События. Интеграционных Kafka-событий нет: топика
pin.crm.booking-programне существует; условия распространяются snapshot-ом внутри booking-событий (booking-created.md), уже отправленные booking-события архивом не затрагиваются. Внутренний доменный фактBookingProgramChangedинвалидирует кэш списка опций бронирования. - Согласованность. Блокировка застройщика → счётчик живых (INV-BPR-5) → soft-delete фиксируются в одном
согласованном изменении (атомарно). Поэтому конкурентный архив двух последних разных программ одного
застройщика не может обойти проверку: второй запрос ждёт блокировку, перечитывает счётчик
= 1и получаетCRM_BOOKING_PROGRAM_LAST. - Результат. Тела ответа нет → HTTP
204 No Content. Повторный вызов на той же программе →CRM_BOOKING_PROGRAM_NOT_FOUND(404).
Диаграмма
Побочные эффекты
| Тип | Детали |
|---|---|
| Integration events | none (программы kafka не публикуют; условия уезжают snapshot-ом в booking-событиях) |
| External calls | none |
| Cache / projections / search | доменное BookingProgramChanged → инвалидация кэша списка опций бронирования (витрина/агент); архивная программа исчезает из живых списков (deleted_at not null) |
| Notifications | none |
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|---|---|---|---|
VALIDATION_FAILED | 400 | ProgramId = Guid.Empty | «Некорректный идентификатор программы» | no retry |
CRM_BOOKING_PROGRAM_NOT_FOUND | 404 | программа не найдена среди живых в TenantId (или уже архивна) | «Программа бронирования не найдена» | no retry |
CRM_BOOKING_PROGRAM_FORBIDDEN | 403 | чужой tenant / нет crm.booking-programs.manage | «Нет доступа к программе бронирования» | no retry |
CRM_BOOKING_PROGRAM_IS_DEFAULT | 409 | IsDefault = true (INV-BPR-2: сначала переназначить default) | «Нельзя архивировать программу по умолчанию — назначьте другую» | no retry (сделать default другую) |
CRM_BOOKING_PROGRAM_LAST | 409 | Count(живые) <= 1 — последняя живая программа застройщика (INV-BPR-5) | «Нельзя архивировать последнюю программу бронирования» | no retry (создать другую) |
Предусловия и постусловия
Предусловия (истинны до сценария):
- актор — менеджер застройщика (или Менеджер ПИН, platform-scope) с permission
crm.booking-programs.manage; - программа существует среди живых (
deleted_at is null) вTenantId = DeveloperCompanyIdактора; - программа не default (
IsDefault == false, INV-BPR-2); - у застройщика более одной живой программы (INV-BPR-5) — иначе архивировать нельзя.
Постусловия (гарантированы после успеха):
- программа помечена архивной (
DeletedAt,DeletedByUserId,Version+1) и исчезает из живых списков опций и управления; повторный архив той же программы даёт404; - уже созданные брони со ссылкой
program_idне изменены: их условия зафиксированы иммутабельным snapshotBookingProgramTerms(INV-BPR-4), FK остаётся валидным (связьrestrict, архив ≠ физическое удаление); - default-программа застройщика и число живых программ (≥1) сохраняют инварианты;
- интеграционных Kafka-событий не публикуется (топика
pin.crm.booking-programнет); внутренний доменный фактBookingProgramChangedинвалидирует кэш списка опций бронирования, поэтому у витрины/агента программа выпадает из выбора при следующем чтении (синхронный источник, без окна eventual-consistency наружу).
Acceptance Criteria
- Happy path. Дано: живая не-default программа, у застройщика есть ещё программы. Когда: менеджер
архивирует. Тогда:
204, программа архивна, существующие брони сохраняют условия snapshot, событий нет. - Последняя программа. Дано: у застройщика ровно одна живая программа. Когда: попытка архива. Тогда:
409 CRM_BOOKING_PROGRAM_LAST, программа остаётся живой. - Default-программа. Дано:
IsDefault == true. Когда: архив. Тогда:409 CRM_BOOKING_PROGRAM_IS_DEFAULT(сначала назначить default другую программу). - Повторный архив. Дано: программа уже архивна. Когда: повтор. Тогда:
404 CRM_BOOKING_PROGRAM_NOT_FOUND. - Чужой застройщик. Дано: программа другого tenant. Когда: архив. Тогда:
403 CRM_BOOKING_PROGRAM_FORBIDDEN(чужой tenant неотличим от отсутствия). - Гонка двух последних. Дано: у застройщика две живые программы, два параллельных запроса архивируют разные.
Когда: оба выполняются. Тогда: один —
204, второй ждёт блокировку застройщика, перечитывает счётчик= 1и получает409 CRM_BOOKING_PROGRAM_LAST(INV-BPR-5 не обойти).
Совместимость и наблюдаемость
- Route/поведение стабильны; операция терминальна (архив). Восстановление из архива — вне этого контракта
(отдельная команда/
create). Смена HTTP-статуса или семантики soft-delete — breaking (migration + rollout). - Logs:
booking-program-archive requested/succeeded/last/forbidden(TenantId,ProgramId,actor; PII отсутствует); metrics:crm_booking_program_archive_total{result}; traces — сквозной trace черезtraceparent; dashboardpin-crm; runbook —docs/06-services/crm/runbooks/(будущий путь по конвенции).
Обратные ссылки
Автоссылки: где используется этот документ.
- registries (1): Реестр API-методов PIN
Связанные документы
- Сущность и инварианты: booking-program.md (INV-BPR-5 последняя живая,
INV-BPR-4 иммутабельность snapshot, INV-BPR-2 default,
ISoftDeleteEntity, индексыux_booking_programs_tenant_default/ux_booking_programs_tenant_name, доменноеBookingProgramChanged); потребитель условий: booking.md (BookingProgramTerms); карта областей — ../../README.md. - Snapshot условий в событиях брони: booking-created.md (условия программы уезжают snapshot-ом; архив программы их не меняет).
- ADR: 0052 tenancy, 0056 simplicity-first.