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

GET /api/v1/agency/calendar/events — Календарь команды (read-only)

Назначение

Отдаёт руководителю агентства события календарей всех сотрудников его компании за диапазон дат: загрузку агентов, показы, консультации и дедлайны броней — чтобы планировать распределение клиентов и контролировать работу команды. Выборка строго read-only: управлять чужим событием нельзя ни одной операцией — жизненным циклом события владеет только его владелец (agent-область) и системные каскады.

Metadata

ПолеЗначение
Сервис/контекстcalendar
API areaagency
Feature groupteam-calendar
Статусdraft
Документdocs/06-services/calendar/api/agency/list-team-events.md

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

ПроверкаЗначение
Actor typeUser (руководитель агентства / уполномоченный сотрудник)
Auth policy / Permissionscalendar.team.view
Scope (RBAC)организация: компания актора
Record-levelне применяется — область команды: видны события всех владельцев внутри tenant актора (это и есть смысл permission)
Tenant isolationTenantId из токена (tenant_id), не из query: события чужих компаний недоступны независимо от параметров (ADR-0052)
Auditread-sensitive: актор, фильтры и OwnerUserId фиксируются в trace (руководитель читает чужие календари); Title/Description/Location в логи не пишутся

HTTP-контракт

ПолеЗначение
MethodGET
Route/api/v1/agency/calendar/events
Success status200
Idempotency headerнет (безопасное чтение)
Correlationсквозной trace через заголовок traceparent

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

Все фильтры комбинируются по «И».

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
TenantIdтокен (tenant_id)GuidДаRequired; не принимается из queryTenant isolation
FromqueryDateTimeOffsetДаUTC; начало диапазона
ToqueryDateTimeOffsetДаUTC; To > From; To − From ≤ 92 дня (квартальное окно, как в list-events)
OwnerUserIdqueryGuid?Неткалендарь конкретного сотрудника; сотрудник другого tenant → пустая выборка (fail-closed, без раскрытия существования)
KindqueryEventKind?Нетenum-строка Showing/Consultation/Meeting/Task/BookingDeadline
StatusqueryEventStatus?Нетenum-строка Planned/InProgress/Completed/Cancelled
Page / PageSizequeryintНетPage ≥ 1; PageSize 1..500, default 200

Модель ответа — страница CalendarEventModel

Модель, правило выборки диапазона и правило HasConflicts — по дому list-events.md: поля идентичны, OwnerUserId в каждой строке указывает сотрудника-владельца. Сортировка — OwnerUserId, затем StartAt возр., затем Id (события группируются по сотрудникам). HasConflicts вычисляется в пределах календаря каждого владельца отдельно (пересечения событий разных сотрудников конфликтом не являются).

Алгоритм

  1. Контекст и доступ. Актор — сотрудник компании с calendar.team.view; нет права → FORBIDDEN. TenantId берётся из токена.
  2. Проверка входа. From/To заданы, To > From, диапазон ≤ 92 дней; enum-значения допустимы; paging в границах — иначе VALIDATION_FAILED.
  3. Что читаем. Страница событий TenantId = tenant актора по правилу выборки диапазона (list-events) с наложением фильтров OwnerUserId/Kind/Status по «И»; сортировка OwnerUserId, StartAt возр., Id.
  4. Вычисление HasConflicts. Per-owner: для каждого интервального события — строгое пересечение с активными интервальными событиями того же владельца.
  5. Результат. Страница CalendarEventModel → HTTP 200; пустая выборка — 200 с Items = []. Данные — из source of truth calendar, актуальны на момент чтения.

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
VALIDATION_FAILED400From/To отсутствуют; To ≤ From; диапазон > 92 дней; enum вне значений; paging вне границ«Некорректные параметры выборки»no retry
UNAUTHORIZED401нет/просрочен JWTno retry
FORBIDDEN403нет права calendar.team.viewno retry

Совместимость и наблюдаемость

  • Контракт модели — по дому list-events.md (additive-правила там же); правило сортировки agency-выборки (OwnerUserId первым) — стабильная часть контракта.
  • Logs: TenantId, актор, OwnerUserId-фильтр, диапазон, TotalCount (без содержимого событий); metrics: calendar_api_request_duration_seconds{route}; traces — сквозной traceparent.

Acceptance Criteria

  • AC-1 (вся команда). Дано: в агентстве 3 агента с событиями в диапазоне. Когда: GET с From/To без OwnerUserId. Тогда: 200, события всех трёх агентов, сгруппированы по OwnerUserId.
  • AC-2 (один сотрудник). Дано: OwnerUserId агента из tenant актора. Тогда: только его события.
  • AC-3 (tenant isolation). Дано: OwnerUserId агента другой компании. Тогда: 200, Items = [] — существование чужого календаря не раскрывается.
  • AC-4 (нет права). Дано: агент без calendar.team.view. Тогда: 403 FORBIDDEN (свои события он смотрит через list-events).
  • AC-5 (per-owner конфликты). Дано: события двух агентов пересекаются по времени между собой, но не внутри своих календарей. Тогда: у обоих HasConflicts = false.
  • AC-6 (read-only). Дано: руководитель с calendar.team.view и без calendar.events.manage. Тогда: никакая операция изменения чужого события ему недоступна — в agency-области таких операций нет.

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

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

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