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

GET /api/v1/agency/calendar/appointments — Записи команды (read-only)

Назначение

Отдаёт руководителю агентства записи инвесторов ко всем сотрудникам его компании (appointment.md): кто записан, к какому агенту, на какое время, в каком статусе и чем закончилась встреча. Это картина спроса и конверсии самозаписи по команде. Выборка строго read-only: решения по записи (подтвердить/отклонить/отменить/исход) принимает только агент-участник — управляющих операций в agency-области нет. Документ — дом модели TeamAppointmentModel (переиспользуется pin-manager-областью).

Metadata

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

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

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

HTTP-контракт

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

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

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

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

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

Ответ — страница с полями Items, Page, PageSize, TotalCount; сортировка — StartAt по убыванию, затем Id. Дом модели — этот документ (pin-manager-область ссылается сюда); поля — управленческая проекция appointment.md + display-данные участников из реплики identity.

ПолеТипОбязательностьИсточникMaskingОписание
IdGuidДаentityИдентификатор записи.
AgentUserIdGuidДаentityАгент/консультант — участник записи.
AgentDisplayNamestring?Нетdisplay-реплика identityИмя сотрудника.
InvestorUserIdGuidДаentityИнвестор.
InvestorDisplayNamestring?Нетdisplay-реплика identityв логи не пишетсяИмя клиента (null, если реплика ещё не получила пользователя). Контакты клиента не отдаются — они в CRM/identity.
KindEventKindДаentityВид встречи: Showing / Consultation.
StatusAppointmentStatusДаentityТекущий статус.
StartAtDateTimeOffsetДаentity (снапшот слота)Начало встречи, UTC.
EndAtDateTimeOffsetДаentity (снапшот слота)Конец встречи, UTC.
TimeZonestringДаentity (снапшот слота)IANA-таймзона.
Commentstring?Нетentityкласс Internal — в логи не пишетсяКомментарий инвестора при записи.
PendingExpiresAtDateTimeOffset?НетentityДедлайн решения агента по Pending-записи.
ConfirmedAtDateTimeOffset?НетentityМомент подтверждения.
CompletedAtDateTimeOffset?НетentityМомент фиксации исхода.
CancelReasonstring?НетentityПричина отклонения/отмены.
CancelledByAppointmentCancelledBy?НетentityКто отменил (Investor/Agent/System).
RescheduledFromAppointmentIdGuid?НетentityОткуда перенесена.
RescheduledToAppointmentIdGuid?НетentityКуда перенесена.
SlotIdGuidДаentityЗабронированный слот.
CalendarEventIdGuidДаentityПорождённое календарное событие (связь с list-team-events).
ProjectIdGuid?НетслотПроект застройщика (логическая ссылка).
PropertyObjectIdGuid?НетслотОбъект недвижимости (логическая ссылка).
Locationstring?Нетслотв логи не пишетсяМесто встречи.
CreatedAtDateTimeOffsetДаentityМомент бронирования.

Алгоритм

  1. Контекст и доступ. Актор — сотрудник компании с calendar.team.view; нет права → FORBIDDEN. TenantId берётся из токена.
  2. Проверка входа. From/To заданы, To > From, окно ≤ 92 дней; enum-значения допустимы; paging в границах — иначе VALIDATION_FAILED.
  3. Что читаем. Страница записей TenantId = tenant актора, StartAt ∈ [From, To) с наложением фильтров AgentUserId/Status/Kind по «И»; сортировка StartAt убыв., Id. Display-имена агентов и инвесторов дополняются из реплики identity одним заходом.
  4. Результат. Страница TeamAppointmentModel → 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

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

  • Новые поля TeamAppointmentModel — additive optional; удаление/переименование/смена типа — breaking. Новые значения AppointmentStatus в ответе клиент обязан переживать.
  • Logs: TenantId, актор, фильтры, TotalCount (без Comment/Location и display-имён клиентов); metrics: calendar_api_request_duration_seconds{route}; traces — сквозной traceparent.

Acceptance Criteria

  • AC-1 (вся команда). Дано: записи к трём агентам компании в окне. Когда: GET с From/To. Тогда: 200, записи всех трёх агентов с display-именами агентов и клиентов.
  • AC-2 (воронка ожидания). Дано: Status = Pending. Тогда: только ожидающие решения записи, у каждой — PendingExpiresAt.
  • AC-3 (tenant isolation). Дано: AgentUserId сотрудника другой компании. Тогда: 200, Items = [].
  • AC-4 (нет права). Дано: агент без calendar.team.view. Тогда: 403 FORBIDDEN (свои записи — ../agent/list-appointments.md).
  • AC-5 (исходы). Дано: Status = NoShow. Тогда: только записи с неявкой; CompletedAt заполнен.

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

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

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