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:
@@ -50,6 +50,7 @@ Provides:
|
|||||||
'views/fp_configurator_menu.xml',
|
'views/fp_configurator_menu.xml',
|
||||||
'views/fp_sale_description_template_views.xml',
|
'views/fp_sale_description_template_views.xml',
|
||||||
'wizard/fp_direct_order_wizard_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',
|
'wizard/fp_part_catalog_import_wizard_views.xml',
|
||||||
'data/fp_sale_description_template_data.xml',
|
'data/fp_sale_description_template_data.xml',
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -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_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_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_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_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_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
|
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
|
||||||
|
|||||||
|
@@ -4,4 +4,5 @@
|
|||||||
|
|
||||||
from . import fp_direct_order_wizard
|
from . import fp_direct_order_wizard
|
||||||
from . import fp_direct_order_line
|
from . import fp_direct_order_line
|
||||||
|
from . import fp_add_from_so_wizard
|
||||||
from . import fp_part_catalog_import_wizard
|
from . import fp_part_catalog_import_wizard
|
||||||
|
|||||||
@@ -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',
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
@@ -145,6 +145,23 @@ class FpDirectOrderWizard(models.TransientModel):
|
|||||||
self.partner_shipping_id = False
|
self.partner_shipping_id = False
|
||||||
|
|
||||||
# ---- Actions ----
|
# ---- 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):
|
def action_create_order(self):
|
||||||
"""Create and confirm the sale order with one SO line per wizard line."""
|
"""Create and confirm the sale order with one SO line per wizard line."""
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
|
|||||||
@@ -70,6 +70,13 @@
|
|||||||
|
|
||||||
<notebook>
|
<notebook>
|
||||||
<page string="Lines" name="lines">
|
<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">
|
<field name="line_ids">
|
||||||
<list editable="bottom"
|
<list editable="bottom"
|
||||||
decoration-warning="is_missing_info">
|
decoration-warning="is_missing_info">
|
||||||
|
|||||||
Reference in New Issue
Block a user