[MOV] fleet: Split models and views into separate files
It's a PITA to have a huge files with all the models into it. We took this opportunity to stick to the guideline.
This commit is contained in:
@@ -31,7 +31,9 @@ Main Features
|
||||
'data': [
|
||||
'security/fleet_security.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'views/fleet_view.xml',
|
||||
'views/fleet_vehicle_model_views.xml',
|
||||
'views/fleet_vehicle_views.xml',
|
||||
'views/fleet_vehicle_cost_views.xml',
|
||||
'views/fleet_board_view.xml',
|
||||
'data/fleet_cars_data.xml',
|
||||
'data/fleet_data.xml',
|
||||
|
||||
@@ -1 +1,6 @@
|
||||
import fleet
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import fleet_vehicle
|
||||
import fleet_vehicle_cost
|
||||
import fleet_vehicle_model
|
||||
|
||||
@@ -1,668 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from odoo import api, fields, models, tools, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class FleetVehicleCost(models.Model):
|
||||
_name = 'fleet.vehicle.cost'
|
||||
_description = 'Cost related to a vehicle'
|
||||
_order = 'date desc, vehicle_id asc'
|
||||
|
||||
name = fields.Char(related='vehicle_id.name', string='Name', store=True)
|
||||
vehicle_id = fields.Many2one('fleet.vehicle', 'Vehicle', required=True, help='Vehicle concerned by this log')
|
||||
cost_subtype_id = fields.Many2one('fleet.service.type', 'Type', help='Cost type purchased with this cost')
|
||||
amount = fields.Float('Total Price')
|
||||
cost_type = fields.Selection([('contract', 'Contract'), ('services', 'Services'), ('fuel', 'Fuel'), ('other', 'Other')],
|
||||
'Category of the cost', default="other", help='For internal purpose only', required=True)
|
||||
parent_id = fields.Many2one('fleet.vehicle.cost', 'Parent', help='Parent cost to this current cost')
|
||||
cost_ids = fields.One2many('fleet.vehicle.cost', 'parent_id', 'Included Services', copy=True)
|
||||
odometer_id = fields.Many2one('fleet.vehicle.odometer', 'Odometer', help='Odometer measure of the vehicle at the moment of this log')
|
||||
odometer = fields.Float(compute="_get_odometer", inverse='_set_odometer', string='Odometer Value', help='Odometer measure of the vehicle at the moment of this log')
|
||||
odometer_unit = fields.Selection(related='vehicle_id.odometer_unit', string="Unit", readonly=True)
|
||||
date = fields.Date(help='Date when the cost has been executed')
|
||||
contract_id = fields.Many2one('fleet.vehicle.log.contract', 'Contract', help='Contract attached to this cost')
|
||||
auto_generated = fields.Boolean('Automatically Generated', readonly=True)
|
||||
description = fields.Char("Cost Description")
|
||||
|
||||
def _get_odometer(self):
|
||||
for record in self:
|
||||
if record.odometer_id:
|
||||
record.odometer = record.odometer_id.value
|
||||
|
||||
def _set_odometer(self):
|
||||
for record in self:
|
||||
if not record.odometer:
|
||||
raise UserError(_('Emptying the odometer value of a vehicle is not allowed.'))
|
||||
odometer = self.env['fleet.vehicle.odometer'].create({
|
||||
'value': record.odometer,
|
||||
'date': record.date or fields.Date.context_today(record),
|
||||
'vehicle_id': record.vehicle_id.id
|
||||
})
|
||||
self.odometer_id = odometer
|
||||
|
||||
@api.model
|
||||
def create(self, data):
|
||||
#make sure that the data are consistent with values of parent and contract records given
|
||||
if 'parent_id' in data and data['parent_id']:
|
||||
parent = self.browse(data['parent_id'])
|
||||
data['vehicle_id'] = parent.vehicle_id.id
|
||||
data['date'] = parent.date
|
||||
data['cost_type'] = parent.cost_type
|
||||
if 'contract_id' in data and data['contract_id']:
|
||||
contract = self.env['fleet.vehicle.log.contract'].browse(data['contract_id'])
|
||||
data['vehicle_id'] = contract.vehicle_id.id
|
||||
data['cost_subtype_id'] = contract.cost_subtype_id.id
|
||||
data['cost_type'] = contract.cost_type
|
||||
if 'odometer' in data and not data['odometer']:
|
||||
#if received value for odometer is 0, then remove it from the data as it would result to the creation of a
|
||||
#odometer log with 0, which is to be avoided
|
||||
del(data['odometer'])
|
||||
return super(FleetVehicleCost, self).create(data)
|
||||
|
||||
class FleetVehicleTag(models.Model):
|
||||
_name = 'fleet.vehicle.tag'
|
||||
|
||||
name = fields.Char(required=True, translate=True)
|
||||
color = fields.Integer('Color Index', default=10)
|
||||
|
||||
_sql_constraints = [('name_uniq', 'unique (name)', "Tag name already exists !")]
|
||||
|
||||
class FleetVehicleState(models.Model):
|
||||
_name = 'fleet.vehicle.state'
|
||||
_order = 'sequence asc'
|
||||
|
||||
name = fields.Char(required=True)
|
||||
sequence = fields.Integer(help="Used to order the note stages")
|
||||
|
||||
_sql_constraints = [('fleet_state_name_unique', 'unique(name)', 'State name already exists')]
|
||||
|
||||
class FleetVehicleModel(models.Model):
|
||||
_name = 'fleet.vehicle.model'
|
||||
_description = 'Model of a vehicle'
|
||||
_order = 'name asc'
|
||||
|
||||
name = fields.Char('Model name', required=True)
|
||||
brand_id = fields.Many2one('fleet.vehicle.model.brand', 'Make', required=True, help='Make of the vehicle')
|
||||
vendors = fields.Many2many('res.partner', 'fleet_vehicle_model_vendors', 'model_id', 'partner_id', string='Vendors')
|
||||
image = fields.Binary(related='brand_id.image', string="Logo")
|
||||
image_medium = fields.Binary(related='brand_id.image_medium', string="Logo (medium)")
|
||||
image_small = fields.Binary(related='brand_id.image_small', string="Logo (small)")
|
||||
|
||||
@api.multi
|
||||
@api.depends('name', 'brand_id')
|
||||
def name_get(self):
|
||||
res = []
|
||||
for record in self:
|
||||
name = record.name
|
||||
if record.brand_id.name:
|
||||
name = record.brand_id.name + '/' + name
|
||||
res.append((record.id, name))
|
||||
return res
|
||||
|
||||
@api.onchange('brand_id')
|
||||
def _onchange_brand(self):
|
||||
if self.brand_id:
|
||||
self.image_medium = self.brand_id.image
|
||||
else:
|
||||
self.image_medium = False
|
||||
|
||||
class FleetVehicleModelBrand(models.Model):
|
||||
_name = 'fleet.vehicle.model.brand'
|
||||
_description = 'Brand model of the vehicle'
|
||||
_order = 'name asc'
|
||||
|
||||
name = fields.Char('Make', required=True)
|
||||
image = fields.Binary("Logo", attachment=True,
|
||||
help="This field holds the image used as logo for the brand, limited to 1024x1024px.")
|
||||
image_medium = fields.Binary("Medium-sized image", attachment=True,
|
||||
help="Medium-sized logo of the brand. It is automatically "
|
||||
"resized as a 128x128px image, with aspect ratio preserved. "
|
||||
"Use this field in form views or some kanban views.")
|
||||
image_small = fields.Binary("Small-sized image", attachment=True,
|
||||
help="Small-sized logo of the brand. It is automatically "
|
||||
"resized as a 64x64px image, with aspect ratio preserved. "
|
||||
"Use this field anywhere a small image is required.")
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
tools.image_resize_images(vals)
|
||||
return super(FleetVehicleModelBrand, self).create(vals)
|
||||
|
||||
@api.multi
|
||||
def write(self, vals):
|
||||
tools.image_resize_images(vals)
|
||||
return super(FleetVehicleModelBrand, self).write(vals)
|
||||
|
||||
class FleetVehicle(models.Model):
|
||||
_inherit = 'mail.thread'
|
||||
_name = 'fleet.vehicle'
|
||||
_description = 'Information on a vehicle'
|
||||
_order = 'license_plate asc'
|
||||
|
||||
def _get_default_state(self):
|
||||
state = self.env.ref('fleet.vehicle_state_active', raise_if_not_found=False)
|
||||
return state and state.id or False
|
||||
|
||||
name = fields.Char(compute="_compute_vehicle_name", store=True)
|
||||
active = fields.Boolean(default=True)
|
||||
company_id = fields.Many2one('res.company', 'Company')
|
||||
license_plate = fields.Char(required=True, help='License plate number of the vehicle (i = plate number for a car)')
|
||||
vin_sn = fields.Char('Chassis Number', help='Unique number written on the vehicle motor (VIN/SN number)', copy=False)
|
||||
driver_id = fields.Many2one('res.partner', 'Driver', help='Driver of the vehicle')
|
||||
model_id = fields.Many2one('fleet.vehicle.model', 'Model', required=True, help='Model of the vehicle')
|
||||
log_fuel = fields.One2many('fleet.vehicle.log.fuel', 'vehicle_id', 'Fuel Logs')
|
||||
log_services = fields.One2many('fleet.vehicle.log.services', 'vehicle_id', 'Services Logs')
|
||||
log_contracts = fields.One2many('fleet.vehicle.log.contract', 'vehicle_id', 'Contracts')
|
||||
cost_count = fields.Integer(compute="_compute_count_all", string="Costs")
|
||||
contract_count = fields.Integer(compute="_compute_count_all", string='Contracts')
|
||||
service_count = fields.Integer(compute="_compute_count_all", string='Services')
|
||||
fuel_logs_count = fields.Integer(compute="_compute_count_all", string='Fuel Logs')
|
||||
odometer_count = fields.Integer(compute="_compute_count_all", string='Odometer')
|
||||
acquisition_date = fields.Date('Immatriculation Date', required=False, help='Date when the vehicle has been immatriculated')
|
||||
color = fields.Char(help='Color of the vehicle')
|
||||
state_id = fields.Many2one('fleet.vehicle.state', 'State', default=_get_default_state, help='Current state of the vehicle', ondelete="set null")
|
||||
location = fields.Char(help='Location of the vehicle (garage, ...)')
|
||||
seats = fields.Integer('Seats Number', help='Number of seats of the vehicle')
|
||||
doors = fields.Integer('Doors Number', help='Number of doors of the vehicle', default=5)
|
||||
tag_ids = fields.Many2many('fleet.vehicle.tag', 'fleet_vehicle_vehicle_tag_rel', 'vehicle_tag_id', 'tag_id', 'Tags', copy=False)
|
||||
odometer = fields.Float(compute='_get_odometer', inverse='_set_odometer', string='Last Odometer', help='Odometer measure of the vehicle at the moment of this log')
|
||||
odometer_unit = fields.Selection([('kilometers', 'Kilometers'), ('miles', 'Miles')],
|
||||
'Odometer Unit', default='kilometers', help='Unit of the odometer ', required=True)
|
||||
transmission = fields.Selection([('manual', 'Manual'), ('automatic', 'Automatic')], 'Transmission', help='Transmission Used by the vehicle')
|
||||
fuel_type = fields.Selection([('gasoline', 'Gasoline'), ('diesel', 'Diesel'), ('electric', 'Electric'), ('hybrid', 'Hybrid')], 'Fuel Type', help='Fuel Used by the vehicle')
|
||||
horsepower = fields.Integer()
|
||||
horsepower_tax = fields.Float('Horsepower Taxation')
|
||||
power = fields.Integer('Power', help='Power in kW of the vehicle')
|
||||
co2 = fields.Float('CO2 Emissions', help='CO2 emissions of the vehicle')
|
||||
image = fields.Binary(related='model_id.image', string="Logo")
|
||||
image_medium = fields.Binary(related='model_id.image_medium', string="Logo (medium)")
|
||||
image_small = fields.Binary(related='model_id.image_small', string="Logo (small)")
|
||||
contract_renewal_due_soon = fields.Boolean(compute='_compute_contract_reminder', search='_search_contract_renewal_due_soon', string='Has Contracts to renew', multi='contract_info')
|
||||
contract_renewal_overdue = fields.Boolean(compute='_compute_contract_reminder', search='_search_get_overdue_contract_reminder', string='Has Contracts Overdue', multi='contract_info')
|
||||
contract_renewal_name = fields.Text(compute='_compute_contract_reminder', string='Name of contract to renew soon', multi='contract_info')
|
||||
contract_renewal_total = fields.Text(compute='_compute_contract_reminder', string='Total of contracts due or overdue minus one', multi='contract_info')
|
||||
car_value = fields.Float(string="Catalog Value (VAT incl.)", help='Value of the bought vehicle')
|
||||
residual_value = fields.Float()
|
||||
|
||||
_sql_constraints = [
|
||||
('driver_id_unique', 'UNIQUE(driver_id)', 'Only one car can be assigned to the same employee!')
|
||||
]
|
||||
|
||||
@api.depends('model_id', 'license_plate')
|
||||
def _compute_vehicle_name(self):
|
||||
for record in self:
|
||||
record.name = record.model_id.brand_id.name + '/' + record.model_id.name + '/' + record.license_plate
|
||||
|
||||
def _get_odometer(self):
|
||||
FleetVehicalOdometer = self.env['fleet.vehicle.odometer']
|
||||
for record in self:
|
||||
vehicle_odometer = FleetVehicalOdometer.search([('vehicle_id', '=', record.id)], limit=1, order='value desc')
|
||||
if vehicle_odometer:
|
||||
record.odometer = vehicle_odometer.value
|
||||
else:
|
||||
record.odometer = 0
|
||||
|
||||
def _set_odometer(self):
|
||||
for record in self:
|
||||
if record.odometer:
|
||||
date = fields.Date.context_today(record)
|
||||
data = {'value': record.odometer, 'date': date, 'vehicle_id': record.id}
|
||||
self.env['fleet.vehicle.odometer'].create(data)
|
||||
|
||||
def _compute_count_all(self):
|
||||
Odometer = self.env['fleet.vehicle.odometer']
|
||||
LogFuel = self.env['fleet.vehicle.log.fuel']
|
||||
LogService = self.env['fleet.vehicle.log.services']
|
||||
LogContract = self.env['fleet.vehicle.log.contract']
|
||||
Cost = self.env['fleet.vehicle.cost']
|
||||
for record in self:
|
||||
record.odometer_count = Odometer.search_count([('vehicle_id', '=', record.id)])
|
||||
record.fuel_logs_count = LogFuel.search_count([('vehicle_id', '=', record.id)])
|
||||
record.service_count = LogService.search_count([('vehicle_id', '=', record.id)])
|
||||
record.contract_count = LogContract.search_count([('vehicle_id', '=', record.id), ('state', '=', 'open')])
|
||||
record.cost_count = Cost.search_count([('vehicle_id', '=', record.id), ('parent_id', '=', False)])
|
||||
|
||||
@api.depends('log_contracts')
|
||||
def _compute_contract_reminder(self):
|
||||
for record in self:
|
||||
overdue = False
|
||||
due_soon = False
|
||||
total = 0
|
||||
name = ''
|
||||
for element in record.log_contracts:
|
||||
if element.state in ('open', 'toclose') and element.expiration_date:
|
||||
current_date_str = fields.Date.context_today(record)
|
||||
due_time_str = element.expiration_date
|
||||
current_date = fields.Date.from_string(current_date_str)
|
||||
due_time = fields.Date.from_string(due_time_str)
|
||||
diff_time = (due_time - current_date).days
|
||||
if diff_time < 0:
|
||||
overdue = True
|
||||
total += 1
|
||||
if diff_time < 15 and diff_time >= 0:
|
||||
due_soon = True
|
||||
total += 1
|
||||
if overdue or due_soon:
|
||||
log_contract = self.env['fleet.vehicle.log.contract'].search([('vehicle_id', '=', record.id), ('state', 'in', ('open', 'toclose'))],
|
||||
limit=1, order='expiration_date asc')
|
||||
if log_contract:
|
||||
#we display only the name of the oldest overdue/due soon contract
|
||||
name = log_contract.cost_subtype_id.name
|
||||
|
||||
record.contract_renewal_overdue = overdue
|
||||
record.contract_renewal_due_soon = due_soon
|
||||
record.contract_renewal_total = total - 1 # we remove 1 from the real total for display purposes
|
||||
record.contract_renewal_name = name
|
||||
|
||||
def _search_contract_renewal_due_soon(self, operator, value):
|
||||
res = []
|
||||
assert operator in ('=', '!=', '<>') and value in (True, False), 'Operation not supported'
|
||||
if (operator == '=' and value is True) or (operator in ('<>', '!=') and value is False):
|
||||
search_operator = 'in'
|
||||
else:
|
||||
search_operator = 'not in'
|
||||
today = fields.Date.context_today(self)
|
||||
datetime_today = fields.Datetime.from_string(today)
|
||||
limit_date = fields.Datetime.to_string(datetime_today + relativedelta(days=+15))
|
||||
self.env.cr.execute("""SELECT cost.vehicle_id,
|
||||
count(contract.id) AS contract_number
|
||||
FROM fleet_vehicle_cost cost
|
||||
LEFT JOIN fleet_vehicle_log_contract contract ON contract.cost_id = cost.id
|
||||
WHERE contract.expiration_date IS NOT NULL
|
||||
AND contract.expiration_date > %s
|
||||
AND contract.expiration_date < %s
|
||||
AND contract.state IN ('open', 'toclose')
|
||||
GROUP BY cost.vehicle_id""", (today, limit_date))
|
||||
res_ids = [x[0] for x in self.env.cr.fetchall()]
|
||||
res.append(('id', search_operator, res_ids))
|
||||
return res
|
||||
|
||||
def _search_get_overdue_contract_reminder(self, operator, value):
|
||||
res = []
|
||||
assert operator in ('=', '!=', '<>') and value in (True, False), 'Operation not supported'
|
||||
if (operator == '=' and value is True) or (operator in ('<>', '!=') and value is False):
|
||||
search_operator = 'in'
|
||||
else:
|
||||
search_operator = 'not in'
|
||||
today = fields.Date.context_today(self)
|
||||
self.env.cr.execute('''SELECT cost.vehicle_id,
|
||||
count(contract.id) AS contract_number
|
||||
FROM fleet_vehicle_cost cost
|
||||
LEFT JOIN fleet_vehicle_log_contract contract ON contract.cost_id = cost.id
|
||||
WHERE contract.expiration_date IS NOT NULL
|
||||
AND contract.expiration_date < %s
|
||||
AND contract.state IN ('open', 'toclose')
|
||||
GROUP BY cost.vehicle_id ''', (today,))
|
||||
res_ids = [x[0] for x in self.env.cr.fetchall()]
|
||||
res.append(('id', search_operator, res_ids))
|
||||
return res
|
||||
|
||||
@api.onchange('model_id')
|
||||
def _onchange_model(self):
|
||||
if self.model_id:
|
||||
self.image_medium = self.model_id.image
|
||||
else:
|
||||
self.image_medium = False
|
||||
|
||||
@api.model
|
||||
def create(self, data):
|
||||
vehicle = super(FleetVehicle, self.with_context(mail_create_nolog=True)).create(data)
|
||||
vehicle.message_post(body=_('%s %s has been added to the fleet!') % (vehicle.model_id.name, vehicle.license_plate))
|
||||
return vehicle
|
||||
|
||||
@api.multi
|
||||
def write(self, vals):
|
||||
"""
|
||||
This function write an entry in the openchatter whenever we change important information
|
||||
on the vehicle like the model, the drive, the state of the vehicle or its license plate
|
||||
"""
|
||||
for vehicle in self:
|
||||
changes = []
|
||||
if 'model_id' in vals and vehicle.model_id.id != vals['model_id']:
|
||||
value = self.env['fleet.vehicle.model'].browse(vals['model_id']).name
|
||||
oldmodel = vehicle.model_id.name or _('None')
|
||||
changes.append(_("Model: from '%s' to '%s'") % (oldmodel, value))
|
||||
if 'driver_id' in vals and vehicle.driver_id.id != vals['driver_id']:
|
||||
value = self.env['res.partner'].browse(vals['driver_id']).name
|
||||
olddriver = (vehicle.driver_id.name) or _('None')
|
||||
changes.append(_("Driver: from '%s' to '%s'") % (olddriver, value))
|
||||
if 'state_id' in vals and vehicle.state_id.id != vals['state_id']:
|
||||
value = self.env['fleet.vehicle.state'].browse(vals['state_id']).name
|
||||
oldstate = vehicle.state_id.name or _('None')
|
||||
changes.append(_("State: from '%s' to '%s'") % (oldstate, value))
|
||||
if 'license_plate' in vals and vehicle.license_plate != vals['license_plate']:
|
||||
old_license_plate = vehicle.license_plate or _('None')
|
||||
changes.append(_("License Plate: from '%s' to '%s'") % (old_license_plate, vals['license_plate']))
|
||||
|
||||
if len(changes) > 0:
|
||||
self.message_post(body=", ".join(changes))
|
||||
|
||||
return super(FleetVehicle, self).write(vals)
|
||||
|
||||
# TODO: Split models in hr_payroll and fleet into several files (this is a real pain in the arse)
|
||||
|
||||
@api.multi
|
||||
def return_action_to_open(self):
|
||||
""" This opens the xml view specified in xml_id for the current vehicle """
|
||||
self.ensure_one()
|
||||
xml_id = self.env.context.get('xml_id')
|
||||
if xml_id:
|
||||
res = self.env['ir.actions.act_window'].for_xml_id('fleet', xml_id)
|
||||
res.update(
|
||||
context=dict(self.env.context, default_vehicle_id=self.id, group_by=False),
|
||||
domain=[('vehicle_id', '=', self.id)]
|
||||
)
|
||||
return res
|
||||
return False
|
||||
|
||||
@api.multi
|
||||
def act_show_log_cost(self):
|
||||
""" This opens log view to view and add new log for this vehicle, groupby default to only show effective costs
|
||||
@return: the costs log view
|
||||
"""
|
||||
self.ensure_one()
|
||||
res = self.env['ir.actions.act_window'].for_xml_id('fleet', 'fleet_vehicle_costs_action')
|
||||
res.update(
|
||||
context=dict(self.env.context, default_vehicle_id=self.id, search_default_parent_false=True),
|
||||
domain=[('vehicle_id', '=', self.id)]
|
||||
)
|
||||
return res
|
||||
|
||||
class FleetVehicleOdometer(models.Model):
|
||||
_name = 'fleet.vehicle.odometer'
|
||||
_description = 'Odometer log for a vehicle'
|
||||
_order = 'date desc'
|
||||
|
||||
name = fields.Char(compute='_compute_vehicle_log_name', store=True)
|
||||
date = fields.Date(default=fields.Date.context_today)
|
||||
value = fields.Float('Odometer Value', group_operator="max")
|
||||
vehicle_id = fields.Many2one('fleet.vehicle', 'Vehicle', required=True)
|
||||
unit = fields.Selection(related='vehicle_id.odometer_unit', string="Unit", readonly=True)
|
||||
driver_id = fields.Many2one(related="vehicle_id.driver_id", string="Driver")
|
||||
|
||||
@api.depends('vehicle_id', 'date')
|
||||
def _compute_vehicle_log_name(self):
|
||||
for record in self:
|
||||
name = record.vehicle_id.name
|
||||
if not name:
|
||||
name = record.date
|
||||
elif record.date:
|
||||
name += ' / ' + record.date
|
||||
self.name = name
|
||||
|
||||
@api.onchange('vehicle_id')
|
||||
def _onchange_vehicle(self):
|
||||
if self.vehicle_id:
|
||||
self.unit = self.vehicle_id.odometer_unit
|
||||
|
||||
class FleetVehicleLogFuel(models.Model):
|
||||
_name = 'fleet.vehicle.log.fuel'
|
||||
_description = 'Fuel log for vehicles'
|
||||
_inherits = {'fleet.vehicle.cost': 'cost_id'}
|
||||
|
||||
@api.model
|
||||
def default_get(self, default_fields):
|
||||
res = super(FleetVehicleLogFuel, self).default_get(default_fields)
|
||||
service = self.env.ref('fleet.type_service_refueling', raise_if_not_found=False)
|
||||
res.update({
|
||||
'date': fields.Date.context_today(self),
|
||||
'cost_subtype_id': service and service.id or False,
|
||||
'cost_type': 'fuel'
|
||||
})
|
||||
return res
|
||||
|
||||
liter = fields.Float()
|
||||
price_per_liter = fields.Float()
|
||||
purchaser_id = fields.Many2one('res.partner', 'Purchaser', domain="['|',('customer','=',True),('employee','=',True)]")
|
||||
inv_ref = fields.Char('Invoice Reference', size=64)
|
||||
vendor_id = fields.Many2one('res.partner', 'Vendor', domain="[('supplier','=',True)]")
|
||||
notes = fields.Text()
|
||||
cost_id = fields.Many2one('fleet.vehicle.cost', 'Cost', required=True, ondelete='cascade')
|
||||
cost_amount = fields.Float(related='cost_id.amount', string='Amount', store=True) # we need to keep this field as a related with store=True because the graph view doesn't support (1) to address fields from inherited table and (2) fields that aren't stored in database
|
||||
|
||||
@api.onchange('vehicle_id')
|
||||
def _onchange_vehicle(self):
|
||||
if self.vehicle_id:
|
||||
self.odometer_unit = self.vehicle_id.odometer_unit
|
||||
self.purchaser_id = self.vehicle_id.driver_id.id
|
||||
|
||||
@api.onchange('liter', 'price_per_liter', 'amount')
|
||||
def _onchange_liter_price_amount(self):
|
||||
#need to cast in float because the value receveid from web client maybe an integer (Javascript and JSON do not
|
||||
#make any difference between 3.0 and 3). This cause a problem if you encode, for example, 2 liters at 1.5 per
|
||||
#liter => total is computed as 3.0, then trigger an onchange that recomputes price_per_liter as 3/2=1 (instead
|
||||
#of 3.0/2=1.5)
|
||||
#If there is no change in the result, we return an empty dict to prevent an infinite loop due to the 3 intertwine
|
||||
#onchange. And in order to verify that there is no change in the result, we have to limit the precision of the
|
||||
#computation to 2 decimal
|
||||
liter = float(self.liter)
|
||||
price_per_liter = float(self.price_per_liter)
|
||||
amount = float(self.amount)
|
||||
if liter > 0 and price_per_liter > 0 and round(liter * price_per_liter, 2) != amount:
|
||||
self.amount = round(liter * price_per_liter, 2)
|
||||
elif amount > 0 and liter > 0 and round(amount / liter, 2) != price_per_liter:
|
||||
self.price_per_liter = round(amount / liter, 2)
|
||||
elif amount > 0 and price_per_liter > 0 and round(amount / price_per_liter, 2) != liter:
|
||||
self.liter = round(amount / price_per_liter, 2)
|
||||
|
||||
class FleetVehicleLogServices(models.Model):
|
||||
_name = 'fleet.vehicle.log.services'
|
||||
_inherits = {'fleet.vehicle.cost': 'cost_id'}
|
||||
_description = 'Services for vehicles'
|
||||
|
||||
@api.model
|
||||
def default_get(self, default_fields):
|
||||
res = super(FleetVehicleLogServices, self).default_get(default_fields)
|
||||
service = self.env.ref('fleet.type_service_service_8', raise_if_not_found=False)
|
||||
res.update({
|
||||
'date': fields.Date.context_today(self),
|
||||
'cost_subtype_id': service and service.id or False,
|
||||
'cost_type': 'services'
|
||||
})
|
||||
return res
|
||||
|
||||
purchaser_id = fields.Many2one('res.partner', 'Purchaser', domain="['|',('customer','=',True),('employee','=',True)]")
|
||||
inv_ref = fields.Char('Invoice Reference')
|
||||
vendor_id = fields.Many2one('res.partner', 'Vendor', domain="[('supplier','=',True)]")
|
||||
# we need to keep this field as a related with store=True because the graph view doesn't support
|
||||
# (1) to address fields from inherited table and (2) fields that aren't stored in database
|
||||
cost_amount = fields.Float(related='cost_id.amount', string='Amount', store=True)
|
||||
notes = fields.Text()
|
||||
cost_id = fields.Many2one('fleet.vehicle.cost', 'Cost', required=True, ondelete='cascade')
|
||||
|
||||
@api.onchange('vehicle_id')
|
||||
def _onchange_vehicle(self):
|
||||
if self.vehicle_id:
|
||||
self.odometer_unit = self.vehicle_id.odometer_unit
|
||||
self.purchaser_id = self.vehicle_id.driver_id.id
|
||||
|
||||
class FleetServiceType(models.Model):
|
||||
_name = 'fleet.service.type'
|
||||
_description = 'Type of services available on a vehicle'
|
||||
|
||||
name = fields.Char(required=True, translate=True)
|
||||
category = fields.Selection([('contract', 'Contract'), ('service', 'Service'), ('both', 'Both')], 'Category',
|
||||
required=True, help='Choose wheter the service refer to contracts, vehicle services or both')
|
||||
|
||||
class FleetVehicleLogContract(models.Model):
|
||||
|
||||
_inherits = {'fleet.vehicle.cost': 'cost_id'}
|
||||
_name = 'fleet.vehicle.log.contract'
|
||||
_description = 'Contract information on a vehicle'
|
||||
_order = 'state desc,expiration_date'
|
||||
|
||||
def compute_next_year_date(self, strdate):
|
||||
oneyear = relativedelta(years=1)
|
||||
start_date = fields.Date.from_string(strdate)
|
||||
return fields.Date.to_string(start_date + oneyear)
|
||||
|
||||
@api.model
|
||||
def default_get(self, default_fields):
|
||||
res = super(FleetVehicleLogContract, self).default_get(default_fields)
|
||||
contract = self.env.ref('fleet.type_contract_leasing', raise_if_not_found=False)
|
||||
res.update({
|
||||
'date': fields.Date.context_today(self),
|
||||
'cost_subtype_id': contract and contract.id or False,
|
||||
'cost_type': 'contract'
|
||||
})
|
||||
return res
|
||||
|
||||
name = fields.Text(compute='_compute_contract_name', store=True)
|
||||
active = fields.Boolean(default=True)
|
||||
start_date = fields.Date('Contract Start Date', default=fields.Date.context_today, help='Date when the coverage of the contract begins')
|
||||
expiration_date = fields.Date('Contract Expiration Date', default=lambda self: self.compute_next_year_date(fields.Date.context_today(self)),
|
||||
help='Date when the coverage of the contract expirates (by default, one year after begin date)')
|
||||
days_left = fields.Integer(compute='_compute_days_left', string='Warning Date')
|
||||
insurer_id = fields.Many2one('res.partner', 'Vendor')
|
||||
purchaser_id = fields.Many2one('res.partner', 'Contractor', default=lambda self: self.env.user.partner_id.id, help='Person to which the contract is signed for')
|
||||
ins_ref = fields.Char('Contract Reference', size=64, copy=False)
|
||||
state = fields.Selection([('open', 'In Progress'), ('toclose', 'To Close'), ('closed', 'Terminated')],
|
||||
'Status', default='open', readonly=True, help='Choose wheter the contract is still valid or not',
|
||||
copy=False)
|
||||
notes = fields.Text('Terms and Conditions', help='Write here all supplementary information relative to this contract', copy=False)
|
||||
cost_generated = fields.Float('Recurring Cost Amount', help="Costs paid at regular intervals, depending on the cost frequency."
|
||||
"If the cost frequency is set to unique, the cost will be logged at the start date")
|
||||
cost_frequency = fields.Selection([('no', 'No'), ('daily', 'Daily'), ('weekly', 'Weekly'), ('monthly', 'Monthly'), ('yearly', 'Yearly')], 'Recurring Cost Frequency',
|
||||
default='no', help='Frequency of the recuring cost', required=True)
|
||||
generated_cost_ids = fields.One2many('fleet.vehicle.cost', 'contract_id', 'Generated Costs')
|
||||
sum_cost = fields.Float(compute='_compute_sum_cost', string='Indicative Costs Total')
|
||||
cost_id = fields.Many2one('fleet.vehicle.cost', 'Cost', required=True, ondelete='cascade')
|
||||
cost_amount = fields.Float(related='cost_id.amount', string='Amount', store=True) # we need to keep this field as a related with store=True because the graph view doesn't support (1) to address fields from inherited table and (2) fields that aren't stored in database
|
||||
odometer = fields.Float(string='Odometer at creation', help='Odometer measure of the vehicle at the moment of the contract creation')
|
||||
|
||||
@api.depends('vehicle_id', 'cost_subtype_id', 'date')
|
||||
def _compute_contract_name(self):
|
||||
for record in self:
|
||||
name = record.vehicle_id.name
|
||||
if record.cost_subtype_id.name:
|
||||
name += ' / ' + record.cost_subtype_id.name
|
||||
if record.date:
|
||||
name += ' / ' + record.date
|
||||
record.name = name
|
||||
|
||||
@api.depends('expiration_date', 'state')
|
||||
def _compute_days_left(self):
|
||||
"""return a dict with as value for each contract an integer
|
||||
if contract is in an open state and is overdue, return 0
|
||||
if contract is in a closed state, return -1
|
||||
otherwise return the number of days before the contract expires
|
||||
"""
|
||||
for record in self:
|
||||
if (record.expiration_date and (record.state == 'open' or record.state == 'toclose')):
|
||||
today = fields.Date.from_string(fields.Date.today())
|
||||
renew_date = fields.Date.from_string(record.expiration_date)
|
||||
diff_time = (renew_date - today).days
|
||||
record.days_left = diff_time > 0 and diff_time or 0
|
||||
else:
|
||||
record.days_left = -1
|
||||
|
||||
@api.depends('cost_ids.amount')
|
||||
def _compute_sum_cost(self):
|
||||
for contract in self:
|
||||
contract.sum_cost = sum(contract.cost_ids.mapped('amount'))
|
||||
|
||||
@api.onchange('vehicle_id')
|
||||
def _onchange_vehicle(self):
|
||||
if self.vehicle_id:
|
||||
self.odometer_unit = self.vehicle_id.odometer_unit
|
||||
|
||||
@api.multi
|
||||
def contract_close(self):
|
||||
for record in self:
|
||||
record.state = 'closed'
|
||||
|
||||
@api.multi
|
||||
def contract_open(self):
|
||||
for record in self:
|
||||
record.state = 'open'
|
||||
|
||||
@api.multi
|
||||
def act_renew_contract(self):
|
||||
assert len(self.ids) == 1, "This operation should only be done for 1 single contract at a time, as it it suppose to open a window as result"
|
||||
for element in self:
|
||||
#compute end date
|
||||
startdate = fields.Date.from_string(element.start_date)
|
||||
enddate = fields.Date.from_string(element.expiration_date)
|
||||
diffdate = (enddate - startdate)
|
||||
default = {
|
||||
'date': fields.Date.context_today(self),
|
||||
'start_date': fields.Date.to_string(fields.Date.from_string(element.expiration_date) + relativedelta(days=1)),
|
||||
'expiration_date': fields.Date.to_string(enddate + diffdate),
|
||||
}
|
||||
newid = element.copy(default).id
|
||||
return {
|
||||
'name': _("Renew Contract"),
|
||||
'view_mode': 'form',
|
||||
'view_id': self.env.ref('fleet.fleet_vehicle_log_contract_view_form').id,
|
||||
'view_type': 'tree,form',
|
||||
'res_model': 'fleet.vehicle.log.contract',
|
||||
'type': 'ir.actions.act_window',
|
||||
'domain': '[]',
|
||||
'res_id': newid,
|
||||
'context': {'active_id': newid},
|
||||
}
|
||||
|
||||
@api.model
|
||||
def scheduler_manage_auto_costs(self):
|
||||
#This method is called by a cron task
|
||||
#It creates costs for contracts having the "recurring cost" field setted, depending on their frequency
|
||||
#For example, if a contract has a reccuring cost of 200 with a weekly frequency, this method creates a cost of 200 on the first day of each week, from the date of the last recurring costs in the database to today
|
||||
#If the contract has not yet any recurring costs in the database, the method generates the recurring costs from the start_date to today
|
||||
#The created costs are associated to a contract thanks to the many2one field contract_id
|
||||
#If the contract has no start_date, no cost will be created, even if the contract has recurring costs
|
||||
VehicleCost = self.env['fleet.vehicle.cost']
|
||||
deltas = {'yearly': relativedelta(years=+1), 'monthly': relativedelta(months=+1), 'weekly': relativedelta(weeks=+1), 'daily': relativedelta(days=+1)}
|
||||
contracts = self.env['fleet.vehicle.log.contract'].search([('state', '!=', 'closed')], offset=0, limit=None, order=None)
|
||||
for contract in contracts:
|
||||
if not contract.start_date or contract.cost_frequency == 'no':
|
||||
continue
|
||||
found = False
|
||||
last_cost_date = contract.start_date
|
||||
if contract.generated_cost_ids:
|
||||
last_autogenerated_cost = VehicleCost.search([('contract_id', '=', contract.id), ('auto_generated', '=', True)], offset=0, limit=1, order='date desc')
|
||||
if last_autogenerated_cost:
|
||||
found = True
|
||||
last_cost_date = last_autogenerated_cost.date
|
||||
startdate = fields.Date.from_string(last_cost_date)
|
||||
if found:
|
||||
startdate += deltas.get(contract.cost_frequency)
|
||||
today = fields.Date.from_string(fields.Date.context_today(self))
|
||||
while (startdate <= today) & (startdate <= fields.Date.from_string(contract.expiration_date)):
|
||||
data = {
|
||||
'amount': contract.cost_generated,
|
||||
'date': fields.Date.context_today(self),
|
||||
'vehicle_id': contract.vehicle_id.id,
|
||||
'cost_subtype_id': contract.cost_subtype_id.id,
|
||||
'contract_id': contract.id,
|
||||
'auto_generated': True
|
||||
}
|
||||
self.env['fleet.vehicle.cost'].create(data)
|
||||
startdate += deltas.get(contract.cost_frequency)
|
||||
return True
|
||||
|
||||
@api.model
|
||||
def scheduler_manage_contract_expiration(self):
|
||||
#This method is called by a cron task
|
||||
#It manages the state of a contract, possibly by posting a message on the vehicle concerned and updating its status
|
||||
date_today = fields.Date.from_string(fields.Date.context_today(self))
|
||||
limit_date = fields.Date.to_string(date_today + relativedelta(days=+15))
|
||||
contracts = self.search([('state', '=', 'open'), ('expiration_date', '<', limit_date)])
|
||||
res = {}
|
||||
for contract in contracts:
|
||||
if contract.vehicle_id.id in res:
|
||||
res[contract.vehicle_id.id] += 1
|
||||
else:
|
||||
res[contract.vehicle_id.id] = 1
|
||||
|
||||
Vehicle = self.env['fleet.vehicle']
|
||||
for vehicle, value in res.items():
|
||||
Vehicle.browse(vehicle).message_post(body=_('%s contract(s) need(s) to be renewed and/or closed!') % value)
|
||||
return contracts.write({'state': 'toclose'})
|
||||
|
||||
@api.model
|
||||
def run_scheduler(self):
|
||||
self.scheduler_manage_auto_costs()
|
||||
self.scheduler_manage_contract_expiration()
|
||||
@@ -0,0 +1,312 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
|
||||
|
||||
class FleetVehicle(models.Model):
|
||||
_inherit = 'mail.thread'
|
||||
_name = 'fleet.vehicle'
|
||||
_description = 'Information on a vehicle'
|
||||
_order = 'license_plate asc'
|
||||
|
||||
def _get_default_state(self):
|
||||
state = self.env.ref('fleet.vehicle_state_active', raise_if_not_found=False)
|
||||
return state and state.id or False
|
||||
|
||||
name = fields.Char(compute="_compute_vehicle_name", store=True)
|
||||
active = fields.Boolean(default=True)
|
||||
company_id = fields.Many2one('res.company', 'Company')
|
||||
license_plate = fields.Char(required=True, help='License plate number of the vehicle (i = plate number for a car)')
|
||||
vin_sn = fields.Char('Chassis Number', help='Unique number written on the vehicle motor (VIN/SN number)', copy=False)
|
||||
driver_id = fields.Many2one('res.partner', 'Driver', help='Driver of the vehicle')
|
||||
model_id = fields.Many2one('fleet.vehicle.model', 'Model', required=True, help='Model of the vehicle')
|
||||
log_fuel = fields.One2many('fleet.vehicle.log.fuel', 'vehicle_id', 'Fuel Logs')
|
||||
log_services = fields.One2many('fleet.vehicle.log.services', 'vehicle_id', 'Services Logs')
|
||||
log_contracts = fields.One2many('fleet.vehicle.log.contract', 'vehicle_id', 'Contracts')
|
||||
cost_count = fields.Integer(compute="_compute_count_all", string="Costs")
|
||||
contract_count = fields.Integer(compute="_compute_count_all", string='Contracts')
|
||||
service_count = fields.Integer(compute="_compute_count_all", string='Services')
|
||||
fuel_logs_count = fields.Integer(compute="_compute_count_all", string='Fuel Logs')
|
||||
odometer_count = fields.Integer(compute="_compute_count_all", string='Odometer')
|
||||
acquisition_date = fields.Date('Immatriculation Date', required=False, help='Date when the vehicle has been immatriculated')
|
||||
color = fields.Char(help='Color of the vehicle')
|
||||
state_id = fields.Many2one('fleet.vehicle.state', 'State', default=_get_default_state, help='Current state of the vehicle', ondelete="set null")
|
||||
location = fields.Char(help='Location of the vehicle (garage, ...)')
|
||||
seats = fields.Integer('Seats Number', help='Number of seats of the vehicle')
|
||||
doors = fields.Integer('Doors Number', help='Number of doors of the vehicle', default=5)
|
||||
tag_ids = fields.Many2many('fleet.vehicle.tag', 'fleet_vehicle_vehicle_tag_rel', 'vehicle_tag_id', 'tag_id', 'Tags', copy=False)
|
||||
odometer = fields.Float(compute='_get_odometer', inverse='_set_odometer', string='Last Odometer',
|
||||
help='Odometer measure of the vehicle at the moment of this log')
|
||||
odometer_unit = fields.Selection([
|
||||
('kilometers', 'Kilometers'),
|
||||
('miles', 'Miles')
|
||||
], 'Odometer Unit', default='kilometers', help='Unit of the odometer ', required=True)
|
||||
transmission = fields.Selection([('manual', 'Manual'), ('automatic', 'Automatic')], 'Transmission', help='Transmission Used by the vehicle')
|
||||
fuel_type = fields.Selection([
|
||||
('gasoline', 'Gasoline'),
|
||||
('diesel', 'Diesel'),
|
||||
('electric', 'Electric'),
|
||||
('hybrid', 'Hybrid')
|
||||
], 'Fuel Type', help='Fuel Used by the vehicle')
|
||||
horsepower = fields.Integer()
|
||||
horsepower_tax = fields.Float('Horsepower Taxation')
|
||||
power = fields.Integer('Power', help='Power in kW of the vehicle')
|
||||
co2 = fields.Float('CO2 Emissions', help='CO2 emissions of the vehicle')
|
||||
image = fields.Binary(related='model_id.image', string="Logo")
|
||||
image_medium = fields.Binary(related='model_id.image_medium', string="Logo (medium)")
|
||||
image_small = fields.Binary(related='model_id.image_small', string="Logo (small)")
|
||||
contract_renewal_due_soon = fields.Boolean(compute='_compute_contract_reminder', search='_search_contract_renewal_due_soon',
|
||||
string='Has Contracts to renew', multi='contract_info')
|
||||
contract_renewal_overdue = fields.Boolean(compute='_compute_contract_reminder', search='_search_get_overdue_contract_reminder',
|
||||
string='Has Contracts Overdue', multi='contract_info')
|
||||
contract_renewal_name = fields.Text(compute='_compute_contract_reminder', string='Name of contract to renew soon', multi='contract_info')
|
||||
contract_renewal_total = fields.Text(compute='_compute_contract_reminder', string='Total of contracts due or overdue minus one',
|
||||
multi='contract_info')
|
||||
car_value = fields.Float(string="Catalog Value (VAT incl.)", help='Value of the bought vehicle')
|
||||
residual_value = fields.Float()
|
||||
|
||||
_sql_constraints = [
|
||||
('driver_id_unique', 'UNIQUE(driver_id)', 'Only one car can be assigned to the same employee!')
|
||||
]
|
||||
|
||||
@api.depends('model_id', 'license_plate')
|
||||
def _compute_vehicle_name(self):
|
||||
for record in self:
|
||||
record.name = record.model_id.brand_id.name + '/' + record.model_id.name + '/' + record.license_plate
|
||||
|
||||
def _get_odometer(self):
|
||||
FleetVehicalOdometer = self.env['fleet.vehicle.odometer']
|
||||
for record in self:
|
||||
vehicle_odometer = FleetVehicalOdometer.search([('vehicle_id', '=', record.id)], limit=1, order='value desc')
|
||||
if vehicle_odometer:
|
||||
record.odometer = vehicle_odometer.value
|
||||
else:
|
||||
record.odometer = 0
|
||||
|
||||
def _set_odometer(self):
|
||||
for record in self:
|
||||
if record.odometer:
|
||||
date = fields.Date.context_today(record)
|
||||
data = {'value': record.odometer, 'date': date, 'vehicle_id': record.id}
|
||||
self.env['fleet.vehicle.odometer'].create(data)
|
||||
|
||||
def _compute_count_all(self):
|
||||
Odometer = self.env['fleet.vehicle.odometer']
|
||||
LogFuel = self.env['fleet.vehicle.log.fuel']
|
||||
LogService = self.env['fleet.vehicle.log.services']
|
||||
LogContract = self.env['fleet.vehicle.log.contract']
|
||||
Cost = self.env['fleet.vehicle.cost']
|
||||
for record in self:
|
||||
record.odometer_count = Odometer.search_count([('vehicle_id', '=', record.id)])
|
||||
record.fuel_logs_count = LogFuel.search_count([('vehicle_id', '=', record.id)])
|
||||
record.service_count = LogService.search_count([('vehicle_id', '=', record.id)])
|
||||
record.contract_count = LogContract.search_count([('vehicle_id', '=', record.id), ('state', '=', 'open')])
|
||||
record.cost_count = Cost.search_count([('vehicle_id', '=', record.id), ('parent_id', '=', False)])
|
||||
|
||||
@api.depends('log_contracts')
|
||||
def _compute_contract_reminder(self):
|
||||
for record in self:
|
||||
overdue = False
|
||||
due_soon = False
|
||||
total = 0
|
||||
name = ''
|
||||
for element in record.log_contracts:
|
||||
if element.state in ('open', 'toclose') and element.expiration_date:
|
||||
current_date_str = fields.Date.context_today(record)
|
||||
due_time_str = element.expiration_date
|
||||
current_date = fields.Date.from_string(current_date_str)
|
||||
due_time = fields.Date.from_string(due_time_str)
|
||||
diff_time = (due_time - current_date).days
|
||||
if diff_time < 0:
|
||||
overdue = True
|
||||
total += 1
|
||||
if diff_time < 15 and diff_time >= 0:
|
||||
due_soon = True
|
||||
total += 1
|
||||
if overdue or due_soon:
|
||||
log_contract = self.env['fleet.vehicle.log.contract'].search([
|
||||
('vehicle_id', '=', record.id),
|
||||
('state', 'in', ('open', 'toclose'))
|
||||
], limit=1, order='expiration_date asc')
|
||||
if log_contract:
|
||||
# we display only the name of the oldest overdue/due soon contract
|
||||
name = log_contract.cost_subtype_id.name
|
||||
|
||||
record.contract_renewal_overdue = overdue
|
||||
record.contract_renewal_due_soon = due_soon
|
||||
record.contract_renewal_total = total - 1 # we remove 1 from the real total for display purposes
|
||||
record.contract_renewal_name = name
|
||||
|
||||
def _search_contract_renewal_due_soon(self, operator, value):
|
||||
res = []
|
||||
assert operator in ('=', '!=', '<>') and value in (True, False), 'Operation not supported'
|
||||
if (operator == '=' and value is True) or (operator in ('<>', '!=') and value is False):
|
||||
search_operator = 'in'
|
||||
else:
|
||||
search_operator = 'not in'
|
||||
today = fields.Date.context_today(self)
|
||||
datetime_today = fields.Datetime.from_string(today)
|
||||
limit_date = fields.Datetime.to_string(datetime_today + relativedelta(days=+15))
|
||||
self.env.cr.execute("""SELECT cost.vehicle_id,
|
||||
count(contract.id) AS contract_number
|
||||
FROM fleet_vehicle_cost cost
|
||||
LEFT JOIN fleet_vehicle_log_contract contract ON contract.cost_id = cost.id
|
||||
WHERE contract.expiration_date IS NOT NULL
|
||||
AND contract.expiration_date > %s
|
||||
AND contract.expiration_date < %s
|
||||
AND contract.state IN ('open', 'toclose')
|
||||
GROUP BY cost.vehicle_id""", (today, limit_date))
|
||||
res_ids = [x[0] for x in self.env.cr.fetchall()]
|
||||
res.append(('id', search_operator, res_ids))
|
||||
return res
|
||||
|
||||
def _search_get_overdue_contract_reminder(self, operator, value):
|
||||
res = []
|
||||
assert operator in ('=', '!=', '<>') and value in (True, False), 'Operation not supported'
|
||||
if (operator == '=' and value is True) or (operator in ('<>', '!=') and value is False):
|
||||
search_operator = 'in'
|
||||
else:
|
||||
search_operator = 'not in'
|
||||
today = fields.Date.context_today(self)
|
||||
self.env.cr.execute('''SELECT cost.vehicle_id,
|
||||
count(contract.id) AS contract_number
|
||||
FROM fleet_vehicle_cost cost
|
||||
LEFT JOIN fleet_vehicle_log_contract contract ON contract.cost_id = cost.id
|
||||
WHERE contract.expiration_date IS NOT NULL
|
||||
AND contract.expiration_date < %s
|
||||
AND contract.state IN ('open', 'toclose')
|
||||
GROUP BY cost.vehicle_id ''', (today,))
|
||||
res_ids = [x[0] for x in self.env.cr.fetchall()]
|
||||
res.append(('id', search_operator, res_ids))
|
||||
return res
|
||||
|
||||
@api.onchange('model_id')
|
||||
def _onchange_model(self):
|
||||
if self.model_id:
|
||||
self.image_medium = self.model_id.image
|
||||
else:
|
||||
self.image_medium = False
|
||||
|
||||
@api.model
|
||||
def create(self, data):
|
||||
vehicle = super(FleetVehicle, self.with_context(mail_create_nolog=True)).create(data)
|
||||
vehicle.message_post(body=_('%s %s has been added to the fleet!') % (vehicle.model_id.name, vehicle.license_plate))
|
||||
return vehicle
|
||||
|
||||
@api.multi
|
||||
def write(self, vals):
|
||||
"""
|
||||
This function write an entry in the openchatter whenever we change important information
|
||||
on the vehicle like the model, the drive, the state of the vehicle or its license plate
|
||||
"""
|
||||
for vehicle in self:
|
||||
changes = []
|
||||
if 'model_id' in vals and vehicle.model_id.id != vals['model_id']:
|
||||
value = self.env['fleet.vehicle.model'].browse(vals['model_id']).name
|
||||
oldmodel = vehicle.model_id.name or _('None')
|
||||
changes.append(_("Model: from '%s' to '%s'") % (oldmodel, value))
|
||||
if 'driver_id' in vals and vehicle.driver_id.id != vals['driver_id']:
|
||||
value = self.env['res.partner'].browse(vals['driver_id']).name
|
||||
olddriver = (vehicle.driver_id.name) or _('None')
|
||||
changes.append(_("Driver: from '%s' to '%s'") % (olddriver, value))
|
||||
if 'state_id' in vals and vehicle.state_id.id != vals['state_id']:
|
||||
value = self.env['fleet.vehicle.state'].browse(vals['state_id']).name
|
||||
oldstate = vehicle.state_id.name or _('None')
|
||||
changes.append(_("State: from '%s' to '%s'") % (oldstate, value))
|
||||
if 'license_plate' in vals and vehicle.license_plate != vals['license_plate']:
|
||||
old_license_plate = vehicle.license_plate or _('None')
|
||||
changes.append(_("License Plate: from '%s' to '%s'") % (old_license_plate, vals['license_plate']))
|
||||
|
||||
if len(changes) > 0:
|
||||
self.message_post(body=", ".join(changes))
|
||||
|
||||
return super(FleetVehicle, self).write(vals)
|
||||
|
||||
@api.multi
|
||||
def return_action_to_open(self):
|
||||
""" This opens the xml view specified in xml_id for the current vehicle """
|
||||
self.ensure_one()
|
||||
xml_id = self.env.context.get('xml_id')
|
||||
if xml_id:
|
||||
res = self.env['ir.actions.act_window'].for_xml_id('fleet', xml_id)
|
||||
res.update(
|
||||
context=dict(self.env.context, default_vehicle_id=self.id, group_by=False),
|
||||
domain=[('vehicle_id', '=', self.id)]
|
||||
)
|
||||
return res
|
||||
return False
|
||||
|
||||
@api.multi
|
||||
def act_show_log_cost(self):
|
||||
""" This opens log view to view and add new log for this vehicle, groupby default to only show effective costs
|
||||
@return: the costs log view
|
||||
"""
|
||||
self.ensure_one()
|
||||
res = self.env['ir.actions.act_window'].for_xml_id('fleet', 'fleet_vehicle_costs_action')
|
||||
res.update(
|
||||
context=dict(self.env.context, default_vehicle_id=self.id, search_default_parent_false=True),
|
||||
domain=[('vehicle_id', '=', self.id)]
|
||||
)
|
||||
return res
|
||||
|
||||
|
||||
class FleetVehicleOdometer(models.Model):
|
||||
_name = 'fleet.vehicle.odometer'
|
||||
_description = 'Odometer log for a vehicle'
|
||||
_order = 'date desc'
|
||||
|
||||
name = fields.Char(compute='_compute_vehicle_log_name', store=True)
|
||||
date = fields.Date(default=fields.Date.context_today)
|
||||
value = fields.Float('Odometer Value', group_operator="max")
|
||||
vehicle_id = fields.Many2one('fleet.vehicle', 'Vehicle', required=True)
|
||||
unit = fields.Selection(related='vehicle_id.odometer_unit', string="Unit", readonly=True)
|
||||
driver_id = fields.Many2one(related="vehicle_id.driver_id", string="Driver")
|
||||
|
||||
@api.depends('vehicle_id', 'date')
|
||||
def _compute_vehicle_log_name(self):
|
||||
for record in self:
|
||||
name = record.vehicle_id.name
|
||||
if not name:
|
||||
name = record.date
|
||||
elif record.date:
|
||||
name += ' / ' + record.date
|
||||
self.name = name
|
||||
|
||||
@api.onchange('vehicle_id')
|
||||
def _onchange_vehicle(self):
|
||||
if self.vehicle_id:
|
||||
self.unit = self.vehicle_id.odometer_unit
|
||||
|
||||
|
||||
class FleetVehicleState(models.Model):
|
||||
_name = 'fleet.vehicle.state'
|
||||
_order = 'sequence asc'
|
||||
|
||||
name = fields.Char(required=True)
|
||||
sequence = fields.Integer(help="Used to order the note stages")
|
||||
|
||||
_sql_constraints = [('fleet_state_name_unique', 'unique(name)', 'State name already exists')]
|
||||
|
||||
|
||||
class FleetVehicleTag(models.Model):
|
||||
_name = 'fleet.vehicle.tag'
|
||||
|
||||
name = fields.Char(required=True, translate=True)
|
||||
color = fields.Integer('Color Index', default=10)
|
||||
|
||||
_sql_constraints = [('name_uniq', 'unique (name)', "Tag name already exists !")]
|
||||
|
||||
|
||||
class FleetServiceType(models.Model):
|
||||
_name = 'fleet.service.type'
|
||||
_description = 'Type of services available on a vehicle'
|
||||
|
||||
name = fields.Char(required=True, translate=True)
|
||||
category = fields.Selection([
|
||||
('contract', 'Contract'),
|
||||
('service', 'Service'),
|
||||
('both', 'Both')
|
||||
], 'Category', required=True, help='Choose wheter the service refer to contracts, vehicle services or both')
|
||||
@@ -0,0 +1,358 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
|
||||
class FleetVehicleCost(models.Model):
|
||||
_name = 'fleet.vehicle.cost'
|
||||
_description = 'Cost related to a vehicle'
|
||||
_order = 'date desc, vehicle_id asc'
|
||||
|
||||
name = fields.Char(related='vehicle_id.name', string='Name', store=True)
|
||||
vehicle_id = fields.Many2one('fleet.vehicle', 'Vehicle', required=True, help='Vehicle concerned by this log')
|
||||
cost_subtype_id = fields.Many2one('fleet.service.type', 'Type', help='Cost type purchased with this cost')
|
||||
amount = fields.Float('Total Price')
|
||||
cost_type = fields.Selection([
|
||||
('contract', 'Contract'),
|
||||
('services', 'Services'),
|
||||
('fuel', 'Fuel'),
|
||||
('other', 'Other')
|
||||
], 'Category of the cost', default="other", help='For internal purpose only', required=True)
|
||||
parent_id = fields.Many2one('fleet.vehicle.cost', 'Parent', help='Parent cost to this current cost')
|
||||
cost_ids = fields.One2many('fleet.vehicle.cost', 'parent_id', 'Included Services', copy=True)
|
||||
odometer_id = fields.Many2one('fleet.vehicle.odometer', 'Odometer', help='Odometer measure of the vehicle at the moment of this log')
|
||||
odometer = fields.Float(compute="_get_odometer", inverse='_set_odometer', string='Odometer Value',
|
||||
help='Odometer measure of the vehicle at the moment of this log')
|
||||
odometer_unit = fields.Selection(related='vehicle_id.odometer_unit', string="Unit", readonly=True)
|
||||
date = fields.Date(help='Date when the cost has been executed')
|
||||
contract_id = fields.Many2one('fleet.vehicle.log.contract', 'Contract', help='Contract attached to this cost')
|
||||
auto_generated = fields.Boolean('Automatically Generated', readonly=True)
|
||||
description = fields.Char("Cost Description")
|
||||
|
||||
def _get_odometer(self):
|
||||
for record in self:
|
||||
if record.odometer_id:
|
||||
record.odometer = record.odometer_id.value
|
||||
|
||||
def _set_odometer(self):
|
||||
for record in self:
|
||||
if not record.odometer:
|
||||
raise UserError(_('Emptying the odometer value of a vehicle is not allowed.'))
|
||||
odometer = self.env['fleet.vehicle.odometer'].create({
|
||||
'value': record.odometer,
|
||||
'date': record.date or fields.Date.context_today(record),
|
||||
'vehicle_id': record.vehicle_id.id
|
||||
})
|
||||
self.odometer_id = odometer
|
||||
|
||||
@api.model
|
||||
def create(self, data):
|
||||
# make sure that the data are consistent with values of parent and contract records given
|
||||
if 'parent_id' in data and data['parent_id']:
|
||||
parent = self.browse(data['parent_id'])
|
||||
data['vehicle_id'] = parent.vehicle_id.id
|
||||
data['date'] = parent.date
|
||||
data['cost_type'] = parent.cost_type
|
||||
if 'contract_id' in data and data['contract_id']:
|
||||
contract = self.env['fleet.vehicle.log.contract'].browse(data['contract_id'])
|
||||
data['vehicle_id'] = contract.vehicle_id.id
|
||||
data['cost_subtype_id'] = contract.cost_subtype_id.id
|
||||
data['cost_type'] = contract.cost_type
|
||||
if 'odometer' in data and not data['odometer']:
|
||||
# if received value for odometer is 0, then remove it from the data as it would result to the creation of a
|
||||
# odometer log with 0, which is to be avoided
|
||||
del(data['odometer'])
|
||||
return super(FleetVehicleCost, self).create(data)
|
||||
|
||||
|
||||
class FleetVehicleLogContract(models.Model):
|
||||
|
||||
_inherits = {'fleet.vehicle.cost': 'cost_id'}
|
||||
_name = 'fleet.vehicle.log.contract'
|
||||
_description = 'Contract information on a vehicle'
|
||||
_order = 'state desc,expiration_date'
|
||||
|
||||
def compute_next_year_date(self, strdate):
|
||||
oneyear = relativedelta(years=1)
|
||||
start_date = fields.Date.from_string(strdate)
|
||||
return fields.Date.to_string(start_date + oneyear)
|
||||
|
||||
@api.model
|
||||
def default_get(self, default_fields):
|
||||
res = super(FleetVehicleLogContract, self).default_get(default_fields)
|
||||
contract = self.env.ref('fleet.type_contract_leasing', raise_if_not_found=False)
|
||||
res.update({
|
||||
'date': fields.Date.context_today(self),
|
||||
'cost_subtype_id': contract and contract.id or False,
|
||||
'cost_type': 'contract'
|
||||
})
|
||||
return res
|
||||
|
||||
name = fields.Text(compute='_compute_contract_name', store=True)
|
||||
active = fields.Boolean(default=True)
|
||||
start_date = fields.Date('Contract Start Date', default=fields.Date.context_today, help='Date when the coverage of the contract begins')
|
||||
expiration_date = fields.Date('Contract Expiration Date', default=lambda self: self.compute_next_year_date(fields.Date.context_today(self)),
|
||||
help='Date when the coverage of the contract expirates (by default, one year after begin date)')
|
||||
days_left = fields.Integer(compute='_compute_days_left', string='Warning Date')
|
||||
insurer_id = fields.Many2one('res.partner', 'Vendor')
|
||||
purchaser_id = fields.Many2one('res.partner', 'Contractor', default=lambda self: self.env.user.partner_id.id,
|
||||
help='Person to which the contract is signed for')
|
||||
ins_ref = fields.Char('Contract Reference', size=64, copy=False)
|
||||
state = fields.Selection([
|
||||
('open', 'In Progress'),
|
||||
('toclose', 'To Close'),
|
||||
('closed', 'Terminated')
|
||||
], 'Status', default='open', readonly=True, help='Choose wheter the contract is still valid or not',
|
||||
copy=False)
|
||||
notes = fields.Text('Terms and Conditions', help='Write here all supplementary information relative to this contract', copy=False)
|
||||
cost_generated = fields.Float('Recurring Cost Amount',
|
||||
help="Costs paid at regular intervals, depending on the cost frequency."
|
||||
"If the cost frequency is set to unique, the cost will be logged at the start date")
|
||||
cost_frequency = fields.Selection([
|
||||
('no', 'No'),
|
||||
('daily', 'Daily'),
|
||||
('weekly', 'Weekly'),
|
||||
('monthly', 'Monthly'),
|
||||
('yearly', 'Yearly')
|
||||
], 'Recurring Cost Frequency', default='no', help='Frequency of the recuring cost', required=True)
|
||||
generated_cost_ids = fields.One2many('fleet.vehicle.cost', 'contract_id', 'Generated Costs')
|
||||
sum_cost = fields.Float(compute='_compute_sum_cost', string='Indicative Costs Total')
|
||||
cost_id = fields.Many2one('fleet.vehicle.cost', 'Cost', required=True, ondelete='cascade')
|
||||
# we need to keep this field as a related with store=True because the graph view doesn't support
|
||||
# (1) to address fields from inherited table
|
||||
# (2) fields that aren't stored in database
|
||||
cost_amount = fields.Float(related='cost_id.amount', string='Amount', store=True)
|
||||
odometer = fields.Float(string='Odometer at creation', help='Odometer measure of the vehicle at the moment of the contract creation')
|
||||
|
||||
@api.depends('vehicle_id', 'cost_subtype_id', 'date')
|
||||
def _compute_contract_name(self):
|
||||
for record in self:
|
||||
name = record.vehicle_id.name
|
||||
if record.cost_subtype_id.name:
|
||||
name += ' / ' + record.cost_subtype_id.name
|
||||
if record.date:
|
||||
name += ' / ' + record.date
|
||||
record.name = name
|
||||
|
||||
@api.depends('expiration_date', 'state')
|
||||
def _compute_days_left(self):
|
||||
"""return a dict with as value for each contract an integer
|
||||
if contract is in an open state and is overdue, return 0
|
||||
if contract is in a closed state, return -1
|
||||
otherwise return the number of days before the contract expires
|
||||
"""
|
||||
for record in self:
|
||||
if (record.expiration_date and (record.state == 'open' or record.state == 'toclose')):
|
||||
today = fields.Date.from_string(fields.Date.today())
|
||||
renew_date = fields.Date.from_string(record.expiration_date)
|
||||
diff_time = (renew_date - today).days
|
||||
record.days_left = diff_time > 0 and diff_time or 0
|
||||
else:
|
||||
record.days_left = -1
|
||||
|
||||
@api.depends('cost_ids.amount')
|
||||
def _compute_sum_cost(self):
|
||||
for contract in self:
|
||||
contract.sum_cost = sum(contract.cost_ids.mapped('amount'))
|
||||
|
||||
@api.onchange('vehicle_id')
|
||||
def _onchange_vehicle(self):
|
||||
if self.vehicle_id:
|
||||
self.odometer_unit = self.vehicle_id.odometer_unit
|
||||
|
||||
@api.multi
|
||||
def contract_close(self):
|
||||
for record in self:
|
||||
record.state = 'closed'
|
||||
|
||||
@api.multi
|
||||
def contract_open(self):
|
||||
for record in self:
|
||||
record.state = 'open'
|
||||
|
||||
@api.multi
|
||||
def act_renew_contract(self):
|
||||
assert len(self.ids) == 1, "This operation should only be done for 1 single contract at a time, as it it suppose to open a window as result"
|
||||
for element in self:
|
||||
# compute end date
|
||||
startdate = fields.Date.from_string(element.start_date)
|
||||
enddate = fields.Date.from_string(element.expiration_date)
|
||||
diffdate = (enddate - startdate)
|
||||
default = {
|
||||
'date': fields.Date.context_today(self),
|
||||
'start_date': fields.Date.to_string(fields.Date.from_string(element.expiration_date) + relativedelta(days=1)),
|
||||
'expiration_date': fields.Date.to_string(enddate + diffdate),
|
||||
}
|
||||
newid = element.copy(default).id
|
||||
return {
|
||||
'name': _("Renew Contract"),
|
||||
'view_mode': 'form',
|
||||
'view_id': self.env.ref('fleet.fleet_vehicle_log_contract_view_form').id,
|
||||
'view_type': 'tree,form',
|
||||
'res_model': 'fleet.vehicle.log.contract',
|
||||
'type': 'ir.actions.act_window',
|
||||
'domain': '[]',
|
||||
'res_id': newid,
|
||||
'context': {'active_id': newid},
|
||||
}
|
||||
|
||||
@api.model
|
||||
def scheduler_manage_auto_costs(self):
|
||||
# This method is called by a cron task
|
||||
# It creates costs for contracts having the "recurring cost" field setted, depending on their frequency
|
||||
# For example, if a contract has a reccuring cost of 200 with a weekly frequency, this method creates a cost of 200 on the
|
||||
# first day of each week, from the date of the last recurring costs in the database to today
|
||||
# If the contract has not yet any recurring costs in the database, the method generates the recurring costs from the start_date to today
|
||||
# The created costs are associated to a contract thanks to the many2one field contract_id
|
||||
# If the contract has no start_date, no cost will be created, even if the contract has recurring costs
|
||||
VehicleCost = self.env['fleet.vehicle.cost']
|
||||
deltas = {
|
||||
'yearly': relativedelta(years=+1),
|
||||
'monthly': relativedelta(months=+1),
|
||||
'weekly': relativedelta(weeks=+1),
|
||||
'daily': relativedelta(days=+1)
|
||||
}
|
||||
contracts = self.env['fleet.vehicle.log.contract'].search([('state', '!=', 'closed')], offset=0, limit=None, order=None)
|
||||
for contract in contracts:
|
||||
if not contract.start_date or contract.cost_frequency == 'no':
|
||||
continue
|
||||
found = False
|
||||
last_cost_date = contract.start_date
|
||||
if contract.generated_cost_ids:
|
||||
last_autogenerated_cost = VehicleCost.search([
|
||||
('contract_id', '=', contract.id),
|
||||
('auto_generated', '=', True)
|
||||
], offset=0, limit=1, order='date desc')
|
||||
if last_autogenerated_cost:
|
||||
found = True
|
||||
last_cost_date = last_autogenerated_cost.date
|
||||
startdate = fields.Date.from_string(last_cost_date)
|
||||
if found:
|
||||
startdate += deltas.get(contract.cost_frequency)
|
||||
today = fields.Date.from_string(fields.Date.context_today(self))
|
||||
while (startdate <= today) & (startdate <= fields.Date.from_string(contract.expiration_date)):
|
||||
data = {
|
||||
'amount': contract.cost_generated,
|
||||
'date': fields.Date.context_today(self),
|
||||
'vehicle_id': contract.vehicle_id.id,
|
||||
'cost_subtype_id': contract.cost_subtype_id.id,
|
||||
'contract_id': contract.id,
|
||||
'auto_generated': True
|
||||
}
|
||||
self.env['fleet.vehicle.cost'].create(data)
|
||||
startdate += deltas.get(contract.cost_frequency)
|
||||
return True
|
||||
|
||||
@api.model
|
||||
def scheduler_manage_contract_expiration(self):
|
||||
# This method is called by a cron task
|
||||
# It manages the state of a contract, possibly by posting a message on the vehicle concerned and updating its status
|
||||
date_today = fields.Date.from_string(fields.Date.context_today(self))
|
||||
limit_date = fields.Date.to_string(date_today + relativedelta(days=+15))
|
||||
contracts = self.search([('state', '=', 'open'), ('expiration_date', '<', limit_date)])
|
||||
res = {}
|
||||
for contract in contracts:
|
||||
if contract.vehicle_id.id in res:
|
||||
res[contract.vehicle_id.id] += 1
|
||||
else:
|
||||
res[contract.vehicle_id.id] = 1
|
||||
|
||||
Vehicle = self.env['fleet.vehicle']
|
||||
for vehicle, value in res.items():
|
||||
Vehicle.browse(vehicle).message_post(body=_('%s contract(s) need(s) to be renewed and/or closed!') % value)
|
||||
return contracts.write({'state': 'toclose'})
|
||||
|
||||
@api.model
|
||||
def run_scheduler(self):
|
||||
self.scheduler_manage_auto_costs()
|
||||
self.scheduler_manage_contract_expiration()
|
||||
|
||||
|
||||
class FleetVehicleLogFuel(models.Model):
|
||||
_name = 'fleet.vehicle.log.fuel'
|
||||
_description = 'Fuel log for vehicles'
|
||||
_inherits = {'fleet.vehicle.cost': 'cost_id'}
|
||||
|
||||
@api.model
|
||||
def default_get(self, default_fields):
|
||||
res = super(FleetVehicleLogFuel, self).default_get(default_fields)
|
||||
service = self.env.ref('fleet.type_service_refueling', raise_if_not_found=False)
|
||||
res.update({
|
||||
'date': fields.Date.context_today(self),
|
||||
'cost_subtype_id': service and service.id or False,
|
||||
'cost_type': 'fuel'
|
||||
})
|
||||
return res
|
||||
|
||||
liter = fields.Float()
|
||||
price_per_liter = fields.Float()
|
||||
purchaser_id = fields.Many2one('res.partner', 'Purchaser', domain="['|',('customer','=',True),('employee','=',True)]")
|
||||
inv_ref = fields.Char('Invoice Reference', size=64)
|
||||
vendor_id = fields.Many2one('res.partner', 'Vendor', domain="[('supplier','=',True)]")
|
||||
notes = fields.Text()
|
||||
cost_id = fields.Many2one('fleet.vehicle.cost', 'Cost', required=True, ondelete='cascade')
|
||||
# we need to keep this field as a related with store=True because the graph view doesn't support
|
||||
# (1) to address fields from inherited table
|
||||
# (2) fields that aren't stored in database
|
||||
cost_amount = fields.Float(related='cost_id.amount', string='Amount', store=True)
|
||||
|
||||
@api.onchange('vehicle_id')
|
||||
def _onchange_vehicle(self):
|
||||
if self.vehicle_id:
|
||||
self.odometer_unit = self.vehicle_id.odometer_unit
|
||||
self.purchaser_id = self.vehicle_id.driver_id.id
|
||||
|
||||
@api.onchange('liter', 'price_per_liter', 'amount')
|
||||
def _onchange_liter_price_amount(self):
|
||||
# need to cast in float because the value receveid from web client maybe an integer (Javascript and JSON do not
|
||||
# make any difference between 3.0 and 3). This cause a problem if you encode, for example, 2 liters at 1.5 per
|
||||
# liter => total is computed as 3.0, then trigger an onchange that recomputes price_per_liter as 3/2=1 (instead
|
||||
# of 3.0/2=1.5)
|
||||
# If there is no change in the result, we return an empty dict to prevent an infinite loop due to the 3 intertwine
|
||||
# onchange. And in order to verify that there is no change in the result, we have to limit the precision of the
|
||||
# computation to 2 decimal
|
||||
liter = float(self.liter)
|
||||
price_per_liter = float(self.price_per_liter)
|
||||
amount = float(self.amount)
|
||||
if liter > 0 and price_per_liter > 0 and round(liter * price_per_liter, 2) != amount:
|
||||
self.amount = round(liter * price_per_liter, 2)
|
||||
elif amount > 0 and liter > 0 and round(amount / liter, 2) != price_per_liter:
|
||||
self.price_per_liter = round(amount / liter, 2)
|
||||
elif amount > 0 and price_per_liter > 0 and round(amount / price_per_liter, 2) != liter:
|
||||
self.liter = round(amount / price_per_liter, 2)
|
||||
|
||||
|
||||
class FleetVehicleLogServices(models.Model):
|
||||
_name = 'fleet.vehicle.log.services'
|
||||
_inherits = {'fleet.vehicle.cost': 'cost_id'}
|
||||
_description = 'Services for vehicles'
|
||||
|
||||
@api.model
|
||||
def default_get(self, default_fields):
|
||||
res = super(FleetVehicleLogServices, self).default_get(default_fields)
|
||||
service = self.env.ref('fleet.type_service_service_8', raise_if_not_found=False)
|
||||
res.update({
|
||||
'date': fields.Date.context_today(self),
|
||||
'cost_subtype_id': service and service.id or False,
|
||||
'cost_type': 'services'
|
||||
})
|
||||
return res
|
||||
|
||||
purchaser_id = fields.Many2one('res.partner', 'Purchaser', domain="['|',('customer','=',True),('employee','=',True)]")
|
||||
inv_ref = fields.Char('Invoice Reference')
|
||||
vendor_id = fields.Many2one('res.partner', 'Vendor', domain="[('supplier','=',True)]")
|
||||
# we need to keep this field as a related with store=True because the graph view doesn't support
|
||||
# (1) to address fields from inherited table and (2) fields that aren't stored in database
|
||||
cost_amount = fields.Float(related='cost_id.amount', string='Amount', store=True)
|
||||
notes = fields.Text()
|
||||
cost_id = fields.Many2one('fleet.vehicle.cost', 'Cost', required=True, ondelete='cascade')
|
||||
|
||||
@api.onchange('vehicle_id')
|
||||
def _onchange_vehicle(self):
|
||||
if self.vehicle_id:
|
||||
self.odometer_unit = self.vehicle_id.odometer_unit
|
||||
self.purchaser_id = self.vehicle_id.driver_id.id
|
||||
@@ -0,0 +1,63 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models, tools
|
||||
|
||||
|
||||
class FleetVehicleModel(models.Model):
|
||||
_name = 'fleet.vehicle.model'
|
||||
_description = 'Model of a vehicle'
|
||||
_order = 'name asc'
|
||||
|
||||
name = fields.Char('Model name', required=True)
|
||||
brand_id = fields.Many2one('fleet.vehicle.model.brand', 'Make', required=True, help='Make of the vehicle')
|
||||
vendors = fields.Many2many('res.partner', 'fleet_vehicle_model_vendors', 'model_id', 'partner_id', string='Vendors')
|
||||
image = fields.Binary(related='brand_id.image', string="Logo")
|
||||
image_medium = fields.Binary(related='brand_id.image_medium', string="Logo (medium)")
|
||||
image_small = fields.Binary(related='brand_id.image_small', string="Logo (small)")
|
||||
|
||||
@api.multi
|
||||
@api.depends('name', 'brand_id')
|
||||
def name_get(self):
|
||||
res = []
|
||||
for record in self:
|
||||
name = record.name
|
||||
if record.brand_id.name:
|
||||
name = record.brand_id.name + '/' + name
|
||||
res.append((record.id, name))
|
||||
return res
|
||||
|
||||
@api.onchange('brand_id')
|
||||
def _onchange_brand(self):
|
||||
if self.brand_id:
|
||||
self.image_medium = self.brand_id.image
|
||||
else:
|
||||
self.image_medium = False
|
||||
|
||||
|
||||
class FleetVehicleModelBrand(models.Model):
|
||||
_name = 'fleet.vehicle.model.brand'
|
||||
_description = 'Brand model of the vehicle'
|
||||
_order = 'name asc'
|
||||
|
||||
name = fields.Char('Make', required=True)
|
||||
image = fields.Binary("Logo", attachment=True,
|
||||
help="This field holds the image used as logo for the brand, limited to 1024x1024px.")
|
||||
image_medium = fields.Binary("Medium-sized image", attachment=True,
|
||||
help="Medium-sized logo of the brand. It is automatically "
|
||||
"resized as a 128x128px image, with aspect ratio preserved. "
|
||||
"Use this field in form views or some kanban views.")
|
||||
image_small = fields.Binary("Small-sized image", attachment=True,
|
||||
help="Small-sized logo of the brand. It is automatically "
|
||||
"resized as a 64x64px image, with aspect ratio preserved. "
|
||||
"Use this field anywhere a small image is required.")
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
tools.image_resize_images(vals)
|
||||
return super(FleetVehicleModelBrand, self).create(vals)
|
||||
|
||||
@api.multi
|
||||
def write(self, vals):
|
||||
tools.image_resize_images(vals)
|
||||
return super(FleetVehicleModelBrand, self).write(vals)
|
||||
@@ -0,0 +1,512 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id='fleet_vehicle_cost_view_tree' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.cost.tree</field>
|
||||
<field name="model">fleet.vehicle.cost</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Vehicle Costs">
|
||||
<field name="vehicle_id"/>
|
||||
<field name="description"/>
|
||||
<field name="cost_subtype_id"/>
|
||||
<field name="date"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='fleet_vehicle_cost_view_pivot' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.cost.pivot</field>
|
||||
<field name="model">fleet.vehicle.cost</field>
|
||||
<field name="arch" type="xml">
|
||||
<pivot string="Vehicles costs">
|
||||
<field name="vehicle_id" type="row"/>
|
||||
<field name="cost_type" type="row"/>
|
||||
<field name="date" interval="year" type="col"/>
|
||||
<field name="amount" type="measure"/>
|
||||
</pivot>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='fleet_vehicle_cost_view_graph' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.cost.graph</field>
|
||||
<field name="model">fleet.vehicle.cost</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Vehicles costs">
|
||||
<field name="vehicle_id"/>
|
||||
<field name="date" interval="year"/>
|
||||
<field name="amount" type="measure"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='fleet_vehicle_cost_indicative_view_pivot' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.cost.pivot</field>
|
||||
<field name="model">fleet.vehicle.cost</field>
|
||||
<field name="arch" type="xml">
|
||||
<pivot string="Vehicles costs" >
|
||||
<field name="vehicle_id" type="row"/>
|
||||
<field name="cost_type" type="col"/>
|
||||
<field name="amount" type="measure"/>
|
||||
</pivot>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='fleet_vehicle_cost_indicative_view_graph' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.cost.graph</field>
|
||||
<field name="model">fleet.vehicle.cost</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Vehicles costs">
|
||||
<field name="parent_id" type="row"/>
|
||||
<field name="cost_type" type="col"/>
|
||||
<field name="amount" type="measure"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='fleet_vehicle_costs_view_search' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.cost.search</field>
|
||||
<field name="model">fleet.vehicle.cost</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Vehicles costs" >
|
||||
<field name="vehicle_id" />
|
||||
<field name="cost_subtype_id"/>
|
||||
<field name="date"/>
|
||||
<field name="parent_id"/>
|
||||
<filter name="parent_false" domain="[('parent_id','=',False)]" string="Effective Costs"/>
|
||||
<filter name="parent_true" domain="[('parent_id','!=',False)]" string="Indicative Costs"/>
|
||||
<group expand="1" string="Group By">
|
||||
<filter name="groupby_cost_type" context="{'group_by': 'cost_type'}" string="Cost Type"/>
|
||||
<filter name="groupby_cost_subtype_id" context="{'group_by': 'cost_subtype_id'}" string="Cost Subtype"/>
|
||||
<filter name="groupby_vehicle_id" context="{'group_by': 'vehicle_id'}" string="Vehicle"/>
|
||||
<filter name="groupby_parent_id" context="{'group_by': 'parent_id'}" string="Parent"/>
|
||||
<separator/>
|
||||
<filter name="groupby_date" context="{'group_by': 'date:month'}" string="Month" help="Vehicle Costs by Month"/>
|
||||
<filter name="groupby_year" context="{'group_by': 'date:year'}" string="Year"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='fleet_vehicle_costs_view_form' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.cost.form</field>
|
||||
<field name="model">fleet.vehicle.cost</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Vehicle costs">
|
||||
<sheet>
|
||||
<group col="2" string="Cost Details">
|
||||
<group>
|
||||
<field name="vehicle_id" />
|
||||
<field name="cost_subtype_id"/>
|
||||
<field name="amount"/>
|
||||
<field name="description"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="date"/>
|
||||
<field name="parent_id" groups="base.group_no_one"/>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="fleet_vehicle_costs_view_graph" model="ir.ui.view">
|
||||
<field name="name">fleet.vehicle.cost.graph</field>
|
||||
<field name="model">fleet.vehicle.cost</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Costs Per Month" stacked="True">
|
||||
<field name="date" type="row"/>
|
||||
<field name="vehicle_id" type="col"/>
|
||||
<field name="amount" type="measure"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='fleet_vehicle_costs_action' model='ir.actions.act_window'>
|
||||
<field name="name">Vehicle Costs</field>
|
||||
<field name="res_model">fleet.vehicle.cost</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form,graph</field>
|
||||
<field name="context">{"search_default_parent_false" : True, "search_default_groupby_vehicle_id" : True,}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to create a new cost.
|
||||
</p><p>
|
||||
Odoo helps you managing the costs for your different
|
||||
vehicles. Costs are created automatically from services,
|
||||
contracts (fixed or recurring) and fuel logs.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem action="fleet_vehicle_costs_action" parent="fleet_vehicles" id="fleet_vehicle_costs_menu" groups="fleet_group_manager"/>
|
||||
|
||||
|
||||
<record id='fleet_vehicle_log_contract_view_form' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.log_contract.form</field>
|
||||
<field name="model">fleet.vehicle.log.contract</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Contract logs">
|
||||
<header>
|
||||
<button modifiers="{'invisible': [['state', '=', 'closed']]}" name="contract_close" states="open" type="object" class="oe_highlight" string="Terminate Contract" groups="fleet.fleet_group_manager"/>
|
||||
<button modifiers="{'invisible': [['state', '=', 'closed']]}" name="contract_close" states="toclose" type="object" class="oe_highlight" string="Terminate Contract" groups="fleet.fleet_group_manager"/>
|
||||
<button modifiers="{'invisible': [['state', 'not in', ['closed']]]}" name="contract_open" states="closed" type="object" class="oe_highlight" string="Set Contract In Progress" groups="fleet.fleet_group_manager"/>
|
||||
<button class="oe_highlight" name="act_renew_contract" type="object" string="Renew Contract" help="Create a new contract automatically with all the same informations except for the date that will start at the end of current contract" groups="fleet.fleet_group_manager"/>
|
||||
<field name="state" widget="statusbar" />
|
||||
</header>
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button name="toggle_active" type="object" class="oe_stat_button" icon="fa-archive">
|
||||
<field name="active" widget="boolean_button" options='{"terminology": "archive"}'/>
|
||||
</button>
|
||||
</div>
|
||||
<group col="2">
|
||||
<group string="Contract details">
|
||||
<field name="vehicle_id"/>
|
||||
<field name="cost_subtype_id" required="1" domain="['|',('category','=','contract'),('category','=','both')]"/>
|
||||
<field name="amount" string="Activation Cost" help="Cost that is paid only once at the creation of the contract"/>
|
||||
<label for="cost_generated"/>
|
||||
<div>
|
||||
<field name="cost_generated" class="oe_inline" attrs="{'invisible': [('cost_frequency','=','no')]}" />
|
||||
<field name="cost_frequency" class="oe_inline" />
|
||||
</div>
|
||||
</group>
|
||||
<group string="Odometer details">
|
||||
<label for="odometer"/>
|
||||
<div class="o_row">
|
||||
<field name="odometer"/>
|
||||
<field name="odometer_unit"/>
|
||||
</div>
|
||||
</group>
|
||||
</group>
|
||||
<group col="2">
|
||||
<group>
|
||||
<field name="date" string="Invoice Date"/>
|
||||
<field name="start_date"/>
|
||||
<field name="expiration_date" />
|
||||
</group>
|
||||
<group>
|
||||
<field name="insurer_id" context="{'default_supplier': True}"/>
|
||||
<field name="purchaser_id" />
|
||||
<field name="ins_ref" />
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Included Services">
|
||||
<group>
|
||||
<field name="cost_ids" context="{'vehicle_id': vehicle_id}" nolabel="1">
|
||||
<tree editable="bottom">
|
||||
<field name="cost_subtype_id" string="Service" domain="[('category','=','service')]"/>
|
||||
<field name="description"/>
|
||||
<field name="amount" sum="Price" string="Indicative Cost" />
|
||||
</tree>
|
||||
</field>
|
||||
|
||||
</group>
|
||||
<div class="oe_right">
|
||||
<group>
|
||||
<field name="sum_cost" string="Indicative Costs Total"/>
|
||||
</group>
|
||||
</div>
|
||||
</page>
|
||||
<page string="Generated Recurring Costs">
|
||||
<group>
|
||||
<field name="generated_cost_ids" context="{'vehicle_id': vehicle_id}" nolabel="1" sum="amount">
|
||||
<tree editable="bottom" >
|
||||
<field name="date" />
|
||||
<field name="amount" sum="amount"/>
|
||||
</tree>
|
||||
|
||||
</field>
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
<group string="Terms and Conditions">
|
||||
<field name="notes" nolabel="1" placeholder="Write here all other information relative to this contract" />
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<act_window
|
||||
id="act_renew_contract"
|
||||
name="Renew Contract"
|
||||
res_model="fleet.vehicle.log.contract"
|
||||
src_model="fleet.vehicle.log.contract"
|
||||
view_mode="form"
|
||||
view_type="form"
|
||||
/>
|
||||
|
||||
|
||||
<record id='fleet_vehicle_log_contract_view_tree' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.log.contract.tree</field>
|
||||
<field name="model">fleet.vehicle.log.contract</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Contract logs" decoration-warning="days_left>0 and days_left<15" decoration-danger="days_left==0" decoration-muted="state=='closed'">
|
||||
<field name="active" invisible="1"/>
|
||||
<field name="start_date" />
|
||||
<field name="expiration_date" />
|
||||
<field name="days_left" invisible="1"/>
|
||||
<field name="vehicle_id" />
|
||||
<field name="cost_subtype_id"/>
|
||||
<field name="insurer_id" />
|
||||
<field name="amount" string="Activation Cost"/>
|
||||
<field name="cost_generated"/>
|
||||
<field name="cost_frequency"/>
|
||||
<field name="state" />
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="fleet_vehicle_log_contract_view_graph" model="ir.ui.view">
|
||||
<field name="name">fleet.vehicle.log.contract.graph</field>
|
||||
<field name="model">fleet.vehicle.log.contract</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Contract Costs Per Month" type="bar" stacked="True">
|
||||
<field name="date" type="row"/>
|
||||
<field name="vehicle_id" type="row"/>
|
||||
<field name="cost_amount" type="measure"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="fleet_vehicle_log_contract_view_search" model="ir.ui.view">
|
||||
<field name="name">fleet.vehicle.log.contract.search</field>
|
||||
<field name="model">fleet.vehicle.log.contract</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Vehicles Contracts">
|
||||
<filter string="Archived" name="inactive" domain="[('active','=',False)]"/>
|
||||
<filter string="In Progress" name="open" domain="[('state', '=', 'open')]"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='fleet_vehicle_log_contract_action' model='ir.actions.act_window'>
|
||||
<field name="name">Vehicles Contracts</field>
|
||||
<field name="res_model">fleet.vehicle.log.contract</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form,graph</field>
|
||||
<field name="context">{'search_default_open': 1}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to create a new contract.
|
||||
</p><p>
|
||||
Manage all your contracts (leasing, insurances, etc.) with
|
||||
their related services, costs. Odoo will automatically warn
|
||||
you when some contracts have to be renewed.
|
||||
</p><p>
|
||||
Each contract (e.g.: leasing) may include several services
|
||||
(reparation, insurances, periodic maintenance).
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem action="fleet_vehicle_log_contract_action" parent="fleet_vehicles" id="fleet_vehicle_log_contract_menu" groups="fleet_group_user"/>
|
||||
|
||||
<record id='fleet_vehicle_log_fuel_view_form' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.log.fuel.form</field>
|
||||
<field name="model">fleet.vehicle.log.fuel</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Fuel Logs">
|
||||
<sheet>
|
||||
<group>
|
||||
<group string="Vehicle Details">
|
||||
<field name="vehicle_id"/>
|
||||
</group>
|
||||
<group string="Refueling Details">
|
||||
<field name="liter"/>
|
||||
<field name="price_per_liter"/>
|
||||
<field name="amount"/>
|
||||
</group>
|
||||
</group>
|
||||
<group>
|
||||
<group string="Odometer Details">
|
||||
<label for="odometer"/>
|
||||
<div class="o_row">
|
||||
<field name="odometer"/>
|
||||
<field name="odometer_unit"/>
|
||||
</div>
|
||||
</group>
|
||||
<group string="Additional Details">
|
||||
<field name="date" />
|
||||
<field name="purchaser_id" />
|
||||
<field name="inv_ref" />
|
||||
<field name="vendor_id" context="{'default_supplier': True}"/>
|
||||
</group>
|
||||
</group>
|
||||
<field name="notes" placeholder="Write here any other information"/>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='fleet_vehicle_log_fuel_view_tree' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.log.fuel.tree</field>
|
||||
<field name="model">fleet.vehicle.log.fuel</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Fuel Logs">
|
||||
<field name="date" />
|
||||
<field name="vehicle_id" />
|
||||
<field name="odometer" invisible="1"/>
|
||||
<field name="odometer_unit" invisible="1"/>
|
||||
<field name="purchaser_id" />
|
||||
<field name="inv_ref" invisible="1"/>
|
||||
<field name="vendor_id" invisible="1"/>
|
||||
<field name="liter" />
|
||||
<field name="price_per_liter" invisible="1"/>
|
||||
<field name="amount" sum="Price"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='fleet_vehicle_log_fuel_view_search' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.log.fuel.search</field>
|
||||
<field name="model">fleet.vehicle.log.fuel</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Vehicles Fuel Logs" >
|
||||
<field name="vehicle_id" />
|
||||
<field name="date"/>
|
||||
<field name="purchaser_id" />
|
||||
<field name="liter" />
|
||||
<field name="amount" sum="Price"/>
|
||||
<group expand="0" string="Group By">
|
||||
<filter name="groupby_vehicle" context="{'group_by' : 'vehicle_id'}" string="Vehicle"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="fleet_vehicle_log_fuel_view_graph" model="ir.ui.view">
|
||||
<field name="name">fleet.vehicle.log.fuel.graph</field>
|
||||
<field name="model">fleet.vehicle.log.fuel</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Fuel Costs Per Month">
|
||||
<field name="vehicle_id" type="row"/>
|
||||
<field name="cost_amount" type="measure"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='fleet_vehicle_log_fuel_action' model='ir.actions.act_window'>
|
||||
<field name="name">Vehicles Fuel Logs</field>
|
||||
<field name="res_model">fleet.vehicle.log.fuel</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form,graph</field>
|
||||
<field name="context">{"search_default_groupby_vehicle" : True}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to create a new fuel log.
|
||||
</p><p>
|
||||
Here you can add refuelling entries for all vehicles. You can
|
||||
also filter logs of a particular vehicle using the search
|
||||
field.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem action="fleet_vehicle_log_fuel_action" parent="fleet_vehicles" id="fleet_vehicle_log_fuel_menu" groups="fleet_group_user"/>
|
||||
|
||||
<record id='fleet_vehicle_log_services_view_form' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.log.services.form</field>
|
||||
<field name="model">fleet.vehicle.log.services</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Services Logs">
|
||||
<sheet>
|
||||
<group col="2">
|
||||
<group string="Services Details">
|
||||
<field name="vehicle_id"/>
|
||||
<field name="cost_subtype_id" string="Service Type" domain="['|',('category','=','service'),('category','=','both')]" required="1"/>
|
||||
<field name="amount"/>
|
||||
</group>
|
||||
<group string="Odometer Details">
|
||||
<label for="odometer"/>
|
||||
<div class="o_row">
|
||||
<field name="odometer"/>
|
||||
<field name="odometer_unit"/>
|
||||
</div>
|
||||
</group>
|
||||
</group>
|
||||
<group col="2">
|
||||
<group string="Additional Details">
|
||||
<field name="date" />
|
||||
<field name="purchaser_id" />
|
||||
<field name="vendor_id" context="{'default_supplier': True}"/>
|
||||
<field name="inv_ref" />
|
||||
</group>
|
||||
</group>
|
||||
<group string="Included Services">
|
||||
<field name="cost_ids" nolabel="1">
|
||||
<tree string="Included Services" editable="bottom">
|
||||
<field name="cost_subtype_id" string="Service" domain="[('category','=','service')]"/>
|
||||
<field name="amount" sum="Price" string="Indicative Cost"/>
|
||||
</tree>
|
||||
</field>
|
||||
</group>
|
||||
<group string="Notes">
|
||||
<field nolabel="1" name="notes" placeholder="Write here any other information related to the service completed."/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='fleet_vehicle_log_services_view_tree' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.log.services.tree</field>
|
||||
<field name="model">fleet.vehicle.log.services</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Services Logs">
|
||||
<field name="date" />
|
||||
<field name="vehicle_id" />
|
||||
<field name="cost_subtype_id"/>
|
||||
<field name="purchaser_id"/>
|
||||
<field name="vendor_id" />
|
||||
<field name="inv_ref" />
|
||||
<field name="notes" />
|
||||
<field name="amount" sum="Total"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="fleet_vehicle_log_services_view_graph" model="ir.ui.view">
|
||||
<field name="name">fleet.vehicle.log.services.graph</field>
|
||||
<field name="model">fleet.vehicle.log.services</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Services Costs Per Month" stacked="True">
|
||||
<field name="date" type="row"/>
|
||||
<field name="vehicle_id" type="row"/>
|
||||
<field name="cost_amount" type="measure"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='fleet_vehicle_log_services_view_search' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.log.services.search</field>
|
||||
<field name="model">fleet.vehicle.log.services</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Services Logs" >
|
||||
<field name="vehicle_id"/>
|
||||
<field name="cost_subtype_id"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='fleet_vehicle_log_services_action' model='ir.actions.act_window'>
|
||||
<field name="name">Vehicles Services Logs</field>
|
||||
<field name="res_model">fleet.vehicle.log.services</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form,graph</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to create a new service entry.
|
||||
</p><p>
|
||||
Odoo helps you keeping track of all the services done
|
||||
on your vehicle. Services can be of many type: occasional
|
||||
repair, fixed maintenance, etc.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem action="fleet_vehicle_log_services_action" parent="fleet_vehicles" id="fleet_vehicle_log_services_menu" groups="fleet_group_user"/>
|
||||
|
||||
</odoo>
|
||||
@@ -0,0 +1,158 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<odoo>
|
||||
<record id='fleet_vehicle_model_view_form' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.model.form</field>
|
||||
<field name="model">fleet.vehicle.model</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Model">
|
||||
<sheet>
|
||||
<field name="image_medium" widget='image' class="oe_avatar"/>
|
||||
<div class="oe_title">
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
<h1>
|
||||
<field name="name" placeholder="e.g. Model S"/>
|
||||
</h1>
|
||||
<label for="brand_id" class="oe_edit_only"/>
|
||||
<h2>
|
||||
<field name="brand_id" placeholder="e.g. Tesla"/>
|
||||
</h2>
|
||||
</div>
|
||||
<notebook>
|
||||
<page string="Vendors" name="vendors">
|
||||
<field name="vendors" widget="many2many_kanban">
|
||||
<kanban quick_create="false" create="true">
|
||||
<field name="name"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div style="position: relative">
|
||||
<a t-if="! read_only_mode" type="delete" style="position: absolute; right: 0; padding: 4px; diplay: inline-block">X</a>
|
||||
<div class="oe_module_vignette">
|
||||
<div class="oe_module_desc">
|
||||
<field name="name"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='fleet_vehicle_model_view_tree' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.model.tree</field>
|
||||
<field name="model">fleet.vehicle.model</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Models">
|
||||
<field name="brand_id" />
|
||||
<field name="name" />
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='fleet_vehicle_model_view_search' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.model.search</field>
|
||||
<field name="model">fleet.vehicle.model</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Vehicles costs" >
|
||||
<field name="brand_id" />
|
||||
<group expand="1" string="Group By">
|
||||
<filter name="groupby_brand" context="{'group_by' : 'brand_id'}" string="Make"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='fleet_vehicle_model_action' model='ir.actions.act_window'>
|
||||
<field name="name">Vehicle Model</field>
|
||||
<field name="res_model">fleet.vehicle.model</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="context">{"search_default_groupby_brand" : True,}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to create a new model.
|
||||
</p><p>
|
||||
You can define several models (e.g. A3, A4) for each make (Audi).
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem name="Fleet" id="menu_root" sequence="115" groups="fleet_group_user" web_icon="fleet,static/description/icon.png"/>
|
||||
<menuitem name="Configuration" parent="menu_root" id="fleet_configuration" sequence="100" groups="fleet_group_manager"/>
|
||||
<menuitem action="fleet_vehicle_model_action" parent="fleet_configuration" id="fleet_vehicle_model_menu"/>
|
||||
|
||||
<record id='fleet_vehicle_model_brand_view_tree' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.model.brand.tree</field>
|
||||
<field name="model">fleet.vehicle.model.brand</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Model Make">
|
||||
<field name="name" />
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='fleet_vehicle_model_brand_view_form' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.model.brand.form</field>
|
||||
<field name="model">fleet.vehicle.model.brand</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Model Make">
|
||||
<group>
|
||||
<div>
|
||||
<field name="image_medium" widget="image" class="oe_avatar"/>
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
<h1>
|
||||
<field name="name"/>
|
||||
</h1>
|
||||
</div>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='fleet_vehicle_model_brand_view_kanban' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.model.brandkanban</field>
|
||||
<field name="model">fleet.vehicle.model.brand</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban>
|
||||
<field name="id"/>
|
||||
<field name="name" />
|
||||
<field name="image" />
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div class="oe_kanban_vignette oe_semantic_html_override">
|
||||
<a type="open" href="#" class="oe_kanban_action">
|
||||
<img t-att-src="kanban_image('fleet.vehicle.model.brand', 'image_small', record.id.value)" class="img-responsive" style="width:100px;"/>
|
||||
</a>
|
||||
<div class="text-center">
|
||||
<h4 class="oe_partner_heading">
|
||||
<a type="open">
|
||||
<field name="name"/>
|
||||
</a>
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='fleet_vehicle_model_brand_action' model='ir.actions.act_window'>
|
||||
<field name="name">Model make of Vehicle</field>
|
||||
<field name="res_model">fleet.vehicle.model.brand</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">kanban,tree,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to create a new make.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem action="fleet_vehicle_model_brand_action" parent="fleet_configuration" id="fleet_vehicle_model_brand_menu"/>
|
||||
</odoo>
|
||||
@@ -0,0 +1,400 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id='fleet_vehicle_view_form' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.form</field>
|
||||
<field name="model">fleet.vehicle</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Vehicle">
|
||||
<header>
|
||||
<field name="state_id" widget="statusbar" clickable="True" />
|
||||
</header>
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button name="return_action_to_open"
|
||||
type="object"
|
||||
class="oe_stat_button"
|
||||
icon="fa-book"
|
||||
context="{'xml_id':'fleet_vehicle_log_contract_action', 'search_default_open': 1}"
|
||||
help="show the contract for this vehicle">
|
||||
<field name="contract_count" widget="statinfo" string="Contracts"/>
|
||||
</button>
|
||||
<button name="act_show_log_cost"
|
||||
type="object"
|
||||
class="oe_stat_button"
|
||||
icon="fa-usd"
|
||||
help="show all the costs for this vehicle"
|
||||
groups="fleet.fleet_group_manager">
|
||||
<field name="cost_count" widget="statinfo" string="Costs"/>
|
||||
</button>
|
||||
<button name="return_action_to_open"
|
||||
type="object"
|
||||
class="oe_stat_button"
|
||||
icon="fa-wrench"
|
||||
context="{'xml_id':'fleet_vehicle_log_services_action'}"
|
||||
help="show the services logs for this vehicle" >
|
||||
<field name="service_count" widget="statinfo" string="Services"/>
|
||||
</button>
|
||||
<button name="return_action_to_open"
|
||||
type="object"
|
||||
class="oe_stat_button"
|
||||
icon="fa-flask"
|
||||
context="{'xml_id':'fleet_vehicle_log_fuel_action'}"
|
||||
help="show the fuel logs for this vehicle" >
|
||||
<field name="fuel_logs_count" widget="statinfo" string="Fuel"/>
|
||||
</button>
|
||||
<button name="return_action_to_open"
|
||||
type="object"
|
||||
class="oe_stat_button"
|
||||
icon="fa-tachometer"
|
||||
context="{'xml_id':'fleet_vehicle_odometer_action'}"
|
||||
help="show the odometer logs for this vehicle" >
|
||||
<field name="odometer_count" widget="statinfo" string="Odometer"/>
|
||||
</button>
|
||||
<button name="toggle_active" type="object" class="oe_stat_button" icon="fa-archive">
|
||||
<field name="active" widget="boolean_button" options='{"terminology": "archive"}'/>
|
||||
</button>
|
||||
</div>
|
||||
<field name="image_medium" widget='image' class="oe_avatar"/>
|
||||
<div class="oe_title">
|
||||
<label for="model_id" class="oe_edit_only"/>
|
||||
<h1>
|
||||
<field name="model_id" placeholder="e.g. Model S"/>
|
||||
</h1>
|
||||
<label for="license_plate" class="oe_edit_only"/>
|
||||
<h2>
|
||||
<field name="license_plate" class="oe_inline" placeholder="e.g. PAE 326"/>
|
||||
</h2>
|
||||
<label for="tag_ids" class="oe_edit_only"/>
|
||||
<field name="tag_ids" widget="many2many_tags" options="{'no_create_edit': True}"/>
|
||||
</div>
|
||||
<group col="2" string="General Properties">
|
||||
<group >
|
||||
<field name="driver_id" />
|
||||
<field name="location" />
|
||||
<field name="vin_sn" />
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
</group>
|
||||
<group >
|
||||
<label for="odometer"/>
|
||||
<div class="o_row">
|
||||
<field name="odometer"/>
|
||||
<field name="odometer_unit"/>
|
||||
</div>
|
||||
<field name="acquisition_date" />
|
||||
<field name="car_value" />
|
||||
<field name="residual_value"/>
|
||||
</group>
|
||||
</group>
|
||||
<group col="2">
|
||||
<group string="Additional Properties">
|
||||
<field name="seats" />
|
||||
<field name="doors" />
|
||||
<field name="color" />
|
||||
</group>
|
||||
<group string="Engine Options">
|
||||
<field name="transmission" />
|
||||
<field name="fuel_type" />
|
||||
<label for="co2"/>
|
||||
<div class="o_row" name="co2">
|
||||
<field name="co2"/><span>g/km</span>
|
||||
</div>
|
||||
<field name="horsepower" />
|
||||
<field name="horsepower_tax" />
|
||||
<label for="power"/>
|
||||
<div class="o_row">
|
||||
<field name="power"/><span>kW</span>
|
||||
</div>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" widget="mail_thread" options='{"thread_level": 1}'/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<record id='fleet_vehicle_view_tree' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.tree</field>
|
||||
<field name="model">fleet.vehicle</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Vehicle" decoration-warning="contract_renewal_due_soon and not contract_renewal_overdue" decoration-danger="contract_renewal_overdue">
|
||||
<field name="active" invisible="1"/>
|
||||
<field name="license_plate" />
|
||||
<field name="model_id" />
|
||||
<field name="driver_id" />
|
||||
<field name="vin_sn" />
|
||||
<field name="acquisition_date" />
|
||||
<field name="state_id"/>
|
||||
<field name="odometer"/>
|
||||
<field name="odometer_unit" />
|
||||
<field name="contract_renewal_due_soon" invisible="1"/>
|
||||
<field name="contract_renewal_overdue" invisible="1" />
|
||||
<field name="contract_renewal_total" invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="fleet_vehicle_view_search" model="ir.ui.view">
|
||||
<field name="name">fleet.vehicle.search</field>
|
||||
<field name="model">fleet.vehicle</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="All vehicles">
|
||||
<field name="name" filter_domain="['|', ('name','ilike',self), ('license_plate','ilike',self)]" string="Vehicle"/>
|
||||
<field name="driver_id"/>
|
||||
<field name="tag_ids"/>
|
||||
<field name="location"/>
|
||||
<field name="state_id" />
|
||||
<filter name="alert_true" domain="['|',('contract_renewal_due_soon','=',True),('contract_renewal_overdue','=',True)]" string="Has Alert(s)"/>
|
||||
<separator/>
|
||||
<filter string="Archived" name="inactive" domain="[('active','=',False)]"/>
|
||||
<group expand="1" string="Group By">
|
||||
<filter name="groupby_status" context="{'group_by' : 'state_id'}" string="Status"/>
|
||||
<filter name="groupby_model" context="{'group_by' : 'model_id'}" string="Model"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record id='fleet_vehicle_view_kanban' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.kanban</field>
|
||||
<field name="model">fleet.vehicle</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban>
|
||||
<field name="license_plate" />
|
||||
<field name="model_id" />
|
||||
<field name="driver_id" />
|
||||
<field name="location" />
|
||||
<field name="state_id" />
|
||||
<field name="id" />
|
||||
<field name="image" />
|
||||
<field name="tag_ids" />
|
||||
<field name="contract_renewal_due_soon" />
|
||||
<field name="contract_renewal_overdue" />
|
||||
<field name="contract_renewal_name" />
|
||||
<field name="contract_renewal_total" />
|
||||
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div class="oe_kanban_global_click">
|
||||
<div class="o_kanban_tags_section">
|
||||
<field name="tag_ids"/>
|
||||
</div>
|
||||
<div class="o_kanban_image">
|
||||
<img t-att-src="kanban_image('fleet.vehicle', 'image_small', record.id.value)"/>
|
||||
</div>
|
||||
<div class="oe_kanban_details">
|
||||
<strong>
|
||||
<field name="license_plate"/> : <field name="model_id"/>
|
||||
</strong>
|
||||
<ul>
|
||||
<li>
|
||||
<t t-if="record.driver_id.raw_value"><field name="driver_id"/></t>
|
||||
</li>
|
||||
<li>
|
||||
<t t-if="record.location.raw_value"><field name="location"/></t>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="o_kanban_button">
|
||||
<a t-if="record.contract_renewal_due_soon.raw_value and !record.contract_renewal_overdue.raw_value" data-type="object" data-name="return_action_to_open" href="#" class="oe_kanban_action btn btn-sm btn-warning" data-context='{"xml_id":"fleet_vehicle_log_contract_action"}'>
|
||||
<field name="contract_renewal_name"/> <t t-if="record.contract_renewal_total.raw_value > 0"> and <field name="contract_renewal_total" /> other(s)</t>
|
||||
</a>
|
||||
<a t-if="record.contract_renewal_overdue.raw_value" data-type="object" data-name="return_action_to_open" href="#" class="oe_kanban_action btn btn-sm btn-danger" data-context='{"xml_id":"fleet_vehicle_log_contract_action"}'>
|
||||
<field name="contract_renewal_name"/> <t t-if="record.contract_renewal_total.raw_value > 0"> and <field name="contract_renewal_total" /> other(s)</t>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record id='fleet_vehicle_action' model='ir.actions.act_window'>
|
||||
<field name="name">Vehicles</field>
|
||||
<field name="res_model">fleet.vehicle</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">kanban,tree,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to create a new vehicle.
|
||||
</p><p>
|
||||
You will be able to manage your fleet by keeping track of the
|
||||
contracts, services, fixed and recurring costs, odometers and
|
||||
fuel logs associated to each vehicle.
|
||||
</p><p>
|
||||
Odoo will warn you when services or contract have to be
|
||||
renewed.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem name="Vehicles" parent="menu_root" id="fleet_vehicles" sequence="2" groups="fleet_group_user"/>
|
||||
<menuitem action="fleet_vehicle_action" parent="fleet_vehicles" id="fleet_vehicle_menu" groups="fleet_group_user"/>
|
||||
|
||||
<record id='fleet_vehicle_odometer_view_form' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.odometer.form</field>
|
||||
<field name="model">fleet.vehicle.odometer</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Odometer Logs">
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="vehicle_id"/>
|
||||
<div>
|
||||
<field name="value" class="oe_inline"/>
|
||||
<field name="unit" class="oe_inline"/>
|
||||
</div>
|
||||
<field name="date" />
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='fleet_vehicle_odometer_view_tree' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.odometer.tree</field>
|
||||
<field name="model">fleet.vehicle.odometer</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Odometer Logs" editable="top">
|
||||
<field name="date" />
|
||||
<field name="vehicle_id"/>
|
||||
<field name="driver_id"/>
|
||||
<field name="value" />
|
||||
<field name="unit" />
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='fleet_vehicle_odometer_view_search' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.odometer.search</field>
|
||||
<field name="model">fleet.vehicle.odometer</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Vehicles odometers" >
|
||||
<field name="vehicle_id" />
|
||||
<field name="value"/>
|
||||
<field name="unit"/>
|
||||
<field name="date"/>
|
||||
<group expand="0" string="Group By">
|
||||
<filter name="groupby_vehicle" context="{'group_by' : 'vehicle_id'}" string="Vehicle"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="fleet_vehicle_odometer_view_graph" model="ir.ui.view">
|
||||
<field name="name">fleet.vehicle.odometer.graph</field>
|
||||
<field name="model">fleet.vehicle.odometer</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Odometer Values Per Vehicle">
|
||||
<field name="vehicle_id" type="row"/>
|
||||
<field name="value" type="measure"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='fleet_vehicle_odometer_action' model='ir.actions.act_window'>
|
||||
<field name="name">Vehicles Odometer</field>
|
||||
<field name="res_model">fleet.vehicle.odometer</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,graph</field>
|
||||
<field name="context">{"search_default_groupby_vehicle" : True}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to create a new odometer log.
|
||||
</p>
|
||||
<p>
|
||||
Here you can add various odometer entries for all vehicles.
|
||||
You can also show odometer value for a particular vehicle using
|
||||
the search field.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem action="fleet_vehicle_odometer_action" parent="fleet_vehicles" id="fleet_vehicle_odometer_menu" groups="fleet_group_user"/>
|
||||
|
||||
<record id='fleet_vehicle_service_types_view_tree' model='ir.ui.view'>
|
||||
<field name="name">fleet.service.type.tree</field>
|
||||
<field name="model">fleet.service.type</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Service types" editable="top">
|
||||
<field name="name" />
|
||||
<field name="category"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='fleet_vehicle_service_types_action' model='ir.actions.act_window'>
|
||||
<field name="name">Service Types</field>
|
||||
<field name="res_model">fleet.service.type</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to create a new type of service.
|
||||
</p><p>
|
||||
Each service can used in contracts, as a standalone service or both.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem action="fleet_vehicle_service_types_action" parent="fleet_configuration" id="fleet_vehicle_service_types_menu"/>
|
||||
|
||||
<record id='fleet_vehicle_state_view_tree' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.state.tree</field>
|
||||
<field name="model">fleet.vehicle.state</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="State" editable="bottom">
|
||||
<field name="sequence" widget="handler"/>
|
||||
<field name="name" />
|
||||
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='fleet_vehicle_state_action' model='ir.actions.act_window'>
|
||||
<field name="name">Vehicle Status</field>
|
||||
<field name="res_model">fleet.vehicle.state</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to create a vehicle status.
|
||||
</p><p>
|
||||
You can customize available status to track the evolution of
|
||||
each vehicle. Example: Active, Being Repaired, Sold.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem action="fleet_vehicle_state_action" parent="fleet_configuration" id="fleet_vehicle_state_menu" groups="fleet_group_manager"/>
|
||||
|
||||
<record id="fleet_vehicle_tag_view_view_form" model="ir.ui.view">
|
||||
<field name="name">fleet.vehicle.tag.form</field>
|
||||
<field name="model">fleet.vehicle.tag</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Vehicle Tags">
|
||||
<group>
|
||||
<field name="name"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="fleet_vehicle_tag_action" model="ir.actions.act_window">
|
||||
<field name="name">Vehicle Tags</field>
|
||||
<field name="res_model">fleet.vehicle.tag</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to add a new tag.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="fleet_vehicle_tag_menu" parent="fleet_configuration" action="fleet_vehicle_tag_action" sequence="112"/>
|
||||
|
||||
</odoo>
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user