feat(configurator): C3 — link direct-order line to a prior quote
Adds quote_id (Many2one fp.quote.configurator) on the wizard line with a domain scoped to the wizard's customer + quote states (sent / accepted / won). Onchange auto-fills part, coating, and unit price (final = estimator_override_price or calculated_price, per-part). Mirrors x_fc_quote_id on sale.order.line for the audit trail. Surfaced as a togglable column on the SO line tree and under "Qty & Price" on the wizard line drill-in form. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -41,3 +41,8 @@ class SaleOrderLine(models.Model):
|
||||
help='Flag for prototype / non-catalog parts that should not be '
|
||||
'reused after this order.',
|
||||
)
|
||||
x_fc_quote_id = fields.Many2one(
|
||||
'fp.quote.configurator',
|
||||
string='Linked Quote',
|
||||
help='Quote that seeded this line. Links back for audit trail.',
|
||||
)
|
||||
|
||||
@@ -103,6 +103,7 @@
|
||||
<field name="x_fc_wo_group_tag" optional="hide"/>
|
||||
<field name="x_fc_start_at_node_id" optional="hide"/>
|
||||
<field name="x_fc_is_one_off" optional="hide"/>
|
||||
<field name="x_fc_quote_id" optional="hide"/>
|
||||
<field name="x_fc_rush_order" optional="hide"/>
|
||||
</xpath>
|
||||
</field>
|
||||
|
||||
@@ -108,6 +108,13 @@ class FpDirectOrderLine(models.TransientModel):
|
||||
help='After submit, write this line\'s coating + additional '
|
||||
'treatments back onto the part catalog as its new defaults.',
|
||||
)
|
||||
quote_id = fields.Many2one(
|
||||
'fp.quote.configurator',
|
||||
string='Linked Quote',
|
||||
domain="[('partner_id', '=', parent.partner_id), ('state', 'in', ['sent','accepted','won'])]",
|
||||
help='Optional: link this line to a prior quote. The unit price '
|
||||
'auto-fills from the quote\'s final price (or override).',
|
||||
)
|
||||
|
||||
# ---- Description ----
|
||||
description_template_id = fields.Many2one(
|
||||
@@ -143,6 +150,21 @@ class FpDirectOrderLine(models.TransientModel):
|
||||
)
|
||||
|
||||
# ---- Onchange ----
|
||||
@api.onchange('quote_id')
|
||||
def _onchange_quote_id(self):
|
||||
"""Auto-fill part, coating, and unit price from the linked quote."""
|
||||
if not self.quote_id:
|
||||
return
|
||||
q = self.quote_id
|
||||
if q.part_catalog_id and not self.part_catalog_id:
|
||||
self.part_catalog_id = q.part_catalog_id
|
||||
if q.coating_config_id and not self.coating_config_id:
|
||||
self.coating_config_id = q.coating_config_id
|
||||
if not self.unit_price:
|
||||
final = q.estimator_override_price or q.calculated_price
|
||||
if final and q.quantity:
|
||||
self.unit_price = final / q.quantity
|
||||
|
||||
@api.onchange('part_catalog_id')
|
||||
def _onchange_part_defaults(self):
|
||||
"""When a part is picked, seed coating + treatments from its catalog defaults."""
|
||||
|
||||
@@ -246,6 +246,7 @@ class FpDirectOrderWizard(models.TransientModel):
|
||||
'x_fc_part_wo_description': line.part_wo_description or False,
|
||||
'x_fc_start_at_node_id': line.start_at_node_id.id or False,
|
||||
'x_fc_is_one_off': line.is_one_off,
|
||||
'x_fc_quote_id': line.quote_id.id or False,
|
||||
}))
|
||||
|
||||
# 5. Create + confirm
|
||||
|
||||
@@ -118,6 +118,8 @@
|
||||
widget="many2many_tags"/>
|
||||
</group>
|
||||
<group string="Qty & Price">
|
||||
<field name="quote_id"
|
||||
options="{'no_create': True, 'no_open': True}"/>
|
||||
<field name="quantity"/>
|
||||
<field name="unit_price"
|
||||
widget="monetary"
|
||||
|
||||
Reference in New Issue
Block a user