diff --git a/product_warranty/__init__.py b/product_warranty/__init__.py
new file mode 100644
index 00000000000..9b4296142f4
--- /dev/null
+++ b/product_warranty/__init__.py
@@ -0,0 +1,2 @@
+from . import models
+from . import wizard
diff --git a/product_warranty/__manifest__.py b/product_warranty/__manifest__.py
new file mode 100644
index 00000000000..3bbfb06c06c
--- /dev/null
+++ b/product_warranty/__manifest__.py
@@ -0,0 +1,18 @@
+{
+ 'name': 'Product Warranty',
+ 'version': '1.0',
+ 'depends': ['sale_management'],
+ 'description': """
+ This module adds warranty availabe feature for products.
+ """,
+ 'data': [
+ 'security/ir.model.access.csv',
+ 'wizard/add_warranty_wizard_views.xml',
+ 'views/product_template_views.xml',
+ 'views/warranty_config_views.xml',
+ 'views/warranty_config_menu.xml',
+ 'views/sale_order_views.xml',
+ ],
+ 'installable': True,
+ 'license': 'LGPL-3'
+}
diff --git a/product_warranty/models/__init__.py b/product_warranty/models/__init__.py
new file mode 100644
index 00000000000..d1ab36003fb
--- /dev/null
+++ b/product_warranty/models/__init__.py
@@ -0,0 +1,4 @@
+from . import product_template
+from . import warranty_config
+from . import sale_order
+from . import sale_order_line
diff --git a/product_warranty/models/product_template.py b/product_warranty/models/product_template.py
new file mode 100644
index 00000000000..9bf4fc57a36
--- /dev/null
+++ b/product_warranty/models/product_template.py
@@ -0,0 +1,7 @@
+from odoo import models, fields
+
+
+class ProductTemplate(models.Model):
+ _inherit = 'product.template'
+
+ is_warranty_available = fields.Boolean(string="Is Warranty Available")
diff --git a/product_warranty/models/sale_order.py b/product_warranty/models/sale_order.py
new file mode 100644
index 00000000000..c753a92723f
--- /dev/null
+++ b/product_warranty/models/sale_order.py
@@ -0,0 +1,16 @@
+from odoo import models
+
+
+class SaleOrder(models.Model):
+ _inherit = "sale.order"
+
+ def open_warranty_wizard(self):
+
+ return {
+ 'type': 'ir.actions.act_window',
+ 'name': 'Add Warranty',
+ 'res_model': 'add.warranty.wizard',
+ 'view_mode': 'form',
+ 'target': 'new',
+ 'context': {'default_sale_order_id': self.id},
+ }
diff --git a/product_warranty/models/sale_order_line.py b/product_warranty/models/sale_order_line.py
new file mode 100644
index 00000000000..69317244439
--- /dev/null
+++ b/product_warranty/models/sale_order_line.py
@@ -0,0 +1,39 @@
+from odoo import api, fields, models
+from odoo.exceptions import ValidationError
+
+
+class SaleOrderLine(models.Model):
+ _inherit = "sale.order.line"
+
+ has_warranty = fields.Boolean(string="Has Warranty")
+ order_line_linked_to_warranty = fields.Many2one(comodel_name="sale.order.line", copy=False, string="Warranty Product", ondelete="cascade")
+ is_warranty = fields.Boolean('Is Warranty')
+
+ @api.constrains('product_uom_qty')
+ def _check_warranty_qty_limit(self):
+ for line in self:
+ if line.is_warranty and line.order_line_linked_to_warranty:
+ linked_line = line.order_line_linked_to_warranty
+ if line.product_uom_qty > linked_line.product_uom_qty:
+ raise ValidationError(
+ f"The warranty quantity ({line.product_uom_qty}) cannot be more than the linked product quantity ({linked_line.product_uom_qty})."
+ )
+
+ elif not line.is_warranty:
+ warranty_line = self.search([
+ ('order_line_linked_to_warranty', '=', line.id),
+ ('is_warranty', '=', True)
+ ], limit=1)
+
+ if warranty_line:
+ if line.product_uom_qty < warranty_line.product_uom_qty:
+ raise ValidationError(
+ f"The quantity of the product ({line.product_uom_qty}) cannot be less than the linked warranty quantity ({warranty_line.product_uom_qty})."
+ )
+
+ @api.ondelete(at_uninstall=False)
+ def _unlink_except_confirmed(self):
+ super()._unlink_except_confirmed()
+ for line in self:
+ if line.is_warranty:
+ line.order_line_linked_to_warranty.write({'has_warranty': False})
diff --git a/product_warranty/models/warranty_config.py b/product_warranty/models/warranty_config.py
new file mode 100644
index 00000000000..26888806fbc
--- /dev/null
+++ b/product_warranty/models/warranty_config.py
@@ -0,0 +1,11 @@
+from odoo import models, fields
+
+
+class WarrantyConfig(models.Model):
+ _name = 'warranty.config'
+ _description = 'Warranty Configuration'
+
+ name = fields.Char(string="Name")
+ product = fields.Many2one('product.product', string="Warranty Product")
+ period = fields.Float(string='Period (in years)', default=1)
+ percentage = fields.Float(string="Percentage (%)")
diff --git a/product_warranty/security/ir.model.access.csv b/product_warranty/security/ir.model.access.csv
new file mode 100644
index 00000000000..548ac20a7fb
--- /dev/null
+++ b/product_warranty/security/ir.model.access.csv
@@ -0,0 +1,4 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+access_warranty_config,access.warranty.config,model_warranty_config,base.group_user,1,1,1,1
+access_add_warranty_wizard,access.add.warranty.wizard,model_add_warranty_wizard,base.group_user,1,1,1,1
+access_add_warranty_lines_wizard,access.add.warranty.lines.wizard,model_add_warranty_lines_wizard,base.group_user,1,1,1,1
diff --git a/product_warranty/views/product_template_views.xml b/product_warranty/views/product_template_views.xml
new file mode 100644
index 00000000000..7d31b43d88b
--- /dev/null
+++ b/product_warranty/views/product_template_views.xml
@@ -0,0 +1,13 @@
+
+
+
+ product.template.form.warranty.inherit
+ product.template
+
+
+
+
+
+
+
+
diff --git a/product_warranty/views/sale_order_views.xml b/product_warranty/views/sale_order_views.xml
new file mode 100644
index 00000000000..39bed4748cf
--- /dev/null
+++ b/product_warranty/views/sale_order_views.xml
@@ -0,0 +1,16 @@
+
+
+
+ sale.order.list.view.inherit.add.warranty
+ sale.order
+
+
+
+
+
+
+
+
diff --git a/product_warranty/views/warranty_config_menu.xml b/product_warranty/views/warranty_config_menu.xml
new file mode 100644
index 00000000000..0ffe0471223
--- /dev/null
+++ b/product_warranty/views/warranty_config_menu.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/product_warranty/views/warranty_config_views.xml b/product_warranty/views/warranty_config_views.xml
new file mode 100644
index 00000000000..8f8f76670a9
--- /dev/null
+++ b/product_warranty/views/warranty_config_views.xml
@@ -0,0 +1,21 @@
+
+
+
+ warranty.config.list
+ warranty.config
+
+
+
+
+
+
+
+
+
+
+
+ Warranty Configuration
+ warranty.config
+ list
+
+
diff --git a/product_warranty/wizard/__init__.py b/product_warranty/wizard/__init__.py
new file mode 100644
index 00000000000..ea5f586d769
--- /dev/null
+++ b/product_warranty/wizard/__init__.py
@@ -0,0 +1 @@
+from . import warranty_wizard
diff --git a/product_warranty/wizard/add_warranty_wizard_views.xml b/product_warranty/wizard/add_warranty_wizard_views.xml
new file mode 100644
index 00000000000..4ec09e8532e
--- /dev/null
+++ b/product_warranty/wizard/add_warranty_wizard_views.xml
@@ -0,0 +1,30 @@
+
+
+
+ add.warranty.wizard.form
+ add.warranty.wizard
+
+
+
+
+
+
+ Add Warranty
+ add.warranty.wizard
+ form
+ new
+
+
diff --git a/product_warranty/wizard/warranty_wizard.py b/product_warranty/wizard/warranty_wizard.py
new file mode 100644
index 00000000000..e9d581cf611
--- /dev/null
+++ b/product_warranty/wizard/warranty_wizard.py
@@ -0,0 +1,68 @@
+from dateutil.relativedelta import relativedelta
+from odoo import api, fields, models
+
+
+class AddWarrantyLinesWizard(models.TransientModel):
+ _name = "add.warranty.lines.wizard"
+ _description = "Warranty Line Wizard"
+
+ wizard_id = fields.Many2one('add.warranty.wizard', string="Wizard Reference", ondelete="cascade")
+ product_id = fields.Many2one(comodel_name="product.product", string="Product")
+ sale_order_line_id = fields.Many2one(comodel_name="sale.order.line", string="Sale Order Line")
+ warranty_name = fields.Many2one(comodel_name="warranty.config", string="Warranty Configuration")
+ end_date = fields.Date(readonly=True, string="End Date", compute="_compute_end_date")
+
+ @api.depends('warranty_name')
+ def _compute_end_date(self):
+ for record in self:
+ if record.warranty_name:
+ record.end_date = fields.Date.today() + relativedelta(years=record.warranty_name.period)
+ else:
+ record.end_date = False
+
+
+class AddWarrantyWizard(models.TransientModel):
+ _name = "add.warranty.wizard"
+ _description = "Add Warranty Wizard"
+
+ wizard_line_ids = fields.One2many(
+ comodel_name='add.warranty.lines.wizard',
+ inverse_name='wizard_id',
+ string="Warranty Lines"
+ )
+
+ def default_get(self, fields_list):
+ res = super().default_get(fields_list)
+
+ sale_order = self.env['sale.order'].browse(self.env.context.get("default_sale_order_id"))
+
+ sale_order_lines = sale_order.order_line.filtered(
+ lambda line: line.product_id.is_warranty_available and not line.has_warranty
+ )
+
+ res.update({
+ 'wizard_line_ids': [(0, 0, {
+ 'sale_order_line_id': line.id,
+ 'product_id': line.product_id.id,
+ }) for line in sale_order_lines]
+ })
+ return res
+
+ def action_add(self):
+ sale_order_line = self.env['sale.order.line']
+ warranty_lines = self.wizard_line_ids
+
+ for line in warranty_lines:
+ if line.warranty_name:
+ sale_order_line.create({
+ 'order_id': line.sale_order_line_id.order_id.id,
+ 'product_id': line.warranty_name.product.id,
+ 'product_uom_qty': 1,
+ 'price_unit': line.warranty_name.percentage / 100 * line.sale_order_line_id.price_unit,
+ 'name': f"{line.sale_order_line_id.product_id.name} Warranty, End Date: {line.end_date}",
+ 'order_line_linked_to_warranty': line.sale_order_line_id.id,
+ 'is_warranty': True,
+ })
+
+ line.sale_order_line_id.write({'has_warranty': True})
+ return True