Two fixes from live testing of the 2026-05-24 redesign:
1. Job Workspace Back button routed to deprecated component.
onBack() hardcoded tag: 'fp_shopfloor_landing' so tapping a card on
the new plant kanban -> opening the workspace -> clicking Back
dropped the user into the OLD per-step kanban (the legacy OWL
component the data-record redirects don't reach because doAction
bypasses the data layer).
Fix: change the hardcoded tag to 'fp_plant_kanban'. Grep
confirmed it's the only such reference in JS.
2. Logo frame shape — wider, shorter, logo bigger.
140x140 square -> 280x110 rectangle. Better fit for horizontal
company logos (mark + name + tagline laid out left-to-right).
Uniform 18px padding on all sides so the image breathes evenly.
Image area is ~244x74 (vs old ~104x104), so a typical horizontal
logo renders ~50% wider. border-radius 28->22 for the flatter
rect; letter-mark placeholder font 52->44 to fit the shorter
frame.
Also augmented CLAUDE.md 'Legacy-action redirect' rule with a new
'grep JS for hardcoded doAction' clause — the XML-record redirect
trick only covers ir.actions.client data; OWL components with inline
this.action.doAction({tag: ...}) calls bypass the data layer entirely
and need a separate sweep.
Asset cache cleared (3 stale attachments).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User feedback after live testing: the 84px logo frame felt too small
and the image inside used only a fraction of the frame area.
Bumped the frame to 140px (1.67x) — image scales with the container
via the existing max-width/max-height: 100% rule. Proportional
adjustments to padding (14→18), border-radius (20→28), margin-bottom
(12→16), and the letter-mark placeholder font (32→52).
SCSS-only change. Asset cache cleared (3 stale attachments).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
LS-T2..T6 of the tablet lock-screen redesign (LS-T1 backend shipped
separately in c6137100).
Files:
- _tablet_lock_tokens.scss (new — design tokens, dark/light branches
via $o-webclient-color-scheme, registered
first in manifest per project rule 8)
- tablet_lock.scss (full rewrite — gradient bg, glassmorphic
tiles, 4 entrance keyframes, hover lift,
click press, clocked-in pulse,
prefers-reduced-motion gate)
- tablet_lock.xml (extended — logo + clock + prompt blocks
wrapping the existing tile loop; tile
inner shape updated for avatar gradient,
has_photo conditional, is_clocked_in
modifier)
- tablet_lock.js (extended — state.clockText / dateText /
company, setInterval(60s) clock tick,
_formatTime / _formatDate / tileStyle /
avatarClass helpers per project rule 20)
- __manifest__.py (19.0.31.0.0 -> 19.0.32.0.0, registered
new tokens SCSS BEFORE tablet_lock.scss)
Verified live on entech:
- Module upgrade clean, registry loaded in 15.5s
- 6 stale asset attachments cleared
- Helpers in tablet_controller.py emit company payload + initials +
gradients correctly (Garry Singh -> GS, EN Tech -> ET, uid=5 ->
pink gradient)
- res.company.logo present (has_logo: True)
- All animations gated by prefers-reduced-motion per spec §6
CLAUDE.md updated with new Critical Rule 22 about Odoo 19 HTML fields
auto-wrapping plain-string writes — caught during Task 1 testing when
the original 'tagline equality' test failed because res.company.report
_header is an HTML field that wraps writes with <p> tags.
Closes the 6-task plan in
docs/superpowers/plans/2026-05-24-tablet-lock-screen-redesign-plan.md
Spec: docs/superpowers/specs/2026-05-24-tablet-lock-screen-redesign-design.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
LS-T1 of the tablet lock-screen redesign.
Adds 3 module-level helpers in tablet_controller.py:
_initials_from(name) — first/last initials for letter-mark fallback
_avatar_gradient_for(uid) — deterministic per-user color (8 gradients)
_lock_company_payload(env) — company name + tagline + logo URL block
Endpoint /fp/tablet/tiles now returns:
{ok, company:{id,name,tagline,logo_url,has_logo,initials},
tiles:[{user_id, name, initials, avatar_url, has_photo,
avatar_gradient, is_clocked_in, has_pin}, ...]}
Tagline reuses res.company.report_header (the existing invoice-letterhead
field) — no new model field. Falls back to 'Shop Floor Terminal' when
empty.
10 tests pass (initials edge cases, gradient determinism, payload shape).
The 'tagline matches input string' assertion was intentionally NOT added
— see new CLAUDE.md Critical Rule 22 about Odoo 19 HTML field
auto-wrapping that makes such an equality test brittle.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User feedback after live testing: cards were too cramped on the 9-column
board. Restoring the Variant C mockup proportions and letting the board
scroll horizontally on smaller viewports (user explicitly accepted
side-scrolling).
Changes:
- .board grid: repeat(9, 1fr) → repeat(9, minmax(300px, 1fr))
plus overflow-x: auto. Each column ~300px so the card has room to
breathe. ~6 columns visible on 1920px desktop, ~4 on 1366px tablet,
smooth horizontal scroll for the rest.
- .col-scroll: gap 4→8, max-height eased so cards aren't packed.
- .o_fp_plant_card: padding 8/10→12, gap 4→6, base font 11→12.
- card-wo: 13→16 (matches mockup header size).
- card-step: 12→14.
- chips: padding 1/6→2/8, font 10→11, radius 10→12.
- mini-timeline blocks: 8→16px tall (current step 11→22px), labels
8→9px. Matches the mockup's punchy timeline strip.
- progress bar: max-width 60→100, height 3→4.
- operator pill / icon-row: bumped to match card scale.
No backend changes. SCSS-only. Asset cache cleared (3 attachments).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The plant-view rollout left two legacy ir.actions.client data records
still claiming tag='fp_shopfloor_landing':
- action_fp_plant_overview (Plant Overview)
- action_fp_shopfloor_tablet (Shop Floor — Tablet Station)
The landing-action resolver dispatched the new view correctly when the
user clicked the Plating root menu, but bookmarks / breadcrumbs /
QR-scan landings / direct URLs still routed through these legacy
actions and loaded the per-step kanban (OWL component is still
registered for back-compat).
Flipping their tag to fp_plant_kanban means every entry point now
opens the new view. The legacy fp_shopfloor_landing OWL component
stays registered (no code removed) but no XMLID points at it
anymore — safe to delete in a future cleanup.
Also documented this as a durable convention in CLAUDE.md under
'Legacy-action redirect (general rule for OWL component swaps)'.
Verified on entech:
- action 1129 (Shop Floor) tag: fp_shopfloor_landing → fp_plant_kanban
- action 1133 (Plant Overview) tag: fp_shopfloor_landing → fp_plant_kanban
- 3 stale asset bundles cleared
- Module re-upgraded clean, registry rebuilt in 15.7s
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PV-Phase5 of the plant-view redesign. Final phase — flips the default
of x_fc_shopfloor_layout from 'legacy' to 'v2' and updates CLAUDE.md
with the new architecture rule.
Verified on entech:
- HTTP 200 on /web/login
- Shopfloor module loads cleanly with all 19 new frontend files
- /fp/landing/plant_kanban returns the assembled payload with 9
columns + denormalized cards
- Card state distribution: 22 contract_review + 8 no_parts + 1 running
(sample data only — dev system)
- Asset bundle re-compiled (9 stale attachments cleared)
- ir.config_parameter['fusion_plating_shopfloor.layout'] = 'v2' set
To switch back to legacy: Settings → Fusion Plating → Shop Floor
Layout, or UPDATE ir_config_parameter SET value='legacy' WHERE
key='fusion_plating_shopfloor.layout'.
CLAUDE.md gets a new ~80-line section documenting:
- Why the redesign (per-step kanban produced duplicate cards)
- 9-column layout + step-kind → area mapping (spec D3, D4, D5)
- 13-state catalog + precedence dispatch in _compute_card_state
- Backend single-endpoint payload shape (/fp/landing/plant_kanban)
- Frontend OWL component tree + critical implementation gotchas
(rule 20 OWL scope, rule 8 SCSS @import, dark-mode compile-time)
- How to switch back to legacy
Closes the 20-task plan in
docs/superpowers/plans/2026-05-23-shopfloor-plant-view-plan.md
Spec: docs/superpowers/specs/2026-05-23-shopfloor-plant-view-design.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PV-Phase4 of the plant-view redesign. 19 new files implementing the
6-component OWL tree plus design tokens.
Components (each = JS + XML + SCSS triple):
- FpMiniTimeline — 9-step bar consuming mini_timeline_json
- FpPlantCard — Variant C card; 13 state-* CSS classes; tap
opens fp_job_workspace
- FpColumnHeader — column label + count badge + 'You're here'
badge when paired
- FpKpiTile — clickable KPI button with urgent/warn/good
variants and active state
- FpFilterChip — toggleable chip
- FpPlantKanban — top-level orchestrator: 10s polling, mode
toggle, search + 6 filter chips, board with
9 fixed columns, localStorage filter persistence
SCSS:
- _plant_tokens.scss (loads first, exposes $plant-* vars to every
later file — required because Odoo 19 forbids @import in custom
SCSS, manifest order IS the concat order)
- Dark mode via $o-webclient-color-scheme compile-time branch
Manifest registers all assets in dependency order: tokens → component
SCSS → component XML → leaf JS → top-level JS. Mirrors the existing
project pattern.
Critical patterns honored:
- Project rule 20 (no String/Number/parseInt in OWL templates):
all coercion in JS, string literals in foreach arrays.
- No t-out without markup() (none in this batch — all card text is
pre-formatted by the controller).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PV-Phase3 of the plant-view redesign.
- /fp/landing/plant_kanban JSONRPC endpoint returns {kpis, columns,
cards} in one payload. One card per fp.job; cards denormalized so
the OWL component doesn't fan out RPCs. Server-side filter handling
for All / Mine / Running / Blocked / Overdue / FAIR. Within-column
sort by (overdue, _SORT_PRIORITY[card_state], due_date).
- fusion_plating_shopfloor.action_fp_plant_kanban client action
registered alongside the existing fp_shopfloor_landing action.
- fp_landing_data.xml resolver extended to read the layout flag and
dispatch to v2 when x_fc_shopfloor_layout='v2' (default still legacy).
Card payload (23 fields): WO, customer, PN+rev, qty, PO, recipe, spec,
tags, current step + work centre, state chip, mini_timeline, operator,
icons (signoff / bake / tracking / etc.), progress.
State-chip mapping per spec §6.1 — one map keyed by card_state with
running-time elapsed, idle-hours, and operator-name interpolation.
Verified live — card payload sample on WO-30036 (contract_review state)
produces all expected keys + 9-element mini_timeline.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Consolidated commit of session work already deployed to entech and
verified via the deep audit + the persona walk:
S22 — Signoff gate (fp.job.step.requires_signoff was 100% unenforced,
42/42 done steps had NULL signoff_user_id). Three-piece fix:
_fp_autosign_if_required (captures finisher on button_finish),
_fp_check_signoff_complete (raises UserError if NULL after autosign),
action_signoff (explicit supervisor pre-sign). Bypass:
fp_skip_signoff_gate=True.
S23 — Transition-form gate (same dormant-field shape as S22, caught
preventively before recipe authors flipped requires_transition_form
on). Model helpers on fp.job.step.move + controller gate in
move_controller (parts commit) + pre-reject in rack commit.
F7 — Chatter standardization: _fp_create_qc_check_if_needed,
_fp_fire_notification, _fp_create_delivery silent failures now also
post to job chatter instead of only logging to file.
UI fixes:
- Critical Rule 20 documented + applied: OWL templates only expose
Math as a global. Calling String(d) inside t-on-click throws
'v2 is not a function'. Fixed pin_pad.xml (string array instead of
number array with String() coercion). Also swept parseInt/
parseFloat in recipe_tree_editor + simple_recipe_editor.
- Notes panel HTML escape fix: chatter messages off /fp/workspace/load
were rendered via t-out, escaping the HTML. Wrap with markup() in
job_workspace.js refresh() before assigning to state.
Versions:
fusion_plating 19.0.20.8.0 → 19.0.20.9.0
fusion_plating_jobs 19.0.10.20.0 → 19.0.10.23.0
fusion_plating_shopfloor 19.0.30.2.0 → 19.0.30.5.0
All deployed to entech (LXC 111) and verified live.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the Phase 6.3 fpRpc wrapper to the web.assets_backend bundle.
Placed before its consumers so the `import { fpRpc } from "./services/fp_rpc"`
calls in job_workspace, shopfloor_landing, manager_dashboard, and
hold_composer resolve.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
JobWorkspace, ShopfloorLanding, ManagerDashboard, and the embedded
FpHoldComposer now call fpRpc() for write-path endpoints (start/finish
step, hold create, sign-off, milestone advance, work-centre move,
assign-worker, assign-tank, manager takeover). fpRpc auto-injects
tablet_tech_id from the tech_store so the server can rebind env via
env_for_tablet_tech() and credit the right user.
Read-path RPCs (workspace/load, landing/kanban, manager/overview,
manager/funnel, manager/approval_inbox, manager/at_risk, shopfloor/scan)
stay as plain rpc() — no audit benefit, no need for the extra plumbing.
Also wires tablet_tech_id into /fp/shopfloor/plant_overview/move_card
which I missed in P6.3.3 — surfaced when grepping JS for write callers.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
10 endpoints in shopfloor_controller (log_chemistry, start_bake, end_bake,
start_wo, stop_wo, bump_qty_done, bump_qty_scrapped, log_thickness_reading,
quality_hold, mark_gate) and 3 in manager_controller (assign_worker,
assign_tank, take_over) now accept a `tablet_tech_id` kwarg. Each rebinds
env via env_for_tablet_tech() so writes carry the correct uid even when
the OS session belongs to the persistent tablet user.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
hold, sign_off, advance_milestone each accept tablet_tech_id and
rebind env via env_for_tablet_tech. Writes (Hold.create, button_finish,
action_advance_next_milestone) now carry the tech-of-record's uid.
load endpoint is read-only and untouched.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Client-side fpRpc() is a drop-in for rpc() that automatically injects
tablet_tech_id from the tech_store into every action call. Read-only
endpoints can keep using plain rpc().
Server-side env_for_tablet_tech(env, tablet_tech_id) returns an env
scoped via with_user() when the id is a valid active user; otherwise
returns the original env unchanged. Controllers call this at the top
of action methods so all subsequent writes carry the right uid.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Registers fp_tablet_pin_setup as an ir.actions.client tag. Triggered
from res.users preferences via action_open_tablet_pin_setup (added
to res_users.py in P6.1.1). Three-stage flow:
loading → check if user has existing PIN via search_count
old → enter current PIN (skipped if first-time)
new → choose new PIN
confirm → enter new PIN again
done → success toast + auto-close 1.5s later
Each stage reuses FpPinPad with a different onSubmit + title. On
mismatch / server error, resets to the first stage with a notification.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three OWL client actions all wrap their root in <FpTabletLock>:
ShopfloorLanding wraps o_fp_landing
JobWorkspace wraps o_fp_ws
ManagerDashboard wraps o_fp_manager
Each adds FpTabletLock to static components, imports tech_store, and
gains a handOff() method that calls techStore.lock(). The Hand-Off
button (yellow, lock icon) lands next to the scan/QR controls in each
header — pressing it instantly returns the tablet to the tile grid
without waiting for the idle timer.
Component composition (per spec §6.5):
FpTabletLock
if isLocked → tile grid + FpPinPad
else → existing client action (via <t t-slot="default"/>) + FpIdleWarning
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Top-level wrapper that renders lock screen (tile grid + PIN pad) when
no tech is signed in, and renders <t t-slot="default"/> otherwise.
Drives the auto-lock countdown via the activity_tracker service +
sends a /fp/tablet/ping heartbeat every 60s while a tech is signed in.
Tiles fetch from /fp/tablet/tiles using the localStorage station id
(set by ShopfloorLanding on QR pair / station picker selection).
State machine for the lock screen body:
loadingTiles → tiles list → tile tapped → PinPad → unlock RPC
↑
onPinCancel → back to tiles
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fixed-position yellow-border overlay + countdown toast shown during
the last N (default 30) seconds before auto-lock. Pure props-driven —
secondsRemaining is the only input; parent (FpTabletLock) decides
when to mount and unmount. Box-shadow pulse animation runs CSS-only
so OWL doesn't need to re-render every tick.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reusable 4-digit PIN pad. Auto-submits on the 4th digit via the
onSubmit prop. On wrong PIN, shake animation + dots clear + error
banner (caller controls the message via the returned {ok:false, error}).
Used by FpTabletLock (unlock flow) and FpPinSetup (set/change flow).
Dark-mode SCSS branch follows the same $o-webclient-color-scheme
pattern as the rest of the shopfloor components.
Also registers tech_store + activity_tracker services in the asset
bundle (assets/web.assets_backend) before the pin_pad files, since
the pin_pad/tablet_lock components consume them.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two registry-level services:
tech_store Shared reactive state holding currentTechId after a
successful PIN unlock. Other components subscribe via
useService("fp_shopfloor_tech_store") and read
currentTechId to inject into action RPCs. setTech(id, name)
on unlock; lock() on auto-lock / Hand-Off.
activity_tracker Document-level event tracker for pointerdown / touchstart
/ keydown / visibilitychange. Mouse-move alone deliberately
EXCLUDED — a tool resting on a tablet would otherwise keep
the session alive indefinitely. Public API:
bump(), getSecondsUntilLock(), getWarnThresholdSec()
Reads thresholds from ir.config_parameter at start +
every 5 min (so manager edits propagate within a shift).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two view inheritances on res.users:
(a) Preferences form — adds a 'Tablet PIN' group with a 'Set / Change
Tablet PIN' button that triggers action_open_tablet_pin_setup → the
fp_tablet_pin_setup OWL client action (Phase 6.2). Shows PIN Last
Set as read-only context.
(b) Standard res.users form — header button 'Reset Tablet PIN' visible
only to the fusion_plating manager group; hidden when no PIN is set
(via the set_date invisible field reference). Confirms before clearing.
Calls the clear_tablet_pin method from the model.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds two fields to fusion.plating.shopfloor.station:
- x_fc_authorised_user_ids (Many2many → res.users): restricts the
tablet lock-screen tile grid to a specific roster per station.
Empty = all operator-group users shown.
- x_fc_idle_lock_minutes (Integer, nullable): per-station override
for the auto-lock idle threshold; null = use system parameter.
Plus data/fp_tablet_config_data.xml registers four ir.config_parameter
defaults (noupdate=1 — manager can override via Settings → Technical
→ Parameters):
fp.shopfloor.tablet_idle_lock_minutes = 5
fp.shopfloor.tablet_pin_fail_threshold = 5
fp.shopfloor.tablet_pin_fail_lockout_minutes = 5
fp.shopfloor.tablet_warn_seconds_before_lock = 30
Form view surfaces both new fields in a dedicated 'Tablet PIN Gate'
group.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tiles returns the lock-screen grid: operator-group users, sorted
clocked-in-first then alphabetical, with avatar URL + has_pin flag.
Honours station.x_fc_authorised_user_ids when non-empty (Phase 6.1.6
adds that field). Ping is a lightweight ack used by FpTabletLock as
a heartbeat — logs current_tech_id at DEBUG for forensic visibility.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Verifies PIN, resets failure counter on success, increments + locks out
on 5 consecutive failures (configurable via ir.config_parameter
fp.shopfloor.tablet_pin_fail_threshold + tablet_pin_fail_lockout_minutes,
both defaulting to 5).
Returns informative payloads:
ok=true current_tech_id, current_tech_name
needs_setup=true user has no PIN yet
locked_until lockout in effect (rejects even correct PIN)
attempts_remaining failed but not yet locked
Logs INFO on success, WARNING on failure (with running counter +
locked flag).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
set_pin is self-service: requires old PIN if a hash exists, validates
4-digit format. reset_pin_for is manager-only (enforced server-side
via has_group); clears the hash + posts to chatter.
Both endpoints log INFO on success and WARNING on access-control denials.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PBKDF2-SHA256 + 16-byte salt + 200k iterations on res.users. Format
of the stored hash string is <salt_hex>$<digest_hex>. Field is
manager-readable only (groups=group_fusion_plating_manager); helpers
that need to read or write it use .sudo() internally so operator-level
callers can still set/verify their own PIN.
Adds set_tablet_pin / verify_tablet_pin / clear_tablet_pin model
methods + action_open_tablet_pin_setup that triggers the OWL setup
modal (Phase 6.2). Tests cover hash uniqueness, verify, clear with
chatter post, and the 4-digit format guard.
Tests verified on entech: -u fusion_plating_shopfloor --test-tags fp_tablet_pin
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Phase 4 endpoints (/fp/manager/funnel, approval_inbox, at_risk)
all use fields.Datetime.now() but the controller only imported http
+ request. Hitting the Workflow Funnel tab on Manager Desk threw:
NameError: name 'fields' is not defined
Funnel auto-loads on dashboard mount → infinite spinner + 'Funnel:
Odoo Server Error' notification. Same bug would have hit at_risk
and approval_inbox on first navigation.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plan tasks P4.5 through P4.10 batched. Existing 3-column Plant Board
becomes one tab of four; adds Workflow Funnel (default), Approval
Inbox, and At-Risk siblings. Adds 2 new KPI tiles for Pending Cert +
At-Risk.
WORKFLOW FUNNEL (default tab)
Calls /fp/manager/funnel. Renders one row per fp.job.workflow.state
with stage chip + count + top 5 WO cards. Tap a card → JobWorkspace.
Bar chart bar behind each row scales with stage count.
APPROVAL INBOX
Calls /fp/manager/approval_inbox. Three strips: Holds to Release,
Certs to Issue, Scrap to Review. Per-row open + Open Workspace
buttons. Tab badge shows total pending count.
PLANT BOARD (existing — relocated as one tab)
The 3-column Needs Worker / In Progress / Team layout that already
exists, wrapped in t-if="activeTab === 'plant_board'". No behaviour
change — still uses /fp/manager/overview with 8s refresh.
AT-RISK
Calls /fp/manager/at_risk. 3 sub-panels: Trending Late (sorted by
late_risk_ratio desc), Hold Reasons (read_group), Bottleneck heatmap
(bottleneck_score from P4.1 with red/yellow/green bars).
KPI STRIP (new conditional tiles)
Pending Cert — count from inbox.certs_to_issue, click to open Inbox tab.
At-Risk — count from at_risk.trending_late, click to open At-Risk.
Auto-refresh: 8s for /fp/manager/overview (existing); the active tab's
data also refreshes every 8s via refreshActiveTab().
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plan tasks P4.2 + P4.3 + P4.4 batched. Adds the backend data layer
for the Manager Desk's 3 new sibling tabs (Phase 4 tablet redesign).
POST /fp/manager/funnel
Workflow funnel: jobs grouped by fp.job.workflow.state. Returns
stages[] with count + top 5 WO cards per stage. Drives the
default tab on the refactored dashboard.
POST /fp/manager/approval_inbox
Four buckets: holds_to_release (state=on_hold|under_review),
certs_to_issue (all_steps_terminal + draft cert), scrap_to_review
(last 24h mark_for_scrap holds), override_requests (deferred —
empty placeholder).
POST /fp/manager/at_risk
Three panels: trending_late (top 20 by late_risk_ratio desc),
hold_reasons (read_group on hold_reason), bottleneck (top 10
work centres by bottleneck_score from P4.1).
All endpoints respect optional facility_id scope. Cheap implementations
— no caching yet; performance can be added if entech load demands.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plan task P3.6 — pragmatic deviation. The plan called for stubs that
internally route to /fp/landing/kanban + reshape; in practice the
legacy fp_shopfloor_tablet OWL component (still registered, just
unhooked from the menu) consumes a much richer payload (my_queue,
active_wo, baths, bake_windows, gates, holds, pending_qcs, stations)
than /fp/landing/kanban returns. Gutting tablet_overview to a stub
would break that legacy component.
Instead: add explicit DEPRECATED markers + INFO log lines on the three
endpoints (tablet_overview, plant_overview, queue). Bodies stay intact
so the legacy components keep working until Phase 5 cleanup retires
both endpoints AND the legacy OWL components together.
Note: /fp/shopfloor/plant_overview/move_card is NOT deprecated — the
new Landing component still uses it for drag-and-drop.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plan task P3.5. Single 'Workstation' menu item replaces both the
legacy 'Tablet Station' and 'Plant Overview' entries. The new
fp_shopfloor_landing component has a Station/All-Plant toggle so
one menu covers both old surfaces.
Old action records redirected for back-compat (so existing bookmarks
+ smart-button references keep working):
action_fp_shopfloor_tablet tag → fp_shopfloor_landing
action_fp_plant_overview tag → fp_shopfloor_landing
params → {'mode': 'all_plant'}
The legacy OWL components (fp_shopfloor_tablet, fp_plant_overview)
remain registered — no code removed, just no menu points at them.
Phase 5 cleanup will remove the OWL components after a release of
soak time on entech.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plan tasks P3.2 + P3.3 + P3.4 batched. Full ShopfloorLanding OWL
client action — replaces fp_shopfloor_tablet AND folds in
fp_plant_overview.
Header strip Title, station chip, station picker dropdown,
Station/All-Plant mode toggle, QR scan controls,
last-refresh indicator.
KPI strip 4 tech-relevant tiles: Ready · Running ·
Bakes Due (warning) · Holds (red when > 0).
Search Live debounced (200ms) across WO# + customer +
part. ESC clears.
Kanban board Columns = work centres from /fp/landing/kanban.
Cards = FpKanbanCard (Phase 1 — P1.7).
Drag-and-drop reuses existing
/fp/shopfloor/plant_overview/move_card.
Card tap doAction → fp_job_workspace with
{job_id, focus_step_id}.
QR scan FP-STATION pairs, FP-JOB / FP-STEP jump to the
Workspace.
Mode + station_id persist in localStorage (LS_STATION_ID, LS_MODE).
Auto-refresh every 15s; suppressed during a drop and for 5s after.
Registers client action `fp_shopfloor_landing`. Menu rewire + endpoint
stubs land in P3.5 + P3.6.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plan task P3.1. New JSON-RPC endpoint for the Shop Floor Landing
client action (Phase 3). Two modes:
station — paired WC + Unassigned + next 1-2 WCs in recipe flow
all_plant — every active WC, recipe-flow order (replaces the data
path for the standalone fp_plant_overview action)
Returns {columns: [{work_center_id, work_center_name, cards}], kpis:
{ready, running, bakes_due, holds}, stations: [...], facility_name,
server_time}. Card payload matches the KanbanCard OWL component
(P1.7) — same shape, no client-side adapter needed.
Light implementation — no urgency scoring or batch prefetch yet.
Both can be ported from plant_overview if performance demands.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plan task P2.6. Per the spec's "techs wear multiple hats" rule, lift
gates so technicians can do their work without permission walls:
fp.certificate operator: read → read+write
(flip draft→issued from tablet)
fp.thickness.reading operator: read → read+write+create
(capture Fischerscope readings from tablet)
fp.job.node.override operator: NEW read-only
(see opt-out badges on steps)
Supervisor-only operations (step Skip, hold Release, override
Re-include) remain enforced in workspace_controller, not ACL — so the
ACL stays minimal and the controller centralizes the gate logic.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plan tasks P1.8 through P1.11 batched into one commit (local tests not
run between them; entech is the verification env).
POST /fp/workspace/load — full payload for one fp.job
POST /fp/workspace/hold — quality.hold create with photo
POST /fp/workspace/sign_off — signature + finish step atomic
POST /fp/workspace/advance_milestone — fire next_milestone_action
Each endpoint logs INFO on success, EXCEPTION on failure, returns a
consistent {'ok': bool, 'error': str?} envelope. Hold endpoint isolates
photo-attach failures so they don't roll back the hold record.
Tests cover: payload shape, bad job_id, hold create with/without photo,
empty qty rejection, empty-signature rejection, sign-off finish, and
the no-milestone-action error path.
Verify on entech: -u fusion_plating_shopfloor --test-tags fp_shopfloor.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plan task P1.7. Final shared service — standard WO card used on Landing
kanban, Manager Plant Board, and Workflow Funnel. Embeds WorkflowChip,
shows progress bar, priority dot, blocker badge from step.blocker_kind.
Density prop ('compact' vs 'normal') swaps padding for funnel use.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plan task P1.6. Modal hold-creation form: reason picker, qty split,
optional photo (camera input on mobile), description, mark-for-scrap
toggle. Calls /fp/workspace/hold (added in P1.9). Reason list kept
client-side, keep in sync with fusion.plating.quality.hold.hold_reason.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plan task P1.5. Modal canvas signature capture using HTML pointer events
+ Odoo Dialog service. Returns image/png dataURI via onSubmit callback;
caller decides what to do with it (e.g. /fp/workspace/sign_off attaches
to fp.job.step).
Canvas stays light even in dark mode for signature legibility.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plan task P1.4. "Can't start yet — Waiting on Step N: X" block reused
across JobWorkspace step rows and Manager Plant Board cards. Icon set
maps to blocker_kind (predecessor/contract_review/parts_not_received/
racking_required/manager_input). Optional Jump button propagates to
parent via onJump callback.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plan task P1.3. Bootstraps the tests/ dir and adds the first of 5
shared OWL services. Pill renders fp.job.workflow.state with color
mapping + optional next-action hint.
Per CLAUDE.md "Dark Mode" rule: registered once in web.assets_backend;
Odoo 19 auto-compiles into both bright and dark bundles via the
\$o-webclient-color-scheme SCSS branch.
Version bumped to 19.0.27.0.0 (Phase 1 — Workspace foundation).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Audit of all 86 data XML files in the fusion_plating module set
turned up 3 more files that lacked noupdate=1 protection — every
module upgrade would re-import them and silently overwrite user
customisations. Following the ENP-ALUM-BASIC recovery (a68bf2e),
locked these too:
1. fusion_tasks/data/ir_cron_data.xml — 4 ir.cron records
(technician travel times, push notifications, late-arrival
checks, location cleanup). Users may disable / re-schedule.
2. fusion_plating_shopfloor/data/fp_cron_data.xml — 1 ir.cron
(Bake Window state updater). Same reasoning.
3. fusion_plating_bridge_maintenance/data/fp_maintenance_stage_data.xml
— 3 maintenance.stage records (kanban columns: New / Active /
Completed). Admin may rename, reorder, or add new stages.
Companion entech-side action (executed via SQL during the fix
session): 11 ir.model.data rows for these records were updated to
noupdate=true so the next module upgrade respects the new flag.
Files left explicitly noupdate=0 — verified safe:
- fusion_plating/data/fp_landing_data.xml — 1 ir.actions.server
(system action, code-defined; re-import is harmless)
- fusion_plating_reports/data/fp_hide_default_reports.xml —
re-asserts deletion of default Odoo report bindings; intentional
to re-run on every upgrade
Final audit confirmed 0 user-editable noupdate=false records remain.
ir.model.inherit + report.paperformat rows still noupdate=false but
those are system metadata (Odoo manages) and Odoo's standard
paperformat pattern, both safe.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>