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

GET /api/v1/investor/calendar/appointments — Мои записи на встречи

Назначение

Отдаёт инвестору список его записей к агентам и консультантам ПИН (appointment.md): предстоящие и прошедшие, с их статусами, временем и правилами отмены. Инвестор видит только собственные записи (record-level по InvestorUserId) — включая созданные анонимно по его телефону через book-slot. Документ — дом модели MyAppointmentModel: остальные investor-операции записей ссылаются сюда.

Metadata

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

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

ПроверкаЗначение
Actor typeUser (Инвестор с JWT)
Auth policy / Permissionsnone (достаточно аутентификации инвестора)
Scope (RBAC)не применяется — инвестор вне tenant (ADR-0052)
Record-levelInvestorUserId = ActorUserId из токена (sub), не из query — чужие записи в выборку не попадают (fail-closed)
Tenant isolationне применяется: выборка пересекает tenants агентов — изоляция обеспечивается record-level фильтром
Auditread: актор и фильтры фиксируются в trace; Comment в логи не пишется

HTTP-контракт

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

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

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

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
ActorUserIdтокен (sub)GuidДаRequired; не принимается из queryrecord-level фильтр InvestorUserId
StatusqueryAppointmentStatus?Нетenum-строка Pending/Confirmed/Declined/Cancelled/Expired/Completed/NoShow (appointment.md)
ActiveOnlyquerybool?Нетdefault false; true — только активные (Pending/Confirmed); несовместим с Status (задать можно что-то одно)
KindqueryEventKind?Нетenum-строка Showing/Consultation
From / ToqueryDateTimeOffset?НетUTC; окно по StartAt; To > From, To − From ≤ 366 дней
Page / PageSizequeryintНетPage ≥ 1; PageSize 1..200, default 50

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

Ответ — страница с полями Items, Page, PageSize, TotalCount; сортировка — StartAt по убыванию, затем Id (ближайшие и недавние сверху). Дом модели — этот документ; поля — investor-проекция appointment.md + display-данные агента и место встречи из слота. Внутренние поля (TenantId, SlotId, CalendarEventId, Version) инвестору не отдаются.

ПолеТипОбязательностьИсточникMaskingОписание
IdGuidДаentityИдентификатор записи.
AgentUserIdGuidДаentityАгент/консультант.
AgentDisplayNamestring?Нетdisplay-реплика identityбез контактовОтображаемое имя агента; null, если реплика ещё не получила пользователя.
KindEventKindДаentityВид встречи: Showing / Consultation.
StatusAppointmentStatusДаentityТекущий статус записи.
StartAtDateTimeOffsetДаentity (снапшот слота)Начало встречи, UTC.
EndAtDateTimeOffsetДаentity (снапшот слота)Конец встречи, UTC.
TimeZonestringДаentity (снапшот слота)IANA-таймзона — локальное отображение.
Commentstring?Нетentityв логи не пишетсяСобственный комментарий инвестора, оставленный при записи.
PendingExpiresAtDateTimeOffset?НетentityДля Pending: до какого момента агент примет решение (после — запись истечёт).
ConfirmedAtDateTimeOffset?НетentityМомент подтверждения.
CompletedAtDateTimeOffset?НетentityМомент фиксации исхода (Completed/NoShow).
CancelReasonstring?НетentityПричина отклонения/отмены (заполнена при Declined/Cancelled).
CancelledByAppointmentCancelledBy?НетentityКто отменил (Investor/Agent/System).
RescheduledFromAppointmentIdGuid?НетentityЗапись, из которой выполнен перенос.
RescheduledToAppointmentIdGuid?НетentityЗапись, в которую выполнен перенос (у отменённой при переносе).
ProjectIdGuid?НетслотПроект застройщика (логическая ссылка).
PropertyObjectIdGuid?НетслотОбъект недвижимости (логическая ссылка).
Locationstring?НетслотМесто встречи — свободный текст.
CancellationCutoffMinutesintДапараметр слотаПравило отмены (INV-APT-3).
CancellableUntilDateTimeOffsetДаcomputed: StartAt − CancellationCutoffMinutesДо какого момента доступны отмена и перенос; для терминальных статусов носит справочный характер.
CreatedAtDateTimeOffsetДаentityМомент бронирования.

Алгоритм

  1. Контекст и доступ. Актор — аутентифицированный инвестор; InvestorUserId берётся из токена, не из запроса.
  2. Проверка входа. Enum-значения Status/Kind (если заданы) допустимы; ActiveOnly = true и Status одновременно не заданы; To > From, окно ≤ 366 дней; paging в границах — иначе VALIDATION_FAILED.
  3. Что читаем. Страница записей InvestorUserId = actor с наложением фильтров по «И» (при ActiveOnly = trueStatus ∈ {Pending, Confirmed}), сортировка StartAt убыв., затем Id; display-имена агентов и место встречи (из слота) дополняются одним заходом.
  4. Результат. Страница MyAppointmentModel → HTTP 200; пустая выборка — 200 с Items = [], не 404. Данные — из source of truth calendar, актуальны на момент чтения.

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
VALIDATION_FAILED400enum вне значений; ActiveOnly вместе со Status; To ≤ From; окно > 366 дней; paging вне границ«Некорректные параметры выборки»no retry
UNAUTHORIZED401нет/просрочен JWTno retry

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

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

Acceptance Criteria

  • AC-1 (happy). Дано: у инвестора 2 активные и 3 завершённые записи. Когда: GET без фильтров. Тогда: 200, все 5 записей, сортировка StartAt убыв., у каждой — CancellableUntil и AgentDisplayName без контактов.
  • AC-2 (только активные). Дано: ActiveOnly = true. Тогда: только Pending/Confirmed.
  • AC-3 (record-level). Дано: в системе есть записи других инвесторов. Тогда: в выборку они не попадают; параметры, пытающиеся подменить InvestorUserId, игнорируются (ключ — из токена).
  • AC-4 (анонимная бронь видна). Дано: запись создана анонимно по телефону инвестора через book-slot. Когда: инвестор вошёл по этому телефону и запросил список. Тогда: запись в выборке.
  • AC-5 (несовместимые фильтры). Дано: ActiveOnly = true и Status = Completed. Тогда: 400 VALIDATION_FAILED.
  • AC-6 (пустая выборка). Дано: записей нет. Тогда: 200, Items = [], TotalCount = 0.

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

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

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