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

POST /api/v1/agent/calendar/slots — Публикация слотов доступности (batch)

Назначение

Агент (или инвест-консультант ПИН) публикует свою доступность для самозаписи клиентов: задаёт локальные окна («Вт, Чт 10:00–13:00»), дни недели, длительность слота и горизонт — сервис нарезает окна на конкретные слоты (availability-slot.md) и выставляет их на витрину (../public/list-available-slots.md). Ответ — отчёт публикации: сколько слотов создано и какие кандидаты пропущены с причинами. Поштучного создания слотов нет — только batch.

Metadata

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

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

ПроверкаЗначение
Actor typeUser (Агент или консультант ПИН — владелец публикуемых слотов)
Auth policy / Permissionscalendar.slots.manage
Scope (RBAC)организация — TenantId = компания актора (агентство / ПИН)
Record-levelслоты создаются только на себя: AgentUserId = actor (публикация за другого агента не поддерживается)
Tenant isolationTenantId в каждом запросе (ADR-0052)
Auditcreate; BatchId — след публикации

HTTP-контракт

ПолеЗначение
MethodPOST
Route/api/v1/agent/calendar/slots
Success status201 (батч создан; в отчёте возможно CreatedCount = 0)
Idempotency headerне применяется: повтор батча с теми же окнами структурно безопасен — все кандидаты пересекаются с уже созданными слотами и пропускаются (INV-SLT-2), отчёт «создано 0»
Correlationсквозной trace через заголовок traceparent

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

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
TenantIdidentity claimsGuidДаRequired (компания актора)Tenant isolation
KindbodyEventKindДатолько Showing / Consultation (подмножество EventKind); иное значение → VALIDATION_FAILED
DateFrombodyDateOnlyДалокальная дата начала горизонта (в TimeZone)
DateTobodyDateOnlyДа>= DateFrom; горизонт целиком в прошлом → CAL_SLOT_BATCH_INVALID_WINDOW
DaysOfWeekbodyDayOfWeek[]Да1..7 значений Monday..Sunday, без дублей; пустой массив → CAL_SLOT_BATCH_INVALID_WINDOW
WindowsbodySlotWindowModel[]Да1..8 окон; в каждом StartLocalTime < EndLocalTime, иначе → CAL_SLOT_BATCH_INVALID_WINDOW
SlotDurationMinutesbodyintДа1..1440; <= 0CAL_SLOT_BATCH_INVALID_WINDOW
TimeZonebodystringДавалидный IANA-идентификатор, max 64; неизвестный → CAL_SLOT_TIMEZONE_UNKNOWN
AutoConfirmbodyboolНетdefault false — запись на слот требует решения агента
CancellationCutoffMinutesbodyintНет>= 0, default 120 (INV-APT-3)
MinLeadTimeMinutesbodyintНет>= 0, default 60 (INV-SLT-5)
ProjectIdbodyGuid?Нетлогическая ссылка на проект (chessboard), синхронно не проверяется
PropertyObjectIdbodyGuid?Нетлогическая ссылка на объект недвижимости (chessboard), синхронно не проверяется
Locationbodystring?Нет0..512, свободный текст (ADR-0064 п.7)может содержать адрес — в логи не пишется

Модель SlotWindowModel

ПолеТипОбязательностьВалидацияОписание
StartLocalTimeTimeOnlyДаформат HH:mmНачало окна доступности в локальном времени TimeZone.
EndLocalTimeTimeOnlyДаформат HH:mm, > StartLocalTimeКонец окна.

Модель ответа PublishSlotsResult

ПолеТипОбязательностьИсточникMaskingОписание
BatchIdGuidДагенерируется публикациейИдентификатор батча — ключ отчётов и массовой архивации (archive-slot.md).
CreatedCountintДаcomputedСколько слотов создано (Available).
SkippedCountintДаcomputedСколько кандидатов пропущено.
SkippedSkippedSlotCandidateModel[]ДаcomputedПропущенные кандидаты с причинами; пустой массив, если пропусков нет.

Модель SkippedSlotCandidateModel

ПолеТипОбязательностьОписание
StartAtDateTimeOffsetДаНачало пропущенного кандидата, UTC.
EndAtDateTimeOffsetДаКонец кандидата, UTC.
ReasonSlotSkipReasonДаПричина пропуска, см. enum.

Enum SlotSkipReason

ЗначениеОписание
Overlap = 1Кандидат пересекается с существующим слотом агента (Available/Booked) или с другим кандидатом батча (INV-SLT-2).
LeadTime = 2StartAt кандидата раньше now + MinLeadTimeMinutes (INV-SLT-5).
NonExistentLocalTime = 3Локальное время кандидата не существует в день перевода часов (DST).

Алгоритм

Правила генерации — дом availability-slot.md, раздел «Правила batch-генерации»; здесь — порядок применения.

  1. Контекст и доступ. Актор — владелец календаря с permission calendar.slots.manage (иначе CAL_SLOT_ACCESS_DENIED); слоты создаются на AgentUserId = actor в его TenantId.
  2. Проверка входа. TimeZone — валидный IANA-идентификатор (иначе CAL_SLOT_TIMEZONE_UNKNOWN); Kind ∈ {Showing, Consultation}, длина Location ≤ 512 (иначе VALIDATION_FAILED); окна, дни недели, длительность и горизонт корректны — начало окна < конца, длительность > 0, дни недели непусты, горизонт не целиком в прошлом (иначе CAL_SLOT_BATCH_INVALID_WINDOW).
  3. Нарезка кандидатов. Кандидаты = локальные окна × подходящие дни недели горизонта × длительность: каждое окно нарезается подряд идущими интервалами SlotDurationMinutes; неполный хвост окна отбрасывается. Вычисление — в локальном времени TimeZone, хранение — UTC (DST-корректность: несуществующее локальное время → пропуск NonExistentLocalTime, неоднозначное — первое вхождение).
  4. Лимит. Если кандидатов больше 500 → CAL_SLOT_BATCH_LIMIT_EXCEEDED, батч целиком отклоняется, ничего не создаётся.
  5. Фильтрация. Кандидаты с StartAt < now + MinLeadTimeMinutes пропускаются (LeadTime); кандидаты, пересекающиеся с существующими слотами агента в Available/Booked или друг с другом, пропускаются (Overlap, INV-SLT-2).
  6. Создание. Оставшиеся кандидаты создаются слотами Available с общим BatchId и параметрами батча (Kind, AutoConfirm, CancellationCutoffMinutes, MinLeadTimeMinutes, TimeZone, ProjectId/PropertyObjectId/Location). Публикация атомарна: либо создан весь отчётный набор, либо ничего.
  7. Результат. { BatchId, CreatedCount, SkippedCount, Skipped[] } → HTTP 201. Созданные слоты сразу видны на витрине свободных слотов.

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
VALIDATION_FAILED400Kind вне {Showing, Consultation}, Location длиннее 512, некорректный формат полей«Проверьте корректность полей запроса»no retry
CAL_SLOT_BATCH_INVALID_WINDOW400начало окна ≥ конца, длительность ≤ 0, пустые дни недели, DateTo < DateFrom, горизонт целиком в прошлом«Некорректные параметры окон доступности»no retry
CAL_SLOT_TIMEZONE_UNKNOWN400TimeZone — не валидный IANA-идентификатор«Неизвестная таймзона»no retry
CAL_SLOT_BATCH_LIMIT_EXCEEDED400батч породил бы более 500 слотов«Слишком большой батч: уменьшите горизонт или окна»no retry
UNAUTHORIZED401нет/просрочен JWT«Требуется авторизация»no retry
CAL_SLOT_ACCESS_DENIED403нет permission calendar.slots.manage«Недостаточно прав»no retry

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

  • Новые optional параметры батча — additive; изменение семантики генерации (лимит, правила пропуска), удаление/переименование полей — breaking. Unknown enum во входе (Kind, DaysOfWeek) — VALIDATION_FAILED, не игнор.
  • Logs: slots published (TenantId, AgentUserId, BatchId, CreatedCount, SkippedCount; Location не пишется); metrics: calendar_slots_published_total{result}, calendar_slots_skipped_total{reason}, latency p95 < 1 c; traces — сквозной trace через traceparent.

Постусловия

После 201: в TenantId актора существует батч слотов Available с общим BatchId; каждый созданный слот удовлетворяет INV-SLT-1/2; слоты немедленно видны анонимной витрине (../public/list-available-slots.md) и в списке агента (list-slots.md). Интеграционные события не публикуются — публикация слотов внутреннее дело контекста (availability-slot.md).

Acceptance Criteria

  • AC-1 (генерация). Given окно «Вт, Чт 10:00–13:00 Europe/Moscow», длительность 60 мин, горизонт 2 недели. When публикация. Then 201: создано 12 слотов Available (по 3 на каждый из 4 дней), время в UTC, SkippedCount = 0.
  • AC-2 (DST). Given окно 10:00–12:00 в таймзоне с переводом часов, горизонт охватывает дату перевода. When публикация. Then слоты по обе стороны перевода начинаются в 10:00 локальных (разные UTC-моменты); несуществующее локальное время — пропуск NonExistentLocalTime.
  • AC-3 (пересечение). Given у агента уже есть Available-слот 10:00–11:00 на вторник. When батч с окном 10:00–12:00 на вторник. Then кандидат 10:00–11:00 в Skipped (Overlap), кандидат 11:00–12:00 создан.
  • AC-4 (лимит). Given параметры порождают 600 кандидатов. When публикация. Then 400 CAL_SLOT_BATCH_LIMIT_EXCEEDED, ни один слот не создан.
  • AC-5 (повтор батча). Given батч уже опубликован. When повторная публикация тех же окон. Then 201 с CreatedCount = 0 — все кандидаты пропущены как Overlap, дублей нет.
  • AC-6 (таймзона). Given TimeZone = "Europe/Nowhere". When публикация. Then 400 CAL_SLOT_TIMEZONE_UNKNOWN.
  • AC-7 (нет прав). Given актор без calendar.slots.manage. When публикация. Then 403 CAL_SLOT_ACCESS_DENIED.

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

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

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

  • Сущность: availability-slot.md (дом полей, правила генерации, INV-SLT-1..5, SlotStatus); порождаемые записи: appointment.md.
  • Соседние операции: list-slots.md, archive-slot.md; витрина и бронирование: ../public/list-available-slots.md, ../public/book-slot.md; истечение слотов: ../../background/slot-expiration.md.
  • ADR: 0064 calendar п.3/6 (материализованные слоты, DST, лимит батча), 0056 simplicity-first.