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 @@ + +
- 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.
+
+ A state is "passed" when + either:
- -trigger_first_step_started or
+ trigger_all_steps_done),
+ OR
+ trigger_default_kinds (or tagged
+ via the per-node override on the recipe) is
+ in done / skipped /
+ cancelled state.
+ + Blocked by Quality Hold: holds + back the advance even if the trigger conditions + are met, until all open quality holds on the job + are closed. +
+