GET /api/v1/agency/calendar/appointments — Записи команды (read-only)
Назначение
Отдаёт руководителю агентства записи инвесторов ко всем сотрудникам его компании
(appointment.md): кто записан, к какому агенту, на какое время, в каком
статусе и чем закончилась встреча. Это картина спроса и конверсии самозаписи по команде. Выборка строго
read-only: решения по записи (подтвердить/отклонить/отменить/исход) принимает только агент-участник —
управляющих операций в agency-области нет. Документ — дом модели TeamAppointmentModel (переиспользуется
pin-manager-областью).
| Поле | Значение |
|---|
| Сервис/контекст | calendar |
| API area | agency |
| Feature group | team-calendar |
| Статус | draft |
| Документ | docs/06-services/calendar/api/agency/list-team-appointments.md |
Актор и доступ
| Проверка | Значение |
|---|
| Actor type | User (руководитель агентства / уполномоченный сотрудник) |
| Auth policy / Permissions | calendar.team.view |
| Scope (RBAC) | организация: компания актора |
| Record-level | не применяется — область команды: видны записи ко всем агентам внутри tenant актора |
| Tenant isolation | TenantId из токена (tenant_id), не из query (ADR-0052) |
| Audit | read-sensitive: актор и фильтры фиксируются в trace (доступ к записям клиентов команды); Comment в логи не пишется |
HTTP-контракт
| Поле | Значение |
|---|
| Method | GET |
| Route | /api/v1/agency/calendar/appointments |
| Success status | 200 |
| Idempotency header | нет (безопасное чтение) |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
Все фильтры комбинируются по «И».
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|
TenantId | токен (tenant_id) | Guid | Да | Required; не принимается из query | Tenant isolation |
From | query | DateTimeOffset | Да | UTC; окно по StartAt записи | — |
To | query | DateTimeOffset | Да | UTC; To > From; To − From ≤ 92 дня | — |
AgentUserId | query | Guid? | Нет | записи к конкретному сотруднику; сотрудник другого tenant → пустая выборка (fail-closed) | — |
Status | query | AppointmentStatus? | Нет | enum-строка Pending/Confirmed/Declined/Cancelled/Expired/Completed/NoShow | — |
Kind | query | EventKind? | Нет | enum-строка Showing/Consultation | — |
Page / PageSize | query | int | Нет | Page ≥ 1; PageSize 1..500, default 200 | — |
Модель ответа — страница TeamAppointmentModel
Ответ — страница с полями Items, Page, PageSize, TotalCount; сортировка — StartAt по убыванию,
затем Id. Дом модели — этот документ (pin-manager-область ссылается сюда); поля — управленческая проекция
appointment.md + display-данные участников из реплики identity.
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|
Id | Guid | Да | entity | — | Идентификатор записи. |
AgentUserId | Guid | Да | entity | — | Агент/консультант — участник записи. |
AgentDisplayName | string? | Нет | display-реплика identity | — | Имя сотрудника. |
InvestorUserId | Guid | Да | entity | — | Инвестор. |
InvestorDisplayName | string? | Нет | display-реплика identity | в логи не пишется | Имя клиента (null, если реплика ещё не получила пользователя). Контакты клиента не отдаются — они в CRM/identity. |
Kind | EventKind | Да | entity | — | Вид встречи: Showing / Consultation. |
Status | AppointmentStatus | Да | entity | — | Текущий статус. |
StartAt | DateTimeOffset | Да | entity (снапшот слота) | — | Начало встречи, UTC. |
EndAt | DateTimeOffset | Да | entity (снапшот слота) | — | Конец встречи, UTC. |
TimeZone | string | Да | entity (снапшот слота) | — | IANA-таймзона. |
Comment | string? | Нет | entity | класс Internal — в логи не пишется | Комментарий инвестора при записи. |
PendingExpiresAt | DateTimeOffset? | Нет | entity | — | Дедлайн решения агента по Pending-записи. |
ConfirmedAt | DateTimeOffset? | Нет | entity | — | Момент подтверждения. |
CompletedAt | DateTimeOffset? | Нет | entity | — | Момент фиксации исхода. |
CancelReason | string? | Нет | entity | — | Причина отклонения/отмены. |
CancelledBy | AppointmentCancelledBy? | Нет | entity | — | Кто отменил (Investor/Agent/System). |
RescheduledFromAppointmentId | Guid? | Нет | entity | — | Откуда перенесена. |
RescheduledToAppointmentId | Guid? | Нет | entity | — | Куда перенесена. |
SlotId | Guid | Да | entity | — | Забронированный слот. |
CalendarEventId | Guid | Да | entity | — | Порождённое календарное событие (связь с list-team-events). |
ProjectId | Guid? | Нет | слот | — | Проект застройщика (логическая ссылка). |
PropertyObjectId | Guid? | Нет | слот | — | Объект недвижимости (логическая ссылка). |
Location | string? | Нет | слот | в логи не пишется | Место встречи. |
CreatedAt | DateTimeOffset | Да | entity | — | Момент бронирования. |
Алгоритм
- Контекст и доступ. Актор — сотрудник компании с
calendar.team.view; нет права → FORBIDDEN.
TenantId берётся из токена.
- Проверка входа.
From/To заданы, To > From, окно ≤ 92 дней; enum-значения допустимы; paging в
границах — иначе VALIDATION_FAILED.
- Что читаем. Страница записей
TenantId = tenant актора, StartAt ∈ [From, To) с наложением
фильтров AgentUserId/Status/Kind по «И»; сортировка StartAt убыв., Id. Display-имена агентов
и инвесторов дополняются из реплики identity одним заходом.
- Результат. Страница
TeamAppointmentModel → HTTP 200; пустая выборка — 200 с Items = [].
Данные — из source of truth calendar, актуальны на момент чтения.
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|
VALIDATION_FAILED | 400 | From/To отсутствуют; To ≤ From; окно > 92 дней; enum вне значений; paging вне границ | «Некорректные параметры выборки» | no retry |
UNAUTHORIZED | 401 | нет/просрочен JWT | — | no retry |
FORBIDDEN | 403 | нет права calendar.team.view | — | no 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 заполнен.
Обратные ссылки
Автоссылки: где используется этот документ.
Связанные документы