From d6bd43b76e42bd899add79d7ac0667761d5fa98f Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Mon, 4 May 2026 00:11:17 -0400 Subject: [PATCH] =?UTF-8?q?fix(jobs):=20workflow=20state=20=E2=80=94=20cha?= =?UTF-8?q?tter=20+=20field=20cycle=20fix=20+=20force=20view=20reload?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three issues from user testing on entech: 1. RPC error: column fp_step_template.triggers_workflow_state_id does not exist Root cause: the field was declared in fusion_plating CORE, but its target model fp.job.workflow.state lives in fusion_plating_jobs. Odoo loads core BEFORE jobs (jobs depends on core), so when core's field declaration runs, the comodel doesn't exist yet — and Odoo silently skips creating the column. Fix: moved the field to fusion_plating_jobs/models/fp_job.py via _inherit. Now the column is added when jobs loads (after core), and the FK target is resolvable. 2. No chatter on the Workflow State form Added _inherit = ['mail.thread', 'mail.activity.mixin'] to fp.job.workflow.state. Tracking enabled on name/code/sequence so admins see who changed the milestone vocabulary. widget added to the form view. 3. Form layout still showed cramped 2-col help text The XML file on disk had my new alert-info card, but Odoo's DB ir_ui_view still held the old arch. The -u didn't refresh it (likely because the file's mtime didn't change between deploys). Fix: bump version + the next deploy will run a SQL DELETE on the ir_ui_view record so Odoo recreates it from XML on -u. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../fusion_plating/models/fp_step_template.py | 20 ++++------------ .../fusion_plating_jobs/__manifest__.py | 2 +- .../fusion_plating_jobs/models/fp_job.py | 24 +++++++++++++++++++ .../models/fp_job_workflow_state.py | 4 ++++ .../views/fp_workflow_state_views.xml | 3 +++ 5 files changed, 36 insertions(+), 17 deletions(-) diff --git a/fusion_plating/fusion_plating/models/fp_step_template.py b/fusion_plating/fusion_plating/models/fp_step_template.py index 883f2074..43fd1f05 100644 --- a/fusion_plating/fusion_plating/models/fp_step_template.py +++ b/fusion_plating/fusion_plating/models/fp_step_template.py @@ -79,22 +79,10 @@ 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.', - ) + # Sub 14 — triggers_workflow_state_id is declared via _inherit in + # fusion_plating_jobs/models/fp_job.py. It can't live here because + # the target model (fp.job.workflow.state) is defined in jobs, and + # core can't depend on jobs (cyclic dependency). 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_jobs/__manifest__.py b/fusion_plating/fusion_plating_jobs/__manifest__.py index 1b3e9a65..54c6d89b 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.3', + 'version': '19.0.8.18.4', '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/models/fp_job.py b/fusion_plating/fusion_plating_jobs/models/fp_job.py index 761e6d2a..fccfde84 100644 --- a/fusion_plating/fusion_plating_jobs/models/fp_job.py +++ b/fusion_plating/fusion_plating_jobs/models/fp_job.py @@ -1413,3 +1413,27 @@ class FusionPlatingProcessNodeWorkflow(models.Model): 'workflow state. Leave blank to fall back to default-kind ' 'matching defined on the workflow state catalog.', ) + + +class FpStepTemplateWorkflow(models.Model): + """Sub 14 — workflow milestone trigger on the library step template. + Declared here (jobs module) instead of fusion_plating core because + the target model (fp.job.workflow.state) lives in this module — + core can't reference it without a cyclic dependency. + + When the template lands in a recipe via simple_recipe_controller + drag-drop, the value is snapshot-copied to the new process_node + via _SNAPSHOT_FIELDS. + """ + _inherit = 'fp.step.template' + + 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 on its status bar. Leave ' + 'blank to fall back to default-kind matching defined on ' + 'the workflow state catalog.', + ) diff --git a/fusion_plating/fusion_plating_jobs/models/fp_job_workflow_state.py b/fusion_plating/fusion_plating_jobs/models/fp_job_workflow_state.py index 8e2cb053..dffd6bc2 100644 --- a/fusion_plating/fusion_plating_jobs/models/fp_job_workflow_state.py +++ b/fusion_plating/fusion_plating_jobs/models/fp_job_workflow_state.py @@ -28,6 +28,7 @@ from odoo import _, api, fields, models class FpJobWorkflowState(models.Model): _name = 'fp.job.workflow.state' _description = 'Fusion Plating — Job Workflow State (status bar milestone)' + _inherit = ['mail.thread', 'mail.activity.mixin'] _order = 'sequence, id' _rec_name = 'name' @@ -35,12 +36,14 @@ class FpJobWorkflowState(models.Model): string='State Name', required=True, translate=True, + tracking=True, help='Operator-facing label shown in the job status bar ' '(e.g. "Received", "Inspected", "Shipped").', ) code = fields.Char( string='Code', required=True, + tracking=True, help='Stable identifier — used by code/migrations to reference ' 'this state without depending on the (translatable) name. ' 'Lowercase snake_case (e.g. "received", "inspected").', @@ -49,6 +52,7 @@ class FpJobWorkflowState(models.Model): string='Sequence', default=10, required=True, + tracking=True, help='Position of this state on the bar (left to right). ' '10-spacing convention so authors can insert new states ' 'between existing ones without renumbering.', 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 e9b21f1b..2e835572 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 @@ -105,6 +105,9 @@ + +