From 9642a07306868989349f4ef0d006923368d4e587 Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Sun, 19 Apr 2026 20:48:52 -0400 Subject: [PATCH] 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) --- .../__manifest__.py | 1 + .../security/ir.model.access.csv | 2 + .../wizard/__init__.py | 1 + .../wizard/fp_add_from_so_wizard.py | 86 +++++++++++++++++++ .../wizard/fp_add_from_so_wizard_views.xml | 47 ++++++++++ .../wizard/fp_direct_order_wizard.py | 17 ++++ .../wizard/fp_direct_order_wizard_views.xml | 7 ++ 7 files changed, 161 insertions(+) create mode 100644 fusion_plating/fusion_plating_configurator/wizard/fp_add_from_so_wizard.py create mode 100644 fusion_plating/fusion_plating_configurator/wizard/fp_add_from_so_wizard_views.xml diff --git a/fusion_plating/fusion_plating_configurator/__manifest__.py b/fusion_plating/fusion_plating_configurator/__manifest__.py index 87d8a64a..a215b602 100644 --- a/fusion_plating/fusion_plating_configurator/__manifest__.py +++ b/fusion_plating/fusion_plating_configurator/__manifest__.py @@ -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', ], diff --git a/fusion_plating/fusion_plating_configurator/security/ir.model.access.csv b/fusion_plating/fusion_plating_configurator/security/ir.model.access.csv index 79091677..d76b9be3 100644 --- a/fusion_plating/fusion_plating_configurator/security/ir.model.access.csv +++ b/fusion_plating/fusion_plating_configurator/security/ir.model.access.csv @@ -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 diff --git a/fusion_plating/fusion_plating_configurator/wizard/__init__.py b/fusion_plating/fusion_plating_configurator/wizard/__init__.py index 47aa87af..93eba4e1 100644 --- a/fusion_plating/fusion_plating_configurator/wizard/__init__.py +++ b/fusion_plating/fusion_plating_configurator/wizard/__init__.py @@ -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 diff --git a/fusion_plating/fusion_plating_configurator/wizard/fp_add_from_so_wizard.py b/fusion_plating/fusion_plating_configurator/wizard/fp_add_from_so_wizard.py new file mode 100644 index 00000000..f731530f --- /dev/null +++ b/fusion_plating/fusion_plating_configurator/wizard/fp_add_from_so_wizard.py @@ -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', + } diff --git a/fusion_plating/fusion_plating_configurator/wizard/fp_add_from_so_wizard_views.xml b/fusion_plating/fusion_plating_configurator/wizard/fp_add_from_so_wizard_views.xml new file mode 100644 index 00000000..985bf9ae --- /dev/null +++ b/fusion_plating/fusion_plating_configurator/wizard/fp_add_from_so_wizard_views.xml @@ -0,0 +1,47 @@ + + + + fp.add.from.so.wizard.form + fp.add.from.so.wizard + +
+ +
+

Copy Lines From Prior Order

+

+ 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. +

+
+ + + + + + + + + + + + + + + + +
+
+
+
+
+
+
diff --git a/fusion_plating/fusion_plating_configurator/wizard/fp_direct_order_wizard.py b/fusion_plating/fusion_plating_configurator/wizard/fp_direct_order_wizard.py index 11b67c4a..ccb5db0a 100644 --- a/fusion_plating/fusion_plating_configurator/wizard/fp_direct_order_wizard.py +++ b/fusion_plating/fusion_plating_configurator/wizard/fp_direct_order_wizard.py @@ -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() diff --git a/fusion_plating/fusion_plating_configurator/wizard/fp_direct_order_wizard_views.xml b/fusion_plating/fusion_plating_configurator/wizard/fp_direct_order_wizard_views.xml index 8602aeb4..67950799 100644 --- a/fusion_plating/fusion_plating_configurator/wizard/fp_direct_order_wizard_views.xml +++ b/fusion_plating/fusion_plating_configurator/wizard/fp_direct_order_wizard_views.xml @@ -70,6 +70,13 @@ +
+