From 9f9f359acbd43fd8ef42b0645532679404e81a51 Mon Sep 17 00:00:00 2001 From: BOHA Date: Sat, 4 Jul 2026 03:49:46 +0200 Subject: [PATCH] docs(spec): status quick actions + bidirectional project-order sync Co-Authored-By: Claude Fable 5 --- ...-07-04-status-quick-actions-sync-design.md | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 docs/superpowers/specs/2026-07-04-status-quick-actions-sync-design.md diff --git a/docs/superpowers/specs/2026-07-04-status-quick-actions-sync-design.md b/docs/superpowers/specs/2026-07-04-status-quick-actions-sync-design.md new file mode 100644 index 0000000..7614deb --- /dev/null +++ b/docs/superpowers/specs/2026-07-04-status-quick-actions-sync-design.md @@ -0,0 +1,96 @@ +# Status quick actions + project⇄order sync + +**Date:** 2026-07-04 · **Status:** Approved (interactive design w/ owner) · **Scope:** offers, received orders, projects + +## Problem + +Status changes are inconsistent: the Invoices list has a quick chip action (click → confirm → paid), +but Offers/ReceivedOrders/Projects tables have inert chips — every change needs the detail page. +ProjectDetail edits status via a free combobox in the form (no transitions, no confirm), unlike +OfferDetail/OrderDetail's transition buttons. Projects have **no status machine at all** (free-text +column). Order→project completion sync exists one-way (`syncProjectStatus`), but finishing a +**project** leaves its linked order untouched. + +## Decisions (owner) + +1. **Chip opens a menu** of valid next states (not single-click-advance) → ConfirmDialog per pick, + danger styling + cascade notes on destructive/irreversible ones. +2. **Reopen allowed**: `dokonceny/zruseny → aktivni` (projects), `dokoncena/stornovana → v_realizaci` + (received orders). **Reopening never cascades** to the linked record. +3. **Sync scope: finish + cancel, both directions.** Project `dokonceny` ⇄ order `dokoncena`; + project `zruseny` ⇄ order `stornovana`. +4. Offers quick menu: `Aktivovat` (draft — same number-assignment + PDF-archive semantics as the + detail), `Zneplatnit` (danger), and **"Vytvořit objednávku…" which opens the existing + create-order modal** (never a raw label flip to `ordered` — that state is owned by the + create-order flow). + +## Design + +### Backend + +**Projects gain a status machine** (`src/services/projects.service.ts`): +`VALID_TRANSITIONS = { aktivni: [dokonceny, zruseny], dokonceny: [aktivni], zruseny: [aktivni] }`. +`updateProject` validates status changes (token `invalid_transition` → Czech 400 in the route); +a legacy/unknown current status may transition to any canonical value (tolerant). `getProject` +returns `valid_transitions` (same contract as offers/orders). + +**Project → order cascade** (new), inside one transaction with the project update: + +- project → `dokonceny` ⇒ linked order → `dokoncena` **iff** order status ∈ {prijata, v_realizaci} +- project → `zruseny` ⇒ linked order → `stornovana` **iff** order status ∈ {prijata, v_realizaci} +- reopen ⇒ no order change; completed/cancelled orders are never resurrected by a cascade +- direct `tx` write (no service recursion); sibling projects of the same order are NOT touched + (multi-project orders are a rare schema possibility, not a flow; avoid surprise mass updates) +- the service returns what cascaded so the route writes an audit row for the order change too + +**Received orders** (`src/services/orders.service.ts`): + +- `VALID_TRANSITIONS` gains reopen: `dokoncena: [v_realizaci]`, `stornovana: [v_realizaci]` +- `syncProjectStatus` becomes reopen-aware: given the previous status, a transition OUT of a + terminal state (reopen) skips project sync entirely (decision 2). Forward transitions keep the + existing mapping (v_realizaci→aktivni no-op in practice, dokoncena→dokonceny, + stornovana→zruseny), and the cascaded project change now gets an audit row. +- item/section edit guards stay status-based, so a reopened order is editable again (intended). + +No DB migration (status columns are strings already). + +### Frontend + +**Shared `StatusChipMenu`** (`src/admin/components/StatusChipMenu.tsx`): renders a `StatusChip`; +click opens a small MUI `Menu` of actions; each action either opens a `ConfirmDialog` +(danger variant + `loading` during the request; cascade note in the message) or runs a plain +`onClick` (the modal-opening offer item). Hidden affordance fixed via `title` tooltip. Chip is +plain (non-clickable) when the user lacks the edit permission or no actions exist. + +**Offers list**: draft → `Aktivovat` (confirm notes the number assignment; after success the same +fire-and-forget PDF-archive call the detail does); active → `Vytvořit objednávku…` (opens the +existing modal) + `Zneplatnit` (danger); ordered → `Zneplatnit` (danger). + +**ReceivedOrders list**: prijata → `Zahájit realizaci`, `Stornovat` (danger, note: linked project +will be cancelled); v_realizaci → `Dokončit` (note: linked project will be completed), +`Stornovat` (danger); dokoncena/stornovana → `Obnovit` (reopen, no cascade note). + +**Projects list**: aktivni → `Dokončit` (note when `order_id` present: linked order will be +completed), `Zrušit` (danger, order-storno note); dokonceny/zruseny → `Obnovit`. + +**ProjectDetail**: status `Select` removed from the form (and status removed from the save +payload); header gains transition buttons rendered from `valid_transitions` +(`Dokončit projekt` / `Zrušit projekt` danger / `Obnovit projekt`), each via ConfirmDialog with +cascade notes, as a status-only PUT with its own mutation +(invalidate: projects, orders, offers, invoices, warehouse). OrderDetail keeps its pattern; +its transition label shows `Obnovit` when reopening from a terminal state. + +Invalidation for all new status mutations: `["orders","offers","projects","invoices"]` +(+`["warehouse"]` on project mutations, matching the page's existing set). + +### Tests + +Service-level (real `app_test` DB): project transition validation (all legal/illegal edges incl. +legacy-status tolerance), project→order cascade both mappings + the guard (no cascade onto +dokoncena/stornovana orders), reopen-no-cascade both directions, order reopen transitions, and +regression: order→project sync still fires on forward transitions. + +## Out of scope + +Offer machine changes (unchanged: draft→active→ordered/invalidated), issued orders, sibling-project +propagation, ReceivedOrders detail-page button changes beyond the automatic reopen button.