From 32d48ea44d818dff18a523640ac5139c4bd3ba65 Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Sun, 3 May 2026 22:58:21 -0400 Subject: [PATCH] feat(jobs): step sequences are 1, 2, 3, ... not 10, 20, 30, ... MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User feedback: operators kept asking why their work order said "Step 10" for the first row. The 10-spacing was originally there to allow midpoint inserts (insert sequence 15 between 10 and 20 without renumbering). Tradeoff is operator confusion, and recipe authors rarely insert in the middle anyway. Switching to 1-based contiguous sequences. Files changed (every step-sequence allocation in the codebase): fusion_plating_jobs/models/fp_job.py _generate_steps_from_recipe — seq_counter starts at 1, increments by 1. This is the path that builds fp.job.step records, so new jobs now show Step 1, 2, 3, ... in the work order. fusion_plating_bridge_mrp/models/mrp_production.py Same change for the legacy MRP bridge so customers still on mrp.production also get 1-based numbering. fusion_plating/controllers/recipe_controller.py - create_node: max_seq + 1 - reorder_nodes: idx + 1 - swap renumber: i (was i * 10) - paste-import renumber: i (was i * 10) - move_node: max_seq + 1 - _copy_subtree (recipe duplicate/import): i (was i * 10) fusion_plating/controllers/simple_recipe_controller.py - _sequence_for_position rewritten — always renumbers siblings to keep them contiguous. Returns pos + 1 for the inserted node. Old code used midpoint-with-fallback-to-renumber (10/20/30 spacing). - step_reorder: i (was i * 10) - library_input_add + step_add_input: existing_max + 1 What this DOESN'T do Existing fp.job.step records keep their old sequences (10, 20, ...). Re-confirm the SO to spawn a fresh job if you want the clean 1-based numbering on a current test job. No data migration — we're in dev and the user explicitly said test data is disposable. What this DOES do Every NEW job created from this commit forward shows Step 1, 2, 3, ... Every NEW recipe step inserted via the simple editor / tree editor also gets sequence 1, 2, 3, ... Co-Authored-By: Claude Opus 4.7 (1M context) --- fusion_plating/fusion_plating/__manifest__.py | 2 +- .../controllers/recipe_controller.py | 14 +++---- .../controllers/simple_recipe_controller.py | 39 ++++++++++--------- .../fusion_plating_bridge_mrp/__manifest__.py | 2 +- .../models/mrp_production.py | 4 +- .../fusion_plating_jobs/__manifest__.py | 2 +- .../fusion_plating_jobs/models/fp_job.py | 9 ++++- 7 files changed, 39 insertions(+), 33 deletions(-) diff --git a/fusion_plating/fusion_plating/__manifest__.py b/fusion_plating/fusion_plating/__manifest__.py index 697d2988..dd796dcc 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.0', + 'version': '19.0.18.12.1', 'category': 'Manufacturing/Plating', 'summary': 'Core plating / metal finishing ERP: facilities, processes, tanks, baths, jobs, operators.', 'description': """ diff --git a/fusion_plating/fusion_plating/controllers/recipe_controller.py b/fusion_plating/fusion_plating/controllers/recipe_controller.py index a3f988b6..b69f6a56 100644 --- a/fusion_plating/fusion_plating/controllers/recipe_controller.py +++ b/fusion_plating/fusion_plating/controllers/recipe_controller.py @@ -53,7 +53,7 @@ class FpRecipeController(http.Controller): 'name': name, 'node_type': node_type, 'parent_id': parent.id, - 'sequence': max_seq + 10, + 'sequence': max_seq + 1, } if vals: data.update(vals) @@ -132,7 +132,7 @@ class FpRecipeController(http.Controller): Node = request.env['fusion.plating.process.node'] try: for idx, nid in enumerate(node_ids): - Node.browse(int(nid)).write({'sequence': (idx + 1) * 10}) + Node.browse(int(nid)).write({'sequence': idx + 1}) return {'ok': True} except Exception as exc: _logger.exception('Recipe reorder failed') @@ -195,7 +195,7 @@ class FpRecipeController(http.Controller): if a_seq == b_seq: # Sequences collided — renumber everyone cleanly, then swap for i, s in enumerate(siblings, 1): - s.sequence = i * 10 + s.sequence = i a_seq, b_seq = node.sequence, other.sequence node.sequence, other.sequence = b_seq, a_seq return {'ok': True} @@ -260,7 +260,7 @@ class FpRecipeController(http.Controller): vals['sequence'] = base_seq new_node = Node.create(vals) for i, child in enumerate(src_node.child_ids.sorted('sequence'), 1): - _copy_subtree(child, new_node, i * 10) + _copy_subtree(child, new_node, i) return new_node # Phase 1 — create every copied top-level child, tracking their @@ -308,8 +308,8 @@ class FpRecipeController(http.Controller): + existing_top[anchor_idx:] ) for i, node in enumerate(final_order, 1): - if node.sequence != i * 10: - node.sequence = i * 10 + if node.sequence != i: + node.sequence = i return { 'ok': True, @@ -341,7 +341,7 @@ class FpRecipeController(http.Controller): max_seq = max((c.sequence for c in parent.child_ids), default=0) node.write({ 'parent_id': parent.id, - 'sequence': max_seq + 10, + 'sequence': max_seq + 1, }) return {'ok': True} except Exception as exc: diff --git a/fusion_plating/fusion_plating/controllers/simple_recipe_controller.py b/fusion_plating/fusion_plating/controllers/simple_recipe_controller.py index f6c3f1b6..de11edb2 100644 --- a/fusion_plating/fusion_plating/controllers/simple_recipe_controller.py +++ b/fusion_plating/fusion_plating/controllers/simple_recipe_controller.py @@ -271,7 +271,7 @@ class SimpleRecipeController(http.Controller): 'template_id': tpl.id, 'name': (payload or {}).get('name') or 'New Prompt', 'input_type': (payload or {}).get('input_type') or 'text', - 'sequence': existing_max + 10, + 'sequence': existing_max + 1, 'required': bool((payload or {}).get('required')), }) return {'ok': True, 'input_id': rec.id, @@ -356,25 +356,26 @@ class SimpleRecipeController(http.Controller): return {'id': new_node.id, 'sequence': new_node.sequence} def _sequence_for_position(self, recipe, position): + """Return the sequence value for a NEW step inserted at + `position` among the recipe's existing children. + + Always renumbers existing siblings so the result is contiguous + 1, 2, 3, ... matching what the operator sees on the work order. + (Pre-Sub 13c we used 10-spacing to allow midpoint inserts — + operators kept asking why their first step said "Step 10".) + """ siblings = recipe.child_ids.sorted('sequence') if not siblings: - return 10 - if position >= len(siblings): - return siblings[-1].sequence + 10 - if position <= 0: - return max(1, siblings[0].sequence - 10) - before = siblings[position - 1].sequence - after = siblings[position].sequence - if after - before > 1: - return (before + after) // 2 - # Sequences are tightly packed (gap == 1 → midpoint == after, - # which collides). Renumber siblings to 10/20/30… first, then - # the new step lands cleanly between renumbered neighbours. + return 1 + pos = max(0, min(position, len(siblings))) + # Make room: siblings before `pos` keep their 1-based index; + # siblings at or after `pos` shift up by one so the new step + # lands at sequence (pos + 1). for idx, sib in enumerate(siblings): - new_seq = (idx + 1) * 10 - if sib.sequence != new_seq: - sib.sequence = new_seq - return position * 10 + 5 + target = idx + 1 if idx < pos else idx + 2 + if sib.sequence != target: + sib.sequence = target + return pos + 1 def _copy_inputs_from_template(self, tpl, new_node): NodeInput = request.env['fusion.plating.process.node.input'] @@ -412,7 +413,7 @@ class SimpleRecipeController(http.Controller): def step_reorder(self, node_ids): Node = request.env['fusion.plating.process.node'] for i, nid in enumerate(node_ids, start=1): - Node.browse(nid).write({'sequence': i * 10}) + Node.browse(nid).write({'sequence': i}) return {'ok': True} # -------------------------------------------------------------- template @@ -521,7 +522,7 @@ class SimpleRecipeController(http.Controller): 'input_type': (payload or {}).get('input_type') or 'text', 'kind': 'step_input', 'collect': True, - 'sequence': existing_max + 10, + 'sequence': existing_max + 1, 'required': bool((payload or {}).get('required')), }) return {'ok': True, 'input_id': rec.id} diff --git a/fusion_plating/fusion_plating_bridge_mrp/__manifest__.py b/fusion_plating/fusion_plating_bridge_mrp/__manifest__.py index 8be6e557..cd82356a 100644 --- a/fusion_plating/fusion_plating_bridge_mrp/__manifest__.py +++ b/fusion_plating/fusion_plating_bridge_mrp/__manifest__.py @@ -5,7 +5,7 @@ { "name": "Fusion Plating — MRP Bridge", - 'version': '19.0.13.0.0', + 'version': '19.0.13.0.1', 'category': 'Manufacturing/Plating', 'summary': 'Bridge Fusion Plating facilities, baths and tanks to Odoo MRP work orders.', 'description': """ diff --git a/fusion_plating/fusion_plating_bridge_mrp/models/mrp_production.py b/fusion_plating/fusion_plating_bridge_mrp/models/mrp_production.py index adfb9d66..7416d3b3 100644 --- a/fusion_plating/fusion_plating_bridge_mrp/models/mrp_production.py +++ b/fusion_plating/fusion_plating_bridge_mrp/models/mrp_production.py @@ -572,7 +572,7 @@ class MrpProduction(models.Model): # Walk tree and collect operation WO values wo_vals_list = [] wo_steps = {} # {sequence: instruction text} — posted to WO chatter after create - seq_counter = [10] # mutable for closure, increments by 10 + seq_counter = [1] # mutable for closure, increments by 1 def _is_node_included(node): """Determine if a node should be included based on opt-in/out @@ -695,7 +695,7 @@ class MrpProduction(models.Model): wo_vals_list.append(vals) if steps: wo_steps[seq_counter[0]] = '\n'.join(steps) - seq_counter[0] += 10 + seq_counter[0] += 1 elif node.node_type in ('recipe', 'sub_process'): # Container nodes — recurse into children diff --git a/fusion_plating/fusion_plating_jobs/__manifest__.py b/fusion_plating/fusion_plating_jobs/__manifest__.py index 39c16d6e..8144a01e 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.17.3', + 'version': '19.0.8.17.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 3e72606a..61d56041 100644 --- a/fusion_plating/fusion_plating_jobs/models/fp_job.py +++ b/fusion_plating/fusion_plating_jobs/models/fp_job.py @@ -606,7 +606,12 @@ class FpJob(models.Model): step_vals_list = [] wo_steps = {} # {sequence: instruction text} - seq_counter = [10] + # Sequences increment by 1 (operator-friendly: Step 1, 2, 3, + # ...) instead of the legacy 10/20/30 spacing. The 10-spacing + # was originally there to allow midpoint inserts, but + # operators kept asking why their work order said "Step 10" + # for the first row. + seq_counter = [1] def _is_node_included(node): """Determine if a node should be included based on @@ -759,7 +764,7 @@ class FpJob(models.Model): step_vals_list.append(vals) if instructions: wo_steps[seq_counter[0]] = '\n'.join(instructions) - seq_counter[0] += 10 + seq_counter[0] += 1 elif node.node_type in ('recipe', 'sub_process'): for child in node.child_ids.sorted('sequence'):