diff --git a/fusion_plating/fusion_plating/controllers/recipe_controller.py b/fusion_plating/fusion_plating/controllers/recipe_controller.py index a3f988b6..63f97ac0 100644 --- a/fusion_plating/fusion_plating/controllers/recipe_controller.py +++ b/fusion_plating/fusion_plating/controllers/recipe_controller.py @@ -19,11 +19,27 @@ class FpRecipeController(http.Controller): # ------------------------------------------------------------------ @http.route('/fp/recipe/tree', type='jsonrpc', auth='user') def get_tree(self, recipe_id): - """Return the full nested tree for a recipe.""" + """Return the full nested tree for a recipe + the workflow + states catalog for the per-step "Triggers Workflow State" + dropdown in the properties panel (Sub 14). + """ Node = request.env['fusion.plating.process.node'] recipe = Node.browse(int(recipe_id)) if not recipe.exists(): return {'ok': False, 'error': f'Recipe {recipe_id} not found.'} + # Workflow states for the dropdown — runtime-detect the model + # so the tree editor still works on installs without + # fusion_plating_jobs (where the model lives). + workflow_states = [] + WS = request.env.get('fp.job.workflow.state') + if WS is not None: + for ws in WS.search([('active', '=', True)], order='sequence, id'): + workflow_states.append({ + 'id': ws.id, + 'name': ws.name or '', + 'code': ws.code or '', + 'sequence': ws.sequence, + }) return { 'ok': True, 'recipe': { @@ -34,6 +50,7 @@ class FpRecipeController(http.Controller): 'process_type': recipe.process_type_id.name if recipe.process_type_id else '', }, 'tree': recipe.get_tree_data(), + 'workflow_states': workflow_states, } # ------------------------------------------------------------------ @@ -88,6 +105,8 @@ class FpRecipeController(http.Controller): 'requires_signoff', 'opt_in_out', 'sequence', 'version', # Sub 13 — sequential enforcement 'enforce_sequential', 'parallel_start', + # Sub 14 — workflow milestone trigger + 'triggers_workflow_state_id', } safe_vals = {k: v for k, v in vals.items() if k in allowed} if not safe_vals: diff --git a/fusion_plating/fusion_plating/models/fp_process_node.py b/fusion_plating/fusion_plating/models/fp_process_node.py index 890d01c3..757e371e 100644 --- a/fusion_plating/fusion_plating/models/fp_process_node.py +++ b/fusion_plating/fusion_plating/models/fp_process_node.py @@ -569,6 +569,19 @@ class FpProcessNode(models.Model): 'enforce_sequential': self.enforce_sequential, 'parallel_start': self.parallel_start, 'requires_predecessor_done': self.requires_predecessor_done, + # Sub 14 — workflow milestone trigger (Many2one or False) + 'triggers_workflow_state_id': ( + self.triggers_workflow_state_id.id + if 'triggers_workflow_state_id' in self._fields + and self.triggers_workflow_state_id + else False + ), + 'triggers_workflow_state_name': ( + self.triggers_workflow_state_id.name + if 'triggers_workflow_state_id' in self._fields + and self.triggers_workflow_state_id + else '' + ), 'version': self.version, 'child_count': len(children), 'opt_in_out': self.opt_in_out or 'disabled', diff --git a/fusion_plating/fusion_plating/static/src/js/recipe_tree_editor.js b/fusion_plating/fusion_plating/static/src/js/recipe_tree_editor.js index 29bd5aea..3dd85f9f 100644 --- a/fusion_plating/fusion_plating/static/src/js/recipe_tree_editor.js +++ b/fusion_plating/fusion_plating/static/src/js/recipe_tree_editor.js @@ -102,6 +102,7 @@ export class RecipeTreeEditor extends Component { this.state = useState({ recipe: null, tree: null, + workflowStates: [], // Sub 14 — populated by loadTree loading: false, saving: false, selectedNodeId: null, @@ -157,6 +158,9 @@ export class RecipeTreeEditor extends Component { if (result && result.ok) { this.state.recipe = result.recipe; this.state.tree = result.tree; + // Sub 14 — workflow states for the per-step trigger + // dropdown in the properties panel. + this.state.workflowStates = result.workflow_states || []; // Auto-expand every node on first load AND auto-expand // any node we haven't seen before (e.g. freshly imported // nodes after a "Import from recipe" run). Nodes the @@ -271,6 +275,8 @@ export class RecipeTreeEditor extends Component { // Sub 13 — sequential enforcement enforce_sequential: !!node.enforce_sequential, parallel_start: !!node.parallel_start, + // Sub 14 — workflow milestone trigger + triggers_workflow_state_id: node.triggers_workflow_state_id || false, }; const result = await rpc("/fp/recipe/node/write", { node_id: node.id, diff --git a/fusion_plating/fusion_plating/static/src/xml/recipe_tree_editor.xml b/fusion_plating/fusion_plating/static/src/xml/recipe_tree_editor.xml index a09aecf2..75cf6d26 100644 --- a/fusion_plating/fusion_plating/static/src/xml/recipe_tree_editor.xml +++ b/fusion_plating/fusion_plating/static/src/xml/recipe_tree_editor.xml @@ -374,6 +374,31 @@ + +
+ + + + When this step finishes (or is skipped/cancelled), the + job's status bar advances to the chosen state. Leave + blank to fall back to the default-kind mapping + configured on the workflow state catalog. + +
+