From 2742d4042d4a400e378c56fdf36e51f5e70faff5 Mon Sep 17 00:00:00 2001 From: keman-odoo Date: Tue, 16 Jun 2026 12:01:57 +0530 Subject: [PATCH] [ADD] rental_deposit: implement deposit product configuration - Add deposit product field to rental products - Enforce deposit product selection for rental items - Automatically add deposit line to sales orders - Calculate deposit amount based on the configured product price - Display deposit details in Website and the order summary --- rental_deposit/__init__.py | 1 + rental_deposit/__manifest__.py | 16 +++++ rental_deposit/models/__init__.py | 3 + rental_deposit/models/product_template.py | 8 +++ rental_deposit/models/res_config_settings.py | 11 +++ rental_deposit/models/sale_order_line.py | 68 +++++++++++++++++++ rental_deposit/static/src/js/deposit.js | 18 +++++ .../views/product_template_views.xml | 16 +++++ .../views/res_config_settings_views.xml | 19 ++++++ .../views/website_sale_template_views.xml | 15 ++++ 10 files changed, 175 insertions(+) create mode 100644 rental_deposit/__init__.py create mode 100644 rental_deposit/__manifest__.py create mode 100644 rental_deposit/models/__init__.py create mode 100644 rental_deposit/models/product_template.py create mode 100644 rental_deposit/models/res_config_settings.py create mode 100644 rental_deposit/models/sale_order_line.py create mode 100644 rental_deposit/static/src/js/deposit.js create mode 100644 rental_deposit/views/product_template_views.xml create mode 100644 rental_deposit/views/res_config_settings_views.xml create mode 100644 rental_deposit/views/website_sale_template_views.xml diff --git a/rental_deposit/__init__.py b/rental_deposit/__init__.py new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/rental_deposit/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/rental_deposit/__manifest__.py b/rental_deposit/__manifest__.py new file mode 100644 index 00000000000..4d397fa30cd --- /dev/null +++ b/rental_deposit/__manifest__.py @@ -0,0 +1,16 @@ +{ + 'name': 'Rental deposit', + 'author': "keman", + 'license': 'LGPL-3', + 'depends': ['sale_renting', 'website_sale'], + 'data': [ + 'views/res_config_settings_views.xml', + 'views/product_template_views.xml', + 'views/website_sale_template_views.xml' + ], + 'assets': { + 'web.assets_frontend': [ + 'rental_deposit/static/src/js/deposit.js', + ], + }, +} diff --git a/rental_deposit/models/__init__.py b/rental_deposit/models/__init__.py new file mode 100644 index 00000000000..2b5d0f43a47 --- /dev/null +++ b/rental_deposit/models/__init__.py @@ -0,0 +1,3 @@ +from . import res_config_settings +from . import product_template +from . import sale_order_line diff --git a/rental_deposit/models/product_template.py b/rental_deposit/models/product_template.py new file mode 100644 index 00000000000..54c1a69f68f --- /dev/null +++ b/rental_deposit/models/product_template.py @@ -0,0 +1,8 @@ +from odoo import fields, models + + +class ProductTemplate(models.Model): + _inherit = "product.template" + + require_deposite = fields.Boolean() + amount = fields.Float() diff --git a/rental_deposit/models/res_config_settings.py b/rental_deposit/models/res_config_settings.py new file mode 100644 index 00000000000..38cf5257f31 --- /dev/null +++ b/rental_deposit/models/res_config_settings.py @@ -0,0 +1,11 @@ +from odoo import fields, models + + +class ResConfigSettings(models.TransientModel): + _inherit = "res.config.settings" + + deposit_product_id = fields.Many2one( + "product.product", + string="Deposit", + config_parameter="rental_deposit.deposit_product_id", + ) diff --git a/rental_deposit/models/sale_order_line.py b/rental_deposit/models/sale_order_line.py new file mode 100644 index 00000000000..2f809cd907f --- /dev/null +++ b/rental_deposit/models/sale_order_line.py @@ -0,0 +1,68 @@ +from odoo import api, fields, models + + +class SaleOrderLine(models.Model): + _inherit = "sale.order.line" + + is_deposit_line = fields.Boolean(default=False) + + deposit_origin_line_id = fields.Many2one( + "sale.order.line", + ondelete="cascade", + ) + + def _get_line_header(self): + if self.is_deposit_line and self.name: + return self.name + return super()._get_line_header() + + @api.model_create_multi + def create(self, vals_list): + order_lines = super().create(vals_list) + for line in order_lines: + if ( + line.product_id.require_deposite + and not line.is_deposit_line + ): + deposit_product_param = (self.env["ir.config_parameter"].sudo().get_param("rental_deposit.deposit_product_id")) + if not deposit_product_param: + continue + deposit_product_id = int( + deposit_product_param + ) + deposit_amount = line.product_id.amount + self.create({ + "order_id": line.order_id.id, + "product_id": deposit_product_id, + "product_uom_qty": line.product_uom_qty, + "price_unit": deposit_amount, + "name": f"Deposit for {line.product_id.name}", + "is_deposit_line": True, + "deposit_origin_line_id": line.id, + }) + return order_lines + + def write(self, vals): + res = super().write(vals) + + deposit_lines = self.env[ + "sale.order.line" + ].search([("deposit_origin_line_id", "in", self.ids)]) + + for line in self: + if line.is_deposit_line: + continue + + deposit_line = deposit_lines.filtered(lambda l: l.deposit_origin_line_id == line) + if not deposit_line: + continue + deposit_line.write({ + "product_uom_qty": + line.product_uom_qty, + "price_unit": + line.product_id.amount, + "name": + f"Deposit for {line.product_id.name}", + }) + + return res diff --git a/rental_deposit/static/src/js/deposit.js b/rental_deposit/static/src/js/deposit.js new file mode 100644 index 00000000000..36bda5c2001 --- /dev/null +++ b/rental_deposit/static/src/js/deposit.js @@ -0,0 +1,18 @@ +document.addEventListener('change', function (ev) { + const input = ev.target; + if (!input.matches('.js_main_product input[name="add_qty"]')) { + return; + } + const productEl = input.closest('.js_product'); + const depositEl = productEl?.querySelector('.o_deposit_wrapper'); + if (!depositEl) { + return; + } + const depositUnit = parseFloat(depositEl.dataset.depositUnit) || 0; + const quantity = parseFloat(input.value) || 0; + const total = (depositUnit * quantity).toFixed(2); + const target = depositEl.querySelector('.o_deposit_amount_value'); + if (target) { + target.textContent = total; + } +}); diff --git a/rental_deposit/views/product_template_views.xml b/rental_deposit/views/product_template_views.xml new file mode 100644 index 00000000000..f268731a762 --- /dev/null +++ b/rental_deposit/views/product_template_views.xml @@ -0,0 +1,16 @@ + + + + + product.template.inherit.stock.rental + product.template + + + + + + + + + + \ No newline at end of file diff --git a/rental_deposit/views/res_config_settings_views.xml b/rental_deposit/views/res_config_settings_views.xml new file mode 100644 index 00000000000..82dfdeff3d5 --- /dev/null +++ b/rental_deposit/views/res_config_settings_views.xml @@ -0,0 +1,19 @@ + + + + + res.config.settings.view.form.inherit.rental + res.config.settings + + + + +
+
+
+
+
+ +
\ No newline at end of file diff --git a/rental_deposit/views/website_sale_template_views.xml b/rental_deposit/views/website_sale_template_views.xml new file mode 100644 index 00000000000..af1ff995184 --- /dev/null +++ b/rental_deposit/views/website_sale_template_views.xml @@ -0,0 +1,15 @@ + + +