Dev Specs

Epic E03 — Tasks [desktop]

Назначение эпика: управление задачами и их полным жизненным циклом — список/фильтрация задач и issue-задач, детальная карточка задачи (Details / Requirements / Costs / History) и календарный вид задач по сотрудникам. Центральный операционный экран back-office: сюда стекаются задачи из сервисов (Planned), разовых заявок (Direct), резерваций (Reservation) и инцидентов (Issue). Участвует в use-cases: UC-1 (recalculation engine → Tasks), UC-2 (исполнение задач), UC-3 (issue → linked task), UC-5 (cost-учёт → Maintenance Report). Сущности (owns/touches): owns — Task (+ TaskRequirement, TaskCost, TaskEvent, Attachment, Comment). touches — Unit, UnitArea, Employee (owner/assignment), Service/TaskTemplate (источник service-tasks), Issue (linked_task), Reservation. Экраны: S13 (Tasks list: вкладки All Tasks / Issues), S20 + S25 + S21 + S29 (Task Detail — один экран с вкладками Details / Requirements / Costs / History; «Copy of …»-дубли сведены), S44 (Schedule — календарный вид задач per employee). Открытые findings: F-filter-hierarchy-assignee (00-overview §3 — модель доступа Vendors к задачам не уточнена); тексты-заглушки в Requirements/Comments — реальные лейблы уточнить у заказчика (technical §Tech debt).


Доменная модель эпика (опорная, из technical §Data model)

Task: id, name, type[Direct|Schedule|Issue|Reservation], status[Planned|New|In Progress|Finished],
      unit, area, department, subdepartment, owner, assignment[], priority,
      created_at, due_at, finished_at, started_at, total_time, on_track(bool),
      template_id?, service_id?, issue_id?, locked(bool)
Task ─< TaskRequirement (area → tasks + reference + photo + comment)
Task ─< TaskCost (name, type[Task|Hour|Items|Supplies], direct_cost, invoice, total)
Task ─< TaskEvent (time, user, event, before, after)   # history
Task ─< Attachment, Comment

Task type — семантика (4 источника происхождения)

  • Direct — разовая ручная задача (оператор создал «здесь и сейчас», без шаблона). Пример из контента: Check Wifi.
  • Schedule — порождена recalculation engine (S46) из сервиса с time/vacancy-триггером; несёт service_id + template_id. Может быть locked (engine не трогает при пересчёте).
  • Issue — порождена из инцидента (E05); несёт issue_id, связана обратной ссылкой Issue.linked_task. Пример: Plumbing Repair.
  • Reservation — порождена резервацией (cleaning/check-in/check-out); несёт service_id + ссылку на reservation. 🟡 [не было додумано] в каталоге S13 type Reservation в строках не встречается явно (видны Direct/Issue), но тип объявлен в модели Task — оставляем как фильтр-значение, источник = recalc по reservation-триггеру (overview §5).

Status — машина состояний

            (engine: создаёт Schedule/Reservation-task)
                         │
                         ▼
   ┌───────────► Planned ──(наступил день/активирована вручную)──► New
   │ (Direct/Issue                                                  │
   │  создаются                            (assignee начал работу — старт таймера)
   │  сразу как New)                                                ▼
   │                                                          In Progress
   │                                          (все requirement-задачи отмечены done /
   │                                           assignee завершил, фиксируется finished_at)
   │                                                                ▼
   └────────────────────────────────────────────────────────► Finished

Описание переходов:

  • (нет) → Planned — engine S46 создаёт будущую service/reservation-задачу (на завтра…+30 дней). Direct- и Issue-задачи в Planned не попадают — создаются сразу как New.
  • Planned → New — наступает плановый день задачи (engine/cron активирует) либо оператор вручную активирует раньше срока. 🟡 [не было додумано] ручную активацию вывели логически (контент S29 показывает событие Created Task → New, отдельного Planned→New-события в каталоге нет) — обоснование: статус Planned обязан где-то стать New, иначе задача не доходит до исполнителя.
  • New → In Progress — assignee (mobile, E04) начинает исполнение → пишется started_at, запускается total_time. Подтверждено событием S29: Changed Task status: New → In Progress (user Sarah Martinez, 13:00).
  • In Progress → Finished — исполнение завершено → пишется finished_at, фиксируется total_time (S20 показывает Started 11:00, Finished 13:05, Total Time 2h 05m), вычисляется on_track (finished_at ≤ due_at). Дальше задача попадает в snapshot Maintenance Report (E06).
  • Терминальность: Finished — терминальный статус. 🟡 [не было додумано] reopen/cancel в каталоге отсутствуют — НЕ добавляем (минимализм); если потребуется отмена — фиксируется отдельным TaskEvent на фазе реализации.
  • locked — флаг (не статус): защищает Schedule-задачу от перезаписи recalculation engine; ортогонален status.

S13 — Tasks list [desktop]

  • Назначение: единый реестр всех задач объекта/департамента с переключением между обычными задачами и issue-задачами и набором фильтров.
  • Источник (Miro): «Copy of Copy of Templates» id 3458764671320125392 (canvas x=4275 y=7963, 1440×900). Вкладка Issues делит экран и шапку фильтров с S15 (id 3458764672845971639) — это тот же экран в состоянии вкладки Issues; detail issue-строки принадлежит эпику E05.
  • Layout: header Tasks → строка вкладок All Tasks | Issues → панель фильтров (5 dropdown + поиск) → data-table → переход в Task Detail по строке (right-side detail / отдельный экран).
  • Данные на экране (вкладка All Tasks — колонки таблицы, дословно): ID, Task Name, Task (тип задачи / задача-источник), Created, Due, Finished. Дополнительно в строках видны Department (Operations), owner (Sarah Martinez), type-чип (Direct / Issue), status-чип (Planned). Примеры строк: 1 · AC Maintenance · Operations · Planned, 2 · Check Wifi · Direct, 3 · Plumbing Repair · Issue.
  • Данные на экране (вкладка Issues — колонки, из S15): ID, Unit, Issue, Description, Reported at, Department, Sub Department, Reported by, Linked Task, Linked Task Status. (Контент detail — эпик E05; здесь — только список как вкладка реестра задач.)
  • Контролы и действия:
    • All Tasks / Issuestab → переключает набор колонок и источник (Task vs Issue).
    • Departmentdropdown (Operations / Housekeeping Coordinator / Handyman / Accounting / Property Services / Owner Services / Vendors) → фильтр по department.
    • Unitdropdown → фильтр по объекту.
    • Statusdropdown (Planned / New / In Progress / Finished) → фильтр по статусу.
    • Task Typedropdown (Direct / Schedule / Issue / Reservation) → фильтр по типу. (В контенте видны значения Maintenance/Cleaning/Inspection/Maintenance Issue — это category-значения шаблонных секций; 🟡 [не было додумано] трактуем dropdown Task Type как фильтр по Task.type, а перечисленные категории — как вторичную группировку из TaskTemplate.category; уточнить у заказчика.)
    • Assignmentdropdown → фильтр по назначенному исполнителю.
    • поиск — input (по ID/Task Name).
    • клик по строке — переход в Task Detail (S20…).
  • Состояния/вкладки: tab All Tasks (default), tab Issues; filtered (применены dropdown'ы); empty (нет задач под фильтр); loading; error.
  • Связи (data-flow): читает Task (+ join Unit, Employee owner/assignment, Issue для linked); строки порождаются engine S46 (Schedule/Reservation), ручным созданием (Direct), эскалацией Issue (E05). Клик → Task Detail. Вкладка Issues читает Issue.
  • Мутации: read-only список (фильтрация — клиентская/серверная выборка, не мутация). Создание задачи — 🟡 см. ниже.
  • Права: Coordinator/Operations — все department-scoped задачи; Accounting — read (+ доступ к costs во вкладке detail); Owner Services — read; Vendors — только назначенные (F-filter-hierarchy-assignee).
  • 🟡 [не было додумано]:
    • В каталоге S13 нет кнопки + Add Task (controls: только 5 dropdown + 1 input). Но Direct-задачи должны откуда-то создаваться. Минимально: фиксируем, что ручное создание Direct-задачи существует (вероятно из карточки Unit→Tasks или отдельной +-кнопкой) — точку входа уточнить у заказчика, в этом эпике не достраиваем UI создания.
    • Колонка Finished пустая для не-Finished задач — это нормальное состояние (см. edge cases).
  • Edge cases: empty (нет задач / фильтр без совпадений → «No tasks»); Finished/Due пустые для Planned/New; длинные Task Name — усечение с tooltip; Vendor без прав на строку — строка скрыта; задача без owner/assignment — пустые ячейки (валидно до назначения).

S20 + S25 + S21 + S29 — Task Detail [desktop]

Один экран с общей шапкой и 4 вкладками. «Copy of …»-дубли S20/S25/S21/S29 = состояния вкладок Details / Requirements / Costs / History. Не плодить компоненты — один tabbed detail-view.

  • Назначение: полная карточка одной задачи — атрибуты и назначение, area-by-area требования, финансовый учёт, журнал изменений.
  • Источник (Miro): S20 Details (id 3458764671320866892), S25 Requirements (id 3458764671459037363), S21 Costs (id 3458764671465447325), S29 History (id 3458764671463726827); все canvas 1440×900, кластер Units x≈6081 y≈8000–11000.
  • Layout: header-полоса задачи (общая для всех вкладок) → строка вкладок Details | Requirements | Costs | History → тело активной вкладки → секция Comments (общая, присутствует на Details/Costs/History) → Attachments.
  • Header задачи (общий, дословно): Task Name, Unit (Villa Alaya), task_id 120324, Started at (11:00 12 May 2026), Finished at (13:05 12 May 2026), Total Time (2h 05m), On Track (bool-чип).

Вкладка Details (S20)

  • Данные/поля (дословно): Department: Operations · Owner: Sarah Martinez + + change owner · Subdepartment: Maintenance · Assignment: (список назначенных, каждый с remove) + + add assignee · Task: AC Maintenance · Item: (+ remove) · System Item: · Description: · Due: (May 12, 2026 · 15:00) · Priority: Standard · Comments · Attachments: + + add attachment.
  • Контролы: + change owner (button → смена owner), + add assignee / remove (button → правка assignment[]), + add attachment (button → upload в Attachment), Comments (input → добавить Comment). Priority — dropdown (Standard / …; др. значения 🟡 не перечислены в каталоге — минимум Standard; шкалу уточнить).
  • Мутации: update Task.owner, add/remove Task.assignment[], update Task.due_at, update Task.priority, create Comment, create Attachment. Каждая мутация → пишет TaskEvent (см. History).

Вкладка Requirements (S25)

  • Назначение: area-by-area список требований к выполнению (что проверить/сделать по каждой зоне) + фото зоны + комментарии. Совпадает по структуре с mobile-исполнением (E04 / S45 — тот же чеклист, но desktop = просмотр/конфигурация, mobile = отметка).
  • Данные/поля (дословно): Check In: / Check Out: · Villa Alaya · адрес (Jl. Raya Semat No.88, Canggu…) · блок Areas → по каждой зоне (Bedroom 1, Bedroom 2, …): список Task + Task Description … + comment … + Area Photos + Comments.
  • Контролы: просмотр требований; Area Photos — галерея (на desktop read; добавление фото — mobile-исполнение E04). 🟡 [не было додумано] на desktop Requirements преимущественно read/конфиг (нет явных контролов в каталоге — input_button×1 = общая + шапки); отметка выполнения и заливка фото происходят на mobile (E04). Обоснование: разделение field-исполнения (mobile) и back-office-просмотра (desktop) задано архитектурой клиентов.
  • Мутации: desktop — преимущественно read; (правка состава requirement'ов — конфиг шаблона, эпик E02). Source данных — TaskRequirement (area → tasks/reference/photo/comment).

Вкладка Costs (S21)

  • Назначение: финансовый учёт по задаче, сгруппированный по типам затрат.
  • Данные/поля (дословно): таблица с колонками Cost Name | Details | Direct Cost | Invoice | Total; группы затрат:
    • Task CostsTask Cost (2,000,000.00 IDR / 2,500,000.00 IDR)
    • Hour Cost (2,000,000.00 IDR / 2,500,000.00 IDR)
    • Items CostsLight Bulb, Pool Pump, Pool Pump (по строке Direct/Invoice)
    • Supplies CostsNew Linen
    • + add cost · Comments.
  • Контролы: + add cost (button → новая строка TaskCost, выбор type ∈ {Task | Hour | Items | Supplies}), правка direct_cost / invoice (input), Comments (input). Валюта IDR, формат 2,000,000.00 IDR.
  • Мутации: create/update/delete TaskCost. Total = вычисляемое (direct_cost vs invoice — отображаются обе колонки; итог 🟡 формула суммирования в каталоге не задана — минимально: Total = invoice при наличии, иначе direct_cost; уточнить).
  • Данные уходят в: Maintenance Report (E06) — costs снапшотятся в месячный отчёт.

Вкладка History (S29)

  • Назначение: неизменяемый журнал событий задачи (audit log).

  • Данные/поля (дословно): группировка Today → таблица Time | User | Event | Before | After. Примеры строк:

    • 13:00 · Sarah Martinez · Changed Task status · New → In Progress
    • 12:00 · Sarah Martinez · Created Task · - → New
  • Контролы: read-only (нет input-контролов; input_button×1 = общая + шапки). Возможна фильтрация по дате (группировка Today).

  • Мутации: нет (append-only; записи создаются автоматически на каждую мутацию задачи на других вкладках). Source — TaskEvent.

  • Состояния/вкладки (общие для экрана): active tab ∈ {Details, Requirements, Costs, History}; задача в статусе Planned/New (Started at/Finished at/Total Time пустые) vs Finished (заполнены); loading; error; no-permission (Accounting видит Costs, но Details read-only — 🟡 уточнить).

  • Связи (data-flow): читает Task + TaskRequirement/TaskCost/TaskEvent/Comment/Attachment; owner/assignment join Employee; unit/area join Unit/UnitArea. Пишет Details/Costs/Comments → TaskEvent (History) → данные Costs снапшотятся в Maintenance Report (E06). Открывается из S13 (клик по строке).

  • Права: Coordinator/Operations — полный доступ ко всем вкладкам своих department-scoped задач. Accounting — Costs (read+write financial fields), Details read. Owner Services — read. Field staff — карточку видят на mobile (E04), не здесь.

  • 🟡 [не было додумано]:

    • History как append-only audit подтверждён контентом (Created/Changed status); расширили утверждением, что любая мутация на Details/Costs пишет TaskEvent — обоснование: иначе журнал неполон, а before/after-колонки требуют источника.
    • Priority-шкала: каталог даёт только Standard. Полный набор (Low/Standard/High/Urgent?) — уточнить; в прототипе минимум Standard.
    • On Track вычисляется как finished_at ≤ due_at (S44 даёт парность on track / overdue) — формула выведена логически.
  • Edge cases: задача без costs (вкладка Costs empty → «No costs»); задача без requirements (Direct-задача без шаблона → Requirements empty); History всегда ≥1 строка (Created Task); длинные Description/comment (заглушки «Task Description…» × N) — усечение/скролл; задача в Planned → Started at/Finished at/Total Time/On Track пустые; отсутствие прав на Costs — вкладка скрыта/disabled.


S44 — Schedule (календарный вид задач per employee) [desktop]

  • Назначение: календарное расписание задач по сотрудникам с маркировкой соблюдения срока (on track / overdue) — операционный обзор загрузки персонала во времени.
  • Источник (Miro): «Copy of Property Card» id 3458764671638361703 (canvas x=4101 y=17611, 1440×900). В IA вынесен из карточки Unit как общий модуль Schedule (overview §2, помечен 🟡).
  • Layout: header Schedule + навигация периода < > → поиск + фильтр → сетка-календарь: строки = задачи/слоты, ячейка несёт status (on track / overdue), время (00:00 / 02:30), исполнителя (Sarah Martinez), тип задачи (Cleaning).
  • Данные на экране (дословно): Schedule, навигация < >, search, повторяющиеся Cleaning (тип задачи в ячейке), статус-чипы on track / overdue, время 00:00 / 02:30, исполнитель Sarah Martinez, кнопки clear / apply.
  • Контролы и действия:
    • < >button → пред./след. период (день/неделя — 🟡 гранулярность не указана; минимально — навигация периода).
    • searchinput → поиск по сотруднику/задаче.
    • clear / applybutton → сброс/применение фильтра.
    • клик по ячейке-задаче — переход в Task Detail (S20…). 🟡 [не было додумано] переход выведен логически (ячейка = задача); в каталоге явной навигации нет.
  • Состояния/вкладки: период (текущий/листание); filtered (применён search/filter); on-track vs overdue раскраска ячеек (electric-green / red — overview §4); empty (нет задач в периоде); loading.
  • Связи (data-flow): читает Taskdue_at, assignment/owner, type, on_track) сгруппированные по сотруднику и времени; задачи те же, что в S13 — другой ракурс (календарь vs таблица). Клик → Task Detail.
  • Мутации: read-only обзор. (Перетаскивание/перепланирование — 🟡 в каталоге нет, НЕ добавляем.)
  • Права: Coordinator/Operations — расписание своих department-scoped сотрудников; прочие роли — по scope. Field staff своё расписание видят на mobile.
  • 🟡 [не было додумано]: гранулярность календаря (день/неделя/месяц) и точная ось (per-employee строки) выведены из контента (повтор исполнителя + время в ячейках); подтвердить макет с заказчиком. Drag-to-reschedule намеренно не вводим (минимализм).
  • Edge cases: период без задач (empty grid); overdue-задачи подсвечены красным; сотрудник без задач — пустая строка/скрыт; пересечение задач по времени — стек/overlap-индикатор (🟡 деталь рендера на фазе вёрстки).

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

S13 (Tasks list) ──клик по строке──► Task Detail (S20/S25/S21/S29, нужная вкладка)
S13 ──tab Issues──► список issue-задач (detail issue = эпик E05)
S44 (Schedule) ──клик по ячейке──► Task Detail
Task Detail · Details/Costs ──любая мутация──► Task Detail · History (TaskEvent, append-only)
Task Detail · Costs ──снапшот──► Maintenance Report (эпик E06)

Внешние входы/выходы эпика:

  • Вход: recalculation engine S46 (UC-1, overview §6) создаёт Schedule/Reservation-задачи → появляются в S13/S44 как Planned. Issue (E05) эскалируется → Issue-задача. Ручное создание → Direct-задача.
  • Выход: Finished-задачи + их Costs снапшотятся в Maintenance Report (E06); Requirements/фото отмечаются на mobile-исполнении (E04, S45).

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

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

  • Q-09 · priority — РЕШЕНО (resolves §6/§8 «Priority-шкала», S20 dropdown): Low / Standard / High / Emergency (4 уровня), дефолт при создании — Standard (можно сменить). Normal/MediumStandard, UrgentEmergency. Единый enum tasks/issues/services. → Tier B U1.

  • Q-06 · task lifecycle / точка создания — РЕШЕНО (resolves §3 «точка создания Direct»): Task всегда создаётся на desktop, из 4 источников: (1) новая бронь → recalc-движок (Reservation-task); (2) ежедневный движок авто-тасков по графикам (Schedule-task); (3) менеджер вручную (Direct); (4) авто из зарепорченного Issue (Issue-task). Исполнение — desktop И mobile (в основном mobile; часть manager-report тасков — desktop). Апрув — desktop. Mobile (E04) — только исполнение. Подтверждает 4-источниковую модель Task.type.

  • Q-11 · task dates vs reservations — РЕШЕНО: даты задач независимы от дат броней. У каждого таска свои даты (создание + duration/deadline). Reservation-task порождается из брони, но даты у неё свои; issue/периодические/ручные — к броням не относятся.

  • Q-07 · auto-assignment hierarchy — РЕШЕНО (домен + подход, ADR-001) (resolves finding filter-hierarchy-assignee); открыт только engineering edge-cases → Akira × Соя:

    Авто-назначение задачи — трёхступенчатая иерархия (department → cluster → property)

    По логике Breezeway. При создании задачи дефолтный ответственный резолвится по самому специфичному заданному уровню:

    1. Property-override (самый специфичный): если у профиля проперти для этого департамента задан конкретный ответственный (S39 Department Defaults, E01) → берётся он. Покрывает edge-case «проперти в кластере, но её уборщик не в команде кластера».
    2. Cluster-default: иначе, если у кластера (группа пропертей района) для этого типа задач / департамента есть авто-ответственный → берётся команда/ответственный кластера. Для Cleaning: clean-staff прикреплён группой к кластеру → cleaning-task по дефолту назначается на всю команду кластера в clean-департаменте (далее распределяют между собой).
    3. Department-default (самый общий): иначе → дефолтный ответственный департамента.

    Engineering/Maintenance: прямого cluster-team-дефолта как у Cleaning нет — авто-логику назначения для инжиниринга нужно допродумать (Akira × Соя; возможны edge-cases).

    Реализация (подход ПРИНЯТ 2026-06-23, ADR-001): иерархию НЕ зашивать жёстким хардкодом и НЕ строить пользовательский «конструктор фильтров». Принято: adjacency list (parentId, нейтрально к бэкенду) для иерархии + отдельная сущность назначения со scopeLevel/scopeNodeId + полиморфные targets[] (kind: user|group|role|cluster|department, сохраняет идентичность группы для UI) + резолюция most-specific-wins на чтение (подняться по предкам property, взять ближайшее заданное назначение). ReBAC-движок (OpenFGA/SpiceDB) — overkill на прототипе, держим как мысленную модель. Каскад в UI (Department > Cluster > Assignee) — отражение той же иерархии. Анти-паттерн user_id/group_id (вместо targets[]) — запрещён. Открыто: engineering-edge-cases авто-логики, нестандартные привязки и tie-break most-specific-wins — после разговора Akira × Соя.


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

  1. Машина состояний Task (Planned→New→In Progress→Finished) — переходы New→In Progress→Finished прямо подтверждены событиями S29; (нет)→Planned следует из recalc-движка (overview §6); Planned→New (наступление дня / ручная активация) достроен логически — иначе плановая задача не доходит до исполнителя. Reopen/cancel НЕ добавлены (нет в скелете). Отклонение от прототипа: минимальное (один недостающий переход).
  2. Task type Reservation как фильтр-значение — в строках S13 не встречается (видны Direct/Issue), но объявлен в модели Task; оставлен как значение dropdown Task Type, источник = reservation-триггер recalc. Уточнить у заказчика наличие живых reservation-задач.
  3. Точка создания Direct-задачи — в S13 кнопки + Add Task нет (только фильтры). Зафиксировано, что ручное создание существует (вероятно из Unit→Tasks); UI-точку входа в этом эпике не достраиваем — уточнить.
  4. History = полный audit — расширено утверждением «любая мутация Details/Costs → TaskEvent»; обоснование: before/after-журнал требует источника на каждое изменение.
  5. Desktop Requirements = read/конфиг, отметка выполнения и фото — mobile (E04). Разделение задано архитектурой двух клиентов.
  6. Task Type dropdown vs category-значения (Maintenance/Cleaning/Inspection в контенте) — трактуем dropdown как фильтр по Task.type, категории — вторичная группировка из TaskTemplate.category. Уточнить.
  7. Schedule (S44) гранулярность и ось per-employee, клик-в-detail, on-track/overdue раскраска — выведены из контента; drag-reschedule намеренно опущен.
  8. Total/On Track формулыTotal = invoice ?? direct_cost (Costs), on_track = finished_at ≤ due_at — выведены логически, формулы в каталоге отсутствуют.