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

UC-CAL-002. Агент публикует слоты доступности

Назначение

Описывает сценарий публикации доступности: агент (или инвест-консультант ПИН) задаёт локальные окна («Вт, Чт 10:00–13:00»), дни недели, длительность слота и горизонт — система нарезает окна на конкретные слоты и выставляет их на публичную витрину для самозаписи инвесторов. Генерация DST-корректна (окна считаются в локальном времени агента), батч ограничен 500 слотами, результат — отчёт «создано/пропущено с причинами». Связан с контекстом calendar.

Metadata

ПолеЗначение
IDUC-CAL-002
Контекстcalendar (см. docs/06-services/calendar/)
Типосновной
Статусdraft

Актор

ПолеЗначение
РольАгент (или консультант ПИН) — владелец публикуемой доступности
Permissions / scopecalendar.slots.manage — scope Organization
TenantTenantId = CompanyId агентства (для консультанта ПИН — компания ПИН), ADR-0052
Record-levelСлоты создаются только на себя (AgentUserId = актор); публикация за другого агента не поддерживается

Предусловие

  • Пользователь авторизован, активен и имеет calendar.slots.manage.
  • Агент знает свою рабочую таймзону; default-значение предзаполняется из профиля (Timezone) — при недоступности profile таймзона указывается вручную (деградация UX, операция не блокируется).

Триггер

ПолеЗначение
ИсточникUI button
Триггерящий элементЛК агента → «Календарь» → «Доступность» → «Опубликовать слоты»

Основной Поток

  1. Агент открывает форму публикации и задаёт параметры батча: вид встречи (Kind ∈ {Showing, Consultation}), горизонт (локальные даты DateFrom/DateTo), дни недели, локальные окна (HH:mm–HH:mm, до 8), длительность слота (минуты), TimeZone (IANA), AutoConfirm (default false), CancellationCutoffMinutes (default 120), MinLeadTimeMinutes (default 60), опционально ProjectId/PropertyObjectId/Location; клиент вызывает publish-slots.
  2. Система проверяет вход: окна корректны (начало < конца), длительность > 0, дни недели непусты, горизонт не целиком в прошлом (иначе CAL_SLOT_BATCH_INVALID_WINDOW); TimeZone — валидный IANA-идентификатор (иначе CAL_SLOT_TIMEZONE_UNKNOWN); Kind — только Showing/Consultation (иначе VALIDATION_FAILED).
  3. Система нарезает кандидатов: локальные окна × подходящие дни недели горизонта × длительность; неполный хвост окна отбрасывается. Вычисление — в локальном времени агента по IANA-таймзоне, хранение — в UTC: окно «10:00–13:00» остаётся 10:00 локальными по обе стороны перевода часов; несуществующее локальное время дня перевода — кандидат пропускается (NonExistentLocalTime), неоднозначное — первое вхождение (availability-slot.md, правила batch-генерации).
  4. Если кандидатов больше 500 — весь батч отклоняется (CAL_SLOT_BATCH_LIMIT_EXCEEDED), ничего не создаётся.
  5. Система фильтрует кандидатов: StartAt < now + MinLeadTimeMinutes → пропуск (LeadTime); пересечение с существующими слотами агента в Available/Booked или друг с другом → пропуск (Overlap, INV-SLT-2). Пропуск — не ошибка: кандидат попадает в отчёт с причиной.
  6. Оставшиеся кандидаты создаются слотами Available с общим BatchId и параметрами батча. Публикация атомарна: либо создан весь отчётный набор, либо ничего. Интеграционные события не публикуются — публикация слотов внутреннее дело контекста.
  7. Система возвращает отчёт публикации: BatchId, CreatedCount, SkippedCount, список пропущенных кандидатов с причинами (Overlap / LeadTime / NonExistentLocalTime).
  8. Созданные слоты немедленно видны на анонимной витрине (../api/public/list-available-slots.md) и в списке слотов агента (../api/agent/list-slots.md); инвесторы записываются на них — UC-CAL-003.

Альтернативные Потоки

A1. Повторная публикация тех же окон

  1. Агент повторяет публикацию батча с теми же параметрами (double-click, retry клиента). Все кандидаты пересекаются с уже созданными слотами и пропускаются по INV-SLT-2 — отчёт «создано 0, пропущено N (Overlap)». Дублей слотов структурно не возникает; отдельный ключ идемпотентности не нужен.

A2. Батч слишком большой

  1. Параметры порождают > 500 кандидатов → отказ CAL_SLOT_BATCH_LIMIT_EXCEEDED, ни один слот не создан. Агент уменьшает горизонт или окна и повторяет.

A3. Частично прошедший горизонт

  1. Горизонт начинается в прошлом, но не целиком: прошедшие кандидаты и кандидаты ближе MinLeadTimeMinutes пропускаются (LeadTime), будущие создаются. Отчёт отражает оба исхода. Горизонт целиком в прошлом → CAL_SLOT_BATCH_INVALID_WINDOW (шаг 2).

A4. Уборка опубликованного

  1. Агент архивирует ненужный слот (../api/agent/archive-slot.md): Available/ExpiredArchived (слот уходит с витрины).
  2. Слот Booked архивировать напрямую нельзя → отказ CAL_SLOT_BOOKED (INV-SLT-4): сначала нужно разрешить судьбу записи (отменить/отклонить — UC-CAL-004, UC-CAL-005); слот освободится каскадом записи.

A5. Невостребованные слоты

  1. Available-слот, чей StartAt наступил, переводится worker-ом (../background/slot-expiration.md) в Expired и уходит с витрины; действий агента не требуется. Повторный проход worker-а — no-op.

Постусловие

  • В tenant агента существует батч слотов Available с общим BatchId; каждый слот удовлетворяет INV-SLT-1/2 (интервал корректен, пересечений с активными слотами агента нет).
  • Слоты видны на публичной витрине и доступны для бронирования до StartAt − MinLeadTimeMinutes.
  • Отчёт публикации выдан агенту; пропущенные кандидаты не создали строк.

Возможные Ошибки

КодHTTPУсловиеПоведение клиента
VALIDATION_FAILED400Kind вне {Showing, Consultation}, некорректный формат полейПодсветить поля
CAL_SLOT_BATCH_INVALID_WINDOW400Окно/длительность/дни недели/горизонт некорректны (шаг 2, A3)Подсветить параметры окон
CAL_SLOT_TIMEZONE_UNKNOWN400Невалидный IANA-идентификаторПодсветить таймзону
CAL_SLOT_BATCH_LIMIT_EXCEEDED400Батч породил бы > 500 слотов (A2)Предложить сузить горизонт/окна
CAL_SLOT_ACCESS_DENIED403Нет calendar.slots.manageСкрыть публикацию
CAL_SLOT_NOT_FOUND404Архивация несуществующего/чужого слота (A4)Обновить список слотов
CAL_SLOT_BOOKED409Прямая архивация Booked-слота (A4, INV-SLT-4)Открыть карточку записи

Acceptance Criteria

#КритерийПроверка
AC1Окно «Вт, Чт 10:00–13:00 Europe/Moscow», 60 мин, горизонт 2 недели → создано 12 слотов Available (по 3 на каждый из 4 дней), время в UTC, отчёт «создано 12, пропущено 0»интеграционный тест генерации
AC2Горизонт через перевод часов: слоты по обе стороны начинаются в 10:00 локальных (разные UTC-моменты); несуществующее локальное время — пропуск NonExistentLocalTime, двойных/пропавших слотов неттест DST
AC3Кандидат, пересекающийся с существующим Available/Booked-слотом агента, пропускается с причиной Overlap; непересекающиеся кандидаты того же батча создаютсятест пересечений
AC4Батч из 600 кандидатов → CAL_SLOT_BATCH_LIMIT_EXCEEDED, ни один слот не создан (атомарность)негативный тест лимита
AC5Повторная публикация тех же окон → «создано 0», все кандидаты Overlap; дублей нет (A1)тест идемпотентности
AC6Прямая архивация Booked-слота → CAL_SLOT_BOOKED; слот и запись не изменены (A4)негативный тест защиты
AC7Публикация атомарна: при сбое посреди создания не существует частичного батчатест атомарности
AC8Созданные слоты сразу возвращаются анонимной витриной свободных слотовтест витрины

Наблюдаемость

  • Логи (без PII). slots-published (tenantId, agentUserId, batchId, createdCount, skippedCount); Location в логи не пишется (может содержать адрес).
  • Метрики. calendar_slots_published_total{result}, calendar_slots_skipped_total{reason} — рост Overlap без роста созданных сигналит о повторных публикациях; calendar_slots_expired_total (worker).

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

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

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

  • Domain: availability-slot.md (поля, правила batch-генерации, INV-SLT-1..5, SlotStatus, stateDiagram).
  • API: publish-slots.md (контракт батча и отчёта), ../api/agent/list-slots.md, ../api/agent/archive-slot.md, ../api/public/list-available-slots.md.
  • Worker: ../background/slot-expiration.md (истечение невостребованных слотов).
  • ADR: 0064 контекст calendar п.3/6 (материализованные слоты, DST, лимит батча).
  • Связанные UC: UC-CAL-003 (бронирование опубликованных слотов), UC-CAL-001 (ручные события того же календаря).