diff --git a/new_producttype/__init__.py b/new_producttype/__init__.py
new file mode 100644
index 00000000000..9b4296142f4
--- /dev/null
+++ b/new_producttype/__init__.py
@@ -0,0 +1,2 @@
+from . import models
+from . import wizard
diff --git a/new_producttype/__manifest__.py b/new_producttype/__manifest__.py
new file mode 100644
index 00000000000..c911da1919c
--- /dev/null
+++ b/new_producttype/__manifest__.py
@@ -0,0 +1,17 @@
+{
+ 'name': "New Product Type",
+ 'version': '0.1',
+ 'depends': ['sale'],
+ 'sequence': 1,
+ 'application': True,
+ 'installable': True,
+ 'data': [
+ "security/ir.model.access.csv",
+ "report/sub_product_report.xml",
+ "wizard/sub_product_kit_wizard_view.xml",
+ "views/product_views.xml",
+ "views/sale_order_view.xml",
+ "views/sub_product_customer_preview.xml",
+ ],
+ 'license': 'AGPL-3'
+}
diff --git a/new_producttype/models/__init__.py b/new_producttype/models/__init__.py
new file mode 100644
index 00000000000..8f2f8c0cbc1
--- /dev/null
+++ b/new_producttype/models/__init__.py
@@ -0,0 +1,3 @@
+from . import product_template
+from . import sale_order_line
+from . import sale_order
diff --git a/new_producttype/models/product_template.py b/new_producttype/models/product_template.py
new file mode 100644
index 00000000000..f1ec80566f9
--- /dev/null
+++ b/new_producttype/models/product_template.py
@@ -0,0 +1,25 @@
+from odoo import fields, models, api
+from odoo.exceptions import ValidationError
+
+
+class ProductTemplate(models.Model):
+ _inherit = 'product.template'
+
+ is_kit = fields.Boolean(default=False, string='Is kit')
+ sub_products_ids = fields.Many2many('product.product', string='Sub products')
+
+ @api.constrains("sub_products_ids")
+ def check_sub_product_not_itself(self):
+ for record in self:
+ if record.id in record.sub_products_ids.mapped("product_tmpl_id.id"):
+ raise ValidationError(
+ "A product cannot be added as its own sub-product"
+ )
+
+ @api.constrains("sub_products_ids", "is_kit")
+ def check_sub_product(self):
+ for record in self:
+ if record.is_kit and not record.sub_products_ids:
+ raise ValidationError(
+ "A kit product must have at least one sub-product"
+ )
diff --git a/new_producttype/models/sale_order.py b/new_producttype/models/sale_order.py
new file mode 100644
index 00000000000..64b6caff4ce
--- /dev/null
+++ b/new_producttype/models/sale_order.py
@@ -0,0 +1,7 @@
+from odoo import models, fields
+
+
+class SaleOrder(models.Model):
+ _inherit = 'sale.order'
+
+ print_report = fields.Boolean(string="Print in Report")
diff --git a/new_producttype/models/sale_order_line.py b/new_producttype/models/sale_order_line.py
new file mode 100644
index 00000000000..d1da7861180
--- /dev/null
+++ b/new_producttype/models/sale_order_line.py
@@ -0,0 +1,23 @@
+from odoo import fields, models
+
+
+class SaleOrderLine(models.Model):
+ _inherit = 'sale.order.line'
+
+ is_kit = fields.Boolean(string='Is kit', related='product_id.is_kit')
+ main_product_line_id = fields.Many2one('sale.order.line', ondelete='cascade')
+ sub_products_ids = fields.Many2many(
+ "product.product",
+ related="product_template_id.sub_products_ids",
+ string="Sub Products",
+ )
+ last_price = fields.Float()
+
+ def action_open_kit_wizard(self):
+ return {
+ 'type': 'ir.actions.act_window',
+ 'name': 'sub.product.kit.wizard.view',
+ 'res_model': 'sub.product.kit.wizard',
+ 'view_mode': 'form',
+ 'target': 'new',
+ }
diff --git a/new_producttype/report/sub_product_report.xml b/new_producttype/report/sub_product_report.xml
new file mode 100644
index 00000000000..3e705c34cfc
--- /dev/null
+++ b/new_producttype/report/sub_product_report.xml
@@ -0,0 +1,8 @@
+
+
+
+
+ (doc.print_report) or (not line.main_product_line_id)
+
+
+
diff --git a/new_producttype/security/ir.model.access.csv b/new_producttype/security/ir.model.access.csv
new file mode 100644
index 00000000000..2ec380ca018
--- /dev/null
+++ b/new_producttype/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_sub_product_kit_wizard,access_sub_product_kit_wizard,model_sub_product_kit_wizard,base.group_user,1,1,1,1
+access_sub_product_line_kit_wizard,access_sub_product_line_kit_wizard,model_sub_product_line_kit_wizard,base.group_user,1,1,1,1
diff --git a/new_producttype/views/product_views.xml b/new_producttype/views/product_views.xml
new file mode 100644
index 00000000000..2924bde217e
--- /dev/null
+++ b/new_producttype/views/product_views.xml
@@ -0,0 +1,14 @@
+
+
+
+ product.template.form.view.inherit
+ product.template
+
+
+
+
+
+
+
+
+
diff --git a/new_producttype/views/sale_order_view.xml b/new_producttype/views/sale_order_view.xml
new file mode 100644
index 00000000000..b01a32dd595
--- /dev/null
+++ b/new_producttype/views/sale_order_view.xml
@@ -0,0 +1,35 @@
+
+
+
+ view.order.form.inherit.sub.product
+ sale.order
+
+
+
+
+
+
+ main_product_line_id
+
+
+ main_product_line_id
+
+
+ main_product_line_id
+
+
+ main_product_line_id
+
+
+ main_product_line_id
+
+
+
+
+
+
+
diff --git a/new_producttype/views/sub_product_customer_preview.xml b/new_producttype/views/sub_product_customer_preview.xml
new file mode 100644
index 00000000000..e661cffea5d
--- /dev/null
+++ b/new_producttype/views/sub_product_customer_preview.xml
@@ -0,0 +1,8 @@
+
+
+
+
+ (sale_order.print_report) or (not line.main_product_line_id)
+
+
+
diff --git a/new_producttype/wizard/__init__.py b/new_producttype/wizard/__init__.py
new file mode 100644
index 00000000000..2b0626748af
--- /dev/null
+++ b/new_producttype/wizard/__init__.py
@@ -0,0 +1,2 @@
+from . import sub_product_kit_wizard
+from . import sub_product_line_kit_wizard
diff --git a/new_producttype/wizard/sub_product_kit_wizard.py b/new_producttype/wizard/sub_product_kit_wizard.py
new file mode 100644
index 00000000000..4b60d9b2710
--- /dev/null
+++ b/new_producttype/wizard/sub_product_kit_wizard.py
@@ -0,0 +1,85 @@
+from odoo import fields, models, api, Command
+
+
+class SubProductKitWizard(models.TransientModel):
+ _name = 'sub.product.kit.wizard'
+
+ sale_order_line_id = fields.Many2one('sale.order.line', required=True)
+ sub_products_ids = fields.One2many('sub.product.line.kit.wizard', 'sub_products_line_id')
+
+ @api.model
+ def default_get(self, fields_list):
+ res = super().default_get(fields_list)
+ sale_order_line_id = self._context.get("active_id")
+ if sale_order_line_id:
+ sale_order_line = self.env["sale.order.line"].browse(sale_order_line_id)
+ existing_lines = self.env["sale.order.line"].search([
+ ("main_product_line_id", "=", sale_order_line_id)
+ ])
+ sub_product_line_wizard = self.env["sub.product.line.kit.wizard"]
+ line_values = []
+ if existing_lines:
+ vals_list = [
+ {
+ "product_id": line.product_id.id,
+ "price": line.price_unit,
+ "quantity": line.product_uom_qty,
+ }
+ for line in existing_lines
+ ]
+ else:
+ vals_list = [
+ {
+ "product_id": sub_product.id,
+ "price": sub_product.list_price,
+ "quantity": 1,
+ }
+ for sub_product in sale_order_line.product_id.sub_products_ids
+ ]
+ if vals_list:
+ curr_products = sub_product_line_wizard.create(vals_list)
+ line_values = [Command.link(product.id) for product in curr_products]
+ res.update({
+ "sub_products_ids": line_values,
+ "sale_order_line_id": sale_order_line_id,
+ })
+ return res
+
+ def action_confirm(self):
+ existing_sub_product_lines_map = {
+ line.product_id.id: line
+ for line in self.env["sale.order.line"].search(
+ [
+ ("main_product_line_id", "=", self.sale_order_line_id.id),
+ ("order_id", "=", self.sale_order_line_id.order_id.id)
+ ]
+ )
+ }
+ total_of_sub_product_price = 0
+ for rec in self.sub_products_ids:
+ product_id = rec.product_id.id
+ existing_line = existing_sub_product_lines_map.get(product_id)
+ if existing_line:
+ existing_line.write({
+ "product_uom_qty": rec.quantity,
+ "last_price": rec.price,
+ "price_unit": 0,
+ })
+ else:
+ self.env["sale.order.line"].create(
+ {
+ "order_id": self.sale_order_line_id.order_id.id,
+ "product_id": rec.product_id.id,
+ "product_uom_qty": rec.quantity,
+ "last_price": rec.price,
+ "price_unit": 0,
+ "main_product_line_id": self.sale_order_line_id.id,
+ }
+ )
+ total_of_sub_product_price += (rec.quantity * rec.price)
+ total_of_sub_product_price += (
+ self.sale_order_line_id.product_uom_qty
+ * self.sale_order_line_id.product_template_id.list_price
+ )
+ self.sale_order_line_id.price_unit = total_of_sub_product_price
+ return True
diff --git a/new_producttype/wizard/sub_product_kit_wizard_view.xml b/new_producttype/wizard/sub_product_kit_wizard_view.xml
new file mode 100644
index 00000000000..19359466c21
--- /dev/null
+++ b/new_producttype/wizard/sub_product_kit_wizard_view.xml
@@ -0,0 +1,24 @@
+
+
+
+ sub.product.kit.wizard.view
+ sub.product.kit.wizard
+
+
+
+
+
diff --git a/new_producttype/wizard/sub_product_line_kit_wizard.py b/new_producttype/wizard/sub_product_line_kit_wizard.py
new file mode 100644
index 00000000000..5e6072327f0
--- /dev/null
+++ b/new_producttype/wizard/sub_product_line_kit_wizard.py
@@ -0,0 +1,10 @@
+from odoo import fields, models
+
+
+class SubProductLineKitWizard(models.TransientModel):
+ _name = 'sub.product.line.kit.wizard'
+
+ sub_products_line_id = fields.Many2one('sub.product.kit.wizard')
+ product_id = fields.Many2one('product.product', required=True)
+ quantity = fields.Float(default=0.0)
+ price = fields.Float(default=0)