diff --git a/fusion_plating/fusion_plating/__manifest__.py b/fusion_plating/fusion_plating/__manifest__.py index 6eea2cc8..717e7429 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.3', + 'version': '19.0.18.12.4', '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 116bde9d..6e2c43f4 100644 --- a/fusion_plating/fusion_plating/controllers/simple_recipe_controller.py +++ b/fusion_plating/fusion_plating/controllers/simple_recipe_controller.py @@ -102,6 +102,15 @@ class SimpleRecipeController(http.Controller): 'work_center_id': step.work_center_id.id if step.work_center_id else False, 'source_template_id': step.source_template_id.id or False, 'collect_measurements': bool(step.collect_measurements), + # Sub 13 — per-step opt-out of the sequential gate + 'parallel_start': bool(step.parallel_start), + # Sub 14 — workflow milestone trigger override + 'triggers_workflow_state_id': ( + step.triggers_workflow_state_id.id + if 'triggers_workflow_state_id' in step._fields + and step.triggers_workflow_state_id + else False + ), 'measurements_badge_text': badge_text, 'measurements_badge_class': badge_class, 'inputs': [ @@ -437,8 +446,29 @@ class SimpleRecipeController(http.Controller): @http.route('/fp/simple_recipe/step/write', type='jsonrpc', auth='user') def step_write(self, node_id, vals): - node = request.env['fusion.plating.process.node'].browse(node_id) - node.write(vals) + """Update fields on an existing recipe step (operation node). + + Whitelisted to the fields the inline edit panel actually surfaces + — never trust client-provided node_type / parent_id / etc. + """ + node = request.env['fusion.plating.process.node'].browse(int(node_id)) + if not node.exists(): + return {'ok': False, 'error': 'not_found'} + node.check_access('write') + allowed = { + 'name', 'description', 'icon', + 'default_kind', + 'requires_signoff', 'requires_predecessor_done', + 'parallel_start', # Sub 13 + 'triggers_workflow_state_id', # Sub 14 + 'requires_rack_assignment', + 'requires_transition_form', + 'estimated_duration', + 'collect_measurements', + } + clean = {k: v for k, v in (vals or {}).items() if k in allowed} + if clean: + node.write(clean) return {'ok': True} @http.route('/fp/simple_recipe/step/remove', type='jsonrpc', auth='user') diff --git a/fusion_plating/fusion_plating/migrations/19.0.18.8.0/post-migrate.py b/fusion_plating/fusion_plating/migrations/19.0.18.8.0/post-migrate.py index b035a9c4..07214187 100644 --- a/fusion_plating/fusion_plating/migrations/19.0.18.8.0/post-migrate.py +++ b/fusion_plating/fusion_plating/migrations/19.0.18.8.0/post-migrate.py @@ -27,10 +27,15 @@ have a paper trail. import logging +from markupsafe import Markup + _logger = logging.getLogger(__name__) -_AUDIT_BODY = ( +# Wrapped in Markup so Odoo's message_post recognises it as safe HTML +# instead of escaping the tags. Without Markup, the chatter pill renders +# the raw "
...
" string literally to the operator. +_AUDIT_BODY = Markup( 'Recipe migrated to v19.0.18.8.0 step layout.
' 'Step nodes that were direct children of this recipe (Simple ' 'Editor authoring) have been promoted to operation nodes so they ' 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 d24a4c4d..4d18e404 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 @@ -44,6 +44,12 @@ export class FpSimpleRecipeEditor extends Component { editingStepId: null, editName: "", editInstructions: "", + // Sub 14 + Sub 13 — additional per-step settings that the + // user can change inline without delete + re-add. + editDefaultKind: "", + editTriggersWorkflowStateId: false, + editParallelStart: false, + editRequiresSignoff: false, // Inline library form — open when authoring or editing a // library template directly from the right pane. null = // closed; otherwise carries the template payload. @@ -287,6 +293,13 @@ export class FpSimpleRecipeEditor extends Component { // we want to be able to mutate this.state.libraryEditor.* in // place without triggering library list re-renders. this.state.libraryEditor = JSON.parse(JSON.stringify(data.template)); + // description is fields.Html on the server → strip
tags + // for clean display in the textarea (operators don't want + // to see raw HTML markup). Saved back via _textToHtml on + // onSaveLibraryEditor below. + this.state.libraryEditor.description = this._htmlToText( + this.state.libraryEditor.description || "" + ); } else { this.notification.add( _t("Could not load library template — it may have been deleted."), @@ -560,18 +573,35 @@ export class FpSimpleRecipeEditor extends Component { * Save discards changes — operator-style "I clicked the wrong row" * shouldn't write garbage to the recipe. */ - onToggleEdit(stepId) { + async onToggleEdit(stepId) { if (this.state.editingStepId === stepId) { - this.state.editingStepId = null; - this.state.editName = ""; - this.state.editInstructions = ""; + this._fpResetStepEdit(); return; } const step = this.state.steps.find((s) => s.id === stepId); if (!step) return; + // Sub 14 — make sure the workflow-state catalog is cached so + // the dropdown in the inline form has options to render. + await this._fpEnsureWorkflowStatesLoaded(); this.state.editingStepId = stepId; this.state.editName = step.name || ""; this.state.editInstructions = this._htmlToText(step.description || ""); + // Settings the user can now change WITHOUT delete + re-add. + this.state.editDefaultKind = step.default_kind || ""; + this.state.editTriggersWorkflowStateId = + step.triggers_workflow_state_id || false; + this.state.editParallelStart = !!step.parallel_start; + this.state.editRequiresSignoff = !!step.requires_signoff; + } + + _fpResetStepEdit() { + this.state.editingStepId = null; + this.state.editName = ""; + this.state.editInstructions = ""; + this.state.editDefaultKind = ""; + this.state.editTriggersWorkflowStateId = false; + this.state.editParallelStart = false; + this.state.editRequiresSignoff = false; } async onSaveStep() { @@ -580,22 +610,25 @@ export class FpSimpleRecipeEditor extends Component { const vals = { name: this.state.editName || _t("Untitled Step"), description: this._textToHtml(this.state.editInstructions), + // New per-step settings — user can flip these without + // deleting and re-adding the step. + default_kind: this.state.editDefaultKind || false, + triggers_workflow_state_id: + this.state.editTriggersWorkflowStateId || false, + parallel_start: !!this.state.editParallelStart, + requires_signoff: !!this.state.editRequiresSignoff, }; await rpc("/fp/simple_recipe/step/write", { node_id: stepId, vals: vals, }); - this.state.editingStepId = null; - this.state.editName = ""; - this.state.editInstructions = ""; + this._fpResetStepEdit(); await this.loadAll(); this.notification.add(_t("Step updated"), { type: "success" }); } onCancelEdit() { - this.state.editingStepId = null; - this.state.editName = ""; - this.state.editInstructions = ""; + this._fpResetStepEdit(); } // -------------------- Sub 12d — measurements config -------------------- 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 6bfdb7a8..a2fd1a09 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 @@ -116,6 +116,83 @@ Shown to operators when running this step at the tank. Use line breaks for separate points.
+ + +
+ Drives workflow milestone triggers (e.g. final_inspect fires
+ the Inspected status) and routing (e.g. contract_review opens
+ QA-005 instead of the input wizard).
+
+ Override the default-kind matching. Wins over Step Type when set. +
+