feat(plating): tree-editor import supports insert-before position

The import feature appended every imported node to the end of the
target recipe. That's wrong for the common case — General Processing
has Shipping as its last operation, so importing an Electroless
Nickel pack should land BEFORE Shipping, not after it. The user
would otherwise have to click Move Up dozens of times.

Controller: /fp/recipe/node/import_children now accepts
insert_before_id:
  null/missing  → append at end (default, unchanged)
  0             → insert at the start
  <positive id> → insert right before that top-level child

Implementation reorders target's top-level children in one pass
after Phase 1 creates the copies (placeholder sequence=0). Phase 2
splits existing vs. new, finds the anchor index in the existing
list, and reassigns sequences 10/20/30/... across the merged list.
Collisions on the old max_seq-based append strategy are eliminated.

JS: state.importInsertBefore drives a new "Insert:" dropdown in the
toolbar with options:
  — At the end — (default)
  — At the start —
  Before <each top-level child name>

Smoke on entech (3-case): insert-before-middle, insert-at-start,
insert-at-end all produce the expected ordering.

fusion_plating → 19.0.7.2.0

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-04-23 08:16:05 -04:00
parent 03f41422de
commit 9d7b7daf5a
4 changed files with 92 additions and 15 deletions

View File

@@ -120,6 +120,11 @@ export class RecipeTreeEditor extends Component {
importRecipeOptions: [],
importRecipeId: null,
importDedupe: true,
// Insertion anchor for the imported nodes. Values:
// "" → append at the end (default)
// "0" → insert at the start
// "<id>" → insert right before that existing top-level child
importInsertBefore: "",
importing: false,
});
@@ -472,11 +477,21 @@ export class RecipeTreeEditor extends Component {
}
if (!this._recipeId) return;
this.state.importing = true;
// Map the UI anchor to the controller's insert_before_id contract:
// "" → null (append at end)
// "0" → 0 (insert at start)
// "<id>" → int (insert before that top-level child)
const rawBefore = this.state.importInsertBefore;
const insertBeforeId =
rawBefore === "" || rawBefore == null
? null
: parseInt(rawBefore, 10);
try {
const result = await rpc("/fp/recipe/node/import_children", {
source_recipe_id: parseInt(this.state.importRecipeId, 10),
target_parent_id: this._recipeId,
dedupe_by_name: !!this.state.importDedupe,
insert_before_id: insertBeforeId,
});
if (result && result.ok) {
this.notification.add(
@@ -485,6 +500,7 @@ export class RecipeTreeEditor extends Component {
);
this.state.importOpen = false;
this.state.importRecipeId = null;
this.state.importInsertBefore = "";
await this.loadTree();
} else {
this.notification.add(result?.error || "Import failed.", { type: "warning" });
@@ -496,6 +512,15 @@ export class RecipeTreeEditor extends Component {
}
}
// Convenience getter for the XML: the list of top-level children
// currently under the open recipe, for the "Insert before" dropdown.
// Falls back to [] on an unloaded tree.
get topLevelChildren() {
const root = this.state.tree;
if (!root || !root.children) return [];
return root.children;
}
// ---- Navigation ---------------------------------------------------------
onBackToList() {

View File

@@ -207,6 +207,17 @@
<option t-att-value="r.id" t-esc="r.name"/>
</t>
</select>
<label class="o_fp_re_import_label">Insert:</label>
<select class="o_fp_re_import_select o_fp_re_import_pos"
t-model="state.importInsertBefore">
<option value="">At the end</option>
<option value="0">At the start</option>
<t t-foreach="topLevelChildren" t-as="c" t-key="c.id">
<option t-att-value="c.id">
Before <t t-esc="c.name"/>
</option>
</t>
</select>
<label class="o_fp_re_import_dedupe">
<input type="checkbox" t-model="state.importDedupe"/>
Skip duplicate names