@ -0,0 +1,46 @@ |
|||
.. image:: https://img.shields.io/badge/license-AGPL--3-blue.svg |
|||
:target: http://www.gnu.org/licenses/AGPL-3.0-standalone.html |
|||
:alt: License: AGPL-3 |
|||
|
|||
Vehicle Subscription |
|||
==================== |
|||
This module helps you to Subscribe,Cancel and Change subscription through website as well as backend. |
|||
|
|||
Configuration |
|||
============= |
|||
* No additional configurations needed |
|||
|
|||
Company |
|||
------- |
|||
* `Cybrosys Techno Solutions <https://cybrosys.com/>`__ |
|||
|
|||
Credits |
|||
------- |
|||
* Developers: (v17) Jumana Jabin MP, Contact: odoo@cybrosys.com |
|||
|
|||
License |
|||
------- |
|||
GNU AFFERO GENERAL PUBLIC LICENSE v3.0 (AGPL-3) |
|||
(https://www.gnu.org/licenses/agpl-3.0-standalone.html) |
|||
|
|||
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 `Our Website <https://cybrosys.com/>`__ |
|||
|
|||
Further information |
|||
=================== |
|||
HTML Description: `<static/description/index.html>`__ |
@ -0,0 +1,24 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################# |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# Author: Jumana Jabin MP(<https://www.cybrosys.com>) |
|||
# |
|||
# You can modify it under the terms of the GNU AFFERO |
|||
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
|||
# (AGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################# |
|||
from . import controllers |
|||
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:Jumana Jabin MP (<https://www.cybrosys.com>) |
|||
# |
|||
# You can modify it under the terms of the GNU AFFERO |
|||
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
|||
# (AGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################# |
|||
{ |
|||
'name': 'Vehicle Subscription', |
|||
'version': "17.0.1.0.0", |
|||
'category': 'Fleet', |
|||
'summary': """Subscribe,Cancel and Switch subscription""", |
|||
'description': """This module helps you to Subscribe,Cancel and Change |
|||
subscription through website as well as backend.""", |
|||
'author': 'Cybrosys Techno Solutions', |
|||
'company': 'Cybrosys Techno Solutions', |
|||
'maintainer': 'Cybrosys Techno Solutions', |
|||
'website': 'https://www.cybrosys.com', |
|||
'depends': ['mail', 'contacts', 'fleet', 'account', 'sale', 'website', |
|||
'portal', ], |
|||
'data': [ |
|||
'security/vehicle_subscription_groups.xml', |
|||
'security/ir.model.access.csv', |
|||
'data/ir_cron_data.xml', |
|||
'data/product_template_data.xml', |
|||
'data/website_menu_data.xml', |
|||
'data/mail_data.xml', |
|||
'views/website_portal_subscription_template.xml', |
|||
'views/fleet_vehicle_model_views.xml', |
|||
'views/fleet_subscription_views.xml', |
|||
'views/vehicle_insurance_views.xml', |
|||
'views/subscription_request_views.xml', |
|||
'views/insurance_type_views.xml', |
|||
'views/online_subscription_template.xml', |
|||
'views/online_vehicle_template.xml', |
|||
'views/account_move_views.xml', |
|||
'views/subscription_form_success_template.xml', |
|||
'views/online_vehicle_cancellation_template.xml', |
|||
'views/cancellation_request_views.xml', |
|||
'views/change_vehicle_subscription_template.xml', |
|||
'wizard/change_subscription_views.xml', |
|||
], |
|||
'assets': { |
|||
'web.assets_frontend': [ |
|||
'vehicle_subscription/static/src/js/vehicle_subscription_success.js', |
|||
'vehicle_subscription/static/src/js/website_subscription.js', |
|||
'vehicle_subscription/static/src/js/vehicle_booking.js', |
|||
'vehicle_subscription/static/src/js/change_subscription_form.js', |
|||
'vehicle_subscription/static/src/js/change_subscription_request.js', |
|||
], |
|||
}, |
|||
'images': ['static/description/banner.jpg'], |
|||
'license': 'AGPL-3', |
|||
'installable': True, |
|||
'auto_install': False, |
|||
'application': False, |
|||
} |
@ -0,0 +1,23 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################# |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# Author: Jumana Jabin MP (<https://www.cybrosys.com>) |
|||
# |
|||
# You can modify it under the terms of the GNU AFFERO |
|||
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
|||
# (AGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################# |
|||
from . import vehicle_subscription |
|||
from . import website_portal |
@ -0,0 +1,440 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################# |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# Author: Jumana Jabin MP(<https://www.cybrosys.com>) |
|||
# |
|||
# You can modify it under the terms of the GNU AFFERO |
|||
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
|||
# (AGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################# |
|||
from datetime import datetime, timedelta |
|||
from odoo import http |
|||
from odoo.http import request |
|||
|
|||
|
|||
class OnlineSubscription(http.Controller): |
|||
"""Online Vehicle subscription through website""" |
|||
|
|||
@http.route(['/online/subscription/city'], type='json', auth="public", |
|||
website=True) |
|||
def get_city(self, **kwargs): |
|||
"""Calling this function using ajax rpc in order to get city |
|||
based on state """ |
|||
states = request.env['res.country.state'].sudo().search_read([], |
|||
['name']) |
|||
return states |
|||
|
|||
@http.route('/online/subscription', auth='public', website=True) |
|||
def subscription_form(self): |
|||
"""This function will return vehicle with which state is not null""" |
|||
vehicle_id = request.env['fleet.vehicle'].sudo().search( |
|||
[('states_id', '!=', False)]) |
|||
insurance_type = request.env['insurance.type'].sudo().search([]) |
|||
vals = { |
|||
'states': vehicle_id.states_id, |
|||
'all_states': request.env['res.country.state'].sudo().search_read( |
|||
[], ['name']), |
|||
'cities': [rec.location for rec in vehicle_id], |
|||
'insurance_type': insurance_type, |
|||
} |
|||
return http.request.render('vehicle_subscription.subscription_form', |
|||
vals) |
|||
|
|||
@http.route('/online/subscription/next', auth='public', website=True) |
|||
def vehicle_form(self, **kw): |
|||
"""Redirect to corresponding templates according to the |
|||
data provided by user in form page """ |
|||
if kw.get('start_date'): |
|||
start_date = kw.get('start_date') |
|||
end_date = kw.get('end_date') |
|||
insurance = kw.get('insurance_type') |
|||
start = datetime.strptime(start_date, '%Y-%m-%d').date() |
|||
end = datetime.strptime(end_date, '%Y-%m-%d').date() |
|||
insurance_amount = request.env['vehicle.insurance'].sudo().browse( |
|||
int(insurance)).insurance_amount |
|||
# print("insurance_amount",insurance_amount) |
|||
insurance_type = request.env['vehicle.insurance'].sudo().search( |
|||
[('insurance_type_id.id', '=', insurance), |
|||
('start_date', '<=', start), ('end_date', '>=', end)]) |
|||
vehicle_ids = insurance_type.vehicle_id |
|||
subscribed_vehicle_id = (request.env['fleet.subscription'].sudo(). |
|||
search( |
|||
[('state', '=', 'subscribed')]).vehicle_id) |
|||
vehicle = request.env['fleet.vehicle'].sudo().search( |
|||
[('id', 'in', vehicle_ids.ids)]) |
|||
vehicle_id = vehicle.filtered( |
|||
lambda v: v.id not in subscribed_vehicle_id.ids) |
|||
if vehicle_id: |
|||
for rec in vehicle_id: |
|||
rec.write({ |
|||
'insurance': insurance, |
|||
'start': start, |
|||
'end': end, |
|||
}) |
|||
data = { |
|||
'vehicles': vehicle_id, |
|||
'amount': insurance_amount, |
|||
'customers': request.env.user.partner_id.name, |
|||
} |
|||
return http.request.render('vehicle_subscription.vehicle_form', |
|||
data) |
|||
else: |
|||
return http.request.render( |
|||
'vehicle_subscription.subscription_vehicle_missing') |
|||
else: |
|||
return http.request.render('vehicle_subscription.vehicle_form') |
|||
|
|||
@http.route(['/online/subscription/book'], type='json', auth="public", |
|||
website=True) |
|||
def get_vehicle(self, **kwargs): |
|||
"""Ajax RPC handler for booking vehicle subscription and |
|||
creating corresponding invoices in the backend.""" |
|||
extra_km = kwargs.get('extra_km') |
|||
product_template_id = (request.env.ref( |
|||
'vehicle_subscription.product_template_vehicle_subscription_form'). |
|||
id) |
|||
product_id = request.env['product.product'].sudo().search( |
|||
[('product_tmpl_id', '=', product_template_id)]) |
|||
vehicle = int(kwargs.get('vehicle')) |
|||
customer = kwargs.get('customer') |
|||
checked = int(kwargs.get('checked')) |
|||
invoice_type = int(kwargs.get('invoice')) |
|||
vehicle_id = request.env['fleet.vehicle'].sudo().browse(int(vehicle)) |
|||
customer_id = request.env['res.partner'].sudo().search( |
|||
[('name', '=', customer)]) |
|||
if extra_km == '': |
|||
km = 0 |
|||
else: |
|||
km = extra_km |
|||
# print(vehicle_id.insurance, "bbbbbbbbbb") |
|||
# insurance_amount = vehicle_id.insurance.insurance_amount |
|||
# insurance_amount = request.env['vehicle.insurance'].sudo().browse( |
|||
# int(vehicle_id.insurance)).insurance_amount |
|||
# price = vehicle_id.duration * vehicle_id.subscription_price |
|||
# print("aaaaaaaaaaaaaaaa",insurance_amount + price) |
|||
subscribe = request.env['fleet.subscription'].sudo().create({ |
|||
'vehicle_id': vehicle_id.id, |
|||
'customer_id': customer_id.id, |
|||
'insurance_type_id': vehicle_id.insurance, |
|||
'start_date': vehicle_id.start, |
|||
'end_date': vehicle_id.end, |
|||
'extra_km': km, |
|||
'fuel': 'without_fuel' if checked == False else 'with_fuel', |
|||
}) |
|||
subscribe.action_invoice() |
|||
# subscribe.sale_id.order_line.price_unit = price |
|||
subscribe.sale_id.action_confirm() |
|||
if invoice_type: |
|||
subscribe.sale_id._create_invoices().action_post() |
|||
subscribe.invoice_ids.is_subscription = True |
|||
subscribe.sale_id.invoice_ids.is_subscription = True |
|||
else: |
|||
subscribe.sale_id.invoice_status = 'invoiced' |
|||
total_price = subscribe.sale_id.order_line.price_unit |
|||
duration = vehicle_id.duration |
|||
per_day = total_price / duration |
|||
start_date = vehicle_id.start |
|||
end_date = vehicle_id.end |
|||
next_invoice_day = start_date |
|||
while next_invoice_day <= end_date: |
|||
next_invoice_day = next_invoice_day + timedelta(days=30) |
|||
if next_invoice_day <= end_date: |
|||
durations = (next_invoice_day - start_date).days |
|||
generate_invoice = request.env[ |
|||
'account.move'].sudo().create({ |
|||
'move_type': 'out_invoice', |
|||
'partner_id': customer_id.id, |
|||
'invoice_date': next_invoice_day, |
|||
'invoice_origin': subscribe.sale_id.name, |
|||
'invoice_line_ids': [(0, 0, { |
|||
'product_id': product_id.id, |
|||
'name': vehicle_id.name, |
|||
'price_unit': per_day * durations, |
|||
})] |
|||
}) |
|||
generate_invoice.is_subscription = True |
|||
generate_invoice.action_post() |
|||
subscribe.sale_id.invoice_ids = [(4, generate_invoice.id)] |
|||
subscribe.invoice_ids = [(4, generate_invoice.id)] |
|||
else: |
|||
next_invoice_day = end_date |
|||
durations = (next_invoice_day - start_date).days |
|||
generate_invoice = request.env[ |
|||
'account.move'].sudo().create({ |
|||
'move_type': 'out_invoice', |
|||
'partner_id': customer_id.id, |
|||
'invoice_date': next_invoice_day, |
|||
'invoice_line_ids': [(0, 0, { |
|||
'product_id': product_id.id, |
|||
'name': vehicle_id.name, |
|||
'price_unit': per_day * durations, |
|||
})] |
|||
}) |
|||
generate_invoice.is_subscription = True |
|||
generate_invoice.action_post() |
|||
subscribe.sale_id.invoice_ids = [(4, generate_invoice.id)] |
|||
subscribe.invoice_ids = [(4, generate_invoice.id)] |
|||
break |
|||
start_date = start_date + timedelta(days=30) |
|||
values = { |
|||
'subscription_id': subscribe.id |
|||
} |
|||
return values |
|||
|
|||
@http.route(['/next/vehicle', '/next/vehicle/<int:subscription_id>'], |
|||
auth='public', website=True, type='http') |
|||
def subscription_create(self): |
|||
"""Return template for successful subscription""" |
|||
current_vehicle = request.env['fleet.subscription'].sudo().search([ |
|||
('customer_id', '=', request.env.user.partner_id.id), |
|||
('state', '=', 'subscribed'), |
|||
], order='write_date desc', limit=1) |
|||
context = { |
|||
'vehicle_name': current_vehicle.vehicle_id.name, |
|||
'customer_name': request.env.user.partner_id.name, |
|||
} |
|||
return request.render('vehicle_subscription.subscription_form_success', |
|||
context) |
|||
|
|||
@http.route(['/online/subscription/with/fuel'], type='json', auth="public", |
|||
website=True) |
|||
def get_with_fuel(self, **kwargs): |
|||
"""Calculate price for vehicle according to fuel type """ |
|||
vehicle = int(kwargs.get('vehicle')) |
|||
km = kwargs.get('extra_km') |
|||
vehicle = request.env['fleet.vehicle'].sudo().browse(vehicle) |
|||
vehicle.write({ |
|||
'extra_km': km, |
|||
}) |
|||
insurance_amount = vehicle.insurance |
|||
amount = request.env['vehicle.insurance'].sudo() \ |
|||
.browse(int(insurance_amount)).insurance_amount |
|||
if float(km) > vehicle.free_km: |
|||
new_price = (((vehicle.extra_km / vehicle.mileage) * |
|||
vehicle.fuel_rate) + |
|||
(vehicle.duration * vehicle.subscription_price) + |
|||
amount) |
|||
else: |
|||
if float(km) <= vehicle.free_km: |
|||
new_price = ((vehicle.duration * vehicle.subscription_price) + |
|||
amount) |
|||
return str(new_price) |
|||
|
|||
@http.route(['/online/subscription/without/fuel'], type='json', |
|||
auth="public", |
|||
website=True) |
|||
def get_without_fuel(self, **kwargs): |
|||
"""Calculate price for vehicle according to fuel type """ |
|||
vehicle = int(kwargs.get('vehicle')) |
|||
km = kwargs.get('extra_km') |
|||
vehicle = request.env['fleet.vehicle'].sudo().browse(vehicle) |
|||
insurance_amount = vehicle.insurance |
|||
amount = request.env['vehicle.insurance'].sudo() \ |
|||
.browse(int(insurance_amount)).insurance_amount |
|||
vehicle.write({ |
|||
'extra_km': km, |
|||
}) |
|||
if float(km) > vehicle.free_km: |
|||
new_price = (((vehicle.duration * vehicle.subscription_price) + |
|||
amount) + (vehicle.charge_km * vehicle.extra_km)) |
|||
else: |
|||
new_price = ( |
|||
vehicle.duration * vehicle.subscription_price) + amount |
|||
return str(new_price) |
|||
|
|||
@http.route('/online/subscription/cancel', auth='public', website=True) |
|||
def cancellation_form(self): |
|||
"""Cancel subscription form through website""" |
|||
customer_id = request.env['res.partner'].sudo().search( |
|||
[('name', '=', request.env.user.partner_id.name)]) |
|||
vehicle_id = request.env['fleet.subscription'].sudo().search( |
|||
[('customer_id', '=', customer_id.id), |
|||
('state', '=', 'subscribed')]) |
|||
vals = { |
|||
'customers': customer_id.name, |
|||
'vehicles': vehicle_id, |
|||
} |
|||
return http.request.render( |
|||
'vehicle_subscription.subscription_cancellation_form', vals) |
|||
|
|||
@http.route('/online/choose/vehicle', type='json', auth="public", |
|||
website=True) |
|||
def choose_vehicle(self, **kwargs): |
|||
"""Only display vehicle of selected customer in website""" |
|||
customer = kwargs.get('customer_id') |
|||
customer_id = request.env['res.partner'].sudo().search( |
|||
[('name', '=', customer)]) |
|||
vehicle_id = request.env['fleet.subscription'].sudo().search( |
|||
[('state', '=', 'subscribed'), |
|||
('customer_id', '=', customer_id.id)]).mapped('vehicle_id') |
|||
if vehicle_id: |
|||
vehicle = [(rec.id, rec.name) for rec in vehicle_id] |
|||
return [*set(vehicle)] |
|||
|
|||
@http.route('/online/cancellation/click', auth='public', type='http', |
|||
website=True) |
|||
def cancellation_click_form(self, **kwargs): |
|||
"""Proceed with cancellation button click""" |
|||
customer = kwargs.get('customer') |
|||
vehicle = int(kwargs.get('vehicle')) |
|||
reason = kwargs.get('reason') |
|||
customer_id = request.env['res.partner'].sudo().search( |
|||
[('name', '=', customer)]) |
|||
subscription = request.env['fleet.subscription'].sudo().browse(vehicle) |
|||
vehicle_id = subscription.vehicle_id |
|||
existing_request = request.env['cancellation.request'].sudo().search([ |
|||
('customer_id', '=', customer_id.id), |
|||
('vehicle_id', '=', vehicle_id.id), |
|||
('state', '!=', 'cancel') |
|||
], limit=1) |
|||
if existing_request: |
|||
values = { |
|||
'customer': customer, |
|||
'vehicle': vehicle_id.name, |
|||
'existing_request': existing_request, |
|||
} |
|||
return request.render( |
|||
'vehicle_subscription.existing_cancellation_popup', values) |
|||
cancel_request = request.env['cancellation.request'].sudo().create({ |
|||
'customer_id': customer_id.id, |
|||
'vehicle_id': vehicle_id.id, |
|||
'reason': reason, |
|||
}) |
|||
values = { |
|||
'customer': customer, |
|||
'vehicle': vehicle_id.name, |
|||
} |
|||
cancel_request.state = 'to_approve' |
|||
return request.render('vehicle_subscription.booking_cancellation', |
|||
values) |
|||
|
|||
@http.route('/online/subscription/change', auth='public', website=True) |
|||
def subscription_change_form(self): |
|||
"""Rendered response for the 'vehicle_subscription. |
|||
subscription_change_form' template, |
|||
containing the available vehicles and the current customer's name.""" |
|||
customer = request.env.user.partner_id.name |
|||
customer_id = request.env['res.partner'].sudo().search( |
|||
[('name', '=', customer)]) |
|||
invoice = request.env['account.move'].sudo().search([ |
|||
('invoice_line_ids.product_id', 'like', 'Vehicle Subscription'), |
|||
('partner_id', '=', customer_id.id) |
|||
]) |
|||
vehicle_names = [line.name for line in invoice.invoice_line_ids if |
|||
line.product_id.name == 'Vehicle Subscription'] |
|||
vehicles = request.env['fleet.vehicle'].sudo().search( |
|||
[('name', 'in', vehicle_names)]) |
|||
filtered_vehicles = vehicles |
|||
for vehicle in vehicles: |
|||
existing_request = request.env['subscription.request'].sudo().search([ |
|||
('customer_id', '=', customer_id.id), |
|||
('current_vehicle_id', '=', vehicle.id), |
|||
('state', '!=', 'cancel'),('is_subscription' ,'!=',True) |
|||
]) |
|||
already_request = request.env['subscription.request'].sudo().search([ |
|||
('customer_id', '=', customer_id.id), |
|||
('current_vehicle_id', '=', vehicle.id), |
|||
('state', '!=', 'cancel'),('is_subscription' ,'=',True) |
|||
]) |
|||
already_vehicle = already_request.current_vehicle_id |
|||
if existing_request: |
|||
values = { |
|||
'customer': customer, |
|||
'current_vehicle': existing_request.current_vehicle_id.name, |
|||
'existing_request': existing_request, |
|||
} |
|||
return request.render( |
|||
'vehicle_subscription.existing_subscription_popup', values) |
|||
else: |
|||
vals = { |
|||
'vehicles': filtered_vehicles.filtered(lambda v: v.id != already_vehicle.id), |
|||
'customers': customer_id.name, |
|||
} |
|||
return http.request.render( |
|||
'vehicle_subscription.subscription_change_form', vals) |
|||
|
|||
@http.route('/online/subscription/change/vehicle', auth='public', |
|||
type='http', website=True) |
|||
def change_click_form(self, **kwargs): |
|||
""" Rendered response based on the conditions: |
|||
- If the 'customer' parameter exists, render the |
|||
'vehicle_subscription.subscription_change_button' template |
|||
with the provided data. |
|||
- If the 'customer' parameter does not exist, render the |
|||
'vehicle_subscription.subscription_change_boolean_false' |
|||
template.""" |
|||
if kwargs.get('customer'): |
|||
customer = kwargs.get('customer') |
|||
vehicle = int(kwargs.get('vehicle')) |
|||
reason = kwargs.get('reason') |
|||
checkbox = kwargs.get('checkbox_model') |
|||
customer_id = request.env['res.partner'].sudo(). \ |
|||
search([('name', '=', customer)]) |
|||
vehicle_id = request.env['fleet.vehicle'].sudo().browse(vehicle) |
|||
new_vehicle_id = request.env['fleet.vehicle'].sudo() \ |
|||
.search([('model_id', '!=', vehicle_id.model_id.id)]) |
|||
if checkbox == 'on': |
|||
values = { |
|||
'customer_name': customer_id.name, |
|||
'vehicle_name': vehicle_id.name, |
|||
'vehicles': [rec for rec in new_vehicle_id], |
|||
'reason': reason, |
|||
} |
|||
return request.render( |
|||
'vehicle_subscription.subscription_change_button', values) |
|||
else: |
|||
return request.render( |
|||
'vehicle_subscription.subscription_change_boolean_false') |
|||
else: |
|||
return request.render( |
|||
'vehicle_subscription.subscription_change_button') |
|||
|
|||
@http.route('/online/subscription/change/button', auth='public', |
|||
type='http', website=True) |
|||
def click_form(self, **kwargs): |
|||
"""Rendered response for the |
|||
'vehicle_subscription.change_subscription' template. """ |
|||
customer = kwargs.get('customer') |
|||
reason = kwargs.get('reason') |
|||
current_vehicle = kwargs.get('vehicle') |
|||
vehicle_id = int(kwargs.get('new_vehicle')) |
|||
current_vehicle_id = request.env['fleet.vehicle'].sudo() \ |
|||
.search([('name', '=', current_vehicle)]) |
|||
customer_id = request.env['res.partner'].sudo() \ |
|||
.search([('name', '=', customer)]) |
|||
vehicle = request.env['fleet.vehicle'].sudo().browse(vehicle_id) |
|||
change_subscription = request.env['subscription.request'] \ |
|||
.sudo().create({ |
|||
'current_vehicle_id': current_vehicle_id.id, |
|||
'new_vehicle_id': vehicle.id, |
|||
'reason_to_change': reason, |
|||
'customer_id': customer_id.id, |
|||
'is_subscription': True, |
|||
}) |
|||
change_subscription.is_subscription = True |
|||
change_subscription.state = 'to_approve' |
|||
return request.render('vehicle_subscription.change_subscription') |
|||
|
|||
@http.route('/online/proceed/cancellation', auth='public', type='http', |
|||
website=True) |
|||
def proceed_cancellation(self): |
|||
"""Proceed with cancellation in change subscription """ |
|||
return request.redirect('/online/subscription/cancel') |
|||
|
|||
@http.route(['/web/signup/user'], type='http', auth="user", |
|||
website=True) |
|||
def redirect_login(self): |
|||
"""Used to redirect on clicking signup page""" |
|||
return request.redirect('/online/subscription') |
@ -0,0 +1,63 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################# |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# Author: Jumana Jabin MP (<https://www.cybrosys.com>) |
|||
# |
|||
# You can modify it under the terms of the GNU AFFERO |
|||
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
|||
# (AGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################# |
|||
from odoo import http |
|||
from odoo.http import request |
|||
from odoo.addons.portal.controllers.portal import CustomerPortal, \ |
|||
pager as portal_pager |
|||
|
|||
|
|||
class PortalAccount(CustomerPortal): |
|||
"""PortalAccount for subscription""" |
|||
|
|||
@http.route(['/my/subscription/invoice'], type='http', auth="user", |
|||
website=True) |
|||
def portal_my_subscription_order(self, page=1, date_begin=None, |
|||
date_end=None, sortby=None, filterby=None): |
|||
"""Rendered response for the ' |
|||
vehicle_subscription.portal_my_invoices_subscription' template, |
|||
containing the subscription invoices.""" |
|||
partner = request.env.user.partner_id |
|||
values = self._prepare_my_invoices_values(page, date_begin, date_end, |
|||
sortby, filterby) |
|||
pager = portal_pager(**values['pager']) |
|||
domain = [ |
|||
('invoice_line_ids.product_id', 'like', 'Vehicle Subscription'), |
|||
('partner_id', '=', partner.id) |
|||
] |
|||
values.update({ |
|||
'invoices': request.env['account.move'].sudo().search(domain), |
|||
'pager': pager, |
|||
}) |
|||
return request.render( |
|||
"vehicle_subscription.portal_my_invoices_subscription", values) |
|||
|
|||
def _prepare_home_portal_values(self, counters): |
|||
"""Prepare the values for the home portal page.""" |
|||
values = super()._prepare_home_portal_values(counters) |
|||
partner = request.env.user.partner_id |
|||
if 'subscription_count' in counters: |
|||
values['subscription_count'] = request.env['account.move'].sudo() \ |
|||
.search_count( |
|||
[( |
|||
'invoice_line_ids.product_id', 'like', 'Vehicle Subscription'), |
|||
('partner_id', '=', partner.id)]) |
|||
return values |
@ -0,0 +1,16 @@ |
|||
<?xml version="1.0" encoding="utf-8" ?> |
|||
<odoo> |
|||
<data noupdate="1"> |
|||
<!--Cron action for checking expiration--> |
|||
<record id="ir_cron_expiry_date_action" model="ir.cron"> |
|||
<field name="name">Subscription Expired</field> |
|||
<field name="model_id" ref="model_fleet_subscription"/> |
|||
<field name="state">code</field> |
|||
<field name="code">model._onchange_end_date()</field> |
|||
<field name="interval_number">1</field> |
|||
<field name="interval_type">days</field> |
|||
<field name="numbercall">-1</field> |
|||
<field name="active" eval="True"/> |
|||
</record> |
|||
</data> |
|||
</odoo> |
@ -0,0 +1,79 @@ |
|||
<?xml version="1.0" encoding="utf-8" ?> |
|||
<odoo> |
|||
<data noupdate="1"> |
|||
<!--Cron action for checking expiration--> |
|||
<record id="ir_cron_expiry_date_action" model="ir.cron"> |
|||
<field name="name">Subscription Expired</field> |
|||
<field name="model_id" ref="model_fleet_subscription"/> |
|||
<field name="state">code</field> |
|||
<field name="code">model._onchange_end_date()</field> |
|||
<field name="interval_number">1</field> |
|||
<field name="interval_type">days</field> |
|||
<field name="numbercall">-1</field> |
|||
<field name="active" eval="True"/> |
|||
</record> |
|||
<!--Subscription cancellation mail template--> |
|||
<record id="cancellation_request_mail" model="mail.template"> |
|||
<field name="name">Vehicle Cancellation</field> |
|||
<field name="model_id" |
|||
ref="vehicle_subscription.model_cancellation_request"/> |
|||
<field name="subject">Subscription Cancellation</field> |
|||
<field name="body_html" type="html"> |
|||
<div style="margin: 0px; padding: 0px;"> |
|||
<p> |
|||
Dear |
|||
<br/> |
|||
Sorry....you need to pay amount till date inorder to |
|||
cancel |
|||
subscription.Your invoice is attached |
|||
below |
|||
<br/> |
|||
</p> |
|||
Regards, |
|||
<br/> |
|||
<t t-out="user.name"/> |
|||
</div> |
|||
</field> |
|||
</record> |
|||
<!-- Mail template for refund in subscription cancellation --> |
|||
<record id="cancellation_request_refund_mail" model="mail.template"> |
|||
<field name="name">Vehicle Cancellation</field> |
|||
<field name="model_id" |
|||
ref="vehicle_subscription.model_cancellation_request"/> |
|||
<field name="subject">Subscription Cancellation</field> |
|||
<field name="body_html" type="html"> |
|||
<div style="margin: 0px; padding: 0px;"> |
|||
<p> |
|||
Dear |
|||
<br/> |
|||
The amount should be refunded immediately. |
|||
<br/> |
|||
</p> |
|||
Regards, |
|||
<br/> |
|||
<t t-out="user.name"/> |
|||
</div> |
|||
</field> |
|||
</record> |
|||
<!-- Mail template for approval of subscription cancellation --> |
|||
<record id="cancellation_approved" model="mail.template"> |
|||
<field name="name">Vehicle Cancellation</field> |
|||
<field name="model_id" |
|||
ref="vehicle_subscription.model_cancellation_request"/> |
|||
<field name="subject">Subscription Cancellation</field> |
|||
<field name="body_html" type="html"> |
|||
<div style="margin: 0px; padding: 0px;"> |
|||
<p> |
|||
Dear |
|||
<br/> |
|||
Your request for subscription cancellation is approved |
|||
<br/> |
|||
</p> |
|||
Regards, |
|||
<br/> |
|||
<t t-out="user.name"/> |
|||
</div> |
|||
</field> |
|||
</record> |
|||
</data> |
|||
</odoo> |
@ -0,0 +1,13 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<odoo> |
|||
<data noupdate="1"> |
|||
<!--Created vehicle subscription as a service product--> |
|||
<record id="product_template_vehicle_subscription_form" |
|||
model="product.template"> |
|||
<field name="name">Vehicle Subscription</field> |
|||
<field name="detailed_type">service</field> |
|||
<field name="invoice_policy">order</field> |
|||
<field name="sequence" type="int">55</field> |
|||
</record> |
|||
</data> |
|||
</odoo> |
@ -0,0 +1,12 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<odoo> |
|||
<!--Subscription form in website --> |
|||
<data noupdate="1"> |
|||
<record id="menu_subscription_form" model="website.menu"> |
|||
<field name="name">Subscription Form</field> |
|||
<field name="url">/online/subscription</field> |
|||
<field name="parent_id" ref="website.main_menu"/> |
|||
<field name="sequence" type="int">55</field> |
|||
</record> |
|||
</data> |
|||
</odoo> |
@ -0,0 +1,6 @@ |
|||
## Module <vehicle_subscription> |
|||
|
|||
#### 04.01.2025 |
|||
#### Version 17.0.1.0.0 |
|||
#### ADD |
|||
- Initial Commit Vehicle Subscription |
@ -0,0 +1,29 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################# |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# Author: Jumana Jabin MP (<https://www.cybrosys.com>) |
|||
# |
|||
# You can modify it under the terms of the GNU AFFERO |
|||
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
|||
# (AGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################# |
|||
from . import account_move |
|||
from . import cancellation_request |
|||
from . import fleet_subscription |
|||
from . import fleet_vehicle |
|||
from . import fleet_vehicle_model |
|||
from . import insurance_type |
|||
from . import subscription_request |
|||
from . import vehicle_insurance |
@ -0,0 +1,32 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################# |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# Author:Jumana Jabin MP (<https://www.cybrosys.com>) |
|||
# |
|||
# You can modify it under the terms of the GNU AFFERO |
|||
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
|||
# (AGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################# |
|||
from odoo import fields, models |
|||
|
|||
|
|||
class AccountMove(models.Model): |
|||
"""Inherited account move to add a field """ |
|||
_inherit = 'account.move' |
|||
|
|||
is_subscription = fields.Boolean(string="Subscribe", |
|||
help="This field will be set true for " |
|||
"invoice corresponding to vehicle " |
|||
"subscription") |
@ -0,0 +1,265 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################# |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# Author: Jumana jabin MP (<https://www.cybrosys.com>) |
|||
# |
|||
# You can modify it under the terms of the GNU AFFERO |
|||
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
|||
# (AGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################# |
|||
import base64 |
|||
from datetime import date |
|||
from odoo import fields, models |
|||
|
|||
|
|||
class CancellationRequest(models.Model): |
|||
"""Created new model to add new fields and function""" |
|||
_name = "cancellation.request" |
|||
_description = "Cancellation Request" |
|||
_inherit = "mail.thread" |
|||
_rec_name = 'vehicle_id' |
|||
|
|||
vehicle_id = fields.Many2one('fleet.vehicle', |
|||
string="Vehicle", help="Choose vehicle " |
|||
"inorder to cancel " |
|||
"subscription", |
|||
required=True) |
|||
date = fields.Date(string="Cancellation Date", default=fields.Date.today(), |
|||
help="Date for cancellation of vehicle") |
|||
customer_id = fields.Many2one('res.partner', string="Customer", |
|||
help="Choose Customer for the cancellation " |
|||
"of vehicle", required=True) |
|||
reason = fields.Char(string="Cancellation Reason", help="Describe the " |
|||
"reason for " |
|||
"cancellation") |
|||
state = fields.Selection(selection=[('draft', 'Draft'), |
|||
('to_approve', 'To Approve'), |
|||
('approved', 'Approved')], |
|||
string='State', default='draft', |
|||
help="States of cancellation subscription") |
|||
|
|||
def action_request(self): |
|||
"""Change state to to_approve""" |
|||
self.write({'state': 'to_approve'}) |
|||
|
|||
def action_approve(self): |
|||
"""Handle cancellation approval by manager. |
|||
This method handles the approval process for a cancellation |
|||
request in a vehicle subscription system. |
|||
It updates the cancellation state, generates invoices or refunds |
|||
based on the payment status, and sends notifications to the customer.""" |
|||
self.write({'state': 'approved'}) |
|||
subscription = self.env['fleet.subscription'].search( |
|||
[('vehicle_id', '=', self.vehicle_id.id), |
|||
('customer_id', '=', self.customer_id.id)]) |
|||
invoice = subscription.sale_id.invoice_ids |
|||
multy_invoice = subscription.invoice_ids |
|||
product_template_id = self.env.ref( |
|||
'vehicle_subscription.product_template_vehicle_subscription_form').id |
|||
product_id = self.env['product.product'].search( |
|||
[('product_tmpl_id', '=', product_template_id)], limit=1) |
|||
if subscription.duration != 0: |
|||
per_day_price = subscription.sale_id.amount_untaxed / subscription.duration |
|||
else: |
|||
per_day_price = 0 |
|||
if isinstance(self.date, date) and isinstance(subscription.start_date, |
|||
date): |
|||
invoice_duration = (self.date - subscription.start_date).days |
|||
else: |
|||
invoice_duration = 0 |
|||
email_template = self.env.ref( |
|||
'vehicle_subscription.cancellation_request_mail') |
|||
template_approved = self.env.ref( |
|||
'vehicle_subscription.cancellation_approved') |
|||
refund_approved = self.env.ref( |
|||
'vehicle_subscription.cancellation_request_refund_mail') |
|||
uptodate_price = round(per_day_price * invoice_duration, 2) |
|||
paid_amount = self.env['account.move'].search( |
|||
[('id', 'in', subscription.invoice_ids.ids), |
|||
('payment_state', 'in', ['paid', 'partial'])]).mapped( |
|||
'amount_untaxed_signed') |
|||
if paid_amount: |
|||
if sum(paid_amount) == uptodate_price: |
|||
subscription.state = 'cancel' |
|||
email_values = { |
|||
'email_to': self.customer_id.email, |
|||
'email_from': self.env.user.email, |
|||
} |
|||
template_approved.send_mail(self.id, email_values=email_values, |
|||
force_send=True) |
|||
elif sum(paid_amount) < uptodate_price: |
|||
if len(invoice) == 1 or len(multy_invoice) == 1: |
|||
invoice.button_cancel() |
|||
generate_invoice = self.env['account.move'].sudo().create({ |
|||
'move_type': 'out_invoice', |
|||
'partner_id': self.customer_id.id, |
|||
'invoice_date': self.date, |
|||
'invoice_line_ids': fields.Command.create([{ |
|||
'product_id': product_id.id, |
|||
'name': self.vehicle_id.name, |
|||
'price_unit': (per_day_price * invoice_duration) - sum( |
|||
paid_amount), |
|||
}]) |
|||
}) |
|||
generate_invoice.action_post() |
|||
data_record = base64.b64encode( |
|||
self.env['ir.actions.report'].sudo()._render_qweb_pdf( |
|||
"account.account_invoices", generate_invoice.ids)[ |
|||
0]) |
|||
ir_values = { |
|||
'name': 'Invoice', |
|||
'type': 'binary', |
|||
'datas': data_record, |
|||
'store_fname': 'invoice.pdf', |
|||
'mimetype': 'application/pdf', |
|||
'res_model': 'account.move', |
|||
'res_id': generate_invoice.id, |
|||
} |
|||
invoice_report_attachment_id = self.env[ |
|||
'ir.attachment'].sudo().create(ir_values) |
|||
email_values = { |
|||
'email_to': self.customer_id.email, |
|||
'email_from': self.env.user.email, |
|||
'attachment_ids': [invoice_report_attachment_id.id] |
|||
} |
|||
email_template.send_mail(self.id, |
|||
email_values=email_values, |
|||
force_send=True) |
|||
email_template.attachment_ids = [] |
|||
subscription.invoice_ids = fields.Command.link( |
|||
generate_invoice.id) |
|||
subscription.sale_id.write({ |
|||
'invoice_ids': fields.Command.link(generate_invoice.id) |
|||
}) |
|||
else: |
|||
for invoice_id in multy_invoice: |
|||
invoice = self.env['account.move'].browse( |
|||
invoice_id.id) |
|||
invoice.button_cancel() |
|||
generate_invoice = self.env['account.move'].sudo().create({ |
|||
'move_type': 'out_invoice', |
|||
'partner_id': self.customer_id.id, |
|||
'invoice_date': self.date, |
|||
'invoice_line_ids': fields.Command.create([{ |
|||
'product_id': product_id.id, |
|||
'name': self.vehicle_id.name, |
|||
'price_unit': (per_day_price * invoice_duration) - sum( |
|||
paid_amount), |
|||
}]) |
|||
}) |
|||
generate_invoice.action_post() |
|||
data_record = base64.b64encode( |
|||
self.env['ir.actions.report'].sudo()._render_qweb_pdf( |
|||
"account.account_invoices", generate_invoice.ids)[ |
|||
0]) |
|||
ir_values = { |
|||
'name': 'Invoice', |
|||
'type': 'binary', |
|||
'datas': data_record, |
|||
'store_fname': 'invoice.pdf', |
|||
'mimetype': 'application/pdf', |
|||
'res_model': 'account.move', |
|||
'res_id': generate_invoice.id, |
|||
} |
|||
invoice_report_attachment_id = self.env[ |
|||
'ir.attachment'].sudo().create(ir_values) |
|||
email_values = { |
|||
'email_to': self.customer_id.email, |
|||
'email_from': self.env.user.email, |
|||
'attachment_ids': [invoice_report_attachment_id.id] |
|||
} |
|||
email_template.send_mail(self.id, |
|||
email_values=email_values, |
|||
force_send=True) |
|||
email_template.attachment_ids = [] |
|||
subscription.invoice_ids = fields.Command.link( |
|||
generate_invoice.id) |
|||
subscription.sale_id.write({ |
|||
'invoice_ids': fields.Command.link(generate_invoice.id) |
|||
}) |
|||
else: |
|||
generate_refund = self.env['account.move'].sudo().create({ |
|||
'move_type': 'out_refund', |
|||
'invoice_date': fields.Date.today(), |
|||
'partner_id': self.customer_id.id, |
|||
'invoice_line_ids': fields.Command.create([{ |
|||
'product_id': product_id.id, |
|||
'name': self.vehicle_id.name, |
|||
'price_unit': (sum(paid_amount) - uptodate_price) |
|||
}]) |
|||
}) |
|||
generate_refund.action_post() |
|||
subscription.refund_id = generate_refund |
|||
data_record = base64.b64encode( |
|||
self.env['ir.actions.report'].sudo()._render_qweb_pdf( |
|||
"account.account_invoices", generate_refund.ids)[0]) |
|||
ir_values = { |
|||
'name': 'Invoice', |
|||
'type': 'binary', |
|||
'datas': data_record, |
|||
'store_fname': 'invoice.pdf', |
|||
'mimetype': 'application/pdf', |
|||
'res_model': 'account.move', |
|||
'res_id': generate_refund.id, |
|||
} |
|||
invoice_report_attachment_id = self.env[ |
|||
'ir.attachment'].sudo().create(ir_values) |
|||
email_values = { |
|||
'email_to': self.customer_id.email, |
|||
'email_from': self.env.user.email, |
|||
'attachment_ids': [invoice_report_attachment_id.id] |
|||
} |
|||
refund_approved.send_mail(self.id, email_values=email_values, |
|||
force_send=True) |
|||
refund_approved.attachment_ids = fields.Command.clear() |
|||
else: |
|||
invoice.button_cancel() |
|||
generate_invoice = self.env['account.move'].sudo().create({ |
|||
'move_type': 'out_invoice', |
|||
'partner_id': self.customer_id.id, |
|||
'invoice_date': self.date, |
|||
'invoice_line_ids': [fields.Command.create({ |
|||
'product_id': product_id.id, |
|||
'name': self.vehicle_id.name, |
|||
'price_unit': per_day_price * invoice_duration, |
|||
})] |
|||
}) |
|||
generate_invoice.action_post() |
|||
data_record = base64.b64encode( |
|||
self.env['ir.actions.report'].sudo()._render_qweb_pdf( |
|||
"account.account_invoices", generate_invoice.ids)[0]) |
|||
ir_values = { |
|||
'name': 'Invoice', |
|||
'type': 'binary', |
|||
'datas': data_record, |
|||
'store_fname': 'invoice.pdf', |
|||
'mimetype': 'application/pdf', |
|||
'res_model': 'account.move', |
|||
'res_id': generate_invoice.id, |
|||
} |
|||
invoice_report_attachment_id = self.env[ |
|||
'ir.attachment'].sudo().create(ir_values) |
|||
email_values = { |
|||
'email_to': self.customer_id.email, |
|||
'email_from': self.env.user.email, |
|||
'attachment_ids': [invoice_report_attachment_id.id] |
|||
} |
|||
email_template.send_mail(self.id, email_values=email_values, |
|||
force_send=True) |
|||
email_template.attachment_ids = [] |
|||
subscription.invoice_ids = fields.Command.link(generate_invoice.id) |
|||
subscription.sale_id.write({ |
|||
'invoice_ids': fields.Command.link(generate_invoice.id) |
|||
}) |
@ -0,0 +1,385 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################# |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# Author: Jumana Jabin MP (<https://www.cybrosys.com>) |
|||
# |
|||
# You can modify it under the terms of the GNU AFFERO |
|||
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
|||
# (AGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################# |
|||
from datetime import datetime |
|||
from odoo import api, fields, models, _ |
|||
from odoo.exceptions import ValidationError |
|||
|
|||
|
|||
class VehicleSubscription(models.Model): |
|||
"""Created new model to add new fields and function""" |
|||
_name = "fleet.subscription" |
|||
_description = "Fleet Subscription" |
|||
_inherit = "mail.thread" |
|||
_rec_name = 'vehicle_id' |
|||
|
|||
vehicle_id = fields.Many2one('fleet.vehicle', string="Vehicle", |
|||
domain="[('id', 'in',vehicle_ids)]", |
|||
help="This field help you to choose vehicle", |
|||
required=True) |
|||
vehicle_ids = fields.Many2many('fleet.vehicle', string="Vehicle", |
|||
compute='_compute_vehicle_ids', |
|||
help="Returns vehicle by satisfying " |
|||
"the domain") |
|||
model_id = fields.Many2one(related="vehicle_id.model_id", string='Model', |
|||
help="This field help you to choose model " |
|||
"of vehicle") |
|||
price = fields.Float(compute="_compute_price", string='Price', |
|||
help="Compute field which results the price of vehicle") |
|||
uptodate_price = fields.Float(compute="_compute_uptodate_price", |
|||
string='Price', |
|||
help="Compute field which results the price" |
|||
"of vehicle until the date ") |
|||
extra_price = fields.Float(string="Extra Price", |
|||
compute="_compute_extra_price", |
|||
help="Compute field which results the extra " |
|||
"price of vehicle") |
|||
start_date = fields.Date(string="Start Date", required=True, |
|||
help="Start date of subscription") |
|||
end_date = fields.Date(string="End Date", required=True, |
|||
help="End date of subscription") |
|||
cancellation_date = fields.Date(string="Cancellation Date", |
|||
default=fields.Date.today(), |
|||
help="Subscription cancellation date") |
|||
duration = fields.Integer(string="Duration", compute='_compute_duration', |
|||
help="Compute subscription duration") |
|||
cancel_duration = fields.Integer(string="Duration", |
|||
compute='_compute_cancel_duration', |
|||
help="compute cancel duration") |
|||
state = fields.Selection( |
|||
selection=[('draft', 'Draft'), ('subscribed', 'Subscribed'), |
|||
('cancel', 'Cancelled'), ('expired', 'Expired') |
|||
], string='State', default='draft', |
|||
help="States of subscription") |
|||
street = fields.Char(string="Street", help="Choose the street") |
|||
state_id = fields.Many2one("res.country.state", string='State', |
|||
ondelete='restrict', |
|||
domain="[('country_id', '=?', country_id)]", |
|||
help="Choose the state") |
|||
city = fields.Char(string="City", help="Choose the city") |
|||
country_id = fields.Many2one('res.country', string='Country', |
|||
ondelete='restrict', |
|||
help="Choose the country") |
|||
fuel = fields.Selection(selection=[('with_fuel', 'With Fuel'), |
|||
('without_fuel', 'Without Fuel')], |
|||
string="Fuel Choice", default='without_fuel', |
|||
help="Help you to choose the type of fuel") |
|||
fuel_type = fields.Selection(string="Fuel Type", |
|||
related= |
|||
"vehicle_id.model_id.default_fuel_type", |
|||
help="Fuel type will be given which is related" |
|||
" to the model") |
|||
fuel_rate = fields.Integer(String="Rate", default=300, help="Rate of fuel") |
|||
charge_km = fields.Integer(string="Charge in km", default=12, |
|||
help="Rate per kilometer") |
|||
default_km = fields.Float(string="Default KMS", |
|||
related='vehicle_id.free_km', |
|||
help="Default km is set based on free km of " |
|||
"vehicle which is given by authorised " |
|||
"person") |
|||
extra_km = fields.Float(string="Extra KMS", default_km=1, |
|||
help="As per customer he/she can choose extra km") |
|||
mileage = fields.Float(string='Mileage', |
|||
related='vehicle_id.model_id.mileage', |
|||
help="Helps to set mileage of vehicle") |
|||
sale = fields.Integer(string="sale", compute='_compute_sale', |
|||
help="Helps you to store count of sale") |
|||
invoice = fields.Integer(string="Invoice", compute='_compute_invoice', |
|||
help="Helps you to store count of invoice") |
|||
invoice_ids = fields.Many2many('account.move', string='Invoices', |
|||
help="Used to store ids of invoices") |
|||
customer_id = fields.Many2one('res.partner', string="Customer", |
|||
help="Helps you to choose customer") |
|||
sale_id = fields.Many2one('sale.order', string='sale', readonly=True, |
|||
help="Stores id of sale order") |
|||
refund_id = fields.Many2one('account.move', string='Refund', readonly=True, |
|||
help="Stores id of invoice which belongs " |
|||
"to refund") |
|||
insurance_type_id = fields.Many2one('vehicle.insurance', |
|||
domain= |
|||
"[('vehicle_id', '=',vehicle_id)]") |
|||
refund = fields.Integer(compute='_compute_refund', |
|||
help="Helps you to store count of refund") |
|||
seating_capacity = fields.Integer(string='Seating Capacity', |
|||
help="Seating capacity of vehicle can " |
|||
"be set") |
|||
invisible_sub = fields.Boolean(string="Approve Subscription", |
|||
help="As subscription request get approved " |
|||
"this field will be enabled") |
|||
|
|||
def _get_vehicle_domain(self): |
|||
"""This method retrieves the vehicles that meet the following |
|||
criteria""" |
|||
insurance_ids = self.env['vehicle.insurance'].search([]).mapped( |
|||
'vehicle_id') |
|||
domain = [] |
|||
for record in insurance_ids: |
|||
state = record.log_services.mapped('state') |
|||
if 'done' in state and 'running' not in state and 'new' \ |
|||
not in state and 'cancelled' not in state: |
|||
if not self.search( |
|||
[('vehicle_id', '=', record.id), |
|||
('state', '!=', 'subscribe')]): |
|||
domain.append(record.id) |
|||
return domain |
|||
|
|||
@api.onchange('vehicle_id') |
|||
def _onchange_vehicle_id(self): |
|||
"""Function used to fill the seating capacity""" |
|||
if self.vehicle_id: |
|||
self.seating_capacity = self.vehicle_id.model_id.seats |
|||
|
|||
@api.onchange('seating_capacity') |
|||
def _onchange_seating_capacity(self): |
|||
"""As the seating capacity changes vehicles are shown """ |
|||
if self.seating_capacity != self.vehicle_id.model_id.seats: |
|||
self.vehicle_id = False |
|||
|
|||
@api.onchange('default_km') |
|||
def _onchange_default_km(self): |
|||
"""Charge per km is set as onchange of default_km""" |
|||
if self.default_km <= self.vehicle_id.free_km: |
|||
self.charge_km = 0 |
|||
|
|||
@api.depends('vehicle_id', 'seating_capacity') |
|||
def _compute_vehicle_ids(self): |
|||
"""Compute the vehicle_IDS based on the vehicle and seating |
|||
capacity.""" |
|||
for rec in self: |
|||
if not rec.vehicle_ids: |
|||
domain = rec._get_vehicle_domain() |
|||
if rec.seating_capacity: |
|||
model_id = self.env['fleet.vehicle'].search( |
|||
[('state_id', '=', 'registered'), |
|||
('model_id.seats', '=', rec.seating_capacity), |
|||
('id', 'in', domain)]) |
|||
else: |
|||
model_id = self.env['fleet.vehicle'].search( |
|||
[('id', 'in', domain)]) |
|||
for record in model_id: |
|||
rec.vehicle_ids = [(4, record.id)] |
|||
|
|||
@api.depends('start_date', 'end_date') |
|||
def _compute_duration(self): |
|||
"""Compute duration based on start and end date""" |
|||
for record in self: |
|||
if record.end_date: |
|||
if record.end_date < record.start_date: |
|||
raise ValidationError(_( |
|||
"End date should be greater than start date.")) |
|||
if record.start_date and record.end_date: |
|||
start = record.start_date.strftime("%Y-%m-%d") |
|||
end = record.end_date.strftime("%Y-%m-%d") |
|||
start_datetime = datetime.strptime(start, "%Y-%m-%d") |
|||
end_datetime = datetime.strptime(end, "%Y-%m-%d") |
|||
delta = end_datetime - start_datetime |
|||
record.duration = delta.days |
|||
else: |
|||
record.duration = 0 |
|||
|
|||
@api.depends('start_date', 'cancellation_date') |
|||
def _compute_cancel_duration(self): |
|||
"""Compute duration based on cancellation date""" |
|||
for record in self: |
|||
if record.start_date and record.cancellation_date: |
|||
start = record.start_date.strftime("%Y-%m-%d") |
|||
end = record.cancellation_date.strftime("%Y-%m-%d") |
|||
start_datetime = datetime.strptime(start, "%Y-%m-%d") |
|||
end_datetime = datetime.strptime(end, "%Y-%m-%d") |
|||
delta = end_datetime - start_datetime |
|||
record.cancel_duration = delta.days |
|||
else: |
|||
record.cancel_duration = 0 |
|||
|
|||
@api.depends('extra_km', 'charge_km', 'fuel_rate', 'fuel') |
|||
def _compute_extra_price(self): |
|||
"""Compute extra charges based on criteria""" |
|||
for rec in self: |
|||
if rec.fuel == 'without_fuel': |
|||
rec.extra_price = (rec.extra_km * rec.charge_km) |
|||
elif rec.mileage == 0: |
|||
raise ValidationError(_("Mileage cannot be zero.")) |
|||
else: |
|||
rec.extra_price = ( |
|||
(rec.extra_km / rec.mileage) * rec.fuel_rate) |
|||
|
|||
@api.depends('duration') |
|||
def _compute_price(self): |
|||
"""Function used to compute price of vehicle""" |
|||
for rec in self: |
|||
rec.price = (rec.duration * rec.vehicle_id.subscription_price) \ |
|||
+ rec.insurance_type_id.insurance_amount |
|||
|
|||
@api.depends('cancel_duration') |
|||
def _compute_uptodate_price(self): |
|||
"""Compute price as per the cancellation date""" |
|||
for rec in self: |
|||
rec.uptodate_price = ( |
|||
(rec.sale_id.order_line.price_unit / rec.duration) * ( |
|||
(rec.cancellation_date - rec.start_date).days)) |
|||
|
|||
def action_get_car_insurance(self): |
|||
"""Get the action to view the car |
|||
insurance associated with the subscription.""" |
|||
self.ensure_one() |
|||
return { |
|||
'type': 'ir.actions.act_window', |
|||
'name': 'Insurance', |
|||
'view_mode': 'form', |
|||
'res_model': 'vehicle.insurance', |
|||
'res_id': self.insurance_type_id.id, |
|||
'context': [('create', '=', False)] |
|||
} |
|||
def action_get_sale(self): |
|||
"""Get the action to view the sale |
|||
associated with the subscription.""" |
|||
return { |
|||
'type': 'ir.actions.act_window', |
|||
'name': 'Sale Order', |
|||
'view_mode': 'form', |
|||
'res_model': 'sale.order', |
|||
'res_id': self.sale_id.id, |
|||
'context': [('create', '=', False)] |
|||
} |
|||
|
|||
def _compute_sale(self): |
|||
"""Used to calculate the sale count""" |
|||
for record in self: |
|||
record.sale = self.env['sale.order'].search_count( |
|||
[('id', '=', self.sale_id.id)]) |
|||
|
|||
def action_get_refund(self): |
|||
"""Get the action to view the refund |
|||
associated with the subscription.""" |
|||
return { |
|||
'type': 'ir.actions.act_window', |
|||
'name': 'Refund', |
|||
'view_mode': 'form', |
|||
'res_id': self.refund_id.id, |
|||
'res_model': 'account.move', |
|||
'context': [('create', '=', False)] |
|||
} |
|||
|
|||
def _compute_refund(self): |
|||
"""Used to calculate count of refund""" |
|||
for record in self: |
|||
record.refund = self.env['account.move'].search_count( |
|||
[('id', '=', self.refund_id.id)]) |
|||
|
|||
def action_get_invoice(self): |
|||
"""Get the action to view the invoice |
|||
associated with the subscription.""" |
|||
invoice_ids = self.invoice_ids + self.sale_id.invoice_ids |
|||
return { |
|||
'type': 'ir.actions.act_window', |
|||
'name': 'Sale Order', |
|||
'view_mode': 'tree,form', |
|||
'res_model': 'account.move', |
|||
'domain': [('id', 'in', invoice_ids.ids)], |
|||
'context': [('create', '=', False)] |
|||
} |
|||
|
|||
def _compute_invoice(self): |
|||
"""Used to calculate invoice count""" |
|||
for record in self: |
|||
invoice_ids = record.invoice_ids + record.sale_id.invoice_ids |
|||
record.invoice = self.env['account.move'].search_count( |
|||
[('id', 'in', invoice_ids.ids)]) |
|||
|
|||
def action_invoice(self): |
|||
"""Used to generate invoice on clicking the button""" |
|||
self.write({'state': 'subscribed'}) |
|||
product_template_id = self.env.ref( |
|||
'vehicle_subscription.product_template_vehicle_subscription_form').id |
|||
product_id = self.env['product.product'].search( |
|||
[('product_tmpl_id', '=', product_template_id)]) |
|||
sale_order_id = self.env['sale.order'].create({ |
|||
'partner_id': self.customer_id.id, |
|||
'order_line': [(0, 0, { |
|||
'product_id': product_id.id, |
|||
'name': self.vehicle_id.name, |
|||
'price_unit': self.price + self.extra_price, |
|||
})] |
|||
}) |
|||
self.sale_id = sale_order_id |
|||
def action_request(self): |
|||
"""Request for change subscription is generated """ |
|||
return { |
|||
'type': 'ir.actions.act_window', |
|||
'view_mode': 'form', |
|||
'res_model': 'change.subscription', |
|||
'target': 'new', |
|||
} |
|||
|
|||
def action_cancel(self): |
|||
"""Proceed with cancellation of subscription""" |
|||
product_template_id = self.env.ref( |
|||
'vehicle_subscription.product_template_vehicle_subscription_form').id |
|||
product_id = self.env['product.product'].search( |
|||
[('product_tmpl_id', '=', product_template_id)], limit=1) |
|||
invoice = self.env['account.move'].search( |
|||
[('id', 'in', self.invoice_ids.ids), |
|||
('payment_state', 'in', ['paid', 'partial'])]).mapped( |
|||
'amount_untaxed_signed') |
|||
invoiced_amount = sum(invoice) |
|||
total_price = self.uptodate_price |
|||
if invoiced_amount == total_price: |
|||
self.write({'state': 'cancel'}) |
|||
self.sale_id.action_done() |
|||
elif invoiced_amount > total_price: |
|||
self.write({'state': 'cancel'}) |
|||
self.refund_id = self.env['account.move'].create({ |
|||
'move_type': 'out_refund', |
|||
'invoice_date': fields.Date.today(), |
|||
'partner_id': self.customer_id.id, |
|||
'invoice_line_ids': [fields.Command.create({ |
|||
'product_id': product_id.id, |
|||
'name': self.vehicle_id.name, |
|||
'price_unit': self.uptodate_price + self.extra_price, |
|||
})] |
|||
}) |
|||
else: |
|||
return { |
|||
'type': 'ir.actions.client', |
|||
'tag': 'display_notification', |
|||
'params': { |
|||
'title': _('Warning'), |
|||
'message': _( |
|||
'You need to pay the amount till date in order to ' |
|||
'cancel the subscription'), |
|||
'sticky': True, |
|||
} |
|||
} |
|||
|
|||
@api.onchange('end_date') |
|||
def _onchange_end_date(self): |
|||
"""Check expiry for subscription""" |
|||
if self.end_date: |
|||
if self.end_date < fields.Date.today(): |
|||
self.write({'state': 'expired'}) |
|||
|
|||
@api.constrains('start_date', 'end_date') |
|||
def _check_dates(self): |
|||
"""Ensure that the start date is not greater than the end date.""" |
|||
for rec in self: |
|||
if rec.start_date > rec.end_date: |
|||
raise ValidationError( |
|||
"Start Date cannot be greater than End Date") |
@ -0,0 +1,69 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################# |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# Author: Jumana Jabin MP (<https://www.cybrosys.com>) |
|||
# |
|||
# You can modify it under the terms of the GNU AFFERO |
|||
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
|||
# (AGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################# |
|||
from datetime import datetime |
|||
from odoo import api, fields, models |
|||
|
|||
|
|||
class FleetVehicle(models.Model): |
|||
"""Inherited model to add fields and functions""" |
|||
_inherit = 'fleet.vehicle' |
|||
|
|||
free_km = fields.Float(string="Free KM", required=True, default=1500, |
|||
help="Set free km for each vehicle") |
|||
subscription_price = fields.Float(string="Subscription price per day", |
|||
help='Price of vehicle per day', |
|||
required=True, default=12) |
|||
states_id = fields.Many2one("res.country.state", string='State', |
|||
help="Help you choose the state") |
|||
countries_id = fields.Many2one('res.country', string='Country', |
|||
help="help you to choose country") |
|||
insurance = fields.Char(string="Insurance", |
|||
help="Helps you to set Insurance") |
|||
start = fields.Date(string="Start Date", |
|||
help="Helps you to choose start date") |
|||
end = fields.Date(string="End Date", help="Helps you to choose end date") |
|||
duration = fields.Integer(string="Duration", compute='_compute_duration') |
|||
fuel = fields.Selection(selection=[('with_fuel', 'With Fuel'), |
|||
('without_fuel', 'Without Fuel')], |
|||
string="Fuel Choice", default='without_fuel', |
|||
help="Help you to choose the type of fuel") |
|||
fuel_rate = fields.Integer(String="Rate", default=300, help="Rate of fuel") |
|||
charge_km = fields.Integer(string="Charge in km", default=12, |
|||
help="Rate per kilometer") |
|||
extra_km = fields.Float(string="Extra KMS", default=1500, |
|||
help="As per customer he/she can choose extra km") |
|||
mileage = fields.Float(related='model_id.mileage', string='Mileage', |
|||
help="Helps to set mileage of vehicle") |
|||
|
|||
@api.depends('start', 'end') |
|||
def _compute_duration(self): |
|||
"""Compute duration of days based on start and end date""" |
|||
for record in self: |
|||
if record.start and record.end: |
|||
start = record.start.strftime("%Y-%m-%d") |
|||
end = record.end.strftime("%Y-%m-%d") |
|||
start_datetime = datetime.strptime(start, "%Y-%m-%d") |
|||
end_datetime = datetime.strptime(end, "%Y-%m-%d") |
|||
delta = end_datetime - start_datetime |
|||
record.duration = delta.days |
|||
else: |
|||
record.duration = 0 |
@ -0,0 +1,34 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################# |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# Author: Jumana Jabin MP (<https://www.cybrosys.com>) |
|||
# |
|||
# You can modify it under the terms of the GNU AFFERO |
|||
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
|||
# (AGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################# |
|||
from odoo import fields, models |
|||
|
|||
|
|||
class FleetVehicleModel(models.Model): |
|||
"""Inherited fleet.vehicle.model to add fields""" |
|||
_inherit = 'fleet.vehicle.model' |
|||
|
|||
mileage = fields.Float(string="Mileage", required=True, |
|||
default=12, help="Helps you to set mileage for " |
|||
"vehicle") |
|||
seats = fields.Integer(string='Seats Number', required=True, |
|||
default=4, helps="Helps you to choose seating " |
|||
"capacity") |
@ -0,0 +1,48 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################# |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# Author: Jumana Jabin MP (<https://www.cybrosys.com>) |
|||
# |
|||
# You can modify it under the terms of the GNU AFFERO |
|||
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
|||
# (AGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################# |
|||
from odoo import fields, models |
|||
|
|||
|
|||
class InsuranceType(models.Model): |
|||
"""New model insurance type to add fields""" |
|||
_name = "insurance.type" |
|||
_description = "Insurance type" |
|||
_inherit = "mail.thread" |
|||
|
|||
name = fields.Char(string="Insurance Name", required=True, |
|||
help="This field is used to set name for insurance") |
|||
coverage_ids = fields.One2many('insurance.coverage', 'coverage_id', |
|||
string="Coverage", |
|||
help="Helps you to give details of coverage") |
|||
|
|||
|
|||
class InsuranceCoverageType(models.Model): |
|||
"""One2many field for insurance type""" |
|||
_name = 'insurance.coverage' |
|||
_description = "Insurance Coverage" |
|||
|
|||
description = fields.Char(string="Description", help="Detail of coverage", |
|||
required=True) |
|||
coverage_price = fields.Float(string="Price", help="Rate of insurance", |
|||
required=True) |
|||
coverage_id = fields.Many2one('insurance.type', |
|||
help="Can choose insurance type") |
@ -0,0 +1,95 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################# |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# Author: Jumana Jabin MP (<https://www.cybrosys.com>) |
|||
# |
|||
# You can modify it under the terms of the GNU AFFERO |
|||
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
|||
# (AGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################# |
|||
from odoo import api, fields, models |
|||
|
|||
|
|||
class SubscriptionRequest(models.Model): |
|||
"""New model subscription.request""" |
|||
_name = "subscription.request" |
|||
_description = "Subscription Request" |
|||
_inherit = "mail.thread" |
|||
_rec_name = "new_vehicle_id" |
|||
|
|||
customer_id = fields.Many2one('res.partner', string="Customer", |
|||
help="Choose the customer for subscription " |
|||
"request") |
|||
sale_id = fields.Many2one('sale.order', string='sale', readonly=True, |
|||
help="Helps you to store sale order") |
|||
refund_id = fields.Many2one('account.move', string='Refund', readonly=True) |
|||
current_vehicle_id = fields.Many2one('fleet.vehicle', |
|||
string="Current Vehicle", |
|||
help="Currently using vehicle of " |
|||
"customer will be set", |
|||
required=True) |
|||
new_vehicle_id = fields.Many2one('fleet.vehicle', string="New Vehicle" |
|||
, domain="[('id', 'in', vehicle_ids)]", |
|||
help="Can choose different vehicle " |
|||
"with same model", required=True) |
|||
vehicle_ids = fields.Many2many('fleet.vehicle', |
|||
compute='_compute_vehicle_ids', |
|||
help="Compute and can choose vehicle with " |
|||
"satisfying domain ") |
|||
reason_to_change = fields.Char(string="Reason", required=True, |
|||
help="Reason for changing vehicle") |
|||
state = fields.Selection( |
|||
selection=[('to_approve', 'To Approve'), |
|||
('approved', 'Approved'), |
|||
], string='State', default='to_approve', |
|||
help="States of subscription") |
|||
is_subscription = fields.Boolean(string="Subscription", |
|||
help="Online Subscriptions") |
|||
|
|||
@api.depends('current_vehicle_id') |
|||
def _compute_vehicle_ids(self): |
|||
"""This method searches for vehicles with the same model and brand |
|||
as the current vehicle, excluding the current vehicle itself. |
|||
The vehicle IDs are updated accordingly, and the state |
|||
is set to 'to_approve'.""" |
|||
self.vehicle_ids = False |
|||
model_id = self.env['fleet.vehicle'].search( |
|||
[('model_id', '=', self.current_vehicle_id.model_id.id), |
|||
('model_id.brand_id', '=', |
|||
self.current_vehicle_id.model_id.brand_id.id), |
|||
('id', '!=', self.current_vehicle_id.id)]) |
|||
for record in model_id: |
|||
self.vehicle_ids = [(4, record.id)] |
|||
self.write({'state': 'to_approve'}) |
|||
|
|||
def action_approve(self): |
|||
""" Process the approval of the subscription request.""" |
|||
subscription = self.env['fleet.subscription'].search( |
|||
[('vehicle_id', '=', self.current_vehicle_id.id), |
|||
('state', '=', 'subscribed')]) |
|||
subscription.update({ |
|||
'vehicle_id': self.new_vehicle_id, |
|||
'invisible_sub': True, |
|||
}) |
|||
self.write({'state': 'approved'}) |
|||
sale_order = subscription.sale_id |
|||
if sale_order.order_line: |
|||
sale_order.order_line[0].name = self.new_vehicle_id.name |
|||
invoice_ids = subscription.invoice_ids |
|||
for rec in invoice_ids: |
|||
if rec.invoice_line_ids: |
|||
rec.invoice_line_ids[0].name = self.new_vehicle_id.name |
|||
subscription.write({'sale_id': sale_order.id}) |
|||
subscription.write({'invoice_ids': invoice_ids.ids}) |
@ -0,0 +1,59 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################# |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# Author: Jumana Jabin MP (<https://www.cybrosys.com>) |
|||
# |
|||
# You can modify it under the terms of the GNU AFFERO |
|||
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
|||
# (AGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################# |
|||
from odoo import api, fields, models |
|||
from odoo.exceptions import ValidationError |
|||
|
|||
|
|||
class VehicleInsurance(models.Model): |
|||
"""New model vehicle .insurance""" |
|||
_name = "vehicle.insurance" |
|||
_description = "Vehicle Insurance" |
|||
_inherit = "mail.thread" |
|||
_rec_name = "vehicle_id" |
|||
|
|||
vehicle_id = fields.Many2one('fleet.vehicle', string="Vehicle", |
|||
help="Help you to choose vehicle", |
|||
required=True) |
|||
start_date = fields.Date(string="Start Date", help="Insurance start date", |
|||
required=True) |
|||
end_date = fields.Date(string="End Date", help="Insurance end date", |
|||
required=True) |
|||
insurance_type_id = fields.Many2one('insurance.type', |
|||
string='Insurance Type', |
|||
help="Choose insurance type") |
|||
insurance_amount = fields.Float(string="Amount", |
|||
compute="_compute_insurance_amount", |
|||
help="Calculate insurance amount") |
|||
|
|||
def _compute_insurance_amount(self): |
|||
"""Function used to compute insurance amount""" |
|||
for rec in self: |
|||
rec.insurance_amount = sum(rec.insurance_type_id.coverage_ids. |
|||
mapped('coverage_price')) |
|||
|
|||
@api.constrains('start_date', 'end_date') |
|||
def _check_dates(self): |
|||
"""Ensure that the start date is not greater than the end date.""" |
|||
for rec in self: |
|||
if rec.start_date > rec.end_date: |
|||
raise ValidationError( |
|||
"Start Date cannot be greater than End Date") |
|
@ -0,0 +1,17 @@ |
|||
<?xml version="1.0" encoding="utf-8" ?> |
|||
<odoo> |
|||
<!--User access levels for Fleet module--> |
|||
<record id="module_vehicle_subscription" model="ir.module.category"> |
|||
<field name="name">Vehicle Subscription</field> |
|||
<field name="description">User access levels for Fleet module</field> |
|||
<field name="sequence">10</field> |
|||
</record> |
|||
<record id="vehicle_subscription_group_user" model="res.groups"> |
|||
<field name="name">User</field> |
|||
<field name="category_id" ref="module_vehicle_subscription"/> |
|||
</record> |
|||
<record id="vehicle_subscription_group_manager" model="res.groups"> |
|||
<field name="name">Manager</field> |
|||
<field name="category_id" ref="module_vehicle_subscription"/> |
|||
</record> |
|||
</odoo> |
After Width: | Height: | Size: 36 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: 576 B |
After Width: | Height: | Size: 733 B |
After Width: | Height: | Size: 911 B |
After Width: | Height: | Size: 1.1 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: 11 KiB |
After Width: | Height: | Size: 878 B |
After Width: | Height: | Size: 653 B |
After Width: | Height: | Size: 905 B |
After Width: | Height: | Size: 839 B |
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: 1.2 KiB |
After Width: | Height: | Size: 80 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 589 B |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 565 B |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 967 B |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 43 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 4.0 KiB |
After Width: | Height: | Size: 38 KiB |
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 81 KiB |
After Width: | Height: | Size: 82 KiB |
After Width: | Height: | Size: 79 KiB |
After Width: | Height: | Size: 86 KiB |
After Width: | Height: | Size: 95 KiB |
After Width: | Height: | Size: 88 KiB |
After Width: | Height: | Size: 97 KiB |
After Width: | Height: | Size: 110 KiB |
After Width: | Height: | Size: 204 KiB |
After Width: | Height: | Size: 66 KiB |
After Width: | Height: | Size: 138 KiB |
After Width: | Height: | Size: 63 KiB |
After Width: | Height: | Size: 86 KiB |
After Width: | Height: | Size: 96 KiB |
After Width: | Height: | Size: 105 KiB |
After Width: | Height: | Size: 77 KiB |
After Width: | Height: | Size: 211 KiB |
After Width: | Height: | Size: 59 KiB |
After Width: | Height: | Size: 806 KiB |
After Width: | Height: | Size: 84 KiB |
After Width: | Height: | Size: 86 KiB |
After Width: | Height: | Size: 97 KiB |
After Width: | Height: | Size: 146 KiB |
After Width: | Height: | Size: 138 KiB |
After Width: | Height: | Size: 94 KiB |
After Width: | Height: | Size: 84 KiB |
After Width: | Height: | Size: 590 KiB |
After Width: | Height: | Size: 84 KiB |
After Width: | Height: | Size: 12 KiB |
@ -0,0 +1,31 @@ |
|||
odoo.define('vehicle_subscription.subscription_change', function (require) { |
|||
"use strict"; |
|||
var publicWidget = require('web.public.widget'); |
|||
const ajax = require('web.ajax'); |
|||
publicWidget.registry.Change = publicWidget.Widget.extend({ |
|||
selector: '.change_sub_vehicle', |
|||
start: function() { |
|||
this._super.apply(this, arguments); |
|||
this._onChangeCustomer(); // Call the function initially
|
|||
}, |
|||
//On the onchange function customer is passed to controller
|
|||
_onChangeCustomer: async function(ev){ |
|||
var self=this; |
|||
var customer_id = this.$('input[name="customer"]')[0].value |
|||
awaitajax.jsonRpc('/online/choose/vehicle', "call", { |
|||
'customer_id': customer_id, |
|||
}) |
|||
.then(function(result) { |
|||
const select = self.$el.find('#vehicle_change')[0]; |
|||
const options = Array.from(select.options); |
|||
options.forEach((option) => { |
|||
option.remove(); |
|||
}); |
|||
result.forEach((item) => { |
|||
let newOption = new Option(item[1], item[0]); |
|||
select.add(newOption, undefined); |
|||
}); |
|||
}); |
|||
}, |
|||
}) |
|||
}) |
@ -0,0 +1,19 @@ |
|||
odoo.define('vehicle_subscription.subscription_submit_request', function (require) { |
|||
"use strict"; |
|||
var publicWidget = require('web.public.widget'); |
|||
const ajax = require('web.ajax'); |
|||
//To getitem to get vehicle.
|
|||
$(function() { |
|||
if (localStorage.getItem('current_vehicle')) { |
|||
$('#current_vehicle').val(localStorage.getItem('current_vehicle')); |
|||
} |
|||
}); |
|||
publicWidget.registry.Request = publicWidget.Widget.extend({ |
|||
selector: '.submit_boolean_on', |
|||
start: function() { |
|||
var self = this;//setitem to store the element.
|
|||
var current_vehicle = self.$('#current_vehicle').val(); |
|||
localStorage.setItem('current_vehicle', current_vehicle); |
|||
} |
|||
}) |
|||
}) |
@ -0,0 +1,31 @@ |
|||
odoo.define('vehicle_subscription.subscription_cancellation', function (require) { |
|||
"use strict"; |
|||
var publicWidget = require('web.public.widget'); |
|||
const ajax = require('web.ajax'); |
|||
publicWidget.registry.Cancellation = publicWidget.Widget.extend({ |
|||
selector: '.cancel_sub', |
|||
start: function() { |
|||
this._super.apply(this, arguments); |
|||
this._onChangeCustomer(); // Call the function initially
|
|||
}, |
|||
//On the onchange function customer is passed to controller
|
|||
_onChangeCustomer: async function(ev){ |
|||
var self=this; |
|||
var customer_id = this.$('input[name="customer"]')[0].value |
|||
await ajax.jsonRpc('/online/choose/vehicle', "call", { |
|||
'customer_id': customer_id, |
|||
}) |
|||
.then(function(result) { |
|||
const select = self.$el.find('#vehicle_cancellation')[0]; |
|||
const options = Array.from(select.options); |
|||
options.forEach((option) => { |
|||
option.remove(); |
|||
}); |
|||
result.forEach((item) => { |
|||
let newOption = new Option(item[1], item[0]); |
|||
select.add(newOption, undefined); |
|||
}); |
|||
}); |
|||
} |
|||
}) |
|||
}) |