From 82df1dfccc6b4e3031b1434c7ec1bec2bf7aeb2d Mon Sep 17 00:00:00 2001 From: Kevin Baptiste Date: Fri, 7 Jun 2019 09:37:49 +0000 Subject: [PATCH] [IMP] fleet: model cleanup, dashboard Purpose ======= It is currently difficult to know exactly what is used for costs and services on the contracts. Hence it make it difficult to evolve the fleet application We should clean the models in order to have something that is clearer Specification ============= Cleanup of models: removed Fuel and Cost. Contract and Services are no longer inheriting Cost and are seperate models simplifying the use of Fleet. A contract now has included services and they are no longer generating cost. closes odoo/odoo#34090 Taskid: 1931775 Related: odoo/enterprise#4572 Signed-off-by: Yannick Tivisse (yti) --- addons/fleet/__init__.py | 1 + addons/fleet/__manifest__.py | 8 +- addons/fleet/data/fleet_data.xml | 15 + addons/fleet/data/fleet_demo.xml | 679 ++---------------- addons/fleet/models/fleet_vehicle.py | 81 ++- addons/fleet/models/fleet_vehicle_cost.py | 477 ++++-------- addons/fleet/models/fleet_vehicle_model.py | 31 +- addons/fleet/report/__init__.py | 1 + addons/fleet/report/fleet_report.py | 131 ++++ addons/fleet/security/fleet_security.xml | 30 - addons/fleet/security/ir.model.access.csv | 4 +- addons/fleet/views/fleet_board_view.xml | 104 ++- .../fleet/views/fleet_vehicle_cost_views.xml | 434 ++--------- .../fleet/views/fleet_vehicle_model_views.xml | 10 +- addons/fleet/views/fleet_vehicle_views.xml | 58 +- 15 files changed, 563 insertions(+), 1501 deletions(-) create mode 100644 addons/fleet/report/__init__.py create mode 100644 addons/fleet/report/fleet_report.py diff --git a/addons/fleet/__init__.py b/addons/fleet/__init__.py index be9f4fab00a..8484f21c236 100644 --- a/addons/fleet/__init__.py +++ b/addons/fleet/__init__.py @@ -1,3 +1,4 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. from . import models +from . import report diff --git a/addons/fleet/__manifest__.py b/addons/fleet/__manifest__.py index a61d2508b9d..548c08330d3 100644 --- a/addons/fleet/__manifest__.py +++ b/addons/fleet/__manifest__.py @@ -11,16 +11,16 @@ Vehicle, leasing, insurances, cost ================================== With this module, Odoo helps you managing all your vehicles, the -contracts associated to those vehicle as well as services, fuel log -entries, costs and many other features necessary to the management -of your fleet of vehicle(s) +contracts associated to those vehicle as well as services, costs +and many other features necessary to the management of your fleet +of vehicle(s) Main Features ------------- * Add vehicles to your fleet * Manage contracts for vehicles * Reminder when a contract reach its expiration date -* Add services, fuel log entry, odometer values for all vehicles +* Add services, odometer values for all vehicles * Show all costs associated to a vehicle or to a type of service * Analysis graph for costs """, diff --git a/addons/fleet/data/fleet_data.xml b/addons/fleet/data/fleet_data.xml index 912688a0323..a7d52b59b88 100644 --- a/addons/fleet/data/fleet_data.xml +++ b/addons/fleet/data/fleet_data.xml @@ -15,7 +15,22 @@ New Request + 4 + + + + To Order 5 + + + Registered + 7 + + + + Downgraded + 8 + diff --git a/addons/fleet/data/fleet_demo.xml b/addons/fleet/data/fleet_demo.xml index 24d3e93783f..f18087df910 100644 --- a/addons/fleet/data/fleet_demo.xml +++ b/addons/fleet/data/fleet_demo.xml @@ -1,24 +1,14 @@ - + - - Ordered - 6 - - - - Registered - 7 - - - - Downgraded - 8 - + + Ordered + 6 + Reserve @@ -398,7 +388,7 @@ Repairing - service + contract @@ -474,7 +464,7 @@ Grand-Rosiere 5 - + kilometers 20000 @@ -489,7 +479,7 @@ Grand-Rosiere 5 - + kilometers 16000 @@ -543,617 +533,200 @@ - 2012-01-01 + 0 - - - - 6 - 2012-01-01 - 40 - 1.3 - 52 - 67458 - - First refueling - - - 2012-02-11 + 658 - - - - - 2012-02-11 - 50 - 1.3 - 65 - 455812 - - Second refueling of the car - - - 2012-03-23 + 1360 - - - - - 2012-03-23 - 45 - 1.32 - 59.4 - 45199877 - - Some notes ... - - - 2012-04-5 + 2044 - - - - - 2012-04-5 - 55 - 1.32 - 72.6 - 654 - - Some notes - - - 2012-04-25 + 2756 - - - - - 2012-04-25 - 40 - 1.35 - 54 - 55488 - - Some notes - - - 2012-05-15 + 3410 - - - - - 2012-05-15 - 55.8 - 1.35 - 75.33 - 4952 - - Some notes - - - 2012-06-11 + 3750 - - - - - 2012-06-11 - 55.8 - 1.35 - 75.33 - 4952 - - Some notes - - - 2012-07-02 + 4115 - - - - - 2012-07-02 - 47.8 - 1.35 - 64.53 - 15151 - - Some notes - - - 2012-07-28 + 4750 - - - - - 2012-07-28 - 25 - 1.35 - 33.75 - 874195 - - Some notes - - - 2012-08-17 + 5171 - - - - - 2012-08-17 - 51.2 - 1.35 - 69.12 - 4431 - - Some notes - - - 2012-09-11 + 5873 - - - - - 2012-09-11 - 45.36 - 1.35 - 61.236 - 11234 - - Some notes - - - 2012-10-06 + 6571 - - - - - 2012-10-06 - 53.4 - 1.37 - 73.158 - 798846 - - Some notes - - - 2012-11-02 + 7954 - - - - - 2012-11-02 - 48.5 - 1.37 - 66.445 - 7954 - - Some notes - - - 2012-12-02 + 7981 - - - - - 2012-12-02 - 35 - 1.4 - 49 - 545 - - Some notes - - - 2012-01-01 + 0 - - - - - 2012-01-01 - 45 - 1.27 - 57.15 - 5881418 - - First refueling - - - 2012-02-11 + 702 - - - - - 2012-02-11 - 30 - 1.3 - 39 - 884351 - - Second refueling of the car - - - 2012-03-23 + 1205.4 - - - - - 2012-03-23 - 52 - 1.32 - 68.64 - 984 - - Some notes ... - - - 2012-04-5 + 2122 - - - - - 2012-04-5 - 35 - 1.32 - 46.2 - 777 - - Some notes - - - 2012-04-25 + 2430 - - - - - 2012-04-25 - 55 - 1.35 - 74.25 - 4454112 - - Some notes - - - 2012-05-15 + 3015 - - - - - 2012-05-15 - 41.8 - 1.35 - 56.43 - 98754 - - Some notes - - - 2012-06-11 + 3602.1 - - - - - 2012-06-11 - 50 - 1.35 - 67.5 - 4952 - - Some notes - - - 2012-07-02 + 4205.5 - - - - - 2012-07-02 - 50 - 1.35 - 67.5 - 8976435 - - Some notes - - - 2012-07-28 + 4935 - - - - - 2012-07-28 - 50 - 1.35 - 67.5 - 9564 - - Some notes - - - 2012-08-17 + 5555 - - - - - 2012-08-17 - 20 - 1.35 - 27 - 975 - - Some notes - - - 2012-09-11 + 5987 - - - - - 2012-09-11 - 44.5 - 1.35 - 60.075 - 9876 - - Some notes - - - 2012-10-06 + 6571 - - - - - 2012-10-06 - 48.5 - 1.37 - 66.445 - 786 - - Some notes - - - 2012-11-02 + 7201.5 - - - - - 2012-11-02 - 55 - 1.37 - 75.35 - 895 - - Some notes - - - 2012-12-02 + 8001.2 - - - - - 2012-12-02 - 35 - 1.4 - 49 - 545 - - Some notes - - - - - 2012-10-01 - 1000 - 650 - - 2012-09-02 + + 4586 Usual vehicle repairing - - - 50 - - 2012-09-02 - - - - - - 25 - - 2012-09-02 - - - - - - 100 - - 2012-09-02 - - - 350 - - 2012-11-02 + + 4814 After crash repairing - - - 100 - - 2012-11-02 - - - - - - 150 - - 2012-11-02 - - - - - - 100 - - 2012-11-02 - - - 513 - - 2012-10-15 + + 124 @@ -1163,8 +736,8 @@ 412 - - 2012-10-08 + + 20984 @@ -1174,8 +747,8 @@ 275 - - 2012-09-25 + + 241 @@ -1185,8 +758,8 @@ 302 - - 2012-09-15 + + 22513 @@ -1195,196 +768,90 @@ + 0 + Daily leasing contract 20 daily - - - + Daily leasing contract + open - - - - - 50 - - 2012-01-01 - - - - - - 25 - - 2012-01-01 - - - - - - 100 - - 2012-01-01 - + + 0 + Weekly leasing contract 150 weekly - - + Weekly leasing contract + open - - - - - 50 - - 2012-01-01 - - - - - - 25 - - 2012-01-01 - - - - - - 100 - - 2012-01-01 - + + 0 + Monthly leasing 400 monthly - Monthly leasing contract + open - - - - - 50 - - 2012-01-01 - - - - - - 25 - - 2012-01-01 - - - - - - 100 - - 2012-01-01 - + + 0 + Yearly leasing 4000 yearly - Yearly leasing contract + open - - - - - 50 - - 2012-01-01 - - - - - - 25 - - 2012-01-01 - - - - - - 100 - - 2012-01-01 - + + 17000 + Unique leasing 0 no - - - - + + + Unique leasing contract + open - - - - - 50 - - 2012-01-01 - - - - - - 25 - - 2012-01-01 - - - - - - 100 - - 2012-01-01 - + diff --git a/addons/fleet/models/fleet_vehicle.py b/addons/fleet/models/fleet_vehicle.py index 6f61451c914..389b77a87a0 100644 --- a/addons/fleet/models/fleet_vehicle.py +++ b/addons/fleet/models/fleet_vehicle.py @@ -18,6 +18,7 @@ class FleetVehicle(models.Model): return state if state and state.id else False name = fields.Char(compute="_compute_vehicle_name", store=True) + description = fields.Text("Vehicle Description") active = fields.Boolean('Active', default=True, tracking=True) company_id = fields.Many2one('res.company', 'Company', default=lambda self: self.env.company) currency_id = fields.Many2one('res.currency', related='company_id.currency_id') @@ -28,16 +29,21 @@ class FleetVehicle(models.Model): future_driver_id = fields.Many2one('res.partner', 'Future Driver', tracking=True, help='Next Driver of the vehicle', copy=False, domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]") model_id = fields.Many2one('fleet.vehicle.model', 'Model', tracking=True, required=True, help='Model of the vehicle') - manager_id = fields.Many2one('res.users', related='model_id.manager_id') + manager_id = fields.Many2one('res.users', compute='_compute_manager_id', domain=lambda self: [('groups_id', 'in', self.env.ref('fleet.fleet_group_manager').id)], store=True, readonly=False) + + @api.depends('model_id') + def _compute_manager_id(self): + if self.model_id: + self.manager_id = self.model_id.manager_id + else: + self.manager_id = None + brand_id = fields.Many2one('fleet.vehicle.model.brand', 'Brand', related="model_id.brand_id", store=True, readonly=False) log_drivers = fields.One2many('fleet.vehicle.assignation.log', 'vehicle_id', string='Assignation Logs') - 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='Contract Count') service_count = fields.Integer(compute="_compute_count_all", string='Services') - fuel_logs_count = fields.Integer(compute="_compute_count_all", string='Fuel Log Count') odometer_count = fields.Integer(compute="_compute_count_all", string='Odometer') history_count = fields.Integer(compute="_compute_count_all", string="Drivers History Count") next_assignation_date = fields.Date('Assignation Date', help='This is the date at which the car will be available, if not set it means available instantly') @@ -108,16 +114,12 @@ class FleetVehicle(models.Model): 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', '!=', 'closed')]) - record.cost_count = Cost.search_count([('vehicle_id', '=', record.id), ('parent_id', '=', False)]) record.history_count = self.env['fleet.vehicle.assignation.log'].search_count([('vehicle_id', '=', record.id)]) @api.depends('log_contracts') @@ -130,7 +132,7 @@ class FleetVehicle(models.Model): total = 0 name = '' for element in record.log_contracts: - if element.state in ('open', 'diesoon', 'expired') 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) @@ -145,11 +147,11 @@ 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', 'diesoon', 'expired')) + ('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 - name = log_contract.cost_subtype_id.name + name = log_contract.name record.contract_renewal_overdue = overdue record.contract_renewal_due_soon = due_soon @@ -168,16 +170,11 @@ class FleetVehicle(models.Model): today = fields.Date.context_today(self) datetime_today = fields.Datetime.from_string(today) limit_date = fields.Datetime.to_string(datetime_today + relativedelta(days=+delay_alert_contract)) - 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', 'diesoon', 'expired') - GROUP BY cost.vehicle_id""", (today, limit_date)) - res_ids = [x[0] for x in self.env.cr.fetchall()] + res_ids = self.env['fleet.vehicle.log.contract'].search([ + ('expiration_date', '>', today), + ('expiration_date', '<', limit_date), + ('state', 'in', ['open', 'expired']) + ]).mapped('id') res.append(('id', search_operator, res_ids)) return res @@ -189,26 +186,26 @@ class FleetVehicle(models.Model): 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', 'diesoon', 'expired') - GROUP BY cost.vehicle_id ''', (today,)) - res_ids = [x[0] for x in self.env.cr.fetchall()] + res_ids = self.env['fleet.vehicle.log.contract'].search([ + ('expiration_date', '!=', False), + ('expiration_date', '<', today), + ('state', 'in', ['open', 'expired']) + ]).mapped('id') res.append(('id', search_operator, res_ids)) return res @api.model def create(self, vals): - res = super(FleetVehicle, self).create(vals) + res = super(FleetVehicle, self.with_context({'odometer_no_chatter': True})).create(vals) if 'driver_id' in vals and vals['driver_id']: res.create_driver_history(vals['driver_id']) if 'future_driver_id' in vals and vals['future_driver_id']: future_driver = self.env['res.partner'].browse(vals['future_driver_id']) future_driver.write({'plan_to_change_car': True}) + for rec in res: + if rec.odometer: + date = fields.Date.context_today(rec) + rec.message_post(body=_("Odometer on %s: %s %s") % (date, rec.odometer, rec.odometer_unit)) return res def write(self, vals): @@ -256,6 +253,12 @@ class FleetVehicle(models.Model): def _read_group_stage_ids(self, stages, domain, order): return self.env['fleet.vehicle.state'].search([], order=order) + @api.model + def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True): + if 'co2' in fields: + fields.remove('co2') + return super(FleetVehicle, self).read_group(domain, fields, groupby, offset, limit, orderby, lazy) + @api.model def _name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None): args = args or [] @@ -337,6 +340,22 @@ class FleetVehicleOdometer(models.Model): if self.vehicle_id: self.unit = self.vehicle_id.odometer_unit + @api.model + def create(self, vals): + odometers = super(FleetVehicleOdometer, self).create(vals) + + for odometer in odometers: + if not self.env.context.get('odometer_no_chatter'): + odometer.vehicle_id.message_post(body=_("Odometer on %s: %s %s") % (odometer.date, odometer.value, odometer.unit)) + + return odometers + + def write(self, vals): + odometers = super(FleetVehicleOdometer, self).write(vals) + if 'value' in vals and not self.env.context.get('odometer_no_chatter'): + for odometer in self: + odometer.vehicle_id.message_post(body=_("Odometer on %s: %s %s") % (odometer.date, odometer.value, odometer.unit)) + return odometers class FleetVehicleState(models.Model): _name = 'fleet.vehicle.state' diff --git a/addons/fleet/models/fleet_vehicle_cost.py b/addons/fleet/models/fleet_vehicle_cost.py index 69c55dce09a..ab073a6a877 100644 --- a/addons/fleet/models/fleet_vehicle_cost.py +++ b/addons/fleet/models/fleet_vehicle_cost.py @@ -6,39 +6,158 @@ from odoo.exceptions import UserError from dateutil.relativedelta import relativedelta +class FleetVehicleLogContract(models.Model): + _inherit = ['mail.thread', 'mail.activity.mixin'] + _name = 'fleet.vehicle.log.contract' + _description = 'Contract information on a vehicle' + _order = 'state desc,expiration_date' -class FleetVehicleCost(models.Model): - _name = 'fleet.vehicle.cost' - _description = 'Cost related to a vehicle' - _order = 'date desc, vehicle_id asc' + 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) - name = fields.Char(related='vehicle_id.name', string='Name', store=True, readonly=False) - 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) + vehicle_id = fields.Many2one('fleet.vehicle', 'Vehicle', default=1, required=True, help='Vehicle concerned by this log') + cost_subtype_id = fields.Many2one('fleet.service.type', 'Type', help='Cost type purchased with this cost', domain=[('category', '=', 'contract')]) + amount = fields.Float('Cost') + date = fields.Date(help='Date when the cost has been executed') + company_id = fields.Many2one('res.company', 'Company', default=lambda self: self.env.company) + currency_id = fields.Many2one('res.currency', related='company_id.currency_id') + name = fields.Char(string='Name', compute='_compute_contract_name', store=True) + active = fields.Boolean(default=True) + user_id = fields.Many2one('res.users', 'Responsible', default=lambda self: self.env.user, index=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(related='vehicle_id.driver_id', string='Driver') + ins_ref = fields.Char('Reference', size=64, copy=False) + state = fields.Selection([ + ('futur', 'Incoming'), + ('open', 'In Progress'), + ('expired', 'Expired'), + ('closed', 'Closed') + ], 'Status', default='open', readonly=True, + help='Choose whether the contract is still valid or not', + tracking=True, + 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') + cost_frequency = fields.Selection([ + ('no', 'No'), + ('daily', 'Daily'), + ('weekly', 'Weekly'), + ('monthly', 'Monthly'), + ('yearly', 'Yearly') + ], 'Recurring Cost Frequency', default='monthly', help='Frequency of the recuring cost', required=True) + service_ids = fields.Many2many('fleet.service.type', string="Included Services") + + @api.depends('vehicle_id', 'cost_subtype_id') + 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 + ' ' + name + 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 in ['open', 'expired']: + 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 + + def write(self, vals): + res = super(FleetVehicleLogContract, self).write(vals) + if vals.get('expiration_date') or vals.get('user_id'): + self.activity_reschedule(['fleet.mail_act_fleet_contract_to_renew'], date_deadline=vals.get('expiration_date'), new_user_id=vals.get('user_id')) + return res + + def contract_close(self): + for record in self: + record.state = 'closed' + + def contract_draft(self): + for record in self: + record.state = 'futur' + + def contract_open(self): + for record in self: + record.state = 'open' + + @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 + params = self.env['ir.config_parameter'].sudo() + delay_alert_contract = int(params.get_param('hr_fleet.delay_alert_contract', default=30)) + date_today = fields.Date.from_string(fields.Date.today()) + outdated_days = fields.Date.to_string(date_today + relativedelta(days=+delay_alert_contract)) + nearly_expired_contracts = self.search([('state', '=', 'open'), ('expiration_date', '<', outdated_days)]) + + for contract in nearly_expired_contracts.filtered(lambda contract: contract.user_id): + contract.activity_schedule( + 'fleet.mail_act_fleet_contract_to_renew', contract.expiration_date, + user_id=contract.user_id.id) + + expired_contracts = self.search([('state', 'not in', ['expired', 'closed']), ('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'}) + + def run_scheduler(self): + self.scheduler_manage_contract_expiration() + +class FleetVehicleLogServices(models.Model): + _name = 'fleet.vehicle.log.services' + _inherit = ['mail.thread', 'mail.activity.mixin'] + _rec_name = 'service_type_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), + 'service_type_id': service.id if service else None, + }) + return res + + vehicle_id = fields.Many2one('fleet.vehicle', 'Vehicle', default=1, required=True, help='Vehicle concerned by this log') + amount = fields.Float('Cost') 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") company_id = fields.Many2one('res.company', 'Company', default=lambda self: self.env.company) currency_id = fields.Many2one('res.currency', related='company_id.currency_id') + purchaser_id = fields.Many2one(related='vehicle_id.driver_id', string="Driver") + inv_ref = fields.Char('Vendor Reference') + vendor_id = fields.Many2one('res.partner', 'Vendor') + notes = fields.Text() + service_type_id = fields.Many2one('fleet.service.type', 'Service Type', required=True) def _get_odometer(self): - self.odometer = 0.0 + self.odometer = 0 for record in self: - record.odometer = False if record.odometer_id: record.odometer = record.odometer_id.value @@ -56,323 +175,9 @@ class FleetVehicleCost(models.Model): @api.model_create_multi def create(self, vals_list): for data in vals_list: - # 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(vals_list) - - -class FleetVehicleLogContract(models.Model): - _inherit = ['mail.thread', 'mail.activity.mixin'] - _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) - user_id = fields.Many2one('res.users', 'Responsible', default=lambda self: self.env.user, index=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', 'Driver', 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([ - ('futur', 'Incoming'), - ('open', 'In Progress'), - ('diesoon', 'Expiring Soon'), - ('expired', 'Expired'), - ('closed', 'Closed') - ], 'Status', default='open', readonly=True, - help='Choose whether the contract is still valid or not', - tracking=True, - 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', tracking=True, - 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, readonly=False) - odometer = fields.Float(string='Creation Contract Odometer', - 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 += ' / ' + str(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 in ['open', 'diesoon', 'expired']: - 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 - - def write(self, vals): - res = super(FleetVehicleLogContract, self).write(vals) - if vals.get('expiration_date') or vals.get('user_id'): - self.activity_reschedule(['fleet.mail_act_fleet_contract_to_renew'], date_deadline=vals.get('expiration_date'), new_user_id=vals.get('user_id')) - return res - - def contract_close(self): - for record in self: - record.state = 'closed' - - def contract_open(self): - for record in self: - record.state = 'open' - - 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, - '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 - startdate = 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 - startdate = last_autogenerated_cost.date - if found: - startdate += deltas.get(contract.cost_frequency) - today = fields.Date.context_today(self) - while (startdate <= today) & (startdate <= 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 - params = self.env['ir.config_parameter'].sudo() - delay_alert_contract = int(params.get_param('hr_fleet.delay_alert_contract', default=30)) - date_today = fields.Date.from_string(fields.Date.today()) - outdated_days = fields.Date.to_string(date_today + relativedelta(days=+delay_alert_contract)) - nearly_expired_contracts = self.search([('state', '=', 'open'), ('expiration_date', '<', outdated_days)]) - - nearly_expired_contracts.write({'state': 'diesoon'}) - for contract in nearly_expired_contracts.filtered(lambda contract: contract.user_id): - contract.activity_schedule( - 'fleet.mail_act_fleet_contract_to_renew', contract.expiration_date, - user_id=contract.user_id.id) - - expired_contracts = self.search([('state', 'not in', ['expired', 'closed']), ('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'}) - - 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') - inv_ref = fields.Char('Invoice Reference', size=64) - vendor_id = fields.Many2one('res.partner', 'Vendor') - 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, readonly=False) - - @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') - inv_ref = fields.Char('Invoice Reference') - vendor_id = fields.Many2one('res.partner', 'Vendor') - # 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, readonly=False) - 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 + return super(FleetVehicleLogServices, self).create(vals_list) \ No newline at end of file diff --git a/addons/fleet/models/fleet_vehicle_model.py b/addons/fleet/models/fleet_vehicle_model.py index ab6981c7600..53bc2759b68 100644 --- a/addons/fleet/models/fleet_vehicle_model.py +++ b/addons/fleet/models/fleet_vehicle_model.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from odoo import api, fields, models +from odoo import api, fields, models, tools, _ class FleetVehicleModel(models.Model): @@ -15,6 +15,7 @@ class FleetVehicleModel(models.Model): manager_id = fields.Many2one('res.users', 'Fleet Manager', default=lambda self: self.env.uid, domain=lambda self: [('groups_id', 'in', self.env.ref('fleet.fleet_group_manager').id)]) image_128 = fields.Image(related='brand_id.image_128', readonly=False) + active = fields.Boolean(default=True) @api.depends('name', 'brand_id') def name_get(self): @@ -26,11 +27,37 @@ class FleetVehicleModel(models.Model): res.append((record.id, name)) return res + def write(self, vals): + if 'manager_id' in vals: + old_manager = self.manager_id.id if self.manager_id else None + + self.env['fleet.vehicle'].search([('model_id', '=', self.id), ('manager_id', '=', old_manager)]).write({'manager_id': vals['manager_id']}) + + return super(FleetVehicleModel, self).write(vals) + class FleetVehicleModelBrand(models.Model): _name = 'fleet.vehicle.model.brand' _description = 'Brand of the vehicle' - _order = 'name asc' + _order = 'model_count desc, name asc' name = fields.Char('Make', required=True) image_128 = fields.Image("Logo", max_width=128, max_height=128) + model_count = fields.Integer(compute="_compute_model_count", string="", store=True) + + def _compute_model_count(self): + Model = self.env['fleet.vehicle.model'] + for record in self: + record.model_count = Model.search_count([('brand_id', '=', record.id)]) + + def action_brand_model(self): + self.ensure_one() + view = { + 'type': 'ir.actions.act_window', + 'view_mode': 'tree,form', + 'res_model': 'fleet.vehicle.model', + 'name': 'Models', + 'context': {'search_default_brand_id': self.id, 'default_brand_id': self.id} + } + + return view diff --git a/addons/fleet/report/__init__.py b/addons/fleet/report/__init__.py new file mode 100644 index 00000000000..a5b36f4db3f --- /dev/null +++ b/addons/fleet/report/__init__.py @@ -0,0 +1 @@ +from . import fleet_report diff --git a/addons/fleet/report/fleet_report.py b/addons/fleet/report/fleet_report.py new file mode 100644 index 00000000000..6d1fdc56497 --- /dev/null +++ b/addons/fleet/report/fleet_report.py @@ -0,0 +1,131 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import tools +from odoo import api, fields, models + + +class FleetReport(models.Model): + _name = "fleet.vehicle.cost.report" + _description = "Fleet Analysis Report" + _auto = False + _order = 'date_start desc' + + company_id = fields.Many2one('res.company', 'Company', readonly=True) + vehicle_id = fields.Many2one('fleet.vehicle', 'Vehicle', readonly=True) + name = fields.Char('Vehicle Name', readonly=True) + driver_id = fields.Many2one('res.partner', 'Driver', readonly=True) + fuel_type = fields.Char('Fuel', readonly=True) + date_start = fields.Date('Date', readonly=True) + + cost = fields.Float('Cost', readonly=True) + cost_type = fields.Selection(string='Cost Type', selection=[ + ('contract', 'Contract'), + ('service', 'Service') + ], readonly=True) + + def init(self): + query = """ +WITH service_costs AS ( + SELECT + ve.id AS vehicle_id, + ve.company_id AS company_id, + ve.name AS name, + ve.driver_id AS driver_id, + ve.fuel_type AS fuel_type, + date(date_trunc('month', d)) AS date_start, + COALESCE(sum(se.amount), 0) AS + COST, + 'service' AS cost_type + FROM + fleet_vehicle ve + CROSS JOIN generate_series(( + SELECT + min(acquisition_date) + FROM fleet_vehicle), CURRENT_DATE, '1 month') d + LEFT JOIN fleet_vehicle_log_services se ON se.vehicle_id = ve.id + AND date_trunc('month', se.date) = date_trunc('month', d) + WHERE + ve.active + GROUP BY + ve.id, + ve.company_id, + ve.name, + date_start, + d + ORDER BY + ve.id, + date_start +), +contract_costs AS ( + SELECT + ve.id AS vehicle_id, + ve.company_id AS company_id, + ve.name AS name, + ve.driver_id AS driver_id, + ve.fuel_type AS fuel_type, + date(date_trunc('month', d)) AS date_start, + (COALESCE(sum(co.amount), 0) + COALESCE(sum(cod.cost_generated * extract(day FROM least (date_trunc('month', d) + interval '1 month', cod.expiration_date) - greatest (date_trunc('month', d), cod.start_date))), 0) + COALESCE(sum(com.cost_generated), 0) + COALESCE(sum(coy.cost_generated), 0)) AS + COST, + 'contract' AS cost_type + FROM + fleet_vehicle ve + CROSS JOIN generate_series(( + SELECT + min(acquisition_date) + FROM fleet_vehicle), CURRENT_DATE, '1 month') d + LEFT JOIN fleet_vehicle_log_contract co ON co.vehicle_id = ve.id + AND date_trunc('month', co.date) = date_trunc('month', d) + LEFT JOIN fleet_vehicle_log_contract cod ON cod.vehicle_id = ve.id + AND date_trunc('month', cod.start_date) <= date_trunc('month', d) + AND date_trunc('month', cod.expiration_date) >= date_trunc('month', d) + AND cod.cost_frequency = 'daily' + LEFT JOIN fleet_vehicle_log_contract com ON com.vehicle_id = ve.id + AND date_trunc('month', com.start_date) <= date_trunc('month', d) + AND date_trunc('month', com.expiration_date) >= date_trunc('month', d) + AND com.cost_frequency = 'monthly' + LEFT JOIN fleet_vehicle_log_contract coy ON coy.vehicle_id = ve.id + AND date_trunc('month', coy.date) = date_trunc('month', d) + AND date_trunc('month', coy.start_date) <= date_trunc('month', d) + AND date_trunc('month', coy.expiration_date) >= date_trunc('month', d) + AND coy.cost_frequency = 'yearly' +WHERE + ve.active +GROUP BY + ve.id, + ve.company_id, + ve.name, + date_start, + d +ORDER BY + ve.id, + date_start +) +SELECT + vehicle_id AS id, + company_id, + vehicle_id, + name, + driver_id, + fuel_type, + date_start, + COST, + cost_type +FROM + service_costs sc +UNION ALL ( + SELECT + vehicle_id AS id, + company_id, + vehicle_id, + name, + driver_id, + fuel_type, + date_start, + COST, + cost_type + FROM + contract_costs cc) +""" + tools.drop_view_if_exists(self.env.cr, self._table) + self.env.cr.execute("""CREATE or REPLACE VIEW %s as (%s)""" % (self._table, query)) \ No newline at end of file diff --git a/addons/fleet/security/fleet_security.xml b/addons/fleet/security/fleet_security.xml index aab267a175c..4e8b55c45e0 100644 --- a/addons/fleet/security/fleet_security.xml +++ b/addons/fleet/security/fleet_security.xml @@ -30,16 +30,6 @@ [('cost_id.vehicle_id.driver_id','=',user.partner_id.id)] - - User can only see his/her costs - - - - - - - [('vehicle_id.driver_id','=',user.partner_id.id)] - User can only see his/her vehicle's services @@ -60,16 +50,6 @@ [('vehicle_id.driver_id','=',user.partner_id.id)] - - User can only see his/her vehicle's fuel log - - - - - - - [('cost_id.vehicle_id.driver_id','=',user.partner_id.id)] - User can only see his/her vehicle @@ -85,11 +65,6 @@ - - Administrator has all rights on vehicle's costs - - - Administrator has all rights on vehicle's services @@ -100,11 +75,6 @@ - - Administrator has all rights on vehicle's fuel log - - - Administrator has all rights on vehicle diff --git a/addons/fleet/security/ir.model.access.csv b/addons/fleet/security/ir.model.access.csv index 127ed636912..6da83f50983 100644 --- a/addons/fleet/security/ir.model.access.csv +++ b/addons/fleet/security/ir.model.access.csv @@ -7,17 +7,15 @@ fleet_vehicle_access_right_user,fleet_vehicle_access_right,model_fleet_vehicle,f fleet_vehicle_log_services_access_right_user,fleet_vehicle_log_services_access_right,model_fleet_vehicle_log_services,fleet_group_user,1,0,0,0 fleet_vehicle_log_contract_access_right_user,fleet_vehicle_log_contract_access_right,model_fleet_vehicle_log_contract,fleet_group_user,1,0,0,0 fleet_service_type_access_right_user,fleet_service_type_access_right,model_fleet_service_type,fleet_group_user,1,0,0,0 -access_fleet_vehicle_cost_user,access_fleet_vehicle_cost,model_fleet_vehicle_cost,fleet_group_user,1,1,1,0 fleet_vehicle_model_access_right,fleet_vehicle_model_access_right,model_fleet_vehicle_model,fleet_group_manager,1,1,1,1 fleet_vehicle_tag_access_right,fleet_vehicle_tag_access_right,model_fleet_vehicle_tag,fleet_group_manager,1,1,1,1 fleet_vehicle_state_access_right,fleet_vehicle_state_access_right,model_fleet_vehicle_state,fleet_group_manager,1,1,1,1 fleet_vehicle_odometer_access_right,fleet_vehicle_odometer_access_right,model_fleet_vehicle_odometer,fleet_group_user,1,1,1,1 fleet_vehicle_model_brand_access_right,fleet_vehicle_model_brand_access_right,model_fleet_vehicle_model_brand,fleet_group_manager,1,1,1,1 fleet_vehicle_access_right,fleet_vehicle_access_right,model_fleet_vehicle,fleet_group_manager,1,1,1,1 -fleet_vehicle_log_fuel_access_right,fleet_vehicle_log_fuel_access_right,model_fleet_vehicle_log_fuel,fleet_group_user,1,1,1,1 fleet_vehicle_log_services_access_right,fleet_vehicle_log_services_access_right,model_fleet_vehicle_log_services,fleet_group_manager,1,1,1,1 fleet_vehicle_log_contract_access_right,fleet_vehicle_log_contract_access_right,model_fleet_vehicle_log_contract,fleet_group_manager,1,1,1,1 fleet_service_type_access_right,fleet_service_type_access_right,model_fleet_service_type,fleet_group_manager,1,1,1,1 -access_fleet_vehicle_cost,access_fleet_vehicle_cost,model_fleet_vehicle_cost,fleet_group_manager,1,1,1,1 access_mail_activity_type_fleet_manager,mail.activity.type.fleet.manager,mail.model_mail_activity_type,fleet.fleet_group_manager,1,1,1,1 access_fleet_vehicle_assignation_log_fleet_group_user,fleet_vehicle_assignation_log fleet_group_user,fleet.model_fleet_vehicle_assignation_log,fleet.fleet_group_user,1,1,1,1 +access_fleet_report_manager,fleet_vehicle_cost_report_access_right,model_fleet_vehicle_cost_report,fleet_group_manager,1,1,1,1 \ No newline at end of file diff --git a/addons/fleet/views/fleet_board_view.xml b/addons/fleet/views/fleet_board_view.xml index 755e2f32956..f577a8063ef 100644 --- a/addons/fleet/views/fleet_board_view.xml +++ b/addons/fleet/views/fleet_board_view.xml @@ -1,11 +1,56 @@ - + + fleet.vehicle.cost.view.search + fleet.vehicle.cost.report + + + + + + + + + + + + + + + + + fleet.vehicle.cost.view.pivot + fleet.vehicle.cost.report + + + + + + + + + + + + fleet.vehicle.cost.view.graph + fleet.vehicle.cost.report + + + + + + + + + + + Costs Analysis - fleet.vehicle.cost + fleet.vehicle.cost.report graph,pivot - - {"search_default_parent_false" : True,} + + { 'time_ranges': {'field':'date_start', 'range':'last_365_days'} } +

No data for analysis @@ -18,62 +63,11 @@

- - - pivot - - - - - - graph - - - - - - Indicative Costs Analysis - fleet.vehicle.cost - graph,pivot - - {"search_default_parent_true" : True,} - -

- No data for analysis -

- Odoo helps you managing the costs for your different vehicles - Costs are generally created from services and contract and appears here. -

- Thanks to the different filters, Odoo can only print the effective - costs, sort them by type and by vehicle. -

-
-
- - - pivot - - - - - - graph - - - - - -
diff --git a/addons/fleet/views/fleet_vehicle_cost_views.xml b/addons/fleet/views/fleet_vehicle_cost_views.xml index b29646e3a40..df09890c3f8 100644 --- a/addons/fleet/views/fleet_vehicle_cost_views.xml +++ b/addons/fleet/views/fleet_vehicle_cost_views.xml @@ -1,203 +1,34 @@ - - - fleet.vehicle.cost.tree - fleet.vehicle.cost - - - - - - - - - - - - - - fleet.vehicle.cost.kanban - fleet.vehicle.cost - - - - - -
-
- - - - -
-
- - -
-
-
-
-
-
-
- - - fleet.vehicle.cost.pivot - fleet.vehicle.cost - - - - - - - - - - - - fleet.vehicle.cost.graph - fleet.vehicle.cost - - - - - - - - - - - fleet.vehicle.cost.pivot - fleet.vehicle.cost - - - - - - - - - - - fleet.vehicle.cost.graph - fleet.vehicle.cost - - - - - - - - - - - fleet.vehicle.cost.search - fleet.vehicle.cost - - - - - - - - - - - - - - - - - - - - - - fleet.vehicle.cost.form - fleet.vehicle.cost - -
- - - - - - - - - - - - - - - -
-
-
- - - fleet.vehicle.cost.graph - fleet.vehicle.cost - - - - - - - - - - - Vehicles Costs - fleet.vehicle.cost - tree,kanban,form,graph - {"search_default_parent_false" : True, "search_default_groupby_vehicle_id" : True,} - -

- Create a new cost -

- Manage the costs for your different vehicles. - Costs are created automatically from services, contracts and fuel logs. -

-
-
- - - - fleet.vehicle.log_contract.form fleet.vehicle.log.contract
-
+ +
+

+
- - - - + + + - @@ -215,33 +46,9 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + @@ -255,26 +62,17 @@
- - fleet.vehicle.log.contract.tree fleet.vehicle.log.contract - + + - @@ -308,7 +106,6 @@
-
@@ -324,7 +121,7 @@ - +
@@ -334,13 +131,12 @@ fleet.vehicle.log.contract - - - - - + + + + + - @@ -369,7 +165,6 @@
-
@@ -378,7 +173,7 @@ - Vehicles Contracts + Contracts fleet.vehicle.log.contract tree,kanban,form,graph,activity {'search_default_open': 1} @@ -396,149 +191,25 @@
- - - - fleet.vehicle.log.fuel.form - fleet.vehicle.log.fuel - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - fleet.vehicle.log.fuel.tree - fleet.vehicle.log.fuel - - - - - - - - - - - - - - - - - - fleet.vehicle.log.fuel.kanban - fleet.vehicle.log.fuel - - - - -
-
- - - - -
-
- -
-
- Liter - -
-
-
-
-
-
-
- - - fleet.vehicle.log.fuel.search - fleet.vehicle.log.fuel - - - - - - - - - - - - - - - - fleet.vehicle.log.fuel.graph - fleet.vehicle.log.fuel - - - - - - - - - - Vehicles Fuel Logs - fleet.vehicle.log.fuel - tree,kanban,form,graph - {"search_default_groupby_vehicle" : True} - -

- Create a new fuel log -

- Here you can add refuelling entries for all vehicles. -

-
-
- - + fleet.vehicle.log.services.form fleet.vehicle.log.services
+ - - - - + + + + + - + + + - - - - - - - - - - - - - - - - +
+ + + +
@@ -576,11 +236,11 @@ + - - + @@ -592,6 +252,7 @@ fleet.vehicle.log.services +
@@ -601,15 +262,12 @@
-
- -
- +
@@ -625,7 +283,7 @@ - +
@@ -636,13 +294,13 @@ - +
- Vehicles Services Logs + Services fleet.vehicle.log.services tree,kanban,form,graph @@ -655,6 +313,6 @@ - +
diff --git a/addons/fleet/views/fleet_vehicle_model_views.xml b/addons/fleet/views/fleet_vehicle_model_views.xml index 9f69f06f91e..12449915e66 100644 --- a/addons/fleet/views/fleet_vehicle_model_views.xml +++ b/addons/fleet/views/fleet_vehicle_model_views.xml @@ -6,6 +6,7 @@
+
+ @@ -144,7 +146,7 @@ -
+
Open @@ -155,6 +157,12 @@
+
diff --git a/addons/fleet/views/fleet_vehicle_views.xml b/addons/fleet/views/fleet_vehicle_views.xml index 829b75cc22d..49cc2975837 100644 --- a/addons/fleet/views/fleet_vehicle_views.xml +++ b/addons/fleet/views/fleet_vehicle_views.xml @@ -26,14 +26,6 @@ help="show the contract for this vehicle"> - -