Skip to content

Commit 71b1c0c

Browse files
committed
[ADD] product_warranty: add warranty config and wizard to auto-add warranty
Purpose: Allow sales users to configure and apply product warranties with automatic pricing. Approach: Added model to define warranty durations and percentages per product Implemented 'Add Warranty' wizard to attach warranties to sale order lines Auto-generated warranty lines with computed prices based on config Handled end date calculation and display in warranty lines Included warranty lines in SO and invoice with correct amounts Impact: Simplifies warranty management and ensures accurate, automated pricing in sales flow.
1 parent fbf9ee9 commit 71b1c0c

18 files changed

+403
-0
lines changed

product_warranty/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Part of Odoo. See LICENSE file for full copyright and licensing details.
2+
3+
from . import models
4+
from . import wizard

product_warranty/__manifest__.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Part of Odoo. See LICENSE file for full copyright and licensing details.
2+
3+
{
4+
"name": "Warranty Management for Products",
5+
"version": "1.0",
6+
"category": "Sales",
7+
"summary": "Manage product warranties",
8+
"description": """
9+
This module allows you to manage product warranties, including warranty periods and conditions.
10+
""",
11+
"depends": ["sale_management"],
12+
"data": [
13+
"security/ir.model.access.csv",
14+
"views/product_warranty.xml",
15+
"views/product_warranty_config_views.xml",
16+
"views/product_warranty_config_menus.xml",
17+
"views/sale_order_views.xml",
18+
"wizard/product_warranty_wizard.xml",
19+
],
20+
"installable": True,
21+
"license": "LGPL-3",
22+
}

product_warranty/models/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Part of Odoo. See LICENSE file for full copyright and licensing details.
2+
3+
from . import product_warranty
4+
from . import product_warranty_config
5+
from . import sale_order
6+
from . import sale_order_line
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Part of Odoo. See LICENSE file for full copyright and licensing details.
2+
3+
from odoo import fields, models
4+
5+
6+
class ProductTemplate(models.Model):
7+
_inherit = "product.template"
8+
9+
warranty = fields.Boolean(string="Warranty", help="Is warranty available for this product?")
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Part of Odoo. See LICENSE file for full copyright and licensing details.
2+
3+
from odoo import fields, models
4+
5+
6+
class ProductWarrantyConfig(models.Model):
7+
_name = "product.warranty.config"
8+
_description = "Product Warranty Configuration"
9+
10+
name = fields.Char(string="Warranty Name", required=True)
11+
percentage = fields.Float(string="Percentage", help="Warranty percentage")
12+
year = fields.Integer(
13+
string="Warranty Period (Year)",
14+
required=True,
15+
help="Warranty period in years"
16+
)
17+
product_id = fields.Many2one(
18+
comodel_name="product.product",
19+
string="Product",
20+
help="Warranty product name"
21+
)

product_warranty/models/sale_order.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Part of Odoo. See LICENSE file for full copyright and licensing details.
2+
3+
from odoo import api, fields, models
4+
5+
6+
class SaleOrder(models.Model):
7+
_inherit = 'sale.order'
8+
9+
has_warranty_product = fields.Boolean(
10+
string="Has Warranty Product",
11+
compute='_compute_has_warranty_product',
12+
store=True
13+
)
14+
15+
@api.depends('order_line.product_id')
16+
def _compute_has_warranty_product(self):
17+
for order in self:
18+
order.has_warranty_product = any(line.product_id.warranty for line in order.order_line)
19+
20+
def open_add_warranty_wizard(self):
21+
self.ensure_one()
22+
return {
23+
'type': 'ir.actions.act_window',
24+
'name': 'Add Warranty Products',
25+
'res_model': 'product.warranty.wizard',
26+
'view_mode': 'form',
27+
'target': 'new',
28+
'context': {'default_sale_order_id': self.id}
29+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Part of Odoo. See LICENSE file for full copyright and licensing details.
2+
3+
from odoo import fields, models
4+
5+
6+
class SaleOrderLine(models.Model):
7+
_inherit = "sale.order.line"
8+
9+
source_order_line_id = fields.Many2one(
10+
comodel_name="sale.order.line",
11+
string="Source Order Line",
12+
ondelete="cascade",
13+
)
14+
15+
def write(self, vals):
16+
res = super().write(vals)
17+
if "price_unit" in vals:
18+
for line in self:
19+
if not line.source_order_line_id:
20+
warranty_lines = self.env["sale.order.line"].search(
21+
[("source_order_line_id", "=", line.id)]
22+
)
23+
for warranty_line in warranty_lines:
24+
config = self.env["product.warranty.config"].search(
25+
[("product_id", "=", warranty_line.product_id.id)], limit=1
26+
)
27+
if config:
28+
warranty_line.price_unit = line.price_unit * (
29+
config.percentage / 100.0
30+
)
31+
return res
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
2+
product_warranty.access_product_warranty_config,access_product_warranty_config,product_warranty.model_product_warranty_config,base.group_user,1,1,1,1
3+
product_warranty.access_product_warranty_wizard,access_product_warranty_wizard,product_warranty.model_product_warranty_wizard,base.group_user,1,1,1,1
4+
product_warranty.access_product_warranty_wizard_lines,access_product_warranty_wizard_lines,product_warranty.model_product_warranty_wizard_lines,base.group_user,1,1,1,1

product_warranty/tests/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Part of Odoo. See LICENSE file for full copyright and licensing details.
2+
3+
from . import test_product_warranty_wizard
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Part of Odoo. See LICENSE file for full copyright and licensing details.
2+
3+
from odoo.tests.common import TransactionCase
4+
from odoo.exceptions import UserError
5+
6+
7+
class TestProductWarrantyWizard(TransactionCase):
8+
9+
def setUp(self):
10+
super().setUp()
11+
self.partner = self.env['res.partner'].create({
12+
'name': 'Test Customer'
13+
})
14+
ProductTemplate = self.env['product.template']
15+
warranty_template = ProductTemplate.create({
16+
'name': 'Warranty Product',
17+
'warranty': True,
18+
})
19+
no_warranty_template = ProductTemplate.create({
20+
'name': 'Non-Warranty Product',
21+
'warranty': False,
22+
})
23+
self.warranty_product = warranty_template.product_variant_id
24+
self.no_warranty_product = no_warranty_template.product_variant_id
25+
26+
self.sale_order = self.env['sale.order'].create({
27+
'partner_id': self.partner.id,
28+
})
29+
self.sol_with_warranty = self.env['sale.order.line'].create({
30+
'order_id': self.sale_order.id,
31+
'product_id': self.warranty_product.id,
32+
'product_uom_qty': 1,
33+
'price_unit': 100,
34+
})
35+
self.sol_without_warranty = self.env['sale.order.line'].create({
36+
'order_id': self.sale_order.id,
37+
'product_id': self.no_warranty_product.id,
38+
'product_uom_qty': 1,
39+
'price_unit': 100,
40+
})
41+
42+
def test_default_get_populates_wizard_lines(self):
43+
"""Wizard should only include products with warranty=True"""
44+
45+
wizard = self.env['product.warranty.wizard'].with_context(
46+
default_sale_order_id=self.sale_order.id
47+
).new({})
48+
49+
wizard.default_get(['wizard_line_ids'])
50+
self.assertEqual(len(wizard.wizard_line_ids), 1)
51+
52+
wizard_line = wizard.wizard_line_ids[0]
53+
self.assertEqual(wizard_line.product_id.id, self.warranty_product.id)
54+
self.assertEqual(wizard_line.sale_order_line_id.id, self.sol_with_warranty.id)
55+
56+
def test_action_add_warranty_raises_if_config_missing(self):
57+
"""Should raise UserError if any wizard line has no warranty_config_id"""
58+
59+
wizard = self.env['product.warranty.wizard'].with_context(
60+
default_sale_order_id=self.sale_order.id
61+
).create({})
62+
63+
with self.assertRaises(UserError):
64+
wizard.action_add_warranty()

0 commit comments

Comments
 (0)