POST /api/v1/agent/calendar/appointments/{appointmentId}/complete — Фиксация исхода встречи
Назначение
Агент фиксирует исход подтверждённой встречи после её наступления: Completed — встреча состоялась,
NoShow — клиент не пришёл. Исход закрывает запись терминальным статусом, каскадом архивирует слот и
завершает календарное событие; CRM получает исход в ленту лида (материал для воронки), инвестор — при
Completed — уведомление по итогам. Зафиксировать исход может только агент, только из Confirmed и
только после StartAt (INV-APT-4).
| Поле | Значение |
|---|
| Сервис/контекст | calendar |
| API area | agent |
| Feature group | appointments |
| Статус | draft |
| Документ | docs/06-services/calendar/api/agent/complete-appointment.md |
Актор и доступ
| Проверка | Значение |
|---|
| Actor type | User (агент агентства / консультант ПИН — сторона записи) |
| Auth policy / Permissions | calendar.appointments.decide |
| Scope (RBAC) | организация — компания актора (агентство / ПИН) |
| Record-level | только собственные записи (AgentUserId = actor); чужая — APPOINTMENT_NOT_FOUND (fail-closed) |
| Tenant isolation | TenantId из токена (ADR-0052) |
| Audit | update: CompletedAt, UpdatedByUserId = actor; история — событие appointment-completed |
HTTP-контракт
| Поле | Значение |
|---|
| Method | POST |
| Route | /api/v1/agent/calendar/appointments/{appointmentId}/complete |
| Success status | 200 (обновлённая запись) |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|
ActorUserId | токен (sub) | Guid | Да | Required | record-level |
TenantId | токен (tenant_id) | Guid | Да | Required | Tenant isolation |
AppointmentId | route | Guid | Да | Required; валидный Guid | — |
Outcome | body | AppointmentOutcome | Да | enum-строка Completed / NoShow; иное значение → VALIDATION_FAILED | — |
Enum AppointmentOutcome
Исход встречи; маппится в терминальный статус записи и в поле Outcome события appointment-completed.
| Значение | Статус записи | Описание |
|---|
Completed = 1 | Completed | Встреча состоялась. |
NoShow = 2 | NoShow | Клиент не пришёл. |
Модель ответа
Обновлённая запись — AgentAppointmentModel (дом — list-appointments.md):
Status = Completed | NoShow, заполнен CompletedAt, инкрементирован Version.
Алгоритм
- Контекст и доступ. Актор с permission
calendar.appointments.decide (иначе
APPOINTMENT_ACCESS_DENIED); запись ищется среди записей актора в его tenant — не найдена/чужая →
APPOINTMENT_NOT_FOUND.
- Проверка входа.
Outcome ∈ {Completed, NoShow} — иначе VALIDATION_FAILED.
- Проверка состояния. Допустимый исходный статус — только
Confirmed (INV-APT-4): Pending и
терминальные → APPOINTMENT_INVALID_STATE (неподтверждённая встреча исхода не имеет — она истекает
таймаутом или отклоняется).
- Проверка времени. Исход фиксируется только после наступления встречи:
now ≥ StartAt, иначе —
APPOINTMENT_NOT_STARTED (INV-APT-4). Верхней границы нет — агент может закрыть встречу задним числом
(например, на следующий день).
- Переход и каскады. В одном согласованном изменении (INV-APT-6):
- запись
Confirmed → Completed (при Outcome = Completed) или Confirmed → NoShow;
CompletedAt = now;
- слот:
Booked → Archived — из оборота навсегда (таблица каскадов —
appointment.md);
- календарное событие (
Origin = Appointment): → Completed — факт встречи зафиксирован в календаре
при обоих исходах, сам исход хранится на записи; оставшиеся Pending-напоминания → Skipped
(event-reminder.md);
- публикуется
appointment-completed (Outcome, CompletedAt).
- Конкуренция. Гонка «complete ↔ отмена агентом» — арбитраж версией записи; проигравший получает
APPOINTMENT_INVALID_STATE.
- Результат.
200 + обновлённая AgentAppointmentModel.
Побочные эффекты
| Тип | Детали |
|---|
| Интеграционные события | pin.calendar.appointment-completed.v1 (Outcome = Completed / NoShow), key = AppointmentId (../../events/appointment-completed.md); атомарно с переходом |
| Проекции downstream | crm — исход встречи в ленту лида (../../../crm/consumers/calendar-appointments.md) — материал для скоринга воронки (в т.ч. счётчик no-show) |
| Уведомления | notifications консюмит событие (../../../notifications/consumers/calendar-events.md) — уведомление инвестору по итогам встречи |
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|
VALIDATION_FAILED | 400 | Outcome отсутствует или вне {Completed, NoShow} | «Укажите исход встречи» | no retry |
UNAUTHORIZED | 401 | нет/просрочен JWT | «Требуется авторизация» | no retry |
APPOINTMENT_ACCESS_DENIED | 403 | нет permission calendar.appointments.decide | «Недостаточно прав» | no retry |
APPOINTMENT_NOT_FOUND | 404 | запись не существует или принадлежит другому агенту/tenant | «Запись не найдена» | no retry |
APPOINTMENT_NOT_STARTED | 409 | now < StartAt — встреча ещё не наступила (INV-APT-4) | «Встреча ещё не началась» | retry после StartAt |
APPOINTMENT_INVALID_STATE | 409 | запись не Confirmed (в т.ч. Pending или терминальная), либо проигрыш конкурентной гонки | «Запись уже в другом состоянии» | no retry (перечитать состояние) |
Постусловия
После 200: запись в терминальном статусе Completed/NoShow с CompletedAt; слот Archived
(терминален); календарное событие Completed; напоминания Skipped; опубликовано ровно одно
appointment-completed с исходом. Повторная фиксация исхода невозможна (APPOINTMENT_INVALID_STATE).
Лента лида CRM пополняется асинхронно.
Acceptance Criteria
- AC-1 (состоялась). Given
Confirmed-запись, now ≥ StartAt. When агент фиксирует
Outcome = Completed. Then 200: запись Completed, слот Archived, событие Completed; опубликовано
appointment-completed (Outcome = Completed).
- AC-2 (no-show). Given
Confirmed-запись, now ≥ StartAt. When Outcome = NoShow. Then 200: запись
NoShow, слот Archived, событие Completed; опубликовано appointment-completed (Outcome = NoShow).
- AC-3 (рано). Given
Confirmed-запись, now < StartAt. When фиксация любого исхода. Then
409 APPOINTMENT_NOT_STARTED; запись не изменилась.
- AC-4 (неподтверждённая). Given
Pending-запись, now ≥ StartAt. When complete. Then
409 APPOINTMENT_INVALID_STATE.
- AC-5 (повторная фиксация). Given запись уже
NoShow. When повторный complete. Then
409 APPOINTMENT_INVALID_STATE; второе событие не публикуется.
- AC-6 (неизвестный исход). Given body
Outcome = "Postponed". Then 400 VALIDATION_FAILED.
Совместимость и наблюдаемость
- Новые значения
AppointmentOutcome — breaking для консюмеров события (crm, notifications) —
вводить только согласованно с контрактом ../../events/appointment-completed.md; неизвестное значение
во входе → VALIDATION_FAILED, не игнор.
- Logs:
appointment completed (TenantId, AgentUserId, AppointmentId, Outcome); metrics:
calendar_appointment_outcomes_total{outcome} (доля no-show — продуктовый сигнал качества записи);
traces — сквозной traceparent.
Обратные ссылки
Автоссылки: где используется этот документ.
Связанные документы