Consolidated commit of session work already deployed to entech and
verified via the deep audit + the persona walk:
S22 — Signoff gate (fp.job.step.requires_signoff was 100% unenforced,
42/42 done steps had NULL signoff_user_id). Three-piece fix:
_fp_autosign_if_required (captures finisher on button_finish),
_fp_check_signoff_complete (raises UserError if NULL after autosign),
action_signoff (explicit supervisor pre-sign). Bypass:
fp_skip_signoff_gate=True.
S23 — Transition-form gate (same dormant-field shape as S22, caught
preventively before recipe authors flipped requires_transition_form
on). Model helpers on fp.job.step.move + controller gate in
move_controller (parts commit) + pre-reject in rack commit.
F7 — Chatter standardization: _fp_create_qc_check_if_needed,
_fp_fire_notification, _fp_create_delivery silent failures now also
post to job chatter instead of only logging to file.
UI fixes:
- Critical Rule 20 documented + applied: OWL templates only expose
Math as a global. Calling String(d) inside t-on-click throws
'v2 is not a function'. Fixed pin_pad.xml (string array instead of
number array with String() coercion). Also swept parseInt/
parseFloat in recipe_tree_editor + simple_recipe_editor.
- Notes panel HTML escape fix: chatter messages off /fp/workspace/load
were rendered via t-out, escaping the HTML. Wrap with markup() in
job_workspace.js refresh() before assigning to state.
Versions:
fusion_plating 19.0.20.8.0 → 19.0.20.9.0
fusion_plating_jobs 19.0.10.20.0 → 19.0.10.23.0
fusion_plating_shopfloor 19.0.30.2.0 → 19.0.30.5.0
All deployed to entech (LXC 111) and verified live.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
Per-model counter fields on sale.order renamed to x_fc_pn_*_count
to avoid collision with pre-existing compute fields of the same
short name in bridge_mrp / receiving / configurator (silent
compute-override was suppressing the storage). 4 child models
(fp.certificate, fp.receiving, fusion.plating.delivery,
fusion.plating.pickup.request) now derive names as PFX-<parent>
with -NN suffix from the 2nd onward.
fusion.plating.pickup.request gains a sale_order_id field
(optional) so pickups created against an SO get parent-derived
names, while standalone pickups (pre-SO) fall back to PU/YYYY/NNNN.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User wanted Finish & Next to drop its text label like the other row
buttons, but stand out visually as the primary action. Solution:
icon-only with a vivid green color and a subtle pulse animation.
- New SCSS: fp_finish_btn.scss with branch-on-$o-webclient-color-
scheme so the dark bundle uses green-400 (pops on dark bg) and
light bundle uses green-600. Pulse animation 1.8s ease-in-out
infinite, scale 1.0 ↔ 1.18. Pauses on hover/focus so the click
target is steady.
- Registered in both web.assets_backend and web.assets_web_dark
per the project's dark-mode rule (CLAUDE.md).
- View: string="Finish & Next" → title="Finish & Next",
class drops "text-primary", gains "o_fp_finish_btn".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Scrapped the v2/v3 form-view + list-as-cards CSS approach after
extensive failure to make Odoo's editable list look like cards.
Built a proper OWL Dialog component instead, mirroring the pattern
used by fusion_plating_shopfloor's move_parts_dialog.js.
What changed
============
* New OWL Dialog: fp_record_inputs_dialog.js
- Loads step + prompt definitions via /fp/record_inputs/load
- Renders each prompt as a semantic <div class="o_fp_ri_card">
- Per-row widget chosen by input_type:
numeric/temperature/thickness/time_seconds/ph -> number input
boolean/pass_fail -> custom CSS toggle (clearer than Bootstrap)
date -> datetime-local input
photo -> file picker w/ preview + clear
multi_point_thickness -> 5-cell grid + live average
bath_chemistry_panel -> pH/Conc/Temp/Bath grid
selection -> dropdown sourced from selection_options
text/signature/... -> text input
- Live in-range hint for numeric prompts
("in range" / "below target" / "above target")
- Save validates ad-hoc rows have a Prompt label
- Save dispatches the next_action returned by the wizard model
(e.g. action_finish_and_advance for the Finish & Next flow)
* New XML template: fp_record_inputs_dialog.xml
Full DOM control. No fighting Odoo's list view, no class-stripping
bugs from canUseFormatter, no read-mode-vs-edit-mode CSS dance.
* New SCSS: fp_record_inputs_dialog.scss
- Dark mode aware (compile-time @if $o-webclient-color-scheme==dark)
- Pure semantic selectors (.o_fp_ri_card, .o_fp_ri_input, etc.)
- 14 surface tokens with light/dark hex pairs
- Tablet polish via @media (max-width: 768px)
- Custom toggle widget (no <input type="checkbox"> hidden trick)
* New controller: controllers/record_inputs.py
- /fp/record_inputs/load: returns step + prompts payload
- /fp/record_inputs/commit: creates a wizard, populates lines,
calls action_commit (reuses existing audit-trail / synthetic
move semantics — no commit logic duplicated)
* fp_job_step.py wired to dispatch the new action
- _fp_open_input_wizard returns
{ type: 'ir.actions.client', tag: 'fp_record_inputs_dialog' }
- action_open_input_wizard same
- Contract-review redirect gate preserved (Sub 4 work intact)
* Manifest registers JS/XML/SCSS in BOTH backend + dark bundles
per the dark-mode pattern in CLAUDE.md.
What was kept
=============
* fp.job.step.input.wizard TransientModel — UNCHANGED. The new
controller's commit endpoint creates a wizard record and calls
action_commit() on it, so all the audit-trail / synthetic-move
/ chatter logic stays in Python where it belongs.
* v2 + v3 form views still exist in the XML file. If the OWL
dialog ever fails, switch action_open_input_wizard back to
ir.actions.act_window with view_id=v2 or v3.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
I was wrong about the DOM. Verified from Odoo 19 source on entech:
web/static/src/views/fields/float/float_field.xml
web/static/src/views/fields/char/char_field.xml
web/static/src/views/list/list_renderer.xml
Float/Char fields render as a BARE <span> (read mode) or BARE
<input class="o_input"> (edit mode) directly inside the <td>.
There is NO .o_field_widget wrapper. So all my prior CSS targeting
.o_field_widget matched nothing.
Also discovered: Odoo's getCellClass() in list_renderer.js calls
canUseFormatter() which strips custom <field> classes when the
column has widget="..." set:
canUseFormatter(column, record) {
if (column.widget) {
return false; // ← class stripped here
}
...
}
So o_fp_iw_value class doesn't even land on cells with
widget="boolean_toggle"/"image". Those cells render natively;
boolean toggle and image styling now targets the widgets directly
wherever they appear (.o_boolean_toggle, .o_field_image).
Fix: put visible chrome (border, bg, padding, min-height) on the
<td> itself for prompt/meta/value/extras cells. Make inner span
and input transparent + inherit. Focus ring travels up via
:focus-within on the td.
Cells now look like obvious input boxes from first paint, regardless
of whether the user has clicked into edit mode.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Root cause user kept seeing inputs as bare/borderless text:
Odoo's <list editable="bottom"> renders each cell as a read-mode
<span> inside .o_field_widget UNTIL the user clicks the cell.
Only then does an <input> swap in. My CSS was targeting
`td.o_fp_iw_value input { ... }` so the chrome only appeared on
focus. Every other (unclicked) cell looked like dead text.
Fix:
Move all input chrome (border, bg, padding, min-height) to the
.o_field_widget wrapper which is ALWAYS in the DOM. Then make
the inner <input> / <span> transparent so they inherit. Effect:
the cell looks like an input box from first paint, regardless
of focus state. Focus ring travels up via :focus-within.
Special widgets (boolean toggle, photo upload, multi-point,
bath panel) opt OUT of the wrapper chrome via :has() so they
keep their own visual treatment.
Same fix applied to .o_fp_iw_extra cells (composite types).
User reproduction: WH/JOB/00339 → Record on Masking step. After
hard-refresh + this build, every value cell should read as an
obvious input box even before the operator clicks.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Four visible bugs reported by user after deploy:
1. Type + Unit pills overlapped at top-right of every card.
Root cause: both <field>s carried the same .o_fp_iw_meta class
AND both mapped to grid-area: meta. CSS Grid stacked them on
top of each other so the labels rendered as overlap garbage
(e.g. "eachnber", "Time(secs)Time(seconds)").
Fix: distinct classes (.o_fp_iw_meta_type / .o_fp_iw_meta_unit)
each in its own grid column. Grid is now 4 columns wide:
"prompt | type | unit | trash"
2. Input borders barely visible in dark mode (#343942 on #22262d).
Operators couldn't tell where to click.
Fix: brighter border using $fp-iw-ink-faint instead of $fp-iw-border.
Hover bumps to $fp-iw-ink-mute. Focus uses brand purple. Also
added a slight surface tint ($fp-iw-page) so empty inputs read
as obviously-interactive instead of blending into the card.
3. Photo widget rendered enormous (full card width).
Root cause: max-width applied only to the preview image, not
to the .o_field_image container itself.
Fix: max-width 240px on .o_field_image AND its inner controls.
4. Numeric values floated centered in empty space.
Root cause: input width wasn't stretching to its grid cell;
default Odoo numeric-cell text-align: right plus our missing
width: 100% left tiny inputs centered in the value area.
Fix: explicit width: 100%, text-align: left, and 420px
max-width on the .o_field_widget container.
Bonus polish:
* Trash icon hidden by default (opacity: 0), reveals to 0.6 on
row hover, full opacity on direct hover. Reduces visual noise
for the common case where operator just types and saves.
* Boolean toggle scale bumped from 1.4 to 1.5 + adds left margin
so the switch sits properly inside the value cell.
* Mobile (<900px) grid collapses to: prompt|trash / type|unit /
value / extras — keeps the type+unit pair on one row but lets
them flow naturally below the prompt.
No model changes. SCSS + XML view only. v2 view still in place
for instant rollback.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two coherent feature drops shipping together because their fp_job_step
edits overlap. Both target operator workflow correctness.
## Sub 13 — Sequential step enforcement (recipe + per-step)
Background:
Investigation on WH/JOB/00339 showed operators starting Incoming
Inspection while Contract Review was still in_progress. Audit:
98.7% of recipe operations system-wide had requires_predecessor_done
= false (the legacy per-step opt-in defaults off, recipe authors
rarely tick the box).
Architecture:
Recipe-level toggle + per-step opt-out (Option A from /investigate).
* fusion.plating.process.node.enforce_sequential — Boolean on the
recipe root. Default True. When True, every operation under this
recipe waits for earlier-sequence steps to finish before it can
start.
* fusion.plating.process.node.parallel_start — Boolean on operation
nodes. When True, this step bypasses the sequential gate (e.g.
paperwork or QA review that runs alongside production).
* Mirrored on fp.step.template (parallel_start) so library steps
carry the flag into snapshots.
* fp.job.enforce_sequential — related from recipe_id. Snapshotted
at job creation so a recipe author flipping the recipe's flag
AFTER job generation does NOT change behaviour mid-run.
* fp.job.step.parallel_start — related from recipe_node_id.
* Decision matrix (encapsulated in
fp.job.step._fp_should_block_predecessors):
recipe.enforce_sequential | step.parallel_start | step.req_pred_done | block?
--------------------------|---------------------|--------------------|------
True | False | any | YES
True | True | any | no
False | any | True | YES
False | any | False | no
* Manager bypass via context fp_skip_predecessor_check=True (existing).
Runtime gates:
* fp.job.step.button_start — calls _fp_should_block_predecessors;
raises UserError naming the blocking earlier step(s).
* fp.job.step.can_start — computed Boolean for view-side disable.
* Move wizard predecessor check
(fusion_plating_shopfloor/controllers/move_controller.py) — uses
the same helper so tablet + backend behave identically.
UI surface:
* Recipe form (fp_process_node_views.xml) — enforce_sequential
toggle on recipe root, parallel_start checkbox on operations.
* Step template form — parallel_start checkbox.
* Simple Recipe Editor (inline library form) — Parallel Start
checkbox + legacy flag demoted with muted styling + supervisor
group gate.
* Recipe Tree Editor (properties panel) — both flags exposed,
only-show on the right node_type.
* Controllers updated to allowlist + payload the new fields.
Migration:
fusion_plating/migrations/19.0.18.12.0/post-migrate.py — sets
enforce_sequential = TRUE on every existing recipe-root node.
Idempotent. User confirmed dev-stage data, so retroactive flip
is safe (no production jobs to disrupt).
Tests:
TestSequentialEnforcement (10 tests) covering:
* sequential mode blocks out-of-order start
* first step always startable
* predecessor finish/skip unlocks next
* parallel_start opts out of gate
* free-flow mode bypasses gate
* legacy requires_predecessor_done still honoured in free-flow
* manager bypass via context
* can_start compute reflects state correctly
* library template parallel_start snapshots into recipe-node
## Sub 12e — Record Inputs Wizard v3 (card layout, dark-mode aware)
Background:
v2 wizard was a 17-column wide editable table. Operators got lost
finding which value column applied to their row's type, horizontal
scroll required on tablets, composite types crammed into one row.
New layout:
* Each measurement renders as a stacked card (CSS Grid + display
transformation on the existing list widget — preserves inline
editing, no JS rewrite).
* Card header: prompt name (large, bold) + type/unit pills.
* Card body: ONLY the value widget for this row's type
(number / boolean / date / text / photo / multi-point / panel).
* Composite types (multi-point thickness 5x reading + avg, bath
panel 4 fields) get inline sub-grid inside the card.
* Empty state ("no measurement prompts") with friendly CTA.
Dark mode:
* SCSS branches at compile time on $o-webclient-color-scheme
(per fusion-plating/CLAUDE.md note).
* Tokens: 7 surface colours + 4 ink levels with light/dark hex
pairs, all behind var(--fp-*) custom properties for per-deploy
override.
* Registered in BOTH web.assets_backend AND web.assets_web_dark
so each bundle compiles its own palette.
Tablet polish:
@media (max-width: 900px) — collapse meta below prompt + bump
numeric input min-height to 56px.
Defensive:
* v2 view kept in the XML file (instant rollback by changing one
view_id ref).
* `:has(.o_invisible_modifier)` rule drops empty cells out of the
grid so Odoo's invisible="..." doesn't punch holes in layout.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Operator Instructions panel had a hardcoded inline style
(background: #f8f9fa) which became a white-on-dark unreadable blob
in dark mode. Replaced with a CSS class backed by an SCSS file that
branches at compile-time via $o-webclient-color-scheme — registered
in both web.assets_backend (light) and web.assets_web_dark (dark)
bundles per the CLAUDE.md pattern.
Tokens: panel bg #f8f9fa light / #22262d dark; border #d8dadd /
#3a3f47; text #212529 / #e8eaed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>