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.
+
+