diff --git a/product_kit/__init__.py b/product_kit/__init__.py new file mode 100644 index 00000000000..aee8895e7a3 --- /dev/null +++ b/product_kit/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import wizards diff --git a/product_kit/__manifest__.py b/product_kit/__manifest__.py new file mode 100644 index 00000000000..213f929d586 --- /dev/null +++ b/product_kit/__manifest__.py @@ -0,0 +1,20 @@ +{ + 'name': "product_kit", + 'version': '1.0', + 'license': 'LGPL-3', + 'depends': ['sale_management'], + 'author': "Kalpan Desai", + 'category': 'Sales/Sales', + 'description': """ + Module specifically designed to manage product as kits in Odoo. + """, + 'installable': True, + 'application': True, + 'data': [ + 'views/product_views.xml', + 'wizards/sub_product_wizard_view.xml', + 'views/sale_order_view.xml', + 'views/sale_portal_view.xml', + 'security/ir.model.access.csv', + ], +} diff --git a/product_kit/models/__init__.py b/product_kit/models/__init__.py new file mode 100644 index 00000000000..60ef5571f3b --- /dev/null +++ b/product_kit/models/__init__.py @@ -0,0 +1,4 @@ +from . import sale_order_line +from . import product_template +from . import sale_order +from . import sale_invoice diff --git a/product_kit/models/product_template.py b/product_kit/models/product_template.py new file mode 100644 index 00000000000..8539052f070 --- /dev/null +++ b/product_kit/models/product_template.py @@ -0,0 +1,9 @@ +from odoo import fields, models + + +class ProductTemplate(models.Model): + _inherit = 'product.template' + + is_kit = fields.Boolean(string='Is Kit', help='Check this box if the product is a kit that contains other products.', default=False) + + kit_product_ids = fields.Many2many('product.product', string='Sub Products', help='Products included in this kit.') diff --git a/product_kit/models/sale_invoice.py b/product_kit/models/sale_invoice.py new file mode 100644 index 00000000000..9fd5676945f --- /dev/null +++ b/product_kit/models/sale_invoice.py @@ -0,0 +1,7 @@ +from odoo import fields, models + + +class AccountMove(models.Model): + _inherit = 'account.move' + + is_printable_kit = fields.Boolean(string="Show Sub-Products", default=True) diff --git a/product_kit/models/sale_order.py b/product_kit/models/sale_order.py new file mode 100644 index 00000000000..4ac2ef6eed5 --- /dev/null +++ b/product_kit/models/sale_order.py @@ -0,0 +1,54 @@ +from odoo import api, fields, models + + +class SaleOrder(models.Model): + _inherit = "sale.order" + + is_printable_kit = fields.Boolean( + string="Print Kit in Report", + default=False, + help="If checked, kit products will be printed in the report." + ) + + has_kit = fields.Boolean( + string="Has Kit Product", + compute="_compute_has_kit", + store=True, + default=False, + ) + + @api.depends('order_line', 'order_line.is_kit') + def _compute_has_kit(self): + for order in self: + order.has_kit = any(line.is_kit for line in order.order_line) + + def _prepare_invoice(self): + """ + Prepare the dict of values to create the new invoice for a sales order. + """ + invoice_vals = super()._prepare_invoice() + # Copy the value of your toggle to the invoice + invoice_vals['is_printable_kit'] = self.is_printable_kit + return invoice_vals + + def _create_invoices(self, grouped=False, final=False, date=None): + # First, create the invoice(s) using the standard Odoo method. + # This will include lines for the main product and all sub-products. + invoices = super()._create_invoices(grouped, final, date) + + # Now, loop through the newly created invoices to apply your logic + for invoice in invoices: + # Check the toggle copied from the Sale Order + if not invoice.is_printable_kit: + + # Find all invoice lines that came from a sale order line + # marked as a sub-product. + sub_product_invoice_lines = invoice.invoice_line_ids.filtered( + lambda line: line.sale_line_ids and line.sale_line_ids[0].is_subproduct + ) + + # If any sub-product lines were found, delete them + if sub_product_invoice_lines: + sub_product_invoice_lines.unlink() + + return invoices diff --git a/product_kit/models/sale_order_line.py b/product_kit/models/sale_order_line.py new file mode 100644 index 00000000000..737cae8ad3b --- /dev/null +++ b/product_kit/models/sale_order_line.py @@ -0,0 +1,40 @@ +from odoo import fields, models +from odoo.exceptions import UserError + + +class SaleOrderLine(models.Model): + _inherit = "sale.order.line" + + is_kit = fields.Boolean(related="product_template_id.is_kit") + parent_product_id = fields.Many2one('product.product') + is_subproduct = fields.Boolean(default=False) + + def unlink(self): + """ + Override unlink to also delete sub-product lines when a kit line is deleted. + It also prevents the direct deletion of a sub-product line. + """ + # Prevent direct deletion of sub-product lines. + sub_product_lines = self.filtered('is_subproduct') + if sub_product_lines: + # Find the parent kit lines for these sub-products + parent_lines = self.env['sale.order.line'].search([ + ('order_id', 'in', sub_product_lines.mapped('order_id').ids), + ('product_id', 'in', sub_product_lines.mapped('parent_product_id').ids), + ('is_kit', '=', True) + ]) + # If any of the required parent lines are not in the current deletion set, raise an error. + if parent_lines - self: + raise UserError("You cannot delete a component of a kit directly. Please remove the main kit product instead.") + + # Cascade delete: find all sub-products of kits being deleted. + sub_products_to_delete = self.env['sale.order.line'] + for line in self.filtered('is_kit'): + sub_products_to_delete |= self.search([ + ('order_id', '=', line.order_id.id), + ('parent_product_id', '=', line.product_id.id), + ('is_subproduct', '=', True) + ]) + + all_records_to_delete = self | sub_products_to_delete + return super(SaleOrderLine, all_records_to_delete).unlink() diff --git a/product_kit/report/sale_order_report_view.xml b/product_kit/report/sale_order_report_view.xml new file mode 100644 index 00000000000..ffa9f712ad4 --- /dev/null +++ b/product_kit/report/sale_order_report_view.xml @@ -0,0 +1,8 @@ + + + + diff --git a/product_kit/security/ir.model.access.csv b/product_kit/security/ir.model.access.csv new file mode 100755 index 00000000000..2e34c350689 --- /dev/null +++ b/product_kit/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_subproduct,access_subproduct,model_product_kit_subproduct_wizard,base.group_user,1,1,1,1 +access_subproduct_line,access_subproduct_line,model_product_kit_subproduct_line,base.group_user,1,1,1,1 diff --git a/product_kit/views/product_views.xml b/product_kit/views/product_views.xml new file mode 100755 index 00000000000..625172cd296 --- /dev/null +++ b/product_kit/views/product_views.xml @@ -0,0 +1,14 @@ + + + + product.template.form.kit + product.template + + + + + + + + + diff --git a/product_kit/views/sale_order_view.xml b/product_kit/views/sale_order_view.xml new file mode 100755 index 00000000000..4d843898131 --- /dev/null +++ b/product_kit/views/sale_order_view.xml @@ -0,0 +1,33 @@ + + + + sale.order.form.view.kit + sale.order + + + +