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>
Reports updated to print Specification (with revision via display_name):
- report_fp_sale.xml — header sections show "SPECIFICATION" instead
of "COATING CONFIG", reads doc.x_fc_customer_spec_id (added on
sale.order via quality inherit, computed from line.customer_spec_id)
- report_fp_wo_sticker.xml — propagates _spec alongside _coating
- fusion_plating_reports/report_fp_job_traveller.xml — header row
now shows Specification (falls back to coating)
- fusion_plating_jobs/report_fp_job_traveller.xml — same fall-back
- fusion_plating_jobs/report_fp_job_sticker.xml — _spec added
sale.order.x_fc_customer_spec_id added as a stored compute on
sale.order (in quality) so reports can render order-level spec.
Mirrors the line's first spec; updates on line edit.
Tablet payload (shopfloor_controller.py):
- spec_label added to the job payload dict
- defensive 'customer_spec_id' in job._fields check (shopfloor doesn't
depend on quality — circular if added)
Portal: deferred (same circular-dep issue, more substantial UI rewrite
needed; Phase E backlog item).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two issues on the Process Tree client action:
1. Back to Work Order kept growing breadcrumbs (WO -> Tree -> WO ->
Tree -> ...) because onBack used action.doAction() which PUSHES
a new act_window onto the stack instead of popping. Fixed by
trying action.restore() first (pops the Tree off the stack and
returns to the parent WO/Step controller). Falls through to
explicit doAction only when there's no parent in the stack
(direct URL access).
2. The empty-state banner referenced productionId, a dead variable
from the bridge_mrp era when the tree was tied to mrp.production.
Since the component now uses jobId (fp.job context key), the
"No manufacturing order selected" message ALWAYS fired regardless
of whether a job was loaded. Fixed by using jobId and updating
the message to "No work order selected".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two improvements to the Process Tree visualization opened from the
Work Order's Process Tree header button:
1. Back button returns to the Work Order (job form) instead of
Plant Overview. fp.job.action_open_process_tree now passes
back_job_id in the client-action context; process_tree.js
reads it via a new backJobId getter, updates the button label
to "Back to Work Order", and routes onBack to fp.job form.
The Plant Overview fallback stays for callers that don't pass
either back_step_id or back_job_id.
2. Completed operation/step cards now have a green fill (#1e8449)
and a subtle pulsing glow (box-shadow animation, 2.6s alternate)
so finished work pops against still-pending dark cards. Hover
pauses the animation so the click target is steady. Reuses the
same green the workflow-state slice already used.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two coherent feature drops shipping together because their fp_job_step
edits overlap. Both target operator workflow correctness.
## Sub 13 — Sequential step enforcement (recipe + per-step)
Background:
Investigation on WH/JOB/00339 showed operators starting Incoming
Inspection while Contract Review was still in_progress. Audit:
98.7% of recipe operations system-wide had requires_predecessor_done
= false (the legacy per-step opt-in defaults off, recipe authors
rarely tick the box).
Architecture:
Recipe-level toggle + per-step opt-out (Option A from /investigate).
* fusion.plating.process.node.enforce_sequential — Boolean on the
recipe root. Default True. When True, every operation under this
recipe waits for earlier-sequence steps to finish before it can
start.
* fusion.plating.process.node.parallel_start — Boolean on operation
nodes. When True, this step bypasses the sequential gate (e.g.
paperwork or QA review that runs alongside production).
* Mirrored on fp.step.template (parallel_start) so library steps
carry the flag into snapshots.
* fp.job.enforce_sequential — related from recipe_id. Snapshotted
at job creation so a recipe author flipping the recipe's flag
AFTER job generation does NOT change behaviour mid-run.
* fp.job.step.parallel_start — related from recipe_node_id.
* Decision matrix (encapsulated in
fp.job.step._fp_should_block_predecessors):
recipe.enforce_sequential | step.parallel_start | step.req_pred_done | block?
--------------------------|---------------------|--------------------|------
True | False | any | YES
True | True | any | no
False | any | True | YES
False | any | False | no
* Manager bypass via context fp_skip_predecessor_check=True (existing).
Runtime gates:
* fp.job.step.button_start — calls _fp_should_block_predecessors;
raises UserError naming the blocking earlier step(s).
* fp.job.step.can_start — computed Boolean for view-side disable.
* Move wizard predecessor check
(fusion_plating_shopfloor/controllers/move_controller.py) — uses
the same helper so tablet + backend behave identically.
UI surface:
* Recipe form (fp_process_node_views.xml) — enforce_sequential
toggle on recipe root, parallel_start checkbox on operations.
* Step template form — parallel_start checkbox.
* Simple Recipe Editor (inline library form) — Parallel Start
checkbox + legacy flag demoted with muted styling + supervisor
group gate.
* Recipe Tree Editor (properties panel) — both flags exposed,
only-show on the right node_type.
* Controllers updated to allowlist + payload the new fields.
Migration:
fusion_plating/migrations/19.0.18.12.0/post-migrate.py — sets
enforce_sequential = TRUE on every existing recipe-root node.
Idempotent. User confirmed dev-stage data, so retroactive flip
is safe (no production jobs to disrupt).
Tests:
TestSequentialEnforcement (10 tests) covering:
* sequential mode blocks out-of-order start
* first step always startable
* predecessor finish/skip unlocks next
* parallel_start opts out of gate
* free-flow mode bypasses gate
* legacy requires_predecessor_done still honoured in free-flow
* manager bypass via context
* can_start compute reflects state correctly
* library template parallel_start snapshots into recipe-node
## Sub 12e — Record Inputs Wizard v3 (card layout, dark-mode aware)
Background:
v2 wizard was a 17-column wide editable table. Operators got lost
finding which value column applied to their row's type, horizontal
scroll required on tablets, composite types crammed into one row.
New layout:
* Each measurement renders as a stacked card (CSS Grid + display
transformation on the existing list widget — preserves inline
editing, no JS rewrite).
* Card header: prompt name (large, bold) + type/unit pills.
* Card body: ONLY the value widget for this row's type
(number / boolean / date / text / photo / multi-point / panel).
* Composite types (multi-point thickness 5x reading + avg, bath
panel 4 fields) get inline sub-grid inside the card.
* Empty state ("no measurement prompts") with friendly CTA.
Dark mode:
* SCSS branches at compile time on $o-webclient-color-scheme
(per fusion-plating/CLAUDE.md note).
* Tokens: 7 surface colours + 4 ink levels with light/dark hex
pairs, all behind var(--fp-*) custom properties for per-deploy
override.
* Registered in BOTH web.assets_backend AND web.assets_web_dark
so each bundle compiles its own palette.
Tablet polish:
@media (max-width: 900px) — collapse meta below prompt + bump
numeric input min-height to 56px.
Defensive:
* v2 view kept in the XML file (instant rollback by changing one
view_id ref).
* `:has(.o_invisible_modifier)` rule drops empty cells out of the
grid so Odoo's invisible="..." doesn't punch holes in layout.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Gap 1 — Rack Travel Ticket PDF (Sub 12b's Save+Print 404):
+ report_fp_rack_travel.xml in fusion_plating_reports — A5 landscape
single page, big rack name, Code 128 of FP-RACK:<name>, tag chips,
contained part-batches table.
+ ir.actions.report bound to fusion.plating.rack so it appears in
the rack form's Print menu too.
+ Sub 12b's rack_parts_dialog.js Save+Print URL fixed to use the
standard /report/pdf/<xmlid>/<id> route.
Gap 2 — Per-customer cert statement:
+ res.company.x_fc_default_cert_statement (company-level fallback).
+ res.partner.x_fc_cert_statement (per-customer override).
+ Surfaced on the partner form under the existing Cert + Document
Routing block.
+ Chronological CoC body resolves: customer override → company
default → hardcoded AS9100/ISO 9001 boilerplate. Three-tier
fallback so existing certs without overrides keep working.
Gap 3 — Chronological CoC 'Actual' column:
+ Build a captured_values_by_input dict from the move's
transition_input_value_ids (Sub 12b captures these on every
Move Parts commit).
+ Render typed Actual: text → as-is, number → with target unit,
boolean → PASS/FAIL, date → formatted, attachment → '[Attachment]'
placeholder.
+ Falls back to prompts from the destination step's step_input list
when no values were captured (still useful as audit-of-what-was-
asked even if blank).
Version bumps:
fusion_plating → 19.0.10.3.0
fusion_plating_reports → 19.0.10.1.0
fusion_plating_certificates → 19.0.5.3.0
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Controller: extend /fp/shopfloor/plant_overview return payload to
include 'racks' array (filtered to loaded/in_use/awaiting_unrack
states). Each entry has tag chips, part count, current node
breadcrumb, current step + tank code, and a precomputed
next_step_id (next sequence in the job's recipe — operator
overrides at runtime in the Move Rack dialog).
JS: state.racks populated from payload. New openMoveRackDialog()
method spawns FpMoveRackDialog. Notification when rack has no
successor (last step of job).
XML: top section above the existing work-centre columns. Renders
rack rows with tags, part count, breadcrumb, and primary MOVE RACK
button per row. Visible only when state.racks.length > 0.
SCSS: minimal styling for the racks pane (extends move_dialogs.scss
to keep all Sub 12b styles in one file).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ShopfloorTablet component:
- Imports the 3 new OWL dialogs.
- useService('dialog') for spawning.
- Listens for 'fp-resolve-rack' window CustomEvent fired from inside
FpMovePartsDialog → spawns FpRackPartsDialog inline.
- New methods: openMovePartsDialog(from, to) + openStopTimerDialog(id).
Refresh tablet after commit/reconcile so the UI reflects new state.
Listener cleanup on unmount.
Note: the actual buttons that call these methods are added to the
existing tablet XML in a follow-up step — for now they are wired but
not surfaced. Operators get them after Task 16 + smoke test.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3-column grid layout for field rows (label / input / hint).
Compliance prompts + Blockers blocks have their own backgrounds.
Soft blockers amber + left border, hard blockers red + left border —
matches the spec's protocol.
Token pattern + dark-mode @if branch (CLAUDE.md rule: Odoo 19 doesn't
flip dark mode via runtime DOM class; we branch at SCSS compile time
on $o-webclient-color-scheme).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>