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

UC-CAL-001. Агент ведёт рабочий календарь

Назначение

Описывает базовый сценарий рабочего календаря: агент (или консультант ПИН) просматривает свои события за период, создаёт события вручную (показ, консультация, встреча, задача), редактирует их, переносит и ведёт по жизненному циклу «запланировано → в работе → завершено/отменено». Календарь показывает пересечения событий мягко (признак конфликта, без блокировки), а события, порождённые записями инвесторов и бронями CRM, защищены от прямого редактирования. Связан с контекстом calendar.

Metadata

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

Актор

ПолеЗначение
РольАгент (владелец календаря); те же операции доступны консультанту ПИН в своём календаре
Permissions / scopecalendar.events.view (просмотр), calendar.events.manage (создание/изменение/переходы статусов) — scope Organization
TenantTenantId = CompanyId агентства (для консультанта ПИН — компания ПИН), ADR-0052
Record-levelУправляет только собственными событиями (OwnerUserId = актор); чтение чужих календарей — отдельный сценарий UC-CAL-009

Предусловие

  • Пользователь авторизован, активен и состоит в компании (агентство / ПИН) с ролью, дающей calendar.events.view / calendar.events.manage.
  • Для привязки события к клиенту/сделке/объекту соответствующие идентификаторы известны агенту (клиент из CRM, объект из каталога) — календарь хранит их как логические ссылки без синхронной проверки в чужих контекстах.

Триггер

ПолеЗначение
ИсточникUI button
Триггерящий элементЛК агента → «Календарь» (просмотр, «Создать событие», карточка события)

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

  1. Агент открывает календарь; клиент запрашивает события за период (../api/agent/list-events.md, диапазон from/to — представление день/неделя/месяц собирает клиент, ADR-0064 п.2).
  2. Система возвращает события владельца за период: Kind, Origin, Status, Priority, StartAt/EndAt/TimeZone, заголовок/место, ссылки (ClientUserId/DealId/BookingId/ PropertyObjectId) и признак HasConflicts для каждого события, пересекающегося по времени с другим Planned/InProgress-событием владельца (INV-CAL-6, calendar-event.md).
  3. Агент нажимает «Создать событие» и заполняет форму: Kind, StartAt/EndAt (UTC), TimeZone (IANA), Title?, Description?, Location?, MeetingLink?, Priority, ClientUserId?, DealId?/LeadId?, PropertyObjectId?/ProjectId?, ReminderOffsetsMinutes (default {1440, 60}); клиент вызывает ../api/agent/create-event.md.
  4. Система проверяет вход: интервал корректен (INV-CAL-1: EndAt > StartAt; точечное событие без EndAt — только Task), матрица kind-обязательностей соблюдена (INV-CAL-5: MeetingLink — только Consultation/Meeting; BookingDeadline вручную не создаётся — Origin = CrmBooking порождает только consumer), TimeZone — валидный IANA-идентификатор, офсеты напоминаний в ограничениях (≤ 5, диапазон 5..40320, без дублей). Любое нарушение → отказ 400 (таблица ошибок).
  5. Система создаёт событие Status = Planned, Origin = Manual, CreatedByUserId = актор; в том же согласованном изменении материализует reminder-строки по офсетам (event-reminder.md, офсеты с уже прошедшим DueAt — сразу Skipped) и публикует pin.calendar.event-created.v1.
  6. Агент ведёт событие по жизненному циклу (../api/agent/start-event.md, complete-event.md, cancel-event.md): Planned → InProgress (начал), Planned/InProgress → Completed (завершил), Planned/InProgress → Cancelled (отменил — CancelReason обязателен). Каждый переход валидируется матрицей статусов (INV-CAL-2) и публикует pin.calendar.event-status-changed.v1; при отмене/завершении все Pending-напоминания переводятся в Skipped.
  7. Перенос (../api/agent/reschedule-event.md): агент задаёт новый StartAt — длительность события сохраняется (EndAt пересчитывается), статус не меняется (допустим для Planned/InProgress); Pending-напоминания пересоздаются от нового StartAt; публикуется pin.calendar.event-rescheduled.v1.
  8. Правка полей (../api/agent/update-event.md): заголовок, описание, место, ссылка, приоритет, привязки, офсеты напоминаний — с повторной проверкой INV-CAL-1/5; при изменении офсетов Pending-напоминания пересоздаются.
  9. Агент видит обновлённый календарь; уведомления о переносах/статусах доставляет notifications (consumer ../../notifications/consumers/calendar-events.md).

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

A1. Пересечение с существующим событием (soft-conflict)

  1. Новое или перенесённое событие пересекается по времени с другим Planned/InProgress-событием владельца. Создание/перенос не блокируется (INV-CAL-6): агент сам решает, как разрулить занятость.
  2. Обе стороны пересечения возвращаются в выборках с HasConflicts = true; клиент подсвечивает конфликт. Возвращается к основному потоку.

A2. Событие порождено записью инвестора

  1. Агент пытается изменить/перенести/отменить событие Origin = Appointment прямыми операциями → отказ CAL_EVENT_MANAGED_BY_APPOINTMENT (409, INV-CAL-3).
  2. Управлять таким событием можно только операциями записи (подтвердить/отклонить/отменить/зафиксировать исход — UC-CAL-004, UC-CAL-005, UC-CAL-006); каскад записи сам изменит событие.

A3. Событие — дедлайн брони CRM

  1. Агент пытается изменить событие Origin = CrmBooking → отказ CAL_EVENT_MANAGED_BY_CRM_BOOKING (409, INV-CAL-4). Дедлайны живут по booking-событиям CRM (UC-CAL-007).

A4. Операция над терминальным событием

  1. Любой переход/правка события в Completed/Cancelled → отказ CAL_EVENT_ALREADY_TERMINAL (409, INV-CAL-7). Клиент перечитывает событие и снимает управляющие кнопки.

A5. Конкурентная правка

  1. Два изменения одного события (агент с двух устройств; агент ↔ worker) — побеждает первая зафиксированная мутация (оптимистическая блокировка по версии строки); проигравший получает 409, перечитывает событие и повторяет действие уже на актуальном состоянии.

A6. Некорректный вход

  1. EndAt ≤ StartAt или отсутствует у интервального KindCAL_EVENT_INVALID_INTERVAL; нарушение матрицы kind-полей → CAL_EVENT_KIND_FIELDS_MISMATCH; невалидная таймзона → CAL_EVENT_TIMEZONE_UNKNOWN; офсеты вне ограничений → CAL_EVENT_REMINDER_OFFSETS_INVALID; отмена без причины → CAL_EVENT_CANCEL_REASON_REQUIRED. Агент исправляет форму и повторяет.

Постусловие

  • Ручные события агента существуют в целевых статусах; Origin = Manual, CreatedByUserId заполнен.
  • Reminder-строки согласованы с актуальными StartAt и офсетами каждого события (INV-REM-1).
  • По каждому созданию/переносу/переходу опубликовано соответствующее событие pin.calendar.* — история изменений восстановима из потока событий.
  • События Origin = Appointment/CrmBooking прямыми операциями не изменены.

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

КодHTTPУсловиеПоведение клиента
CAL_EVENT_NOT_FOUND404Событие не найдено или вне tenant актораВернуться к списку
CAL_EVENT_ACCESS_DENIED403Актор — не владелец событияСкрыть управляющие действия
CAL_EVENT_INVALID_INTERVAL400INV-CAL-1 (A6)Подсветить поля времени
CAL_EVENT_KIND_FIELDS_MISMATCH400INV-CAL-5 (A6)Подсветить несовместимые поля
CAL_EVENT_TIMEZONE_UNKNOWN400Невалидный IANA-идентификатор (A6)Подсветить таймзону
CAL_EVENT_REMINDER_OFFSETS_INVALID400Офсеты вне ограничений (A6)Подсветить офсеты
CAL_EVENT_CANCEL_REASON_REQUIRED400Отмена без причины (A6)Запросить причину
CAL_EVENT_INVALID_STATE409Переход вне матрицы статусов (INV-CAL-2)Перечитать событие
CAL_EVENT_ALREADY_TERMINAL409Операция над Completed/Cancelled (A4)Перечитать событие
CAL_EVENT_MANAGED_BY_APPOINTMENT409Прямая операция над событием записи (A2)Открыть карточку записи
CAL_EVENT_MANAGED_BY_CRM_BOOKING409Прямая операция над дедлайном брони (A3)Открыть карточку брони

Kafka Интеграционные События

СобытиеTopicКогда публикуетсяConsumers
CalendarEventCreatedEvent.v1pin.calendar.event-created.v1Создание события (шаг 5)notifications
CalendarEventRescheduledEvent.v1pin.calendar.event-rescheduled.v1Перенос (шаг 7)notifications (CalendarEventRescheduled)
CalendarEventStatusChangedEvent.v1pin.calendar.event-status-changed.v1Каждый переход статуса (шаг 6)notifications

Payload — без PII (Title/Description/Location в события не попадают); ключ партиционирования — EventId (порядок изменений одного события гарантирован).

Acceptance Criteria

#КритерийПроверка
AC1Создание Consultation с корректным интервалом и MeetingLink даёт событие Planned (Origin = Manual), reminder-строки по офсетам и event-created — атомарноинтеграционный тест создания
AC2start переводит Planned → InProgress (не Cancelled — фикс бага черновика, ADR-0064 п.2) и публикует event-status-changedтест перехода
AC3Showing без EndAtCAL_EVENT_INVALID_INTERVAL; Showing с MeetingLinkCAL_EVENT_KIND_FIELDS_MISMATCHнегативные тесты матрицы
AC4Перенос сохраняет длительность, пересоздаёт Pending-напоминания от нового StartAt и публикует event-rescheduled; Sent-строки не тронутытест переноса
AC5Пересекающиеся Planned-события создаются без блокировки и оба возвращаются с HasConflicts = trueтест выборки с пересечением
AC6Прямые операции над Origin = Appointment / CrmBooking → 409 (MANAGED_BY_APPOINTMENT / MANAGED_BY_CRM_BOOKING)негативные тесты защиты
AC7Отмена без CancelReason → 400; отмена с причиной переводит Pending-напоминания в Skippedтест отмены
AC8Повторный переход/правка терминального события → CAL_EVENT_ALREADY_TERMINAL, событий не публикуетсятест идемпотентности guard-ов
AC9Выборка list-events возвращает только события OwnerUserId = актор в диапазоне from/toтест изоляции выборки

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

  • Логи (без PII). calendar-event-created / -rescheduled / -status-changed (eventId, tenantId, ownerUserId, kind, origin, fromStatus?, toStatus?); Title/Description/Location в логи не пишутся (могут содержать персональные данные клиента).
  • Метрики. calendar_events_total{kind,origin,status}; calendar_event_conflicts_returned_total (объём soft-конфликтов); calendar_event_forbidden_ops_total{reason} (попытки править managed-события).

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

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

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

  • Domain: calendar-event.md (поля, enum, INV-CAL-1..7, статус-машина), event-reminder.md (материализация напоминаний).
  • API: ../api/agent/list-events.md, get-event.md, create-event.md, update-event.md, reschedule-event.md, start-event.md, complete-event.md, cancel-event.md.
  • События: ../events/event-created.md, ../events/event-rescheduled.md, ../events/event-status-changed.md.
  • ADR: 0064 контекст calendar (маппинг черновика, фикс «start → InProgress», таймзоны).
  • Связанные UC: UC-CAL-007 (автособытия дедлайнов), UC-CAL-008 (доставка напоминаний), UC-CAL-009 (календарь команды).