Skip to content

Commit d3d3212

Browse files
committed
[ADD] product_warranty: add warranty config and wizard to 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 d3d3212

18 files changed

+383
-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: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
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)", required=True,
14+
help="Warranty period in years"
15+
)
16+
product_id = fields.Many2one(
17+
comodel_name="product.product", string="Product",
18+
help="Warranty product name"
19+
)

product_warranty/models/sale_order.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
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", store=True,
11+
compute='_compute_has_warranty_product'
12+
)
13+
14+
@api.depends('order_line.product_id')
15+
def _compute_has_warranty_product(self):
16+
for order in self:
17+
order.has_warranty_product = any(line.product_id.warranty for line in order.order_line)
18+
19+
def open_add_warranty_wizard(self):
20+
self.ensure_one()
21+
return {
22+
'type': 'ir.actions.act_window',
23+
'name': 'Add Warranty Products',
24+
'res_model': 'product.warranty.wizard',
25+
'view_mode': 'form',
26+
'target': 'new',
27+
'context': {'default_sale_order_id': self.id}
28+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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", ondelete="cascade",
12+
)
13+
14+
def write(self, vals):
15+
res = super().write(vals)
16+
if "price_unit" in vals:
17+
source_lines = self.filtered(lambda l: not l.source_order_line_id)
18+
warranty_lines = self.env["sale.order.line"].search([
19+
("source_order_line_id", "in", source_lines.ids)
20+
])
21+
22+
configs = self.env["product.warranty.config"].search([
23+
("product_id", "in", warranty_lines.mapped("product_id").ids)
24+
]).mapped(lambda c: (c.product_id.id, c))
25+
config_map = dict(configs)
26+
27+
for wl in warranty_lines:
28+
source = wl.source_order_line_id
29+
config = config_map.get(wl.product_id.id)
30+
if source in source_lines and config:
31+
wl.price_unit = source.price_unit * (config.percentage / 100.0)
32+
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.exceptions import UserError
4+
from odoo.tests.common import TransactionCase
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)