[IMP] fleet: Improve dashboard, vehicule form and contracts

Purpose: Improve fleet for a better usability

Dashboard:
  - Replace 'leasing' button by a contract status icon in kanban tiles
  - Add a link to contract
  - Link to vehicule form when clicking on the tile

Vehicle form:
  - Track contract status in a chatter
  - Add a model_year

Contracts:
  - Imporve the states names
  - Split service types in two list views service/contracts
  - Execute a cron once a day to upgrade contract status
  - Add a new contract status is introduced 'Expiring Soon' to help
    spot contracts that will expire until 15 days with a filter.
  - Add a 'group by vehicle' to improve list usability

Fuel logs view: Add an an odometer column
This commit is contained in:
Baris Kocak
2017-08-07 14:05:16 +02:00
committed by Christophe Monniez
parent b79c2a773c
commit bbcebcc123
6 changed files with 101 additions and 54 deletions
+1 -1
View File
@@ -380,7 +380,7 @@
<record id="type_contract_repairing" model="fleet.service.type">
<field name="name">Repairing</field>
<field name="category">both</field>
<field name="category">service</field>
</record>
<record id="type_service_refueling" model="fleet.service.type">
+10 -9
View File
@@ -17,7 +17,7 @@ class FleetVehicle(models.Model):
return state and state.id or False
name = fields.Char(compute="_compute_vehicle_name", store=True)
active = fields.Boolean(default=True)
active = fields.Boolean('Active', default=True, track_visibility="onchange")
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)
@@ -33,9 +33,11 @@ class FleetVehicle(models.Model):
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")
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')
model_year = fields.Char('Model Year',help='Year of the model')
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',
@@ -103,7 +105,7 @@ class FleetVehicle(models.Model):
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.contract_count = LogContract.search_count([('vehicle_id', '=', record.id),('state','!=','closed')])
record.cost_count = Cost.search_count([('vehicle_id', '=', record.id), ('parent_id', '=', False)])
@api.depends('log_contracts')
@@ -114,7 +116,7 @@ class FleetVehicle(models.Model):
total = 0
name = ''
for element in record.log_contracts:
if element.state in ('open', 'toclose') and element.expiration_date:
if element.state in ('open', 'expired') 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)
@@ -129,7 +131,7 @@ class FleetVehicle(models.Model):
if overdue or due_soon:
log_contract = self.env['fleet.vehicle.log.contract'].search([
('vehicle_id', '=', record.id),
('state', 'in', ('open', 'toclose'))
('state', 'in', ('open', 'expired'))
], limit=1, order='expiration_date asc')
if log_contract:
# we display only the name of the oldest overdue/due soon contract
@@ -157,7 +159,7 @@ class FleetVehicle(models.Model):
WHERE contract.expiration_date IS NOT NULL
AND contract.expiration_date > %s
AND contract.expiration_date < %s
AND contract.state IN ('open', 'toclose')
AND contract.state IN ('open', 'expired')
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))
@@ -177,7 +179,7 @@ class FleetVehicle(models.Model):
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')
AND contract.state IN ('open', 'expired')
GROUP BY cost.vehicle_id ''', (today,))
res_ids = [x[0] for x in self.env.cr.fetchall()]
res.append(('id', search_operator, res_ids))
@@ -307,6 +309,5 @@ class FleetServiceType(models.Model):
name = fields.Char(required=True, translate=True)
category = fields.Selection([
('contract', 'Contract'),
('service', 'Service'),
('both', 'Both')
('service', 'Service')
], 'Category', required=True, help='Choose wheter the service refer to contracts, vehicle services or both')
+36 -19
View File
@@ -65,14 +65,15 @@ class FleetVehicleCost(models.Model):
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
# 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'])
del data['odometer']
return super(FleetVehicleCost, self).create(data)
class FleetVehicleLogContract(models.Model):
_inherit = ['mail.thread']
_inherits = {'fleet.vehicle.cost': 'cost_id'}
_name = 'fleet.vehicle.log.contract'
_description = 'Contract information on a vehicle'
@@ -96,8 +97,10 @@ class FleetVehicleLogContract(models.Model):
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)),
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')
@@ -105,15 +108,19 @@ class FleetVehicleLogContract(models.Model):
help='Person to which the contract is signed for')
ins_ref = fields.Char('Contract Reference', size=64, copy=False)
state = fields.Selection([
('futur', 'Incoming'),
('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)
('expired', 'Expired'),
('diesoon', 'Expiring Soon'),
('closed', 'Closed')
], 'Status', default='open', readonly=True,
help='Choose wheter the contract is still valid or not',
track_visibility="onchange",
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',
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")
"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'),
@@ -128,7 +135,8 @@ class FleetVehicleLogContract(models.Model):
# (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')
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):
@@ -148,7 +156,7 @@ class FleetVehicleLogContract(models.Model):
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')):
if (record.expiration_date and (record.state == 'open' or record.state == 'expired')):
today = fields.Date.from_string(fields.Date.today())
renew_date = fields.Date.from_string(record.expiration_date)
diff_time = (renew_date - today).days
@@ -253,11 +261,11 @@ class FleetVehicleLogContract(models.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)])
date_today = fields.Date.from_string(fields.Date.today())
in_fifteen_days = fields.Date.to_string(date_today + relativedelta(days=+15))
nearly_expired_contracts = self.search([('state', '=', 'open'), ('expiration_date', '<', in_fifteen_days)])
res = {}
for contract in contracts:
for contract in nearly_expired_contracts:
if contract.vehicle_id.id in res:
res[contract.vehicle_id.id] += 1
else:
@@ -265,8 +273,17 @@ class FleetVehicleLogContract(models.Model):
Vehicle = self.env['fleet.vehicle']
for vehicle, value in pycompat.items(res):
Vehicle.browse(vehicle).message_post(body=_('%s contract(s) need(s) to be renewed and/or closed!') % value)
return contracts.write({'state': 'toclose'})
Vehicle.browse(vehicle).message_post(body=_('%s contract(s) will expire soon and should be renewed and/or closed!') % value)
nearly_expired_contracts.write({'state': 'diesoon'})
expired_contracts = self.search([('state', '!=', 'expired'), ('expiration_date', '<',fields.Date.today() )])
expired_contracts.write({'state': 'expired'})
futur_contracts = self.search([('state', 'not in', ['futur', 'closed']), ('start_date', '>', fields.Date.today())])
futur_contracts.write({'state': 'futur'})
now_running_contracts = self.search([('state', '=', 'futur'), ('start_date', '<=', fields.Date.today())])
now_running_contracts.write({'state': 'open'})
@api.model
def run_scheduler(self):
@@ -148,9 +148,8 @@
<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 name="contract_close" states="open,expired,future" type="object" class="oe_highlight" string="Close Contract" groups="fleet.fleet_group_manager"/>
<button 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>
@@ -225,6 +224,10 @@
<field name="notes" nolabel="1" placeholder="Write here all other information relative to this contract" />
</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>
@@ -238,7 +241,6 @@
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>
@@ -278,6 +280,11 @@
<search string="Vehicles Contracts">
<filter string="Archived" name="inactive" domain="[('active','=',False)]"/>
<filter string="In Progress" name="open" domain="[('state', '=', 'open')]"/>
<filter string="Expired" name="expired" domain="[('state', '=', 'expired')]"/>
<filter string="Expiring Soon" name="expire_soon" domain="[('state', '=', 'diesoon')]"/>
<group expand="0" string="Group By">
<filter string="Vehicle" name="vehicle" context="{'group_by':'vehicle_id'}"/>
</group>
</search>
</field>
</record>
@@ -348,11 +355,11 @@
<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="odometer" />
<field name="odometer_unit" />
<field name="liter" />
<field name="price_per_liter" invisible="1"/>
<field name="amount" sum="Price"/>
@@ -84,7 +84,7 @@
<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"/>
<menuitem action="fleet_vehicle_model_action" parent="fleet_configuration" id="fleet_vehicle_model_menu" sequence="1"/>
<record id='fleet_vehicle_model_brand_view_tree' model='ir.ui.view'>
<field name="name">fleet.vehicle.model.brand.tree</field>
@@ -154,5 +154,5 @@
</field>
</record>
<menuitem action="fleet_vehicle_model_brand_action" parent="fleet_configuration" id="fleet_vehicle_model_brand_menu"/>
<menuitem action="fleet_vehicle_model_brand_action" parent="fleet_configuration" id="fleet_vehicle_model_brand_menu" sequence="2"/>
</odoo>
+39 -17
View File
@@ -73,6 +73,7 @@
<field name="driver_id" />
<field name="location" />
<field name="vin_sn" />
<field name="model_year"/>
<field name="company_id" groups="base.group_multi_company"/>
</group>
<group >
@@ -175,6 +176,7 @@
<field name="contract_renewal_overdue" />
<field name="contract_renewal_name" />
<field name="contract_renewal_total" />
<field name="contract_count" />
<templates>
<t t-name="kanban-box">
@@ -197,14 +199,20 @@
<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 class="o_kanban_button">
<a t-if="record.contract_count.raw_value>0" data-type="object"
data-name="return_action_to_open" href="#" class="oe_kanban_action oe_kanban_action_a"
data-context='{"xml_id":"fleet_vehicle_log_contract_action","search_default_open": 1}'>
<field name="contract_count"/>
Contract(s)
<span t-if="record.contract_renewal_due_soon.raw_value and !record.contract_renewal_overdue.raw_value"
class="fa fa-exclamation-triangle" t-att-style="'color:orange'">
</span>
<span t-if="record.contract_renewal_overdue.raw_value"
class="fa fa-exclamation-triangle" t-att-style="'color:red;'">
</span>
</a>
</div>
</div>
</t>
@@ -244,10 +252,8 @@
<sheet>
<group>
<field name="vehicle_id"/>
<div>
<field name="value" class="oe_inline"/>
<field name="unit" class="oe_inline"/>
</div>
<field name="value" class="oe_inline"/>
<field name="unit" class="oe_inline"/>
<field name="date" />
</group>
</sheet>
@@ -300,7 +306,7 @@
<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="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">
@@ -322,7 +328,7 @@
<field name="arch" type="xml">
<tree string="Service types" editable="top">
<field name="name" />
<field name="category"/>
<field name="category" invisible="1"/>
</tree>
</field>
</record>
@@ -332,6 +338,8 @@
<field name="res_model">fleet.service.type</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="domain">[('category','=','service')]</field>
<field name="context">{'default_category':'service'}</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to create a new type of service.
@@ -340,17 +348,31 @@
</p>
</field>
</record>
<menuitem action="fleet_vehicle_service_types_action" parent="fleet_configuration" id="fleet_vehicle_service_types_menu" sequence="3"/>
<record id='fleet_vehicle_contract_types_action' model='ir.actions.act_window'>
<field name="name">Contract 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="domain">[('category','=','contract')]</field>
<field name="context">{'default_category':'contract'}</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to create a new type of contract.
</p>
</field>
</record>
<menuitem action="fleet_vehicle_contract_types_action" parent="fleet_configuration" id="fleet_vehicle_contract_types_menu" sequence="4"/>
<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="sequence" widget="handle"/>
<field name="name" />
</tree>
</field>
</record>