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