feat(jobs): Sub 13 sequential step enforcement + Sub 12e v3 wizard
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>
This commit is contained in:
@@ -93,11 +93,154 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- ================================================================== -->
|
||||
<!-- v3 — Card-based stacked layout (light + dark mode aware) -->
|
||||
<!-- -->
|
||||
<!-- Replaces v2's wide editable table. Each measurement renders as a -->
|
||||
<!-- card with: prompt name (header), type/unit pills (meta), and ONLY -->
|
||||
<!-- the live input widget for that prompt's type (value). Composite -->
|
||||
<!-- types (multi-point thickness, bath chemistry panel) keep their -->
|
||||
<!-- sub-fields inside the same card via the "extras" grid area. -->
|
||||
<!-- -->
|
||||
<!-- Auto-adapts to custom recipes/steps because default_get on the -->
|
||||
<!-- model already pre-fills line_ids from recipe_node.input_ids. -->
|
||||
<!-- -->
|
||||
<!-- All visual styling lives in -->
|
||||
<!-- static/src/scss/fp_job_step_input_wizard_v3.scss -->
|
||||
<!-- which is registered in BOTH web.assets_backend AND -->
|
||||
<!-- web.assets_web_dark so both themes compile their own palette. -->
|
||||
<!-- ================================================================== -->
|
||||
<record id="view_fp_job_step_input_wizard_form_v3" model="ir.ui.view">
|
||||
<field name="name">fp.job.step.input.wizard.form.v3</field>
|
||||
<field name="model">fp.job.step.input.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Record Step Inputs">
|
||||
<sheet class="o_fp_input_wizard_v3">
|
||||
<div class="o_fp_input_header">
|
||||
<h2><field name="step_id" readonly="1" nolabel="1"/></h2>
|
||||
<p class="o_fp_input_subhead">
|
||||
Job <field name="job_id" readonly="1" nolabel="1"/>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="o_fp_input_section_title">Measurements</div>
|
||||
|
||||
<p class="o_fp_input_empty_state" invisible="line_ids">
|
||||
No measurement prompts on this step yet.
|
||||
Click <strong>Add a line</strong> below to record an
|
||||
ad-hoc reading.
|
||||
</p>
|
||||
|
||||
<field name="line_ids" class="o_fp_input_card_list" nolabel="1">
|
||||
<list editable="bottom" create="true" delete="true">
|
||||
<!-- Hidden flag fields — drive value-cell visibility -->
|
||||
<field name="is_authored" column_invisible="1"/>
|
||||
<field name="is_boolean_type" column_invisible="1"/>
|
||||
<field name="is_date_type" column_invisible="1"/>
|
||||
<field name="is_numeric_type" column_invisible="1"/>
|
||||
<field name="is_photo_type" column_invisible="1"/>
|
||||
<field name="is_multi_point_type" column_invisible="1"/>
|
||||
<field name="is_panel_type" column_invisible="1"/>
|
||||
<field name="point_avg" column_invisible="1"/>
|
||||
<field name="photo_filename" column_invisible="1"/>
|
||||
|
||||
<!-- Card header — prompt name (large, bold via SCSS) -->
|
||||
<field name="name"
|
||||
string="Measurement"
|
||||
readonly="is_authored"
|
||||
placeholder="e.g. Oven Temp, Bath Reading, Operator Initials"
|
||||
class="o_fp_iw_prompt"/>
|
||||
|
||||
<!-- Meta — type + unit rendered as pills (top-right) -->
|
||||
<field name="input_type"
|
||||
string="Type"
|
||||
readonly="is_authored"
|
||||
class="o_fp_iw_meta"/>
|
||||
<field name="target_unit"
|
||||
string="Unit"
|
||||
readonly="is_authored"
|
||||
class="o_fp_iw_meta"
|
||||
optional="show"/>
|
||||
|
||||
<!-- Hidden by default — operator can opt in via the cog menu
|
||||
if they want to see/edit target ranges per row -->
|
||||
<field name="target_min" optional="hide"/>
|
||||
<field name="target_max" optional="hide"/>
|
||||
|
||||
<!-- Mutually exclusive value widgets — only the one
|
||||
matching the row's input_type renders -->
|
||||
<field name="value_number"
|
||||
string="Value"
|
||||
invisible="not is_numeric_type"
|
||||
class="o_fp_iw_value"/>
|
||||
<field name="value_boolean"
|
||||
string="Value"
|
||||
widget="boolean_toggle"
|
||||
invisible="not is_boolean_type"
|
||||
class="o_fp_iw_value"/>
|
||||
<field name="value_date"
|
||||
string="Value"
|
||||
invisible="not is_date_type"
|
||||
class="o_fp_iw_value"/>
|
||||
<field name="value_text"
|
||||
string="Value"
|
||||
invisible="is_numeric_type or is_boolean_type or is_date_type or is_photo_type or is_multi_point_type or is_panel_type"
|
||||
class="o_fp_iw_value"/>
|
||||
<field name="photo_value"
|
||||
string="Photo"
|
||||
widget="image"
|
||||
options="{'preview_image': 'photo_value'}"
|
||||
invisible="not is_photo_type"
|
||||
class="o_fp_iw_value"/>
|
||||
|
||||
<!-- Composite type 1: Multi-Point Thickness — 5 readings -->
|
||||
<field name="point_1" string="R1"
|
||||
invisible="not is_multi_point_type"
|
||||
class="o_fp_iw_extra" optional="show"/>
|
||||
<field name="point_2" string="R2"
|
||||
invisible="not is_multi_point_type"
|
||||
class="o_fp_iw_extra" optional="show"/>
|
||||
<field name="point_3" string="R3"
|
||||
invisible="not is_multi_point_type"
|
||||
class="o_fp_iw_extra" optional="show"/>
|
||||
<field name="point_4" string="R4"
|
||||
invisible="not is_multi_point_type"
|
||||
class="o_fp_iw_extra" optional="hide"/>
|
||||
<field name="point_5" string="R5"
|
||||
invisible="not is_multi_point_type"
|
||||
class="o_fp_iw_extra" optional="hide"/>
|
||||
|
||||
<!-- Composite type 2: Bath Chemistry Panel -->
|
||||
<field name="panel_ph" string="pH"
|
||||
invisible="not is_panel_type"
|
||||
class="o_fp_iw_extra"/>
|
||||
<field name="panel_concentration" string="Conc"
|
||||
invisible="not is_panel_type"
|
||||
class="o_fp_iw_extra"/>
|
||||
<field name="panel_temperature" string="Temp"
|
||||
invisible="not is_panel_type"
|
||||
class="o_fp_iw_extra"/>
|
||||
<field name="panel_bath_id" string="Bath"
|
||||
invisible="not is_panel_type"
|
||||
class="o_fp_iw_extra"/>
|
||||
</list>
|
||||
</field>
|
||||
</sheet>
|
||||
<footer>
|
||||
<button name="action_commit" type="object"
|
||||
string="Save" class="btn-primary"/>
|
||||
<button string="Cancel" class="btn-secondary"
|
||||
special="cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_fp_job_step_input_wizard" model="ir.actions.act_window">
|
||||
<field name="name">Record Step Inputs</field>
|
||||
<field name="res_model">fp.job.step.input.wizard</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="view_id" ref="view_fp_job_step_input_wizard_form_v2"/>
|
||||
<field name="view_id" ref="view_fp_job_step_input_wizard_form_v3"/>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user