@ -0,0 +1,42 @@ |
|||||
|
.. image:: https://img.shields.io/badge/license-OPL--1-red.svg |
||||
|
:target: https://www.odoo.com/documentation/17.0/legal/licenses.html#odoo-apps |
||||
|
:alt: License: OPL-1 |
||||
|
|
||||
|
Advanced Fleet Rental Management |
||||
|
========================== |
||||
|
Advanced Fleet Rental Management system is designed to streamline the process of managing a fleet of vehicles for rental businesses |
||||
|
|
||||
|
License |
||||
|
------- |
||||
|
Odoo Proprietary License v1.0. |
||||
|
(https://www.odoo.com/documentation/17.0/legal/licenses.html#odoo-apps) |
||||
|
|
||||
|
Company |
||||
|
------- |
||||
|
* `Cybrosys Techno Solutions <https://cybrosys.com/>`__ |
||||
|
|
||||
|
Credits |
||||
|
------- |
||||
|
* Developer: (V17)Anfas Faisal K, Contact: odoo@cybrosys.com |
||||
|
|
||||
|
Contacts |
||||
|
-------- |
||||
|
* Mail Contact : odoo@cybrosys.com |
||||
|
* Website : https://cybrosys.com |
||||
|
|
||||
|
Bug Tracker |
||||
|
----------- |
||||
|
Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. |
||||
|
|
||||
|
Maintainer |
||||
|
========== |
||||
|
.. image:: https://cybrosys.com/images/logo.png |
||||
|
:target: https://cybrosys.com |
||||
|
|
||||
|
This module is maintained by Cybrosys Technologies. |
||||
|
|
||||
|
For support and more information, please visit https://www.cybrosys.com |
||||
|
|
||||
|
Further information |
||||
|
=================== |
||||
|
HTML Description: `<static/description/index.html>`__ |
@ -0,0 +1,23 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################### |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
||||
|
# Author: Anfas Faisal K (odoo@cybrosys.com) |
||||
|
# |
||||
|
# This program is under the terms of the Odoo Proprietary License v1.0(OPL-1) |
||||
|
# It is forbidden to publish, distribute, sublicense, or sell copies of the |
||||
|
# Software or modified copies of the Software. |
||||
|
# |
||||
|
# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL |
||||
|
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,DAMAGES OR OTHER |
||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,ARISING |
||||
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
||||
|
# DEALINGS IN THE SOFTWARE. |
||||
|
# |
||||
|
############################################################################### |
||||
|
from . import models |
||||
|
from . import wizard |
@ -0,0 +1,71 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################### |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
||||
|
# Author: Anfas Faisal K (odoo@cybrosys.com) |
||||
|
# |
||||
|
# This program is under the terms of the Odoo Proprietary License v1.0(OPL-1) |
||||
|
# It is forbidden to publish, distribute, sublicense, or sell copies of the |
||||
|
# Software or modified copies of the Software. |
||||
|
# |
||||
|
# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL |
||||
|
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,DAMAGES OR OTHER |
||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,ARISING |
||||
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
||||
|
# DEALINGS IN THE SOFTWARE. |
||||
|
# |
||||
|
############################################################################### |
||||
|
{ |
||||
|
'name': 'Advanced Fleet Rental Management', |
||||
|
'version': '17.0.1.0.0', |
||||
|
'category': "Extra Tools", |
||||
|
'summary': """This module will helps you to give the vehicles for Rent.""", |
||||
|
'description': "This module enhances Odoo’s fleet management " |
||||
|
"functionality for vehicle rentals, including cars, " |
||||
|
"vans, bikes, and jeeps. Key features include:" |
||||
|
"Detailed rental contracts and invoicing." |
||||
|
"Management of daily, hourly, and kilometer-based rental terms." |
||||
|
"Integration with Odoo’s accounting module for automated invoicing and payments." |
||||
|
"Tracking of vehicle status, maintenance, and extra service charges." |
||||
|
"A user-friendly dashboard for monitoring fleet performance and rental contracts.", |
||||
|
'author': 'Cybrosys Techno Solutions', |
||||
|
'company': 'Cybrosys Techno Solutions', |
||||
|
'maintainer': 'Cybrosys Techno Solutions', |
||||
|
'website': "https://www.cybrosys.com", |
||||
|
'depends': ['fleet', 'mail', 'sale_management', 'account', ], |
||||
|
'data': [ |
||||
|
'security/ir.model.access.csv', |
||||
|
'data/ir_cron_data.xml', |
||||
|
'data/product_product_data.xml', |
||||
|
'data/ir_sequence_data.xml', |
||||
|
'views/fleet_vehicle_views.xml', |
||||
|
'views/fleet_dashboard.xml', |
||||
|
'views/multi_image_views.xml', |
||||
|
'views/fleet_rental_contract_views.xml', |
||||
|
'report/fleet_rental_contract_report.xml', |
||||
|
'report/fleet_rental_contract_template.xml', |
||||
|
'views/res_partner_views.xml', |
||||
|
'views/account_move_views.xml', |
||||
|
'views/rental_payment_plan_views.xml', |
||||
|
'views/cancellation_policy_views.xml', |
||||
|
'views/fleet_dashboard.xml', |
||||
|
'wizard/damage_invoice_views.xml', |
||||
|
], |
||||
|
'assets': { |
||||
|
'web.assets_backend': [ |
||||
|
'advanced_fleet_rental/static/src/js/fleet_dashboard.js', |
||||
|
'advanced_fleet_rental/static/src/xml/fleet_dashboard.xml', |
||||
|
'advanced_fleet_rental/static/src/css/xero_dashboard.css', |
||||
|
'https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.0/chart.umd.min.js', |
||||
|
] |
||||
|
}, |
||||
|
'images': ['static/description/banner.jpg'], |
||||
|
'license': 'OPL-1', |
||||
|
'installable': True, |
||||
|
'auto_install': False, |
||||
|
'application': False, |
||||
|
} |
@ -0,0 +1,16 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8" ?> |
||||
|
<odoo> |
||||
|
<data noupdate="1"> |
||||
|
<!-- Schedule action for generating automatic database backup--> |
||||
|
<record id="ir_cron_auto_invoice_create" model="ir.cron"> |
||||
|
<field name="name">Fleet : Automatic Invoice Creation</field> |
||||
|
<field name="model_id" ref="model_rental_payment_plan"/> |
||||
|
<field name="state">code</field> |
||||
|
<field name="code">model._schedule_auto_invoice_checker()</field> |
||||
|
<field name="interval_number">1</field> |
||||
|
<field name="interval_type">days</field> |
||||
|
<field name="numbercall">-1</field> |
||||
|
<field name="active">False</field> |
||||
|
</record> |
||||
|
</data> |
||||
|
</odoo> |
@ -0,0 +1,11 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8" ?> |
||||
|
<odoo> |
||||
|
<!-- Sequence Number--> |
||||
|
<record id="vehicle_sequence_id" model="ir.sequence"> |
||||
|
<field name="name">Vehicle Rental Code</field> |
||||
|
<field name="code">vehicle.sequence</field> |
||||
|
<field name="prefix">VC/%(year)s/%(month)s/%(day)s/</field> |
||||
|
<field name="padding">5</field> |
||||
|
<field name="number_next">1</field> |
||||
|
</record> |
||||
|
</odoo> |
@ -0,0 +1,49 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<odoo> |
||||
|
<data noupdate="1"> |
||||
|
<!--Vehicle Rental Charge--> |
||||
|
<record id="product_product_vehicle_rental_charge" model="product.product"> |
||||
|
<field name="name">Vehicle Rent Charge</field> |
||||
|
<field name="default_code">VRC</field> |
||||
|
<field name="description">Lets you charge a fixed rate for Vehicle Rental.</field> |
||||
|
<field name="type">service</field> |
||||
|
<field name="sale_ok" eval="True"/> |
||||
|
<field name="purchase_ok" eval="False"/> |
||||
|
<field name="list_price">0.0</field> |
||||
|
<field name="invoice_policy">order</field> |
||||
|
</record> |
||||
|
<!--Extra Charge--> |
||||
|
<record id="product_product_vehicle_extra_rental_charge" model="product.product"> |
||||
|
<field name="name">Extra Charges</field> |
||||
|
<field name="default_code">EC</field> |
||||
|
<field name="description">Fixed rate for Extra Charges.</field> |
||||
|
<field name="type">service</field> |
||||
|
<field name="sale_ok" eval="True"/> |
||||
|
<field name="purchase_ok" eval="False"/> |
||||
|
<field name="list_price">0.0</field> |
||||
|
<field name="invoice_policy">order</field> |
||||
|
</record> |
||||
|
<!--Damage Charge--> |
||||
|
<record id="product_product_vehicle_damage_charge" model="product.product"> |
||||
|
<field name="name">Damage Charge</field> |
||||
|
<field name="default_code">DC</field> |
||||
|
<field name="description">Fixed rate for Damage Charges.</field> |
||||
|
<field name="type">service</field> |
||||
|
<field name="sale_ok" eval="True"/> |
||||
|
<field name="purchase_ok" eval="False"/> |
||||
|
<field name="list_price">0.0</field> |
||||
|
<field name="invoice_policy">order</field> |
||||
|
</record> |
||||
|
<!--Cancellation Charge--> |
||||
|
<record id="product_product_vehicle_cancel_charge" model="product.product"> |
||||
|
<field name="name">Cancellation Charge</field> |
||||
|
<field name="default_code">CC</field> |
||||
|
<field name="description">Fixed rate for Cancellation Charges.</field> |
||||
|
<field name="type">service</field> |
||||
|
<field name="sale_ok" eval="True"/> |
||||
|
<field name="purchase_ok" eval="False"/> |
||||
|
<field name="list_price">0.0</field> |
||||
|
<field name="invoice_policy">order</field> |
||||
|
</record> |
||||
|
</data> |
||||
|
</odoo> |
@ -0,0 +1,7 @@ |
|||||
|
## Module <advanced_fleet_rental> |
||||
|
|
||||
|
#### 07.04.2025 |
||||
|
#### Version 17.0.1.0.0 |
||||
|
#### ADD |
||||
|
|
||||
|
- Initial commit for Advanced Fleet Rental Management |
@ -0,0 +1,31 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################### |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
||||
|
# Author: Anfas Faisal K (odoo@cybrosys.com) |
||||
|
# |
||||
|
# This program is under the terms of the Odoo Proprietary License v1.0(OPL-1) |
||||
|
# It is forbidden to publish, distribute, sublicense, or sell copies of the |
||||
|
# Software or modified copies of the Software. |
||||
|
# |
||||
|
# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL |
||||
|
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,DAMAGES OR OTHER |
||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,ARISING |
||||
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
||||
|
# DEALINGS IN THE SOFTWARE. |
||||
|
# |
||||
|
############################################################################### |
||||
|
from . import account_move |
||||
|
from . import cancellation_policy |
||||
|
from . import extra_service |
||||
|
from . import fleet_dashboard |
||||
|
from . import fleet_rental_contract |
||||
|
from . import fleet_vehicle |
||||
|
from . import insurance_policy |
||||
|
from . import multi_image |
||||
|
from . import rental_payment_plan |
||||
|
|
@ -0,0 +1,38 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################### |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
||||
|
# Author: Anfas Faisal K (odoo@cybrosys.com) |
||||
|
# |
||||
|
# This program is under the terms of the Odoo Proprietary License v1.0(OPL-1) |
||||
|
# It is forbidden to publish, distribute, sublicense, or sell copies of the |
||||
|
# Software or modified copies of the Software. |
||||
|
# |
||||
|
# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL |
||||
|
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,DAMAGES OR OTHER |
||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,ARISING |
||||
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
||||
|
# DEALINGS IN THE SOFTWARE. |
||||
|
# |
||||
|
############################################################################### |
||||
|
from odoo import fields, models |
||||
|
|
||||
|
|
||||
|
class AccountMove(models.Model): |
||||
|
""" |
||||
|
Inherits the 'account.move' model to add a relationship with the |
||||
|
'fleet.rental.contract' model. This allows linking a vehicle rental |
||||
|
contract to an accounting move. |
||||
|
""" |
||||
|
_inherit = "account.move" |
||||
|
|
||||
|
vehicle_rental_id = fields.Many2one('fleet.rental.contract', |
||||
|
string="Vehicle Contract", |
||||
|
readonly=True) |
||||
|
|
||||
|
|
||||
|
|
@ -0,0 +1,37 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################### |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
||||
|
# Author: Anfas Faisal K (odoo@cybrosys.com) |
||||
|
# |
||||
|
# This program is under the terms of the Odoo Proprietary License v1.0(OPL-1) |
||||
|
# It is forbidden to publish, distribute, sublicense, or sell copies of the |
||||
|
# Software or modified copies of the Software. |
||||
|
# |
||||
|
# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL |
||||
|
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,DAMAGES OR OTHER |
||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,ARISING |
||||
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
||||
|
# DEALINGS IN THE SOFTWARE. |
||||
|
# |
||||
|
############################################################################### |
||||
|
from odoo import fields, models |
||||
|
|
||||
|
|
||||
|
class CancellationPolicy(models.Model): |
||||
|
""" |
||||
|
Model for defining a cancellation policy, including the policy name, |
||||
|
creation date, and detailed terms and conditions. |
||||
|
""" |
||||
|
_name = 'cancellation.policy' |
||||
|
_description = 'Cancellation Policy' |
||||
|
|
||||
|
name = fields.Char(string='Policy Name', required=True, |
||||
|
help='Name of the cancellation policy.') |
||||
|
terms_conditions = fields.Text(string='Terms & Conditions', |
||||
|
help='Detailed terms and conditions ' |
||||
|
'of the cancellation policy.') |
@ -0,0 +1,66 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################### |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
||||
|
# Author: Anfas Faisal K (odoo@cybrosys.com) |
||||
|
# |
||||
|
# This program is under the terms of the Odoo Proprietary License v1.0(OPL-1) |
||||
|
# It is forbidden to publish, distribute, sublicense, or sell copies of the |
||||
|
# Software or modified copies of the Software. |
||||
|
# |
||||
|
# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL |
||||
|
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,DAMAGES OR OTHER |
||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,ARISING |
||||
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
||||
|
# DEALINGS IN THE SOFTWARE. |
||||
|
# |
||||
|
############################################################################### |
||||
|
from odoo import api, fields, models |
||||
|
|
||||
|
|
||||
|
class ExtraService(models.Model): |
||||
|
""" |
||||
|
Model for defining extra services that can be added to a fleet rental |
||||
|
contract Each service is associated with a product, and the total amount |
||||
|
is computed based on the quantity and unit price of the product. |
||||
|
""" |
||||
|
_name = 'extra.service' |
||||
|
_description = 'Extra Services' |
||||
|
|
||||
|
product_id = fields.Many2one('product.product', |
||||
|
string='Product', |
||||
|
required=True, |
||||
|
help='Description of the extra service.') |
||||
|
quantity = fields.Float(string='Quantity', default=1.0, |
||||
|
required=True, |
||||
|
help='Quantity of the extra service.') |
||||
|
unit_price = fields.Float(string='Unit Price', |
||||
|
related='product_id.lst_price', store=True, |
||||
|
readonly=False, |
||||
|
help='Unit price of the extra service.') |
||||
|
amount = fields.Float(string='Amount', compute='_compute_amount', |
||||
|
store=True, |
||||
|
help='Total amount for the extra service.') |
||||
|
description = fields.Char(string='Description', |
||||
|
help='Description of the Product') |
||||
|
contract_id = fields.Many2one('fleet.rental.contract', |
||||
|
string='Contract Rent', |
||||
|
help='Reference to the vehicle rent.') |
||||
|
|
||||
|
@api.depends('quantity', 'unit_price') |
||||
|
def _compute_amount(self): |
||||
|
""" |
||||
|
Compute the total amount for the extra service based on the |
||||
|
quantity and unit price. The amount is calculated as |
||||
|
quantity * unit_price and stored in the 'amount' field. |
||||
|
""" |
||||
|
for service in self: |
||||
|
service.amount = service.quantity * service.unit_price |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
@ -0,0 +1,102 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################### |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
||||
|
# Author: Anfas Faisal K (odoo@cybrosys.com) |
||||
|
# |
||||
|
# This program is under the terms of the Odoo Proprietary License v1.0(OPL-1) |
||||
|
# It is forbidden to publish, distribute, sublicense, or sell copies of the |
||||
|
# Software or modified copies of the Software. |
||||
|
# |
||||
|
# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL |
||||
|
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,DAMAGES OR OTHER |
||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,ARISING |
||||
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
||||
|
# DEALINGS IN THE SOFTWARE. |
||||
|
# |
||||
|
############################################################################### |
||||
|
from odoo import api, models |
||||
|
from datetime import datetime, timedelta |
||||
|
|
||||
|
|
||||
|
class FleetDashboard(models.Model): |
||||
|
""" |
||||
|
Model for the Fleet Dashboard, providing various statistics and data |
||||
|
related to the fleet vehicles, contracts, and invoices. |
||||
|
""" |
||||
|
_name = 'fleet.dashboard' |
||||
|
_description = 'Fleet Dashboard' |
||||
|
|
||||
|
@api.model |
||||
|
def get_datas(self): |
||||
|
""" |
||||
|
Retrieves overall statistics related to fleet vehicles, contracts, |
||||
|
and invoices. |
||||
|
""" |
||||
|
total_vehicles = self.env['fleet.vehicle'].search_count([]) |
||||
|
operational_vehicles = self.env['fleet.vehicle'].search_count( |
||||
|
[('status', '=', 'operational')]) |
||||
|
maintenance_vehicles = self.env['fleet.vehicle'].search_count( |
||||
|
[('status', '=', 'undermaintenance')]) |
||||
|
all_invoices = self.env['account.move'].search_count( |
||||
|
[('vehicle_rental_id', '!=', False)]) |
||||
|
pending_invoices = self.env['account.move'].search_count([ |
||||
|
('vehicle_rental_id', '!=', False), |
||||
|
('state', '=', 'posted'), |
||||
|
('payment_state', '!=', 'paid') |
||||
|
]) |
||||
|
total_contracts = self.env['fleet.rental.contract'].search_count([]) |
||||
|
total_contract_working = self.env[ |
||||
|
'fleet.rental.contract'].search_count( |
||||
|
[('state', '=', 'in_progress')]) |
||||
|
total_contract_returned = self.env[ |
||||
|
'fleet.rental.contract'].search_count( |
||||
|
[('state', '=', 'return')]) |
||||
|
total_contract_cancel = self.env['fleet.rental.contract'].search_count( |
||||
|
[('state', '=', 'cancel')]) |
||||
|
|
||||
|
return { |
||||
|
'total_vehicles': total_vehicles, |
||||
|
'total_contracts': total_contracts, |
||||
|
'total_contract_working': total_contract_working, |
||||
|
'total_contract_return': total_contract_returned, |
||||
|
'total_contract_cancel': total_contract_cancel, |
||||
|
'operational': operational_vehicles, |
||||
|
'under_maintenance': maintenance_vehicles, |
||||
|
'all_customers': self.env['res.partner'].search_count([]), |
||||
|
'all_invoices': all_invoices, |
||||
|
'pending_invoices': pending_invoices, |
||||
|
} |
||||
|
|
||||
|
@api.model |
||||
|
def get_monthly_contract_invoices(self): |
||||
|
""" |
||||
|
Retrieves the count of posted invoices for each month of the |
||||
|
current year related to vehicle rentals. |
||||
|
""" |
||||
|
current_year = datetime.now().year |
||||
|
data = [] |
||||
|
labels = ['January', 'February', 'March', 'April', 'May', 'June', |
||||
|
'July', 'August', 'September', 'October', 'November', |
||||
|
'December'] |
||||
|
|
||||
|
for month in range(1, 13): |
||||
|
start_date = datetime(current_year, month, 1) |
||||
|
end_date = (start_date + timedelta(days=32)).replace( |
||||
|
day=1) - timedelta(days=1) |
||||
|
|
||||
|
invoice_count = self.env['account.move'].search_count([ |
||||
|
('vehicle_rental_id', '!=', False), |
||||
|
('invoice_date', '>=', start_date), |
||||
|
('invoice_date', '<=', end_date), |
||||
|
('state', '=', 'posted') |
||||
|
]) |
||||
|
data.append(invoice_count) |
||||
|
return { |
||||
|
'labels': labels, |
||||
|
'data': data |
||||
|
} |
@ -0,0 +1,677 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################### |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
||||
|
# Author: Anfas Faisal K (odoo@cybrosys.com) |
||||
|
# |
||||
|
# This program is under the terms of the Odoo Proprietary License v1.0(OPL-1) |
||||
|
# It is forbidden to publish, distribute, sublicense, or sell copies of the |
||||
|
# Software or modified copies of the Software. |
||||
|
# |
||||
|
# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL |
||||
|
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,DAMAGES OR OTHER |
||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,ARISING |
||||
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
||||
|
# DEALINGS IN THE SOFTWARE. |
||||
|
# |
||||
|
############################################################################### |
||||
|
from datetime import date |
||||
|
from odoo import api, fields, models, _ |
||||
|
from dateutil.relativedelta import relativedelta |
||||
|
from odoo.exceptions import ValidationError |
||||
|
|
||||
|
|
||||
|
class FleetRentalContract(models.Model): |
||||
|
""" |
||||
|
Represents a car rental contract, including details about the customer, |
||||
|
vehicle, rental period, charges, and various related information. |
||||
|
""" |
||||
|
_name = 'fleet.rental.contract' |
||||
|
_description = 'Fleet Rental Contract' |
||||
|
_inherit = ['mail.thread', 'mail.activity.mixin'] |
||||
|
|
||||
|
name = fields.Char(string="Sequence", |
||||
|
default=lambda self: _('New'), |
||||
|
copy=False, readonly=True, tracking=True, |
||||
|
help='Unique contract number for the rental agreement.') |
||||
|
|
||||
|
state = fields.Selection( |
||||
|
[('new', 'New'), |
||||
|
('in_progress', 'In Progress'), |
||||
|
('return', 'Return'), ('cancel', 'Cancel')], |
||||
|
string='State', default='new', |
||||
|
help='Contract Progress', |
||||
|
group_expand='_group_expand_states',tracking=True) |
||||
|
customer_id = fields.Many2one( |
||||
|
'res.partner', string='Customer', |
||||
|
help='The customer who is renting the vehicle.', |
||||
|
required=True, tracking=True) |
||||
|
email = fields.Char(string='Email', |
||||
|
related='customer_id.email', |
||||
|
help='Email address of the customer.', |
||||
|
readonly=False) |
||||
|
phone = fields.Char('Phone', related='customer_id.phone', |
||||
|
help="Phone Number of the customer") |
||||
|
pickup_date = fields.Datetime( |
||||
|
string='Pick-up Date', |
||||
|
help='Date and time when the vehicle will be picked up.', |
||||
|
required=True, tracking=True) |
||||
|
dropoff_date = fields.Datetime( |
||||
|
string='Drop-off Date', |
||||
|
help='Date and time when the vehicle will be returned.', |
||||
|
required=True, tracking=True) |
||||
|
pickup_location = fields.Char( |
||||
|
string='Pick-Up Location', |
||||
|
help='Location where the vehicle will be picked up.', |
||||
|
required=True) |
||||
|
dropoff_location = fields.Char( |
||||
|
string='Drop-Off Location', |
||||
|
help='Location where the vehicle will be dropped off.', |
||||
|
required=True) |
||||
|
pickup_street = fields.Char( |
||||
|
string='Pick-Up Street', |
||||
|
help='Street address for the pick-up location.', |
||||
|
required=True) |
||||
|
dropoff_street = fields.Char( |
||||
|
string='Drop-Off Street', |
||||
|
help='Street address for the drop-off location.', |
||||
|
required=True) |
||||
|
pickup_city = fields.Char( |
||||
|
string='Pick-Up City', |
||||
|
help='City where the vehicle will be picked up.', |
||||
|
required=True) |
||||
|
dropoff_city = fields.Char( |
||||
|
string='Drop-Off City', |
||||
|
help='City where the vehicle will be dropped off.', |
||||
|
required=True) |
||||
|
pickup_state_id = fields.Many2one( |
||||
|
'res.country.state', |
||||
|
string='Pick-Up State', |
||||
|
help='State where the vehicle will be picked up.', |
||||
|
required=True) |
||||
|
dropoff_state_id = fields.Many2one( |
||||
|
'res.country.state', |
||||
|
string='Drop-Off State', |
||||
|
help='State where the vehicle will be dropped off.', |
||||
|
required=True) |
||||
|
pickup_zip = fields.Char( |
||||
|
string='Pick-Up ZIP', |
||||
|
help='ZIP code for the pick-up location.', |
||||
|
required=True) |
||||
|
dropoff_zip = fields.Char(string='Drop-Off ZIP', |
||||
|
help='ZIP code for the drop-off location.') |
||||
|
pickup_country_id = fields.Many2one( |
||||
|
'res.country', |
||||
|
string='Pick-Up Country', |
||||
|
help='Country where the vehicle will be picked up.', |
||||
|
required=True) |
||||
|
dropoff_country_id = fields.Many2one( |
||||
|
'res.country', |
||||
|
string='Drop-Off Country', |
||||
|
help='Country where the vehicle will be dropped off.', |
||||
|
required=True) |
||||
|
vehicle_id = fields.Many2one( |
||||
|
'fleet.vehicle', string='Vehicle', |
||||
|
help='The vehicle being rented.', |
||||
|
required=True) |
||||
|
model = fields.Char(related='vehicle_id.model_id.name', string='Model', |
||||
|
help='Model of the rented vehicle.') |
||||
|
transmission = fields.Selection( |
||||
|
[('automatic', 'Automatic'), ('manual', 'Manual')], |
||||
|
string='Transmission', related='vehicle_id.transmission', |
||||
|
help='Transmission type of the rented vehicle.') |
||||
|
fuel_type = fields.Selection( |
||||
|
[ |
||||
|
('diesel', 'Diesel'), |
||||
|
('gasoline', 'Gasoline'), |
||||
|
('full_hybrid', 'Full Hybrid'), |
||||
|
('plug_in_hybrid_diesel', 'Plug-in Hybrid Diesel'), |
||||
|
('plug_in_hybrid_gasoline', 'Plug-in Hybrid Gasoline'), |
||||
|
('cng', 'CNG'), |
||||
|
('lpg', 'LPG'), |
||||
|
('hydrogen', 'Hydrogen'), |
||||
|
('electric', 'Electric'), |
||||
|
], string='Fuel Type', related='vehicle_id.fuel_type', |
||||
|
help='Fuel type of the rented vehicle.') |
||||
|
last_odometer = fields.Float( |
||||
|
string='Last Odometer', |
||||
|
related='vehicle_id.odometer', |
||||
|
help='Last recorded odometer reading of the vehicle.') |
||||
|
odometer_unit = fields.Selection( |
||||
|
[('kilometers', 'km'), ('miles', 'mi')], |
||||
|
string='Odometer Unit', default='kilometers', |
||||
|
related='vehicle_id.odometer_unit', |
||||
|
help='Unit of measurement for the odometer reading.') |
||||
|
|
||||
|
driver_required = fields.Boolean( |
||||
|
string='Driver Required', |
||||
|
help='Indicates if a driver is required for the rental.') |
||||
|
driver_id = fields.Many2one( |
||||
|
'res.partner', string='Driver', |
||||
|
help='Driver assigned to the rental if required.') |
||||
|
charge_type = fields.Selection( |
||||
|
[('excluding', 'Excluding in rent charge'), |
||||
|
('including', 'Including in rent charge')], |
||||
|
string='Charge Type', |
||||
|
help='Specifies if the driver charge is included in the rental ' |
||||
|
'charge or not.') |
||||
|
driver_charge = fields.Float( |
||||
|
string='Driver Charge', |
||||
|
help='Charge for the driver if not included ' |
||||
|
'in the rental charge.') |
||||
|
|
||||
|
# Rent Details |
||||
|
rent_type = fields.Selection( |
||||
|
[('hours', 'Hours'), ('days', 'Days'), |
||||
|
('kilometers', 'Kilometers')], |
||||
|
string='Rent Type', |
||||
|
default='hours', |
||||
|
help='Type of rent calculation (per day, per kilometer, or per mile).') |
||||
|
rent_per_hour = fields.Float(string='Rent / Hour', |
||||
|
help='Rental charge per hour.', |
||||
|
related='vehicle_id.rent_hour') |
||||
|
total_hours = fields.Float(string='Total Hours ', |
||||
|
help="Total Hours Taken for Rent", |
||||
|
compute='_compute_rental_period', store=True, |
||||
|
readonly=False) |
||||
|
rent_per_day = fields.Float(string='Rent / Day', |
||||
|
help='Rental charge per day.', |
||||
|
related='vehicle_id.rent_day') |
||||
|
total_days = fields.Integer(string='Total Days', |
||||
|
help='Total number of rental days.', |
||||
|
compute='_compute_rental_period', store=True, |
||||
|
readonly=False) |
||||
|
rent_per_km = fields.Float(string='Rent / KM', |
||||
|
help='Rental charge per km.' |
||||
|
, related='vehicle_id.rent_kilometer') |
||||
|
total_km = fields.Integer(string='Total KM', |
||||
|
help='Total Kilometers.') |
||||
|
total_rental_charge = fields.Float( |
||||
|
string='Total Rental Charge', |
||||
|
compute='_compute_total_rental_charge', store=True, |
||||
|
help='Total rental charge for the contract.') |
||||
|
|
||||
|
payment_type = fields.Selection( |
||||
|
[('daily', 'Daily'), ('weekly', 'Weekly'), |
||||
|
('monthly', 'Monthly'), |
||||
|
('full', 'Fully')], |
||||
|
string='Payment Type', |
||||
|
help='Payment schedule for the rental charge.', |
||||
|
default='full') |
||||
|
invoice_item_id = fields.Many2one( |
||||
|
'product.product', |
||||
|
string='Invoice Item', |
||||
|
help='Description of the item to be invoiced.', |
||||
|
default=lambda self: self.env.ref( |
||||
|
'advanced_fleet_rental.product_product_vehicle_rental_charge')) |
||||
|
is_extra_charge = fields.Boolean(string='Is any extra charge', |
||||
|
help='Indicates if there are any extra ' |
||||
|
'charges applicable.') |
||||
|
extra_per_hour = fields.Float(string='Extra Charges / Hour', |
||||
|
help='Rental charge per hour.', |
||||
|
related='vehicle_id.charge_hour') |
||||
|
total_extra_hours = fields.Integer(string='Total Extra Hours', |
||||
|
help='Total number of rental hours.') |
||||
|
extra_per_day = fields.Float(string='Extra Charges / Day', |
||||
|
help='Rental charge per hour.', |
||||
|
related='vehicle_id.charge_day') |
||||
|
total_extra_days = fields.Integer(string='Total Extra Days', |
||||
|
help='Total number of rental days.') |
||||
|
extra_per_km = fields.Float(string='Extra Charges / KM', |
||||
|
help='Rental charge per hour.', |
||||
|
related='vehicle_id.charge_kilometer') |
||||
|
|
||||
|
total_extra_km = fields.Float(string='Total Extra K/M', |
||||
|
help="Total Extra K/M taken") |
||||
|
total_extra_charge = fields.Float(string="Total Extra Charge", |
||||
|
help="Extra Charges per K/M", |
||||
|
compute='_compute_total_extra_charge' |
||||
|
, store=True) |
||||
|
|
||||
|
rental_payment_plan_ids = (fields.One2many |
||||
|
('rental.payment.plan', |
||||
|
'contract_id', |
||||
|
string="Vehicle Payment Details", |
||||
|
help="Details of the paymentplans for the" |
||||
|
" vehicle rental.")) |
||||
|
extra_service_ids = (fields.One2many |
||||
|
('extra.service', |
||||
|
'contract_id', |
||||
|
string="Extra Services", |
||||
|
help="List of extra services associated with this" |
||||
|
"vehicle rental." |
||||
|
)) |
||||
|
is_extra_invoice_created = fields.Boolean( |
||||
|
string='Extra Invoice Created', |
||||
|
help="Indicates whether an extra invoice has been created for the" |
||||
|
" extra services.") |
||||
|
image_ids = fields.One2many( |
||||
|
'multi.image', |
||||
|
'contract_id', |
||||
|
string="Images of the Vehicle", |
||||
|
help="Images related to the Vehicles of vehicle rental." |
||||
|
) |
||||
|
insurance_ids = fields.One2many( |
||||
|
'insurance.policy', |
||||
|
'contract_id', |
||||
|
string="Insurance Policy", |
||||
|
help="Insurance policies associated with the vehicle rental.") |
||||
|
|
||||
|
vehicle_to_invoice_count = fields.Integer( |
||||
|
string='Number of vehicle rent to invoice', |
||||
|
compute='_compute_vehicle_to_invoice', |
||||
|
readonly=True, |
||||
|
help="Number of vehicle rental invoices that is created." |
||||
|
) |
||||
|
is_damaged_invoiced = fields.Boolean( |
||||
|
string='Damage Invoice Created', |
||||
|
help="Indicates whether an extra invoice has been created for the" |
||||
|
" extra services.") |
||||
|
cancellation_policy_id = fields.Many2one( |
||||
|
'cancellation.policy', string='Cancellation Policy', |
||||
|
help='Select the cancellation policy applicable for this record. ' |
||||
|
'The cancellation charges will be calculated based on the ' |
||||
|
'selected policy.') |
||||
|
cancellation_charge = fields.Float(string='Cancellation Charge') |
||||
|
|
||||
|
cancellation_terms = fields.Text( |
||||
|
string='Cancellation Terms and Conditions', |
||||
|
related="cancellation_policy_id.terms_conditions", |
||||
|
readonly=False) |
||||
|
is_cancelled_invoiced = fields.Boolean( |
||||
|
string='Cancelled Invoice Created', |
||||
|
help="Indicates whether an cancelled invoice has been created for the" |
||||
|
"Cancellation Policy.") |
||||
|
digital_sign = fields.Binary(string='Signature', help="Binary field to " |
||||
|
"store digital " |
||||
|
"signatures.") |
||||
|
sign_date = fields.Datetime( |
||||
|
string='Sign Date', |
||||
|
help='Date and time of the signature signed.') |
||||
|
currency_id = fields.Many2one( |
||||
|
'res.currency', 'Currency', |
||||
|
default=lambda self: self.env.user.company_id.currency_id.id, |
||||
|
help="if you select this currency bidding will be on that currency " |
||||
|
"itself") |
||||
|
damage_description = fields.Text(string="Damage Description") |
||||
|
damage_amount = fields.Float(string="Damage Amount", |
||||
|
help="Total Amount for the damages") |
||||
|
# Responsible |
||||
|
responsible_id = fields.Many2one( |
||||
|
'res.users', string='Responsible', |
||||
|
help='User responsible for managing the rental contract.', |
||||
|
required=True, tracking=True) |
||||
|
|
||||
|
@api.onchange('pickup_state_id') |
||||
|
def _onchange_pickup_state(self): |
||||
|
""" |
||||
|
Automatically updates the 'Pick-Up Country' field based on the selected 'Pick-Up State'. |
||||
|
|
||||
|
When a user selects a state for vehicle pick-up, this method fetches the corresponding |
||||
|
country from the selected state and sets it in the 'Pick-Up Country' field. |
||||
|
""" |
||||
|
if self.pickup_state_id: |
||||
|
self.pickup_country_id = self.pickup_state_id.country_id |
||||
|
|
||||
|
@api.onchange('dropoff_state_id') |
||||
|
def _onchange_dropoff_state(self): |
||||
|
""" |
||||
|
Automatically updates the 'Drop-Off Country' field based on the selected 'Drop-Off State'. |
||||
|
|
||||
|
When a user selects a state for vehicle drop-off, this method fetches the corresponding |
||||
|
country from the selected state and sets it in the 'Drop-Off Country' field. |
||||
|
""" |
||||
|
if self.dropoff_state_id: |
||||
|
self.dropoff_country_id = self.dropoff_state_id.country_id |
||||
|
|
||||
|
def _group_expand_states(self, states, domain, order): |
||||
|
""" |
||||
|
Expands the available group states for selection. |
||||
|
""" |
||||
|
return ['new', 'in_progress', 'return', 'cancel'] |
||||
|
|
||||
|
@api.model |
||||
|
def create(self, vals_list): |
||||
|
""" |
||||
|
Override the create method to set a default sequence number if not |
||||
|
provided. |
||||
|
""" |
||||
|
if vals_list.get('name', 'New') == 'New': |
||||
|
vals_list['name'] = self.env['ir.sequence'].next_by_code( |
||||
|
'vehicle.sequence') or 'New' |
||||
|
return super().create(vals_list) |
||||
|
|
||||
|
@api.depends('extra_per_hour', 'total_extra_hours', 'extra_per_day', |
||||
|
'total_extra_days', 'extra_per_km', 'total_extra_km') |
||||
|
def _compute_total_extra_charge(self): |
||||
|
""" |
||||
|
Compute the total extra charge based on the rent type and extra usage |
||||
|
(hours, days, kilometers). |
||||
|
""" |
||||
|
for record in self: |
||||
|
if record.rent_type == 'hours': |
||||
|
record.total_extra_charge = (record.extra_per_hour * |
||||
|
record.total_extra_hours) |
||||
|
elif record.rent_type == 'days': |
||||
|
record.total_extra_charge = (record.extra_per_day * |
||||
|
record.total_extra_days) |
||||
|
elif record.rent_type == 'kilometers': |
||||
|
record.total_extra_charge = ( |
||||
|
record.extra_per_km * record.total_extra_km) |
||||
|
else: |
||||
|
record.total_extra_charge = 0 |
||||
|
|
||||
|
@api.depends( |
||||
|
'rent_type', |
||||
|
'rent_per_hour', 'total_hours', |
||||
|
'rent_per_day', 'total_days', |
||||
|
'rent_per_km', 'total_km', |
||||
|
'driver_charge', 'charge_type', |
||||
|
'driver_required' |
||||
|
) |
||||
|
def _compute_total_rental_charge(self): |
||||
|
""" |
||||
|
Compute the total rental charge based on the rent type and usage |
||||
|
(hours, days, kilometers). Include driver charge if applicable. |
||||
|
""" |
||||
|
for record in self: |
||||
|
if record.rent_type == 'hours': |
||||
|
record.total_rental_charge = ( |
||||
|
record.rent_per_hour * record.total_hours) |
||||
|
elif record.rent_type == 'days': |
||||
|
record.total_rental_charge = ( |
||||
|
record.rent_per_day * record.total_days) |
||||
|
elif record.rent_type == 'kilometers': |
||||
|
record.total_rental_charge = ( |
||||
|
record.rent_per_km * record.total_km) |
||||
|
else: |
||||
|
record.total_rental_charge = 0 |
||||
|
if record.charge_type == 'including' and record.driver_required: |
||||
|
record.total_rental_charge += record.driver_charge |
||||
|
|
||||
|
def action_create_extra_invoice(self): |
||||
|
""" |
||||
|
Create an invoice for extra charges incurred during the rental period. |
||||
|
""" |
||||
|
product_id = self.env.ref( |
||||
|
'advanced_fleet_rental.product_product_vehicle_extra_rental_charge') |
||||
|
invoice_vals = { |
||||
|
'partner_id': self.customer_id.id, |
||||
|
'move_type': 'out_invoice', |
||||
|
'vehicle_rental_id': self.id, |
||||
|
'invoice_date': date.today(), |
||||
|
'invoice_line_ids': [(0, 0, { |
||||
|
'product_id': product_id.id, |
||||
|
'name': product_id.name, |
||||
|
'quantity': 1, |
||||
|
'price_unit': self.total_extra_charge, |
||||
|
})], |
||||
|
} |
||||
|
invoice = self.env['account.move'].create(invoice_vals) |
||||
|
invoice.action_post() |
||||
|
|
||||
|
def action_installment(self): |
||||
|
""" |
||||
|
Generate the rental payment plan based on the selected payment type. |
||||
|
""" |
||||
|
self.ensure_one() |
||||
|
self.rental_payment_plan_ids.unlink() |
||||
|
if self.rent_type == 'kilometers' and self.total_km == 0: |
||||
|
raise ValidationError( |
||||
|
_('If the rent type is "kilometers", the total ' |
||||
|
'kilometers cannot be 0.')) |
||||
|
if self.rent_type == 'hours' and self.payment_type != 'full': |
||||
|
raise ValidationError( |
||||
|
_('If the rent type is "hours", the payment type must be ' |
||||
|
'"full".')) |
||||
|
if self.rent_type == 'kilometers' and self.payment_type != 'full': |
||||
|
raise ValidationError( |
||||
|
_('If the rent type is "kilometers", the payment type must be ' |
||||
|
'"full".')) |
||||
|
if (self.rent_type == 'days' and self.payment_type == 'weekly' |
||||
|
and self.total_days < 7): |
||||
|
raise ValidationError(_( |
||||
|
'The total days are less than a week. ' |
||||
|
'Please select a valid payment type.')) |
||||
|
if (self.rent_type == 'days' and self.payment_type == 'monthly' |
||||
|
and self.total_days < 30): |
||||
|
raise ValidationError(_( |
||||
|
'The total days are less than a month. ' |
||||
|
'Please select a valid payment type.')) |
||||
|
pick_up = self.pickup_date |
||||
|
drop_date = self.dropoff_date |
||||
|
total_amount = self.total_rental_charge |
||||
|
|
||||
|
if self.payment_type == 'full': |
||||
|
self.env['rental.payment.plan'].create({ |
||||
|
'contract_id': self.id, |
||||
|
'invoice_item_id': self.invoice_item_id.id, |
||||
|
'payment_date': pick_up, |
||||
|
'payment_amount': total_amount, |
||||
|
'payment_state': 'not_paid', |
||||
|
}) |
||||
|
return |
||||
|
|
||||
|
# Calculate interval and number of installments based on rent_type |
||||
|
if self.rent_type == 'hours': |
||||
|
amount_per_unit = self.rent_per_hour |
||||
|
base_interval = relativedelta(hours=1) |
||||
|
elif self.rent_type == 'days': |
||||
|
amount_per_unit = self.rent_per_day |
||||
|
base_interval = relativedelta(days=1) |
||||
|
|
||||
|
if self.payment_type == 'daily': |
||||
|
payment_interval = relativedelta(days=1) |
||||
|
current_date = pick_up |
||||
|
elif self.payment_type == 'weekly': |
||||
|
payment_interval = relativedelta(weeks=1) |
||||
|
current_date = pick_up |
||||
|
elif self.payment_type == 'monthly': |
||||
|
payment_interval = relativedelta(months=1) |
||||
|
current_date = pick_up + payment_interval |
||||
|
else: |
||||
|
payment_interval = base_interval |
||||
|
while current_date < drop_date: |
||||
|
next_date = min(current_date + payment_interval, drop_date) |
||||
|
|
||||
|
# Calculate units in this payment period |
||||
|
if self.rent_type == 'hours': |
||||
|
units_in_period = ( |
||||
|
next_date - current_date).total_seconds() / 3600 |
||||
|
elif self.rent_type == 'days': |
||||
|
units_in_period = (next_date - current_date).days |
||||
|
|
||||
|
installment_amount = units_in_period * amount_per_unit |
||||
|
|
||||
|
if installment_amount > 0: |
||||
|
self.env['rental.payment.plan'].create({ |
||||
|
'contract_id': self.id, |
||||
|
'invoice_item_id': self.invoice_item_id.id, |
||||
|
'payment_date': current_date, |
||||
|
'payment_amount': installment_amount, |
||||
|
'payment_state': 'not_paid', |
||||
|
}) |
||||
|
|
||||
|
current_date = next_date |
||||
|
# Handle any remaining amount due to rounding |
||||
|
remaining_amount = total_amount - sum( |
||||
|
self.rental_payment_plan_ids.mapped('payment_amount')) |
||||
|
if remaining_amount > 0: |
||||
|
self.env['rental.payment.plan'].create({ |
||||
|
'contract_id': self.id, |
||||
|
'invoice_item_id': self.invoice_item_id.id, |
||||
|
'payment_date': drop_date, |
||||
|
'payment_amount': remaining_amount, |
||||
|
'payment_state': 'not_paid', |
||||
|
}) |
||||
|
|
||||
|
@api.depends('pickup_date', 'dropoff_date') |
||||
|
def _compute_rental_period(self): |
||||
|
""" |
||||
|
Compute the total rental period in hours and days based on pickup |
||||
|
and drop-off dates. |
||||
|
""" |
||||
|
for record in self: |
||||
|
if record.pickup_date and record.dropoff_date: |
||||
|
pickup = fields.Datetime.from_string(record.pickup_date) |
||||
|
dropoff = fields.Datetime.from_string(record.dropoff_date) |
||||
|
delta = dropoff - pickup |
||||
|
|
||||
|
# Calculate total days |
||||
|
total_days = delta.days + 1 |
||||
|
record.total_days = total_days |
||||
|
|
||||
|
# Calculate total hours |
||||
|
total_hours = delta.total_seconds() / 3600 |
||||
|
record.total_hours = total_hours |
||||
|
|
||||
|
else: |
||||
|
record.total_days = 0 |
||||
|
record.total_hours = 0 |
||||
|
|
||||
|
@api.constrains('rent_type', 'pickup_date', 'dropoff_date', 'total_hours', |
||||
|
'total_days', ) |
||||
|
def _check_rental_period(self): |
||||
|
""" |
||||
|
Ensure the drop-off date is not before the pick-up date. |
||||
|
""" |
||||
|
for record in self: |
||||
|
if record.pickup_date and record.dropoff_date: |
||||
|
pickup = fields.Datetime.from_string(record.pickup_date) |
||||
|
dropoff = fields.Datetime.from_string(record.dropoff_date) |
||||
|
delta = dropoff - pickup |
||||
|
|
||||
|
if record.rent_type == 'hours': |
||||
|
total_hours_computed = delta.total_seconds() / 3600 |
||||
|
if record.total_hours > total_hours_computed: |
||||
|
raise ValidationError( |
||||
|
f'The total hours ({record.total_hours})' |
||||
|
f' exceed the period between the pickup ' |
||||
|
f'and dropoff dates.') |
||||
|
if record.total_hours == 0: |
||||
|
raise ValidationError( |
||||
|
f'The total hours cannot be zero.') |
||||
|
if record.rent_type == 'days': |
||||
|
total_days_computed = delta.days + 1 |
||||
|
if record.total_days > total_days_computed: |
||||
|
raise ValidationError( |
||||
|
f'The total days ({record.total_days})' |
||||
|
f' exceed the period between the pickup ' |
||||
|
f'and dropoff dates.') |
||||
|
if record.total_days == 0: |
||||
|
raise ValidationError( |
||||
|
f'The total days cannot be zero.') |
||||
|
|
||||
|
def action_account_tab(self): |
||||
|
"""View the Invoices in the Smart Tab.""" |
||||
|
return { |
||||
|
'type': 'ir.actions.act_window', |
||||
|
'name': 'Invoices', |
||||
|
'res_model': 'account.move', |
||||
|
'target': 'current', |
||||
|
'domain': [('partner_id', '=', self.customer_id.id), |
||||
|
('vehicle_rental_id', '=', self.id)], |
||||
|
'view_mode': 'tree,form', |
||||
|
} |
||||
|
|
||||
|
def _compute_vehicle_to_invoice(self): |
||||
|
""" |
||||
|
Computes the number of invoices related to the vehicle rental |
||||
|
contract and updates the 'vehicle_to_invoice_count' field for each record. |
||||
|
""" |
||||
|
for record in self: |
||||
|
record.vehicle_to_invoice_count = self.env[ |
||||
|
'account.move'].search_count([ |
||||
|
('vehicle_rental_id', '=', record.id) |
||||
|
]) |
||||
|
|
||||
|
def action_extra_invoice_charge(self): |
||||
|
""" |
||||
|
Creates an invoice for extra services added to the vehicle |
||||
|
rental contract. If there are no extra services, raises a |
||||
|
ValidationError. |
||||
|
""" |
||||
|
if self.extra_service_ids: |
||||
|
invoice_line_vals = [ |
||||
|
{ |
||||
|
'product_id': line.product_id.id, |
||||
|
'name': line.description or line.product_id.name, |
||||
|
'quantity': line.quantity, |
||||
|
'price_unit': line.amount, |
||||
|
} |
||||
|
for line in self.extra_service_ids |
||||
|
] |
||||
|
invoice_vals = { |
||||
|
'move_type': 'out_invoice', |
||||
|
'partner_id': self.customer_id.id, |
||||
|
'invoice_line_ids': [(0, 0, line) for line in |
||||
|
invoice_line_vals], |
||||
|
'vehicle_rental_id': self.id, |
||||
|
} |
||||
|
invoice = self.env['account.move'].create(invoice_vals) |
||||
|
invoice.action_post() |
||||
|
self.is_extra_invoice_created = True |
||||
|
else: |
||||
|
raise ValidationError( |
||||
|
_('Add Extra Services Products.')) |
||||
|
|
||||
|
def action_damage_invoice(self): |
||||
|
""" |
||||
|
This method opens a new window to link invoices and remove invoices |
||||
|
for the current sale order. |
||||
|
""" |
||||
|
return { |
||||
|
"type": "ir.actions.act_window", |
||||
|
"name": "Damage Invoices", |
||||
|
"view_mode": "form", |
||||
|
"res_model": "damage.invoice", |
||||
|
"target": "new", |
||||
|
"context": { |
||||
|
"default_contract_id": self.id, |
||||
|
}, |
||||
|
} |
||||
|
|
||||
|
def action_cancel(self): |
||||
|
""" |
||||
|
Cancels the rental contract by setting its state to 'cancel'. |
||||
|
""" |
||||
|
self.write({'state': 'cancel'}) |
||||
|
|
||||
|
def action_cancel_charges(self): |
||||
|
""" |
||||
|
Creates an invoice for the cancellation charges based on the contract's |
||||
|
cancellation policy. The invoice is created using a predefined product |
||||
|
for cancellation charges. If the cancellation policy is not set, |
||||
|
it raises a ValidationError. |
||||
|
""" |
||||
|
if self.cancellation_policy_id: |
||||
|
product_id = self.env.ref( |
||||
|
'advanced_fleet_rental.product_product_vehicle_cancel_charge') |
||||
|
invoice_vals = { |
||||
|
'partner_id': self.customer_id.id, |
||||
|
'move_type': 'out_invoice', |
||||
|
'vehicle_rental_id': self.id, |
||||
|
'invoice_date': date.today(), |
||||
|
'invoice_line_ids': [(0, 0, { |
||||
|
'product_id': product_id.id, |
||||
|
'name': self.cancellation_terms, |
||||
|
'quantity': 1, |
||||
|
'price_unit': self.cancellation_charge, |
||||
|
})], |
||||
|
} |
||||
|
invoice = self.env['account.move'].create(invoice_vals) |
||||
|
self.is_cancelled_invoiced = True |
||||
|
return { |
||||
|
'type': 'ir.actions.act_window', |
||||
|
'res_model': 'account.move', |
||||
|
'view_mode': 'form', |
||||
|
'res_id': invoice.id, |
||||
|
'target': 'current', |
||||
|
} |
||||
|
else: |
||||
|
raise ValidationError( |
||||
|
_("No cancellation policy set on the contract.")) |
@ -0,0 +1,45 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################### |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
||||
|
# Author: Anfas Faisal K (odoo@cybrosys.com) |
||||
|
# |
||||
|
# This program is under the terms of the Odoo Proprietary License v1.0(OPL-1) |
||||
|
# It is forbidden to publish, distribute, sublicense, or sell copies of the |
||||
|
# Software or modified copies of the Software. |
||||
|
# |
||||
|
# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL |
||||
|
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,DAMAGES OR OTHER |
||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,ARISING |
||||
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
||||
|
# DEALINGS IN THE SOFTWARE. |
||||
|
# |
||||
|
############################################################################### |
||||
|
from odoo import fields, models |
||||
|
|
||||
|
|
||||
|
class FleetVehicle(models.Model): |
||||
|
""" |
||||
|
Inherits from 'fleet.vehicle' to add additional fields and functionality. |
||||
|
""" |
||||
|
_inherit = 'fleet.vehicle' |
||||
|
|
||||
|
status = fields.Selection([ |
||||
|
('operational', 'Operational'), |
||||
|
('undermaintenance', 'Under Maintenance'), |
||||
|
], default='operational') |
||||
|
rent_hour = fields.Float(string='Rent / Hour', help="Rent per hour") |
||||
|
rent_day = fields.Float(string='Rent / Day', help="Rent per day") |
||||
|
rent_kilometer = fields.Float(string='Rent / Kilometer', |
||||
|
help="Rent per kilometer") |
||||
|
charge_hour = fields.Float(string='Extra Charge / Hour', |
||||
|
help="Extra charge per hour") |
||||
|
charge_day = fields.Float(string='Extra Charge / Day', |
||||
|
help="Extra charge per day") |
||||
|
|
||||
|
charge_kilometer = fields.Float(string='Extra Charge / Kilometer', |
||||
|
help="Extra charge per kilometer") |
@ -0,0 +1,55 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################### |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
||||
|
# Author: Anfas Faisal K (odoo@cybrosys.com) |
||||
|
# |
||||
|
# This program is under the terms of the Odoo Proprietary License v1.0(OPL-1) |
||||
|
# It is forbidden to publish, distribute, sublicense, or sell copies of the |
||||
|
# Software or modified copies of the Software. |
||||
|
# |
||||
|
# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL |
||||
|
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,DAMAGES OR OTHER |
||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,ARISING |
||||
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
||||
|
# DEALINGS IN THE SOFTWARE. |
||||
|
# |
||||
|
############################################################################### |
||||
|
from odoo import fields, models |
||||
|
|
||||
|
|
||||
|
class InsurancePolicy(models.Model): |
||||
|
""" |
||||
|
Model for managing insurance policies related to fleet rental contracts. |
||||
|
""" |
||||
|
_name = 'insurance.policy' |
||||
|
_description = 'Insurance Policy' |
||||
|
|
||||
|
policy_number = fields.Char( |
||||
|
string='Policy Number', |
||||
|
required=True, |
||||
|
help='Unique identifier for the insurance policy.') |
||||
|
name = fields.Char( |
||||
|
string='Name', |
||||
|
required=True, |
||||
|
help='Name of the insurance policy.') |
||||
|
description = fields.Char( |
||||
|
string='Description', |
||||
|
help='Brief description of the insurance policy.') |
||||
|
document = fields.Binary( |
||||
|
string='Document', |
||||
|
help='Upload the document related to the insurance policy.') |
||||
|
file_name = fields.Char( |
||||
|
string="File Name", |
||||
|
help='File Name of the Document') |
||||
|
|
||||
|
policy_amount = fields.Float( |
||||
|
string='Policy Amount', |
||||
|
help='Total amount covered by the insurance policy.') |
||||
|
contract_id = fields.Many2one('fleet.rental.contract', |
||||
|
string='Contract Rent', |
||||
|
help='Reference to the vehicle rent.') |
@ -0,0 +1,35 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################### |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
||||
|
# Author: Anfas Faisal K (odoo@cybrosys.com) |
||||
|
# |
||||
|
# This program is under the terms of the Odoo Proprietary License v1.0(OPL-1) |
||||
|
# It is forbidden to publish, distribute, sublicense, or sell copies of the |
||||
|
# Software or modified copies of the Software. |
||||
|
# |
||||
|
# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL |
||||
|
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,DAMAGES OR OTHER |
||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,ARISING |
||||
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
||||
|
# DEALINGS IN THE SOFTWARE. |
||||
|
# |
||||
|
############################################################################### |
||||
|
from odoo import fields, models |
||||
|
|
||||
|
|
||||
|
class MultiImage(models.Model): |
||||
|
""" |
||||
|
Model for storing multiple images related to a fleet rental contract. |
||||
|
""" |
||||
|
_name = 'multi.image' |
||||
|
_description = 'Multi Image' |
||||
|
|
||||
|
image = fields.Binary(string="Image", help="Upload the Images") |
||||
|
contract_id = fields.Many2one( |
||||
|
'fleet.rental.contract', string='Rental Contract', |
||||
|
ondelete='cascade', readonly=True) |
@ -0,0 +1,105 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################### |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
||||
|
# Author: Anfas Faisal K (odoo@cybrosys.com) |
||||
|
# |
||||
|
# This program is under the terms of the Odoo Proprietary License v1.0(OPL-1) |
||||
|
# It is forbidden to publish, distribute, sublicense, or sell copies of the |
||||
|
# Software or modified copies of the Software. |
||||
|
# |
||||
|
# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL |
||||
|
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,DAMAGES OR OTHER |
||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,ARISING |
||||
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
||||
|
# DEALINGS IN THE SOFTWARE. |
||||
|
# |
||||
|
############################################################################### |
||||
|
from datetime import date |
||||
|
from odoo import api, fields, models |
||||
|
|
||||
|
|
||||
|
class RentalPaymentPlan(models.Model): |
||||
|
""" |
||||
|
Model for managing rental payment plans associated with fleet rental |
||||
|
contracts. |
||||
|
""" |
||||
|
_name = 'rental.payment.plan' |
||||
|
_description = 'Rental Payment Plan' |
||||
|
|
||||
|
contract_id = fields.Many2one( |
||||
|
'fleet.rental.contract', string='Rental Contract', |
||||
|
ondelete='cascade') |
||||
|
invoice_item_id = fields.Many2one('product.product', |
||||
|
string='Invoice Item', ) |
||||
|
payment_date = fields.Date(string='Payment Date' ) |
||||
|
payment_amount = fields.Float( |
||||
|
string='Payment Amount', |
||||
|
help='Amount to be paid based on the invoice item.') |
||||
|
invoice_id = fields.Many2one('account.move', string='Invoice', |
||||
|
readonly=True) |
||||
|
payment_state = fields.Selection( |
||||
|
[ |
||||
|
('not_paid', 'Not Paid'), |
||||
|
('in_payment', 'In Payment'), |
||||
|
('paid', 'Paid'), |
||||
|
('partial', 'Partially Paid'), |
||||
|
('reversed', 'Reversed'), |
||||
|
('invoicing_legacy', 'Invoicing App Legacy'), |
||||
|
], compute='_compute_payment_state', |
||||
|
string='Payment State') |
||||
|
is_invoiced = fields.Boolean(string="Invoice Button", help="Invoiced") |
||||
|
|
||||
|
@api.depends('invoice_id.payment_state') |
||||
|
def _compute_payment_state(self): |
||||
|
""" |
||||
|
Computes the payment state based on the associated invoice's |
||||
|
payment state. |
||||
|
""" |
||||
|
for record in self: |
||||
|
if record.invoice_id: |
||||
|
record.payment_state = record.invoice_id.payment_state |
||||
|
else: |
||||
|
record.payment_state = 'not_paid' |
||||
|
|
||||
|
def action_create_invoice(self): |
||||
|
""" |
||||
|
Creates an invoice for the payment plan. |
||||
|
""" |
||||
|
self.ensure_one() |
||||
|
invoice_vals = { |
||||
|
'partner_id': self.contract_id.customer_id.id, |
||||
|
'move_type': 'out_invoice', |
||||
|
'invoice_date': self.payment_date, |
||||
|
'vehicle_rental_id': self.contract_id.id, |
||||
|
'invoice_line_ids': [(0, 0, { |
||||
|
'product_id': self.invoice_item_id.id, |
||||
|
'name': self.invoice_item_id.name, |
||||
|
'quantity': 1, |
||||
|
'price_unit': self.payment_amount, |
||||
|
})], |
||||
|
} |
||||
|
invoice = self.env['account.move'].create(invoice_vals) |
||||
|
invoice.action_post() |
||||
|
self.invoice_id = invoice.id |
||||
|
self.is_invoiced = True |
||||
|
return invoice |
||||
|
|
||||
|
@api.model |
||||
|
def _schedule_auto_invoice_checker(self): |
||||
|
""" |
||||
|
Scheduled action to automatically generate invoices for rental payment |
||||
|
plans where the payment date is today. Searches for payment plans with |
||||
|
today's date and no associated invoice, and generates invoices for them. |
||||
|
""" |
||||
|
today = date.today() |
||||
|
payment_plans = self.search([ |
||||
|
('payment_date', '=', today), |
||||
|
('invoice_id', '=', False) |
||||
|
]) |
||||
|
for plan in payment_plans: |
||||
|
plan.action_create_invoice() |
@ -0,0 +1,13 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<odoo> |
||||
|
<!-- Report Function inside Contract Form--> |
||||
|
<record id="vehicle_contract_report" model="ir.actions.report"> |
||||
|
<field name="name">Vehicle Rental Report</field> |
||||
|
<field name="model">fleet.rental.contract</field> |
||||
|
<field name="report_type">qweb-pdf</field> |
||||
|
<field name="report_name">advanced_fleet_rental.report_fleet_rental_contract</field> |
||||
|
<field name="report_file">advanced_fleet_rental.report_fleet_rental_contract</field> |
||||
|
<field name="binding_model_id" ref="model_fleet_rental_contract"/> |
||||
|
<field name="binding_type">report</field> |
||||
|
</record> |
||||
|
</odoo> |
@ -0,0 +1,393 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8" ?> |
||||
|
<odoo> |
||||
|
<!-- Report Template--> |
||||
|
<template id="report_fleet_rental_contract"> |
||||
|
<t t-call="web.html_container"> |
||||
|
<t t-foreach="docs" t-as="data"> |
||||
|
<t t-call="web.external_layout"> |
||||
|
<div class="page" style="font-size: 12pt;"> |
||||
|
<h2 style="text-align: center;">Vehicle Contract |
||||
|
Details |
||||
|
</h2> |
||||
|
<p>Reference Number: |
||||
|
<span t-field="data.name"/> |
||||
|
</p> |
||||
|
|
||||
|
<table class="table table-sm" |
||||
|
style="border: 1px solid transparent; border-collapse: collapse; width: 100%;"> |
||||
|
<tr> |
||||
|
<td colspan="2"> |
||||
|
<strong>Customer Details</strong> |
||||
|
</td> |
||||
|
</tr> |
||||
|
<tr> |
||||
|
<td style="width: 50%;"> |
||||
|
<span>Customer:</span> |
||||
|
<span t-field="data.customer_id.name"/> |
||||
|
</td> |
||||
|
<td style="width: 50%; text-align: right;"> |
||||
|
<span>Phone:</span> |
||||
|
<span t-field="data.customer_id.phone"/> |
||||
|
<br/> |
||||
|
<span>Email:</span> |
||||
|
<span t-field="data.email"/> |
||||
|
</td> |
||||
|
</tr> |
||||
|
</table> |
||||
|
<table class="table table-sm" |
||||
|
style="border: 1px solid transparent; width: 100%; margin-top: 10px;"> |
||||
|
|
||||
|
<tr> |
||||
|
<td colspan="2"> |
||||
|
<strong>Pick-Up & Drop-Off Details |
||||
|
</strong> |
||||
|
</td> |
||||
|
</tr> |
||||
|
<tr> |
||||
|
<td style="width: 50%; vertical-align: top;"> |
||||
|
<span>Pick-up Date:</span> |
||||
|
<span t-field="data.pickup_date"/> |
||||
|
<br/> |
||||
|
<span>Pick-Up Location:</span> |
||||
|
<span t-field="data.pickup_location"/> |
||||
|
<br/> |
||||
|
<span t-field="data.pickup_street"/> |
||||
|
<br/> |
||||
|
<span t-field="data.pickup_city"/>, |
||||
|
<span t-field="data.pickup_state_id.name"/> |
||||
|
<span t-field="data.pickup_zip"/> |
||||
|
<br/> |
||||
|
<span t-field="data.pickup_country_id.name"/> |
||||
|
</td> |
||||
|
<td style="width: 50%; vertical-align: top;"> |
||||
|
<span>Drop-off Date:</span> |
||||
|
<span t-field="data.dropoff_date"/> |
||||
|
<br/> |
||||
|
<span>Drop-Off Location:</span> |
||||
|
<span t-field="data.dropoff_location"/> |
||||
|
<br/> |
||||
|
<span t-field="data.dropoff_street"/> |
||||
|
<br/> |
||||
|
<span t-field="data.dropoff_city"/>, |
||||
|
<span t-field="data.dropoff_state_id.name"/> |
||||
|
<span t-field="data.dropoff_zip"/> |
||||
|
<br/> |
||||
|
<span t-field="data.dropoff_country_id.name"/> |
||||
|
</td> |
||||
|
</tr> |
||||
|
</table> |
||||
|
|
||||
|
<!--Vehicle Deatils--> |
||||
|
<table class="table table-sm" |
||||
|
style="border: 1px solid transparent; width: 100%; margin-top: 10px;"> |
||||
|
<tr> |
||||
|
<td colspan="2"> |
||||
|
<strong>Vehicle Details</strong> |
||||
|
</td> |
||||
|
</tr> |
||||
|
<tr> |
||||
|
<td style="width: 50%; vertical-align: top;"> |
||||
|
<span>Vehicle:</span> |
||||
|
<span t-field="data.vehicle_id.name"/> |
||||
|
<br/> |
||||
|
<span>Model:</span> |
||||
|
<span t-field="data.model"/> |
||||
|
<br/> |
||||
|
<span>Transmission:</span> |
||||
|
<span t-field="data.transmission"/> |
||||
|
<br/> |
||||
|
<span>Fuel Type:</span> |
||||
|
<span t-field="data.fuel_type"/> |
||||
|
<br/> |
||||
|
<span>Last Odometer:</span> |
||||
|
<span t-field="data.last_odometer"/> |
||||
|
<span t-field="data.odometer_unit"/> |
||||
|
</td> |
||||
|
<td style="width: 50%; vertical-align: top;"> |
||||
|
<t t-if="data.driver_required"> |
||||
|
<span>Driver:</span> |
||||
|
<span t-field="data.driver_id.name"/> |
||||
|
<br/> |
||||
|
<span>Driver Charge Type:</span> |
||||
|
<span t-field="data.charge_type"/> |
||||
|
<br/> |
||||
|
|
||||
|
<span>Driver Charge:</span> |
||||
|
<span t-field="data.driver_charge" |
||||
|
t-options="{'widget': 'monetary', 'display_currency': data.currency_id}"/> |
||||
|
</t> |
||||
|
</td> |
||||
|
</tr> |
||||
|
</table> |
||||
|
|
||||
|
<table class="table table-sm" |
||||
|
style="border: 1px solid transparent; width: 100%; margin-top: 10px;"> |
||||
|
<tr> |
||||
|
<td style="width: 50%; vertical-align: top;"> |
||||
|
<strong>Rent Details</strong> |
||||
|
<br/> |
||||
|
|
||||
|
<span>Rent Type:</span> |
||||
|
<span t-field="data.rent_type"/> |
||||
|
<br/> |
||||
|
<span>Rent /</span> |
||||
|
<span t-field="data.rent_type"/> |
||||
|
<span>:</span> |
||||
|
<span t-field="data.rent_per_hour" |
||||
|
t-if="data.rent_type == 'hours'" |
||||
|
t-options="{'widget': 'monetary', 'display_currency': data.currency_id}"/> |
||||
|
<span t-field="data.rent_per_day" |
||||
|
t-if="data.rent_type == 'days'" |
||||
|
t-options="{'widget': 'monetary', 'display_currency': data.currency_id}"/> |
||||
|
<span t-field="data.rent_per_km" |
||||
|
t-if="data.rent_type == 'kilometers'" |
||||
|
t-options="{'widget': 'monetary', 'display_currency': data.currency_id}"/> |
||||
|
<br/> |
||||
|
<span>Total</span> |
||||
|
<span t-field="data.rent_type"/> |
||||
|
<span>:</span> |
||||
|
<span t-field="data.total_hours" |
||||
|
t-if="data.rent_type == 'hours'"/> |
||||
|
<span t-field="data.total_days" |
||||
|
t-if="data.rent_type == 'days'"/> |
||||
|
<span t-field="data.total_km" |
||||
|
t-if="data.rent_type == 'kilometers'"/> |
||||
|
<br/> |
||||
|
<span>Total:</span> |
||||
|
<span t-field="data.total_rental_charge" |
||||
|
t-options="{'widget': 'monetary', 'display_currency': data.currency_id}"/> |
||||
|
</td> |
||||
|
<td style="width: 50%; vertical-align: top;"> |
||||
|
<strong>Payment Details</strong> |
||||
|
<br/> |
||||
|
<span>Payment Type:</span> |
||||
|
<span t-field="data.payment_type"/> |
||||
|
<br/> |
||||
|
<span>Invoice Item:</span> |
||||
|
<span t-field="data.invoice_item_id"/> |
||||
|
<br/> |
||||
|
</td> |
||||
|
</tr> |
||||
|
</table> |
||||
|
<!-- Extra Charges Details--> |
||||
|
<table class="table table-sm" |
||||
|
style="border: 1px solid transparent; width: 100%; margin-top: 10px;"> |
||||
|
<tr> |
||||
|
<td style="width: 50%; vertical-align: top;"> |
||||
|
<strong>Extra Charges Details</strong> |
||||
|
<br/> |
||||
|
<span>Is any extra charges:</span> |
||||
|
|
||||
|
|
||||
|
<span t-field="data.is_extra_charge"/> |
||||
|
<br/> |
||||
|
<span>Total Extra:</span> |
||||
|
<span t-field="data.rent_type"/> |
||||
|
: |
||||
|
<span t-field="data.total_extra_hours" |
||||
|
t-if="data.rent_type == 'hours'"/> |
||||
|
<span t-field="data.total_extra_days" |
||||
|
t-if="data.rent_type == 'days'"/> |
||||
|
<span t-field="data.total_extra_km" |
||||
|
t-if="data.rent_type == 'kilometers'"/> |
||||
|
<br/> |
||||
|
|
||||
|
<span>Extra Charge /</span> |
||||
|
<span t-field="data.rent_type"/> |
||||
|
<span t-field="data.extra_per_hour" |
||||
|
t-if="data.rent_type == 'hours'"/> |
||||
|
<span t-field="data.extra_per_day" |
||||
|
t-if="data.rent_type == 'days'"/> |
||||
|
<span t-field="data.extra_per_km" |
||||
|
t-if="data.rent_type == 'kilometers'"/> |
||||
|
<br/> |
||||
|
<span>Total Extra Charges:</span> |
||||
|
<span t-field="data.total_extra_charge" |
||||
|
t-options="{'widget': 'monetary', 'display_currency': data.currency_id}"/> |
||||
|
|
||||
|
</td> |
||||
|
<td style="width: 50%; vertical-align: top;"> |
||||
|
<strong>Responsible Details</strong> |
||||
|
<br/> |
||||
|
<span>Responsible:</span> |
||||
|
<span t-field="data.responsible_id.name"/> |
||||
|
</td> |
||||
|
</tr> |
||||
|
</table> |
||||
|
<!--Vehicle Payment Option--> |
||||
|
<h4 style="margin-top: 20px;">Vehicle Payment Option |
||||
|
</h4> |
||||
|
<table class="table table-sm" |
||||
|
style="border: 1px solid black; border-collapse: collapse; width: 100%;"> |
||||
|
<thead> |
||||
|
<tr style="background-color: #f2f2f2;"> |
||||
|
<th style="border: 1px solid black; padding: 5px; text-align: left;"> |
||||
|
Invoice Item |
||||
|
</th> |
||||
|
<th style="border: 1px solid black; padding: 5px; text-align: left;"> |
||||
|
Payment Date |
||||
|
</th> |
||||
|
<th style="border: 1px solid black; padding: 5px; text-align: left;"> |
||||
|
Payment Amount Invoice |
||||
|
</th> |
||||
|
<th style="border: 1px solid black; padding: 5px; text-align: left;"> |
||||
|
Payment State |
||||
|
</th> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody> |
||||
|
<tr t-foreach="data.rental_payment_plan_ids" |
||||
|
t-as="payment"> |
||||
|
<td style="border: 1px solid black; padding: 5px;"> |
||||
|
<span t-field="payment.invoice_item_id"/> |
||||
|
</td> |
||||
|
<td style="border: 1px solid black; padding: 5px;"> |
||||
|
<span t-field="payment.payment_date"/> |
||||
|
</td> |
||||
|
<td style="border: 1px solid black; padding: 5px;"> |
||||
|
<span t-field="payment.payment_amount" |
||||
|
t-options="{'widget': 'monetary', 'display_currency': data.currency_id}"/> |
||||
|
<t t-if="payment.invoice_id"> |
||||
|
<span t-field="payment.invoice_id.name"/> |
||||
|
</t> |
||||
|
<t t-else=""> |
||||
|
Draft Invoice |
||||
|
</t> |
||||
|
</td> |
||||
|
<td style="border: 1px solid black; padding: 5px;"> |
||||
|
<span t-field="payment.payment_state"/> |
||||
|
</td> |
||||
|
</tr> |
||||
|
</tbody> |
||||
|
</table> |
||||
|
<!-- Insurance Policy--> |
||||
|
<h4 style="margin-top: 20px;">Insurance Policy</h4> |
||||
|
<table class="table table-sm" |
||||
|
style="border: 1px solid black; border-collapse: collapse; width: 100%;"> |
||||
|
<thead> |
||||
|
<tr style="background-color: #f2f2f2;"> |
||||
|
<th style="border: 1px solid black; padding: 5px; text-align: left;"> |
||||
|
Policy Number |
||||
|
</th> |
||||
|
<th style="border: 1px solid black; padding: 5px; text-align: left;"> |
||||
|
Name |
||||
|
</th> |
||||
|
<th style="border: 1px solid black; padding: 5px; text-align: left;"> |
||||
|
Description |
||||
|
</th> |
||||
|
<th style="border: 1px solid black; padding: 5px; text-align: left;"> |
||||
|
Policy Amount |
||||
|
</th> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody> |
||||
|
<tr t-foreach="data.insurance_ids" |
||||
|
t-as="insurance"> |
||||
|
<td style="border: 1px solid black; padding: 5px;"> |
||||
|
<span t-field="insurance.policy_number"/> |
||||
|
</td> |
||||
|
<td style="border: 1px solid black; padding: 5px;"> |
||||
|
<span t-field="insurance.name"/> |
||||
|
</td> |
||||
|
<td style="border: 1px solid black; padding: 5px;"> |
||||
|
<span t-field="insurance.description"/> |
||||
|
</td> |
||||
|
<td style="border: 1px solid black; padding: 5px;"> |
||||
|
<span t-field="insurance.policy_amount" |
||||
|
t-options="{'widget': 'monetary', 'display_currency': data.currency_id}"/> |
||||
|
</td> |
||||
|
</tr> |
||||
|
</tbody> |
||||
|
</table> |
||||
|
<!--Extra Services--> |
||||
|
<h4 style="margin-top: 20px;">Extra Services</h4> |
||||
|
<table class="table table-sm" |
||||
|
style="border: 1px solid black; border-collapse: collapse; width: 100%;"> |
||||
|
<thead> |
||||
|
<tr style="background-color: #f2f2f2;"> |
||||
|
<th style="border: 1px solid black; padding: 5px; text-align: left;"> |
||||
|
Product |
||||
|
</th> |
||||
|
<th style="border: 1px solid black; padding: 5px; text-align: left;"> |
||||
|
Qty |
||||
|
</th> |
||||
|
<th style="border: 1px solid black; padding: 5px; text-align: left;"> |
||||
|
Description |
||||
|
</th> |
||||
|
<th style="border: 1px solid black; padding: 5px; text-align: left;"> |
||||
|
Amount |
||||
|
</th> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody> |
||||
|
<tr t-foreach="data.extra_service_ids" |
||||
|
t-as="service"> |
||||
|
<td style="border: 1px solid black; padding: 5px;"> |
||||
|
<span t-field="service.product_id"/> |
||||
|
</td> |
||||
|
<td style="border: 1px solid black; padding: 5px;"> |
||||
|
<span t-field="service.quantity"/> |
||||
|
</td> |
||||
|
<td style="border: 1px solid black; padding: 5px;"> |
||||
|
<span t-field="service.description"/> |
||||
|
</td> |
||||
|
<td style="border: 1px solid black; padding: 5px;"> |
||||
|
<span t-field="service.amount" |
||||
|
t-options="{'widget': 'monetary', 'display_currency': data.currency_id}"/> |
||||
|
</td> |
||||
|
</tr> |
||||
|
</tbody> |
||||
|
</table> |
||||
|
<!-- Damage Details Tab --> |
||||
|
<h4 style="margin-top: 20px;">Vehicle Damages</h4> |
||||
|
<table class="table table-sm" |
||||
|
style="border: 1px solid black; border-collapse: collapse; width: 100%;"> |
||||
|
<tr> |
||||
|
<td style="border: 1px solid black; padding: 5px;"> |
||||
|
<strong>Total Damage Amount:</strong> |
||||
|
</td> |
||||
|
<td style="border: 1px solid black; padding: 5px;"> |
||||
|
<span t-field="data.damage_amount" |
||||
|
t-options="{'widget': 'monetary', 'display_currency': data.currency_id}"/> |
||||
|
</td> |
||||
|
</tr> |
||||
|
</table> |
||||
|
|
||||
|
<p style="margin-top: 10px;"> |
||||
|
<strong>Descriptions</strong> |
||||
|
</p> |
||||
|
<p t-field="data.damage_description"/> |
||||
|
<!-- Vehicle Images --> |
||||
|
<h4 style="margin-top: 20px;">Vehicle Images</h4> |
||||
|
<div class="vehicle-images" |
||||
|
style="text-align: center;"> |
||||
|
<t t-foreach="data.image_ids" t-as="image_record"> |
||||
|
<img t-if="image_record.image" |
||||
|
t-att-src="image_record.image and ('data:image/png;base64,%s' % image_record.image.decode('utf-8'))" |
||||
|
alt="Vehicle Image" |
||||
|
style="max-width: 200px; margin: 10px;"/> |
||||
|
</t> |
||||
|
</div> |
||||
|
|
||||
|
<!-- Signature Section --> |
||||
|
<div class="signature-section" |
||||
|
style="margin-top: 30px; text-align: right;"> |
||||
|
|
||||
|
<p>Signature Date: |
||||
|
<span t-field="data.sign_date"/> |
||||
|
</p> |
||||
|
<p> |
||||
|
<strong>Signature</strong> |
||||
|
<img t-if="data.digital_sign" |
||||
|
class="mobile-hide" |
||||
|
t-attf-src="data:image/png;base64,{{data.digital_sign}}" |
||||
|
style="width:125px; margin-top:8px;margin-bottom:-25px;" |
||||
|
alt="Signature"/> |
||||
|
</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
</t> |
||||
|
</t> |
||||
|
</t> |
||||
|
</template> |
||||
|
</odoo> |
|
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 628 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 310 B |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 542 B |
After Width: | Height: | Size: 576 B |
After Width: | Height: | Size: 733 B |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 4.0 KiB |
After Width: | Height: | Size: 911 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 673 B |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 878 B |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 653 B |
After Width: | Height: | Size: 905 B |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 839 B |
After Width: | Height: | Size: 5.9 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 427 B |
After Width: | Height: | Size: 627 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 988 B |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 912 KiB |
After Width: | Height: | Size: 81 KiB |
After Width: | Height: | Size: 46 KiB |
After Width: | Height: | Size: 44 KiB |
After Width: | Height: | Size: 46 KiB |
After Width: | Height: | Size: 85 KiB |
After Width: | Height: | Size: 97 KiB |
After Width: | Height: | Size: 162 KiB |
After Width: | Height: | Size: 407 KiB |
After Width: | Height: | Size: 87 KiB |
After Width: | Height: | Size: 134 KiB |
After Width: | Height: | Size: 96 KiB |
After Width: | Height: | Size: 107 KiB |
After Width: | Height: | Size: 102 KiB |
After Width: | Height: | Size: 74 KiB |
After Width: | Height: | Size: 120 KiB |
After Width: | Height: | Size: 353 KiB |
After Width: | Height: | Size: 118 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 184 KiB |
After Width: | Height: | Size: 231 KiB |
After Width: | Height: | Size: 71 KiB |
After Width: | Height: | Size: 96 KiB |
After Width: | Height: | Size: 265 KiB |
After Width: | Height: | Size: 85 KiB |
After Width: | Height: | Size: 114 KiB |
After Width: | Height: | Size: 87 KiB |
After Width: | Height: | Size: 103 KiB |
After Width: | Height: | Size: 103 KiB |
After Width: | Height: | Size: 410 KiB |
After Width: | Height: | Size: 880 KiB |
After Width: | Height: | Size: 95 KiB |
After Width: | Height: | Size: 20 KiB |
@ -0,0 +1,78 @@ |
|||||
|
.components{ |
||||
|
margin: 40px !important; |
||||
|
border-radius: 17px; |
||||
|
padding-bottom: 25px; |
||||
|
} |
||||
|
|
||||
|
.heading{ |
||||
|
margin-top:15px; |
||||
|
margin-bottom:8px; |
||||
|
} |
||||
|
|
||||
|
.tile{ |
||||
|
height: 125px; |
||||
|
border-radius: 30px; |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
flex-direction: column; |
||||
|
} |
||||
|
.tile:hover{ |
||||
|
box-shadow: 0 12px 16px 0 rgba(0,0,0,0.24),0 17px 50px 0 rgba(0,0,0,0.19); |
||||
|
} |
||||
|
.card:hover{ |
||||
|
box-shadow: 0 12px 16px 0 rgba(0,0,0,0.24),0 17px 50px 0 rgba(0,0,0,0.19); |
||||
|
} |
||||
|
|
||||
|
.chart{ |
||||
|
margin: 40px !important; |
||||
|
} |
||||
|
|
||||
|
.row_table{ |
||||
|
margin: 40px !important; |
||||
|
padding-bottom: 40px; |
||||
|
} |
||||
|
/* Basic styling for the header */ |
||||
|
.contracts-header { |
||||
|
font-family: 'Poppins', 'Open Sans', sans-serif; |
||||
|
font-size: 1.5rem; |
||||
|
font-weight: 600; |
||||
|
color: #333; |
||||
|
margin-bottom: 1.5rem; |
||||
|
padding-bottom: 0.5rem; |
||||
|
position: relative; |
||||
|
} |
||||
|
|
||||
|
/* Option 1: With subtle text shadow */ |
||||
|
.contracts-header-shadow { |
||||
|
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.1); |
||||
|
} |
||||
|
|
||||
|
/* Option 2: With gradient text color */ |
||||
|
.contracts-header-gradient { |
||||
|
background: linear-gradient(90deg, #3498db, #2980b9); |
||||
|
-webkit-background-clip: text; |
||||
|
background-clip: text; |
||||
|
color: transparent; |
||||
|
} |
||||
|
|
||||
|
/* Option 3: With icon prefix */ |
||||
|
.contracts-header-icon::before { |
||||
|
content: "📄 "; |
||||
|
margin-right: 8px; |
||||
|
} |
||||
|
|
||||
|
/* Option 4: With subtle background */ |
||||
|
.contracts-header-background { |
||||
|
background-color: #f5f9ff; |
||||
|
padding: 10px 15px; |
||||
|
border-radius: 6px; |
||||
|
border-bottom: none; |
||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); |
||||
|
} |
||||
|
|
||||
|
/* Option 5: Uppercase with letter spacing */ |
||||
|
.contracts-header-uppercase { |
||||
|
text-transform: uppercase; |
||||
|
letter-spacing: 1.5px; |
||||
|
font-size: 1.2rem; |
||||
|
} |
After Width: | Height: | Size: 8.0 KiB |
After Width: | Height: | Size: 5.9 KiB |