Skip to content

Commit dfb6609

Browse files
committed
[ADD] rental_deposit: adding deposit to rental products.
- Added and fields on to allow per-product deposit configuration - Added on , exposed via config settings - Auto-add/update/delete deposit line on sale order via CRUD overrides - Blocked direct editing or deletion of deposit lines using context guards - Added deposit display on webshop product page with dynamic qty update
1 parent 781b590 commit dfb6609

14 files changed

Lines changed: 469 additions & 0 deletions

rental_deposit/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from . import models

rental_deposit/__manifest__.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
'author': 'Aditi (adpaw)',
3+
'name': 'Deposit Rental App',
4+
'license': 'LGPL-3',
5+
'depends': ['sale_renting', 'website_sale'],
6+
'data': [
7+
'views/res_config_settings_view.xml',
8+
'views/product_template_view.xml',
9+
'views/template_view.xml'
10+
],
11+
'assets': {
12+
'web.assets_frontend': [
13+
'rental_deposit/static/src/deposit_amount.js',
14+
],
15+
},
16+
'installable': True,
17+
'auto_install': True,
18+
}

rental_deposit/models/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from . import res_config_settings
2+
from . import product_template
3+
from . import sale_order_line
4+
from . import res_company
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from odoo import fields, models
2+
3+
4+
class ProductTemplate(models.Model):
5+
_inherit = 'product.template'
6+
7+
requires_deposit = fields.Boolean(string="Requires Deposit")
8+
deposit_amount = fields.Float()
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from odoo import fields, models
2+
3+
4+
class ResCompany(models.Model):
5+
_inherit = "res.company"
6+
7+
deposit_product = fields.Many2one("product.product")
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from odoo import fields, models
2+
3+
4+
class ResConfigSettings(models.TransientModel):
5+
_inherit = 'res.config.settings'
6+
7+
deposit_product = fields.Many2one(
8+
"product.product",
9+
related="company_id.deposit_product",
10+
string="Deposit",
11+
readonly=False
12+
)
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
from odoo import api, fields, models
2+
from odoo.exceptions import UserError
3+
4+
5+
class SaleOrderLine(models.Model):
6+
_inherit = 'sale.order.line'
7+
8+
is_deposit_line = fields.Boolean(default=False)
9+
parent_rental_line_id = fields.Many2one(
10+
'sale.order.line',
11+
string="Parent Rental Line",
12+
ondelete="cascade",
13+
index=True
14+
)
15+
16+
@api.model_create_multi
17+
def create(self, vals_list):
18+
lines = super().create(vals_list)
19+
rental_lines = lines.filtered(
20+
lambda l: l.product_id.rent_ok
21+
and l.product_id.requires_deposit
22+
and l.product_id.deposit_amount > 0
23+
and not l.is_deposit_line
24+
)
25+
if not rental_lines:
26+
return lines
27+
company_map = {}
28+
for line in rental_lines:
29+
company = line.company_id
30+
if company not in company_map:
31+
if not company.deposit_product:
32+
raise UserError("Please set deposit product in settings.")
33+
company_map[company] = company.deposit_product
34+
deposit_vals = []
35+
for line in rental_lines:
36+
deposit_product = company_map[line.company_id]
37+
deposit_vals.append({
38+
'order_id': line.order_id.id,
39+
'product_id': deposit_product.id,
40+
'product_uom_qty': line.product_uom_qty,
41+
'price_unit': line.product_id.deposit_amount,
42+
'name': f"Deposit fee for product: {line.product_id.name}",
43+
'is_deposit_line': True,
44+
'parent_rental_line_id': line.id,
45+
})
46+
self.create(deposit_vals)
47+
return lines
48+
49+
# deposit_vals = []
50+
# for line in rental_lines:
51+
# deposit_product = line.company_id.deposit_product
52+
# if not deposit_product:
53+
# raise UserError("Please set deposit product in settings.")
54+
# deposit_vals.append({
55+
# 'order_id': line.order_id.id,
56+
# 'product_id': deposit_product.id,
57+
# 'product_uom_qty': line.product_uom_qty,
58+
# 'price_unit': line.product_id.deposit_amount,
59+
# 'name': f"Deposit fee for product: {line.product_id.name}",
60+
# 'is_deposit_line': True,
61+
# 'parent_rental_line_id': line.id,
62+
# })
63+
# self.create(deposit_vals)
64+
# return lines
65+
66+
@api.ondelete(at_uninstall=False)
67+
def _unlink_deposit_fee(self):
68+
if not self.env.context.get("bypass_deposit_protection_for_delete"):
69+
for record in self:
70+
if record.is_deposit_line:
71+
raise UserError("You can't delete a Deposit Product line directly.")
72+
73+
def write(self, vals):
74+
if not self.env.context.get("bypass_deposit_protection_for_write"):
75+
for record in self:
76+
if record.is_deposit_line:
77+
raise UserError("You can't edit Deposit Product line directly.")
78+
res = super().write(vals)
79+
if 'product_uom_qty' not in vals:
80+
return res
81+
rental_lines = self.filtered(
82+
lambda l: l.product_id.rent_ok
83+
and l.product_id.requires_deposit
84+
and l.product_id.deposit_amount > 0
85+
and not l.is_deposit_line
86+
)
87+
for line in rental_lines:
88+
deposit_line = self.search([('parent_rental_line_id', '=', line.id)], limit=1)
89+
if deposit_line:
90+
deposit_line.with_context(bypass_deposit_protection_for_write=True).write({
91+
'product_uom_qty': line.product_uom_qty,
92+
'price_unit': line.product_id.deposit_amount,
93+
})
94+
return res
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { Component, onMounted } from "@odoo/owl";
2+
import { registry } from "@web/core/registry";
3+
4+
export class DepositAmount extends Component {
5+
setup() {
6+
onMounted(() => {
7+
const depositEl = document.querySelector('#deposit_amount_val');
8+
const qtyInput = document.querySelector('input[name="add_qty"]');
9+
10+
if (!depositEl || !qtyInput) return;
11+
12+
const baseAmount = parseFloat(depositEl.dataset.baseAmount) || 0;
13+
14+
const calculateTotal = () => {
15+
const quantity = parseFloat(qtyInput.value) || 1;
16+
depositEl.textContent = (baseAmount * quantity).toFixed(2);
17+
};
18+
19+
qtyInput.addEventListener('input', calculateTotal);
20+
qtyInput.addEventListener('change', calculateTotal);
21+
22+
calculateTotal();
23+
});
24+
}
25+
}
26+
27+
registry.category("public_components").add("deposit_amount", DepositAmount);

rental_deposit/tests/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from . import common
2+
from . import test_sale_order_line

rental_deposit/tests/common.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
from odoo.tests.common import TransactionCase
2+
3+
4+
class RentalDepositCommon(TransactionCase):
5+
@classmethod
6+
def setUpClass(cls):
7+
super().setUpClass()
8+
9+
cls.deposit_product = cls.env['product.product'].create({
10+
'name': 'Deposit Fee',
11+
'type': 'service',
12+
})
13+
cls.env.company.deposit_product = cls.deposit_product
14+
15+
cls.rental_product_a = cls.env['product.product'].create({
16+
'name': 'Test Projector',
17+
'rent_ok': True,
18+
'requires_deposit': True,
19+
'deposit_amount': 50.0,
20+
})
21+
22+
cls.rental_product_b = cls.env['product.product'].create({
23+
'name': 'Test Bike',
24+
'rent_ok': True,
25+
'requires_deposit': True,
26+
'deposit_amount': 30.0,
27+
})
28+
29+
cls.rental_product_c = cls.env['product.product'].create({
30+
'name': 'Test Camera',
31+
'rent_ok': True,
32+
'requires_deposit': True,
33+
'deposit_amount': 20.0,
34+
})
35+
36+
cls.rental_no_deposit = cls.env['product.product'].create({
37+
'name': 'Test Chair',
38+
'rent_ok': True,
39+
'requires_deposit': False,
40+
})
41+
42+
cls.partner = cls.env['res.partner'].create({'name': 'Test Customer'})
43+
44+
def _make_order(self):
45+
"""Creates a fresh sale order each time."""
46+
return self.env['sale.order'].create({
47+
'partner_id': self.partner.id,
48+
})
49+
50+
def _add_line(self, order, product, qty=1, price=10.0):
51+
"""Helper — adds a single order line and returns it."""
52+
return self.env['sale.order.line'].create({
53+
'order_id': order.id,
54+
'product_id': product.id,
55+
'product_uom_qty': qty,
56+
'price_unit': price,
57+
})

0 commit comments

Comments
 (0)