feat(configurator): menu reorder, currency/unit display polish, line description templates

Three UX improvements:

1. Sales menu reordered — New Quote (seq 1) is now the first entry,
   followed by New Direct Order (5), Quotations (10), Sale Orders (20).
   "New Quote" moved out of Configurator submenu into Sales so both
   quote-creation paths live side-by-side.

2. Currency + unit display audit:
   - fp.customer.price.list.unit_price flipped from Float to Monetary
     with currency_field='currency_id' — list view now shows $ symbol
     and a Total sum row
   - fp.direct.order.wizard.unit_price flipped to Monetary, added
     currency_id field and computed line_subtotal ($)
   - % suffix appended to deposit_percent and progress_initial_percent
     in the wizard
   - Unit suffixes added where missing: bake_window.quantity (pcs),
     window_hours (h), bake_temp (°F), bake_duration_hours (h);
     bath.volume (L), bath.mto_count (turnovers); tank.volume shows
     volume_uom inline

3. Saved line descriptions (new feature):
   - New model fp.sale.description.template with name, description,
     tag (standard/masking/rework/aerospace/nuclear/packaging/other),
     optional coating_config_id and partner_id, usage_count bumped
     on each use
   - List + form + search views; new "Line Descriptions" menu under
     Configurator
   - 8 starter templates seeded (noupdate=1): ENP Standard/Aerospace/
     Nuclear, masking variants, rework, packaging, delicate handling
   - Direct Order Wizard gets a template picker (searchable Many2one)
     + editable paragraph; picking a template copies text to the
     editable field, user tweaks freely, tweaked text lands on the
     SO line as "<header>\n\n<description>"
   - Auto-suggests template on coating+partner match if nothing
     picked yet

Smoke-tested end-to-end: picked aerospace template, tweaked text,
confirmed wizard → SO S00030 has full description on line, usage
counter bumped from 0 to 1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-04-17 18:43:58 -04:00
parent b09538b4e2
commit b85642816f
14 changed files with 406 additions and 38 deletions

View File

@@ -56,10 +56,18 @@ class FpDirectOrderWizard(models.TransientModel):
'fp.coating.config', string='Coating', required=True,
)
quantity = fields.Integer(string='Quantity', required=True, default=1)
unit_price = fields.Float(
string='Unit Price', digits=(12, 2),
currency_id = fields.Many2one(
'res.currency', string='Currency',
default=lambda self: self.env.company.currency_id,
)
unit_price = fields.Monetary(
string='Unit Price', currency_field='currency_id',
help='Negotiated price per part. Leave blank to set later.',
)
line_subtotal = fields.Monetary(
string='Line Subtotal', currency_field='currency_id',
compute='_compute_line_subtotal',
)
rush_order = fields.Boolean(string='Rush Order')
delivery_method = fields.Selection(
[('local_delivery', 'Local Delivery'),
@@ -86,6 +94,24 @@ class FpDirectOrderWizard(models.TransientModel):
notes = fields.Text(string='Internal Notes')
# Description template picker (searchable / ajax-like via Odoo Many2one)
description_template_id = fields.Many2one(
'fp.sale.description.template',
string='Description Template',
help='Pick a saved boilerplate description and tweak it below. '
'The final text lands on the sale order line.',
)
line_description = fields.Text(
string='Line Description',
help='This text becomes the description of the sale order line. '
'Edit freely — your changes override the template.',
)
@api.depends('quantity', 'unit_price')
def _compute_line_subtotal(self):
for rec in self:
rec.line_subtotal = (rec.quantity or 0) * (rec.unit_price or 0.0)
@api.onchange('partner_id')
def _onchange_partner_id(self):
"""Reset part selection when customer changes + pull invoice defaults."""
@@ -94,6 +120,35 @@ class FpDirectOrderWizard(models.TransientModel):
self.invoice_strategy = self.partner_id.x_fc_default_invoice_strategy or False
self.deposit_percent = self.partner_id.x_fc_default_deposit_percent or 0.0
@api.onchange('description_template_id')
def _onchange_description_template(self):
"""Copy the template's text into the editable paragraph — user tweaks from there."""
if self.description_template_id:
self.line_description = self.description_template_id.description
@api.onchange('coating_config_id', 'partner_id')
def _onchange_suggest_template(self):
"""Offer a sensible default template based on coating + customer."""
if self.description_template_id or self.line_description:
return # respect user's choice
if not self.coating_config_id:
return
Template = self.env['fp.sale.description.template']
# Prefer customer+coating match, then coating-only
match = Template.search([
('active', '=', True),
('partner_id', '=', self.partner_id.id if self.partner_id else False),
('coating_config_id', '=', self.coating_config_id.id),
], limit=1)
if not match:
match = Template.search([
('active', '=', True),
('coating_config_id', '=', self.coating_config_id.id),
], limit=1)
if match:
self.description_template_id = match.id
self.line_description = match.description
@api.onchange('coating_config_id', 'quantity', 'partner_id')
def _onchange_lookup_price(self):
"""Auto-fill unit_price from customer price list when available."""
@@ -169,12 +224,23 @@ class FpDirectOrderWizard(models.TransientModel):
'purchase_ok': False,
})
line_desc = '%s%s Rev %s (x%d)' % (
# Canonical line label (always present)
header = '%s%s Rev %s (x%d)' % (
self.coating_config_id.name,
part.name,
part.revision or part.revision_number,
self.quantity,
)
# Optional extended description from template / user tweak
extended = (self.line_description or '').strip()
if extended:
line_desc = '%s\n\n%s' % (header, extended)
else:
line_desc = header
# Bump template usage counter so popular ones float to the top over time
if self.description_template_id:
self.description_template_id._register_usage()
so_vals = {
'partner_id': self.partner_id.id,

View File

@@ -35,8 +35,11 @@
<field name="current_revision" invisible="not part_catalog_id"/>
</group>
<group>
<field name="surface_area" invisible="not part_catalog_id"/>
<field name="surface_area_uom" invisible="not part_catalog_id"/>
<label for="surface_area" invisible="not part_catalog_id"/>
<div class="o_row" invisible="not part_catalog_id">
<field name="surface_area" nolabel="1" class="oe_inline"/>
<field name="surface_area_uom" nolabel="1" class="oe_inline"/>
</div>
</group>
</group>
@@ -54,7 +57,11 @@
<group string="Order">
<field name="coating_config_id"/>
<field name="quantity"/>
<field name="unit_price"/>
<field name="currency_id" invisible="1"/>
<field name="unit_price" widget="monetary"
options="{'currency_field': 'currency_id'}"/>
<field name="line_subtotal" widget="monetary"
options="{'currency_field': 'currency_id'}"/>
</group>
<group string="Fulfilment">
<field name="rush_order"/>
@@ -65,15 +72,35 @@
<group string="Invoicing">
<group>
<field name="invoice_strategy"/>
<field name="deposit_percent"
<label for="deposit_percent"
invisible="invoice_strategy != 'deposit'"/>
<field name="progress_initial_percent"
<div class="o_row"
invisible="invoice_strategy != 'deposit'">
<field name="deposit_percent" nolabel="1" class="oe_inline"/>
<span class="ms-1">%</span>
</div>
<label for="progress_initial_percent"
invisible="invoice_strategy != 'progress'"/>
<div class="o_row"
invisible="invoice_strategy != 'progress'">
<field name="progress_initial_percent" nolabel="1" class="oe_inline"/>
<span class="ms-1">%</span>
</div>
</group>
</group>
<group string="Notes">
<field name="notes" nolabel="1" colspan="2" placeholder="Internal notes..."/>
<!-- ===== Line description — template picker + editable paragraph ===== -->
<group string="Line Description">
<field name="description_template_id"
options="{'no_create': True, 'no_open': True}"
placeholder="Start typing to search saved descriptions..."/>
<field name="line_description" nolabel="1" colspan="2"
placeholder="Pick a template above, then tweak the text here. Whatever you leave in this box lands on the sale order line."/>
</group>
<group string="Internal Notes">
<field name="notes" nolabel="1" colspan="2"
placeholder="Notes for the estimator / planner — not shown to the customer."/>
</group>
</sheet>