GET /api/v1/agent/calendar/appointments — Список записей к агенту
Назначение
Отдаёт агенту (или консультанту ПИН) записи инвесторов на его слоты: ожидающие решения (Pending — с
дедлайном ответа), подтверждённые, отменённые и завершённые. Основной рабочий список раздела «Записи»
кабинета агента: отсюда агент уходит в подтверждение/отклонение и фиксацию исхода. Документ — дом модели
AgentAppointmentModel: карточка записи и team-/pin-manager-выборки ссылаются сюда.
Metadata
| Поле | Значение |
|---|---|
| Сервис/контекст | calendar |
| API area | agent |
| Feature group | appointments |
| Статус | draft |
| Документ | docs/06-services/calendar/api/agent/list-appointments.md |
Актор и доступ
| Проверка | Значение |
|---|---|
| Actor type | User (агент агентства / консультант ПИН) |
| Auth policy / Permissions | calendar.appointments.view |
| Scope (RBAC) | организация — компания актора (агентство / ПИН) |
| Record-level | AgentUserId = ActorUserId из токена (sub), не из query — актор видит только записи к себе (fail-closed); записи команды — ../agency/list-team-appointments.md (calendar.team.view) |
| Tenant isolation | TenantId из токена (tenant_id), ADR-0052 |
| Audit | read; Comment (класс Internal) и display-данные инвестора в логи не пишутся |
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | GET |
| Route | /api/v1/agent/calendar/appointments |
| Success status | 200 |
| Idempotency header | нет (безопасное чтение) |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|---|---|---|---|---|
ActorUserId | токен (sub) | Guid | Да | Required; не принимается из query | record-level фильтр AgentUserId |
TenantId | токен (tenant_id) | Guid | Да | Required | Tenant isolation |
Status | query | AppointmentStatus? | Нет | enum-строка Pending/Confirmed/Declined/Cancelled/Expired/Completed/NoShow (appointment.md) | — |
Kind | query | EventKind? | Нет | только Showing / Consultation | — |
From | query | DateTimeOffset? | Нет | UTC; нижняя граница StartAt | — |
To | query | DateTimeOffset? | Нет | UTC; верхняя граница StartAt; если заданы оба — To > From | — |
InvestorUserId | query | Guid? | Нет | записи конкретного инвестора | — |
Page / PageSize | query | int | Нет | Page ≥ 1; PageSize 1..200, default 50 | — |
Все фильтры комбинируются по «И»; диапазон применяется к StartAt (снапшот времени слота): From ≤ StartAt
и StartAt < To. Без фильтра Status возвращаются записи во всех статусах, включая терминальные.
Модель ответа — страница AgentAppointmentModel
Ответ — страница с полями Items, Page, PageSize, TotalCount. Сортировка — StartAt по убыванию,
затем Id (стабильный порядок: ближайшие и недавние — первыми). Поля — проекция агрегата
appointment.md плюс контекст слота и display-данные инвестора из реплики
identity (../../consumers/identity-user-upserted.md).
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|---|---|---|---|---|
Id | Guid | Да | entity | — | Идентификатор записи. |
Status | AppointmentStatus | Да | entity | — | Текущий статус. |
Kind | EventKind | Да | entity | — | Вид встречи (Showing / Consultation). |
StartAt | DateTimeOffset | Да | entity | — | Начало встречи, UTC (снапшот слота, иммутабелен — INV-APT-5). |
EndAt | DateTimeOffset | Да | entity | — | Конец встречи, UTC. |
TimeZone | string | Да | entity | — | IANA-таймзона слота на момент бронирования. |
SlotId | Guid | Да | entity | — | Забронированный слот. |
InvestorUserId | Guid | Да | entity | — | Инвестор (пользователь identity, вне tenant). |
InvestorDisplayName | string? | Нет | реплика identity | в логи не пишется | Отображаемое имя инвестора из display-реплики; null при отставании реплики — клиент показывает заглушку. |
Comment | string? | Нет | entity | Internal — маскирование в логах | Комментарий инвестора при записи («интересует 2-комнатная…»). |
PendingExpiresAt | DateTimeOffset? | Нет | entity | — | Дедлайн решения агента по Pending-записи; null при auto-confirm. |
ConfirmedAt | DateTimeOffset? | Нет | entity | — | Момент подтверждения (в т.ч. auto-confirm). |
CompletedAt | DateTimeOffset? | Нет | entity | — | Момент фиксации исхода. |
CancelReason | string? | Нет | entity | — | Причина отклонения/отмены. |
CancelledBy | AppointmentCancelledBy? | Нет | entity | — | Кто отменил (Investor/Agent/System); заполнен при Cancelled. |
RescheduledFromAppointmentId | Guid? | Нет | entity | — | Запись, из которой выполнен перенос. |
RescheduledToAppointmentId | Guid? | Нет | entity | — | Запись, в которую выполнен перенос (у отменённой). |
CalendarEventId | Guid | Да | entity | — | Порождённое календарное событие (1:1, Origin = Appointment). |
ProjectId | Guid? | Нет | слот | — | Проект застройщика (chessboard, логическая ссылка). |
PropertyObjectId | Guid? | Нет | слот | — | Объект недвижимости (chessboard, логическая ссылка). |
Location | string? | Нет | слот | в логи не пишется | Место встречи (свободный текст слота). |
CreatedAt | DateTimeOffset | Да | entity | — | Создание записи (= бронирование слота). |
UpdatedAt | DateTimeOffset | Да | entity | — | Последнее изменение. |
Version | int | Да | entity | — | Версия строки — арбитраж конкурентных решений «агент ↔ инвестор ↔ worker». |
Алгоритм
- Контекст и доступ. Актор — агент/консультант с permission
calendar.appointments.view(иначеAPPOINTMENT_ACCESS_DENIED).AgentUserIdиTenantIdберутся из токена, не из запроса. - Проверка входа. Enum-значения
Status/Kind(если заданы) допустимы;To > From, если заданы оба; paging в границах — иначеVALIDATION_FAILED. - Что читаем. Страница записей актора по фильтрам (по «И»), сортировка
StartAtубыв., затемId. Ведущий индекс —ix_appointments_agent_start(AgentUserId,StartAt). Для строк страницы подтягиваютсяProjectId/PropertyObjectId/Locationслота иInvestorDisplayNameиз display-реплики identity; отсутствие строки реплики (лаг консюмера) — не ошибка, полеnull. - Результат. Страница
AgentAppointmentModel→ HTTP200. Пустая выборка —200сItems = [], не404. Записи — из source of truthcalendar, всегда актуальны; display-имя — eventual consistency реплики (лаг обычно < 1 мин).
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|---|---|---|---|
VALIDATION_FAILED | 400 | enum Status/Kind вне значений; To ≤ From; paging вне границ | «Некорректные параметры выборки» | no retry |
UNAUTHORIZED | 401 | нет/просрочен JWT | «Требуется авторизация» | no retry |
APPOINTMENT_ACCESS_DENIED | 403 | нет permission calendar.appointments.view | «Недостаточно прав» | no retry |
Acceptance Criteria
- AC-1 (happy path). Given у агента записи во всех статусах. When
GETбез фильтров. Then200, страницаAgentAppointmentModel, сортировкаStartAtубыв.; у каждой строки заполненыInvestorDisplayName(из реплики) и контекст слота. - AC-2 (ожидающие решения). Given три
Pending-записи с разнымиPendingExpiresAt. WhenGET ?Status=Pending. Then только они; у каждой заполненPendingExpiresAt. - AC-3 (диапазон). Given записи на этой и следующей неделе. When
From/To= текущая неделя. Then записи следующей недели отсутствуют. - AC-4 (лаг реплики). Given инвестор зарегистрирован только что, display-реплика ещё не догнала. When
выборка. Then запись возвращается с
InvestorDisplayName = null— остальные поля полны. - AC-5 (record-level). Given в tenant есть записи к другому агенту. When выборка. Then они не попадают в результат.
- AC-6 (пустая выборка). Given записей нет. Then
200,Items = [],TotalCount = 0.
Совместимость и наблюдаемость
- Новые поля
AgentAppointmentModel— additive nullable; удаление/переименование/смена типа — breaking. Неизвестное enum-значение в query →VALIDATION_FAILED; новые значенияAppointmentStatusв ответе клиент обязан переживать (forward-compatible отображение «прочее»). - Logs:
TenantId,AgentUserId, фильтры,TotalCount(безComment/InvestorDisplayName/Location); metrics:calendar_api_request_duration_seconds{route}; traces — сквознойtraceparent.
Обратные ссылки
Автоссылки: где используется этот документ.
- API (5): GET /api/v1/agent/calendar/appointments/{appointmentId} — Карточка записи, POST /api/v1/agent/calendar/appointments/{appointmentId}/cancel — Отмена записи агентом, POST /api/v1/agent/calendar/appointments/{appointmentId}/complete — Фиксация исхода встречи, POST /api/v1/agent/calendar/appointments/{appointmentId}/confirm — Подтверждение записи, POST /api/v1/agent/calendar/appointments/{appointmentId}/decline — Отклонение записи
- registries (1): Реестр API-методов PIN
Связанные документы
- Агрегат, статус-машина и INV-APT-1..6: appointment.md; слот:
availability-slot.md; display-реплика:
../../consumers/identity-user-upserted.md. - Операции над записью: get-appointment.md, confirm-appointment.md, decline-appointment.md, cancel-appointment.md, complete-appointment.md.
- Записи команды:
../agency/list-team-appointments.md; записи консультантов ПИН:../pin-manager/list-appointments.md; сторона инвестора:../investor/list-my-appointments.md. - Таймаут
Pending-записей:../../background/appointment-timeout.md; ADR-0064 п.4.