[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:
@@ -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">
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user