POST /api/v1/agent/calendar/events/{id}/complete — Завершение события (→ Completed)
Назначение
Владелец календаря фиксирует, что событие состоялось/выполнено: показ проведён, задача закрыта. Переход
допустим из Planned (завершение без явного start) и из InProgress; статус становится терминальным
Completed, неотправленные напоминания гасятся (Pending → Skipped), публикуется
pin.calendar.event-status-changed.v1. Исход встречи по записи (Completed/NoShow) фиксируется не
здесь, а операцией complete-appointment.md — она каскадно завершает и событие.
| Поле | Значение |
|---|
| Сервис/контекст | calendar |
| API area | agent |
| Feature group | calendar-events |
| Статус | draft |
| Документ | docs/06-services/calendar/api/agent/complete-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}/complete |
| Success status | 200 |
| Idempotency header | нет (повтор над завершённым событием → CAL_EVENT_ALREADY_TERMINAL, состояние не портится) |
| 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 = Completed, инкрементированная Version. Завершённое событие перестаёт участвовать в расчёте
конфликтов других событий (INV-CAL-6 учитывает только Planned/InProgress).
Алгоритм
- Контекст и доступ. Актор — владелец; требуется
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: исход
встречи фиксируется операцией записи complete-appointment.md, каскад завершает событие);
Origin = CrmBooking → CAL_EVENT_MANAGED_BY_CRM_BOOKING (INV-CAL-4: дедлайн закрывает consumer по
событиям брони).
- Guard статуса (INV-CAL-2). Допустимые исходные статусы —
Planned и InProgress (матрица
переходов); Completed/Cancelled → CAL_EVENT_ALREADY_TERMINAL. Отношение к StartAt/EndAt не
проверяется: владелец вправе закрыть событие раньше или позже планового времени.
- Переход. В одном согласованном изменении:
Status = Completed; все Pending-reminder-строки →
Skipped (правило 3 event-reminder.md); публикация
pin.calendar.event-status-changed.v1 (Status = Completed).
- Результат.
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_ALREADY_TERMINAL | 409 | статус уже Completed/Cancelled (INV-CAL-7) | «Событие завершено или отменено» | no retry |
Предусловия и постусловия
- Предусловия. Событие существует, принадлежит актору,
Origin = Manual, статус Planned или
InProgress.
- Постусловия.
Status = Completed (терминальный, дальнейшие изменения невозможны — INV-CAL-7); все
Pending-напоминания переведены в Skipped (ни одно reminder-due по ним больше не выйдет),
Sent-строки нетронуты; опубликовано ровно одно pin.calendar.event-status-changed.v1.
Acceptance-критерии
- Из InProgress. Дано: событие
InProgress. Когда: POST …/complete. Тогда: 200,
Status = Completed, опубликовано event-status-changed.
- Из Planned (без start). Дано: событие
Planned. Тогда: 200, Status = Completed — явный start не
обязателен (матрица допускает Planned → Completed).
- Напоминания погашены. Дано: событие с
Pending-строкой (DueAt в будущем). Когда: завершение. Тогда:
строка Skipped, reminder-due по ней не публикуется.
- Повторное завершение. Дано: событие
Completed. Тогда: 409 CAL_EVENT_ALREADY_TERMINAL; второе
event-status-changed не публикуется.
- Событие записи. Дано:
Origin = Appointment. Тогда: 409 CAL_EVENT_MANAGED_BY_APPOINTMENT —
исход (Completed/NoShow) фиксируется операцией записи, каскад завершает событие.
- Дедлайн брони. Дано:
Origin = CrmBooking. Тогда: 409 CAL_EVENT_MANAGED_BY_CRM_BOOKING.
- Гонка с worker-ом/consumer-ом. Дано: параллельные complete владельца и изменение
worker-а/consumer-а. Тогда: побеждает один; проигравший получает
409 по актуальному состоянию.
Совместимость и наблюдаемость
- Контракт стабилен (body нет); новые значения
EventStatus клиент обязан переживать. Контракт
event-status-changed — ContractVersion = 1, additive-эволюция.
- Logs:
TenantId, OwnerUserId, EventId, переход → Completed, число погашенных напоминаний; metrics:
счётчик переходов по статусам; traces — сквозной traceparent.
Обратные ссылки
Автоссылки: где используется этот документ.
Связанные документы