Skip to content

Commit 7495d2d

Browse files
committed
[ADD] deposit_rental: adds a deposit feature to the Rental application.
Business Context: The deposit feature is required to secure rental transactions by collecting a refundable amount from customers. This helps protect the company against damage, loss, or delayed returns of rented items and ensures customer accountability. Technical changes: 1. add deposit product in rental settings 2. add Deposit Configuration Fields in product page - Added requires_deposit (Boolean) and deposit_amount (Float) fields on the product.template model to define whether a rental product requires a deposit and the deposit amount per unit. 3. Deposit Line Identification - Added is_deposit_line Boolean field on sale.order.line 4. Automatic Deposit Line Creation 5. Deposit Line Protection - Prevented manual editing of deposit lines by overriding the write method. - Prevented manual deletion of deposit lines by overriding the unlink method. 6. Add deposit amount on product website page - when user change quantity of product then deposit amount also update task-5403952
1 parent b68a192 commit 7495d2d

File tree

11 files changed

+218
-0
lines changed

11 files changed

+218
-0
lines changed

deposit_rental/__init__.py

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

deposit_rental/__manifest__.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
'author': 'Odoo S.A.',
3+
'name': 'Deposit Rental App',
4+
'description': """
5+
Implements a security deposit feature in the Rental app
6+
to manage refundable deposits for rental products.
7+
""",
8+
'depends': ['sale_renting', 'website_sale'],
9+
'license': 'LGPL-3',
10+
'data': [
11+
'views/res_config_settings_views.xml',
12+
'views/product_template_views.xml',
13+
'views/template_views.xml'
14+
],
15+
'assets': {
16+
'web.assets_frontend': [
17+
'deposit_rental/static/src/deposit_amount.js',
18+
],
19+
},
20+
'application': True,
21+
'installable': True
22+
}

deposit_rental/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: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
from odoo import api, fields, models
2+
from odoo.exceptions import UserError, MissingError
3+
from odoo.tools import _
4+
5+
6+
class SaleOrderLine(models.Model):
7+
_inherit = 'sale.order.line'
8+
9+
is_deposit_line = fields.Boolean(default=False)
10+
11+
@api.model
12+
def create(self, vals_list):
13+
lines = super().create(vals_list)
14+
rental_lines = lines.filtered(
15+
lambda l: l.product_id.rent_ok
16+
and l.product_id.requires_deposit
17+
and l.product_id.deposit_amount > 0
18+
)
19+
if not rental_lines:
20+
return lines
21+
company_map = {}
22+
for line in rental_lines:
23+
company = line.company_id
24+
if company not in company_map:
25+
if not company.deposit_product:
26+
raise UserError(_("Please set deposit product in settings."))
27+
company_map[company] = company.deposit_product
28+
deposit_vals = []
29+
for line in rental_lines:
30+
deposit_product = company_map[line.company_id]
31+
deposit_vals.append({
32+
'order_id': line.order_id.id,
33+
'product_id': deposit_product.id,
34+
'product_uom_qty': line.product_uom_qty,
35+
'price_unit': line.product_id.deposit_amount,
36+
'name': f"This amount is deposit for {line.product_id.name} product",
37+
'is_deposit_line': True,
38+
})
39+
self.create(deposit_vals)
40+
return lines
41+
42+
@api.ondelete(at_uninstall=False)
43+
def _unlink_deposit_fee(self):
44+
if not self.env.context.get("bypass_deposit_protection_for_delete"):
45+
for record in self:
46+
if record.is_deposit_line:
47+
raise UserError(_("You can't delete a Deposit Product line directly."))
48+
rental_lines = self.filtered(
49+
lambda l: l.product_id.rent_ok
50+
and l.product_id.requires_deposit
51+
and l.product_id.deposit_amount > 0
52+
)
53+
if not rental_lines:
54+
return
55+
deposit_lines = self.search([
56+
('order_id', 'in', rental_lines.mapped('order_id').ids),
57+
('is_deposit_line', '=', True),
58+
])
59+
deposit_map = {}
60+
for line in deposit_lines:
61+
deposit_map[line.name] = line
62+
for line in rental_lines:
63+
deposit_line = deposit_map.get(
64+
f"This amount is deposit for {line.product_id.name} product"
65+
)
66+
if not deposit_line:
67+
raise MissingError(_("Deposit fee is not present."))
68+
deposit_line.with_context(bypass_deposit_protection_for_delete=True).unlink()
69+
70+
def write(self, vals):
71+
if not self.env.context.get("bypass_deposit_protection_for_write"):
72+
for record in self:
73+
if record.is_deposit_line:
74+
raise UserError(_("You can't edit Deposit Product line directly."))
75+
res = super().write(vals)
76+
if 'product_uom_qty' not in vals:
77+
return res
78+
rental_lines = self.filtered(
79+
lambda l: l.product_id.rent_ok
80+
and l.product_id.requires_deposit
81+
and l.product_id.deposit_amount > 0
82+
)
83+
if not rental_lines:
84+
return res
85+
deposit_lines = self.search([
86+
('order_id', 'in', rental_lines.mapped('order_id').ids),
87+
('is_deposit_line', '=', True),
88+
])
89+
deposit_map = {}
90+
for line in deposit_lines:
91+
deposit_map[line.name] = line
92+
for line in rental_lines:
93+
deposit_line = deposit_map.get(
94+
f"This amount is deposit for {line.product_id.name} product"
95+
)
96+
if not deposit_line:
97+
raise MissingError(_("Deposit product line not found."))
98+
deposit_line.with_context(bypass_deposit_protection_for_write=True).write({
99+
'product_uom_qty': line.product_uom_qty,
100+
'price_unit': line.product_id.deposit_amount,
101+
})
102+
return res
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import publicWidget from "@web/legacy/js/public/public_widget";
2+
3+
publicWidget.registry.DepositRental = publicWidget.Widget.extend({
4+
selector: "#product_detail",
5+
events: {
6+
'change input[name="add_qty"]': '_updateDepositAmount',
7+
},
8+
start: function () {
9+
this._super.apply(this, arguments);
10+
if ($("#deposit_amount").length) {
11+
this._updateDepositAmount();
12+
}
13+
else {
14+
this.$el.off('change input[name="add_qty"]');
15+
}
16+
},
17+
_updateDepositAmount: function () {
18+
var qty = parseFloat($("#o_wsale_cta_wrapper").find("input[name='add_qty']").val());
19+
var depositAmount = parseFloat($("#deposit_amount").attr("data-base-amount")) || 0;
20+
$("#deposit_amount").text(depositAmount * qty);
21+
}
22+
})
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<odoo>
3+
<record id="product_template_form_view_rental_deposit" model="ir.ui.view">
4+
<field name="name">product.template.view.form.inherit.rental.deposit</field>
5+
<field name="model">product.template</field>
6+
<field name="inherit_id" ref="product.product_template_only_form_view"/>
7+
<field name="arch" type="xml">
8+
<xpath expr="//group[@name='extra_rental']" position="inside">
9+
<field name="requires_deposit"/>
10+
<field name="deposit_amount" invisible="not requires_deposit" widget="monetary"/>
11+
</xpath>
12+
</field>
13+
</record>
14+
</odoo>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<odoo>
2+
<record id="res_config_settings_view_form_deposit_inherit" model="ir.ui.view">
3+
<field name="name">res.config.settings.view.form.inherit.deposit</field>
4+
<field name="model">res.config.settings</field>
5+
<field name="inherit_id" ref="base.res_config_settings_view_form"/>
6+
<field name="arch" type="xml">
7+
<xpath expr="//field[@name='extra_product']" position="after">
8+
<label for="deposit_product"/>
9+
<field name="deposit_product"/>
10+
</xpath>
11+
</field>
12+
</record>
13+
</odoo>

0 commit comments

Comments
 (0)