fix(express): show Tax on totals + add tooling as real SO line
Three related fixes on the Express Orders totals card: 1. Totals card now breaks out Subtotal / Tax / Tooling Charge / Grand Total. Previously the "Subtotal" and "Grand Total" rows both read from total_amount (same value rendered twice) and no tax was shown at all. Customers on a fiscal position-mapped tax rate (Ontario HST, etc.) had their taxes silently dropped from the preview. 2. tooling_charge now feeds the Grand Total. The total_amount compute previously summed line subtotals only. Added a real SO line for the tooling charge in action_create_order so the eventual sale.order.amount_total matches the preview AND the invoice carries a "Tooling Charge" line item. 3. tax_ids is now visible as an optional column on the lines list. Operator can see + override the auto-applied tax per line. Default still comes from FP-SERVICE product mapped through partner.property_account_position_id (fiscal position). New compute fields on fp.direct.order.wizard: - total_subtotal (sum of line.qty * line.unit_price, pre-tax) - total_tax (sum of line + tooling taxes via compute_all) - total_amount (subtotal + tax + tooling — was just subtotal) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
|
||||
{
|
||||
'name': 'Fusion Plating — Configurator',
|
||||
'version': '19.0.22.5.0',
|
||||
'version': '19.0.22.6.0',
|
||||
'category': 'Manufacturing/Plating',
|
||||
'summary': 'Quotation configurator with part catalog, coating configs, and formula-based pricing engine.',
|
||||
'description': """
|
||||
|
||||
@@ -63,8 +63,6 @@
|
||||
<field name="name" readonly="1"/>
|
||||
<span class="o_fp_xpr_pill">EXPRESS</span>
|
||||
</h1>
|
||||
<field name="user_id" readonly="state != 'draft'"
|
||||
options="{'no_create': True}"/>
|
||||
<field name="view_source" invisible="1"/>
|
||||
</div>
|
||||
|
||||
@@ -87,16 +85,17 @@
|
||||
</div>
|
||||
|
||||
<!-- ============================================================
|
||||
PO Block fills LEFT half (cols 1-2) across rows 2-5.
|
||||
RIGHT half (cols 3-4) flows 4 pairs of fields
|
||||
PO Block fills LEFT half (cols 1-2) across rows 2-7.
|
||||
RIGHT half (cols 3-4) flows 6 pairs of fields
|
||||
alongside it — Customer Job #/Job Sorting, Material
|
||||
Process/Lead Time, Payment Terms/Delivery Method,
|
||||
Pricelist/Quote Validity.
|
||||
Pricelist/Quote Validity, Blanket SO/Invoice Strategy,
|
||||
Sales Rep/conditional Deposit-or-Progress %.
|
||||
|
||||
Net: PO block ~250px height matches 4 × ~60px right
|
||||
stack — no dead air on either side.
|
||||
Net: PO block height matches 6 × ~60px right stack —
|
||||
no dead air on either side.
|
||||
============================================================ -->
|
||||
<div class="o_fp_xpr_cell span-2 row-span-4 o_fp_xpr_po_block">
|
||||
<div class="o_fp_xpr_cell span-2 row-span-6 o_fp_xpr_po_block">
|
||||
<div class="o_fp_xpr_po_head">
|
||||
<span>CUSTOMER PO</span>
|
||||
<field name="po_status" widget="badge"
|
||||
@@ -185,12 +184,14 @@
|
||||
<field name="validity_date" nolabel="1"/>
|
||||
</div>
|
||||
|
||||
<!-- Row 6 (after PO block ends) — Blanket + Invoice + conditional % -->
|
||||
<div class="o_fp_xpr_cell">
|
||||
<!-- Right side row 6: Blanket Sales Order + Invoice Strategy -->
|
||||
<div class="o_fp_xpr_cell o_fp_xpr_inline_label">
|
||||
<label for="is_blanket_order">Blanket Sales Order</label>
|
||||
<div class="o_fp_xpr_inline_pair">
|
||||
<field name="is_blanket_order" nolabel="1"/>
|
||||
<field name="is_blanket_order" nolabel="1"
|
||||
widget="boolean_toggle"/>
|
||||
<field name="block_partial_shipments" nolabel="1"
|
||||
widget="boolean_toggle"
|
||||
invisible="not is_blanket_order"/>
|
||||
<span class="o_fp_xpr_inline_help"
|
||||
invisible="not is_blanket_order">Block partial</span>
|
||||
@@ -200,6 +201,14 @@
|
||||
<label for="invoice_strategy">Invoice Strategy</label>
|
||||
<field name="invoice_strategy" nolabel="1"/>
|
||||
</div>
|
||||
|
||||
<!-- Right side row 7: Sales Rep + conditional Deposit/Progress % -->
|
||||
<div class="o_fp_xpr_cell">
|
||||
<label for="user_id">Sales Rep</label>
|
||||
<field name="user_id" nolabel="1"
|
||||
readonly="state != 'draft'"
|
||||
options="{'no_create': True}"/>
|
||||
</div>
|
||||
<div class="o_fp_xpr_cell" invisible="invoice_strategy != 'deposit'">
|
||||
<label for="deposit_percent">Deposit %</label>
|
||||
<field name="deposit_percent" nolabel="1"/>
|
||||
@@ -298,6 +307,12 @@
|
||||
options="{'no_quick_create': True}"
|
||||
invisible="not part_catalog_id"
|
||||
optional="hide"/>
|
||||
<field name="tax_ids"
|
||||
string="Tax"
|
||||
widget="many2many_tags"
|
||||
options="{'no_create': True}"
|
||||
optional="show"
|
||||
width="110px"/>
|
||||
<field name="currency_id" column_invisible="1"/>
|
||||
</list>
|
||||
</field>
|
||||
@@ -328,7 +343,14 @@
|
||||
<div class="o_fp_xpr_card o_fp_xpr_totals">
|
||||
<div class="o_fp_xpr_total_row">
|
||||
<span class="o_fp_xpr_total_label">Subtotal</span>
|
||||
<field name="total_amount"
|
||||
<field name="total_subtotal"
|
||||
widget="monetary"
|
||||
options="{'currency_field': 'currency_id'}"
|
||||
readonly="1" nolabel="1"/>
|
||||
</div>
|
||||
<div class="o_fp_xpr_total_row">
|
||||
<span class="o_fp_xpr_total_label">Tax</span>
|
||||
<field name="total_tax"
|
||||
widget="monetary"
|
||||
options="{'currency_field': 'currency_id'}"
|
||||
readonly="1" nolabel="1"/>
|
||||
|
||||
@@ -342,9 +342,24 @@ class FpDirectOrderWizard(models.Model):
|
||||
line_ids = fields.One2many(
|
||||
'fp.direct.order.line', 'wizard_id', string='Order Lines',
|
||||
)
|
||||
total_amount = fields.Monetary(
|
||||
string='Order Total',
|
||||
total_subtotal = fields.Monetary(
|
||||
string='Subtotal (pre-tax)',
|
||||
compute='_compute_totals', currency_field='currency_id',
|
||||
help='Sum of (qty × unit_price) across all lines, before tax. '
|
||||
'Excludes the tooling charge.',
|
||||
)
|
||||
total_tax = fields.Monetary(
|
||||
string='Tax',
|
||||
compute='_compute_totals', currency_field='currency_id',
|
||||
help='Sum of taxes across all lines + the tooling charge. '
|
||||
'Each line uses the partner\'s fiscal position to map the '
|
||||
'FP-SERVICE product taxes to the right rate.',
|
||||
)
|
||||
total_amount = fields.Monetary(
|
||||
string='Grand Total',
|
||||
compute='_compute_totals', currency_field='currency_id',
|
||||
help='Subtotal + tax + tooling charge. Matches the eventual '
|
||||
'sale.order.amount_total when the order is confirmed.',
|
||||
)
|
||||
total_qty = fields.Integer(string='Total Qty', compute='_compute_totals')
|
||||
total_line_count = fields.Integer(
|
||||
@@ -366,10 +381,66 @@ class FpDirectOrderWizard(models.Model):
|
||||
return super().create(vals_list)
|
||||
|
||||
# ---- Computes ----
|
||||
@api.depends('line_ids.line_subtotal', 'line_ids.quantity')
|
||||
@api.depends(
|
||||
'line_ids.line_subtotal',
|
||||
'line_ids.quantity',
|
||||
'line_ids.unit_price',
|
||||
'line_ids.tax_ids',
|
||||
'tooling_charge',
|
||||
'partner_id',
|
||||
'currency_id',
|
||||
)
|
||||
def _compute_totals(self):
|
||||
"""Roll up subtotal / tax / grand total across lines + tooling.
|
||||
|
||||
Each line's taxes are computed via account.tax.compute_all so the
|
||||
Express form's totals card mirrors what the eventual SO will show
|
||||
once the operator hits Confirm. The tooling charge is added at the
|
||||
wizard level here AND pushed as an actual sale.order.line at
|
||||
action_create_order time (so it carries into the invoice).
|
||||
"""
|
||||
for rec in self:
|
||||
rec.total_amount = sum(rec.line_ids.mapped('line_subtotal'))
|
||||
subtotal = 0.0
|
||||
tax_total = 0.0
|
||||
for line in rec.line_ids:
|
||||
line_pre_tax = (line.quantity or 0) * (line.unit_price or 0.0)
|
||||
subtotal += line_pre_tax
|
||||
if line.tax_ids and line_pre_tax:
|
||||
taxes_res = line.tax_ids.compute_all(
|
||||
line.unit_price or 0.0,
|
||||
currency=rec.currency_id,
|
||||
quantity=line.quantity or 0,
|
||||
product=None,
|
||||
partner=rec.partner_id or None,
|
||||
)
|
||||
tax_total += (
|
||||
taxes_res['total_included']
|
||||
- taxes_res['total_excluded']
|
||||
)
|
||||
# Tooling charge: pick the tax set from the FIRST line that
|
||||
# has one (best proxy for the customer's standard rate). If
|
||||
# no line has taxes set yet, tooling is shown untaxed in the
|
||||
# preview; the eventual SO line will apply product defaults.
|
||||
tooling = rec.tooling_charge or 0.0
|
||||
if tooling:
|
||||
first_taxed_line = next(
|
||||
(l for l in rec.line_ids if l.tax_ids), False,
|
||||
)
|
||||
if first_taxed_line:
|
||||
tooling_res = first_taxed_line.tax_ids.compute_all(
|
||||
tooling,
|
||||
currency=rec.currency_id,
|
||||
quantity=1,
|
||||
product=None,
|
||||
partner=rec.partner_id or None,
|
||||
)
|
||||
tax_total += (
|
||||
tooling_res['total_included']
|
||||
- tooling_res['total_excluded']
|
||||
)
|
||||
rec.total_subtotal = subtotal
|
||||
rec.total_tax = tax_total
|
||||
rec.total_amount = subtotal + tax_total + tooling
|
||||
rec.total_qty = sum(rec.line_ids.mapped('quantity'))
|
||||
rec.total_line_count = len(rec.line_ids)
|
||||
|
||||
@@ -824,6 +895,30 @@ class FpDirectOrderWizard(models.Model):
|
||||
if line.tax_ids else False),
|
||||
}))
|
||||
|
||||
# 4b. Tooling charge — surface as a real sale.order.line so it
|
||||
# carries through SO.amount_total + invoice naturally, instead of
|
||||
# sitting orphaned on the SO header. Tax: inherit from the first
|
||||
# part line that has taxes set (best proxy for "the customer's
|
||||
# standard tax rate"); falls back to product defaults if no line
|
||||
# has taxes yet.
|
||||
if self.tooling_charge:
|
||||
tooling_taxes = False
|
||||
first_taxed = next(
|
||||
(l for l in self.line_ids if l.tax_ids), False,
|
||||
)
|
||||
if first_taxed:
|
||||
tooling_taxes = [(6, 0, first_taxed.tax_ids.ids)]
|
||||
so_vals['order_line'].append((0, 0, {
|
||||
'product_id': product.id,
|
||||
'name': _('Tooling Charge'),
|
||||
'product_uom_qty': 1.0,
|
||||
'price_unit': self.tooling_charge,
|
||||
'x_fc_internal_description': _(
|
||||
'One-time tooling fee added via Express Orders.'
|
||||
),
|
||||
'tax_ids': tooling_taxes,
|
||||
}))
|
||||
|
||||
# 5. Create — stays in draft / quotation. Sub 1: user reviews
|
||||
# and manually clicks Confirm / Send. No auto-confirm, no
|
||||
# auto-email to the client.
|
||||
|
||||
Reference in New Issue
Block a user