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:
@@ -67,12 +67,20 @@
|
||||
<field name="tank_id"/>
|
||||
<field name="process_type_id"/>
|
||||
<field name="facility_id" readonly="1"/>
|
||||
<field name="volume"/>
|
||||
<label for="volume"/>
|
||||
<div class="o_row">
|
||||
<field name="volume" nolabel="1" class="oe_inline"/>
|
||||
<span class="ms-1">L</span>
|
||||
</div>
|
||||
</group>
|
||||
<group>
|
||||
<field name="makeup_date"/>
|
||||
<field name="makeup_by_id"/>
|
||||
<field name="mto_count" readonly="1"/>
|
||||
<label for="mto_count"/>
|
||||
<div class="o_row">
|
||||
<field name="mto_count" nolabel="1" readonly="1" class="oe_inline"/>
|
||||
<span class="ms-1">turnovers</span>
|
||||
</div>
|
||||
<field name="last_log_date" readonly="1"/>
|
||||
<field name="last_log_status" readonly="1" widget="badge"
|
||||
decoration-success="last_log_status == 'ok'"
|
||||
|
||||
@@ -64,8 +64,11 @@
|
||||
<page string="Physical">
|
||||
<group>
|
||||
<group>
|
||||
<field name="volume"/>
|
||||
<field name="volume_uom"/>
|
||||
<label for="volume"/>
|
||||
<div class="o_row">
|
||||
<field name="volume" nolabel="1" class="oe_inline"/>
|
||||
<field name="volume_uom" nolabel="1" class="oe_inline"/>
|
||||
</div>
|
||||
<field name="material"/>
|
||||
</group>
|
||||
<group>
|
||||
|
||||
@@ -39,8 +39,6 @@ Provides:
|
||||
'security/ir.model.access.csv',
|
||||
'data/fp_configurator_sequence_data.xml',
|
||||
'data/fp_treatment_data.xml',
|
||||
'wizard/fp_direct_order_wizard_views.xml',
|
||||
'wizard/fp_part_catalog_import_wizard_views.xml',
|
||||
'views/fp_treatment_views.xml',
|
||||
'views/fp_part_catalog_views.xml',
|
||||
'views/fp_coating_config_views.xml',
|
||||
@@ -50,6 +48,10 @@ Provides:
|
||||
'views/sale_order_views.xml',
|
||||
'views/res_partner_views.xml',
|
||||
'views/fp_configurator_menu.xml',
|
||||
'views/fp_sale_description_template_views.xml',
|
||||
'wizard/fp_direct_order_wizard_views.xml',
|
||||
'wizard/fp_part_catalog_import_wizard_views.xml',
|
||||
'data/fp_sale_description_template_data.xml',
|
||||
],
|
||||
'assets': {
|
||||
'web.assets_backend': [
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
<?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.
|
||||
|
||||
Eight starter description templates covering the common things
|
||||
estimators write on SO lines. noupdate="1" so customers can edit
|
||||
freely without upgrades clobbering their changes.
|
||||
-->
|
||||
<odoo noupdate="1">
|
||||
|
||||
<record id="desc_tpl_enp_standard" model="fp.sale.description.template">
|
||||
<field name="name">ENP — Standard (AMS 2404 Class I)</field>
|
||||
<field name="tag">standard</field>
|
||||
<field name="sequence">10</field>
|
||||
<field name="description">Electroless nickel plating per AMS 2404, Class I, Type II (medium phosphorus, 6–9%). Plate to 0.0005" thickness, heat-treat 4 hours @ 375°F for hydrogen embrittlement relief. Parts to be cleaned, deoxidised and activated prior to plating. All threaded holes & tapped features to remain unplated.</field>
|
||||
</record>
|
||||
|
||||
<record id="desc_tpl_enp_aerospace" model="fp.sale.description.template">
|
||||
<field name="name">ENP — Aerospace (AMS 2404 w/ CoC)</field>
|
||||
<field name="tag">aerospace</field>
|
||||
<field name="sequence">20</field>
|
||||
<field name="description">Electroless nickel plating per AMS 2404, Class I, Type II, Grade A. Plate to customer-specified thickness. Post-bake 4 hours @ 375°F min. Certificate of Conformance and thickness readings (3 points minimum per lot) required. Traceability to raw material heat lot. Nadcap-accredited process.</field>
|
||||
</record>
|
||||
|
||||
<record id="desc_tpl_enp_nuclear" model="fp.sale.description.template">
|
||||
<field name="name">ENP — Nuclear (CSA N299 / 10CFR50 App B)</field>
|
||||
<field name="tag">nuclear</field>
|
||||
<field name="sequence">25</field>
|
||||
<field name="description">Electroless nickel plating under CSA N299 / 10CFR50 Appendix B quality program. Full material traceability, dedicated tooling, independent QA inspection. Certificate package includes thickness, adhesion tape test, visual inspection sign-off, and chemistry log for the processing shift.</field>
|
||||
</record>
|
||||
|
||||
<record id="desc_tpl_masking_threaded" model="fp.sale.description.template">
|
||||
<field name="name">Masking — Threaded & Tapped Features</field>
|
||||
<field name="tag">masking</field>
|
||||
<field name="sequence">30</field>
|
||||
<field name="description">Selective plating. Mask all threaded holes, tapped features and mating surfaces per customer drawing. Non-plated areas to be free of residue. Remove masking prior to shipment. Any masking residue is cause for rejection.</field>
|
||||
</record>
|
||||
|
||||
<record id="desc_tpl_masking_od" model="fp.sale.description.template">
|
||||
<field name="name">Masking — Selective O.D. / Journals</field>
|
||||
<field name="tag">masking</field>
|
||||
<field name="sequence">35</field>
|
||||
<field name="description">Plate O.D. and specified journal surfaces only. Mask all bore surfaces, end faces, and sealing surfaces. Maintain ±0.0001" on masked-feature edges. Rack holes to be plugged.</field>
|
||||
</record>
|
||||
|
||||
<record id="desc_tpl_rework_strip" model="fp.sale.description.template">
|
||||
<field name="name">Rework — Strip & Replate</field>
|
||||
<field name="tag">rework</field>
|
||||
<field name="sequence">40</field>
|
||||
<field name="description">Rework of previously-plated parts. Chemically strip existing nickel deposit without attacking the base metal. Dimensional inspection after strip — any parts outside blueprint tolerance to be held for customer disposition. Replate to original spec. New Certificate of Conformance issued for the rework lot.</field>
|
||||
</record>
|
||||
|
||||
<record id="desc_tpl_packaging_individual" model="fp.sale.description.template">
|
||||
<field name="name">Packaging — Individual Bag + Desiccant</field>
|
||||
<field name="tag">packaging</field>
|
||||
<field name="sequence">50</field>
|
||||
<field name="description">Each part individually bagged in anti-static poly bag with desiccant pack. Bagged parts packed in cushioned cardboard cartons with corner protection. Outer carton labelled with part number, lot, quantity, and Entech W/O number. Do not ship open-top or mixed part-number cartons.</field>
|
||||
</record>
|
||||
|
||||
<record id="desc_tpl_hazmat_note" model="fp.sale.description.template">
|
||||
<field name="name">Handling — Delicate / No Tumble</field>
|
||||
<field name="tag">other</field>
|
||||
<field name="sequence">60</field>
|
||||
<field name="description">Delicate parts — rack plating only, no barrel. No tumbling or vibratory finishing before or after plating. Inspect for handling damage prior to final packaging. Any edge, surface or impact damage is cause for segregation.</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -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
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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
|
||||
@@ -24,3 +24,6 @@ access_fp_part_import_wizard_manager,fp.part.catalog.import.wizard.manager,model
|
||||
access_fp_customer_price_list_operator,fp.customer.price.list.operator,model_fp_customer_price_list,fusion_plating.group_fusion_plating_operator,1,0,0,0
|
||||
access_fp_customer_price_list_estimator,fp.customer.price.list.estimator,model_fp_customer_price_list,fusion_plating_configurator.group_fp_estimator,1,1,1,0
|
||||
access_fp_customer_price_list_manager,fp.customer.price.list.manager,model_fp_customer_price_list,fusion_plating.group_fusion_plating_manager,1,1,1,1
|
||||
access_fp_sale_desc_template_user,fp.sale.description.template.user,model_fp_sale_description_template,base.group_user,1,0,0,0
|
||||
access_fp_sale_desc_template_estimator,fp.sale.description.template.estimator,model_fp_sale_description_template,fusion_plating_configurator.group_fp_estimator,1,1,1,0
|
||||
access_fp_sale_desc_template_manager,fp.sale.description.template.manager,model_fp_sale_description_template,fusion_plating.group_fusion_plating_manager,1,1,1,1
|
||||
|
||||
|
@@ -22,17 +22,24 @@
|
||||
sequence="5"
|
||||
groups="group_fp_estimator,fusion_plating.group_fusion_plating_supervisor"/>
|
||||
|
||||
<menuitem id="menu_fp_quotations"
|
||||
name="Quotations"
|
||||
<!-- === New Quote — top-of-menu entry point for a fresh quote === -->
|
||||
<menuitem id="menu_fp_new_quote"
|
||||
name="New Quote"
|
||||
parent="menu_fp_sales"
|
||||
action="action_fp_quotations"
|
||||
sequence="10"/>
|
||||
action="action_fp_quote_configurator"
|
||||
sequence="1"/>
|
||||
|
||||
<menuitem id="menu_fp_direct_order"
|
||||
name="New Direct Order"
|
||||
parent="menu_fp_sales"
|
||||
action="action_fp_direct_order_wizard"
|
||||
sequence="15"/>
|
||||
sequence="5"/>
|
||||
|
||||
<menuitem id="menu_fp_quotations"
|
||||
name="Quotations"
|
||||
parent="menu_fp_sales"
|
||||
action="action_fp_quotations"
|
||||
sequence="10"/>
|
||||
|
||||
<menuitem id="menu_fp_sale_orders"
|
||||
name="Sale Orders"
|
||||
@@ -58,19 +65,13 @@
|
||||
action="action_fp_part_catalog_import_wizard"
|
||||
sequence="45"/>
|
||||
|
||||
<!-- ===== CONFIGURATOR submenu ===== -->
|
||||
<!-- ===== CONFIGURATOR submenu (admin-only: coating/pricing/treatments) ===== -->
|
||||
<menuitem id="menu_fp_configurator"
|
||||
name="Configurator"
|
||||
parent="fusion_plating.menu_fp_root"
|
||||
sequence="8"
|
||||
groups="group_fp_estimator"/>
|
||||
|
||||
<menuitem id="menu_fp_new_quote"
|
||||
name="New Quote"
|
||||
parent="menu_fp_configurator"
|
||||
action="action_fp_quote_configurator"
|
||||
sequence="10"/>
|
||||
|
||||
<menuitem id="menu_fp_coating_configs"
|
||||
name="Coating Configurations"
|
||||
parent="menu_fp_configurator"
|
||||
|
||||
@@ -8,9 +8,10 @@
|
||||
<list editable="bottom">
|
||||
<field name="partner_id"/>
|
||||
<field name="coating_config_id"/>
|
||||
<field name="unit_price"/>
|
||||
<field name="currency_id" column_invisible="True"/>
|
||||
<field name="unit_price" widget="monetary"
|
||||
options="{'currency_field': 'currency_id'}" sum="Total"/>
|
||||
<field name="price_uom"/>
|
||||
<field name="currency_id" optional="hide"/>
|
||||
<field name="min_quantity"/>
|
||||
<field name="effective_from"/>
|
||||
<field name="effective_to"/>
|
||||
@@ -32,9 +33,10 @@
|
||||
<group>
|
||||
<field name="partner_id"/>
|
||||
<field name="coating_config_id"/>
|
||||
<field name="unit_price"/>
|
||||
<field name="price_uom"/>
|
||||
<field name="currency_id"/>
|
||||
<field name="unit_price" widget="monetary"
|
||||
options="{'currency_field': 'currency_id'}"/>
|
||||
<field name="price_uom"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="effective_from"/>
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
<?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.
|
||||
|
||||
Manage reusable descriptions that estimators can drop onto sale order lines.
|
||||
-->
|
||||
<odoo>
|
||||
|
||||
<record id="view_fp_sale_description_template_list" model="ir.ui.view">
|
||||
<field name="name">fp.sale.description.template.list</field>
|
||||
<field name="model">fp.sale.description.template</field>
|
||||
<field name="arch" type="xml">
|
||||
<list multi_edit="1">
|
||||
<field name="sequence" widget="handle"/>
|
||||
<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="usage_count" string="Used"/>
|
||||
<field name="active" widget="boolean_toggle"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_fp_sale_description_template_form" model="ir.ui.view">
|
||||
<field name="name">fp.sale.description.template.form</field>
|
||||
<field name="model">fp.sale.description.template</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<sheet>
|
||||
<div class="oe_title">
|
||||
<label for="name"/>
|
||||
<h1><field name="name" placeholder="e.g. ENP — Standard Aluminium"/></h1>
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
<field name="tag"/>
|
||||
<field name="coating_config_id"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="partner_id"/>
|
||||
<field name="sequence"/>
|
||||
<field name="usage_count" readonly="1"/>
|
||||
<field name="active" widget="boolean_toggle"/>
|
||||
</group>
|
||||
</group>
|
||||
<group string="Description">
|
||||
<field name="description" nolabel="1" colspan="2"
|
||||
placeholder="Electroless nickel plating per AMS 2404, Class I, Type II…"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_fp_sale_description_template_search" model="ir.ui.view">
|
||||
<field name="name">fp.sale.description.template.search</field>
|
||||
<field name="model">fp.sale.description.template</field>
|
||||
<field name="arch" type="xml">
|
||||
<search>
|
||||
<field name="name"/>
|
||||
<field name="description"/>
|
||||
<field name="coating_config_id"/>
|
||||
<field name="partner_id"/>
|
||||
<field name="tag"/>
|
||||
<filter name="active" string="Active" domain="[('active','=',True)]"/>
|
||||
<group>
|
||||
<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>
|
||||
</record>
|
||||
|
||||
<record id="action_fp_sale_description_template" model="ir.actions.act_window">
|
||||
<field name="name">Line Description Templates</field>
|
||||
<field name="res_model">fp.sale.description.template</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="search_view_id" ref="view_fp_sale_description_template_search"/>
|
||||
<field name="context">{'search_default_active': 1}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Create your first line description template
|
||||
</p>
|
||||
<p>
|
||||
Save the language you use on repeat orders — masking rules,
|
||||
spec callouts, packaging notes. The estimator picks one,
|
||||
tweaks it, and it lands on the order line.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_fp_sale_description_templates"
|
||||
name="Line Descriptions"
|
||||
parent="menu_fp_configurator"
|
||||
action="action_fp_sale_description_template"
|
||||
sequence="45"/>
|
||||
|
||||
</odoo>
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -65,11 +65,19 @@
|
||||
<field name="part_ref"/>
|
||||
<field name="lot_ref"/>
|
||||
<field name="customer_ref"/>
|
||||
<field name="quantity"/>
|
||||
<label for="quantity"/>
|
||||
<div class="o_row">
|
||||
<field name="quantity" nolabel="1" class="oe_inline"/>
|
||||
<span class="ms-1">pcs</span>
|
||||
</div>
|
||||
</group>
|
||||
<group string="Window">
|
||||
<field name="plate_exit_time"/>
|
||||
<field name="window_hours"/>
|
||||
<label for="window_hours"/>
|
||||
<div class="o_row">
|
||||
<field name="window_hours" nolabel="1" class="oe_inline"/>
|
||||
<span class="ms-1">h</span>
|
||||
</div>
|
||||
<field name="bake_required_by" readonly="1"/>
|
||||
<field name="time_remaining_display" readonly="1"/>
|
||||
</group>
|
||||
@@ -82,8 +90,16 @@
|
||||
<field name="bake_end_time" readonly="1"/>
|
||||
</group>
|
||||
<group string="Result">
|
||||
<field name="bake_temp"/>
|
||||
<field name="bake_duration_hours" readonly="1"/>
|
||||
<label for="bake_temp"/>
|
||||
<div class="o_row">
|
||||
<field name="bake_temp" nolabel="1" class="oe_inline"/>
|
||||
<span class="ms-1">°F</span>
|
||||
</div>
|
||||
<label for="bake_duration_hours"/>
|
||||
<div class="o_row">
|
||||
<field name="bake_duration_hours" nolabel="1" readonly="1" class="oe_inline"/>
|
||||
<span class="ms-1">h</span>
|
||||
</div>
|
||||
</group>
|
||||
</group>
|
||||
<group string="Notes">
|
||||
|
||||
Reference in New Issue
Block a user