Dev Specs

Epic E02 — Services & Task Templates [desktop]

Назначение эпика: Конструктор повторяющихся сервисов (Service) и шаблонов работ (TaskTemplate) + конфигуратор движка пересчёта (recalculation engine). Это «фабрика задач»: связка Service → Trigger → TaskTemplate материализуется движком в конкретные Task(Planned) по объектам. Ядро UC-1. Участвует в use-cases: UC-1 (плановая генерация задач), косвенно UC-2/UC-3 (Tasks/исполнение получают сгенерированные задачи). Сущности (owns/touches):

  • owns: Service (+ Service.trigger), TaskTemplate (+ SectionRoom|SystemTemplateTask), recalc-конфиг (S46 — read-only-визуализация в прототипе).
  • touches (пишет результат): Task (создаёт service-tasks через движок); reads Unit (connected_properties + Department Defaults для назначения), Reservation (триггеры reservation/vacancy). Экраны: S36 + S43 (Services & Templates list — две вкладки), S33 + S53 + S54 + S55 (New Service — Trigger-билдер, сведены в один экран с состояниями триггера), S01 (Task Template editor), S46 (recalculation rules). Открытые findings: F-filter-hierarchy-assignee (модель назначения/Vendors — overview §3, влияет на то, кому движок назначает service-task). Тексты-заглушки в прототипе («Description Description…») — реальные лейблы уточнить у заказчика.

S36 + S43 — Services & Templates list [desktop]

  • Назначение: Единый список конфигурации с двумя вкладками — Services (повторяющиеся сервисы) и Task Templates (шаблоны работ). Точка входа в создание/редактирование сервисов и шаблонов.
  • Источник (Miro): S36 «Copy of Copy of Copy of Templates» (id 3458764671469791898) = вкладка Services, расширенный вид; S43 «Copy of Copy of Copy of Copy of Templates» (id 3458764671485737364) = вид со столбцами Department/Sub-Department/Status. Сведены: один экран, переключатель вкладок Services / Task Templates, столбцы Services — объединение обоих вариантов.
  • Layout: header (заголовок Services and Templates) → tab-bar (Services | Task Templates) → fast-filters + filters → data-table → + add (создать). Detail открывается отдельным экраном (New/Edit Service — S33; Template editor — S01).
  • Данные на экране:
    • Вкладка Services (колонки, дословно): ID, Name, Department, Sub-Department, Task (имя связанного TaskTemplate), Trigger (тип: Time / Reservation / Vacancy), Created (дата), Status, Connected Properties. Примеры строк: AC Maintenance / Operations / Time, Daily Cleaning / Reservation, Daily Cleaning / Vacancy.
    • Вкладка Task Templates (колонки): ID, Name, Department, Sub-Department, Status. Примеры: Operations / AC Maintenance, Operations / Daily Cleaning.
  • Контролы и действия:
    • tab Services / Task Templates — переключение списка (toggle).
    • fast filters / filters — фильтрация по department/status/trigger (filter; общий компонент из overview §4).
    • row click — открыть Service на редактирование (→ S33 New/Edit Service) или Template (→ S01 editor) (button/navigation).
    • + add — создать новый Service (→ S33 пустой) или Template (→ S01 пустой), в зависимости от активной вкладки (button → create).
    • текстовый поиск по Name (input).
  • Состояния/вкладки: tab=Services / tab=Task Templates; empty (нет сервисов/шаблонов); filtered; loading; error.
  • Связи (data-flow): читает Service (+ trigger, connected_properties count) и TaskTemplate. Service ссылается на TaskTemplate (Task-колонка = template name). Изменение Service/Template Status → триггер пересчёта движка (S46) по connected properties.
  • Мутации: нет прямых на этом экране (только навигация); переключение Status (active/inactive) может быть инлайн — но в прототипе фиксируем как действие detail-экрана. 🟡 ниже.
  • Права: Coordinator/Operations — полный доступ (CRUD сервисов/шаблонов своего department). Accounting/Owner Services — read. Field staff — нет доступа.
  • 🟡 [не было додумано]:
    • Сведение S36+S43 в один экран — оба «Copy of Templates»-дубля показывают вкладку Services с разным набором колонок; в прототипе один экран, колонки = объединение. Обоснование: overview §IA называет модуль Services & Templates с вкладками Services · Task Templates.
    • Инлайн-toggle Status в строке списка — не подтверждён прототипом; основное место смены статуса — detail. Помечено как опциональное.
  • Edge cases: empty state на каждой вкладке (No services yet / + add); длинное Connected Properties (показывать count + tooltip-список); сервис без связанного TaskTemplate (Status не может быть active — см. S33 валидация).

S33 + S53 + S54 + S55 — New Service (Trigger builder) [desktop]

  • Назначение: Создание/редактирование сервиса: имя, department/subdepartment, связанный TaskTemplate, connected properties и Trigger — правило, по которому движок (S46) генерирует задачи. Центральный экран эпика: здесь задаётся «когда и как часто» рождаются задачи.
  • Источник (Miro): S55 «Copy of Copy of Copy of Copy of Templates» (id …482048855) = базовая форма New Service (Trigger пуст); S33 «Copy of Copy of Copy of Copy of Copy of Templates» (id …482778222) = состояние Trigger = Time (Every N day/week/month/quarter); S53 (id …485225086) = состояние Trigger = Reservation (On + Check In / Check Out / every 2 days of occupancy); S54 (id …485483255) = состояние Trigger = Vacancy (On + every 2 days of vacancy). Сведены в один экран с тремя состояниями Trigger.
  • Layout: header (Services and TemplatesNew Service) → tab-bar (Services | Task Templates) → форма: блок Service-метаданных (Name / Department / Subdepartment / связанный Template / Connected Properties) + блок Trigger (выбор типа → контекстные поля правила) → footer-кнопки Save · Cancel · Save and Activate.
  • Данные на экране:
    • Service-метаданные: Name, Department (напр. Operations), Subdepartment, связанный Task Template, Connected Properties (список units).
    • Блок Trigger (общий заголовок Trigger, тумблер On):
      • Time: Every [N] {day | week | month | quarter}; для week — on {день недели}; для month — on [Nd] last / first day; для quarter — on [Nd] of [1st] month. (дословно из S33).
      • Reservation: On + выбор Check In day / Check Out day / every Nth day of reservation (S53: «Check In Check Out every 2 days of occupancy»).
      • Vacancy: On + every N days of vacancy (S54: «every 2 days of vacancy»).
  • Контролы и действия:
    • Name (input); Department, Subdepartment (dropdown); связанный Task Template (dropdown — список из вкладки Task Templates); Connected Properties (multi-select units).
    • Trigger type — выбор Time / Reservation / Vacancy (dropdown/segmented); On (toggle — включить триггер).
    • Trigger-поля: Every [N] (input number); period (dropdown day/week/month/quarter); день недели / Nd / last/first day / of Nth month — контекстно по типу period (dropdown/input).
    • Save (button → создать/сохранить Service в статусе draft/inactive, без запуска пересчёта).
    • Save and Activate (button → сохранить + Status=active → триггерит recalculation по connected properties, см. S46).
    • Cancel (button → назад к списку без сохранения).
  • Состояния/вкладки:
    • Trigger пуст (S55) — тип не выбран, поля правила скрыты.
    • Trigger=Time (S33) / Trigger=Reservation (S53) / Trigger=Vacancy (S54) — контекстные поля правила.
    • new (создание) vs edit (правка существующего).
    • validation-error (нет Template / нет connected properties / неполное правило → Save and Activate заблокирован).
  • Связи (data-flow): пишет Service (+ Service.trigger {type, rule}). Читает TaskTemplate (выбор шаблона), Unit (connected_properties + Department Defaults — кому назначать сгенерированные задачи). Save and Activate → событие service active → recalculation engine (S46) генерирует Task(type=Schedule|Reservation, status=Planned) по каждому connected unit на горизонт 30 дней.
  • Мутации:
    • create/update Service (Name, dept, subdept, template_id, connected_properties[], trigger).
    • transition Status: draft → active (Save and Activate) / active → inactive.
    • side-effect: активация/деактивация/изменение trigger или connected_properties → recalc-триггер (генерация/удаление service-tasks).
  • Права: Coordinator/Operations — create/edit/activate. Accounting/Owner Services — read. Field staff — нет.
  • 🟡 [не было додумано]:
    • Сведение 4 экранов в один с состояниями Trigger — прототип хранит каждое состояние триггера отдельным «Copy of»-кадром; это один экран с переключаемым типом. Обоснование: overview §8 «свести Copy-дубли».
    • Save = сохранить как inactive (без пересчёта), Save and Activate = сохранить + запустить движок. Разделение выведено из наличия двух кнопок; прототип не подписывает семантику явно.
    • Валидация «нельзя активировать без Template / без connected properties» — додумана: без шаблона движку нечего материализовать. Минимальная, на границе.
    • В S53 текст «every 2 days of occupancy» vs S54 «of vacancy» — трактуем occupancy = «N-й день брони» (reservation trigger), vacancy = «N-й день простоя» (vacancy trigger). Уточнить терминологию у заказчика.
  • Edge cases: Trigger=Time с period=week, но не выбран день недели → ошибка; connected_properties пуст → activate заблокирован; смена trigger у уже активного сервиса → пересчёт переписывает будущие (не-locked) service-tasks; удаление связанного Template, пока сервис активен → запретить или деактивировать сервис.

S01 — Task Template editor [desktop]

  • Назначение: Редактор шаблона работ: дерево Sections → (Room | System) → Tasks. Описывает, ЧТО именно должно быть сделано при срабатывании сервиса — конкретные задачи с описанием, департаментом, категорией, баллом и требованием фото. Это «содержимое» задачи, которую материализует движок.
  • Источник (Miro): S01 «Copy of×6 Templates» (id 3458764671625731644), texts=78 — самый насыщенный экран эпика.
  • Layout: header (Services and Templates) → tab-bar (Services | Task Templates) → панель свойств шаблона (Priority / Task Type / Department / Subdepartment / Due Date / Due Time / Auto Issues) → дерево Sections: каждая секция (General, Bedroom, Bathroom, …) содержит таблицу Tasks; внутри секции — группировка по room / system. Footer: Save · Cancel · Save and Activate.
  • Данные на экране:
    • Свойства шаблона / секции: Priority (напр. Medium), Task Type (Non Blocking / Blocking), Department (Operations), Subdepartment (Operations), Due Date / Due Time (EOD / EOND / EOW / EOM, напр. 12:00), Auto Issues (On / Off).
    • Sections: список секций (General, Bedroom, Bathroom, …), действие arrange (порядок). Кнопки + section, + room, + system.
    • Task-таблица (колонки, дословно): Task (напр. Clean Something), Description (Description Description… — заглушка), Department (Operations), Category (Cleanliness), Score, Photo Req (флаг требования фото). Per-task действия: edit, remove. Per-section: + add photo (Area Photo), + add task.
  • Контролы и действия:
    • Priority (dropdown); Task Type Blocking/Non Blocking (toggle/dropdown); Department/Subdepartment (dropdown); Due Date (dropdown/date); Due Time EOD/EOND/EOW/EOM (dropdown); Auto Issues On/Off (toggle).
    • + section (button → добавить секцию); arrange (drag-reorder секций); + room / + system (button → добавить группу внутри секции — комната или инженерная система).
    • per-task: edit (inline-редактор полей task), remove (удалить task).
    • + add task (button → новая строка task в секции); + add photo / Area Photo (button → требование фото зоны).
    • Save / Save and Activate / Cancel (как в S33).
  • Состояния/вкладки: new vs edit; empty (нет секций → + section); секция свёрнута/развёрнута; task в режиме edit; Auto Issues On (при невыполнении генерится Issue) vs Off.
  • Связи (data-flow): пишет TaskTemplate (+ Section {auto_issues, priority, due_time} → Room|SystemTemplateTask {name, description, department, category, score, photo_required}). Читается из S33 (Service ссылается на template). При генерации задачи движком: TemplateTaskTaskRequirement исполняемой Task (area → tasks + reference + photo + comment, см. data model). Auto Issues=On → при failed task движок/исполнение создаёт связанный Issue.
  • Мутации: create/update/delete TaskTemplate, Section, Room/System, TemplateTask; reorder секций (arrange); transition Status (draft → active через Save and Activate).
  • Права: Coordinator/Operations — full CRUD. Accounting/Owner Services — read. Field staff — нет.
  • 🟡 [не было додумано]:
    • Семантика Section → Room | System: секция группирует задачи по комнате (Bedroom/Bathroom) ИЛИ по инженерной системе (Fire Safety/Pool/CCTV — из InfraItem.system). Выведено из кнопок + room / + system + data model. Обоснование: связывает шаблон с UnitArea и InfraItem объекта при материализации.
    • Due Time коды: EOD = End of Day, EOND = End of Next Day, EOW = End of Week, EOM = End of Month. Расшифровка додумана (прототип даёт только аббревиатуры) — задаёт due_at сгенерированной задачи относительно даты её плановой даты.
    • Category (Cleanliness, …) и Score — оценочная рубрика задачи (вероятно для maintenance-отчёта/качества). Прототип не объясняет шкалу Score — уточнить.
  • Edge cases: пустой шаблон (нет секций) → нельзя активировать; задача без Department → наследует department секции/шаблона; Photo Req=On, но в исполнении фото не приложено → задачу нельзя Finish (контракт с E04); очень длинный Description (заглушки) — truncate + tooltip.

S46 — Recalculation rules [desktop] · ⭐ ядро UC-1

  • Назначение: Экран-конфигуратор/визуализация движка пересчёта плановых задач: горизонт планирования, расписание запуска, триггеры пересчёта по объекту, поведение и поддерживаемые частоты. Описывает, КАК Service + Template + Trigger превращаются в Task(Planned). В прототипе — read-only визуализация правил; исполняемая логика — фаза реализации (cron 00:01 + event-driven, ARQ/Celery-стиль).
  • Источник (Miro): S46 «Screen 1» (id 3458764671474954580), canvas 1440×1707 (высокий — это «правила движка», не таблица).
  • Layout: вертикальный документ-конфиг, три логических блока: (1) горизонт + расписание; (2) поддерживаемые частоты триггеров; (3) триггеры пересчёта по объекту + поведение пересчёта. (В прототипе блоки повторены 3× — это варианты/дубли, сводятся в один.)
  • Данные на экране (дословно):
    • Горизонт + расписание: Schedule ahead: 30 days; Rule: Every day at 00:01 for next day + 30.
    • Поддерживаемые частоты (по типам Trigger):
      • Time/прочее: 1 time per day; every day; on a day of a week every week (e.g. monday, tuesday, friday); every nth day of a month; every last day of a month; every n months on a week day.
      • Vacancy: every nth day of vacancy.
      • Reservation: check in day / check out day / every nth day of reservation.
    • Recalculation trigger per property (дословно): service added to property; service removed from property; service set active / not active; new reservation; reservation edit; reservation deletion.
    • Поведение пересчёта (дословно): recalculation starts with tomorrow; edits only to service tasks; delete before creating service tasks; locked are not edited.
  • Контролы и действия: в прототипе — нет интерактивных контролов (controls: —). Read-only визуализация. 🟡 ниже про потенциальный редактируемый Schedule ahead / cron time.
  • Состояния/вкладки: единственное (статичная конфигурация). В реализации — возможен per-property override горизонта (не в прототипе).
  • Связи (data-flow) — как Service+Template+Trigger → Tasks:
    Триггер пересчёта (одно из: service add/remove, service active/inactive,
                       reservation new/edit/delete) ИЛИ cron 00:01
          │
          ▼
    Для каждого затронутого Unit (Service.connected_properties):
      1. Определить горизонт: [tomorrow .. tomorrow+30].
      2. УДАЛИТЬ существующие service-tasks этого Service в горизонте,
         КРОМЕ locked=true (delete before creating; locked are not edited).
      3. По Service.trigger развернуть occurrences в горизонте:
           Time      → по периоду (day/week/Nd month/last day/N months on weekday);
           Reservation → из Reservation объекта (check-in / check-out / every Nth day);
           Vacancy   → дни простоя между бронями (every Nth day of vacancy).
      4. Для КАЖDOГО occurrence × КАЖDOГО (Room|System) шаблона:
           материализовать Task(status=Planned, type=Schedule|Reservation,
             unit, area, service_id, template_id, due_at = occurrence + Due Time код,
             priority/department из шаблона/секции,
             assignment = Unit.AssignmentDefault[department]).
           TemplateTask-строки → TaskRequirement задачи (area→tasks+reference+photo+comment).
      5. Затрагивает ТОЛЬКО service-tasks (type Schedule/Reservation); Direct/Issue-задачи не трогает.
    
    Читает: Service(+trigger, connected_properties, template_id), TaskTemplate(Sections/Rooms/Systems/TemplateTasks), Unit(areas, AssignmentDefault), Reservation(check-in/out для reservation/vacancy-окон). Пишет: Task(+TaskRequirement).
  • Мутации: массовое delete+create Task(status=Planned) в горизонте по затронутым units; никогда не трогает locked=true и не-service задачи.
  • Права: просмотр конфигурации — Operations/Coordinator (admin). Редактирование расписания/горизонта — master/admin (если станет редактируемым). Движок исполняется системно (cron/event), не от роли.
  • 🟡 [не было додумано]:
    • Idempotency движка — «delete before creating» + горизонт-окно делают пересчёт идемпотентным: повторный запуск даёт тот же набор Planned-задач. Сформулировано явно (прототип подразумевает, но не называет) — критично, чтобы повторные триггеры reservation-edit не плодили дубли.
    • locked-задачаTask.locked=true (in-progress/назначенная/started). Движок её не удаляет и не правит. Связь с Task data model подтверждена; смысл «почему locked» додуман: защита уже начатой/назначенной работы от перезаписи пересчётом.
    • Назначение сгенерированной задачи через Unit.AssignmentDefault[department] (S39 Department Defaults) — выведено из data-flow overview §5 «assign by Department Defaults». В S46 явно не указано.
    • Due Time → due_at: код Due Time шаблона (EOD/EOND/EOW/EOM) применяется к дате occurrence для расчёта due_at. Связка S01↔S46 додумана.
    • Редактируемость Schedule ahead/cron-time — в прототипе read-only; в реализации может стать настройкой. Не расширяем прототип.
  • Edge cases: reservation удалена → пересчёт удаляет связанные reservation-tasks (не locked); сервис деактивирован → его будущие Planned-задачи удаляются (locked остаются); пересекающиеся брони / нулевая vacancy → 0 occurrences; задача стала locked между двумя прогонами → второй прогон её сохраняет; горизонт сдвигается ежедневно (вчерашний «tomorrow+30» хвост достраивается на +1 день).

Cross-screen relations (внутри эпика)

S36/S43 (list)
   │  tab=Services → + add / row click
   ├────────────────────────────────►  S33/S53/S54/S55 (New/Edit Service: Trigger builder)
   │                                          │ выбор Task Template (dropdown)
   │                                          ▼  ссылается на
   │  tab=Task Templates → + add / row click
   └────────────────────────────────►  S01 (Task Template editor: Sections→Rooms/Systems→Tasks)

S33 «Save and Activate» (Service active)  ──событие──►  S46 (recalculation engine)
Reservation new/edit/delete               ──событие──►  S46
cron 00:01                                ──tick────►  S46
                                                        │ delete+create
                                                        ▼
                                                   Task(Planned)  ──►  E03 Tasks / E04 mobile execution

Решения заказчика (2026-06-22)

Ответы со встречи Akira × Sergey (реестр 00-client-questions.md). Перекрывают соответствующие 🟡-допущения ниже.

  • Q-10 · occupancy/vacancy/reservation — РЕШЕНО (resolves §6 терминология): у Property ровно два статуса — Occupied / Vacant; Reservation — источник, не статус. Деривация из импортированных с Hostify броней: сегодня check-in ИЛИ длящаяся бронь покрывает сегодня → Occupied; сегодня check-out ИЛИ нет брони → Vacant. Trigger-типы сервиса (Time/Reservation/Vacancy) НЕ меняются — это про генерацию задач, отдельно от occupancy-статуса юнита.
  • Q-08 · Tasks vs Inspections — РЕШЕНО (refines S01 Task Template editor; resolves finding issues-req-and-not-req): ввести два типа темплейта — обычный Task и Inspection.
    • Task: пункт = «выполнил / не выполнил»; нет Score и не порождает issues.
    • Inspection: по каждому пункту да/нет + Score; ответ «нет» → автоматически создаёт Issue (существующий Auto Issues=On). Та же исполняемая сущность Task (общие листы), но отдельный тип темплейта с другой настройкой — чтобы не путать оператора (качественные пресеты).
    • Дельта к вёрстке (S01): разделить тип темплейта (Task | Inspection); для Inspection — per-item yes/no + score + auto-issue. → Tier C.
  • Q-09 · priority (S01 Priority: Medium) — РЕШЕНО: шкала Low / Standard / High / Emergency, дефолт Standard. Medium/NormalStandard. Единый enum с E03/E05.
  • Q-11 · task dates — РЕШЕНО: даты сгенерированных задач — собственные (occurrence + Due Time → due_at); НЕ привязаны жёстко к датам брони даже для reservation-триггеров (бронь задаёт occurrence, дальше у задачи свои даты/дедлайн).

Дозумано на уровне эпика 🟡

  1. Сведение «Copy of»-дублей. S36+S43 → один list-экран с вкладками; S33+S53+S54+S55 → один New Service с тремя состояниями Trigger. Прототип хранит состояния отдельными кадрами; мы сводим (overview §8). Отклонение нулевое по содержанию — только по числу компонентов.
  2. Семантика Save vs Save and Activate. Save = сохранить inactive/draft без пересчёта; Save and Activate = active + запустить движок. Выведено из пары кнопок, прототип семантику не подписывает.
  3. Контракт «как Service+Template+Trigger → Tasks». S46 даёт правила-обрывки; целостный алгоритм материализации (occurrences × Rooms/Systems → Task+TaskRequirement, назначение через Department Defaults, due_at из Due Time) собран нами из data model + overview §5/§6. Это документация контракта, не новая фича.
  4. Идемпотентность + смысл locked. Явно сформулированы как инвариант движка (delete-before-create в окне; locked неприкосновенны). В прототипе подразумевается строкой «delete before creating / locked are not edited».
  5. Расшифровка кодов Due Time (EOD/EOND/EOW/EOM) и Task Type (Blocking/Non Blocking → блокирует ли невыполнение зависимые/отчёт). Прототип даёт аббревиатуры без легенды — расшифровка помечена для подтверждения заказчиком.
  6. Терминология occupancy vs vacancy (S53 «days of occupancy» vs S54 «days of vacancy») — трактуем как reservation-trigger vs vacancy-trigger; вынесено в открытый вопрос к заказчику.