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>
Builds 7-8 orders in each of 13 workflow states to simulate a
full pipeline:
- Quotation (sale.order draft)
- Quote Sent (sale.order sent)
- Order Confirmed / Job Confirmed
- Job In Progress (Early + Mid)
- Job On Hold (with quality hold)
- Job Done / Delivery Draft
- Delivery Scheduled / En Route / Delivered
- Invoice Draft / Posted / Paid
Each record fills detailed fields: PO numbers, commitment dates,
operator assignments, timelogs, thickness readings on certs,
delivery contacts/vehicles/drivers, payment journals, etc.
Idempotency-ish: each order wrapped in a savepoint so one failure
doesn't crash the whole seed.
Workflow walkthrough findings encoded in script header docstring,
including the gotchas: (1) SO action_confirm creates fp.job in DRAFT
state with 0 steps — must call action_confirm + _generate_steps_from_recipe
explicitly; (2) invoice_payment_term_id is REQUIRED to post invoices;
(3) account.payment.action_validate moves payment to 'paid' but invoice
goes to 'in_payment' (not 'paid' — Odoo 19 design, requires bank
reconciliation for full 'paid' state).
Part of: native job model migration (spec 2026-04-25)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The recipe→steps generator was copying coating.thickness_uom blind
into fp.job.step.thickness_uom, but the two selections use
different value codes:
fp.coating.config.thickness_uom : 'mils' / 'microns' / 'inches'
fp.job.step.thickness_uom : 'mil' / 'um' / 'inch'
Result: any SO confirmed with a coating using the long-form codes
(real demo data uses 'mils') hit a 'Wrong value for ...' selection
error, the savepoint rolled back, and the fp.job ended up with 0
steps.
Add an explicit mapping. Unknown values fall through to the step
default ('um'). Demo seed re-run after the fix produces 234 steps
across 31 jobs (was 207); thickness_uom distribution: 228 um, 6 mil.
Part of: native job model migration (spec 2026-04-25)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The original cleanup script left behind 33 sale.order, 31 invoice
account.move, 13 payments, 1 picking, 17 quote requests. Extended
to nuke them too — SOs in any state, invoices regardless of
posted/draft (force-cancels via SQL when ORM blocks it), payments
forced to cancel before delete, stock.move + stock.move.line
force-deleted, quote requests unlinked.
Section ordering: payments -> invoices -> pickings/moves -> SOs (FK
direction). Reconciliation links cleared via direct SQL.
Sequences for sale.order and account.move.invoice reset to 1 so
fresh SOs start at S00001.
Re-seeded 31 fp.jobs across all 6 states after running the extended
cleanup.
Part of: native job model migration (spec 2026-04-25)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move 'All Jobs' and 'Steps' under fusion_plating_shopfloor's
existing 'Shop Floor' submenu (between Tablet Station and Bake
Windows). The standalone 'Jobs' parent menu is gone — it was
redundant once the operator UIs (Plant Overview / Tablet /
Manager Desk) consolidated under Shop Floor.
Final structure under Plating:
Sales / *
Configurator / *
Shop Floor
Manager Desk
Plant Overview
Tablet Station
All Jobs ← moved here (seq 15)
Steps ← moved here (seq 17, supervisor+)
Bake Windows
First-Piece Gates
Receiving & Inspection
Operations / *
Configuration → Work Centres (manager)
...rest
Work Centres stays under Configuration (no shopfloor equivalent).
New menu file lives in fusion_plating_jobs/views (jobs depends on
shopfloor; core can't reference shopfloor xmlids).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Removes the parallel OWL/controller stack I built in
fusion_plating_jobs (job_process_tree, job_plant_overview,
job_manager_dashboard, job_tablet, job_*.scss, plus parallel
controllers and action XML files). Refactors the existing
fusion_plating_shopfloor components in place to bind to
fp.job / fp.job.step instead of mrp.production / mrp.workorder.
End state:
- ONE operator UI module (shopfloor) instead of two parallel ones
- Existing token system (_fp_shopfloor_tokens.scss) reused as
designed - no duplicate jobs tokens
- Existing /fp/shopfloor/* RPC URLs preserved (no integration
breakage); workorder_id kwargs accepted as legacy aliases for
step_id / job_id so older tablet clients keep working
- Existing visual designs preserved - only the data layer
underneath changed
- Process Tree button on fp.job form now points at
fusion_plating_shopfloor's fp_process_tree client action
- Bake Windows / First-Piece Gates / Bake Oven / Operator Queue
models stay where they were
- legacy_menu_hide.xml trimmed: only the bridge_mrp Production
Priorities entry remains; the 3 shopfloor menus (Manager Desk,
Plant Overview, Tablet Station) are now visible (the canonical
native consoles)
Manifests:
- fusion_plating_jobs 19.0.3.1.0 -> 19.0.4.0.0 (consolidation bump,
no more bundled JS/SCSS, only job_scan controller retained)
- fusion_plating_shopfloor 19.0.14.4.0 -> 19.0.15.0.0 (asset bundle
cache-bust + significant controller refactor)
Tests pass on entech: 0 failed, 0 errors of 41 tests.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The (Native) suffix was a temporary distinguisher during parallel
coexistence with bridge_mrp. Now that fp.job is THE primary
system, the suffix is just noise. Removed across all 5 menus and
both client-action labels.
Also restructured the Jobs submenu under Plating:
- Renamed root from 'Plating Jobs (Native)' (seq=4, manager-only)
to just 'Jobs' (seq=2, operator-visible). Now appears right
below the Plating app header — first-click access for operators.
- All Jobs (was 'Jobs') at seq=20
- Tablet Station at seq=5 (operator entry point)
- Plant Overview at seq=10
- Manager Dashboard at seq=15 (supervisor+ only)
- Steps (renamed from 'Steps (Admin)') at seq=30 (supervisor+ only)
- Work Centres (was 'Work Centres (Native)') in Configuration
Hidden one more legacy menu: bridge_mrp's 'Production Priorities'
(mrp.workorder ordering UI — fp.job has its own priority field).
Manifest unchanged (no new files); skipping version bump.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two follow-up seed scripts after the main demo reset:
1. seed_work_centres.py: creates one fp.work.centre per existing
fusion.plating.work.center (matched by code), classifying kind
from name/code keywords. Then backfills work_centre_id on every
fp.job.step whose recipe_node has a legacy work_center_id with
a matching native code. Plant Overview kanban now has columns
instead of one big 'Unassigned' bucket.
2. seed_part_coatings.py: assigns existing fp.coating.config rows
(with recipes) to up to 20 bare fp.part.catalog rows
round-robin. Field on the part is x_fc_default_coating_config_id.
Future SO confirms via these parts will naturally generate full
recipe-linked steps via _generate_steps_from_recipe.
Both idempotent — re-running creates nothing new.
Run on entech: 9 native work centres created, all 234 existing
fp.job.step rows bound. Parts with coating: 2 -> 22 (28 -> 8 bare).
Part of: native job model migration (spec 2026-04-25)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cleanup_demo_data.py: deletes ALL fp.job, fp.job.step, timelogs,
mrp.production, mrp.workorder, and dependent records (deliveries,
certs, holds, portal jobs, racking inspections, uninvoiced SOs).
Resets the fp.job sequence. Preserves masters. Force-cancels MOs/SOs
via SQL UPDATE before unlink to bypass Odoo's _unlink_except_done
and _unlink_except_draft_or_cancel guards.
seed_demo_data.py: creates 31 fp.job rows distributed across all
6 states (draft=5, confirmed=6, in_progress=8, on_hold=3, done=6,
cancelled=3). In_progress jobs have mixed step states with real
timelogs to simulate a live shop floor. Falls back to direct
fp.job creation when a customer's parts have no coating/recipe,
ensuring customer variety even with sparse coating data.
Both scripts: idempotent (safe to re-run), commit at end, walk
dependents bottom-up to avoid FK violations. Used to reset entech
demo data after the migration trial.
Part of: native job model migration (spec 2026-04-25)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The 4 client-action SCSS files I shipped in Phase 6 ignored the
project's documented design system (CLAUDE.md "Card Styling" + "Dark
Mode" rules) and used hardcoded hex / var(--bs-*) for surfaces.
Result: dark mode rendered white-text-on-white-card.
Companion to "changes" (22573e7) which already landed
_fp_jobs_tokens.scss + the job_plant_overview.scss refactor.
This commit finishes the job:
- Refactored job_process_tree.scss, job_manager_dashboard.scss and
job_tablet.scss to reference the $fp-* tokens — zero hardcoded
hex on theme-sensitive surfaces. Three-layer contrast applied per
CLAUDE.md (page → container → card).
- Process tree keeps the intentional Steelhead-style dark-slate
card fill in BOTH bundles (deliberate visual choice, not a theme
bug); page / header / connectors / empty state are now token-
driven so they look right against light or dark page surfaces.
- Manifest assets list reordered so _fp_jobs_tokens.scss compiles
first in web.assets_backend (CLAUDE.md rule: SCSS variables in
earlier files are visible to later files in the same bundle).
This is what makes the compile-time
$o-webclient-color-scheme branch in the partial actually take
effect for the four consumer files.
Verified on entech: light bundle (web.assets_backend) and dark
bundle (web.assets_web_dark) both compile without SCSS errors and
emit distinct surface hexes (light: #f3f4f6 page / #ffffff card;
dark: #1a1d21 page / #22262d card).
Manifest 19.0.3.0.0 → 19.0.3.1.0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three batched changes that close out the original 10-phase
migration plan.
1. Phase 5 — Job Margin report bound to fp.job (replaces the
mrp.production-bound report_wo_margin). Per-step labour cost
table + margin summary using existing fp.job.step.cost_total
from Phase 1.
2. Polish:
- Real implementations for fp.job.step.button_pause,
button_skip, button_cancel (was NotImplementedError stubs).
button_pause closes the open timelog and sums duration_actual,
mirroring button_finish; button_skip/cancel transition state
with UserError guards.
- Explicit ondelete= policies on fp.job's cross-module Many2ones
(part_catalog/coating restrict, customer_spec/portal/delivery
set null) — was implicit set null.
- Standard Nexa Systems author/website/maintainer/support block
on fusion_plating_jobs manifest, suppressing the install
warning.
3. Legacy hide:
- New 'Plating Legacy Menus' group (group_fusion_plating_legacy_menus)
— nobody in it by default.
- Old shopfloor Manager Desk + Plant Overview + Tablet Station
menus restricted to that group, hiding them from operators
now that the native equivalents under 'Plating Jobs (Native)'
exist. (Note: ir.ui.menu uses group_ids in Odoo 19, not the
deprecated groups_id alias.)
Manifest 19.0.2.4.0 → 19.0.3.0.0. fusion_plating_shopfloor added
to depends so the legacy menu xmlid references resolve at install
time.
Part of: native job model migration (spec 2026-04-25)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Operator-facing touchscreen UI. Three modes:
- job_picker: list of active jobs as big touch cards
- job_detail: job header + steps list, click a step to view detail
- step_detail: big Start/Finish buttons depending on state
Backend: 4 JSON-RPC endpoints under /fp/jobs/tablet/* for jobs
list, job detail, start step, finish step. Calls through to
fp.job.step.button_start / button_finish so all the audit
preservation, timelog creation, duration_actual roll-up logic
from Phase 1 still applies.
Menu entry 'Tablet Station (Native)' at sequence 3 (top) of the
Plating Jobs (Native) submenu inside the existing Plating app.
Manifest 19.0.2.3.0 → 19.0.2.4.0.
Part of: native job model migration (spec 2026-04-25)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two operator-facing client actions for the native job model.
Plant Overview: kanban with columns = fp.work.centre, cards = active
fp.job.step rows (ready/in_progress/paused). Drag a card to a different
column to reassign the step's work_centre_id; click to open the step
form. Backend: /fp/jobs/plant_overview returns columns with cards;
/fp/jobs/plant_overview/move_card reassigns work_centre.
Manager Dashboard: list of in-flight fp.job rows with progress bars,
deadline (overdue highlight), current_step / current_location, and a
priority side-bar (rush=red, high=orange, normal=blue, low=grey). Click
a row to open the job form. State-count pills filter by state. Backend:
/fp/jobs/manager_dashboard returns rows + state counts.
Both menu entries land inside the existing 'Plating Jobs (Native)'
submenu under the Plating app (manager-only). The menu items are
defined in this module rather than in fusion_plating core, because
the action xmlids they reference aren't loaded yet at the time the
core menu file is parsed (fusion_plating_jobs depends on core, not
the other way round).
Manifest 19.0.2.2.0 → 19.0.2.3.0. Three new SCSS, three new JS,
three new XML files registered in web.assets_backend.
Verified on entech: module loaded clean, all 41 fusion_plating_jobs
tests pass, asset bundle regenerates without errors, both menus and
both client actions registered in ir_ui_menu / ir_act_client.
Part of: native job model migration (spec 2026-04-25)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ports fusion_plating_shopfloor's process_tree.js to bind to fp.job
instead of mrp.production. Consumes the /fp/jobs/process_tree
JSON endpoint built in Phase 6 lean.
Renders the recipe tree as cards. Each operation card shows the
step state (pending/ready/in_progress/done/etc.) when there's a
matching fp.job.step. Click an operation card -> open the step
form. Click Back -> return to the job form.
New 'Process Tree' button on the fp.job form (manager-only)
launches the client action with job_id context.
Manifest 19.0.2.1.0 -> 19.0.2.2.0.
Part of: native job model migration (spec 2026-04-25)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three changes to support live cutover on entech (2026-04-25):
1. Bridge_mrp gate: sale.order.action_confirm in
fusion_plating_bridge_mrp now skips _fp_auto_create_mo when the
x_fc_use_native_jobs config flag is True. Without this, every SO
confirm would create both an mrp.production AND an fp.job
(duplicate work). The gate is the only modification to bridge_mrp
during the migration — the rest stays untouched.
2. Menu nesting: Plating Jobs (Native) now lives INSIDE the existing
Plating app (parent=menu_fp_root) instead of as a separate
top-level app. Two parallel 'Plating' apps was confusing UX. Work
Centres (Native) goes under the existing Configuration sub-menu.
'(Native)' suffix is temporary — drops at full legacy removal.
3. Migration script robustness: per-MO savepoints (so one bad MO
doesn't abort the whole transaction with cascading 'transaction
aborted' errors) + extended partner resolver fallback chain
(warehouse partner → company partner) for orphan demo MOs without
SO link or x_fc_customer_id. Verified: 43 MOs + 297 WOs migrated
on entech with 0 errors after these fixes.
Part of: native job model migration (spec 2026-04-25)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comprehensive summary of work performed Apr 25 evening through
Apr 26 morning while user was asleep:
- Status of each phase (Phase 1+2 tested on entech, Phase 3-7
committed locally + pushed but untested due to mid-session
Tailscale SSH lockout)
- All commit hashes
- Architecture decisions made autonomously (parallel coexistence,
Phase 6 lean, qc_check_id deferred, migration context flag)
- Files created vs untouched per constraints
- Recommended morning checklist for resuming work
- Honest assessment of what's solid vs unverified
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Migration script now sets context fp_jobs_migration=True before
creating fp.job records. action_confirm and button_mark_done check
this flag and skip side-effects (portal job creation, QC check,
racking inspection, delivery, certificate, notification dispatch)
when migrating.
Without this, the migration would double-create portal jobs / QC
checks / racking inspections — once via bridge_mrp's original
create on the source MO, once via jobs module's lifecycle hook on
the new fp.job mirror. With the gate, the migration script
explicitly rebinds the existing dependents via x_fc_job_id.
Manifest 19.0.2.0.0 → 19.0.2.1.0.
Part of: native job model migration (spec 2026-04-25)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds legacy_mrp_production_id (Integer index) on fp.job and
legacy_mrp_workorder_id on fp.job.step. Used as the idempotency
key during cutover migration.
Three scripts under fusion_plating_jobs/scripts/:
- audit_pre_migration.py — counts and data-quality concerns BEFORE
- migrate_to_fp_jobs.py — copies MO->fp.job, WO->fp.job.step, time
logs, rebinds cross-refs (batches,
holds, certs, readings, portals,
inspections, deliveries). Idempotent.
- audit_post_migration.py — counts and verifies AFTER
Migration is run manually from \`odoo shell\` at cutover (not as
auto post-migration hook, for safety). README explains usage.
Tests verify the legacy id fields exist and the migration script
files are well-formed Python.
Manifest 19.0.1.9.0 -> 19.0.2.0.0.
Part of: native job model migration (spec 2026-04-25)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 6 originally scoped the full operator UI rewrite (Plant
Overview, Tablet, Manager Dashboard, Process Tree). Tailscale SSH
to entech is currently unavailable, so live in-browser
verification of OWL/JS components isn't possible. Shipping a lean
Phase 6 with the data-layer pieces:
1. /fp/job/<id> scan controller — when a user scans a fp.job
sticker, lands them on the fp.job form (or the process tree
action once that's wired). Mirrors fusion_plating_reports' /fp/wo/
pattern.
2. /fp/jobs/process_tree JSON endpoint — returns the recipe tree
serialized with each node tagged by its fp.job.step state,
ready for an OWL component to render. The component itself is
deferred (see README.md).
The bigger UI deferrals (kanban, tablet, manager dashboard) are
documented in README.md. They get their own focused project after
cutover — the data layer is complete, so they can land
incrementally without touching fp.job/fp.job.step.
Tests verify controller imports + serialization shape (no HTTP
because TransactionCase doesn't easily simulate request context).
Manifest 19.0.1.8.0 → 19.0.1.9.0.
Part of: native job model migration (spec 2026-04-25)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two parallel report definitions for the native job model:
1. Job Sticker (6x4 inch custom paperformat) bound to fp.job. Prints
WH/JOB/... ID, customer, SO, qty, due date, recipe, step
progress. QR encodes /fp/job/<id> for scan-to-job navigation.
2. Job Traveller bound to fp.job, A4 portrait. Job header + all
fp.job.step rows in sequence order with operator sign-off
column.
Coexists with fusion_plating_reports' MO/WO bindings — both print
menus stay live during migration.
Deferred reports (use existing during migration; rebind at cutover):
- BoL, Packing Slip, Invoice (read from SO, no fp.job change needed)
- WO Margin (cost rollup; rebuild against fp.job.step.cost_total
in phase-end polish)
Adds fusion_plating_reports to fusion_plating_jobs depends.
Tests deferred to post-Tailscale-restore: 3 new tests verify
report actions are registered + sticker template renders without
QWeb errors. Module file content verified locally as
well-formed XML.
Manifest 19.0.1.7.0 → 19.0.1.8.0.
Part of: native job model migration (spec 2026-04-25)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Adds 'job_confirmed' and 'job_complete' trigger events to
fp.notification.template (legacy 'mo_confirmed' / 'mo_complete'
stay for bridge_mrp).
- fp.job.action_confirm and button_mark_done now fire those
notifications best-effort via fp.notification.template._dispatch
(silent skip if templates absent or notifications module missing).
- Adds x_fc_source ['mrp', 'jobs'] tag to fusion.plating.kpi.value
so Phase 9 dashboards can filter or display both sources.
- Verified aerospace/nuclear/cgp/safety modules don't directly
reference mrp.production or mrp.workorder.
Configurator integration was already covered by Task 2.5's SO
confirm hook (reads x_fc_part_catalog_id and x_fc_coating_config_id
from sale.order.line).
Manifest 19.0.1.6.0 -> 19.0.1.7.0.
Part of: native job model migration (spec 2026-04-25)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds x_fc_job_id / x_fc_step_id Many2ones via _inherit on:
- fusion.plating.batch (workorder_id stays for legacy MRP-bound batches)
- fusion.plating.quality.hold
- fp.certificate
- fp.thickness.reading
- fusion.plating.delivery (parallel to existing job_ref Char)
- fp.racking.inspection (parallel to existing production_id)
fp.job.action_confirm now also calls a best-effort racking-inspection
auto-create. The current fp.racking.inspection still has a required
production_id, so the helper skips cleanly when this job has no MO link
(pure-native mode). Phase 9 cutover flips the required FK to fp.job.
Strategy: parallel coexistence — bridge_mrp's existing fields stay
populated; this adds NEW fields populated by the native flow. Phase 9
cutover stops populating the old fields.
Adds fusion_plating_batch + fusion_plating_receiving to jobs module
depends.
Note: spec referenced fp.batch / fp.quality.hold; the actual models
in this codebase are fusion.plating.batch / fusion.plating.quality.hold
— used the real model names.
Manifest 19.0.1.5.0 → 19.0.1.6.0. 29 jobs tests pass.
Part of: native job model migration (spec 2026-04-25)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Task 2.6: fp.job.action_confirm auto-creates fusion.plating.portal.job
with x_fc_job_id back-reference. Idempotent (skip if already linked).
- Task 2.7: fp.job.action_confirm checks customer.x_fc_requires_qc
and best-effort creates a fusion.plating.quality.check (the model
lives in bridge_mrp; runtime-detected to avoid dep cycle).
- Task 2.8: fp.job.button_mark_done sets state='done', date_finished,
auto-creates draft fusion.plating.delivery and best-effort triggers
fp.certificate generation.
- Task 2.9: account.move.action_post links invoice -> fp.job via SO
origin lookup, updates portal_job state to complete and stamps
invoice_ref.
5 new tests cover: portal job creation + idempotency, mark_done state
+ delivery, cancel-then-mark-done blocked.
Best-effort patterns (try/except + runtime model detection) used for
QC + cert because their target models are in dependent modules
that this module doesn't depend on by design.
qc_check_id field on fp.job still deferred — adding it here would
require depending on bridge_mrp.
Manifest 19.0.1.4.0 -> 19.0.1.5.0.
Part of: native job model migration (spec 2026-04-25)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Settings flag controls which SO confirm path runs. Default False
keeps the legacy bridge_mrp / mrp.production flow on entech.
Setting True diverts confirm into fp.job creation.
Both hooks coexist — bridge_mrp's _fp_auto_create_mo and the new
_fp_auto_create_job — but only one creates records per SO confirm
(controlled by the flag).
The new _fp_auto_create_job mirrors bridge_mrp's grouping logic
(x_fc_wo_group_tag), recipe resolution (coating → part), and
traceability fields (origin, sale_order_line_ids).
Settings UI shows the flag in a 'Fusion Plating Jobs' app section
of the standard Configuration menu.
3 new tests cover: flag off no-op, flag on creates job, idempotency.
Manifest 19.0.1.3.0 → 19.0.1.4.0.
Part of: native job model migration (spec 2026-04-25)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Native port of fusion_plating_bridge_mrp's
_generate_workorders_from_recipe method. Walks the recipe tree,
creates one fp.job.step per 'operation' node, formats child 'step'
nodes as step instructions on chatter, respects opt-in/out
overrides from fp.job.node.override.
Adaptations from the original:
- Creates fp.job.step (not mrp.workorder)
- Maps fusion.plating.work.center to fp.work.centre via forward
link (x_fc_fp_work_centre_id) or code fallback
- Uses native field names (job_id, work_centre_id, etc.)
- Drops work_role_id (not on fp.job.step yet — Task 2.6+)
- Drops _fp_autofill_default_equipment (not yet on step)
5 new tests cover: basic generation, idempotency, no-recipe skip,
opt-in override behaviour, recipe_node_id link.
Manifest 19.0.1.2.0 → 19.0.1.3.0.
Part of: native job model migration (spec 2026-04-25)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirror of fusion.plating.job.node.override from bridge_mrp, but
bound to fp.job. bridge_mrp's version stays alive for legacy MO
flow during the migration. Both coexist.
Adds override_ids One2many to fp.job via _inherit, plus
unique(job_id, node_id) constraint.
Note: spec-suggested _sql_constraints syntax is deprecated in
Odoo 19 ("Model attribute '_sql_constraints' is no longer
supported, please define model.Constraint on the model"). Used
the new class-attribute form: _unique_job_node = models.Constraint(...).
Verified the UNIQUE index is created on the table.
3 new tests: create, uniqueness, one2many backref.
Manifest 19.0.1.1.1 -> 19.0.1.2.0.
Part of: native job model migration (spec 2026-04-25)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Code review noted this was asymmetric — only part_catalog_id had
explicit index=True among the 5 new fields, and Phase 1 core fp.job
relies on Odoo's implicit FK btree for ALL Many2ones (no explicit
indexes). Removed for consistency.
Important I2 (no explicit ondelete= policies) is deferred to a
phase-end polish task that addresses both Phase 1 core and Phase 2
extension fields uniformly.
Manifest 19.0.1.1.0 → 19.0.1.1.1.
Part of: native job model migration (spec 2026-04-25)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 of the 6 deferred fields from Phase 1 Task 1.4 land here in
fusion_plating_jobs:
- part_catalog_id (fp.part.catalog from configurator)
- coating_config_id (fp.coating.config from configurator)
- customer_spec_id (fusion.plating.customer.spec from quality)
- portal_job_id (fusion.plating.portal.job from portal)
- delivery_id (fusion.plating.delivery from logistics)
qc_check_id deferred to Task 2.7 — its target model
(fusion.plating.quality.check) still lives in
fusion_plating_bridge_mrp and we don't depend on bridge_mrp from
this module. Task 2.7 will address QC sourcing.
6 unit tests (5 field-presence + 1 integration creating linked
records).
Manifest 19.0.1.0.0 → 19.0.1.1.0.
Part of: native job model migration (spec 2026-04-25)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Empty module that will host the native job bridge during Phase 2
of the migration. Coexists with fusion_plating_bridge_mrp during
the migration period — both can be installed simultaneously
without conflict.
Depends on:
- fusion_plating (fp.job, fp.job.step, fp.work.centre from Phase 1)
- fusion_plating_configurator (fp.part.catalog, fp.coating.config)
- fusion_plating_portal (fusion.plating.portal.job)
- fusion_plating_logistics (fusion.plating.delivery)
- fusion_plating_quality (fusion.plating.customer.spec)
- fusion_plating_certificates (fp.certificate)
These deps are why these fields couldn't live in fusion_plating
core (would invert the dep graph). All cross-module fields on
fp.job and fp.job.step land here via _inherit in subsequent tasks.
auto_install=False — opt-in only.
Part of: native job model migration (spec 2026-04-25)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 2 was previously outlined as 'rename bridge_mrp → jobs'.
That's destructive on entech. Revised strategy: build
fusion_plating_jobs IN PARALLEL with bridge_mrp. A settings flag
(x_fc_use_native_jobs) controls which path SO confirm takes.
Default False = legacy MO flow stays. Cutover (Phase 9) flips
the flag.
Phase 2 breakdown into 11 tasks (2.1–2.11), totaling ~5 days
engineering. All preserve bridge_mrp untouched until cutover.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- I1: Lock time_log_ids list inside step Audit tab to read-only
(no create/edit/delete on the nested list). Audit timelog rows
are produced exclusively by button_start / button_finish; if a
manager could hand-edit them, cost_total rollups would silently
drift.
- I2: Add explicit list view (decoration on state) and search view
(filters by state/kind, group_by state/work_centre/job) for
fp.job.step. The Steps (Admin) menu was using Odoo's default
auto-list with no filter, which would be unusable after a few
weeks of step accumulation. Action now references the search
view explicitly.
Manifest 19.0.8.7.0 -> 19.0.8.7.1.
Part of: native job model migration (spec 2026-04-25)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>