fix(simple-editor): also surface step children of operations
Follow-up to821e768b. The previous fix flattened sub_process nodes so all 16 operations of ENP-STEEL-BASIC became visible — but the Tree Editor also shows the 26 `step` nodes that live under each operation ("Ready For Blast / Blast", "Soak Clean / Electroclean / Primary Rinse", etc.). The Simple Editor still hid those, so author + Tree Editor still disagreed by 26 rows. New `_flatten_recipe_nodes(recipe)` helper walks DFS and surfaces BOTH operations and their step children. Each operation is followed immediately by its step children in sequence order so the editor renders them as a contiguous block: 10. Ready For Steel Line 11. Cleaner [Steel Line] ↳ Soak Clean (S-3) [Steel Line › Cleaner] ↳ Electroclean (S-3) [Steel Line › Cleaner] ↳ Primary Rinse (S-4) [Steel Line › Cleaner] 15. Acid Dip (S-5) [Steel Line] ↳ Primary Rinse (S-6) [Steel Line › Acid Dip (S-5)] ... Payload additions on each step: - `node_type`: 'operation' | 'step' - `is_substep`: True for steps (renders indented) - `nested_under`: chained path (sub-process › operation for substeps, sub-process for nested operations, '' for top-level operations) UI: substep rows are indented 2.5rem, smaller font, no drag handle, no numeric position. The "↳" indent glyph and a "[parent operation]" chip make the parent-child relationship obvious. Substeps are not draggable to keep the existing reorder semantics simple — Tree Editor remains the home for structural changes. Legacy `_flatten_recipe_operations` helper retained for back-compat (it now delegates by filtering `node.node_type == 'operation'` from the full walk). ENP-STEEL-BASIC on entech: Simple Editor now shows 42 rows (was 10 before821e768b, was 16 after821e768b) — matches what the Tree Editor displays exactly. Tests: 10 total (was 7), 3 new cover the substep surfacing, path chaining, and is_substep / node_type flags on the payload. Module: fusion_plating 19.0.20.3.0 → 19.0.20.4.0. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -97,10 +97,26 @@ class TestSimpleRecipeFlatten(TransactionCase):
|
||||
self.assertEqual(node_types, {'operation'})
|
||||
# Recipe + sub_process never appear as Simple Editor rows.
|
||||
|
||||
def test_step_children_of_operations_are_not_surfaced(self):
|
||||
# Operations carry `step` children as their internal
|
||||
# instructions. The flat list must NOT emit them as separate
|
||||
# rows.
|
||||
def test_operations_only_helper_skips_step_children(self):
|
||||
# Back-compat: the legacy _flatten_recipe_operations helper
|
||||
# still returns ONLY operations. New callers should use
|
||||
# _flatten_recipe_nodes for the full list (operations + steps).
|
||||
self.env['fusion.plating.process.node'].create({
|
||||
'name': 'Substep 1', 'node_type': 'step',
|
||||
'parent_id': self.op_a.id, 'sequence': 10,
|
||||
})
|
||||
ops = self._flatten()
|
||||
names = [n.name for n, _ in ops]
|
||||
self.assertNotIn('Substep 1', names)
|
||||
self.assertEqual(names, ['Op A', 'Op B', 'Op C', 'Op D'])
|
||||
|
||||
def test_full_nodes_helper_surfaces_step_children(self):
|
||||
# The Simple Editor's load endpoint uses _flatten_recipe_nodes,
|
||||
# which DOES surface step children. They're emitted right after
|
||||
# their parent operation so the editor renders them as a
|
||||
# contiguous block.
|
||||
from odoo.addons.fusion_plating.controllers.simple_recipe_controller \
|
||||
import SimpleRecipeController
|
||||
self.env['fusion.plating.process.node'].create({
|
||||
'name': 'Substep 1', 'node_type': 'step',
|
||||
'parent_id': self.op_a.id, 'sequence': 10,
|
||||
@@ -109,12 +125,54 @@ class TestSimpleRecipeFlatten(TransactionCase):
|
||||
'name': 'Substep 2', 'node_type': 'step',
|
||||
'parent_id': self.op_a.id, 'sequence': 20,
|
||||
})
|
||||
ops = self._flatten()
|
||||
names = [n.name for n, _ in ops]
|
||||
self.assertNotIn('Substep 1', names)
|
||||
self.assertNotIn('Substep 2', names)
|
||||
# The original 4 operations are still there.
|
||||
self.assertEqual(names, ['Op A', 'Op B', 'Op C', 'Op D'])
|
||||
nodes = SimpleRecipeController()._flatten_recipe_nodes(self.recipe)
|
||||
names = [n.name for n, _ in nodes]
|
||||
# Substeps appear immediately after Op A, before Op B.
|
||||
self.assertEqual(
|
||||
names,
|
||||
['Op A', 'Substep 1', 'Substep 2',
|
||||
'Op B', 'Op C', 'Op D'],
|
||||
)
|
||||
|
||||
def test_substeps_carry_parent_operation_in_path(self):
|
||||
from odoo.addons.fusion_plating.controllers.simple_recipe_controller \
|
||||
import SimpleRecipeController
|
||||
self.env['fusion.plating.process.node'].create({
|
||||
'name': 'My Substep', 'node_type': 'step',
|
||||
'parent_id': self.op_b.id, 'sequence': 10,
|
||||
})
|
||||
nodes = SimpleRecipeController()._flatten_recipe_nodes(self.recipe)
|
||||
paths = {n.name: p for n, p in nodes}
|
||||
# Op B lives in Sub-X; its substep's path chains both.
|
||||
self.assertEqual(paths['My Substep'], 'Sub-X › Op B')
|
||||
|
||||
def test_load_payload_marks_substeps_with_is_substep(self):
|
||||
# End-to-end check on the load endpoint payload: substeps get
|
||||
# `is_substep=True` and `node_type='step'` so the UI can render
|
||||
# them as indented sub-rows.
|
||||
from odoo.addons.fusion_plating.controllers.simple_recipe_controller \
|
||||
import SimpleRecipeController
|
||||
self.env['fusion.plating.process.node'].create({
|
||||
'name': 'A1', 'node_type': 'step',
|
||||
'parent_id': self.op_a.id, 'sequence': 10,
|
||||
})
|
||||
# Mock the request — load() reads request.env.
|
||||
from unittest.mock import patch
|
||||
ctrl = SimpleRecipeController()
|
||||
class FakeReq:
|
||||
env = self.env
|
||||
path_to_request = (
|
||||
'odoo.addons.fusion_plating.controllers.'
|
||||
'simple_recipe_controller.request'
|
||||
)
|
||||
with patch(path_to_request, FakeReq()):
|
||||
payload = ctrl.load(self.recipe.id)
|
||||
by_name = {s['name']: s for s in payload['steps']}
|
||||
self.assertEqual(by_name['Op A']['node_type'], 'operation')
|
||||
self.assertFalse(by_name['Op A']['is_substep'])
|
||||
self.assertEqual(by_name['A1']['node_type'], 'step')
|
||||
self.assertTrue(by_name['A1']['is_substep'])
|
||||
self.assertEqual(by_name['A1']['nested_under'], 'Op A')
|
||||
|
||||
def test_load_endpoint_includes_nested_under_in_payload(self):
|
||||
# Direct call to the controller's load (mirroring the JSONRPC).
|
||||
|
||||
Reference in New Issue
Block a user