@ -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 |