Dev Specs

Epic E07 — Projects & Proposals [desktop → PDF]

Назначение эпика: Ведение ремонтных/строительных проектов по виллам как сметных предложений (Proposal): описание проблемы → решение → калькуляция стоимости → согласование → оплата → выполнение через project-задачи → completion report → экспорт PDF для владельца. Это отдельный документ от месячного Maintenance Report (E06) — он про разовый ремонт/проект, не про периодическую эксплуатацию. Участвует в use-cases: UC-5 (Repair Proposal → Approval → Project → PDF). Сущности (owns/touches): owns — Proposal, ProposalItem (problem/solution/photos), ProposalCost (item/labor), ProposalCharge; touches — Unit (E01), Task (project-задачи, E03), MaintenanceReport/completion report (E06 — для прикрепления отчёта о завершении). Экраны: S31 (Proposals list), S30 (Proposal detail), S04 (Problem/Solution/Costs), S58 (PDF cover). Открытые findings: F-handling-fee-calculation (как считается handling fee — % от costs автоматически или вручную; влияет на Total в S04).

Статус реализации (2026-06-21):

  • WP-1 DONE + PROD LIVE (PR #16 007333c, DEV+PROD на /staffapp): S31 Proposals list (/projects, status-табы Proposed/Approved/Paid + search + unit dropdown) + S30 Proposal detail (/projects/[id], 4 таба Proposal/Tasks/Costs/History; Costs = cost-строки + Total Costs subtotal). Owns Proposal/ProposalItem/ProposalTaskLine/ProposalCost{type:item|labor}/ProposalHistoryEntry. Мутации inert 🟡 (нет бэкенда). План: docs/superpowers/plans/2026-06-21-e07-projects-wp1.md.
  • WP-2 DONE + PROD LIVE (PR #20 c5075b9, DEV+PROD на /staffapp): S04 per-position drill-in (/projects/[id]/items/[itemId]: Room/Problem/Solution + Costs-таблица позиции + Position Total; drill-in кликом из вкладки Proposal) + Cost summary (вкладка Costs расширена: Total Costs+Charges+Handling Fee+grand Total+Project Duration/Warranty; fallback при null-summary) + S58 PDF cover (/projects/[id]/pdf) + Export PDF wiring + inert status-transition (proposed→Approve, approved→Mark as Paid). Новые сущности ProposalItemCost/ProposalCostSummary + поля problem/solution на ProposalItem. План: docs/superpowers/plans/2026-06-21-e07-projects-wp2.md. E07 desktop эпик завершён.
    • Open F-handling-fee-calculation (формула grand Total) — fallback Total = Total Costs + Charges + Handling Fee (§108 ASSUMPTION, помечен в коде «требует подтверждения заказчика»); поле правки inert.
    • Defer (backend-phase): Report-колонка S31 как ссылка на E06-документ (нет real report-id внутри E07); реальный PDF-рендер; Total Costs агрегат (sumProposalCosts) vs §103 Σ position_total — намеренная snapshot-независимость (прецедент E06), реконсилировать на бэкенде.

S31 — Projects / Proposals (list) [desktop]

  • Назначение: Реестр всех сметных предложений по виллам с разбивкой по стадии жизненного цикла.
  • Источник (Miro): «Projects Proposals», id 3458764672549068392 (controls: input_text×1, input_dropdown×1, input_button×8).
  • Layout: header с tab-полосой (Proposed · Approved · Paid) → fast-filter (search input + unit dropdown) → data-table → строка кликабельна в S30. + add proposal (button) 🟡.
  • Данные на экране (колонки): ID, Unit (напр. «Alaya»), Status, Started, Completed, Report. Даты в формате DD/MM/YYYY (в макете все 22/05/2026).
    • Report — ссылка на прикреплённый completion report (заполняется после стадии проекта; пусто до завершения).
    • Completed — дата завершения проекта (пусто, пока не Paid+выполнено).
  • Контролы и действия:
    • Tabs Proposed / Approved / Paid (filter) → фильтруют список по status.
    • Search input (input_text) → фильтр по ID/Unit.
    • Unit dropdown (input_dropdown) → фильтр по вилле.
    • Row click → S30 (Proposal detail).
    • Report-ссылка в строке → открыть прикреплённый completion report (E06-документ).
    • 8× input_button в макете → строковые действия (open / report-link) + tab-кнопки + + add 🟡 (точная раскладка кнопок в прототипе не детализирована).
  • Состояния/вкладки:
    • Tabs = стадии: Proposed (черновик/на согласовании), Approved (согласован, ждёт оплаты), Paid (оплачен → в работе/завершён).
    • empty (нет предложений в стадии), filtered (по unit/поиску), loading.
  • Связи (data-flow): читает Proposal (+ Unit для имени виллы, completion report для Report-колонки). Источник создания — потребность в ремонте (вручную оператором/owner services; в прототипе триггер-автоматики нет). Пишет: переход на S30; создание нового Proposal 🟡.
  • Мутации: create Proposal (через + add 🟡); навигация. Сам список — read.
  • Права: Operations/Coordinator, Owner Services — видят и создают; Accounting — read + финансовые поля. Field staff — нет доступа (desktop-only эпик).
  • 🟡 [не было додумано]: (1) кнопка + add proposal — в каталоге 8 безымянных кнопок, явной «создать» нет, но без неё цикл UC-5 не стартует. (2) Семантика колонки Report как ссылки на completion report — выведена из наличия столбца + стадии Paid.
  • Edge cases: пустая стадия (нет Proposed) → empty-state; Proposal без даты Completed/Report → пустые ячейки; длинное имя виллы → truncation.

S30 — Proposal detail [desktop]

  • Назначение: Карточка одного предложения: шапка (вилла/период/дата завершения), вкладки Proposal · Tasks · Costs · History, на вкладке Proposal — построчный перечень ремонтных позиций (item, комната, заметки, фото).
  • Источник (Miro): «Copy of Copy of Copy of Maintenance Report», id 3458764672525398514 (controls: input_button×3, input_text×1, input_dropdown×1). Заголовок в Miro унаследован от шаблона Maintenance Report, но контент — это Proposal (вкладка Proposal, не Area Report); трактуем как Proposal detail.
  • Layout: header (поля Unit, Year, Month, Completed at) + Name: / Status: + Save / Cancel → tab-полоса Proposal · Tasks · Costs · History → на вкладке Proposal таблица позиций + блок Photos: с + add photo.
  • Данные на экране:
    • Шапка: Unit («Villa Alaya»), Year («2026»), Month («1»), Completed at («6 May, 2026»), Name:, Status:.
    • Таблица позиций (вкладка Proposal): ID, Item Name (напр. «Wall Paint», «Door Replacement», «Table Replacement»), Room (напр. «Bathroom», «Entrance», «Living Room»), Notes:.
    • Photos: + + add photo (button) — фото к позиции/предложению.
  • Контролы и действия:
    • Save (button) → сохранить изменения предложения (update).
    • Cancel (button) → отменить редактирование.
    • Status: (вероятно dropdown/индикатор) → отражает/меняет стадию Proposed→Approved→Paid (см. мутации ниже).
    • Unit (input_dropdown) → привязка к вилле.
    • + add photo (button) → прикрепить фото к позиции.
    • input_text — Name/Notes.
    • Tabs: Proposal (позиции, этот экран), Tasks (project-задачи → E03), Costs (калькуляция → S04), History (журнал смены статусов/правок).
  • Состояния/вкладки:
    • Proposal — перечень позиций (problem/solution на уровне позиции раскрывается в S04).
    • Tasks — связанные project-задачи (см. cross-ref E03); до Approved/Paid может быть пусто.
    • Costs — финансовый расчёт (S04).
    • History — аудит изменений и переходов статуса.
    • empty (нет позиций), loading, error (сохранение).
  • Связи (data-flow): читает Proposal + ProposalItem (+ Unit). На вкладке Costs читает ProposalCost/ProposalCharge (S04). На вкладке Tasks — Task, отфильтрованные по proposal (E03). На завершении прикрепляется completion report (E06), который всплывает в колонке Report списка S31. Пишет: update позиций/заметок/фото, смену статуса.
  • Мутации:
    • update Proposal (name, items, notes, photos) — Save.
    • transition status: Proposed → Approved (согласование владельцем/owner services) → Approved → Paid (фиксация оплаты). После Paid — порождение/привязка project-задач (E03) и по завершении — completion report.
  • Права: Owner Services — создание/правка/Approve; Accounting — Costs (read + edit финансовых полей); Operations/Coordinator — правка позиций, перевод в работу. Approve/Paid-переходы — Owner Services/Accounting (точное разграничение 🟡).
  • 🟡 [не было додумано]: (1) поля Year/Month/Completed at унаследованы от шаблона отчёта — для Proposal трактуем Completed at как дату завершения проекта, Year/Month как период привязки (избыточны для разового проекта, оставлены ради верности макету). (2) Связь вкладки Tasks с E03 и вкладки History как audit-лога — структурно достроено (в каталоге только лейблы вкладок).
  • Edge cases: Proposal без позиций → empty в таблице; смена статуса назад (Approved→Proposed) 🟡 не определена в прототипе — по умолчанию запрещаем регресс после Paid; отсутствие прав → вкладки/кнопки скрыты.

S04 — Problem / Solution / Costs (calculation) [desktop]

  • Назначение: Детальная сметная развёртка предложения: по каждой ремонтной позиции — Problem / Solution / Costs, затем сводная калькуляция (Total Costs + Charges + Handling Fee = Total) и параметры Project Duration / Warranty. Источник чисел для PDF.
  • Источник (Miro): «Screen 5», id 3458764672555531925 (texts=63, images=18, controls: —). Это, по сути, развёрнутый вид вкладки Costs экрана S30 (или печатная развёртка).
  • Layout: вертикальный список блоков-позиций (заголовок позиции, напр. «Broken sink in bathroom 1» → Problem: текст → Solution: текст → Costs: с подстроками) → итоговый блок Costs (агрегированная таблица) → строки итогов → метаданные проекта.
  • Данные на экране:
    • Per-position блок (повторяется по позициям):
      • заголовок позиции («Broken sink in bathroom 1»),
      • Problem: — описание проблемы (текст),
      • Solution: — описание решения (текст),
      • Costs: — строки стоимости: Cost Item 1 200,000.00 IDR, Cost Item 100,000.00 IDR, Cost Labor 100,000.00 IDR (две природы строк: материалы/работы (Cost Item) и труд (Cost Labor)).
    • Сводный блок Costs (колонки Cost Item / Cost): агрегирует все позиции (Cost Item 1, Cost Item 2, … + соответствующие Cost Labor).
    • Итоги:
      • Total Costs — сумма всех Cost Item + Cost Labor по позициям.
      • Charges — дополнительные сборы (доставка/прочее) 🟡 точная природа не подписана.
      • Handling Fee — комиссия управляющей компании (см. F-handling-fee-calculation).
      • Total — итог к оплате владельцем.
    • Метаданные: Project Duration: («5 weeks»), Warranty: («5 weeks»).
  • Контролы и действия: в каталоге controls отсутствуют (controls: —) — экран преимущественно отображение/печатная развёртка. Редактирование числовых/текстовых полей предполагается на вкладке Costs S30. + add cost line / + add item 🟡 (для наполнения сметы).
  • Расчёт сметы (бизнес-логика):
    position_total      = Σ(Cost Item) + Σ(Cost Labor)        // по каждой позиции
    Total Costs         = Σ position_total                     // по всем позициям предложения
    Charges             = Σ дополнительных сборов (ручной ввод) // напр. доставка/вывоз
    Handling Fee        = ❓ F-handling-fee-calculation:
                          вариант A — % от (Total Costs [+ Charges]) автоматически;
                          вариант B — ручной ввод суммы.
    Total               = Total Costs + Charges + Handling Fee // сумма к оплате
    
    • Project Duration и Warranty — параметры проекта (в неделях), вводятся вручную, в расчёт суммы не входят; идут в условия предложения и в PDF.
    • Валюта — IDR, формат 200,000.00 IDR (как в макете). Числа сохраняются как decimal; форматирование на отображении.
  • Состояния/вкладки: часть вкладки Costs (S30). Состояния: пустая смета (нет позиций → Total = 0), заполненная, печатный режим (для PDF — без интерактивных контролов).
  • Связи (data-flow): читает ProposalItem (problem/solution), ProposalCost (item/labor), ProposalCharge. Пишет: значения стоимостей/сборов/handling fee/duration/warranty в Proposal. Итог Total и развёртка позиций → источник для PDF (S58 + тело документа).
  • Мутации: update сумм/текстов позиций, charges, handling fee, duration, warranty; пересчёт Total Costs/Total (derived — не хранить рассинхронно с подстроками).
  • Права: Accounting/Owner Services — редактируют финансовые поля и handling fee; Operations — problem/solution/duration; остальные — read.
  • 🟡 [не было додумано]: (1) разделение строк стоимости на Cost Item (материалы/работы) и Cost Labor (труд) выведено из лейблов; структура ProposalCost{type: item|labor, name, amount}. (2) Природа Charges (отдельный сбор vs. наценка) в прототипе не подписана — трактуем как ручные дополнительные сборы. (3) Формула Handling Fee — открытый вопрос (F-handling-fee-calculation), в спеке зафиксированы оба варианта, выбор за заказчиком.
  • Edge cases: позиция без Cost Labor (только материалы) → labor=0; Total Costs=0 при пустой смете; Handling Fee при варианте A зависит от базы (Total Costs или Total Costs+Charges — уточнить с F-handling-fee-calculation); очень длинные тексты Problem/Solution → перенос/скролл (и корректный поток в PDF).

S58 — PDF cover (Repair and Project Proposal) [PDF]

  • Назначение: Титульная обложка экспортируемого PDF-документа предложения по ремонту/проекту.
  • Источник (Miro): «Screen 4», id 3458764672555329690 (texts=4, controls: —).
  • Layout: обложка-страница: лого/слово betterplace → заголовок документа → период → объект.
  • Данные на экране: betterplace (бренд), Repair and Project Proposal (тип документа), May 2026 (период), Villa Alaya (вилла).
  • Контролы и действия: нет (статическая обложка). Генерация инициируется с S30/S04 кнопкой Export PDF 🟡.
  • Состояния/вкладки: единственное состояние — рендер обложки; далее тело документа = развёртка S04 (per-position problem/solution/costs + итоги + duration/warranty).
  • Связи (data-flow): читает Proposal (бренд статичен, период = Month/Year/Completed at, вилла = Unit.name). Тело PDF собирается из S04. Логически генерируется на стадии Paid (или Approved для отправки на согласование владельцу) — точную стадию-триггер уточнить 🟡.
  • Мутации: нет (read-only экспорт; опционально — лог факта генерации в History).
  • Права: Owner Services/Accounting/Operations — экспорт. Документ адресован владельцу виллы.
  • 🟡 [не было додумано]: (1) кнопка Export PDF и стадия-триггер генерации (Approved для отправки на согласование vs. Paid как финальный документ) — в прототипе только обложка, без точки запуска. (2) период обложки (May 2026) маппится на Completed at/Month+Year Proposal.
  • Edge cases: период/вилла отсутствуют → плейсхолдеры; смета с 0 позиций → PDF только с обложкой (блокировать экспорт пустого 🟡); длинное имя виллы на обложке → перенос.

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

S31 (list, tabs Proposed/Approved/Paid)
   │ row click
   ▼
S30 (Proposal detail; tabs Proposal · Tasks · Costs · History)
   │ tab Costs / развёртка
   ▼
S04 (Problem/Solution/Costs → Total Costs + Charges + Handling Fee = Total; Duration/Warranty)
   │ Export PDF 🟡
   ▼
S58 (PDF cover) + тело = развёртка S04

Жизненный цикл (UC-5): S30.status: Proposed → Approved → Paid → порождение project-задач (E03) → выполнение → completion report (E06) → прикрепляется к Proposal → виден в колонке Report (S31). PDF (S58) генерируется из S04 на стадии Approved/Paid 🟡.

Cross-epic

  • E01 Properties/UnitsUnit (вилла), к которой привязано предложение.
  • E03 Tasks — вкладка Tasks (S30): project-задачи, порождаемые после Paid; исполнение и стоимости работ возвращаются в смету.
  • E06 Maintenance Reports — completion report о завершении проекта прикрепляется к Proposal (колонка Report, S31). NB: это не месячный Maintenance Report — отдельный документ завершения разового проекта; модель не дублировать, переиспользовать механизм отчёта E06 как «report-документ, привязанный к Proposal».

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

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

  • K-2 · Projects — отдельная вкладка, дом для денег: «Projects» — самостоятельная top-level вкладка (отделена от Maintenance Reports). Именно здесь costs/charges/handling fee. Projects = воронка согласования крупного (ремонт/реновация/инновация), в отличие от периодического maintenance.

  • Q-02 · handling-fee-calculation — РЕШЕНО (resolves finding F-handling-fee-calculation): handling fee = ручной ввод + возможные пресеты (НЕ авто-%). Это тип charge (charges = группа, handling fee = подтип). На вкладке Costs (S30/S04) не хватает кнопки «добавить charge» — charges-секция мануальная, поверх автоматических items.

  • Q-03 · total-costs structure / правильная вложенность — РЕШЕНО (resolves §103/§108 ASSUMPTION + open reconciliation). Логическая вложенность (дословно из диалога 2026-06-22):

    Projects → Proposal (tab)
      └─ Item ("Wall Paint")
           └─ под-позиции (new sink · installation · transport · waterproof paint · painter labor)
                → position_total
    Projects → Costs (tab)
      ├─ [AUTO] items из Proposal, под-позиции collapse/expand
      │     └─ Total Costs = Σ position_total           ← промежуточный итог («посередине»)
      ├─ [MANUAL] Charges  (ГРУППА)                      ← кнопка «+ add charge»
      │     ├─ Handling Fee = ТИП charge ВНУТРИ группы (ручной ввод + пресеты)
      │     └─ …прочие charges
      ├─ Project Costs = Total Costs + Charges           ← ФИНАЛЬНЫЙ итог (rename из «Total»)
      ├─ Warranty   — свободный текст
      └─ Duration   — свободный текст
    
    • Items на вкладке Proposal (S30); у каждого раскрываемые под-позиции + position_total.
    • На вкладке CostsTotal Costs = живой агрегат Σ position_total (§103-инвариант побеждает §27 snapshot-независимость); под-позиции collapse/expand.
    • ⚠️ Charges — это ГРУППА, Handling Fee — ТИП charge ВНУТРИ неё (НЕ соседняя строка). Это отменяет старую формулу тела (S04 §117, S30 §18-19, cross-screen §155) «Total Costs + Charges + Handling Fee = Total», где handling fee стоял сиблингом — неверно. Корректно: Charges уже включает handling fee.
    • Project Costs = Total Costs + Charges — финальный итог; переименовать Total → «Project Costs» (чтобы не «total total»).
    • Tasks-вкладки в Projects быть НЕ должно (она тут ни при чём — убрать из S30).
    • Warranty / Project Durationдва свободных текстовых поля (не считаются, ни от чего не зависят).
    • Дельта к вёрстке: Total Costs = живой Σ позиций; под-позиции раскрываемы; убрать Tasks-таб; rename итог → Project Costs; Charges = группа с вложенным Handling Fee + кнопка «+ add charge»; warranty/duration free-text. → Tier C U6.

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

  • Жизненный цикл статусов Proposed→Approved→Paid выведен из табов S31 + Status: S30; запрет регресса после Paid и точные роли-гейты переходов — наше уточнение (в прототипе явных transition-кнопок нет).
  • Структура ProposalCost (type: item|labor) и ProposalCharge — выведены из лейблов S04 (Cost Item / Cost Labor / Charges); в каталоге это плоские текст-строки.
  • Точка генерации PDF (Export PDF + стадия-триггер) — S58 в прототипе только обложка без кнопки запуска.
  • Handling Fee — формула не определена (F-handling-fee-calculation); зафиксированы оба варианта (auto-% / ручной), решение за заказчиком.
  • Reconciliation наименования S30 — в Miro назван «Copy of … Maintenance Report», но контент = Proposal; трактуем как Proposal detail во избежание смешения с E06.