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

POST /api/v1/agent/calendar/events/{id}/start — Начало события (Planned → InProgress)

Назначение

Владелец календаря отмечает, что событие началось: встреча идёт, задача взята в работу. Событие переходит из Planned в InProgress — именно в InProgress, а не в Cancelled (исправленный баг черновика, ADR-0064 п.2). Публикуется pin.calendar.event-status-changed.v1. Дальше событие завершается (complete-event.md) или отменяется (cancel-event.md).

Metadata

ПолеЗначение
Сервис/контекстcalendar
API areaagent
Feature groupcalendar-events
Статусdraft
Документdocs/06-services/calendar/api/agent/start-event.md

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

ПроверкаЗначение
Actor typeUser (агент агентства / консультант ПИН — владелец календаря)
Auth policy / Permissionscalendar.events.manage
Scope (RBAC)организация: компания актора (агентство / ПИН)
Record-levelтолько владелец: OwnerUserId = ActorUserId; чужое событие того же tenant → CAL_EVENT_ACCESS_DENIED
Tenant isolationTenantId из токена; чужой tenant → CAL_EVENT_NOT_FOUND
Auditupdate: UpdatedByUserId = ActorUserId, UpdatedAt; история статусов — события pin.calendar.event-status-changed.v1

HTTP-контракт

ПолеЗначение
MethodPOST
Route/api/v1/agent/calendar/events/{id}/start
Success status200
Idempotency headerнет (повтор над уже начатым событием → CAL_EVENT_INVALID_STATE, состояние не портится)
Correlationсквозной trace через заголовок traceparent

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

Body отсутствует — переход определяется маршрутом.

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
ActorUserIdтокен (sub)GuidДаRequiredrecord-level проверка владельца
TenantIdтокен (tenant_id)GuidДаRequiredTenant isolation
IdrouteGuidДавалидный Guid

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

200 с событием после перехода — единичная CalendarEventModel (дом — list-events.md): Status = InProgress, инкрементированная Version.

Алгоритм

  1. Контекст и доступ. Актор — владелец; требуется calendar.events.manage.
  2. Загрузка и принадлежность. Событие по Id в tenant актора: не найдено/чужой tenant → CAL_EVENT_NOT_FOUND; другой владелец → CAL_EVENT_ACCESS_DENIED.
  3. Guards происхождения. Origin = AppointmentCAL_EVENT_MANAGED_BY_APPOINTMENT (INV-CAL-3: жизненный цикл встречи по записи ведётся операциями записи); Origin = CrmBookingCAL_EVENT_MANAGED_BY_CRM_BOOKING (INV-CAL-4).
  4. Guard статуса (INV-CAL-2). Допустим только переход Planned → InProgress. Событие уже InProgressCAL_EVENT_INVALID_STATE; Completed/CancelledCAL_EVENT_ALREADY_TERMINAL. Отношение к StartAt не проверяется: владелец вправе начать раньше или позже планового времени.
  5. Переход. В одном согласованном изменении: Status = InProgress, публикация pin.calendar.event-status-changed.v1 (Status = InProgress). Pending-напоминания не отменяются — событие ещё не завершено (правило 3 event-reminder.md действует только для Completed/Cancelled).
  6. Результат. 200 + обновлённая CalendarEventModel.

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

ТипДетали
Интеграционные событияpin.calendar.event-status-changed.v1, key = EventId, атомарно с переходом (контракт — ../../events/event-status-changed.md)
Уведомленияdownstream: notifications консюмит event-status-changed

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
ValidationError400Id — не валидный Guid«Некорректный идентификатор»no retry
UNAUTHORIZED401нет/просрочен JWTno retry
FORBIDDEN403нет права calendar.events.manageno retry
CAL_EVENT_ACCESS_DENIED403событие другого владельца в tenant актора«Нет доступа к событию»no retry
CAL_EVENT_NOT_FOUND404событие не существует / чужой tenant«Событие не найдено»no retry
CAL_EVENT_MANAGED_BY_APPOINTMENT409Origin = Appointment (INV-CAL-3)«Событие управляется записью»no retry
CAL_EVENT_MANAGED_BY_CRM_BOOKING409Origin = CrmBooking (INV-CAL-4)«Событие управляется бронью CRM»no retry
CAL_EVENT_INVALID_STATE409событие уже InProgress (переход вне матрицы, INV-CAL-2)«Событие уже начато»no retry
CAL_EVENT_ALREADY_TERMINAL409статус Completed/Cancelled (INV-CAL-7)«Событие завершено или отменено»no retry

Предусловия и постусловия

  • Предусловия. Событие существует, принадлежит актору, Origin = Manual, статус Planned.
  • Постусловия. Status = InProgress; Pending-напоминания сохранены; опубликовано ровно одно pin.calendar.event-status-changed.v1 на успешный переход; остальные поля события не изменены.

Acceptance-критерии

  • Start → InProgress (явный). Дано: Planned-событие владельца. Когда: POST …/start. Тогда: 200, Status = InProgressне Cancelled и не любой иной статус (фикс бага черновика, ADR-0064 п.2); опубликовано event-status-changed со Status = InProgress.
  • Повторный start. Дано: событие уже InProgress. Тогда: 409 CAL_EVENT_INVALID_STATE; второе event-status-changed не публикуется.
  • Start раньше планового времени. Дано: Planned-событие с StartAt через час. Тогда: 200, InProgress (плановое время не guard); Pending-напоминания остаются и сработают штатно.
  • Терминальное. Дано: событие Cancelled. Тогда: 409 CAL_EVENT_ALREADY_TERMINAL.
  • Событие записи / дедлайн брони. Дано: Origin = Appointment / CrmBooking. Тогда: 409 CAL_EVENT_MANAGED_BY_APPOINTMENT / CAL_EVENT_MANAGED_BY_CRM_BOOKING.
  • Гонка с отменой. Дано: параллельные start и cancel. Тогда: побеждает один переход; проигравший получает 409 по актуальному состоянию (CAL_EVENT_ALREADY_TERMINAL либо CAL_EVENT_INVALID_STATE).

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

  • Контракт стабилен (body нет); новые значения EventStatus в ответе клиент обязан переживать. Контракт event-status-changedContractVersion = 1, additive-эволюция.
  • Logs: TenantId, OwnerUserId, EventId, переход Planned → InProgress; metrics: счётчик переходов по статусам; traces — сквозной traceparent.

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

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

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