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

POST /api/v1/agent/calendar/appointments/{appointmentId}/complete — Фиксация исхода встречи

Назначение

Агент фиксирует исход подтверждённой встречи после её наступления: Completed — встреча состоялась, NoShow — клиент не пришёл. Исход закрывает запись терминальным статусом, каскадом архивирует слот и завершает календарное событие; CRM получает исход в ленту лида (материал для воронки), инвестор — при Completed — уведомление по итогам. Зафиксировать исход может только агент, только из Confirmed и только после StartAt (INV-APT-4).

Metadata

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

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

ПроверкаЗначение
Actor typeUser (агент агентства / консультант ПИН — сторона записи)
Auth policy / Permissionscalendar.appointments.decide
Scope (RBAC)организация — компания актора (агентство / ПИН)
Record-levelтолько собственные записи (AgentUserId = actor); чужая — APPOINTMENT_NOT_FOUND (fail-closed)
Tenant isolationTenantId из токена (ADR-0052)
Auditupdate: CompletedAt, UpdatedByUserId = actor; история — событие appointment-completed

HTTP-контракт

ПолеЗначение
MethodPOST
Route/api/v1/agent/calendar/appointments/{appointmentId}/complete
Success status200 (обновлённая запись)
Correlationсквозной trace через заголовок traceparent

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

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

Enum AppointmentOutcome

Исход встречи; маппится в терминальный статус записи и в поле Outcome события appointment-completed.

ЗначениеСтатус записиОписание
Completed = 1CompletedВстреча состоялась.
NoShow = 2NoShowКлиент не пришёл.

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

Обновлённая запись — AgentAppointmentModel (дом — list-appointments.md): Status = Completed | NoShow, заполнен CompletedAt, инкрементирован Version.

Алгоритм

  1. Контекст и доступ. Актор с permission calendar.appointments.decide (иначе APPOINTMENT_ACCESS_DENIED); запись ищется среди записей актора в его tenant — не найдена/чужая → APPOINTMENT_NOT_FOUND.
  2. Проверка входа. Outcome ∈ {Completed, NoShow} — иначе VALIDATION_FAILED.
  3. Проверка состояния. Допустимый исходный статус — только Confirmed (INV-APT-4): Pending и терминальные → APPOINTMENT_INVALID_STATE (неподтверждённая встреча исхода не имеет — она истекает таймаутом или отклоняется).
  4. Проверка времени. Исход фиксируется только после наступления встречи: now ≥ StartAt, иначе — APPOINTMENT_NOT_STARTED (INV-APT-4). Верхней границы нет — агент может закрыть встречу задним числом (например, на следующий день).
  5. Переход и каскады. В одном согласованном изменении (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).
  6. Конкуренция. Гонка «complete ↔ отмена агентом» — арбитраж версией записи; проигравший получает APPOINTMENT_INVALID_STATE.
  7. Результат. 200 + обновлённая AgentAppointmentModel.

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

ТипДетали
Интеграционные событияpin.calendar.appointment-completed.v1 (Outcome = Completed / NoShow), key = AppointmentId (../../events/appointment-completed.md); атомарно с переходом
Проекции downstreamcrm — исход встречи в ленту лида (../../../crm/consumers/calendar-appointments.md) — материал для скоринга воронки (в т.ч. счётчик no-show)
Уведомленияnotifications консюмит событие (../../../notifications/consumers/calendar-events.md) — уведомление инвестору по итогам встречи

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
VALIDATION_FAILED400Outcome отсутствует или вне {Completed, NoShow}«Укажите исход встречи»no retry
UNAUTHORIZED401нет/просрочен JWT«Требуется авторизация»no retry
APPOINTMENT_ACCESS_DENIED403нет permission calendar.appointments.decide«Недостаточно прав»no retry
APPOINTMENT_NOT_FOUND404запись не существует или принадлежит другому агенту/tenant«Запись не найдена»no retry
APPOINTMENT_NOT_STARTED409now < StartAt — встреча ещё не наступила (INV-APT-4)«Встреча ещё не началась»retry после StartAt
APPOINTMENT_INVALID_STATE409запись не 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.

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

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

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