GET /api/v1/investor/calendar/appointments/{appointmentId} — Карточка моей записи
Назначение
Отдаёт инвестору карточку одной его записи (appointment.md): статус, время и
место встречи, агента и — главное — действующие правила отмены/переноса (echo cutoff), чтобы клиент показал
кнопки «Отменить»/«Перенести» ровно тогда, когда операции реально доступны. Чужая запись неотличима от
несуществующей (fail-closed 404).
| Поле | Значение |
|---|
| Сервис/контекст | calendar |
| API area | investor |
| Feature group | appointments |
| Статус | draft |
| Документ | docs/06-services/calendar/api/investor/get-my-appointment.md |
Актор и доступ
| Проверка | Значение |
|---|
| Actor type | User (Инвестор с JWT) |
| Auth policy / Permissions | none (достаточно аутентификации инвестора) |
| Scope (RBAC) | не применяется — инвестор вне tenant (ADR-0052) |
| Record-level | запись доступна только при InvestorUserId = ActorUserId из токена; иначе — 404 (без раскрытия существования) |
| Audit | read: актор и AppointmentId фиксируются в trace; Comment в логи не пишется |
HTTP-контракт
| Поле | Значение |
|---|
| Method | GET |
| Route | /api/v1/investor/calendar/appointments/{appointmentId} |
| Success status | 200 |
| Idempotency header | нет (безопасное чтение) |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|
ActorUserId | токен (sub) | Guid | Да | Required | record-level фильтр InvestorUserId |
AppointmentId | route | Guid | Да | Required, валидный Guid | — |
Модель ответа MyAppointmentModel
Модель и все её поля — по дому list-my-appointments.md. Для карточки существенны
echo-поля правил отмены:
CancellationCutoffMinutes — параметр слота (INV-APT-3);
CancellableUntil = StartAt − CancellationCutoffMinutes — до этого момента инвестору доступны
cancel-my-appointment.md и
reschedule-my-appointment.md;
- отмена/перенос возможны только для активной записи (
Pending/Confirmed) и пока
now ≤ CancellableUntil; после cutoff обе операции вернут 409 APPOINTMENT_CUTOFF_PASSED, а по
терминальной записи — 409 APPOINTMENT_INVALID_STATE;
PendingExpiresAt (для Pending) — до какого момента агент примет решение; после — запись автоматически
истечёт (Expired), слот освободится.
Алгоритм
Тривиальное чтение: найти запись по AppointmentId с условием InvestorUserId = actor; не найдена или
принадлежит другому инвестору → APPOINTMENT_NOT_FOUND (404, fail-closed). Найденная запись дополняется
display-именем агента (реплика identity) и местом встречи из слота и возвращается как MyAppointmentModel
→ HTTP 200. Данные — из source of truth calendar, актуальны на момент чтения.
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|
VALIDATION_FAILED | 400 | AppointmentId — не валидный Guid | «Некорректный идентификатор» | no retry |
UNAUTHORIZED | 401 | нет/просрочен JWT | — | no retry |
APPOINTMENT_NOT_FOUND | 404 | запись не существует или принадлежит другому инвестору | «Запись не найдена» | no retry |
Совместимость и наблюдаемость
- Контракт модели — по дому list-my-appointments.md: новые поля — additive
optional; удаление/переименование/смена типа — breaking.
- Logs:
InvestorUserId, AppointmentId, результат (без Comment); metrics:
calendar_api_request_duration_seconds{route}; traces — сквозной traceparent.
Acceptance Criteria
- AC-1 (happy). Дано: активная запись инвестора, до
StartAt далеко. Когда: GET карточки. Тогда:
200, CancellableUntil > now — клиент показывает «Отменить»/«Перенести».
- AC-2 (после cutoff). Дано:
Confirmed-запись, now > StartAt − CancellationCutoffMinutes. Тогда:
200, CancellableUntil < now — клиент скрывает кнопки; попытка отмены вернёт
409 APPOINTMENT_CUTOFF_PASSED.
- AC-3 (чужая запись). Дано:
AppointmentId записи другого инвестора. Тогда: 404 APPOINTMENT_NOT_FOUND — ответ неотличим от несуществующей записи.
- AC-4 (перенесённая). Дано: запись, отменённая переносом. Тогда:
200, Status = Cancelled,
RescheduledToAppointmentId указывает на новую запись.
Обратные ссылки
Автоссылки: где используется этот документ.
Связанные документы