fix(simple-editor): surface operations nested inside sub_process nodes
Bug on ENP-STEEL-BASIC (2026-05-20): authoring used the Tree Editor to build a recipe with a "Steel Line" sub_process holding 7 nested operations (Cleaner, Acid Dip, Nickel Strike, E-Nickel Plate, etc.). The Simple Editor's /fp/simple_recipe/load endpoint only walked `recipe.child_ids`, so it returned 10 steps. The work order generator (fp.job._generate_steps) walked the same tree depth-first and emitted 16 steps. Author and operator disagreed about what was in the recipe. Fix: new `_flatten_recipe_operations(recipe)` helper walks the tree depth-first, recurses into `recipe` and `sub_process`, emits each `operation` exactly once, skips `step` children (they're sub- instructions of operations). Mirrors the WO walker. Step payload now carries a `nested_under` string — the chained sub- process name(s) the operation lives inside (empty for top-level). The Simple Editor XML renders that as a small "↳ Steel Line" badge next to the step name so the author can see where each row came from in the tree. Deep nesting chains with ' › ' (e.g. "Outer › Inner"). `step` children of `recipe` itself remain invisible — they were silently skipped by the WO generator pre-19.0.18.8.0 anyway (only operation nodes spawn fp.job.step rows). Restoring them here would contradict that long-standing contract. Edit/insert/reorder/remove endpoints unchanged: editing a nested operation's name / description / tanks works (no parent change). Drag-reorder within sub-process siblings still works. Drag across sub-process boundaries isn't supported — opens the door for a Tree Editor follow-up if needed, but the immediate "I can't see my steps" complaint is resolved. ENP-STEEL-BASIC on entech now shows all 16 operations in the Simple Editor (was 10), with the 7 inside Steel Line tagged accordingly. Tests: 7 new (TestSimpleRecipeFlatten) — flat recipes still work, nested operations surface with correct path label, sub_process nodes never appear as editor rows, step children of operations stay hidden, deep-nested sub_processes chain path labels. Module: fusion_plating 19.0.20.2.0 → 19.0.20.3.0. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -63,12 +63,60 @@ class SimpleRecipeController(http.Controller):
|
||||
def load(self, recipe_id):
|
||||
recipe = request.env['fusion.plating.process.node'].browse(recipe_id)
|
||||
recipe.check_access('read')
|
||||
steps = recipe.child_ids.sorted('sequence')
|
||||
# A recipe authored in the Tree Editor can have `sub_process`
|
||||
# nodes that hold more operations underneath. The flat
|
||||
# `recipe.child_ids` walk hid those — operators saw a partial
|
||||
# recipe in the Simple Editor even though the work order
|
||||
# generated the full list (bug surfaced on ENP-STEEL-BASIC,
|
||||
# 2026-05-20).
|
||||
#
|
||||
# Match the WO generator: depth-first, collect every
|
||||
# `operation` node, recurse into `recipe` / `sub_process`,
|
||||
# skip `step` children (they're rendered as instructions
|
||||
# within their parent operation). Each operation gets a
|
||||
# `nested_under` label so the UI can tell the operator which
|
||||
# sub-process the row came from.
|
||||
flat_ops = self._flatten_recipe_operations(recipe)
|
||||
return {
|
||||
'recipe': self._recipe_payload(recipe),
|
||||
'steps': [self._step_payload(s) for s in steps],
|
||||
'steps': [
|
||||
dict(self._step_payload(op), nested_under=path)
|
||||
for op, path in flat_ops
|
||||
],
|
||||
}
|
||||
|
||||
def _flatten_recipe_operations(self, recipe):
|
||||
"""Walk a recipe tree DFS, return [(operation_node, path_label)].
|
||||
|
||||
`path_label` is the name of the enclosing `sub_process` if the
|
||||
operation lives inside one, else empty. Used by the Simple
|
||||
Editor to render a "(in Steel Line)" hint next to the step.
|
||||
"""
|
||||
out = []
|
||||
|
||||
def _walk(node, path):
|
||||
if node.node_type == 'operation':
|
||||
out.append((node, path))
|
||||
# Operations don't recurse — child `step` nodes are
|
||||
# the operation's own instructions, not separate
|
||||
# editor rows.
|
||||
return
|
||||
if node.node_type in ('recipe', 'sub_process'):
|
||||
# Recipes themselves carry no path label; sub_process
|
||||
# name becomes the path for nested children.
|
||||
sub_path = (
|
||||
path if node.node_type == 'recipe'
|
||||
else (f"{path} › {node.name}" if path else node.name)
|
||||
)
|
||||
for child in node.child_ids.sorted('sequence'):
|
||||
_walk(child, sub_path)
|
||||
# `step` nodes at the top level are legacy — flat 'step'
|
||||
# children of a recipe were silently skipped by
|
||||
# _generate_steps pre-19.0.18.8.0; we mirror that here.
|
||||
|
||||
_walk(recipe, '')
|
||||
return out
|
||||
|
||||
def _recipe_payload(self, recipe):
|
||||
return {
|
||||
'id': recipe.id,
|
||||
|
||||
Reference in New Issue
Block a user