docs(fusion_plating_shopfloor): brainstorm spec for tablet redesign
Multi-section design covering: - 3 OWL client actions: fp_shopfloor_landing (replaces fp_shopfloor_tablet + folds in fp_plant_overview), fp_job_workspace (NEW full-screen WO surface), fp_manager_dashboard (refactored — 4 sibling tabs incl. Workflow Funnel, Approval Inbox, At-Risk). - 5 shared OWL services: WorkflowChip, GateViz, SignaturePad, HoldComposer, KanbanCard — reused across all three client actions to enforce one-widget-one-place and prevent terminology drift. - Backend additions: 8 new RPC endpoints, blocker_kind/reason computes on fp.job.step, display_wo_name + late_risk_ratio + active_step_id on fp.job, bottleneck_score on fp.work.centre, auto-pause cron (fixes 411h ghost timer), ACL lift for operator group per "techs wear multiple hats" rule. - Terminology pass: WO # 00001 (display only, sequence rename deferred), Shop Floor / Up Next / Embrittlement Bakes / etc. - 5-phase deploy sequence, each phase independently shippable. - Out of scope (deferred to v2): cost roll-up, cycle time, per-tech throughput, system-wide sequence rename. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,487 @@
|
||||
# Shop Floor Tablet Redesign — Design Spec
|
||||
|
||||
**Date:** 2026-05-22
|
||||
**Status:** Brainstorm complete, awaiting user review
|
||||
**Authors:** Garry Singh + Claude
|
||||
**Module owners:** `fusion_plating_shopfloor`, `fusion_plating_jobs`
|
||||
**Target client:** EN Technologies (Fusion Plating)
|
||||
|
||||
---
|
||||
|
||||
## 1. Context
|
||||
|
||||
The current Shop Floor tablet view (client action `fp_shopfloor_tablet`, OWL component `ShopfloorTablet`) was built during the initial Fusion Plating implementation. Since then the underlying models — `fp.job`, `fp.job.step`, `fp.job.workflow.state`, `fp.certificate`, `fp.thickness.reading`, `fp.job.consumption`, `fp.job.node.override`, `fp.racking.inspection` and friends — have grown substantially. Many of those new fields, actions, and workflows are not surfaced on the tablet.
|
||||
|
||||
Symptoms observed on a live development instance:
|
||||
- Step name shows "Active: Blasting" with no WO/customer context
|
||||
- Qty rendered as "Qty 17/1" — ambiguous direction
|
||||
- Step position shown as "step 1.1/11" (sequence divided by 10)
|
||||
- Active timer reading **411:52:16** — a stale start that never finished and was never auto-paused
|
||||
- "SIGN-OFF REQUIRED" chip is informational only; no actual sign-off control
|
||||
- Customer spec, drawings, recipe overrides, milestone progress, holds, and most lifecycle actions are invisible
|
||||
|
||||
Three roles operate the system: **Owner**, **Manager**, **Technician**. Technicians wear multiple hats (receiving, plating, QC, shipping) — the client explicitly wants minimal gating between roles.
|
||||
|
||||
## 2. Goals
|
||||
|
||||
- A technician can manage a WO end-to-end from a single full-screen workspace, without typing into search bars or jumping to the back-office.
|
||||
- A manager can see at a glance: where every WO is in the workflow, what needs their decision right now, what's trending late, and where the bottlenecks are.
|
||||
- All recent additions to `fp.job` / `fp.job.step` (workflow milestones, blocker reasons, recipe overrides, customer spec, etc.) are surfaced on the tablet and manager dashboard.
|
||||
- Terminology matches how techs talk on the shop floor — "WO # 00001" not "WH/JOB/00001".
|
||||
- The system never displays a 411-hour ghost timer.
|
||||
|
||||
## 3. Non-goals (v1)
|
||||
|
||||
- Multi-tablet pairing per technician
|
||||
- Offline-first / PWA mode
|
||||
- Voice input
|
||||
- Performance optimization beyond ~500 active jobs (current scale: ~50)
|
||||
- Webhooks to external dashboards
|
||||
- Cost roll-up per job, cycle time per recipe, per-tech throughput (P2 — deferred to v2)
|
||||
- System-wide rename of `fp.job` sequence (`WH/JOB/...` → `WO ...`) — display-only on tablet for now; back-office/reports/emails keep current sequence until a separate decision is made
|
||||
|
||||
## 4. Terminology decisions
|
||||
|
||||
All approved in brainstorm:
|
||||
|
||||
| Element | Was | Is now |
|
||||
|---|---|---|
|
||||
| Page title | "Tablet Station" | **Shop Floor** (with station chip e.g. "@ EN Plating Tank") |
|
||||
| Document number | `WH/JOB/00001` | **`WO # 00001`** (display only) |
|
||||
| Active step header | "Active: Blasting" | **"WO # 00001 — Blasting"** (Step 1 of 11) |
|
||||
| Qty | "Qty 17/1" | **"1 / 17 done"** + scrap subtext + mini progress bar |
|
||||
| Step position | "step 1.1/11" | **"Step 1 of 11"** |
|
||||
| Sign-off chip | "SIGN-OFF REQUIRED" | **"Finish & Sign Off"** action button (replaces plain Finish) |
|
||||
| Queue heading | "My Queue" | **"Up Next"** (at this station) |
|
||||
| Bath state | "OPERATIONAL / LOG: OUT_OF_SPEC" | **"Operating"** / **"Last log out of spec"** |
|
||||
| Bake panel | "Bake Windows" | **"Embrittlement Bakes"** |
|
||||
| Gate panel | "First-Piece Gates" | **"First-Piece Inspections"** |
|
||||
| Tile set | 6 mixed tiles | **4 tech-relevant tiles**: Ready · Running · Bakes Due · Holds (others move to manager dashboard) |
|
||||
| Stale timer | "411:52:16" | Auto-pause at 8h (configurable) with chatter audit; display switches to "Started Nd ago" past 24h |
|
||||
|
||||
## 5. Architecture — option B (specialized components + shared services)
|
||||
|
||||
Three OWL client actions, five shared OWL services, a small set of backend additions. Each client action is independently deployable.
|
||||
|
||||
```
|
||||
┌────────────────────────────────┐ ┌────────────────────────────────┐ ┌────────────────────────────────┐
|
||||
│ fp_shopfloor_landing │ │ fp_job_workspace │ │ fp_manager_dashboard │
|
||||
│ (replaces fp_shopfloor_tablet │ │ NEW — full-screen WO surface │ │ refactored — 4 tabs │
|
||||
│ + folds in fp_plant_overview)│ │ │ │ │
|
||||
│ • station-scoped kanban │ │ • sticky header + WO chips │ │ • Workflow Funnel (default) │
|
||||
│ • All-Plant toggle │ │ • workflow milestone bar │ │ • Approval Inbox │
|
||||
│ • QR scan, station picker │ │ • step list + side panel │ │ • Plant Board (existing) │
|
||||
│ • tap card → JobWorkspace │ │ • sticky action rail │ │ • At-Risk │
|
||||
└────────────────────────────────┘ └────────────────────────────────┘ └────────────────────────────────┘
|
||||
│ │ │
|
||||
└──────────────────────────────────┴──────────────────────────────────┘
|
||||
│
|
||||
┌────────────┴────────────┐
|
||||
│ Shared OWL services │
|
||||
│ WorkflowChip · GateViz │
|
||||
│ SignaturePad · KanbanCard │
|
||||
│ HoldComposer │
|
||||
└─────────────────────────┘
|
||||
```
|
||||
|
||||
### 5.1 Shared OWL services
|
||||
|
||||
| Service | Used by | Props | Depends on |
|
||||
|---|---|---|---|
|
||||
| **WorkflowChip** | Landing card · Workspace header · Manager funnel | `{ state: {id, name, color}, nextActionLabel? }` | `fp.job.workflow.state` records (already shipped). Reads `color` field. |
|
||||
| **GateViz** | Workspace step rows · Manager "Needs Worker" cards | `{ canStart, blockerKind, blockerReason, jumpTarget? }` | New `fp.job.step.blocker_kind` + `blocker_reason` computes |
|
||||
| **SignaturePad** | Workspace (Finish & Sign Off) · Cert issue | `{ title, contextLabel, onSubmit(dataUri), onCancel }` | Odoo `dialog` service; HTML canvas + pointer events |
|
||||
| **HoldComposer** | Workspace (Hold button) · Manager Approval Inbox | `{ jobId, stepId?, defaultQty, partRef, onCreated(hold) }` | New endpoint `/fp/workspace/hold` (with photo attachment) |
|
||||
| **KanbanCard** | Landing (station + all-plant) · Manager (Plant Board + Workflow Funnel) | `{ data, density: 'compact'\|'normal', showWorkflowChip, showWorkcenter, showAssignedTo, onTap }` | Embeds `WorkflowChip` + `GateViz` badge |
|
||||
|
||||
Each service is its own file under `fusion_plating_shopfloor/static/src/js/components/`. Roughly 80–200 lines OWL + 30–80 lines SCSS per service.
|
||||
|
||||
### 5.2 Landing component (`fp_shopfloor_landing`)
|
||||
|
||||
Replaces today's `fp_shopfloor_tablet`, folds in `fp_plant_overview`. Single entry surface for technicians.
|
||||
|
||||
**Layout regions** (top-to-bottom):
|
||||
|
||||
1. **Header strip** — "Shop Floor" title, station chip, station picker, mode toggle (`Station` ⟷ `All Plant`), QR scan controls (Code + Camera), refresh indicator.
|
||||
2. **KPI tile row (4 tiles)** — Ready · Running · Bakes Due · Holds. Holds turns red when > 0.
|
||||
3. **Kanban board** — columns = work centres; cards = `KanbanCard` (one per WO at that work centre); urgency-sorted within column (existing logic in `plant_overview.py` carries over). Drag-and-drop between columns keeps current behaviour.
|
||||
4. **Optional left filter rail** (collapsed by default) — search box, priority, customer, due-by, blocker filter. Promote the existing plant_overview search bar.
|
||||
5. **Footer** — auto-refresh indicator + "Last sync HH:MM:SS".
|
||||
|
||||
**Mode behaviour:**
|
||||
|
||||
| Mode | Columns shown | Default when |
|
||||
|---|---|---|
|
||||
| **Station** | Paired work centre + Unassigned + next 1–2 work centres in the recipe flow | A station is paired (via QR scan or picker) |
|
||||
| **All Plant** | Every active work centre, recipe-flow order | No station paired, OR user toggles |
|
||||
|
||||
Toggle persists in `localStorage` per tablet (same pattern as `fp_tablet_station_id`).
|
||||
|
||||
**Card tap behaviour:**
|
||||
|
||||
```js
|
||||
action.doAction({
|
||||
type: 'ir.actions.client',
|
||||
tag: 'fp_job_workspace',
|
||||
params: { job_id: card.job_id, focus_step_id: card.current_step_id }
|
||||
});
|
||||
```
|
||||
|
||||
Browser back returns to Landing with kanban scroll/mode preserved.
|
||||
|
||||
**QR scan dispatch** (existing `/fp/shopfloor/scan` endpoint, unchanged):
|
||||
|
||||
| Scanned | Behaviour |
|
||||
|---|---|
|
||||
| `FP-STATION:<code>` | Pair tablet, switch to Station mode |
|
||||
| `FP-JOB:<name>` | Open JobWorkspace for that WO |
|
||||
| `FP-STEP:<id>` | Open JobWorkspace, focus that step |
|
||||
| `FP-TANK:<code>` / `FP-BATH:<name>` | Chemistry quick-log dialog (existing endpoint) |
|
||||
| `FP-OVEN:<code>` | Jump to next bake awaiting that oven |
|
||||
|
||||
**Auto-refresh** — every 15s.
|
||||
|
||||
**Files:**
|
||||
|
||||
```
|
||||
fusion_plating_shopfloor/
|
||||
controllers/landing_controller.py ← NEW (~250 lines)
|
||||
static/src/js/shopfloor_landing.js ← OWL (~600 lines)
|
||||
static/src/xml/shopfloor_landing.xml
|
||||
static/src/scss/shopfloor_landing.scss
|
||||
```
|
||||
|
||||
### 5.3 Job Workspace component (`fp_job_workspace`)
|
||||
|
||||
The heart of the redesign. Full-screen surface a tech opens by tapping a kanban card.
|
||||
|
||||
**Layout regions** (sticky top → scrollable middle → sticky bottom):
|
||||
|
||||
| Region | Sticky | Data | Behaviour |
|
||||
|---|---|---|---|
|
||||
| **Back** | top | — | `doAction` back to Landing, preserves kanban scroll/mode |
|
||||
| **WO header** | top | `display_wo_name`, `partner_id`, `part_catalog_id` + rev, qty/qty_done/qty_scrapped, `date_deadline`, `workflow_state_id`, `quality_hold_count`, `customer_spec_id` | `+1 Done` / `−1 Done` / `+1 Scrap` quick bumps inline. Holds count → opens Holds drawer. |
|
||||
| **Workflow milestone bar** | top | All `fp.job.workflow.state` records ordered by sequence; current = `workflow_state_id`; `next_milestone_action` + `next_milestone_label` | Dots: passed (●), current (filled), pending (○). "Next" button on right fires `/fp/workspace/advance_milestone`. Disabled until preconditions met. |
|
||||
| **Step list** (left/center, scrolls) | scrolls | `fp.job.step_ids` sorted by `sequence` | Each row uses step-row template (see below). Active step auto-scrolled and auto-expanded if `focus_step_id` param set. |
|
||||
| **Side panel** (collapsible right) | scrolls | `customer_spec_id` (PDF), attachments, chatter | Three sub-cards: **Spec** (inline via `fusion_pdf_preview`), **Drawings**, **Notes** (chatter — read + quick-add). Collapses to icon strip on narrow screens. |
|
||||
| **Action rail** | bottom | — | Always: Create Hold (`HoldComposer`), Add Note, Photo. Conditional: Issue Cert (when `_fp_has_draft_required_certs()`), Mark Done / Schedule Delivery / Mark Shipped (per `next_milestone_action`). |
|
||||
|
||||
**Step row anatomy:**
|
||||
|
||||
- **Collapsed** (default for done/pending/paused): one line — icon + Step N · Name + assigned tech + duration + state badge
|
||||
- **Expanded active** (auto for `state == 'in_progress'`): recipe chips + instructions + primary + secondary actions
|
||||
- **Expanded by tap** (any step): same shape, action buttons gated by `can_start`
|
||||
|
||||
**Per-step actions:**
|
||||
|
||||
| Button | Visible when | Calls |
|
||||
|---|---|---|
|
||||
| Start | `state in ('ready','paused')` AND `can_start` | `/fp/shopfloor/start_wo` |
|
||||
| Finish (or Finish & Sign Off) | `state == 'in_progress'` | `/fp/shopfloor/stop_wo` (finish=true) OR `/fp/workspace/sign_off` if `requires_signoff` |
|
||||
| Pause | `state == 'in_progress'` | `step.button_pause()` |
|
||||
| Skip | `state in ('ready','paused')` AND user is supervisor+ | `step.button_skip()` |
|
||||
| Move Parts | always | `FpMovePartsDialog` (existing) |
|
||||
| Move Rack | when `kind == 'rack'` | `FpMoveRackDialog` (existing) |
|
||||
| Quick QC | when `quick_look_prompt_ids` non-empty | `step.action_open_quick_look()` |
|
||||
| Operator Inputs | when step has unrecorded inputs | `step.action_open_input_wizard()` (existing wizard) |
|
||||
| Photo | always | inline camera → attach to step |
|
||||
| Stop Timer (correction) | when duration looks wrong | `FpStopTimerDialog` (existing) |
|
||||
| Open in backend | always (small icon) | `doAction` to fp.job.step form (escape hatch) |
|
||||
|
||||
When step is **blocked** (`can_start == False`), action button row is replaced by `GateViz` block.
|
||||
|
||||
When step is **opted out** (`override_ids` says excluded), row shows ✕ icon + "Skipped per recipe override" + supervisor-only "Re-include" button.
|
||||
|
||||
**Auto-pause integration** — if `_cron_autopause_stale_steps` flips a step to paused, the row's chatter reflects "Auto-paused after Nh idle". Tech can tap Resume.
|
||||
|
||||
**Auto-refresh** — every 15s.
|
||||
|
||||
**Files:**
|
||||
|
||||
```
|
||||
fusion_plating_shopfloor/
|
||||
controllers/workspace_controller.py ← NEW (~400 lines)
|
||||
static/src/js/job_workspace.js ← OWL (~800 lines)
|
||||
static/src/xml/job_workspace.xml
|
||||
static/src/scss/job_workspace.scss
|
||||
static/src/js/components/{workflow_chip,gate_viz,signature_pad,hold_composer,kanban_card}.js
|
||||
```
|
||||
|
||||
### 5.4 Manager Dashboard refactor (`fp_manager_dashboard`)
|
||||
|
||||
Same client action, **four sibling tabs** under a shared header + KPI strip.
|
||||
|
||||
**KPI strip (extended):** keep existing 4 always-on (Unassigned Steps · In Progress · Ready to Ship · Awaiting Assignment) + existing conditional reds (Missed Bakes · Open Holds · Stale Steps · Predecessor Locked) + **2 new: Pending Cert · At-Risk**.
|
||||
|
||||
**Tabs:**
|
||||
|
||||
| Tab | Default? | Content |
|
||||
|---|---|---|
|
||||
| **Workflow Funnel** | yes | Vertical stack of `fp.job.workflow.state` records. Each row shows stage chip + count badge + first ~5 `KanbanCard`s + "+ N more" drawer. Bar chart bar behind the row scaled to count. Tap card → JobWorkspace. |
|
||||
| **Approval Inbox** | no | 4 grouped strips: **Holds to Release** (`state in ('on_hold','under_review')`), **Certs to Issue** (`all_steps_terminal` + draft required cert), **Scrap to Review** (recent `qty_scrapped` bumps with operator reason), **Override Requests** (deferred — placeholder). Per-row inline action buttons + bulk-action ("Release all"). |
|
||||
| **Plant Board** | no | Today's existing 3-column "Needs Worker / In Progress / Team" view — unchanged behaviour. Becomes one tab among four. |
|
||||
| **At-Risk** | no | 3 sub-panels: **Trending Late** (sorted by `late_risk_ratio` desc, top 20), **Hold Reasons** (open holds grouped by `hold_reason`), **Bottleneck Heatmap** (work centres ranked by `bottleneck_score`). |
|
||||
|
||||
**Cross-tab features:** live 8s refresh (existing cadence), QR scan in header, "Take Over Tablet" supervisor handover.
|
||||
|
||||
**Permissions:** dashboard already gated to `group_fusion_plating_supervisor`+. That stays. Owner + Manager hit this; Technicians don't.
|
||||
|
||||
**Files:**
|
||||
|
||||
```
|
||||
fusion_plating_shopfloor/
|
||||
controllers/manager_controller.py ← add 3 endpoints (funnel, approval_inbox, at_risk)
|
||||
static/src/js/manager_dashboard.js ← refactor: extract Plant Board, add 3 sibling tabs
|
||||
static/src/xml/manager_dashboard.xml
|
||||
static/src/scss/manager_dashboard.scss
|
||||
```
|
||||
|
||||
## 6. Backend support
|
||||
|
||||
### 6.1 HTTP endpoints
|
||||
|
||||
All `type='jsonrpc'`, `auth='user'`.
|
||||
|
||||
**NEW** — added by this work:
|
||||
|
||||
| Endpoint | Lives in | Purpose |
|
||||
|---|---|---|
|
||||
| `POST /fp/landing/kanban` | `landing_controller.py` | Station OR all-plant kanban data |
|
||||
| `POST /fp/workspace/load` | `workspace_controller.py` | Full Job Workspace payload |
|
||||
| `POST /fp/workspace/hold` | `workspace_controller.py` | HoldComposer create (with photo) |
|
||||
| `POST /fp/workspace/sign_off` | `workspace_controller.py` | Signature + finish step atomically |
|
||||
| `POST /fp/workspace/advance_milestone` | `workspace_controller.py` | Fire `next_milestone_action` |
|
||||
| `POST /fp/manager/funnel` | `manager_controller.py` (add) | Workflow funnel data |
|
||||
| `POST /fp/manager/approval_inbox` | `manager_controller.py` (add) | Holds + draft certs + scrap to review |
|
||||
| `POST /fp/manager/at_risk` | `manager_controller.py` (add) | Late-risk + hold reasons + bottlenecks |
|
||||
|
||||
**KEPT** — unchanged, used by new components via wrappers: `/fp/shopfloor/scan`, `start_wo`, `stop_wo`, `start_bake`, `end_bake`, `log_chemistry`, `log_thickness_reading`, `bump_qty_done`, `bump_qty_scrapped`, `mark_gate`, `pair_station`.
|
||||
|
||||
**DEPRECATED** — kept as stubs for 1 release, then removed:
|
||||
- `/fp/shopfloor/tablet_overview` → calls `/fp/landing/kanban` internally
|
||||
- `/fp/shopfloor/plant_overview` → calls `/fp/landing/kanban?mode=all_plant`
|
||||
- `/fp/shopfloor/queue` → removed (no replacement)
|
||||
|
||||
### 6.2 Model fields / computes
|
||||
|
||||
On `fp.job` (`fusion_plating_jobs/models/fp_job.py`):
|
||||
|
||||
| Field | Type | Purpose |
|
||||
|---|---|---|
|
||||
| `display_wo_name` | computed Char | "WO # 00001" formatter from `name` |
|
||||
| `late_risk_ratio` | computed Float, stored | `remaining_planned_minutes / minutes_to_deadline` |
|
||||
| `active_step_id` | computed Many2one→fp.job.step | Current `in_progress` step (Workspace landing focus) |
|
||||
|
||||
On `fp.job.step` (`fusion_plating_jobs/models/fp_job_step.py`):
|
||||
|
||||
| Field | Type | Purpose |
|
||||
|---|---|---|
|
||||
| `blocker_kind` | computed Selection | `predecessor` · `contract_review` · `parts_not_received` · `racking_required` · `manager_input` · `none` |
|
||||
| `blocker_reason` | computed Char | Human reason (e.g. "Waiting on Step 3: Activation") |
|
||||
| `blocker_jump_target_model` | computed Char | Optional tap-to-jump target model |
|
||||
| `blocker_jump_target_id` | computed Integer | Optional tap-to-jump target id |
|
||||
|
||||
On `fp.work.centre` (`fusion_plating/models/fp_work_centre.py`):
|
||||
|
||||
| Field | Type | Purpose |
|
||||
|---|---|---|
|
||||
| `bottleneck_score` | computed Float, non-stored | `active_step_count × avg_wait_minutes` |
|
||||
| `avg_wait_minutes` | computed Float, non-stored | Rolling 7-day avg ready→start wait |
|
||||
|
||||
On `fusion.plating.process.node` (recipe node):
|
||||
|
||||
| Field | Type | Purpose |
|
||||
|---|---|---|
|
||||
| `long_running` | Boolean | Opt out of auto-pause (24h bakes etc.) |
|
||||
|
||||
### 6.3 Auto-pause cron
|
||||
|
||||
```xml
|
||||
<!-- fusion_plating_jobs/data/fp_cron_data.xml -->
|
||||
<record id="ir_cron_autopause_stale_steps" model="ir.cron">
|
||||
<field name="name">FP Jobs: auto-pause stale in-progress steps</field>
|
||||
<field name="model_id" ref="model_fp_job_step"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">model._cron_autopause_stale_steps()</field>
|
||||
<field name="interval_number">30</field>
|
||||
<field name="interval_type">minutes</field>
|
||||
<field name="active" eval="True"/>
|
||||
</record>
|
||||
```
|
||||
|
||||
Method (`fp_job_step.py`):
|
||||
|
||||
```python
|
||||
@api.model
|
||||
def _cron_autopause_stale_steps(self):
|
||||
threshold = float(self.env['ir.config_parameter'].sudo()
|
||||
.get_param('fp.shopfloor.autopause_threshold_hours', 8))
|
||||
deadline = fields.Datetime.now() - timedelta(hours=threshold)
|
||||
stale = self.search([
|
||||
('state', '=', 'in_progress'),
|
||||
('date_started', '<', deadline),
|
||||
('recipe_node_id.long_running', '=', False),
|
||||
])
|
||||
for step in stale:
|
||||
step.button_pause()
|
||||
step.message_post(body=Markup(
|
||||
"<b>Auto-paused</b> after %.1fh idle. "
|
||||
"Resume from the tablet when work continues."
|
||||
) % threshold)
|
||||
_logger.info("Auto-paused step %s after %.1fh idle", step.id, threshold)
|
||||
```
|
||||
|
||||
`ir.config_parameter` key: `fp.shopfloor.autopause_threshold_hours` (default `8`).
|
||||
|
||||
### 6.4 ACL changes (operator group)
|
||||
|
||||
Per "techs wear multiple hats" rule — minimal new gates.
|
||||
|
||||
| Model | Read | Write | Create | Unlink | Notes |
|
||||
|---|---|---|---|---|---|
|
||||
| `fp.certificate` | ✓ existing | **NEW ✓** | — | — | Flip draft → issued from tablet "Issue Cert" |
|
||||
| `fp.thickness.reading` | **NEW ✓** | **NEW ✓** | **NEW ✓** | — | Capture Fischerscope readings from tablet |
|
||||
| `fp.job.node.override` | **NEW ✓** | — | — | — | Read-only — tech sees opt-out badge |
|
||||
|
||||
Supervisor-only operations enforced in `workspace_controller.py` (not via ACL):
|
||||
- Step Skip (`button_skip`)
|
||||
- Hold Release (state transition `on_hold` → `released`)
|
||||
- Override Re-include
|
||||
|
||||
### 6.5 Terminology — `display_wo_name`
|
||||
|
||||
`fp.job.display_wo_name` is a computed Char that formats `name` as `WO # 00001`. All new tablet/dashboard payloads use this field. The underlying `fp.job.name` (`WH/JOB/00001`) stays unchanged — reports, emails, back-office forms continue using `name`.
|
||||
|
||||
System-wide sequence rename is **out of scope** for this work. If pursued separately, it requires: (a) updating `ir.sequence` prefix to `WO `, (b) backfill script for existing records, (c) coordination with any external integrations that grep on the old prefix.
|
||||
|
||||
## 7. Build & deploy sequence
|
||||
|
||||
Each phase is independently deployable. Rollback is per-phase, not all-or-nothing.
|
||||
|
||||
| Phase | Ships | Independently deployable? |
|
||||
|---|---|---|
|
||||
| **1** | Shared OWL services + JobWorkspace + workspace_controller + fp.job.step blocker_* computes + display_wo_name. Opens from existing `fp.job` form smart button. | Yes — works before Landing refactor |
|
||||
| **2** | Auto-pause cron + ACL lift + `late_risk_ratio` + `active_step_id` computes | Yes — silent infra |
|
||||
| **3** | landing_controller + `fp_shopfloor_landing` component. Old `fp_shopfloor_tablet` menu redirected. PlantOverview menu hidden. | Yes — Workspace already works via smart button |
|
||||
| **4** | 3 new manager endpoints + manager dashboard refactor (4 tabs) + `bottleneck_score` compute | Yes |
|
||||
| **5** | Cleanup: remove deprecated endpoint stubs, retire fp_plant_overview module dir | Last |
|
||||
|
||||
## 8. Testing strategy
|
||||
|
||||
### 8.1 Python tests (`fusion_plating_shopfloor/tests/`)
|
||||
|
||||
| Test | Verifies |
|
||||
|---|---|
|
||||
| `test_display_wo_name` | Formatter handles various `name` shapes |
|
||||
| `test_late_risk_ratio` | Correct ratio with deadline / no deadline / overdue / not started |
|
||||
| `test_active_step_id` | Sole in_progress step; empty when none; first-by-sequence when multiple |
|
||||
| `test_blocker_kind_and_reason` | Each kind returns correct enum + human string + jump target |
|
||||
| `test_autopause_cron` | Stale flips; chatter posted; respects `long_running`; idempotent |
|
||||
| `test_workspace_load_payload` | Full payload shape — keys, types, opted-out marked |
|
||||
| `test_workspace_sign_off` | Signature captured, step finished, empty-sig rejected |
|
||||
| `test_workspace_advance_milestone` | Fires only when preconditions met; friendly error otherwise |
|
||||
| `test_hold_composer_create` | Hold + photo + qty split; rollback on validation error |
|
||||
| `test_acl_operator_permissions` | Operator can issue cert, cannot skip step (controller gate) |
|
||||
| `test_funnel_and_inbox` | Funnel grouping correct; inbox returns all 4 buckets |
|
||||
|
||||
### 8.2 OWL tests (light)
|
||||
|
||||
- `WorkflowChip` renders correct color per state
|
||||
- `GateViz` renders correct copy per blocker_kind
|
||||
- `SignaturePad` returns non-empty data URI after stroke
|
||||
- `HoldComposer` validates qty ≤ remaining before submit
|
||||
- `KanbanCard` collapses chips at compact density
|
||||
|
||||
### 8.3 Manual QA checklist
|
||||
|
||||
Lives at `docs/qa/shopfloor-redesign-qa.md`. 10-step walkthrough covering: pairing → Landing modes → tap card → Workspace → Finish & Sign Off → Create Hold → Manager Funnel → Approval Inbox release → auto-pause test → dark mode.
|
||||
|
||||
## 9. Observability
|
||||
|
||||
- `_logger.INFO` on milestone advance, hold create from tablet, sign-off, auto-pause
|
||||
- `_logger.WARNING` on workspace_load with bad job_id, sign_off with empty data URI
|
||||
- `_logger.EXCEPTION` on controller failures (existing pattern)
|
||||
- Chatter audit on auto-pause, hold create, milestone advance, sign-off
|
||||
|
||||
Future metrics (flagged, no infra now): tablet refresh frequency, time-to-Start from Workspace open, auto-pause rate, hold creation rate.
|
||||
|
||||
## 10. Edge cases
|
||||
|
||||
| Case | Handling |
|
||||
|---|---|
|
||||
| Job has zero steps | "Recipe not generated" placeholder + back-office link |
|
||||
| Job has 50+ steps | Standard scroll, no virtualization in v1 |
|
||||
| All steps in_progress (defensive) | `active_step_id` picks first by sequence; logs warning |
|
||||
| No workflow states defined | Bar hides; Next button disabled |
|
||||
| Step state changed during 15s gap | Refresh corrects; no error toast |
|
||||
| Two techs tap Start simultaneously | `button_start` idempotent; second call returns state |
|
||||
| Wrong station scanned | Header "Unpair" link; localStorage cleared |
|
||||
| Network drop mid-action | Toast "Saving failed — tap to retry"; UI state preserved |
|
||||
| 24h-bake step | `recipe_node.long_running=True` skips auto-pause |
|
||||
| Customer spec PDF missing | Side panel: "No customer spec attached" |
|
||||
| Funnel with 200+ jobs in stage | Top 5 cards + "View all (N)" drawer |
|
||||
| Operator with no facility | Landing prompts pick-or-scan station |
|
||||
| HoldComposer fails after photo upload | Photo cleaned in `except` block — no orphans |
|
||||
| Manual step (no recipe_node_id) | Chips/instructions empty — OK |
|
||||
| Sign-off step finished from back-office | Workspace re-renders without re-prompting |
|
||||
| Tech opens Workspace for unassigned job | Allowed — read+write per "many hats" rule |
|
||||
|
||||
## 11. Performance
|
||||
|
||||
| Surface | Load shape | Mitigation |
|
||||
|---|---|---|
|
||||
| Landing kanban (All Plant, ~400 cards) | Existing `plant_overview.py` batch prefetch carries over | None new |
|
||||
| Workspace load (1 job × ~11 steps) | Trivial | None |
|
||||
| Manager funnel (~50 jobs × 9 stages) | Trivial | None |
|
||||
| Manager At-Risk (7-day step state scan) | Potentially heavy on large plants | Cache 60s per facility |
|
||||
| Auto-pause cron | Filtered query, no N+1 | None |
|
||||
| `late_risk_ratio` (stored) | Recomputed on step state change | `@api.depends` triggers |
|
||||
|
||||
## 12. Rollback strategy
|
||||
|
||||
| Phase | Rollback |
|
||||
|---|---|
|
||||
| 1 (Workspace) | Hide smart-button entry in fp.job form. Workspace becomes orphan, harmless. |
|
||||
| 2 (Cron + ACL) | Disable cron via UI. ACL changes are CSV-line edits. |
|
||||
| 3 (Landing) | Re-enable old `fp_shopfloor_tablet` menu. Old endpoint stub still active. |
|
||||
| 4 (Manager) | Revert manager_dashboard.xml to single Plant Board tab. |
|
||||
| 5 (Cleanup) | Defer if issues — leave stubs longer. |
|
||||
|
||||
Only stored field added is `late_risk_ratio` Float — additive `ALTER TABLE`, safe to drop.
|
||||
|
||||
## 13. Backwards compatibility
|
||||
|
||||
- All existing QR codes keep working — `/fp/shopfloor/scan` unchanged
|
||||
- Existing `fp.job` form smart buttons → Workspace opens via doAction
|
||||
- Existing `fp.job.step` form view stays as "Open in backend" escape hatch
|
||||
- Old reports/emails keep showing `WH/JOB/00001` (sequence rename deferred)
|
||||
|
||||
## 14. Decisions log
|
||||
|
||||
| Decision | Rationale |
|
||||
|---|---|
|
||||
| Hybrid mental model (queue → workspace), not pure queue or pure job-first | Queue is the natural entry; full workspace solves "manage the whole job" goal |
|
||||
| One tablet, no role-segmentation per persona | Client said techs wear multiple hats; minimal gating |
|
||||
| Architecture B (specialized components + shared services) over mega-component | Each surface has its own lifecycle; shared services enforce consistency |
|
||||
| Station-scoped kanban as default landing, All Plant as toggle | Matches physical reality (tablet at station) + provides escape hatch |
|
||||
| Approval Inbox + Workflow Funnel as new manager tabs, Plant Board stays | Tactical (assignment) and strategic (where is everything) views coexist |
|
||||
| Auto-pause stale timers at 8h default | Solves the 411-hour ghost timer permanently; protects cost/cycle-time math |
|
||||
| WO # display only; sequence rename deferred | Lower risk; user can choose system-wide rename separately |
|
||||
| ACL lift for operator group on cert / thickness reading / override read | Per "techs wear many hats" rule; supervisor-only ops enforced in controller, not ACL |
|
||||
|
||||
## 15. Out of scope for v1
|
||||
|
||||
- Multi-tablet pairing per tech
|
||||
- Offline-first / PWA mode
|
||||
- Voice input
|
||||
- Performance optimisation beyond ~500 active jobs
|
||||
- Webhooks to external dashboards
|
||||
- Cost roll-up per job (P2)
|
||||
- Cycle time per recipe (P2)
|
||||
- Notification audit feed (P2)
|
||||
- Per-tech throughput (P2)
|
||||
- System-wide `fp.job` sequence rename to `WO ` prefix
|
||||
|
||||
---
|
||||
|
||||
**Next step:** user reviews this spec; once approved, transition to `superpowers:writing-plans` skill to produce the phased implementation plan.
|
||||
Reference in New Issue
Block a user