From 31bbad631c3ac112b0a0a12e1bc8b90445c71702 Mon Sep 17 00:00:00 2001 From: rodh-odoo Date: Thu, 24 Jul 2025 10:45:10 +0530 Subject: [PATCH] [ADD] product_type_kit: kit product added with sub product selection wizards Implemented 'Is Kit' boolean field on product form to enable kit functionality Added many2many field to link subproducts with main kit product Implemented conditional visibility for subproduct when 'Is Kit' is enabled Enabled button on sale order line to configure subproducts via wizard Main product price auto-calculates from subproduct total Implemented checkbox to control visibility of subproducts in print reports Ensured deletion of main product also removes all related subproduct lines --- product_type_kit/__init__.py | 2 + product_type_kit/__manifest__.py | 17 +++++++ product_type_kit/models/__init__.py | 3 ++ product_type_kit/models/product_template.py | 16 +++++++ product_type_kit/models/sale_order.py | 18 ++++++++ product_type_kit/models/sale_order_line.py | 42 +++++++++++++++++ product_type_kit/security/ir.model.access.csv | 6 +++ .../views/kit_subproduct_wizards.xml | 25 ++++++++++ .../views/product_template_views.xml | 14 ++++++ .../views/sale_order_line_views.xml | 18 ++++++++ product_type_kit/views/sale_order_views.xml | 16 +++++++ product_type_kit/wizards/__init__.py | 2 + .../wizards/kit_sub_product_line.py | 11 +++++ .../wizards/kit_subproduct_wizards.py | 46 +++++++++++++++++++ 14 files changed, 236 insertions(+) create mode 100644 product_type_kit/__init__.py create mode 100644 product_type_kit/__manifest__.py create mode 100644 product_type_kit/models/__init__.py create mode 100644 product_type_kit/models/product_template.py create mode 100644 product_type_kit/models/sale_order.py create mode 100644 product_type_kit/models/sale_order_line.py create mode 100644 product_type_kit/security/ir.model.access.csv create mode 100644 product_type_kit/views/kit_subproduct_wizards.xml create mode 100644 product_type_kit/views/product_template_views.xml create mode 100644 product_type_kit/views/sale_order_line_views.xml create mode 100644 product_type_kit/views/sale_order_views.xml create mode 100644 product_type_kit/wizards/__init__.py create mode 100644 product_type_kit/wizards/kit_sub_product_line.py create mode 100644 product_type_kit/wizards/kit_subproduct_wizards.py diff --git a/product_type_kit/__init__.py b/product_type_kit/__init__.py new file mode 100644 index 00000000000..aee8895e7a3 --- /dev/null +++ b/product_type_kit/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import wizards diff --git a/product_type_kit/__manifest__.py b/product_type_kit/__manifest__.py new file mode 100644 index 00000000000..bc04b060684 --- /dev/null +++ b/product_type_kit/__manifest__.py @@ -0,0 +1,17 @@ +{ + 'name': 'Product Type Kit', + 'version': '1.0', + 'category': 'Sales', + 'license': 'LGPL-3', + 'summary': 'Custom product kit functionality without using BoM', + 'depends': ['sale', 'product'], + 'data': [ + 'security/ir.model.access.csv', + 'views/product_template_views.xml', + 'views/sale_order_views.xml', + 'views/sale_order_line_views.xml', + 'views/kit_subproduct_wizards.xml', + ], + 'installable': True, + 'application': True, +} diff --git a/product_type_kit/models/__init__.py b/product_type_kit/models/__init__.py new file mode 100644 index 00000000000..53fa79af356 --- /dev/null +++ b/product_type_kit/models/__init__.py @@ -0,0 +1,3 @@ +from . import product_template +from . import sale_order +from . import sale_order_line diff --git a/product_type_kit/models/product_template.py b/product_type_kit/models/product_template.py new file mode 100644 index 00000000000..3aeffd94610 --- /dev/null +++ b/product_type_kit/models/product_template.py @@ -0,0 +1,16 @@ +from odoo import fields, models + + +class ProductTemplate(models.Model): + _inherit = 'product.template' + + is_kit = fields.Boolean(string='Is Kit') + sub_product_ids = fields.Many2many( + 'product.product', + 'product_kit_sub_products_rel', + 'kit_id', + 'sub_product_id', + string='Sub Products', + domain="[('type', '=', 'product')]" + ) + show_subproducts_on_report = fields.Boolean(string='Print Subproducts on Report') diff --git a/product_type_kit/models/sale_order.py b/product_type_kit/models/sale_order.py new file mode 100644 index 00000000000..31b6729e6a6 --- /dev/null +++ b/product_type_kit/models/sale_order.py @@ -0,0 +1,18 @@ +from odoo import api, fields, models + + +class SaleOrder(models.Model): + _inherit = 'sale.order' + + print_kit_details = fields.Boolean(string="Print Kit Details") + display_in_report = fields.Boolean(string="Print in report?", default=True) + + @api.model + def unlink(self): + for order in self: + for line in order.order_line: + if not line.is_subproduct: + # delete sub lines linked to this main line + sub_lines = order.order_line.filtered(lambda l: l.kit_parent_line_id == line.id) + sub_lines.unlink() + return super().unlink() diff --git a/product_type_kit/models/sale_order_line.py b/product_type_kit/models/sale_order_line.py new file mode 100644 index 00000000000..a1018d81189 --- /dev/null +++ b/product_type_kit/models/sale_order_line.py @@ -0,0 +1,42 @@ +# models/sale_order_line.py +from odoo import api, fields, models +from odoo.exceptions import UserError + + +class SaleOrder(models.Model): + _inherit = 'sale.order.line' + + kit_parent_line_id = fields.Many2one('sale.order.line', string='Parent Kit Line', ondelete='cascade') + is_kit_product = fields.Boolean( + string="Is Kit Product", compute="_compute_is_kit", store=True + ) + is_subproduct = fields.Boolean(string="Is Sub Product", default=False) + + @api.depends("product_id") + def _compute_is_kit(self): + for line in self: + line.is_kit_product = line.product_id.product_tmpl_id.is_kit + + def write(self, vals): + for line in self: + if line.kit_parent_line_id: + raise UserError("Sub product lines cannot be edited manually.") + return super().write(vals) + + def open_kit_wizard(self): + self.ensure_one() + return { + 'name': f'Configure Kit: {self.product_id.name}', + 'type': 'ir.actions.act_window', + 'res_model': 'kit.sub.product.wizard', + 'view_mode': 'form', + 'target': 'new', + 'context': { + 'default_order_line_id': self.id, + } + } + + @api.onchange('kit_parent_line_id') + def _onchange_disable_edit(self): + if self.kit_parent_line_id: + self.update({'price_unit': 0}) diff --git a/product_type_kit/security/ir.model.access.csv b/product_type_kit/security/ir.model.access.csv new file mode 100644 index 00000000000..ec1e4e8cc81 --- /dev/null +++ b/product_type_kit/security/ir.model.access.csv @@ -0,0 +1,6 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_product_template,access_product_template,model_product_template,base.group_user,1,1,1,1 +access_sale_order_line,access_sale_order_line,model_sale_order_line,base.group_user,1,1,1,1 +access_sale_order,access_sale_order,model_sale_order,base.group_user,1,1,1,1 +access_kit_sub_product_wizard,access_kit_sub_product_wizard,model_kit_sub_product_wizard,,1,1,1,1 +access_kit_sub_product_line,access_kit_sub_product_line,model_kit_sub_product_line,,1,1,1,1 diff --git a/product_type_kit/views/kit_subproduct_wizards.xml b/product_type_kit/views/kit_subproduct_wizards.xml new file mode 100644 index 00000000000..ba25d6f0d58 --- /dev/null +++ b/product_type_kit/views/kit_subproduct_wizards.xml @@ -0,0 +1,25 @@ + + + + kit.sub.product.wizard + kit.sub.product.wizard + +
+ + + + + + + + + + +
+
+
+
+
+
diff --git a/product_type_kit/views/product_template_views.xml b/product_type_kit/views/product_template_views.xml new file mode 100644 index 00000000000..f2dddce8f68 --- /dev/null +++ b/product_type_kit/views/product_template_views.xml @@ -0,0 +1,14 @@ + + + + product.template.form.kit + product.template + + + + + + + + + diff --git a/product_type_kit/views/sale_order_line_views.xml b/product_type_kit/views/sale_order_line_views.xml new file mode 100644 index 00000000000..73ff97bb4f6 --- /dev/null +++ b/product_type_kit/views/sale_order_line_views.xml @@ -0,0 +1,18 @@ + + + + sale.order.line.tree.readonly.kit + sale.order + + + + 1 + {'readonly': [('is_subproduct', '=', True)]} + + + 1 + {'readonly': [('is_subproduct', '=', True)]} + + + + diff --git a/product_type_kit/views/sale_order_views.xml b/product_type_kit/views/sale_order_views.xml new file mode 100644 index 00000000000..b2a7bd970ac --- /dev/null +++ b/product_type_kit/views/sale_order_views.xml @@ -0,0 +1,16 @@ + + + + sale.order.line.kit.button + sale.order + + + +