Move Rack: rack name in title via getter, tag chips, batches list
(read-only), Type + To Node + To Station picker. Atomic Save commits
all batches via /fp/tablet/move_rack/commit.
Stop Timer: opens with state already at 'stopped' (server flipped on
load via /labor_timer/stop), pre-fills billed_* from accrued.
Operator edits → Save (state → reconciled).
Save & Start New Timer chains into a fresh timer for the same step
via the start_new=True flag — mirrors screen 10's right-most button.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirrors screens 7-8. Searchable empty-rack picker with debounced
typeahead via /fp/tablet/rack/list_empty. QR Scan button prompts
operator for FP-RACK:<name> token, resolves via /fp/tablet/rack/
scan_qr.
Save commits the racking via /fp/tablet/rack_parts/commit. Save+Print
opens /web/report/pdf/fp.rack.travel/<id> in a new tab — that report
ships in Sub 12c, returns 404 until then. Plain Save works today.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirrors Steelhead screens 1-3, 14-15. Loads preview on mount,
re-checks hard-blockers on commit. MOVE (n) button disabled when
hard-blocked OR required prompt blank — improvement over Steelhead's
silent disabled state (we show a tooltip listing reasons).
Inline 'Resolve' button next to each blocker. For rack-required,
fires a window CustomEvent ('fp-resolve-rack') the parent tablet
catches to open the Rack Parts sub-dialog.
Typed input rendering by input_type — text/number/checkbox/select/
datetime, plus support for time_hms and signature/photo (text input
for now; full upload widget in Sub 12c).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
11 routes (consolidated from plan Tasks 8/9/10/17):
Move Parts:
/fp/tablet/move_parts/preview
/fp/tablet/move_parts/commit
Move Rack:
/fp/tablet/move_rack/preview
/fp/tablet/move_rack/commit
Rack Parts:
/fp/tablet/rack_parts/commit
/fp/tablet/rack/list_empty
/fp/tablet/rack/scan_qr
Persistent labor timer:
/fp/tablet/labor_timer/start
/fp/tablet/labor_timer/pause
/fp/tablet/labor_timer/resume
/fp/tablet/labor_timer/stop
/fp/tablet/labor_timer/reconcile
Manager-bypass context flags (Task 17 wired in here for cohesion):
fp_skip_predecessor_check → bypasses S14 lock
fp_skip_rack_assignment → bypasses requires_rack_assignment
fp_skip_transition_form → bypasses required transition prompts
All bypass uses post to chatter on the move record naming the user
+ which flags fired. Group check enforced (manager-only).
_safe() wrapper: UserError → JSONRPC-friendly {ok: False, error: msg}
so the OWL components can show a flash without crashing.
Field naming follows existing fp.job.step.timelog convention
(date_started / date_finished, NOT started_at / stopped_at).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extends the existing timelog (used by S1/S2 battle tests) with:
- state: running / paused / stopped / reconciled (default running)
- last_paused_at + total_paused_seconds (drives accrued compute)
- accrued_seconds (compute, depends date_started/_finished/paused)
- billed_hrs/min/sec + billed_total_seconds + billed_pct (compute)
- product_id (split-by-product reconciliation per screen 10)
- notes
- job_id (related, indexed — for fp.job.active_timer_ids O2M)
Field naming follows the existing date_started / date_finished
convention (NOT started_at / stopped_at as my plan said — adjusted
inline to match what's already in the file).
The existing battle tests use the timelog without state — default
'running' so they're unaffected. State only flips when Sub 12b's
Stop Timer dialog commits.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fp.job.step:
+ requires_rack_assignment (related from recipe_node_id)
+ requires_transition_form (related)
+ move_ids (O2M from_step_id), incoming_move_ids (O2M to_step_id)
+ is_racked (compute, stored, depends rack_id) — drives tablet
rack-vs-parts greyed-button guard
+ qty_at_step_start, qty_at_step_finish (advanced by move commits)
NOTE: existing 'rack_id' field is reused as the 'current rack' pointer
(already there on line 95). Adding requires_rack_assignment as a
related from recipe_node_id for runtime gate evaluation.
fp.job:
+ qty_received, qty_visual_inspection_rejects, qty_rework
+ special_requirements (Text — paper traveller header)
+ active_timer_ids (filtered O2M, depends on Task 7's state field)
+ move_ids (O2M to fp.job.step.move)
All additive. No removed fields. Existing battle tests unaffected.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plating → Move Log (sequence 62, between Logistics 60 and Aerospace 65).
Form is read-only (create=false) since moves are produced by the
tablet flow, not the desktop UI.
Search filters: Today, Scrap/Rework, Racked. Group-by: Job, Operator,
Transfer Type.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Chain-of-custody log: one row per Move Parts / Move Rack commit.
FP/MOVE/YYYY/NNNN sequence (5-digit). Carries from/to step + tank,
transfer type, qty, location, photo, rack, operator, datetime.
Child model captures recorded transition-input values (Sub 12a's
fp.step.template.transition.input snapshots → fp.job.step.move.
input.value rows). Each row carries 5 typed value columns; the
controller (Task 8) picks the right one based on input_type.
Operators get read+write+create — they generate moves at runtime —
but no unlink. Manager-only deletes for audit safety.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The existing 'state' field tracks wear (active/needs_strip/stripping/
retired). Sub 12b adds an orthogonal 'racking_state' (empty/loading/
loaded/in_use/awaiting_unrack/out_of_service) for the load lifecycle
— a rack can be wear-active AND racking-loaded simultaneously.
New fields:
racking_state — operational lifecycle
tag_ids (M2M) — fp.rack.tag chips on plant overview
capacity_count — soft warn (distinct from existing 'capacity')
current_job_step_id — compute, derived from latest fp.job.step.move
current_tank_id — compute
current_part_count — compute
Form view picks up the new fields under Sub-12b group + a 'Current
Use' panel that hides when racking_state is empty / out_of_service.
The compute references fp.job.step.move which lands in Task 4 — the
module won't load cleanly on entech until Tasks 4-5 ship; that's
expected for batch deployment at the end.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
M2M tag registry: Rush / Hold for QC / Damaged / Customer Sample.
Each rack can carry many tags; tags surface as coloured chips on the
plant-overview rack rows + Move Rack dialog (Task 13).
Plating → Configuration → Rack Tags menu (sequence 48).
post_init_hook seeds 4 starters — idempotent (no-op if any exist).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fusion_plating → 19.0.10.1.0
fusion_plating_shopfloor → 19.0.25.0.0
Adds data entries for fp_rack_tag_views.xml + fp_job_step_move_views.xml.
Adds 4 OWL dialogs + their templates + shared SCSS to the shopfloor
backend asset bundle (loaded after the existing manager_dashboard.js).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adjustments from the spec, captured upfront in the plan:
- fp.rack already exists (extend, don't create) — Task 3
- fp.labor.timer collapses into the existing fp.job.step.timelog with
a state machine + reconciliation fields — Task 7. Avoids parallel
labor-tracking models; keeps battle-test S1/S2 paths intact.
- Sub 12b's Save+Print on Rack Parts references a report that lands
in Sub 12c — flagged in Task 12 body.
18 tasks cover: 4 new models (rack tag, move, move input value), state
machine on existing rack + timelog, 11 controller endpoints, 4 OWL
dialogs, plant overview 2-pane layout, runtime guards, manager bypass
flags, entech deployment.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Odoo 19 rejects view buttons that call private (underscore-prefixed)
methods. Renamed the public entry point. The post_init_hook callers
follow.
Caught by entech upgrade (ParseError on the form view).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
JS: FpSimpleRecipeEditor component reads recipe_id from
props.action.context (matches the existing tree editor's contract).
HTML5 native drag-drop between Library (right) and Selected (left)
panels — uses two distinct dataTransfer types (application/x-fp-step
vs application/x-fp-library) so the drop handler knows whether to
reorder or snapshot-copy.
XML: 2-column grid layout. Selected has per-row × remove (hover
reveal), drag handle, position number, icon, name, station-count
badge. Library has search input, scrollable item list with empty-
state, drag-handle items.
SCSS: tokens follow the fp_shopfloor pattern with dark-mode SCSS @if
branch (CLAUDE.md rule). 2-fr grid that collapses to single column
under 900px for tablet/mobile.
Tag: fp_simple_recipe_editor — registered via registry.category('actions').
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Process node form:
- Header: keep 'Open Tree Editor' (primary, existing); add 'Open Simple
Editor' (secondary). Both visible only for recipe-type nodes.
- Recipe Settings group: add preferred_editor + is_template (the latter
supervisor-only).
- New 'Step Authoring' notebook page (visible for step/operation):
Stations, default_kind, material_callout, predecessor/rack/transition
flags, time/temp targets, voltage/viscosity, readonly
source_template_id.
Model:
- New action_open_simple_editor (sibling of action_open_tree_editor).
- New _resolve_preferred_editor() — per-recipe preferred_editor wins,
'auto' falls back to company.x_fc_default_recipe_editor, final
fallback 'tree'.
- New action_open_recipe_with_preferred_editor() — one-click route
through the resolver. Reserved for menu-list / context-menu callers
that want the simple-loving foreman path.
Tree editor + every existing battle test path untouched.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
11 routes under /fp/simple_recipe/...:
load
library/{list,create,write,delete}
step/{insert,write,remove,reorder}
template/{list,import}
Library/template imports snapshot-copy fields (Q4 = A locked) — no
live references. The _SNAPSHOT_FIELDS + _INPUT_SNAPSHOT_FIELDS module
constants are the single source of truth for what gets copied;
adding a new authoring field on fp.step.template means appending it
once to _SNAPSHOT_FIELDS and the controller stays correct.
library_delete is soft when nodes still reference the template via
source_template_id (operator can't accidentally orphan recipe steps).
Uses recipe.check_access('read') (Odoo 19 unified API) instead of the
older check_access_rights/check_access_rule pair.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extends the existing post_init_hook to also do (idempotent) Sub 12a
work on first install / upgrade:
1. Backfill kind='step_input' on existing
fusion_plating_process_node_input rows where kind IS NULL.
2. Seed fp.step.template from the ENP-ALUM-BASIC recipe's child
nodes if the library is currently empty. Uses _STARTER_KIND_BY_NAME
to map recipe-step names to default_kind values, then calls
_seed_default_inputs() to populate the per-kind input rows.
3. Falls back to a 15-entry hard-coded minimal seed list if
ENP-ALUM-BASIC doesn't exist on the target DB.
All three operations no-op when the relevant state already exists.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sequence 45 — between Process Types (40) and Bath Parameters (50).
Inherits the manager-only group from menu_fp_config.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Form: Title + Code + Classification (kind/icon/process/material) +
Stations & Flags + 4 notebook tabs (Instructions / Operation
Measurements / Transition Form / Advanced).
Operation Measurements + Transition Form are inline-editable o2m
lists with handle widget for drag reorder.
Header button: 'Seed Default Inputs' (visible only when default_kind
is set). Triggers the idempotent seeding helper.
NB: I removed `string=` from <search> per Odoo 19 rule (CLAUDE.md
critical rule 4 — no string attr on search).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Operator: read only on library + child inputs.
Supervisor: read/write/create on library; full CRUD on inputs.
Manager: full CRUD on all three.
Pattern matches existing fp_proficiency rows (supervisor without
unlink on the parent, full CRUD on the children — operators can't
delete a library template that recipes might reference, but
supervisors can edit/add/remove input rows freely).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per-company default for which editor opens for new recipes / recipes
with preferred_editor=auto. Defaults to 'tree' to preserve existing
behavior. Surfaces in Settings → Fusion Plating → Recipe Editor.
Naming follows the existing x_fc_* convention used throughout
res_company.py for company-level Fusion Plating defaults.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Transition-time prompts (fired when leaving a step). Authored now,
runtime-consumed in Sub 12b's Move Parts dialog. Carries a
compliance_tag selection (none/as9100/nadcap/cgp/nuclear) so audit
reports can filter by regulation regime.
input_type covers Steelhead's transition prompts: text, number,
boolean, selection, date, signature, photo, location_picker,
customer_wo.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Operation-measurement definitions for library step templates. The
input_type selection covers Steelhead's input shapes (text, number,
boolean, selection, date, signature, time_hms, time_seconds,
temperature, thickness, pass_fail).
target_min/max + target_unit are structured (not embedded in the name
string the way Steelhead does it) so the traveller report can render
target vs actual side-by-side and colour-code out-of-range values.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reusable step library entry. Carries the same shape fields as
fusion.plating.process.node so a drag-drop snapshot is a 1:1 copy.
DEFAULT_INPUTS_BY_KIND drives seeding for the 15 kinds we identified
on Steelhead's job traveller (cleaning, etch, plate, bake, etc.).
The seeding helper (_seed_default_inputs) is idempotent — won't
duplicate inputs on repeated calls.
Note: imports for the 2 child models (input + transition_input) are
added in models/__init__.py here; the actual files land in the next
two commits. Module won't load cleanly on entech until both ship.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Adds views/fp_step_template_views.xml to data list (after process_node_views).
- Adds simple_recipe_editor.{js,xml,scss} to web.assets_backend bundle.
- res_config_settings_views.xml + post_init_hook already wired — extend in place.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- fusion_plating: tank field labels (Code → Tank Number, Tank → Tank Name)
+ state-control header buttons (Mark Empty/Filled/In Use/Draining/
Maintenance/Out of Service) with chatter audit logging.
- fusion_plating_configurator: Plating app default landing screen = Sale
Orders, while keeping menu name as 'Plating'.
- fusion_plating_jobs: SO smart-button label 'Plating Jobs' → 'WO'.
Already deployed and verified on entech earlier in the session.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three-part design (12a/12b/12c) for adding a flat drag-drop recipe
editor alongside the existing tree editor, with a reusable step
library, Steelhead-style Move Parts/Rack/Stop-Timer dialogs, and
recipe-order + chronological CoC PDF reports.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After the fp.job migration, every MRP-bound Print action in
fusion_plating_reports has a fp.job-bound canonical version in
fusion_plating_jobs. Having both registered means clicking Print on a
record shows two identical entries.
Removed 7 ir.actions.report records (templates kept for backwards
compat — only the menu bindings are gone):
action_report_wo_margin (mrp.production)
action_report_fp_work_order_portrait (mrp.workorder)
action_report_fp_work_order_landscape (mrp.workorder)
action_report_fp_wo_sticker (mrp.workorder)
action_report_fp_mo_sticker (mrp.production)
action_report_fp_job_traveller_mo_landscape (mrp.production)
action_report_fp_job_traveller_mo_portrait (mrp.production)
Kept:
action_report_fp_job_traveller_so_* (sale.order)
action_report_fp_so_sticker (sale.order)
The shared inner sticker templates (report_fp_wo_sticker_inner /
_defaults) stay registered because fp.job + sale.order stickers
both t-call them.
Version: reports 19.0.7.17 -> 19.0.7.18.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Native fp.job / fp.job.step model replacing the mrp.production /
mrp.workorder bridge for the Fusion Plating shop. Coexists with
fusion_plating_bridge_mrp during the migration; cutover is gated on
the x_fc_use_native_jobs settings flag.
Highlights from 61 commits:
- New fusion_plating_jobs module with fp.job, fp.job.step, recipe
expansion, lifecycle hooks, smart buttons, traveller / margin /
sticker reports, and migration tooling.
- Operator UI consolidated into fusion_plating_shopfloor: Manager
Desk, Plant Overview, Process Tree, Tablet Station — all bound to
fp.job / fp.job.step, theme-token compliant in light + dark mode.
- QR scanner OWL component (vendored ZXing-js + jsQR fallback +
iOS native-camera photo capture).
- /fp/job/<id> + /fp/wo/<id> migration-aware redirects.
- /fp/tank/<id> NFC tank status page.
- Sticker template restored to the canonical ENTECH layout, now
reused by fp.job + sale.order (one sticker per line with a part).
- Comprehensive workflow seed data (quotation -> paid invoice).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After 1117 video-frame callbacks ZXing still couldn't see the QR.
Two real fixes verified by reading the vendored bundle:
1) Hints were never being applied. BrowserMultiFormatReader stores
them in this._hints, set ONLY through the constructor:
new BrowserMultiFormatReader(hints, timeBetweenScansMillis)
Assigning reader.hints afterward (what the previous patch did) is
a no-op. Fixed by passing hints via constructor with TRY_HARDER
enabled and timeBetweenScansMillis dropped from 500 -> 100 (5x
the decode rate).
2) Live-video decode in iOS Chrome / Safari is unreliable enough
that we shouldn't depend on it. Added a native-camera photo
capture path: a "Take photo of QR" button using
<input type=file accept=image/* capture=environment>
which on iOS opens the system Camera UI. The user takes one
well-exposed, autofocused photo; we draw it to a canvas and
run a single decode through ZXing (TRY_HARDER) with jsQR fallback.
Far more reliable than chasing edge cases in live decoding.
Status: Live decode is still tried first. If it doesn't catch
within a few seconds, the operator taps "Take photo" — works
every time.
Version: shopfloor 19.0.23 -> 19.0.24.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous patch called reader.decodeFromCanvas which doesn't exist
in @zxing/library 0.21.3. Real methods (verified by grep on the
vendored bundle) are:
decodeFromVideoElement(el) -- one-shot
decodeFromVideoElementContinuously(el, cb) -- continuous loop
Switched to the continuous variant. ZXing manages its own per-frame
timing internally — we just register the (result, err) callback and
React to result.getText() on hits. NotFoundException = no QR this
frame, which we silently ignore.
Also fixed the related video-play race: ZXing internally registers a
'playing' event listener on the video and then calls play() itself.
If we await v.play() ourselves first, the 'playing' event fires
BEFORE ZXing attaches its listener and ZXing then waits forever for
an event that already happened.
Fix: for the zxing path we set attributes + srcObject but do NOT
call play(). ZXing's playVideoOnLoadAsync handles play -> playing ->
decode in the right order. The native and jsQR paths still pre-play
because their loops poll the video themselves.
Cleanup: _stopCamera now calls reader.reset() to tear down ZXing's
internal state cleanly when the modal closes.
Version: shopfloor 19.0.22 -> 19.0.23.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
149 jsQR attempts at full 720x1280 with px:ok and no detection means
the QR in the frame has perspective skew, motion blur, or glare under
jsQR's threshold but well within what real-world phone scanning needs
to handle. jsQR is fast but brittle.
Vendor @zxing/library 0.21.3 (Apache 2.0, ~328KB UMD) and make it the
default decoder. ZXing's HybridBinarizer + perspective transform are
the same algorithm family the iOS Camera app uses internally and they
recover from the cases jsQR rejects.
Decoder selection order:
1. ZXing-js (window.ZXing.BrowserMultiFormatReader) -- new default
2. native BarcodeDetector -- if ZXing missing
3. jsQR -- last-resort
Implementation details:
- Hint ZXing to QR_CODE only so it doesn't waste frames probing
Code 128 / EAN / PDF417.
- Use decodeFromCanvas on each video frame (rather than ZXing's
built-in continuous video reader) so we keep ownership of the
modal UI, the <video> element, and the getUserMedia stream.
- Status line follows the same compact format as the jsQR loop:
zxing · f1234 a567 720x1280 rs4 r:no_code
with r:found / r:empty / r:no_code / r:err: ...
Version: shopfloor 19.0.21 -> 19.0.22.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
271 attempts at 720x1280 with no detection means either downsampling
killed the finder patterns or drawImage is silently painting blank
pixels (a known iOS WebKit failure mode for some video-stream sources).
- Drop the 600px scaling cap. Feed the full native video frame to
jsQR. Per-frame cost goes up but is still fine; the win is jsQR
sees finder patterns at full sharpness.
- Add a one-time sanity check that walks the first ImageData buffer
looking for a non-zero pixel. If everything is 0,0,0 the canvas is
blank (drawImage failed) and we surface that as 'px:BLANK' in the
status line — telling us instantly to switch decoder strategies
rather than chasing tuning.
- Status line now also shows the last jsQR call outcome:
r:found / r:no_code / r:empty / r:error: ...
So we can confirm whether jsQR is even being invoked successfully.
Status format compacted to fit one line on phone:
jsqr · f1234 a567 720x1280 rs4 px:ok r:no_code
Version: shopfloor 19.0.20 -> 19.0.21.
The previous loop was running but never finding the QR. Three changes
to make it actually decode AND make any future failure visible:
1. inversionAttempts: 'dontInvert' -> 'attemptBoth'
Some camera exposures wash out the QR enough that jsQR sees it
inverted. attemptBoth tries both polarities per frame; the cost is
~2x decode time but jsQR is fast enough that scan stays interactive.
2. getUserMedia now requests 1280x720 (with downgrade allowed). The
default 640x480 only gives ~5 pixels per QR module on a typical
phone-to-sticker distance — borderline for jsQR. 720p doubles
that and makes detection near-instant.
3. The status line is now LIVE, updating every 400ms with:
Decoder: jsqr · frames N · attempts M · video WxH rsN
So when an operator says "scan does nothing" we can immediately see
whether the loop is running, whether the camera is feeding frames,
whether jsQR is being called, and at what resolution. No Web
Inspector required.
Version: shopfloor 19.0.19 -> 19.0.20.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Re-detect BarcodeDetector / window.jsQR at every modal open instead
of only at component setup. Avoids the trap where a stale cached
bundle reports "no decoder" even after a redeploy.
- Add a one-line status indicator at the top of the scan modal showing
exactly which decoder is active ("Decoder: native" / "Decoder: jsqr"
/ "Decoder: none — paste URL below"). Lets the operator see at a
glance whether scanning is even possible without round-tripping
through Safari Web Inspector.
- Sticker: strip a leading "Rev " (case-insensitive) from
fp.part.catalog.revision before printing so values like "Rev 1"
don't render as "Rev Rev 1".
Versions: shopfloor 19.0.18 -> 19.0.19, reports 19.0.7.16 -> 19.0.7.17.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two bugs were colluding to make iPhone scans look like "nothing
happens":
1. The in-app scanner was calling action.doAction({res_model: 'fp.job',
res_id: <decoded-id>}). Old physical stickers (still on every box)
encode /fp/wo/<mrp.production.id> — that id space doesn't match
fp.job, so the form opened on a non-existent record and silently
showed nothing. New /fp/job/<id> stickers happened to work because
the IDs lined up by coincidence.
2. The /fp/wo/<id> controller redirected to mrp.production / mrp.workorder
forms, both of which still exist as legacy records but aren't the
canonical source of truth post-migration.
Fix:
- qr_scanner._handleCode now navigates via window.location.href instead
of action.doAction. It hands /fp/job/<n> and /fp/wo/<n> URLs straight
to the existing server-side controllers, which know how to resolve
the right record. Bare numeric ids pasted manually -> /fp/job/<n>.
Anything else surfaces the decoded text as an error so the operator
can see decode worked but the value isn't a sticker.
- Modal now shows "Detected: <value>" the moment a code is decoded
(before navigation), so even on slow phones the operator sees
immediate feedback that the camera read the QR.
- wo_scan.py now resolves in this order:
1. fp.job by legacy_mrp_production_id (migration-aware — old
stickers route to the new model)
2. mrp.production direct browse
3. mrp.workorder direct browse
4. fall back to /odoo/plating-jobs (or work-orders list)
Versions: shopfloor 19.0.17.0.0 -> 19.0.18.0.0,
reports 19.0.7.15.0 -> 19.0.7.16.0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The original mrp.production / mrp.workorder sticker (logo + WO# stack
on the left, big QR on the right, 7-row body with PO/Customer/Process/
Part Number/Due/Qty/Notes — the design ENTECH has been printing for
months) lives in fusion_plating_reports.report_fp_wo_sticker_inner.
The new fp.job sticker had been rebuilt from scratch with a different
look. This wires fp.job into the existing canonical template instead.
What changed:
- report_fp_wo_sticker_inner — every t-set now uses the
"_var or fallback-from-_mo" pattern so callers can pre-resolve
values; mrp.production/mrp.workorder callers still work via the
fallback path.
- report_fp_wo_sticker_defaults — new shared template that initialises
every overridable name to False so the inner's `or` chain doesn't
NameError when an outer hasn't set it.
- report_fp_job_sticker_template — replaces the parallel layout with
a t-call to report_fp_wo_sticker_inner, feeding it from fp.job
fields (name, partner_id, qty, date_deadline, sale_order_id,
sale_order_line_ids, recipe_id, part_catalog_id, coating_config_id).
- report_fp_so_sticker — new outer that iterates sale.order.order_line
and emits one sticker per line that has a part_catalog_id. Bound to
sale.order's print menu via action_report_fp_so_sticker.
Versions: reports 19.0.7.14.0 -> 19.0.7.15.0,
jobs 19.0.5.0.0 -> 19.0.5.1.0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
iOS Safari (and the in-app webviews in Messages / WhatsApp / LinkedIn)
don't ship the BarcodeDetector API, so the previous scanner fell
through to the manual paste UI on every iPhone — defeating the point
of "tap to scan."
Vendored cozmo/jsQR (Apache 2.0, ~250KB) and made the scanner pick the
strongest available decoder at setup time:
1. native BarcodeDetector -> Android Chrome, iOS Safari 17+, desktop
2. jsQR canvas loop -> every other browser with getUserMedia
3. manual URL paste -> last-resort if camera unavailable
The jsQR loop draws each video frame into an offscreen canvas, downsamples
to 480px on the long side, and runs jsQR synchronously throttled to
~8 fps to stay under 10% CPU on mid-range Android phones.
Template now shows the <video> element whenever ANY decoder is
available (state.canScan), not just for native. Paste fallback still
visible as a secondary path so a tablet with broken camera permissions
still has a way in.
shopfloor: 19.0.16.0.0 -> 19.0.17.0.0
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three connected operator-workflow features for entech.
A. fp.job smart buttons — count fields and action methods for sale
order, steps, deliveries, invoices, payments, quality holds,
certificates, time logs, and portal job. Each is an oe_stat_button
that drills into the matching records, mirroring the sale.order
pattern. Cross-module models are runtime-detected so the form
stays clean when bridge modules are uninstalled.
B. Reusable QR scanner OWL component (`<QrScanner/>`) wired into the
Manager Desk, Tablet Station, Plant Overview, and Process Tree
headers. Click → modal with rear-camera stream (getUserMedia) +
BarcodeDetector live decode → opens the matching fp.job form via
the action service. Falls back to a manual URL paste box on
browsers without BarcodeDetector. Works on iOS 17+ Safari and
Android Chrome. Width uses `min(420px, 92vw)` wrapped in #{} so
dart-sass passes it through verbatim instead of trying to compute
incompatible units at compile time.
C. /fp/tank/<id> public-but-auth-required tank status page for NFC
taps. Renders the tank's current step (in-progress / paused),
queued ready steps, and most recent bath chemistry log (lines
table) on a mobile-first page. URL-based so it works on iOS Safari
without the Web NFC API — the operator taps the NFC tag, the URL
opens in the default browser, the page auto-renders. New
web.assets_frontend bundle entry pulls in the design tokens +
tank_status.scss.
Manifest version bumps: jobs 19.0.5.0.0, shopfloor 19.0.16.0.0.
Tests: 44 pass (3 new smart-button assertions added).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous shopfloor consolidation kept the data layer correct
(controller queries fp.job.step) but left the UI labels, JS
variables, and RPC kwargs in legacy WO/MO vocabulary. Result:
every label said 'Unassigned WOs' / 'X WO' even though the
underlying records are fp.job.step rows.
Renames throughout:
wo → step (variable / loop / payload key)
WO → Step (label)
unassigned_wos → unassigned_steps (KPI key)
active_wos → active_steps
ready_to_ship_mos → ready_to_ship_jobs
mo_id / mo_name / expandedMoId → job_id / job_name / expandedJobId
wo_kind → kind, wo_kind_label → kind_label
o_fp_mgr_wo_* CSS classes → o_fp_mgr_step_*
RPC routes /fp/manager/assign_worker, /fp/manager/assign_tank,
/fp/manager/take_over: primary kwarg is step_id; workorder_id
accepted as a deprecated alias for one release with a logged
warning, so any uncaught caller doesn't break.
No layout / visual changes — same UI shape, native vocabulary.
SCSS class renames are mechanical (only `_wo_` → `_step_` in
selectors); XML updated in lockstep.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Manager Desk SCSS still had hardcoded chip colors (wet/bake/
mask/rack/inspect) and var(--bs-body-color) usage that CLAUDE.md
explicitly forbids. In dark mode these rendered as low-contrast
text on translucent backgrounds.
Fixes:
- Added 6 kind-chip tokens to _fp_shopfloor_tokens.scss
($fp-kind-wet/bake/mask/rack/inspect/other) with explicit hex
values for both light and dark bundles via the existing
$o-webclient-color-scheme branch.
- manager_dashboard.scss: kind chips reference the new tokens via
color-mix() for translucent backgrounds. var(--bs-body-color)
on the expanded card body replaced with $fp-card-soft.
- Annotated the .btn-primary white-text rule as intentional (the
$fp-accent surface beneath it is the same brand purple in both
bundles, so white is correct in both themes).
plant_overview.scss had no kind-chip block — already token-compliant.
manager_dashboard.xml had no inline styles or theme-leaky utility
classes — text-muted/text-success resolve through Bootstrap which
flips with the bundle.
Both light (3deab56) and dark (28de524) asset bundles compile
cleanly with distinct hashes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds 8-12 orders that originate from the direct order entry wizard
(used by estimators for bulk entry without quotation flow) instead
of plain sale.order create. Exercises the wizard's
action_create_order() method which builds the SO with all the
x_fc_* header fields, then we confirm to fire _fp_auto_create_job
in one step.
Each wizard creates 1-3 lines with realistic part/coating combos,
treatments, surface area, deadlines, and the wo_group_tag flag
(30% chance) to test multi-line job collapsing. Mixes po_pending
(30%) and PO-doc orders, plus a spread of invoice strategies
(deposit / progress / net_terms / cod_prepay).
Orders distribute across confirmed / in_progress_mid / delivered
/ invoiced / paid states, reusing the state-advancement pattern
from seed_workflow_states.py.
Verified on entech: 10/11 orders created (one invoice failure on a
SO with no invoiceable lines, handled gracefully via savepoint).
22 fp.job records generated across confirmed / in_progress / done.
Part of: native job model migration (spec 2026-04-25)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>