This commit is contained in:
gsinghpal
2026-05-01 00:20:40 -04:00
parent bdcbd86db2
commit 1da27ed6bf
8 changed files with 117 additions and 12 deletions

View File

@@ -5,7 +5,7 @@
{ {
'name': 'Fusion Plating', 'name': 'Fusion Plating',
'version': '19.0.18.8.0', 'version': '19.0.18.11.0',
'category': 'Manufacturing/Plating', 'category': 'Manufacturing/Plating',
'summary': 'Core plating / metal finishing ERP: facilities, processes, tanks, baths, jobs, operators.', 'summary': 'Core plating / metal finishing ERP: facilities, processes, tanks, baths, jobs, operators.',
'description': """ 'description': """

View File

@@ -546,15 +546,23 @@ export class RecipeTreeEditor extends Component {
// ---- Navigation --------------------------------------------------------- // ---- Navigation ---------------------------------------------------------
onBackToList() { onBackToList() {
// If the editor was opened from the part-scoped Process Composer // Pop this editor off the action stack and restore the
// (context carried part_id), return to that part's form instead // previous controller — which is whatever opened the editor:
// of the generic Recipes list. // * Recipes list → recipe form → editor ⇒ back to recipe form
// * Part form → composer → editor ⇒ back to composer
// * Part form → editor (direct link) ⇒ back to part form
// //
// clearBreadcrumbs: this is a semantic RETURN, not a forward // restore() preserves the full breadcrumb trail.
// navigation. Without it, every round-trip (part → composer → // clearBreadcrumbs: true (the old behaviour) would wipe parent
// editor → back) leaves its intermediate pages on the breadcrumb // crumbs and isolate the user on a single-crumb page.
// stack, so a second visit shows "…/Process Composer/Process try {
// Editor/Process Composer/Process Editor/Part" nonsense. this.action.restore();
return;
} catch (e) {
// No prior controller — fall through to a sensible default.
}
// Fallback: when opened directly via URL with no prior crumb,
// pick the most contextual landing page we have.
if (this._partId) { if (this._partId) {
this.action.doAction({ this.action.doAction({
type: "ir.actions.act_window", type: "ir.actions.act_window",

View File

@@ -196,6 +196,17 @@ export class FpSimpleRecipeEditor extends Component {
* breadcrumb stack so a second visit shows nonsense. * breadcrumb stack so a second visit shows nonsense.
*/ */
onBackToList() { onBackToList() {
// Pop this editor off the action stack and restore the
// previous controller — preserves the full breadcrumb trail
// (Recipes > LGPS1104 > Editor → back keeps "Recipes"
// visible; Part > Composer > Editor → back returns to the
// Composer with crumbs intact).
try {
this.action.restore();
return;
} catch (e) {
// No prior controller — fall through to a sensible default.
}
if (this._partId) { if (this._partId) {
this.action.doAction( this.action.doAction(
{ {

View File

@@ -51,7 +51,15 @@
class="oe_stat_button" icon="fa-sitemap" class="oe_stat_button" icon="fa-sitemap"
invisible="node_type != 'recipe'"> invisible="node_type != 'recipe'">
<field name="child_count" widget="statinfo" <field name="child_count" widget="statinfo"
string="Steps"/> string="Tree Editor"/>
</button>
<button name="action_open_simple_editor" type="object"
class="oe_stat_button" icon="fa-list-ol"
invisible="node_type != 'recipe'">
<div class="o_stat_info">
<span class="o_stat_text">Simple</span>
<span class="o_stat_text">Editor</span>
</div>
</button> </button>
</div> </div>
<widget name="web_ribbon" title="Archived" <widget name="web_ribbon" title="Archived"

View File

@@ -5,7 +5,7 @@
{ {
'name': 'Fusion Plating — Configurator', 'name': 'Fusion Plating — Configurator',
'version': '19.0.18.6.0', 'version': '19.0.18.8.0',
'category': 'Manufacturing/Plating', 'category': 'Manufacturing/Plating',
'summary': 'Quotation configurator with part catalog, coating configs, and formula-based pricing engine.', 'summary': 'Quotation configurator with part catalog, coating configs, and formula-based pricing engine.',
'description': """ 'description': """

View File

@@ -9,7 +9,7 @@ to depend on the configurator. Any field that references a model defined
in configurator — like fp.pricing.rule, fp.part.catalog — must be in configurator — like fp.pricing.rule, fp.part.catalog — must be
declared here. declared here.
""" """
from odoo import fields, models from odoo import api, fields, models, _
class FpProcessNode(models.Model): class FpProcessNode(models.Model):
@@ -73,3 +73,47 @@ class FpProcessNode(models.Model):
help='Friendly label shown in the variant picker ' help='Friendly label shown in the variant picker '
'(e.g. "Standard ENP", "Selective Masking", "Rework").', '(e.g. "Standard ENP", "Selective Masking", "Rework").',
) )
# ---- Linked Parts (cloned recipes) --------------------------------------
# On a shared template recipe, count + open all part-cloned recipe
# roots that were copied from this template (cloned_from_id == self).
# Only meaningful on shared templates (part_catalog_id IS NULL,
# node_type='recipe').
cloned_recipe_count = fields.Integer(
string='Linked Part Recipes',
compute='_compute_cloned_recipe_count',
)
def _compute_cloned_recipe_count(self):
Node = self.env['fusion.plating.process.node']
groups = Node._read_group(
domain=[
('cloned_from_id', 'in', self.ids),
('node_type', '=', 'recipe'),
('part_catalog_id', '!=', False),
],
groupby=['cloned_from_id'],
aggregates=['__count'],
)
counts = {src.id: count for src, count in groups}
for rec in self:
rec.cloned_recipe_count = counts.get(rec.id, 0)
def action_open_cloned_recipes(self):
"""Open the list of part-cloned recipe roots that came from this
template (i.e. cloned_from_id == self)."""
self.ensure_one()
return {
'type': 'ir.actions.act_window',
'name': _('Linked Parts — %s', self.name),
'res_model': 'fusion.plating.process.node',
'view_mode': 'list,form',
'domain': [
('cloned_from_id', '=', self.id),
('node_type', '=', 'recipe'),
('part_catalog_id', '!=', False),
],
'context': {
'search_default_group_part': 1,
},
}

View File

@@ -217,6 +217,18 @@ export class FpPartProcessComposer extends Component {
} }
backToPart() { backToPart() {
// Pop this composer off the action stack and restore the
// previous controller (the part form the user came from).
// Preserves the full breadcrumb trail — clearBreadcrumbs: true
// would wipe parent crumbs (e.g. "Parts > 2144A6201-105").
// Falls back to the part form only when restore() throws (e.g.
// composer opened directly via URL with no prior crumb).
try {
this.action.restore();
return;
} catch (e) {
// No prior controller — fall through to the part form.
}
this.action.doAction({ this.action.doAction({
type: "ir.actions.act_window", type: "ir.actions.act_window",
res_model: "fp.part.catalog", res_model: "fp.part.catalog",

View File

@@ -42,6 +42,28 @@
</field> </field>
</record> </record>
<!-- ========== Extend form view: Linked Parts smart button ========== -->
<!-- Smart button visible only on shared template recipes (no
part_catalog_id). Opens the list of part-cloned recipe roots
that were copied from this template. -->
<record id="view_fp_process_node_form_linked_parts"
model="ir.ui.view">
<field name="name">fusion.plating.process.node.form.linked.parts</field>
<field name="model">fusion.plating.process.node</field>
<field name="inherit_id"
ref="fusion_plating.view_fp_process_node_form"/>
<field name="arch" type="xml">
<xpath expr="//div[@name='button_box']" position="inside">
<button name="action_open_cloned_recipes" type="object"
class="oe_stat_button" icon="fa-link"
invisible="node_type != 'recipe' or part_catalog_id">
<field name="cloned_recipe_count" widget="statinfo"
string="Linked Parts"/>
</button>
</xpath>
</field>
</record>
<!-- ========== Extend list view: surface part column ========== --> <!-- ========== Extend list view: surface part column ========== -->
<record id="view_fp_process_node_tree_part_scoped" <record id="view_fp_process_node_tree_part_scoped"
model="ir.ui.view"> model="ir.ui.view">