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).
| Поле | Значение |
|---|
| Сервис/контекст | calendar |
| API area | agent |
| Feature group | calendar-events |
| Статус | draft |
| Документ | docs/06-services/calendar/api/agent/start-event.md |
Актор и доступ
| Проверка | Значение |
|---|
| Actor type | User (агент агентства / консультант ПИН — владелец календаря) |
| Auth policy / Permissions | calendar.events.manage |
| Scope (RBAC) | организация: компания актора (агентство / ПИН) |
| Record-level | только владелец: OwnerUserId = ActorUserId; чужое событие того же tenant → CAL_EVENT_ACCESS_DENIED |
| Tenant isolation | TenantId из токена; чужой tenant → CAL_EVENT_NOT_FOUND |
| Audit | update: UpdatedByUserId = ActorUserId, UpdatedAt; история статусов — события pin.calendar.event-status-changed.v1 |
HTTP-контракт
| Поле | Значение |
|---|
| Method | POST |
| Route | /api/v1/agent/calendar/events/{id}/start |
| Success status | 200 |
| Idempotency header | нет (повтор над уже начатым событием → CAL_EVENT_INVALID_STATE, состояние не портится) |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
Body отсутствует — переход определяется маршрутом.
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|
ActorUserId | токен (sub) | Guid | Да | Required | record-level проверка владельца |
TenantId | токен (tenant_id) | Guid | Да | Required | Tenant isolation |
Id | route | Guid | Да | валидный Guid | — |
Модель ответа — CalendarEventModel
200 с событием после перехода — единичная CalendarEventModel (дом — list-events.md):
Status = InProgress, инкрементированная Version.
Алгоритм
- Контекст и доступ. Актор — владелец; требуется
calendar.events.manage.
- Загрузка и принадлежность. Событие по
Id в tenant актора: не найдено/чужой tenant →
CAL_EVENT_NOT_FOUND; другой владелец → CAL_EVENT_ACCESS_DENIED.
- Guards происхождения.
Origin = Appointment → CAL_EVENT_MANAGED_BY_APPOINTMENT (INV-CAL-3:
жизненный цикл встречи по записи ведётся операциями записи); Origin = CrmBooking →
CAL_EVENT_MANAGED_BY_CRM_BOOKING (INV-CAL-4).
- Guard статуса (INV-CAL-2). Допустим только переход
Planned → InProgress. Событие уже InProgress
→ CAL_EVENT_INVALID_STATE; Completed/Cancelled → CAL_EVENT_ALREADY_TERMINAL. Отношение к
StartAt не проверяется: владелец вправе начать раньше или позже планового времени.
- Переход. В одном согласованном изменении:
Status = InProgress, публикация
pin.calendar.event-status-changed.v1 (Status = InProgress). Pending-напоминания не отменяются —
событие ещё не завершено (правило 3 event-reminder.md действует только
для Completed/Cancelled).
- Результат.
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 |
|---|
ValidationError | 400 | Id — не валидный Guid | «Некорректный идентификатор» | no retry |
UNAUTHORIZED | 401 | нет/просрочен JWT | — | no retry |
FORBIDDEN | 403 | нет права calendar.events.manage | — | no retry |
CAL_EVENT_ACCESS_DENIED | 403 | событие другого владельца в tenant актора | «Нет доступа к событию» | no retry |
CAL_EVENT_NOT_FOUND | 404 | событие не существует / чужой tenant | «Событие не найдено» | no retry |
CAL_EVENT_MANAGED_BY_APPOINTMENT | 409 | Origin = Appointment (INV-CAL-3) | «Событие управляется записью» | no retry |
CAL_EVENT_MANAGED_BY_CRM_BOOKING | 409 | Origin = CrmBooking (INV-CAL-4) | «Событие управляется бронью CRM» | no retry |
CAL_EVENT_INVALID_STATE | 409 | событие уже InProgress (переход вне матрицы, INV-CAL-2) | «Событие уже начато» | no retry |
CAL_EVENT_ALREADY_TERMINAL | 409 | статус 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-changed — ContractVersion = 1, additive-эволюция.
- Logs:
TenantId, OwnerUserId, EventId, переход Planned → InProgress; metrics: счётчик переходов по
статусам; traces — сквозной traceparent.
Обратные ссылки
Автоссылки: где используется этот документ.
Связанные документы