From d1fc3d8720a89534d9f2ee3b0e3b2183d53e285a Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Wed, 27 May 2026 10:33:31 -0400 Subject: [PATCH] fix(express): show Tax on totals + add tooling as real SO line MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .../__manifest__.py | 2 +- .../views/fp_express_order_views.xml | 46 ++++++-- .../wizard/fp_direct_order_wizard.py | 103 +++++++++++++++++- 3 files changed, 134 insertions(+), 17 deletions(-) diff --git a/fusion_plating/fusion_plating_configurator/__manifest__.py b/fusion_plating/fusion_plating_configurator/__manifest__.py index 0b415bd8..5d2343ff 100644 --- a/fusion_plating/fusion_plating_configurator/__manifest__.py +++ b/fusion_plating/fusion_plating_configurator/__manifest__.py @@ -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': """ diff --git a/fusion_plating/fusion_plating_configurator/views/fp_express_order_views.xml b/fusion_plating/fusion_plating_configurator/views/fp_express_order_views.xml index 8667af88..2b8ea195 100644 --- a/fusion_plating/fusion_plating_configurator/views/fp_express_order_views.xml +++ b/fusion_plating/fusion_plating_configurator/views/fp_express_order_views.xml @@ -63,8 +63,6 @@ EXPRESS - @@ -87,16 +85,17 @@ -
+
CUSTOMER PO
- -
+ +
- + Block partial @@ -200,6 +201,14 @@
+ + +
+ + +
@@ -298,6 +307,12 @@ options="{'no_quick_create': True}" invisible="not part_catalog_id" optional="hide"/> + @@ -328,7 +343,14 @@
Subtotal - +
+
+ Tax + diff --git a/fusion_plating/fusion_plating_configurator/wizard/fp_direct_order_wizard.py b/fusion_plating/fusion_plating_configurator/wizard/fp_direct_order_wizard.py index fec8eb90..d9f707e3 100644 --- a/fusion_plating/fusion_plating_configurator/wizard/fp_direct_order_wizard.py +++ b/fusion_plating/fusion_plating_configurator/wizard/fp_direct_order_wizard.py @@ -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.