feat(configurator): Sub 3 Phase B — part-scoped Process Composer client action + part form Compose button

- Add fp_part_composer_controller with 3 JSON-RPC endpoints:
  /fp/part/composer/state, /fp/part/composer/templates,
  /fp/part/composer/load_template (deep-clones a shared template
  into a part-owned tree inside a cr.savepoint, sets
  fp.part.catalog.default_process_id atomically)
- _clone_subtree copies name/sequence/opt_in_out/treatment_uom plus
  description/notes/icon/color/timing/behaviour/work_center/process_type
  and stamps part_catalog_id + cloned_from_id on every node
- Add fp_part_process_composer OWL client action (JS + XML + SCSS):
  picks template from dropdown, clones, hands off to existing
  fp_recipe_tree_editor via context={recipe_id, part_id}
- Add Process tab on part form with readonly default_process_id
  field and Compose button calling action_open_part_composer
- Register new assets in web.assets_backend, bump configurator
  version to 19.0.11.0.0
This commit is contained in:
gsinghpal
2026-04-22 09:02:03 -04:00
parent 7d5c826f3e
commit 3de37ea735
7 changed files with 541 additions and 1 deletions

View File

@@ -0,0 +1,90 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2026 Nexa Systems Inc.
License OPL-1 (Odoo Proprietary License v1.0)
Part of the Fusion Plating product family.
OWL template for the part-scoped Process Composer client action.
-->
<templates xml:space="preserve">
<t t-name="fusion_plating_configurator.FpPartProcessComposer">
<div class="o_fp_part_composer">
<t t-if="state.loading">
<div class="o_fp_part_composer_state">
<i class="fa fa-spinner fa-spin"/>
<span> Loading…</span>
</div>
</t>
<t t-elif="state.error">
<div class="o_fp_part_composer_state o_fp_part_composer_error">
<i class="fa fa-exclamation-triangle"/>
<span t-esc="state.error"/>
</div>
</t>
<t t-elif="state.part">
<div class="o_fp_part_composer_header">
<button class="btn btn-secondary" t-on-click="backToPart">
<i class="fa fa-arrow-left"/>
<span> Back to Part</span>
</button>
<div class="o_fp_part_composer_title">
<h2>Process Composer — <t t-esc="state.part.display"/></h2>
<small class="text-muted" t-if="state.part.customer">
Customer: <t t-esc="state.part.customer"/>
</small>
</div>
</div>
<div class="o_fp_part_composer_loader">
<label>Load Existing Process:</label>
<select class="form-select" t-on-change="onSelectTemplate">
<t t-foreach="state.templates" t-as="tpl" t-key="tpl.id">
<option t-att-value="tpl.id"
t-att-selected="tpl.id == state.selectedTemplateId">
<t t-esc="tpl.name"/>
</option>
</t>
</select>
<button class="btn btn-primary"
t-on-click="onLoadTemplate"
t-att-disabled="state.loadingTemplate or !state.selectedTemplateId">
<t t-if="state.loadingTemplate">
<i class="fa fa-spinner fa-spin"/>
<span> Loading…</span>
</t>
<t t-else="">
<t t-if="state.hasTree">Replace with Selected</t>
<t t-else="">Load</t>
</t>
</button>
</div>
<div class="o_fp_part_composer_tree">
<t t-if="state.hasTree">
<div class="o_fp_part_composer_hint">
<p>This part has a composed process tree. Click below to open the
full tree editor where you can add, remove, reorder, and configure
the process nodes.</p>
<button class="btn btn-primary"
t-on-click="() => this.openRecipeEditor()">
<i class="fa fa-edit"/>
<span> Open Tree Editor</span>
</button>
</div>
</t>
<t t-else="">
<div class="o_fp_part_composer_empty">
<i class="fa fa-cogs fa-3x"/>
<p>No process composed yet.</p>
<p class="text-muted">
Pick a template above and click <strong>Load</strong> to get started.
</p>
</div>
</t>
</div>
</t>
</div>
</t>
</templates>