feat(configurator): add header fields + line O2M to direct order wizard

Task A2 of the direct-order-wizard rewrite. Adds SO-header fields for
customer job #, three deadlines (planned start / internal / customer),
bill-to / ship-to address pickers, the line_ids O2M linking to
fp.direct.order.line, computed order totals, and a missing-info
warning banner. Partner onchange now also seeds default addresses.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-04-19 20:31:14 -04:00
parent b4558a223c
commit 057157587d

View File

@@ -94,6 +94,44 @@ class FpDirectOrderWizard(models.TransientModel):
notes = fields.Text(string='Internal Notes')
# ---- Header additions (Phase A) ----
partner_invoice_id = fields.Many2one(
'res.partner', string='Invoice Address',
domain="['|', ('id', '=', partner_id), "
"('parent_id', '=', partner_id)]",
)
partner_shipping_id = fields.Many2one(
'res.partner', string='Delivery Address',
domain="['|', ('id', '=', partner_id), "
"('parent_id', '=', partner_id)]",
)
customer_job_number = fields.Char(
string='Customer Job #',
help="Customer's internal job number for cross-referencing. "
"Appears on work orders and invoices.",
)
planned_start_date = fields.Date(
string='Planned Start', default=fields.Date.context_today,
)
internal_deadline = fields.Date(string='Internal Deadline')
customer_deadline = fields.Date(string='Customer Deadline')
# ---- Lines (Phase A) ----
line_ids = fields.One2many(
'fp.direct.order.line', 'wizard_id', string='Order Lines',
)
total_amount = fields.Monetary(
string='Order Total',
compute='_compute_totals', currency_field='currency_id',
)
total_qty = fields.Integer(string='Total Qty', compute='_compute_totals')
total_line_count = fields.Integer(
string='Line Count', compute='_compute_totals',
)
# ---- Missing info banner ----
missing_info_msg = fields.Char(compute='_compute_missing_info_msg')
# Description template picker — the domain is dynamically narrowed to
# this part's canned descriptions first. When no part is chosen it
# falls through to generic templates.
@@ -120,13 +158,46 @@ class FpDirectOrderWizard(models.TransientModel):
for rec in self:
rec.line_subtotal = (rec.quantity or 0) * (rec.unit_price or 0.0)
@api.depends('line_ids.line_subtotal', 'line_ids.quantity')
def _compute_totals(self):
for rec in self:
rec.total_amount = sum(rec.line_ids.mapped('line_subtotal'))
rec.total_qty = sum(rec.line_ids.mapped('quantity'))
rec.total_line_count = len(rec.line_ids)
@api.depends('line_ids.part_catalog_id', 'line_ids.coating_config_id',
'line_ids.unit_price', 'line_ids.quantity')
def _compute_missing_info_msg(self):
for rec in self:
has_missing = False
for line in rec.line_ids:
if (not line.part_catalog_id
or not line.coating_config_id
or not line.unit_price
or not line.quantity):
has_missing = True
break
rec.missing_info_msg = (
'Some lines are missing quote information '
'(part / treatment / price / qty). '
'Verify before confirming the order.'
if has_missing else False
)
@api.onchange('partner_id')
def _onchange_partner_id(self):
"""Reset part selection when customer changes + pull invoice defaults."""
"""Reset part selection when customer changes + pull invoice defaults + addresses."""
self.part_catalog_id = False
if self.partner_id and 'x_fc_default_invoice_strategy' in self.partner_id._fields:
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
if self.partner_id:
addrs = self.partner_id.address_get(['invoice', 'delivery'])
self.partner_invoice_id = addrs.get('invoice') or self.partner_id.id
self.partner_shipping_id = addrs.get('delivery') or self.partner_id.id
else:
self.partner_invoice_id = False
self.partner_shipping_id = False
@api.onchange('description_template_id')
def _onchange_description_template(self):