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

GET /api/v1/agent/calendar/appointments — Список записей к агенту

Назначение

Отдаёт агенту (или консультанту ПИН) записи инвесторов на его слоты: ожидающие решения (Pending — с дедлайном ответа), подтверждённые, отменённые и завершённые. Основной рабочий список раздела «Записи» кабинета агента: отсюда агент уходит в подтверждение/отклонение и фиксацию исхода. Документ — дом модели AgentAppointmentModel: карточка записи и team-/pin-manager-выборки ссылаются сюда.

Metadata

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

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

ПроверкаЗначение
Actor typeUser (агент агентства / консультант ПИН)
Auth policy / Permissionscalendar.appointments.view
Scope (RBAC)организация — компания актора (агентство / ПИН)
Record-levelAgentUserId = ActorUserId из токена (sub), не из query — актор видит только записи к себе (fail-closed); записи команды — ../agency/list-team-appointments.md (calendar.team.view)
Tenant isolationTenantId из токена (tenant_id), ADR-0052
Auditread; Comment (класс Internal) и display-данные инвестора в логи не пишутся

HTTP-контракт

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

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

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
ActorUserIdтокен (sub)GuidДаRequired; не принимается из queryrecord-level фильтр AgentUserId
TenantIdтокен (tenant_id)GuidДаRequiredTenant isolation
StatusqueryAppointmentStatus?Нетenum-строка Pending/Confirmed/Declined/Cancelled/Expired/Completed/NoShow (appointment.md)
KindqueryEventKind?Неттолько Showing / Consultation
FromqueryDateTimeOffset?НетUTC; нижняя граница StartAt
ToqueryDateTimeOffset?НетUTC; верхняя граница StartAt; если заданы оба — To > From
InvestorUserIdqueryGuid?Нетзаписи конкретного инвестора
Page / PageSizequeryintНет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Описание
IdGuidДаentityИдентификатор записи.
StatusAppointmentStatusДаentityТекущий статус.
KindEventKindДаentityВид встречи (Showing / Consultation).
StartAtDateTimeOffsetДаentityНачало встречи, UTC (снапшот слота, иммутабелен — INV-APT-5).
EndAtDateTimeOffsetДаentityКонец встречи, UTC.
TimeZonestringДаentityIANA-таймзона слота на момент бронирования.
SlotIdGuidДаentityЗабронированный слот.
InvestorUserIdGuidДаentityИнвестор (пользователь identity, вне tenant).
InvestorDisplayNamestring?Нетреплика identityв логи не пишетсяОтображаемое имя инвестора из display-реплики; null при отставании реплики — клиент показывает заглушку.
Commentstring?НетentityInternal — маскирование в логахКомментарий инвестора при записи («интересует 2-комнатная…»).
PendingExpiresAtDateTimeOffset?НетentityДедлайн решения агента по Pending-записи; null при auto-confirm.
ConfirmedAtDateTimeOffset?НетentityМомент подтверждения (в т.ч. auto-confirm).
CompletedAtDateTimeOffset?НетentityМомент фиксации исхода.
CancelReasonstring?НетentityПричина отклонения/отмены.
CancelledByAppointmentCancelledBy?НетentityКто отменил (Investor/Agent/System); заполнен при Cancelled.
RescheduledFromAppointmentIdGuid?НетentityЗапись, из которой выполнен перенос.
RescheduledToAppointmentIdGuid?НетentityЗапись, в которую выполнен перенос (у отменённой).
CalendarEventIdGuidДаentityПорождённое календарное событие (1:1, Origin = Appointment).
ProjectIdGuid?НетслотПроект застройщика (chessboard, логическая ссылка).
PropertyObjectIdGuid?НетслотОбъект недвижимости (chessboard, логическая ссылка).
Locationstring?Нетслотв логи не пишетсяМесто встречи (свободный текст слота).
CreatedAtDateTimeOffsetДаentityСоздание записи (= бронирование слота).
UpdatedAtDateTimeOffsetДаentityПоследнее изменение.
VersionintДаentityВерсия строки — арбитраж конкурентных решений «агент ↔ инвестор ↔ worker».

Алгоритм

  1. Контекст и доступ. Актор — агент/консультант с permission calendar.appointments.view (иначе APPOINTMENT_ACCESS_DENIED). AgentUserId и TenantId берутся из токена, не из запроса.
  2. Проверка входа. Enum-значения Status/Kind (если заданы) допустимы; To > From, если заданы оба; paging в границах — иначе VALIDATION_FAILED.
  3. Что читаем. Страница записей актора по фильтрам (по «И»), сортировка StartAt убыв., затем Id. Ведущий индекс — ix_appointments_agent_start (AgentUserId, StartAt). Для строк страницы подтягиваются ProjectId/PropertyObjectId/Location слота и InvestorDisplayName из display-реплики identity; отсутствие строки реплики (лаг консюмера) — не ошибка, поле null.
  4. Результат. Страница AgentAppointmentModel → HTTP 200. Пустая выборка — 200 с Items = [], не 404. Записи — из source of truth calendar, всегда актуальны; display-имя — eventual consistency реплики (лаг обычно < 1 мин).

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
VALIDATION_FAILED400enum Status/Kind вне значений; To ≤ From; paging вне границ«Некорректные параметры выборки»no retry
UNAUTHORIZED401нет/просрочен JWT«Требуется авторизация»no retry
APPOINTMENT_ACCESS_DENIED403нет permission calendar.appointments.view«Недостаточно прав»no retry

Acceptance Criteria

  • AC-1 (happy path). Given у агента записи во всех статусах. When GET без фильтров. Then 200, страница AgentAppointmentModel, сортировка StartAt убыв.; у каждой строки заполнены InvestorDisplayName (из реплики) и контекст слота.
  • AC-2 (ожидающие решения). Given три Pending-записи с разными PendingExpiresAt. When GET ?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.

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

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

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