diff --git a/product_kit_type/__init__.py b/product_kit_type/__init__.py
new file mode 100644
index 00000000000..9b4296142f4
--- /dev/null
+++ b/product_kit_type/__init__.py
@@ -0,0 +1,2 @@
+from . import models
+from . import wizard
diff --git a/product_kit_type/__manifest__.py b/product_kit_type/__manifest__.py
new file mode 100644
index 00000000000..6a47c526d3a
--- /dev/null
+++ b/product_kit_type/__manifest__.py
@@ -0,0 +1,17 @@
+{
+ 'name': 'Product Type Kit',
+ 'version': '1.0',
+ 'category': 'Sales/Sales',
+ 'summary': 'Allow selecting salesperson for pos order',
+ 'depends': ['sale_management'],
+ 'installable': True,
+ 'data': [
+ 'security/ir.model.access.csv',
+ 'views/product_template_form_view.xml',
+ 'views/sale_order_views.xml',
+ 'views/sale_order_customer_preview.xml',
+ 'wizard/views/sub_products_view.xml',
+ 'report/invoice_report.xml'
+ ],
+ 'license': 'LGPL-3',
+}
diff --git a/product_kit_type/models/__init__.py b/product_kit_type/models/__init__.py
new file mode 100644
index 00000000000..8f2f8c0cbc1
--- /dev/null
+++ b/product_kit_type/models/__init__.py
@@ -0,0 +1,3 @@
+from . import product_template
+from . import sale_order_line
+from . import sale_order
diff --git a/product_kit_type/models/product_template.py b/product_kit_type/models/product_template.py
new file mode 100644
index 00000000000..de473a0c2eb
--- /dev/null
+++ b/product_kit_type/models/product_template.py
@@ -0,0 +1,13 @@
+from odoo import models, fields, api
+
+
+class InheritedProductTemplate(models.Model):
+ _inherit = "product.template"
+
+ is_kit = fields.Boolean("Is kit?")
+ sub_products = fields.Many2many("product.product", string="Sub Products")
+
+ @api.onchange("is_kit")
+ def _onchange_is_kit(self):
+ if not self.is_kit:
+ self.sub_products = None
diff --git a/product_kit_type/models/sale_order.py b/product_kit_type/models/sale_order.py
new file mode 100644
index 00000000000..a06213b1ecc
--- /dev/null
+++ b/product_kit_type/models/sale_order.py
@@ -0,0 +1,7 @@
+from odoo import models, fields
+
+
+class InheritedSalesOrder(models.Model):
+ _inherit = "sale.order"
+
+ print_in_report = fields.Boolean()
diff --git a/product_kit_type/models/sale_order_line.py b/product_kit_type/models/sale_order_line.py
new file mode 100644
index 00000000000..318566ebf7a
--- /dev/null
+++ b/product_kit_type/models/sale_order_line.py
@@ -0,0 +1,31 @@
+from odoo import models, fields, api
+
+
+class InheritedSalesOrderLine(models.Model):
+ _inherit = "sale.order.line"
+
+ is_kit = fields.Boolean(related="product_template_id.is_kit", store=False)
+ is_sub_product = fields.Boolean()
+
+ def action_sub_products(self):
+ return {
+ 'name': f'Product: {self.name}',
+ 'view_mode': 'form',
+ 'res_model': 'product_kit_type.subproducts',
+ 'view_id': self.env.ref('product_kit_type.subproducts_view_form').id,
+ 'type': 'ir.actions.act_window',
+ 'target': 'new',
+ }
+
+ @api.ondelete(at_uninstall=False)
+ def unlink_with_sub_product_lines(self):
+ for line in self:
+ if not line.is_sub_product:
+ sale_order_line_id = line.id
+ sub_lines = self.search([
+ ('product_id', '=', line.product_id.id),
+ ('is_sub_product', '=', True),
+ ('linked_line_id', '=', sale_order_line_id)
+ ])
+ if sub_lines:
+ sub_lines.unlink()
diff --git a/product_kit_type/report/invoice_report.xml b/product_kit_type/report/invoice_report.xml
new file mode 100644
index 00000000000..7b0b7dcc8c0
--- /dev/null
+++ b/product_kit_type/report/invoice_report.xml
@@ -0,0 +1,8 @@
+
+
+
+
+ (doc.print_in_report) or (not line.is_sub_product)
+
+
+
diff --git a/product_kit_type/security/ir.model.access.csv b/product_kit_type/security/ir.model.access.csv
new file mode 100644
index 00000000000..0322bb94160
--- /dev/null
+++ b/product_kit_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_subproducts,access_product_kit_type.subproducts,product_kit_type.model_product_kit_type_subproducts,base.group_user,1,1,1,
+access_subproducts_lines,access_product_kit_type.subproducts.line,product_kit_type.model_product_kit_type_subproducts_line,base.group_user,1,1,1,
\ No newline at end of file
diff --git a/product_kit_type/views/product_template_form_view.xml b/product_kit_type/views/product_template_form_view.xml
new file mode 100644
index 00000000000..2344a8e9b07
--- /dev/null
+++ b/product_kit_type/views/product_template_form_view.xml
@@ -0,0 +1,14 @@
+
+
+
+ product.template.form.inherit.product_type_kit
+ product.template
+
+
+
+
+
+
+
+
+
diff --git a/product_kit_type/views/sale_order_customer_preview.xml b/product_kit_type/views/sale_order_customer_preview.xml
new file mode 100644
index 00000000000..595e9e6bbe2
--- /dev/null
+++ b/product_kit_type/views/sale_order_customer_preview.xml
@@ -0,0 +1,9 @@
+
+
+
+
+ (sale_order.print_in_report) or (not line.is_sub_product)
+
+
+
+
\ No newline at end of file
diff --git a/product_kit_type/views/sale_order_views.xml b/product_kit_type/views/sale_order_views.xml
new file mode 100644
index 00000000000..d143170f57c
--- /dev/null
+++ b/product_kit_type/views/sale_order_views.xml
@@ -0,0 +1,37 @@
+
+
+
+ sale.order.form.inherit.product_type_kit
+ sale.order
+
+
+
+
+
+
+
+ is_sub_product
+
+
+
+ is_sub_product
+
+
+
+ is_sub_product
+
+
+
+ is_sub_product
+
+
+
+ is_sub_product
+
+
+
+
+
+
+
+
diff --git a/product_kit_type/wizard/__init__.py b/product_kit_type/wizard/__init__.py
new file mode 100644
index 00000000000..a753ede14e2
--- /dev/null
+++ b/product_kit_type/wizard/__init__.py
@@ -0,0 +1,2 @@
+from . import sub_products
+from . import sub_product_line
diff --git a/product_kit_type/wizard/sub_product_line.py b/product_kit_type/wizard/sub_product_line.py
new file mode 100644
index 00000000000..af66a39f6ec
--- /dev/null
+++ b/product_kit_type/wizard/sub_product_line.py
@@ -0,0 +1,10 @@
+from odoo import models, fields
+
+
+class SubProductLineWizard(models.TransientModel):
+ _name = "product_kit_type.subproducts.line"
+
+ wizard_id = fields.Many2one("product_kit_type.subproducts", required=True)
+ product_id = fields.Many2one("product.product", string="Product")
+ product_qty = fields.Float(string="Quantity")
+ price_unit = fields.Float(string="Price")
diff --git a/product_kit_type/wizard/sub_products.py b/product_kit_type/wizard/sub_products.py
new file mode 100644
index 00000000000..4acb0f398a3
--- /dev/null
+++ b/product_kit_type/wizard/sub_products.py
@@ -0,0 +1,73 @@
+from odoo import models, fields, api
+
+
+class SubProductsWizard(models.TransientModel):
+ _name = "product_kit_type.subproducts"
+ _description = "Sub Products"
+
+ sale_order_line_id = fields.Many2one("sale.order.line", string="Sale Order Line")
+ sub_product_line_ids = fields.One2many("product_kit_type.subproducts.line", "wizard_id", string="Sub Products")
+
+ @api.model
+ def default_get(self, fields_list):
+ res = super().default_get(fields_list)
+
+ sale_order_line = self.env["sale.order.line"].browse(self.env.context.get("active_id"))
+
+ if sale_order_line:
+ res["sale_order_line_id"] = sale_order_line.id
+
+ existing_lines = self.env["sale.order.line"].search([
+ ("linked_line_id", "=", sale_order_line.id),
+ ("is_sub_product", "=", True)
+ ])
+
+ sub_product_lines = []
+ if existing_lines:
+ for line in existing_lines:
+ sub_product_lines.append((0, 0, {
+ "product_id": line.product_id.id,
+ "product_qty": line.product_uom_qty,
+ "price_unit": line.product_id.list_price,
+ }))
+ else:
+ for sub_product in sale_order_line.product_template_id.sub_products:
+ sub_product_lines.append((0, 0, {
+ "product_id": sub_product.id,
+ "product_qty": 1,
+ "price_unit": sub_product.list_price,
+ }))
+
+ res["sub_product_line_ids"] = sub_product_lines
+ return res
+
+ def action_confirm(self):
+ self.ensure_one()
+ sale_order = self.sale_order_line_id.order_id
+ existing_sub_lines = sale_order.order_line.filtered(lambda line: line.is_sub_product and line.linked_line_id.id == self.sale_order_line_id.id)
+ existing_sub_product_map = {line.product_id.id: line for line in existing_sub_lines}
+ total_price = 0
+
+ for sub_product in self.sub_product_line_ids:
+ if sub_product.product_id.id in existing_sub_product_map:
+ existing_line = existing_sub_product_map[sub_product.product_id.id]
+ existing_line.write({
+ "product_uom_qty": sub_product.product_qty,
+ "price_unit": 0.00,
+ })
+ else:
+ self.env["sale.order.line"].create({
+ "name": sub_product.product_id.name,
+ "order_id": self.sale_order_line_id.order_id.id,
+ "product_id": sub_product.product_id.id,
+ "product_uom_qty": sub_product.product_qty,
+ "price_unit": 0.00,
+ "is_sub_product": True,
+ 'linked_line_id': self.sale_order_line_id.id,
+ })
+
+ total_price += sub_product.product_qty * sub_product.price_unit
+
+ self.sale_order_line_id.write({'price_unit': total_price})
+
+ return {"type": "ir.actions.act_window_close"}
diff --git a/product_kit_type/wizard/views/sub_products_view.xml b/product_kit_type/wizard/views/sub_products_view.xml
new file mode 100644
index 00000000000..89330e6c2d3
--- /dev/null
+++ b/product_kit_type/wizard/views/sub_products_view.xml
@@ -0,0 +1,22 @@
+
+
+
+ product_kit_type.subproducts.form
+ product_kit_type.subproducts
+
+
+
+
+