diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/fleet/models | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/fleet/models')
| -rw-r--r-- | addons/fleet/models/__init__.py | 8 | ||||
| -rw-r--r-- | addons/fleet/models/fleet_vehicle.py | 397 | ||||
| -rw-r--r-- | addons/fleet/models/fleet_vehicle_cost.py | 189 | ||||
| -rw-r--r-- | addons/fleet/models/fleet_vehicle_model.py | 66 | ||||
| -rw-r--r-- | addons/fleet/models/res_config_settings.py | 11 | ||||
| -rw-r--r-- | addons/fleet/models/res_partner.py | 10 |
6 files changed, 681 insertions, 0 deletions
diff --git a/addons/fleet/models/__init__.py b/addons/fleet/models/__init__.py new file mode 100644 index 00000000..d0515378 --- /dev/null +++ b/addons/fleet/models/__init__.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import fleet_vehicle +from . import fleet_vehicle_cost +from . import fleet_vehicle_model +from . import res_config_settings +from . import res_partner diff --git a/addons/fleet/models/fleet_vehicle.py b/addons/fleet/models/fleet_vehicle.py new file mode 100644 index 00000000..8cb15b38 --- /dev/null +++ b/addons/fleet/models/fleet_vehicle.py @@ -0,0 +1,397 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from dateutil.relativedelta import relativedelta + +from odoo import api, fields, models, _ +from odoo.osv import expression + + +class FleetVehicle(models.Model): + _inherit = ['mail.thread', 'mail.activity.mixin'] + _name = 'fleet.vehicle' + _description = 'Vehicle' + _order = 'license_plate asc, acquisition_date asc' + + def _get_default_state(self): + state = self.env.ref('fleet.fleet_vehicle_state_registered', raise_if_not_found=False) + 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') + license_plate = fields.Char(tracking=True, + help='License plate number of the vehicle (i = plate number for a car)') + vin_sn = fields.Char('Chassis Number', help='Unique number written on the vehicle motor (VIN/SN number)', copy=False) + driver_id = fields.Many2one('res.partner', 'Driver', tracking=True, help='Driver of the vehicle', copy=False) + 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', 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): + for vehicle in self: + if vehicle.model_id: + vehicle.manager_id = vehicle.model_id.manager_id + else: + vehicle.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='Assignment Logs') + log_services = fields.One2many('fleet.vehicle.log.services', 'vehicle_id', 'Services Logs') + log_contracts = fields.One2many('fleet.vehicle.log.contract', 'vehicle_id', 'Contracts') + contract_count = fields.Integer(compute="_compute_count_all", string='Contract Count') + service_count = fields.Integer(compute="_compute_count_all", string='Services') + 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('Assignment Date', help='This is the date at which the car will be available, if not set it means available instantly') + acquisition_date = fields.Date('Immatriculation Date', required=False, + default=fields.Date.today, help='Date when the vehicle has been immatriculated') + first_contract_date = fields.Date(string="First Contract Date", default=fields.Date.today) + color = fields.Char(help='Color of the vehicle') + state_id = fields.Many2one('fleet.vehicle.state', 'State', + default=_get_default_state, group_expand='_read_group_stage_ids', + tracking=True, + 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', + help='Odometer measure of the vehicle at the moment of this log') + odometer_unit = fields.Selection([ + ('kilometers', 'km'), + ('miles', 'mi') + ], 'Odometer Unit', default='kilometers', help='Unit of the odometer ', required=True) + transmission = fields.Selection([('manual', 'Manual'), ('automatic', 'Automatic')], 'Transmission', help='Transmission Used by the vehicle') + fuel_type = fields.Selection([ + ('gasoline', 'Gasoline'), + ('diesel', 'Diesel'), + ('lpg', 'LPG'), + ('electric', 'Electric'), + ('hybrid', 'Hybrid') + ], 'Fuel Type', help='Fuel Used by the vehicle') + horsepower = fields.Integer() + horsepower_tax = fields.Float('Horsepower Taxation') + power = fields.Integer('Power', help='Power in kW of the vehicle') + co2 = fields.Float('CO2 Emissions', help='CO2 emissions of the vehicle') + image_128 = fields.Image(related='model_id.image_128', readonly=True) + contract_renewal_due_soon = fields.Boolean(compute='_compute_contract_reminder', search='_search_contract_renewal_due_soon', + string='Has Contracts to renew') + contract_renewal_overdue = fields.Boolean(compute='_compute_contract_reminder', search='_search_get_overdue_contract_reminder', + string='Has Contracts Overdue') + contract_renewal_name = fields.Text(compute='_compute_contract_reminder', string='Name of contract to renew soon') + contract_renewal_total = fields.Text(compute='_compute_contract_reminder', string='Total of contracts due or overdue minus one') + car_value = fields.Float(string="Catalog Value (VAT Incl.)", help='Value of the bought vehicle') + net_car_value = fields.Float(string="Purchase Value", help="Purchase value of the vehicle") + residual_value = fields.Float() + plan_to_change_car = fields.Boolean(related='driver_id.plan_to_change_car', store=True, readonly=False) + vehicle_type = fields.Selection(related='model_id.vehicle_type') + + @api.depends('model_id.brand_id.name', 'model_id.name', 'license_plate') + def _compute_vehicle_name(self): + for record in self: + record.name = (record.model_id.brand_id.name or '') + '/' + (record.model_id.name or '') + '/' + (record.license_plate or _('No Plate')) + + def _get_odometer(self): + FleetVehicalOdometer = self.env['fleet.vehicle.odometer'] + for record in self: + vehicle_odometer = FleetVehicalOdometer.search([('vehicle_id', '=', record.id)], limit=1, order='value desc') + if vehicle_odometer: + record.odometer = vehicle_odometer.value + else: + record.odometer = 0 + + def _set_odometer(self): + for record in self: + if record.odometer: + date = fields.Date.context_today(record) + data = {'value': record.odometer, 'date': date, 'vehicle_id': record.id} + self.env['fleet.vehicle.odometer'].create(data) + + def _compute_count_all(self): + Odometer = self.env['fleet.vehicle.odometer'] + LogService = self.env['fleet.vehicle.log.services'] + LogContract = self.env['fleet.vehicle.log.contract'] + for record in self: + record.odometer_count = Odometer.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.history_count = self.env['fleet.vehicle.assignation.log'].search_count([('vehicle_id', '=', record.id)]) + + @api.depends('log_contracts') + def _compute_contract_reminder(self): + params = self.env['ir.config_parameter'].sudo() + delay_alert_contract = int(params.get_param('hr_fleet.delay_alert_contract', default=30)) + for record in self: + overdue = False + due_soon = False + total = 0 + name = '' + for element in record.log_contracts: + 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) + due_time = fields.Date.from_string(due_time_str) + diff_time = (due_time - current_date).days + if diff_time < 0: + overdue = True + total += 1 + if diff_time < delay_alert_contract: + due_soon = True + total += 1 + if overdue or due_soon: + log_contract = self.env['fleet.vehicle.log.contract'].search([ + ('vehicle_id', '=', record.id), + ('state', 'in', ('open', '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.name + + record.contract_renewal_overdue = overdue + record.contract_renewal_due_soon = due_soon + record.contract_renewal_total = total - 1 # we remove 1 from the real total for display purposes + record.contract_renewal_name = name + + def _get_analytic_name(self): + # This function is used in fleet_account and is overrided in l10n_be_hr_payroll_fleet + return self.license_plate or _('No plate') + + def _search_contract_renewal_due_soon(self, operator, value): + params = self.env['ir.config_parameter'].sudo() + delay_alert_contract = int(params.get_param('hr_fleet.delay_alert_contract', default=30)) + res = [] + assert operator in ('=', '!=', '<>') and value in (True, False), 'Operation not supported' + if (operator == '=' and value is True) or (operator in ('<>', '!=') and value is False): + search_operator = 'in' + else: + search_operator = 'not in' + today = fields.Date.context_today(self) + datetime_today = fields.Datetime.from_string(today) + limit_date = fields.Datetime.to_string(datetime_today + relativedelta(days=+delay_alert_contract)) + 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 + + def _search_get_overdue_contract_reminder(self, operator, value): + res = [] + assert operator in ('=', '!=', '<>') and value in (True, False), 'Operation not supported' + if (operator == '=' and value is True) or (operator in ('<>', '!=') and value is False): + search_operator = 'in' + else: + search_operator = 'not in' + today = fields.Date.context_today(self) + 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): + # Fleet administrator may not have rights to create the plan_to_change_car value when the driver_id is a res.user + # This trick is used to prevent access right error. + ptc_value = 'plan_to_change_car' in vals.keys() and {'plan_to_change_car': vals.pop('plan_to_change_car')} + res = super(FleetVehicle, self).create(vals) + if ptc_value: + res.sudo().write(ptc_value) + 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']: + state_waiting_list = self.env.ref('fleet.fleet_vehicle_state_waiting_list', raise_if_not_found=False) + states = res.mapped('state_id').ids + if not state_waiting_list or state_waiting_list.id not in states: + future_driver = self.env['res.partner'].browse(vals['future_driver_id']) + future_driver.sudo().write({'plan_to_change_car': True}) + return res + + def write(self, vals): + if 'driver_id' in vals and vals['driver_id']: + driver_id = vals['driver_id'] + self.filtered(lambda v: v.driver_id.id != driver_id).create_driver_history(driver_id) + + if 'future_driver_id' in vals and vals['future_driver_id']: + state_waiting_list = self.env.ref('fleet.fleet_vehicle_state_waiting_list', raise_if_not_found=False) + states = self.mapped('state_id').ids if 'state_id' not in vals else [vals['state_id']] + if not state_waiting_list or state_waiting_list.id not in states: + future_driver = self.env['res.partner'].browse(vals['future_driver_id']) + future_driver.sudo().write({'plan_to_change_car': True}) + + res = super(FleetVehicle, self).write(vals) + if 'active' in vals and not vals['active']: + self.mapped('log_contracts').write({'active': False}) + return res + + def _close_driver_history(self): + self.env['fleet.vehicle.assignation.log'].search([ + ('vehicle_id', 'in', self.ids), + ('driver_id', 'in', self.mapped('driver_id').ids), + ('date_end', '=', False) + ]).write({'date_end': fields.Date.today()}) + + def create_driver_history(self, driver_id): + for vehicle in self: + self.env['fleet.vehicle.assignation.log'].create({ + 'vehicle_id': vehicle.id, + 'driver_id': driver_id, + 'date_start': fields.Date.today(), + }) + + def action_accept_driver_change(self): + self._close_driver_history() + # Find all the vehicles for which the driver is the future_driver_id + # remove their driver_id and close their history using current date + vehicles = self.search([('driver_id', 'in', self.mapped('future_driver_id').ids)]) + vehicles.write({'driver_id': False}) + vehicles._close_driver_history() + + for vehicle in self: + vehicle.future_driver_id.sudo().write({'plan_to_change_car': False}) + vehicle.driver_id = vehicle.future_driver_id + vehicle.future_driver_id = False + + @api.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 [] + if operator == 'ilike' and not (name or '').strip(): + domain = [] + else: + domain = ['|', ('name', operator, name), ('driver_id.name', operator, name)] + return self._search(expression.AND([domain, args]), limit=limit, access_rights_uid=name_get_uid) + + def return_action_to_open(self): + """ This opens the xml view specified in xml_id for the current vehicle """ + self.ensure_one() + xml_id = self.env.context.get('xml_id') + if xml_id: + + res = self.env['ir.actions.act_window']._for_xml_id('fleet.%s' % xml_id) + res.update( + context=dict(self.env.context, default_vehicle_id=self.id, group_by=False), + domain=[('vehicle_id', '=', self.id)] + ) + return res + return False + + def act_show_log_cost(self): + """ This opens log view to view and add new log for this vehicle, groupby default to only show effective costs + @return: the costs log view + """ + self.ensure_one() + copy_context = dict(self.env.context) + copy_context.pop('group_by', None) + res = self.env['ir.actions.act_window']._for_xml_id('fleet.fleet_vehicle_costs_action') + res.update( + context=dict(copy_context, default_vehicle_id=self.id, search_default_parent_false=True), + domain=[('vehicle_id', '=', self.id)] + ) + return res + + def _track_subtype(self, init_values): + self.ensure_one() + if 'driver_id' in init_values or 'future_driver_id' in init_values: + return self.env.ref('fleet.mt_fleet_driver_updated') + return super(FleetVehicle, self)._track_subtype(init_values) + + def open_assignation_logs(self): + self.ensure_one() + return { + 'type': 'ir.actions.act_window', + 'name': 'Assignment Logs', + 'view_mode': 'tree', + 'res_model': 'fleet.vehicle.assignation.log', + 'domain': [('vehicle_id', '=', self.id)], + 'context': {'default_driver_id': self.driver_id.id, 'default_vehicle_id': self.id} + } + +class FleetVehicleOdometer(models.Model): + _name = 'fleet.vehicle.odometer' + _description = 'Odometer log for a vehicle' + _order = 'date desc' + + name = fields.Char(compute='_compute_vehicle_log_name', store=True) + date = fields.Date(default=fields.Date.context_today) + value = fields.Float('Odometer Value', group_operator="max") + vehicle_id = fields.Many2one('fleet.vehicle', 'Vehicle', required=True) + unit = fields.Selection(related='vehicle_id.odometer_unit', string="Unit", readonly=True) + driver_id = fields.Many2one(related="vehicle_id.driver_id", string="Driver", readonly=False) + + @api.depends('vehicle_id', 'date') + def _compute_vehicle_log_name(self): + for record in self: + name = record.vehicle_id.name + if not name: + name = str(record.date) + elif record.date: + name += ' / ' + str(record.date) + record.name = name + + @api.onchange('vehicle_id') + def _onchange_vehicle(self): + if self.vehicle_id: + self.unit = self.vehicle_id.odometer_unit + + +class FleetVehicleState(models.Model): + _name = 'fleet.vehicle.state' + _order = 'sequence asc' + _description = 'Vehicle Status' + + name = fields.Char(required=True, translate=True) + sequence = fields.Integer(help="Used to order the note stages") + + _sql_constraints = [('fleet_state_name_unique', 'unique(name)', 'State name already exists')] + + +class FleetVehicleTag(models.Model): + _name = 'fleet.vehicle.tag' + _description = 'Vehicle Tag' + + name = fields.Char('Tag Name', required=True, translate=True) + color = fields.Integer('Color Index') + + _sql_constraints = [('name_uniq', 'unique (name)', "Tag name already exists !")] + + +class FleetServiceType(models.Model): + _name = 'fleet.service.type' + _description = 'Fleet Service Type' + + name = fields.Char(required=True, translate=True) + category = fields.Selection([ + ('contract', 'Contract'), + ('service', 'Service') + ], 'Category', required=True, help='Choose whether the service refer to contracts, vehicle services or both') + + +class FleetVehicleAssignationLog(models.Model): + _name = "fleet.vehicle.assignation.log" + _description = "Drivers history on a vehicle" + _order = "create_date desc, date_start desc" + + vehicle_id = fields.Many2one('fleet.vehicle', string="Vehicle", required=True) + driver_id = fields.Many2one('res.partner', string="Driver", required=True) + date_start = fields.Date(string="Start Date") + date_end = fields.Date(string="End Date") diff --git a/addons/fleet/models/fleet_vehicle_cost.py b/addons/fleet/models/fleet_vehicle_cost.py new file mode 100644 index 00000000..266cf94a --- /dev/null +++ b/addons/fleet/models/fleet_vehicle_cost.py @@ -0,0 +1,189 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, fields, models, _ +from odoo.exceptions import UserError + +from dateutil.relativedelta import relativedelta + +class FleetVehicleLogContract(models.Model): + _inherit = ['mail.thread', 'mail.activity.mixin'] + _name = 'fleet.vehicle.log.contract' + _description = 'Vehicle Contract' + _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) + + 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', domain=[('category', '=', 'contract')]) + amount = fields.Monetary('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='Current 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.Monetary('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.name', 'cost_subtype_id') + def _compute_contract_name(self): + for record in self: + name = record.vehicle_id.name + if name and 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' + + active = fields.Boolean(default=True) + vehicle_id = fields.Many2one('fleet.vehicle', 'Vehicle', required=True, help='Vehicle concerned by this log') + amount = fields.Monetary('Cost') + description = fields.Char('Description') + 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', default=fields.Date.context_today) + 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('res.partner', string="Driver", compute='_compute_purchaser_id', readonly=False, store=True) + 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, + default=lambda self: self.env.ref('fleet.type_service_service_8', raise_if_not_found=False), + ) + state = fields.Selection([ + ('todo', 'To Do'), + ('running', 'Running'), + ('done', 'Done'), + ('cancelled', 'Cancelled'), + ], default='todo', string='Stage') + + def _get_odometer(self): + self.odometer = 0 + for record in self: + if record.odometer_id: + record.odometer = record.odometer_id.value + + def _set_odometer(self): + for record in self: + if not record.odometer: + raise UserError(_('Emptying the odometer value of a vehicle is not allowed.')) + odometer = self.env['fleet.vehicle.odometer'].create({ + 'value': record.odometer, + 'date': record.date or fields.Date.context_today(record), + 'vehicle_id': record.vehicle_id.id + }) + self.odometer_id = odometer + + @api.model_create_multi + def create(self, vals_list): + for data in vals_list: + 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(FleetVehicleLogServices, self).create(vals_list) + + @api.depends('vehicle_id') + def _compute_purchaser_id(self): + for service in self: + service.purchaser_id = service.vehicle_id.driver_id diff --git a/addons/fleet/models/fleet_vehicle_model.py b/addons/fleet/models/fleet_vehicle_model.py new file mode 100644 index 00000000..48f36492 --- /dev/null +++ b/addons/fleet/models/fleet_vehicle_model.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, fields, models, tools + + +class FleetVehicleModel(models.Model): + _name = 'fleet.vehicle.model' + _description = 'Model of a vehicle' + _order = 'name asc' + + name = fields.Char('Model name', required=True) + brand_id = fields.Many2one('fleet.vehicle.model.brand', 'Manufacturer', required=True, help='Manufacturer of the vehicle') + vendors = fields.Many2many('res.partner', 'fleet_vehicle_model_vendors', 'model_id', 'partner_id', string='Vendors') + 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=True) + active = fields.Boolean(default=True) + vehicle_type = fields.Selection([('car', 'Car'), ('bike', 'Bike')], default='car', required=True) + + @api.depends('name', 'brand_id') + def name_get(self): + res = [] + for record in self: + name = record.name + if record.brand_id.name: + name = record.brand_id.name + '/' + name + res.append((record.id, name)) + return res + + 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 = '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) + model_ids = fields.One2many('fleet.vehicle.model', 'brand_id') + + @api.depends('model_ids') + 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/models/res_config_settings.py b/addons/fleet/models/res_config_settings.py new file mode 100644 index 00000000..59b725d8 --- /dev/null +++ b/addons/fleet/models/res_config_settings.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import fields, models + + +class ResConfigSettings(models.TransientModel): + _inherit = ['res.config.settings'] + + delay_alert_contract = fields.Integer(string='Delay alert contract outdated', default=30, config_parameter='hr_fleet.delay_alert_contract') + module_fleet_account = fields.Boolean(string="Analytic Accounting Fleet") diff --git a/addons/fleet/models/res_partner.py b/addons/fleet/models/res_partner.py new file mode 100644 index 00000000..e2cb7c1b --- /dev/null +++ b/addons/fleet/models/res_partner.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import fields, models + + +class ResPartner(models.Model): + _inherit = 'res.partner' + + plan_to_change_car = fields.Boolean('Plan To Change Car', default=False) |
