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:
@@ -94,6 +94,44 @@ class FpDirectOrderWizard(models.TransientModel):
|
|||||||
|
|
||||||
notes = fields.Text(string='Internal Notes')
|
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
|
# Description template picker — the domain is dynamically narrowed to
|
||||||
# this part's canned descriptions first. When no part is chosen it
|
# this part's canned descriptions first. When no part is chosen it
|
||||||
# falls through to generic templates.
|
# falls through to generic templates.
|
||||||
@@ -120,13 +158,46 @@ class FpDirectOrderWizard(models.TransientModel):
|
|||||||
for rec in self:
|
for rec in self:
|
||||||
rec.line_subtotal = (rec.quantity or 0) * (rec.unit_price or 0.0)
|
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')
|
@api.onchange('partner_id')
|
||||||
def _onchange_partner_id(self):
|
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
|
self.part_catalog_id = False
|
||||||
if self.partner_id and 'x_fc_default_invoice_strategy' in self.partner_id._fields:
|
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.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
|
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')
|
@api.onchange('description_template_id')
|
||||||
def _onchange_description_template(self):
|
def _onchange_description_template(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user