From 4476196ae898fdfc7418ee23f245b84d21397462 Mon Sep 17 00:00:00 2001 From: "Aditi Pawar(adpaw)" Date: Thu, 4 Jun 2026 17:11:58 +0530 Subject: [PATCH] [ADD] new_product_type: Implemented custom kit product without BOM dependency. -Added 'is_kit' flag and 'sub_product_ids' M2M relations to product templates. -Added recursive 'kit_main_line_id' self-relations, 'is_kit_sub_line' states, and automated cascade-deletion handlers on order lines. -Build 'sale.kit.wizard' transient model to auto-populate, calculate group pricing, and map updates back to quotation rows, ensuring the values stay intact even after re-opening the wizard -Enforce row-level readonly attributes for generated sub-lines, and introduce a 'print_kit_in_report' toggle. -Customize logic across Sales Orders, Portal views, and Invoice layouts to dynamically filter sub-products based on the parent header flag. --- new_product_type/__init__.py | 2 + new_product_type/__manifest__.py | 20 +++++ new_product_type/models/__init__.py | 3 + new_product_type/models/product_template.py | 15 ++++ new_product_type/models/sale_order.py | 20 +++++ new_product_type/models/sale_order_line.py | 33 +++++++ new_product_type/report/invoice_report.xml | 15 ++++ .../report/sale_order_portal_report.xml | 14 +++ new_product_type/report/sale_order_report.xml | 14 +++ new_product_type/security/ir.model.access.csv | 3 + new_product_type/views/product_views.xml | 17 ++++ .../views/sale_kit_wizard_views.xml | 34 +++++++ .../views/sale_order_line_views.xml | 36 ++++++++ new_product_type/wizard/__init__.py | 2 + new_product_type/wizard/sale_kit_wizard.py | 89 +++++++++++++++++++ .../wizard/sale_kit_wizard_line.py | 11 +++ 16 files changed, 328 insertions(+) create mode 100644 new_product_type/__init__.py create mode 100644 new_product_type/__manifest__.py create mode 100644 new_product_type/models/__init__.py create mode 100644 new_product_type/models/product_template.py create mode 100644 new_product_type/models/sale_order.py create mode 100644 new_product_type/models/sale_order_line.py create mode 100644 new_product_type/report/invoice_report.xml create mode 100644 new_product_type/report/sale_order_portal_report.xml create mode 100644 new_product_type/report/sale_order_report.xml create mode 100644 new_product_type/security/ir.model.access.csv create mode 100644 new_product_type/views/product_views.xml create mode 100644 new_product_type/views/sale_kit_wizard_views.xml create mode 100644 new_product_type/views/sale_order_line_views.xml create mode 100644 new_product_type/wizard/__init__.py create mode 100644 new_product_type/wizard/sale_kit_wizard.py create mode 100644 new_product_type/wizard/sale_kit_wizard_line.py diff --git a/new_product_type/__init__.py b/new_product_type/__init__.py new file mode 100644 index 00000000000..9b4296142f4 --- /dev/null +++ b/new_product_type/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import wizard diff --git a/new_product_type/__manifest__.py b/new_product_type/__manifest__.py new file mode 100644 index 00000000000..4689913dddf --- /dev/null +++ b/new_product_type/__manifest__.py @@ -0,0 +1,20 @@ +{ + 'name': 'New Product Type', + 'author': 'Aditi Pawar(adpaw)', + 'license': 'LGPL-3', + 'summary': 'Sell products as kits without Manufacturing or BoM', + 'depends': [ + "sale", "product", "account" + ], + 'data': [ + 'security/ir.model.access.csv', + 'views/product_views.xml', + 'views/sale_order_line_views.xml', + 'views/sale_kit_wizard_views.xml', + 'report/invoice_report.xml', + 'report/sale_order_portal_report.xml', + 'report/sale_order_report.xml' + ], + 'installable': True, + 'auto_install': True +} diff --git a/new_product_type/models/__init__.py b/new_product_type/models/__init__.py new file mode 100644 index 00000000000..8f2f8c0cbc1 --- /dev/null +++ b/new_product_type/models/__init__.py @@ -0,0 +1,3 @@ +from . import product_template +from . import sale_order_line +from . import sale_order diff --git a/new_product_type/models/product_template.py b/new_product_type/models/product_template.py new file mode 100644 index 00000000000..3375fc94ee2 --- /dev/null +++ b/new_product_type/models/product_template.py @@ -0,0 +1,15 @@ +from odoo import models, fields, api +from odoo.exceptions import ValidationError + + +class ProductTemplate(models.Model): + _inherit = 'product.template' + + is_kit = fields.Boolean(string='Is Kit', default=False) + sub_product_ids = fields.Many2many('product.product', 'product_template_kit_component_rel') + + @api.constrains("sub_product_ids") + def _check_no_self_product_reference(self): + for record in self: + if record.product_variant_ids in record.sub_product_ids: + raise ValidationError("A product cannot be added as a sub-product in its own kit.") diff --git a/new_product_type/models/sale_order.py b/new_product_type/models/sale_order.py new file mode 100644 index 00000000000..353818e35b6 --- /dev/null +++ b/new_product_type/models/sale_order.py @@ -0,0 +1,20 @@ +from odoo import models, fields, api + + +class SaleOrder(models.Model): + _inherit = 'sale.order' + + has_kit_products = fields.Boolean( + compute='_compute_has_kit_products', + store=False + ) + + print_kit_in_report = fields.Boolean( + string="Print Kit Components in Report", + default=False + ) + + @api.depends('order_line.is_kit_line') + def _compute_has_kit_products(self): + for order in self: + order.has_kit_products = any(line.is_kit_line for line in order.order_line) diff --git a/new_product_type/models/sale_order_line.py b/new_product_type/models/sale_order_line.py new file mode 100644 index 00000000000..b635b41b884 --- /dev/null +++ b/new_product_type/models/sale_order_line.py @@ -0,0 +1,33 @@ +from odoo import models, fields, api +from odoo.exceptions import UserError + + +class SaleOrderLine(models.Model): + _inherit = 'sale.order.line' + + is_kit_line = fields.Boolean(related='product_id.product_tmpl_id.is_kit', store=True) + is_kit_sub_line = fields.Boolean(default=False) + kit_main_line_id = fields.Many2one('sale.order.line', string='Kit Main Line', ondelete='cascade') + kit_unit_price = fields.Float(default=0.0) + + @api.ondelete(at_uninstall=False) + def _check_sub_kit_product(self): + for line in self: + if line.is_kit_sub_line: + raise UserError( + "You cannot delete a kit sub-product line directly. " + "Delete the main kit product line instead." + ) + + def action_open_kit_wizard(self): + self.ensure_one() + return { + 'type': 'ir.actions.act_window', + 'name': 'Kit Sub Products', + 'res_model': 'sale.kit.wizard', + 'view_mode': 'form', + 'target': 'new', + 'context': { + 'default_order_line_id': self.id, + }, + } diff --git a/new_product_type/report/invoice_report.xml b/new_product_type/report/invoice_report.xml new file mode 100644 index 00000000000..5affdddc46a --- /dev/null +++ b/new_product_type/report/invoice_report.xml @@ -0,0 +1,15 @@ + + + + diff --git a/new_product_type/report/sale_order_portal_report.xml b/new_product_type/report/sale_order_portal_report.xml new file mode 100644 index 00000000000..650a1e906d9 --- /dev/null +++ b/new_product_type/report/sale_order_portal_report.xml @@ -0,0 +1,14 @@ + + + + \ No newline at end of file diff --git a/new_product_type/report/sale_order_report.xml b/new_product_type/report/sale_order_report.xml new file mode 100644 index 00000000000..38294cd59c4 --- /dev/null +++ b/new_product_type/report/sale_order_report.xml @@ -0,0 +1,14 @@ + + + + \ No newline at end of file diff --git a/new_product_type/security/ir.model.access.csv b/new_product_type/security/ir.model.access.csv new file mode 100644 index 00000000000..1e3cd7337d6 --- /dev/null +++ b/new_product_type/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_sale_kit_wizard,sale.kit.wizard,model_sale_kit_wizard,base.group_user,1,1,1,1 +access_sale_kit_wizard_line,sale.kit.wizard.line,model_sale_kit_wizard_line,base.group_user,1,1,1,1 diff --git a/new_product_type/views/product_views.xml b/new_product_type/views/product_views.xml new file mode 100644 index 00000000000..8179f19148a --- /dev/null +++ b/new_product_type/views/product_views.xml @@ -0,0 +1,17 @@ + + + + product.template.form.kit.inherit + product.template + + + + + + + + + + + diff --git a/new_product_type/views/sale_kit_wizard_views.xml b/new_product_type/views/sale_kit_wizard_views.xml new file mode 100644 index 00000000000..a28ada76bdb --- /dev/null +++ b/new_product_type/views/sale_kit_wizard_views.xml @@ -0,0 +1,34 @@ + + + + sale.kit.wizard.form + sale.kit.wizard + +
+ + + + + + + + + + + + + + +
+
+
+
+
+
diff --git a/new_product_type/views/sale_order_line_views.xml b/new_product_type/views/sale_order_line_views.xml new file mode 100644 index 00000000000..c2a912b49c6 --- /dev/null +++ b/new_product_type/views/sale_order_line_views.xml @@ -0,0 +1,36 @@ + + + sale.order.line.kit.button.inherit + sale.order + + + +