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>
Sequel to the 2026-05-22 tablet redesign (Phases 1-5). Adds a tile-grid
lock screen + 4-digit PIN per tech + 5-min auto-lock + audit propagation
so multiple techs sharing one tablet get correctly-attributed actions.
Key design choices:
- 4-digit PIN (industry norm), PBKDF2-SHA256 with 200k iterations
- Per-user lockout after 5 failures (not per-tablet)
- Single Odoo session + tablet_tech_id kwarg for audit (no JS reload on
every tech switch)
- Manager-side reset only (no SMS/email infra)
- Server-side step timer keeps running on lock (auto-pause cron is
the upper-bound safety net)
Three sub-phases (6.1 backend / 6.2 frontend lock / 6.3 audit kwarg
propagation), each independently deployable.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 3 removed the menu_fp_shopfloor_plant_overview menuitem from
fp_menu.xml, but Odoo doesn't auto-delete orphan records when XML
disappears — the menu stayed in the database. Combined with P3.5's
action retarget (action_fp_plant_overview tag → fp_shopfloor_landing),
clicking it landed on the same Landing component as Workstation —
hence the duplicate menu items both opening the same screen.
Adds <delete model='ir.ui.menu' id='...'> in legacy_menu_hide.xml so
future -u runs scrub the orphan. Drops the now-defunct group_ids
block for the deleted menu. The action record stays (bookmark
back-compat).
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>
Same regression as the previous commit — b0070afc removed all 4 quick_look
related fields, my first fix only caught 2 of them. Restoring the remaining
2 so the quick-look view fully validates.
Commit b0070afc removed these two related fields from fp.job.step but
the view fp_job_step_quick_look_views.xml still references them. The
mismatch was dormant because entech never ran -u between b0070afc
and the 2026-05-22 deploy. Re-running -u during the Phase 1-4 deploy
caught it:
Field "quick_look_part_catalog_id" does not exist in model
"fp.job.step"
Restoring both as related fields (zero-cost, fixes the view without
touching XML).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Caught during entech deploy of the Phase 2 auto-pause cron. Odoo 19
ir.cron no longer accepts numbercall or doall fields; the load fails
with:
ValueError: Invalid field 'numbercall' in 'ir.cron'
Removed both from ir_cron_autopause_stale_steps. The other crons in
the same file (nudge stale paused / in_progress) already used the
minimal field set — matching that pattern now.
Also added a CLAUDE.md section so future-Claude doesn't reintroduce
the speculative fields.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the stale "Plant Overview Dashboard" section with a current
"Shop Floor Architecture" section covering the Phase 1-4 deliverables:
- 3 OWL client actions (Landing / JobWorkspace / Manager Dashboard)
- 5 shared OWL services
- Backend endpoints (workspace / landing / manager)
- Auto-pause cron config knob (ir.config_parameter name)
- Key new model fields with their purpose
- Operator ACL lift summary
- Deprecated-but-still-live legacy surfaces (Phase 5 cleanup pending)
- Old patterns to avoid
Links to the spec + plan docs as the authoritative reference.
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>
Computes for the Manager At-Risk heatmap (Phase 4 tablet redesign).
Non-stored — recomputed on /fp/manager/at_risk read; that endpoint
caches its full payload for 60s so the cost is bounded.
bottleneck_score = active_step_count * avg_wait_minutes
avg_wait_minutes = rolling-7-day avg of (date_started - create_date)
Work centres with high score show red in the heatmap — combination
of queue length AND average wait time.
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 P2.4 + P2.5 batched.
Adds _cron_autopause_stale_steps method on fp.job.step + 30-min cron
registration. Flips in_progress steps idle > threshold to paused with
a chatter audit ("Auto-paused after Nh idle. Resume from the tablet
when work continues.").
Threshold from ir.config_parameter:
fp.shopfloor.autopause_threshold_hours (default 8.0)
Recipe nodes opt out via fusion.plating.process.node.long_running
(added in P2.1) — useful for 24h bakes and multi-shift soaks.
Fixes the 411-hour ghost timer that motivated the redesign. Doesn't
replace the existing nudge crons — those still notify the supervisor;
this one actually pauses the timer.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plan tasks P2.2 + P2.3 batched (both small additive computes on fp.job;
local tests not run between them — entech verifies).
late_risk_ratio — stored Float, remaining_planned / minutes_to_deadline.
Drives the Manager At-Risk view (Phase 4).
Recomputes on step state, duration, deadline changes.
active_step_id — non-stored Many2one. Currently in_progress step
(lowest sequence if multiple — defensive).
Drives JobWorkspace landing focus.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plan task P2.1. Boolean on fusion.plating.process.node that exempts
steps generated from this node from the shop-floor auto-pause cron
(added in P2.4/P2.5). Use for 24h bakes, multi-shift soaks, and
similar long-but-legitimate operations.
Toggle visible on the process-node form for operation/step types,
grouped with parallel_start in the Behaviour section.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plan task P1.16. Header button on the fp.job form that opens the
JobWorkspace OWL client action focused on the current WO. Primary
entry point for techs before the Landing kanban (Phase 3) ships;
remains as a back-office shortcut after.
Hidden when state == 'draft' (no steps to work yet).
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>
Plan task P1.2. Reuses _fp_should_block_predecessors so the new compute
stays in sync with the existing can_start logic. Drives the OWL GateViz
component on the tablet — "Can't start yet — Waiting on Step N: X".
Future work: extend with explicit branches for contract_review /
parts_not_received / racking_required / manager_input as those gate
models mature.
Tests not run locally (no fusion_plating mount in odoo-modsdev).
Verify on entech: -u fusion_plating_jobs --test-tags fp_jobs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plan task P1.1. Formats fp.job.name as "WO # <last-segment>" for
tablet/dashboard surfaces. Underlying name field is unchanged so
back-office forms, reports, and emails keep WH/JOB/00001.
Tests not run locally — fusion_plating not mounted in odoo-modsdev
container. Verify on entech: -u fusion_plating_jobs --test-tags fp_jobs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previous attempt (e5928b96) used CSS to force the binary widget's
"Upload your file" button visible in display mode. Problem: it
rendered a non-clickable stub in every row, then DUPLICATED when
the operator clicked into edit mode (two upload links stacked).
Drop the SCSS hack entirely. Replace with a custom form-view
controller that auto-edits the first incomplete row on mount.
When the wizard opens, the JS:
1. Scopes itself via the form's o_fp_cert_issue_wizard_form class
(no-ops on every other form view in the system).
2. Finds rows where the is_ready toggle is False.
3. Clicks the fischer_file cell of the first such row.
4. The row enters edit mode → Odoo's native binary widget renders
its upload button → operator drops the file → onchange fires
→ readings parse.
Wired via js_class="fp_cert_issue_wizard_form" on the form root.
Banner copy updated to "Click a row, then click Upload your file in
the Fischerscope column" so even if the auto-edit fails for some
DOM reason, the operator knows the click path.
Module: fusion_plating_jobs 19.0.10.16.1 → 19.0.10.16.2.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reported 2026-05-20: the Fischerscope file column shows "↑ Upload
your file" only when the operator clicks the cell. Until then, the
cell looks empty and operators don't know they can upload there.
Root cause: Odoo's default `widget="binary"` only renders the
upload button in EDIT mode. In editable lists, non-selected rows
stay in display mode, which hides the button. Stock theme CSS
hides .o_select_file_button on inactive rows.
Fix: scoped SCSS that overrides the default theme rule for the
Issue Certs wizard ONLY. `.o_select_file_button` becomes
`display: inline-flex !important` so it shows on every row from
the moment the wizard opens. Added a fa-upload icon glyph + dotted
underline so the button reads as clickable-action, not text.
Scoped to `.o_field_one2many[name="line_ids"]` inside the form view
so binary fields elsewhere in the system are unaffected. Registered
in both web.assets_backend and web.assets_web_dark per CLAUDE.md
two-bundle rule.
Module: fusion_plating_jobs 19.0.10.16.0 → 19.0.10.16.1.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reported 2026-05-20: clicking "Issue Cert" on a job opened the
wizard with a banner saying "Fischerscope file or readings needed
— fill it in below before confirming", but the list view only
showed status toggles (Needs Thickness / Is Ready). No upload
affordance was visible. Operators had to know they could click a
list row to expand into a hidden detail form where the upload
field lived.
The wizard model already had the file field, the .docx parser
(_fp_parse_fischerscope_docx), and the @onchange that prefills
readings — only the view was hiding it.
Fix: promote the file upload into the list as its own editable
binary column, alongside the existing Needs Thickness toggle.
Operator now sees:
Reference │ Type │ Customer │ Needs Thickness │
Fischerscope File (PDF or .docx) │ Parsed │ Ready
Drop the file → onchange fires → readings + parsed summary
populate in-row. Click "Confirm & Issue" to commit.
The per-line expanded form is preserved (still accessible via
row click) as a "details" panel for editing individual readings
after upload — but the primary upload action is now in the list
row where the operator's eyes are.
Module: fusion_plating_jobs 19.0.10.15.0 → 19.0.10.16.0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reported 2026-05-20: the receiving state machine had four states
(draft → counted → staged → closed) where the middle pair was pure
ceremony. Real-usage data on entech:
state distribution: 14 draft, 4 closed (zero `staged` records)
median dwell counted → staged: 11 seconds
median dwell staged → closed: 4 minutes
`staged` captured no fields, fired no gates, mapped to the same SO
`x_fc_receiving_status='partial'` as `counted`. Pure click-through.
Cleanup:
- State Selection retains `staged` as `Staged (legacy)` so historical
records remain readable; new transitions never write it.
- statusbar_visible drops it from the chevron header.
- action_mark_staged becomes a thin shim that advances counted →
closed directly (any old button binding still works).
- action_close now accepts `counted` as a valid source state (was
previously only `staged` / legacy `accepted` / `resolved`).
- View: "Stage for Racking" button removed. "Close" button renamed
to "Close — Racking Confirmed" so the racking-crew confirmation
meaning stays obvious.
- _update_so_receiving_status mapping unchanged for legacy `staged`
(still maps to partial) — only the comment block updated to
describe the new canonical flow.
Migration 19.0.3.20.0 advances any `staged` records to `closed`
and syncs the linked SO's x_fc_receiving_status to `received` so
downstream gates (job step start, mark_done qty check, cert
creation) don't see a stale "partial" status.
Module: fusion_plating_receiving 19.0.3.19.0 → 19.0.3.20.0.
Tests: TestQtyReceivedPropagation updated — 5 tests dropped the
action_mark_staged() call, walk draft → counted → closed directly.
All 11 tests green (carrier 6 + propagation 5).
Verified on entech: existing 14 draft + 4 closed records untouched.
Direct draft → counted → closed transition works end-to-end on
RCV-30041 (was the test target).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>