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

@@ -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'"

View File

@@ -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>

View File

@@ -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': [

View File

@@ -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, 69%). 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 &amp; 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 &amp; 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 &amp; 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>

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

View File

@@ -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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
24 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
25 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
26 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
27 access_fp_sale_desc_template_user fp.sale.description.template.user model_fp_sale_description_template base.group_user 1 0 0 0
28 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
29 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

View File

@@ -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"

View File

@@ -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"/>

View File

@@ -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>

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>

View File

@@ -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">