From e54ffe73095896a735ff20a464d14b7e3bf21675 Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Mon, 4 May 2026 00:04:59 -0400 Subject: [PATCH] =?UTF-8?q?feat(jobs):=20Sub=2014=20polish=20=E2=80=94=20w?= =?UTF-8?q?orkflow=20state=20form=20layout=20+=20Simple=20Editor=20field?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two follow-ups on the workflow state work: 1) Form layout The "How triggers combine" help text was crammed into a 2-column group, taking ~25% of the available width. Pulled it out of the group and rendered as a full-width
below the trigger fields. Same fix applied to Notes — uses a + bare for full sheet width. 2) Simple Recipe Editor support The trigger field was only exposed in the Tree Editor. Added it to the Simple Editor's inline library form too: * fp.step.template.triggers_workflow_state_id (new Many2one) — per-template default, snapshot-copied to recipe nodes when dropped into a recipe (added to _SNAPSHOT_FIELDS). * /fp/simple_recipe/workflow_states/list — new endpoint to feed the dropdown. Soft-fails when fusion_plating_jobs isn't installed (returns []). * Library editor JS — _fpEnsureWorkflowStatesLoaded helper caches the catalog on first open (create + edit paths both warm it). Save vals carry the trigger id. * Library editor XML — dropdown rendered after the flag checkboxes. Hidden when the catalog is empty so the form doesn't show a useless "— None —" pick. Co-Authored-By: Claude Opus 4.7 (1M context) --- fusion_plating/fusion_plating/__manifest__.py | 2 +- .../controllers/simple_recipe_controller.py | 39 ++++++++++++ .../fusion_plating/models/fp_step_template.py | 16 +++++ .../static/src/js/simple_recipe_editor.js | 31 +++++++++- .../static/src/xml/simple_recipe_editor.xml | 27 +++++++++ .../fusion_plating_jobs/__manifest__.py | 2 +- .../views/fp_workflow_state_views.xml | 59 ++++++++++++++----- 7 files changed, 157 insertions(+), 19 deletions(-) diff --git a/fusion_plating/fusion_plating/__manifest__.py b/fusion_plating/fusion_plating/__manifest__.py index 7bac50ef..6eea2cc8 100644 --- a/fusion_plating/fusion_plating/__manifest__.py +++ b/fusion_plating/fusion_plating/__manifest__.py @@ -5,7 +5,7 @@ { 'name': 'Fusion Plating', - 'version': '19.0.18.12.2', + 'version': '19.0.18.12.3', 'category': 'Manufacturing/Plating', 'summary': 'Core plating / metal finishing ERP: facilities, processes, tanks, baths, jobs, operators.', 'description': """ diff --git a/fusion_plating/fusion_plating/controllers/simple_recipe_controller.py b/fusion_plating/fusion_plating/controllers/simple_recipe_controller.py index f6c3f1b6..116bde9d 100644 --- a/fusion_plating/fusion_plating/controllers/simple_recipe_controller.py +++ b/fusion_plating/fusion_plating/controllers/simple_recipe_controller.py @@ -24,6 +24,7 @@ _SNAPSHOT_FIELDS = [ 'voltage_target', 'viscosity_target', 'requires_signoff', 'requires_predecessor_done', 'parallel_start', + 'triggers_workflow_state_id', # Sub 14 — workflow milestone trigger 'requires_rack_assignment', 'requires_transition_form', 'default_kind', ] @@ -195,6 +196,15 @@ class SimpleRecipeController(http.Controller): 'requires_signoff': tpl.requires_signoff, 'requires_predecessor_done': tpl.requires_predecessor_done, 'parallel_start': tpl.parallel_start, + # Sub 14 — workflow trigger (id + name for display) + 'triggers_workflow_state_id': ( + tpl.triggers_workflow_state_id.id + if tpl.triggers_workflow_state_id else False + ), + 'triggers_workflow_state_name': ( + tpl.triggers_workflow_state_id.name + if tpl.triggers_workflow_state_id else '' + ), 'requires_rack_assignment': tpl.requires_rack_assignment, 'requires_transition_form': tpl.requires_transition_form, 'tank_ids': [ @@ -230,6 +240,7 @@ class SimpleRecipeController(http.Controller): 'name', 'code', 'icon', 'default_kind', 'description', 'requires_signoff', 'requires_predecessor_done', 'parallel_start', + 'triggers_workflow_state_id', # Sub 14 'requires_rack_assignment', 'requires_transition_form', 'tank_ids', } @@ -320,6 +331,34 @@ class SimpleRecipeController(http.Controller): ], } + @http.route('/fp/simple_recipe/workflow_states/list', + type='jsonrpc', auth='user') + def workflow_states_list(self): + """Sub 14 — workflow-state picker for the inline library form. + Returns active states ordered by sequence so the dropdown + renders left-to-right matching the status bar. + + Soft-fail when fp.job.workflow.state isn't installed (rare, + only when fusion_plating_jobs is missing) — empty list lets the + dropdown render disabled instead of throwing. + """ + WS = request.env.get('fp.job.workflow.state') + if WS is None: + return {'workflow_states': []} + return { + 'workflow_states': [ + { + 'id': ws.id, + 'name': ws.name or '', + 'code': ws.code or '', + 'sequence': ws.sequence, + } + for ws in WS.search( + [('active', '=', True)], order='sequence, id', + ) + ], + } + # ------------------------------------------------------------------ step @http.route('/fp/simple_recipe/step/insert', type='jsonrpc', auth='user') def step_insert(self, recipe_id, template_id=False, position=99, vals=None): diff --git a/fusion_plating/fusion_plating/models/fp_step_template.py b/fusion_plating/fusion_plating/models/fp_step_template.py index 113960e0..883f2074 100644 --- a/fusion_plating/fusion_plating/models/fp_step_template.py +++ b/fusion_plating/fusion_plating/models/fp_step_template.py @@ -79,6 +79,22 @@ class FpStepTemplate(models.Model): 'earlier-sequence steps are still in progress (e.g. ' 'paperwork that runs alongside production).', ) + # Sub 14 — workflow milestone trigger (optional) + # The fp.job.workflow.state model lives in fusion_plating_jobs, so + # this Many2one resolves at runtime only when that module is loaded. + # When the library template is dropped into a recipe, the value is + # snapshot-copied to the new process_node via _SNAPSHOT_FIELDS in + # simple_recipe_controller.py. + triggers_workflow_state_id = fields.Many2one( + 'fp.job.workflow.state', + string='Triggers Workflow State', + ondelete='set null', + help='Sub 14. When a recipe step generated from this template ' + 'finishes (or is skipped/cancelled), the parent job ' + 'advances to this workflow state. Leave blank to fall ' + 'back to default-kind matching defined on the workflow ' + 'state catalog.', + ) requires_rack_assignment = fields.Boolean(string='Requires Rack Assignment', help='Triggers Rack Parts sub-dialog at runtime (Sub 12b).') requires_transition_form = fields.Boolean(string='Requires Transition Form', diff --git a/fusion_plating/fusion_plating/static/src/js/simple_recipe_editor.js b/fusion_plating/fusion_plating/static/src/js/simple_recipe_editor.js index 7a6871a8..d24a4c4d 100644 --- a/fusion_plating/fusion_plating/static/src/js/simple_recipe_editor.js +++ b/fusion_plating/fusion_plating/static/src/js/simple_recipe_editor.js @@ -50,6 +50,10 @@ export class FpSimpleRecipeEditor extends Component { libraryEditor: null, libraryEditorBusy: false, tankSearchResults: [], + // Sub 14 — workflow-state catalog cache for the inline + // library form's "Triggers Workflow State" dropdown. Lazy- + // loaded the first time the user opens the library editor. + workflowStates: [], }); this._recipeId = null; @@ -231,7 +235,8 @@ export class FpSimpleRecipeEditor extends Component { * mirrors the shape returned by `/fp/simple_recipe/library/load` so * the same template renders both create + edit. */ - onOpenLibraryCreate() { + async onOpenLibraryCreate() { + await this._fpEnsureWorkflowStatesLoaded(); this.state.libraryEditor = { id: null, // null = create name: "", @@ -242,6 +247,8 @@ export class FpSimpleRecipeEditor extends Component { requires_signoff: false, requires_predecessor_done: false, parallel_start: false, // Sub 13 — per-step opt-out + triggers_workflow_state_id: false, // Sub 14 — workflow trigger + triggers_workflow_state_name: "", requires_rack_assignment: false, requires_transition_form: false, tank_ids: [], @@ -250,8 +257,28 @@ export class FpSimpleRecipeEditor extends Component { this.state.tankSearchResults = []; } + /** + * Sub 14 — fetch the workflow-state catalog once per editor session, + * cache on this.state.workflowStates. Used by both create + edit + * flows to populate the "Triggers Workflow State" dropdown. + */ + async _fpEnsureWorkflowStatesLoaded() { + if (this.state.workflowStates && this.state.workflowStates.length) { + return; + } + try { + const data = await rpc( + "/fp/simple_recipe/workflow_states/list", {} + ); + this.state.workflowStates = data.workflow_states || []; + } catch (err) { + this.state.workflowStates = []; + } + } + async onOpenLibraryEdit(templateId) { this.state.libraryEditorBusy = true; + await this._fpEnsureWorkflowStatesLoaded(); const data = await rpc("/fp/simple_recipe/library/load", { template_id: templateId, }); @@ -291,6 +318,8 @@ export class FpSimpleRecipeEditor extends Component { requires_signoff: !!ed.requires_signoff, requires_predecessor_done: !!ed.requires_predecessor_done, parallel_start: !!ed.parallel_start, + // Sub 14 — workflow trigger (Many2one int or false) + triggers_workflow_state_id: ed.triggers_workflow_state_id || false, requires_rack_assignment: !!ed.requires_rack_assignment, requires_transition_form: !!ed.requires_transition_form, tank_ids: (ed.tank_ids || []).map((t) => t.id), diff --git a/fusion_plating/fusion_plating/static/src/xml/simple_recipe_editor.xml b/fusion_plating/fusion_plating/static/src/xml/simple_recipe_editor.xml index 2e8370b3..6bfdb7a8 100644 --- a/fusion_plating/fusion_plating/static/src/xml/simple_recipe_editor.xml +++ b/fusion_plating/fusion_plating/static/src/xml/simple_recipe_editor.xml @@ -435,6 +435,33 @@
+ +
+ + + + When a recipe step generated from this template + finishes (or is skipped/cancelled), the parent + job advances to the chosen state on its status + bar. Leave blank to fall back to default-kind + matching configured on the workflow state catalog. + +
+
diff --git a/fusion_plating/fusion_plating_jobs/__manifest__.py b/fusion_plating/fusion_plating_jobs/__manifest__.py index 2c9c8a18..1b3e9a65 100644 --- a/fusion_plating/fusion_plating_jobs/__manifest__.py +++ b/fusion_plating/fusion_plating_jobs/__manifest__.py @@ -3,7 +3,7 @@ # License OPL-1 (Odoo Proprietary License v1.0) { 'name': 'Fusion Plating — Native Jobs', - 'version': '19.0.8.18.2', + 'version': '19.0.8.18.3', 'category': 'Manufacturing/Plating', 'summary': 'Native plating job model — replaces mrp.production / mrp.workorder bridge.', 'author': 'Nexa Systems Inc.', diff --git a/fusion_plating/fusion_plating_jobs/views/fp_workflow_state_views.xml b/fusion_plating/fusion_plating_jobs/views/fp_workflow_state_views.xml index 9ff218c2..e9b21f1b 100644 --- a/fusion_plating/fusion_plating_jobs/views/fp_workflow_state_views.xml +++ b/fusion_plating/fusion_plating_jobs/views/fp_workflow_state_views.xml @@ -43,6 +43,7 @@
+ @@ -56,27 +57,53 @@ - + + + -

- How triggers combine: a state is "passed" - when EITHER the special trigger is true, OR every - recipe step matching the listed default_kinds (or - tagged via the per-node override on the recipe) is - in done/skipped/cancelled state. -
- block_when_quality_hold: holds back the - advance even if the trigger conditions are met, - until all open quality holds on the job are closed. + + + +

+ + +