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

@@ -9,6 +9,7 @@ from . import fp_coating_config
from . import fp_pricing_complexity_surcharge
from . import fp_pricing_rule
from . import fp_customer_price_list
from . import fp_sale_description_template
from . import fp_quote_configurator
from . import sale_order
from . import res_partner

View File

@@ -31,8 +31,9 @@ class FpCustomerPriceList(models.Model):
'fp.coating.config', string='Coating', required=True, ondelete='restrict',
tracking=True,
)
unit_price = fields.Float(
string='Unit Price', required=True, digits=(12, 4), tracking=True,
unit_price = fields.Monetary(
string='Unit Price', required=True, currency_field='currency_id',
tracking=True,
)
price_uom = fields.Selection(
[('per_part', 'per Part'),

View File

@@ -0,0 +1,62 @@
# -*- coding: utf-8 -*-
# Copyright 2026 Nexa Systems Inc.
# License OPL-1 (Odoo Proprietary License v1.0)
# Part of the Fusion Plating product family.
from odoo import fields, models
class FpSaleDescriptionTemplate(models.Model):
"""Reusable boilerplate descriptions for sale.order.line items.
Plating shops run the same customer part over and over with small
variations (masking rules, special handling, packaging). Instead of
retyping — or half-remembering — the description every time, the
estimator picks a named template here, tweaks it, and the tweaked
text lands on the SO line as its description.
"""
_name = 'fp.sale.description.template'
_description = 'Fusion Plating — Sale Order Line Description Template'
_order = 'sequence, name'
name = fields.Char(
string='Template Name', required=True,
help='Short name shown in the picker (e.g. "ENP — Standard Aluminium").',
)
description = fields.Text(
string='Description', required=True,
help='Boilerplate text. The user can tweak this in the wizard before '
'it lands on the order line.',
)
sequence = fields.Integer(default=10)
coating_config_id = fields.Many2one(
'fp.coating.config', string='Associated Coating',
ondelete='set null',
help='If set, this template is offered first when this coating is '
'chosen on the order.',
)
partner_id = fields.Many2one(
'res.partner', string='Customer (optional)',
ondelete='set null',
help='If set, restrict this template to a specific customer.',
)
tag = fields.Selection(
[('standard', 'Standard'),
('masking', 'Masking / Selective'),
('rework', 'Rework / Strip'),
('aerospace', 'Aerospace'),
('nuclear', 'Nuclear'),
('packaging', 'Special Packaging'),
('other', 'Other')],
string='Category', default='standard',
)
active = fields.Boolean(default=True)
usage_count = fields.Integer(
string='Used', default=0, readonly=True,
help='Bumped each time this template is applied on an order line.',
)
def _register_usage(self):
"""Called by the wizard when the template is applied."""
for rec in self:
rec.usage_count = (rec.usage_count or 0) + 1