fix(configurator): part-level saved descriptions (not generic)

The earlier description templates were global — same 8 generic texts
applied to any part. That's useless when a customer has 3,500 parts
and each part has 3–5 canned variants (standard, masked threads,
masked bore, rework, rush packaging). That's ~17,500 rows total, and
the variants ONLY make sense in the context of a specific part number.

Restructured so descriptions live on each part:

Model changes:
  fp.sale.description.template.part_catalog_id (new M2O, indexed,
    ondelete cascade) — the primary scoping field
  fp.sale.description.template.partner_id — now a related store=True
    field pulled from the part, so customer-level search still works
  fp.part.catalog.description_template_ids (new O2M inverse) — the
    5–10 canned descriptions attached to this specific part
  fp.part.catalog.description_template_count (computed)

UI changes:
  Part Catalog form: new "Descriptions" notebook page with inline
    editable list (sequence + name + tag + description + usage_count).
    5 variants take 30 seconds to enter.
  Part Catalog form: new smart button "Descriptions" showing the count,
    jumps to the full list filtered by this part.
  Template list view: part_catalog_id column added, list ordered by
    part first. Search view adds Part filter + Part-Specific /
    Generic (No Part) filters + Group By Part.

Wizard changes:
  description_template_id domain now prioritises part-specific, falls
    through to partner, coating, or generic on a single dynamic domain.
  _onchange_suggest_template priority: part → customer → coating →
    none. No longer auto-picks a random global template when a part
    has its own.

Smoke-tested on VS-HSA201-B (Amphenol):
  5 canned variants seeded on the part form
  Wizard with this part auto-suggested the lowest-sequence one
  The part's Descriptions smart button shows "5"

Bulk data entry path for the client's 3,500 parts: either use the
inline list on each part form, or import via CSV with the new
part_catalog_id column (external_id or DB id).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-04-18 17:46:53 -04:00
parent f340c87b6a
commit a660f1f05d
5 changed files with 144 additions and 38 deletions

View File

@@ -76,6 +76,13 @@
invisible="revision_count &lt; 2">
<field name="revision_count" widget="statinfo" string="Revisions"/>
</button>
<button name="%(action_fp_sale_description_template)d"
type="action"
class="oe_stat_button"
icon="fa-file-text-o"
context="{'search_default_part_catalog_id': id, 'default_part_catalog_id': id}">
<field name="description_template_count" widget="statinfo" string="Descriptions"/>
</button>
</div>
<widget name="web_ribbon" title="Archived" bg_color="text-bg-danger" invisible="active"/>
<widget name="web_ribbon" title="Superseded" bg_color="text-bg-warning" invisible="is_latest_revision"/>
@@ -181,6 +188,28 @@
<field name="model_attachment_id" widget="fp_3d_preview" nolabel="1"/>
</div>
</page>
<page string="Descriptions" name="descriptions">
<p class="text-muted">
Canned descriptions for this part. Whichever one the
estimator picks on the order wizard lands on the SO
line — they can tweak the text before confirming.
Typically 35 variants per part (e.g. <em>Standard</em>,
<em>With threaded holes masked</em>, <em>Special
packaging</em>).
</p>
<field name="description_template_ids"
context="{'default_part_catalog_id': id, 'default_partner_id': partner_id}">
<list editable="bottom">
<field name="sequence" widget="handle"/>
<field name="name" placeholder="e.g. Standard, or With masking, etc."/>
<field name="tag"/>
<field name="description"
placeholder="Full description text that lands on the order line..."/>
<field name="usage_count" readonly="1" optional="show"/>
<field name="active" widget="boolean_toggle"/>
</list>
</field>
</page>
<page string="Revision History" name="revisions"
invisible="not parent_part_id and not revision_ids">
<field name="revision_ids" mode="list">

View File

@@ -14,14 +14,15 @@
<field name="arch" type="xml">
<list multi_edit="1">
<field name="sequence" widget="handle"/>
<field name="part_catalog_id" optional="show"/>
<field name="name"/>
<field name="tag" widget="badge"
decoration-info="tag == 'standard'"
decoration-warning="tag == 'masking'"
decoration-danger="tag == 'rework'"
decoration-success="tag in ('aerospace','nuclear')"/>
<field name="coating_config_id" optional="show"/>
<field name="partner_id" optional="show"/>
<field name="coating_config_id" optional="hide"/>
<field name="usage_count" string="Used"/>
<field name="active" widget="boolean_toggle"/>
</list>
@@ -40,11 +41,14 @@
</div>
<group>
<group>
<field name="part_catalog_id"/>
<field name="partner_id" readonly="part_catalog_id"/>
<field name="tag"/>
<field name="coating_config_id"/>
</group>
<group>
<field name="partner_id"/>
<field name="coating_config_id"
help="Only used for generic (no-part) templates."
invisible="part_catalog_id"/>
<field name="sequence"/>
<field name="usage_count" readonly="1"/>
<field name="active" widget="boolean_toggle"/>
@@ -66,15 +70,22 @@
<search>
<field name="name"/>
<field name="description"/>
<field name="part_catalog_id"/>
<field name="coating_config_id"/>
<field name="partner_id"/>
<field name="tag"/>
<filter name="active" string="Active" domain="[('active','=',True)]"/>
<filter name="with_part" string="Part-Specific"
domain="[('part_catalog_id','!=',False)]"/>
<filter name="no_part" string="Generic (No Part)"
domain="[('part_catalog_id','=',False)]"/>
<group>
<filter name="group_part" string="Part"
context="{'group_by': 'part_catalog_id'}"/>
<filter name="group_customer" string="Customer"
context="{'group_by': 'partner_id'}"/>
<filter name="group_tag" string="Category"
context="{'group_by': 'tag'}"/>
<filter name="group_coating" string="Coating"
context="{'group_by': 'coating_config_id'}"/>
</group>
</search>
</field>