feat(configurator): 'Add From Prior SO' sub-wizard for repeat orders

Task B4. New fp.add.from.so.wizard transient model: given the current
direct-order wizard + customer, lists the customer's prior confirmed
sale orders, lets the estimator tick source lines, and clones them
into fp.direct.order.line rows (part, coating, treatments, qty,
price, deadline, rush, WO group, description).

Button "+ Add From Prior SO" lives on the Lines tab of the main
wizard, visible once the customer is picked. Sub-wizard rejects
source lines that predate the new plating fields (no x_fc_part_catalog_id).

Smoke-tested on odoo-entech: copying all 3 lines of S00066 onto a
fresh wizard reproduces part/coating/qty/price correctly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-04-19 20:48:52 -04:00
parent f55022c3d6
commit 9642a07306
7 changed files with 161 additions and 0 deletions

View File

@@ -50,6 +50,7 @@ Provides:
'views/fp_configurator_menu.xml',
'views/fp_sale_description_template_views.xml',
'wizard/fp_direct_order_wizard_views.xml',
'wizard/fp_add_from_so_wizard_views.xml',
'wizard/fp_part_catalog_import_wizard_views.xml',
'data/fp_sale_description_template_data.xml',
],

View File

@@ -21,6 +21,8 @@ access_fp_direct_order_wizard_estimator,fp.direct.order.wizard.estimator,model_f
access_fp_direct_order_wizard_manager,fp.direct.order.wizard.manager,model_fp_direct_order_wizard,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_direct_order_line_estimator,fp.direct.order.line.estimator,model_fp_direct_order_line,fusion_plating_configurator.group_fp_estimator,1,1,1,1
access_fp_direct_order_line_manager,fp.direct.order.line.manager,model_fp_direct_order_line,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_add_from_so_wizard_estimator,fp.add.from.so.wizard.estimator,model_fp_add_from_so_wizard,fusion_plating_configurator.group_fp_estimator,1,1,1,1
access_fp_add_from_so_wizard_manager,fp.add.from.so.wizard.manager,model_fp_add_from_so_wizard,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_part_import_wizard_estimator,fp.part.catalog.import.wizard.estimator,model_fp_part_catalog_import_wizard,fusion_plating_configurator.group_fp_estimator,1,1,1,1
access_fp_part_import_wizard_manager,fp.part.catalog.import.wizard.manager,model_fp_part_catalog_import_wizard,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_customer_price_list_operator,fp.customer.price.list.operator,model_fp_customer_price_list,fusion_plating.group_fusion_plating_operator,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
21 access_fp_direct_order_wizard_manager fp.direct.order.wizard.manager model_fp_direct_order_wizard fusion_plating.group_fusion_plating_manager 1 1 1 1
22 access_fp_direct_order_line_estimator fp.direct.order.line.estimator model_fp_direct_order_line fusion_plating_configurator.group_fp_estimator 1 1 1 1
23 access_fp_direct_order_line_manager fp.direct.order.line.manager model_fp_direct_order_line fusion_plating.group_fusion_plating_manager 1 1 1 1
24 access_fp_add_from_so_wizard_estimator fp.add.from.so.wizard.estimator model_fp_add_from_so_wizard fusion_plating_configurator.group_fp_estimator 1 1 1 1
25 access_fp_add_from_so_wizard_manager fp.add.from.so.wizard.manager model_fp_add_from_so_wizard fusion_plating.group_fusion_plating_manager 1 1 1 1
26 access_fp_part_import_wizard_estimator fp.part.catalog.import.wizard.estimator model_fp_part_catalog_import_wizard fusion_plating_configurator.group_fp_estimator 1 1 1 1
27 access_fp_part_import_wizard_manager fp.part.catalog.import.wizard.manager model_fp_part_catalog_import_wizard fusion_plating.group_fusion_plating_manager 1 1 1 1
28 access_fp_customer_price_list_operator fp.customer.price.list.operator model_fp_customer_price_list fusion_plating.group_fusion_plating_operator 1 0 0 0

View File

@@ -4,4 +4,5 @@
from . import fp_direct_order_wizard
from . import fp_direct_order_line
from . import fp_add_from_so_wizard
from . import fp_part_catalog_import_wizard

View File

@@ -0,0 +1,86 @@
# -*- coding: utf-8 -*-
# Copyright 2026 Nexa Systems Inc.
# License OPL-1 (Odoo Proprietary License v1.0)
# Part of the Fusion Plating product family.
from odoo import _, api, fields, models
from odoo.exceptions import UserError
class FpAddFromSoWizard(models.TransientModel):
"""Pick lines from a prior sale.order and clone them onto the direct-order wizard.
Entry: a button on the direct-order wizard. The source SO list is
filtered to the wizard's current customer. Selected SO lines are
mapped back to fp.direct.order.line rows (part, coating, qty, price,
treatments, description).
"""
_name = 'fp.add.from.so.wizard'
_description = 'Fusion Plating - Add Lines From Prior SO'
direct_order_wizard_id = fields.Many2one(
'fp.direct.order.wizard',
required=True,
ondelete='cascade',
)
partner_id = fields.Many2one(
related='direct_order_wizard_id.partner_id', readonly=True,
)
source_order_id = fields.Many2one(
'sale.order', string='Source Sales Order',
domain="[('partner_id', '=', partner_id), ('state', 'in', ('sale', 'done'))]",
help='Pick a prior confirmed order for this customer.',
)
source_line_ids = fields.Many2many(
'sale.order.line',
string='Lines to Copy',
domain="[('order_id', '=', source_order_id)]",
help='Tick the lines you want to replicate on the new direct order.',
)
@api.onchange('source_order_id')
def _onchange_source_order_id(self):
self.source_line_ids = False
def action_copy_lines(self):
self.ensure_one()
if not self.source_order_id:
raise UserError(_('Pick a source sales order.'))
if not self.source_line_ids:
raise UserError(_('Pick at least one line to copy.'))
Line = self.env['fp.direct.order.line']
wizard = self.direct_order_wizard_id
copied = 0
for src in self.source_line_ids:
if not src.x_fc_part_catalog_id or not src.x_fc_coating_config_id:
# Skip SO lines that predate the plating fields
continue
Line.create({
'wizard_id': wizard.id,
'part_catalog_id': src.x_fc_part_catalog_id.id,
'coating_config_id': src.x_fc_coating_config_id.id,
'treatment_ids': [(6, 0, src.x_fc_treatment_ids.ids)],
'quantity': int(src.product_uom_qty) or 1,
'unit_price': src.price_unit or 0.0,
'part_deadline': src.x_fc_part_deadline,
'rush_order': src.x_fc_rush_order,
'wo_group_tag': src.x_fc_wo_group_tag or False,
'line_description': src.name,
})
copied += 1
if not copied:
raise UserError(_(
'None of the selected lines carry plating part / coating '
'fields, so there was nothing to copy. Pick lines from a '
'direct order created with the new wizard.'
))
return {
'type': 'ir.actions.act_window',
'res_model': 'fp.direct.order.wizard',
'res_id': wizard.id,
'view_mode': 'form',
'target': 'new',
}

View File

@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_fp_add_from_so_wizard_form" model="ir.ui.view">
<field name="name">fp.add.from.so.wizard.form</field>
<field name="model">fp.add.from.so.wizard</field>
<field name="arch" type="xml">
<form string="Add Lines From Prior SO">
<sheet>
<div class="oe_title">
<h1>Copy Lines From Prior Order</h1>
<p class="text-muted">
Pick one of this customer's previous confirmed orders,
then tick the lines you want replicated on the new
direct order. Each copied row can still be edited
before confirming.
</p>
</div>
<group>
<field name="direct_order_wizard_id" invisible="1"/>
<field name="partner_id" readonly="1"/>
<field name="source_order_id"
options="{'no_create': True, 'no_open': True}"/>
</group>
<separator string="Lines on Source Order"/>
<field name="source_line_ids"
invisible="not source_order_id">
<list>
<field name="name"/>
<field name="x_fc_part_catalog_id"/>
<field name="x_fc_coating_config_id"/>
<field name="product_uom_qty"/>
<field name="price_unit"/>
<field name="x_fc_part_deadline"/>
</list>
</field>
</sheet>
<footer>
<button name="action_copy_lines"
type="object"
string="Copy Selected Lines"
class="btn-primary"/>
<button string="Cancel" special="cancel" class="btn-secondary"/>
</footer>
</form>
</field>
</record>
</odoo>

View File

@@ -145,6 +145,23 @@ class FpDirectOrderWizard(models.TransientModel):
self.partner_shipping_id = False
# ---- Actions ----
def action_add_from_prior_so(self):
"""Open a sub-wizard to copy lines from a prior sale.order."""
self.ensure_one()
if not self.partner_id:
raise UserError(_('Pick a customer first.'))
sub = self.env['fp.add.from.so.wizard'].create({
'direct_order_wizard_id': self.id,
})
return {
'type': 'ir.actions.act_window',
'name': _('Add Lines From Prior SO'),
'res_model': 'fp.add.from.so.wizard',
'res_id': sub.id,
'view_mode': 'form',
'target': 'new',
}
def action_create_order(self):
"""Create and confirm the sale order with one SO line per wizard line."""
self.ensure_one()

View File

@@ -70,6 +70,13 @@
<notebook>
<page string="Lines" name="lines">
<div class="mb-2">
<button name="action_add_from_prior_so"
type="object"
string="+ Add From Prior SO"
class="btn-secondary"
invisible="not partner_id"/>
</div>
<field name="line_ids">
<list editable="bottom"
decoration-warning="is_missing_info">