GET /api/v1/agent/calendar/events/{id} — Карточка события календаря
Назначение
Отдаёт владельцу календаря (агенту агентства или консультанту ПИН) полную карточку одного события: все поля
агрегата, признак пересечения HasConflicts и текущую Version для последующих операций изменения
(update-event.md, reschedule-event.md). Используется при открытии
события из календарной сетки (list-events.md) и как перечитка после проигрыша конкурентной
правки.
| Поле | Значение |
|---|
| Сервис/контекст | calendar |
| API area | agent |
| Feature group | calendar-events |
| Статус | draft |
| Документ | docs/06-services/calendar/api/agent/get-event.md |
Актор и доступ
| Проверка | Значение |
|---|
| Actor type | User (агент агентства / консультант ПИН — владелец календаря) |
| Auth policy / Permissions | calendar.events.view |
| Scope (RBAC) | организация: компания актора (агентство / ПИН) |
| Record-level | владелец: OwnerUserId = ActorUserId из токена (sub). Событие другого агента того же tenant → CAL_EVENT_ACCESS_DENIED (календарь команды — read-only операция ../agency/list-team-events.md, calendar.team.view) |
| Tenant isolation | TenantId из токена (tenant_id); событие чужого tenant неотличимо от несуществующего (CAL_EVENT_NOT_FOUND) |
| Audit | read: актор и EventId фиксируются в trace; Title/Description/Location (могут содержать ПДн клиента) в логи не пишутся |
HTTP-контракт
| Поле | Значение |
|---|
| Method | GET |
| Route | /api/v1/agent/calendar/events/{id} |
| Success status | 200 |
| Idempotency header | нет (безопасное чтение) |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|
ActorUserId | токен (sub) | Guid | Да | Required | record-level проверка владельца |
TenantId | токен (tenant_id) | Guid | Да | Required | Tenant isolation |
Id | route | Guid | Да | валидный Guid | — |
Модель ответа — CalendarEventModel
Единичная CalendarEventModel — дом модели и полная таблица полей (включая правило вычисления
HasConflicts по INV-CAL-6) — list-events.md. Отличий от списочной проекции нет: карточка
возвращает те же поля, HasConflicts вычисляется по тому же правилу строгого пересечения с активными
(Planned/InProgress) интервальными событиями владельца.
Алгоритм
Тривиальное чтение: найти событие по Id в tenant актора — не найдено (или чужой tenant) →
CAL_EVENT_NOT_FOUND; событие принадлежит другому владельцу того же tenant → CAL_EVENT_ACCESS_DENIED;
иначе вычислить HasConflicts (для интервального события — по активным интервальным событиям владельца) и
вернуть 200 с CalendarEventModel. Данные отдаются из source of truth calendar — актуальны на момент
чтения; Version из ответа передаётся в операции изменения (оптимистическая блокировка).
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|
ValidationError | 400 | Id — не валидный Guid | «Некорректный идентификатор» | no retry |
UNAUTHORIZED | 401 | нет/просрочен JWT | — | no retry |
FORBIDDEN | 403 | нет права calendar.events.view | — | no retry |
CAL_EVENT_ACCESS_DENIED | 403 | событие существует в tenant актора, но принадлежит другому владельцу | «Нет доступа к событию» | no retry |
CAL_EVENT_NOT_FOUND | 404 | событие не существует или принадлежит другому tenant | «Событие не найдено» | no retry |
Acceptance-критерии
- Happy path. Дано: событие владельца в любом статусе (включая
Completed/Cancelled). Когда: GET по
его Id. Тогда: 200, все поля CalendarEventModel заполнены из агрегата, Version актуальна.
- HasConflicts. Дано: событие пересекается с другим активным интервальным событием владельца. Тогда:
HasConflicts = true; после отмены контрагента повторный GET возвращает HasConflicts = false.
- Чужое событие в tenant. Дано:
Id события другого агента того же агентства. Тогда: 403
CAL_EVENT_ACCESS_DENIED.
- Чужой tenant / несуществующее. Дано:
Id события другого tenant либо случайный Guid. Тогда: 404
CAL_EVENT_NOT_FOUND — существование чужих событий не раскрывается.
- Терминальное событие читается. Дано: событие
Cancelled с CancelReason. Тогда: 200, Status = Cancelled, CancelReason заполнен (чтение терминальных не ограничено — иммутабельны только изменения).
Совместимость и наблюдаемость
- Модель — общая
CalendarEventModel (list-events.md): новые поля — additive nullable;
удаление/переименование/смена типа — breaking. Новые значения EventKind/EventStatus/EventOrigin в
ответе клиент обязан переживать (forward-compatible отображение «прочее»).
- Logs:
TenantId, ActorUserId, EventId, исход (без содержимого полей); metrics:
calendar_api_request_duration_seconds{route}; traces — сквозной traceparent.
Обратные ссылки
Автоссылки: где используется этот документ.
Связанные документы