[REV] fleet: Revert multiple fleet mechanism

I don't get why you don't get it...

Partial revert of the taskID: 2415309
Com PR: https://github.com/odoo/odoo/pull/70313
Ent PR: https://github.com/odoo/enterprise/pull/18114
Upg PR: https://github.com/odoo/upgrade/pull/2447

closes odoo/odoo#75475

Related: odoo/enterprise#20406
Related: odoo/upgrade#2765
Signed-off-by: Yannick Tivisse (yti) <yti@odoo.com>
This commit is contained in:
Yannick Tivisse
2021-08-25 12:20:39 +00:00
parent b096f2a676
commit 24713b76b5
42 changed files with 122 additions and 910 deletions
-2
View File
@@ -35,8 +35,6 @@ Main Features
'views/fleet_vehicle_views.xml',
'views/fleet_vehicle_cost_views.xml',
'views/fleet_board_view.xml',
'views/fleet_category_views.xml',
'views/fleet_category_tag_views.xml',
'views/mail_activity_views.xml',
'views/res_config_settings_views.xml',
'data/fleet_cars_data.xml',
-18
View File
@@ -1,24 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="fleet_category_tag_internal" model="fleet.category.tag">
<field name="name">Internal</field>
<field name="color" eval="1"/>
</record>
<record id="fleet_category_tag_external" model="fleet.category.tag">
<field name="name">External</field>
<field name="color" eval="2"/>
</record>
<record id="fleet_category_internal" model="fleet.category">
<field name="name">My Fleet</field>
<field name="description">My Fleet</field>
<field name="tag_ids" eval="[(4, ref('fleet.fleet_category_tag_internal'))]"/>
<field name="manager_id" ref="base.user_admin"/>
<field name="favorite_user_ids" eval="[(4, ref('base.user_admin'))]"/>
</record>
<record forcecreate="True" id="ir_cron_contract_costs_generator" model="ir.cron">
<field name="name">Fleet: Generate contracts costs based on costs frequency</field>
<field name="model_id" ref="model_fleet_vehicle_log_contract"/>
-5
View File
@@ -444,7 +444,6 @@
<field name="location">Grand-Rosiere</field>
<field name="doors">5</field>
<field name="driver_id" ref="base.partner_demo" />
<field name="fleet_id" ref="fleet_category_internal"/>
<field name="acquisition_date" eval="(DateTime.now() - timedelta(days=336)).strftime('%Y-%m-%d')" />
<field name="state_id" ref="fleet_vehicle_state_registered"/>
<field name="odometer_unit">kilometers</field>
@@ -460,7 +459,6 @@
<field name="location">Grand-Rosiere</field>
<field name="doors">5</field>
<field name="driver_id" ref="base.res_partner_address_25" />
<field name="fleet_id" ref="fleet_category_internal"/>
<field name="acquisition_date" eval="(DateTime.now() - timedelta(days=233)).strftime('%Y-%m-%d')" />
<field name="state_id" ref="fleet_vehicle_state_downgraded"/>
<field name="odometer_unit">kilometers</field>
@@ -476,7 +474,6 @@
<field name="location">Grand-Rosiere</field>
<field name="doors">3</field>
<field name="driver_id" ref="base.res_partner_address_17" />
<field name="fleet_id" ref="fleet_category_internal"/>
<field name="acquisition_date" eval="time.strftime('%Y-%m-%d 2:00:00')" />
<field name="state_id" ref="fleet_vehicle_state_registered"/>
<field name="odometer_unit">kilometers</field>
@@ -492,7 +489,6 @@
<field name="location">Grand-Rosiere</field>
<field name="doors">3</field>
<field name="driver_id" ref="base.res_partner_address_16" />
<field name="fleet_id" ref="fleet_category_internal"/>
<field name="acquisition_date" eval="time.strftime('%Y-%m-%d 2:00:00')" />
<field name="state_id" ref="fleet_vehicle_state_registered"/>
<field name="odometer_unit">kilometers</field>
@@ -508,7 +504,6 @@
<field name="location">Grand-Rosiere</field>
<field name="doors">5</field>
<field name="driver_id" ref="base.res_partner_address_15" />
<field name="fleet_id" ref="fleet_category_internal"/>
<field name="acquisition_date" eval="time.strftime('%Y-%m-%d 2:00:00')" />
<field name="state_id" ref="fleet_vehicle_state_registered"/>
<field name="odometer_unit">kilometers</field>
-2
View File
@@ -2,8 +2,6 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import fleet_service_type
from . import fleet_category
from . import fleet_category_tag
from . import fleet_vehicle
from . import fleet_vehicle_assignation_log
from . import fleet_vehicle_log_contract
-90
View File
@@ -1,90 +0,0 @@
# -*- coding:utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import _, api, fields, models
class FleetCategory(models.Model):
_name = 'fleet.category'
_description = 'Vehicle Fleet'
def _get_default_favorite_user_ids(self):
return [(6, 0, [self.env.uid])]
name = fields.Char()
active = fields.Boolean(default=True)
tag_ids = fields.Many2many('fleet.category.tag', string='Tags')
description = fields.Text()
manager_id = fields.Many2one(
'res.users', string='Fleet Manager',
default=lambda self: self.env.user,
domain=lambda self: [('groups_id', 'in', self.env.ref('fleet.fleet_group_manager').id)],
)
company_id = fields.Many2one(
'res.company', string='Company',
default=lambda self: self.env.company,
)
color = fields.Integer()
vehicle_ids = fields.One2many('fleet.vehicle', 'fleet_id', string='Vehicles')
car_count = fields.Integer(compute='_compute_vehicle_counts')
bike_count = fields.Integer(compute='_compute_vehicle_counts')
favorite_user_ids = fields.Many2many('res.users', default=_get_default_favorite_user_ids)
is_favorite = fields.Boolean(
compute='_compute_is_favorite', inverse='_inverse_is_favorite',
string='Add to favorite',
help='Whether this fleet is in your favorites or not.',
)
@api.depends('vehicle_ids')
def _compute_vehicle_counts(self):
for fleet in self:
car_ids = fleet.vehicle_ids.filtered(lambda v: v.model_id.vehicle_type == 'car')
fleet.car_count = len(car_ids)
bike_ids = fleet.vehicle_ids - car_ids
fleet.bike_count = len(bike_ids)
def _compute_is_favorite(self):
for fleet in self:
fleet.is_favorite = self.env.user in fleet.favorite_user_ids
def _inverse_is_favorite(self):
# We may not have write access
favorites = not_favorites = self.env['fleet.category'].sudo()
for category in self:
if self.env.user in category.favorite_user_ids:
favorites |= category
else:
not_favorites |= category
not_favorites.write({'favorite_user_ids': [(4, self.env.uid)]})
favorites.write({'favorite_user_ids': [(3, self.env.uid)]})
def toggle_active(self):
res = super().toggle_active()
archived = self.filtered(lambda c: not c.active)
for category in archived:
category.vehicle_ids.write({
'fleet_id': False,
})
return res
def action_view_vehicles(self):
self.ensure_one()
return {
'type': 'ir.actions.act_window',
'name': _('Fleet Vehicles'),
'res_model': 'fleet.vehicle',
'view_mode': 'tree,kanban,form',
'views': [[False, 'list'], [False, 'kanban'], [False, 'form']],
'domain': [('fleet_id', '=', self.id)],
'context': {'default_fleet_id': self.id},
}
def write(self, vals):
# directly compute is_favorite to dodge allow write access right
if 'is_favorite' in vals:
vals.pop('is_favorite')
self._fields['is_favorite'].determine_inverse(self)
return super().write(vals) if vals else True
-15
View File
@@ -1,15 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
class FleetCategoryTag(models.Model):
_name = 'fleet.category.tag'
_description = 'Vehicle Fleet Category'
_sql_constraints = [
('unique_name', 'UNIQUE(name)', 'Name must be unique.'),
]
name = fields.Char()
color = fields.Integer()
+3 -20
View File
@@ -29,19 +29,14 @@ class FleetVehicle(models.Model):
name = fields.Char(compute="_compute_vehicle_name", store=True)
description = fields.Html("Vehicle Description", help="Add a note about this vehicle")
active = fields.Boolean('Active', default=True, tracking=True)
fleet_id = fields.Many2one('fleet.category', string='Fleet')
manager_id = fields.Many2one(
'res.users', 'Manager',
'res.users', 'Fleet Manager',
compute='_compute_manager_id', store=True, readonly=False,
domain=lambda self: [('groups_id', 'in', self.env.ref('fleet.fleet_group_manager').id)],
)
#Company should always be the same as fleet_id, but since we do have a case where fleet_id is null
# and we want the vehicle to be company bound we need to store it aswell, fleet_id.company_id is not editable
company_id = fields.Many2one(
'res.company', 'Company',
compute='_compute_company_id',
default=lambda self: self.env.company,
store=True,
)
currency_id = fields.Many2one('res.currency', related='company_id.currency_id')
country_id = fields.Many2one('res.country', related='company_id.country_id')
@@ -50,8 +45,8 @@ class FleetVehicle(models.Model):
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)
trailer_hook = fields.Boolean(default=False, string='Trailer Hitch', compute='_compute_model_fields', store=True, readonly=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)]")
driver_id = fields.Many2one('res.partner', 'Driver', tracking=True, help='Driver address of the vehicle', copy=False)
future_driver_id = fields.Many2one('res.partner', 'Future Driver', tracking=True, help='Next Driver Address 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')
@@ -115,18 +110,6 @@ class FleetVehicle(models.Model):
electric_assistance = fields.Boolean(compute='_compute_model_fields', store=True, readonly=False)
frame_size = fields.Float()
@api.depends('fleet_id')
def _compute_manager_id(self):
for record in self:
record.manager_id = record.fleet_id.manager_id
@api.depends('fleet_id')
def _compute_company_id(self):
for record in self:
# Retain company when fleet is unset
if record.fleet_id:
record.company_id = record.fleet_id.company_id
@api.depends('model_id')
def _compute_model_fields(self):
'''
@@ -35,7 +35,7 @@ class FleetVehicleLogContract(models.Model):
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')
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'),
+4 -15
View File
@@ -12,7 +12,6 @@ class FleetReport(models.Model):
_auto = False
_order = 'date_start desc'
fleet_id = fields.Many2one('fleet.category', 'Fleet', readonly=True)
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)
@@ -32,8 +31,7 @@ class FleetReport(models.Model):
WITH service_costs AS (
SELECT
ve.id AS vehicle_id,
fl.id as fleet_id,
fl.company_id AS company_id,
ve.company_id AS company_id,
ve.name AS name,
ve.driver_id AS driver_id,
ve.fuel_type AS fuel_type,
@@ -46,8 +44,6 @@ WITH service_costs AS (
fleet_vehicle ve
JOIN
fleet_vehicle_model vem ON vem.id = ve.model_id
JOIN
fleet_category fl ON fl.id = ve.fleet_id
CROSS JOIN generate_series((
SELECT
min(date)
@@ -58,8 +54,7 @@ WITH service_costs AS (
ve.active AND se.active AND se.state != 'cancelled'
GROUP BY
ve.id,
fl.id,
fl.company_id,
ve.company_id,
vem.vehicle_type,
ve.name,
date_start,
@@ -71,8 +66,7 @@ WITH service_costs AS (
contract_costs AS (
SELECT
ve.id AS vehicle_id,
fl.id as fleet_id,
fl.company_id AS company_id,
ve.company_id AS company_id,
ve.name AS name,
ve.driver_id AS driver_id,
ve.fuel_type AS fuel_type,
@@ -85,8 +79,6 @@ contract_costs AS (
fleet_vehicle ve
JOIN
fleet_vehicle_model vem ON vem.id = ve.model_id
JOIN
fleet_category fl ON fl.id = ve.fleet_id
CROSS JOIN generate_series((
SELECT
min(acquisition_date)
@@ -110,8 +102,7 @@ contract_costs AS (
ve.active
GROUP BY
ve.id,
fl.id,
fl.company_id,
ve.company_id,
vem.vehicle_type,
ve.name,
date_start,
@@ -122,7 +113,6 @@ contract_costs AS (
)
SELECT
vehicle_id AS id,
fleet_id,
company_id,
vehicle_id,
name,
@@ -137,7 +127,6 @@ FROM
UNION ALL (
SELECT
vehicle_id AS id,
fleet_id,
company_id,
vehicle_id,
name,
-5
View File
@@ -80,11 +80,6 @@
<field name="model_id" ref="model_fleet_vehicle"/>
<field name="groups" eval="[(4, ref('fleet_group_manager'))]"/>
</record>
<record id="ir_rule_fleet_category" model="ir.rule">
<field name="name">Fleet: Multi Company</field>
<field name="model_id" ref="model_fleet_category"/>
<field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'in', company_ids)]</field>
</record>
<record id="ir_rule_fleet_vehicle" model="ir.rule">
<field name="name">Fleet vehicle: Multi Company</field>
<field name="model_id" ref="model_fleet_vehicle"/>
@@ -1,6 +1,4 @@
id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
fleet_category_access_right_user,fleet_category_access_right,model_fleet_category,fleet_group_user,1,0,0,0
fleet_category_tag_model_acces_right_user,fleet_category_tag_access_right,model_fleet_category_tag,fleet_group_user,1,0,0,0
fleet_vehicle_model_access_right_user,fleet_vehicle_model_access_right,model_fleet_vehicle_model,fleet_group_user,1,0,0,0
fleet_vehicle_tag_access_right_user,fleet_vehicle_tag_access_right,model_fleet_vehicle_tag,fleet_group_user,1,0,0,0
fleet_vehicle_state_access_right_user,fleet_vehicle_state_access_right,model_fleet_vehicle_state,fleet_group_user,1,0,0,0
@@ -10,8 +8,6 @@ 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
fleet_category_access_right,fleet_category_access_right,model_fleet_category,fleet_group_manager,1,1,1,1
fleet_category_tag_model_acces_right,fleet_category_tag_access_right,model_fleet_category_tag,fleet_group_manager,1,1,1,1
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
1 id name model_id/id group_id/id perm_read perm_write perm_create perm_unlink
fleet_category_access_right_user fleet_category_access_right model_fleet_category fleet_group_user 1 0 0 0
fleet_category_tag_model_acces_right_user fleet_category_tag_access_right model_fleet_category_tag fleet_group_user 1 0 0 0
2 fleet_vehicle_model_access_right_user fleet_vehicle_model_access_right model_fleet_vehicle_model fleet_group_user 1 0 0 0
3 fleet_vehicle_tag_access_right_user fleet_vehicle_tag_access_right model_fleet_vehicle_tag fleet_group_user 1 0 0 0
4 fleet_vehicle_state_access_right_user fleet_vehicle_state_access_right model_fleet_vehicle_state fleet_group_user 1 0 0 0
8 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
9 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
10 fleet_service_type_access_right_user fleet_service_type_access_right model_fleet_service_type fleet_group_user 1 0 0 0
fleet_category_access_right fleet_category_access_right model_fleet_category fleet_group_manager 1 1 1 1
fleet_category_tag_model_acces_right fleet_category_tag_access_right model_fleet_category_tag fleet_group_manager 1 1 1 1
11 fleet_vehicle_model_access_right fleet_vehicle_model_access_right model_fleet_vehicle_model fleet_group_manager 1 1 1 1
12 fleet_vehicle_tag_access_right fleet_vehicle_tag_access_right model_fleet_vehicle_tag fleet_group_manager 1 1 1 1
13 fleet_vehicle_state_access_right fleet_vehicle_state_access_right model_fleet_vehicle_state fleet_group_manager 1 1 1 1
@@ -1,37 +0,0 @@
odoo.define('fleet.many2one_fleet_category', function (require) {
"use strict;"
const relationalFields = require('web.relational_fields');
const FieldMany2One = relationalFields.FieldMany2One;
const FieldRegistry = require('web.field_registry');
const FieldMany2OneFleetCategory = FieldMany2One.extend({
/**
* Opens the list view on the vehicles in the fleet instead of the form view
*
* @param {*} event
* @private
*/
_onClick: function (event) {
var self = this;
if (this.mode === 'readonly') {
event.preventDefault();
if (this.noOpen) {
this._super(...arguments);
} else {
event.stopPropagation();
this._rpc({
model: 'fleet.category',
method: 'action_view_vehicles',
args: [[this.value.res_id]],
}).then(function (action) {
self.trigger_up('do_action', {action: action});
});
}
}
}
})
FieldRegistry.add('many2one_fleet_category', FieldMany2OneFleetCategory);
return FieldMany2OneFleetCategory;
});
+1 -3
View File
@@ -10,9 +10,7 @@ odoo.define('fleet.fleet_kanban', function (require) {
* @private
*/
_openRecord() {
if (this.modelName === 'fleet.category' && this.$(".oe_kanban_fleet_vehicle").length) {
this.$(".oe_kanban_fleet_vehicle").first().click();
} else if (this.modelName === 'fleet.vehicle.model.brand' && this.$(".oe_kanban_fleet_model").length) {
if (this.modelName === 'fleet.vehicle.model.brand' && this.$(".oe_kanban_fleet_model").length) {
this.$('.oe_kanban_fleet_model').first().click();
} else {
this._super.apply(this, arguments);
-9
View File
@@ -1,9 +0,0 @@
.o_fleet_category_kanban {
.o_kanban_fleet_category_footer {
z-index: 0;
.o_fleet_avatar_user {
margin-right: -10px
}
}
}
-5
View File
@@ -8,10 +8,6 @@ class TestFleet(common.TransactionCase):
def test_manager_create_vehicle(self):
manager = new_test_user(self.env, "test fleet manager", groups="fleet.fleet_group_manager,base.group_partner_manager")
user = new_test_user(self.env, "test base user", groups="base.group_user")
fleet = self.env['fleet.category'].with_user(manager).create({
'name': 'Test Fleet',
'description': 'Testing Fleet',
})
brand = self.env["fleet.vehicle.model.brand"].create({
"name": "Audi",
})
@@ -22,6 +18,5 @@ class TestFleet(common.TransactionCase):
self.env["fleet.vehicle"].with_user(manager).create({
"model_id": model.id,
"driver_id": user.partner_id.id,
"fleet_id": fleet.id,
"plan_to_change_car": False
})
-1
View File
@@ -8,7 +8,6 @@
<field name="name" filter_domain="[('name', 'ilike', self)]"/>
<field name="driver_id" filter_domain="[('driver_id', 'ilike', self)]"/>
<field name="date_start"/>
<field name="fleet_id" filter_domain="[('fleet_id', 'ilike', self)]"/>
<filter string="Service" name="service" domain="[('cost_type', '=', 'service')]"/>
<filter string="Contract" name="contract" domain="[('cost_type', '=', 'contract')]"/>
<separator/>
@@ -1,36 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="fleet_category_tag_action" model="ir.actions.act_window">
<field name="name">Fleet Tags</field>
<field name="res_model">fleet.category.tag</field>
<field name="view_mode">tree</field>
</record>
<record id="fleet_category_tag_view_form" model="ir.ui.view">
<field name="name">fleet.category.tag.view.form</field>
<field name="model">fleet.category.tag</field>
<field name="arch" type="xml">
<form>
<sheet>
<h1><field name="name"/></h1>
<field name="color" widget="color_picker"/>
</sheet>
</form>
</field>
</record>
<record id="fleet_category_tag_view_tree" model="ir.ui.view">
<field name="name">fleet.category.tag.view.tree</field>
<field name="model">fleet.category.tag</field>
<field name="arch" type="xml">
<tree string="Tags" editable="bottom" sample="1">
<field name="name"/>
<field name="color" widget="color_picker"/>
</tree>
</field>
</record>
<menuitem id="fleet_category_tag_menu" name="Fleet Tags"
parent="fleet.fleet_configuration" sequence="1"
action="fleet_category_tag_action" groups="base.group_no_one"/>
</odoo>
-123
View File
@@ -1,123 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="fleet_category_action" model="ir.actions.act_window">
<field name="name">Fleets</field>
<field name="res_model">fleet.category</field>
<field name="view_mode">kanban,form</field>
<field name="context">{'search_default_my_favorites': 1}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create a new fleet
</p><p>
Manage multiple fleets!
</p>
</field>
</record>
<menuitem action="fleet_category_action" parent="fleet_vehicles"
id="fleet_fleet_menu" groups="fleet_group_user" sequence="0"/>
<record id="fleet_category_view_form" model="ir.ui.view">
<field name="name">fleet.category.view.form</field>
<field name="model">fleet.category</field>
<field name="arch" type="xml">
<form string="Fleet" delete="0">
<sheet>
<widget name="web_ribbon" title="Archived" bg_color="bg-danger" attrs="{'invisible': [('active', '=', True)]}"/>
<h1><field name="name"/></h1>
<group>
<field name="active" invisible="1"/>
<group>
<field name="description"/>
<field name="manager_id"/>
</group>
<group>
<field name="tag_ids" widget="many2many_tags" options="{'color_field': 'color'}"/>
<field name="company_id"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
<record id="fleet_category_view_kanban" model="ir.ui.view">
<field name="name">fleet.category.view.kanban</field>
<field name="model">fleet.category</field>
<field name="arch" type="xml">
<kanban class="oe_background_grey o_kanban_dashboard" sample="1">
<field name="color"/>
<field name="is_favorite"/>
<field name="name"/>
<field name="bike_count"/>
<field name="car_count"/>
<field name="active"/>
<templates>
<t t-name="kanban-box">
<div t-attf-class="#{kanban_color(record.color.raw_value)} oe_kanban_global_click o_fleet_category_kanban">
<div class="o_dropdown_kanban dropdown" groups="fleet.fleet_group_manager">
<a class="dropdown-toggle o-no-caret btn" role="button" data-toggle="dropdown" data-display="static" href="#" aria-label="Dropdown menu" title="Dropdown menu">
<span class="fa fa-ellipsis-v"/>
</a>
<div class="dropdown-menu" role="menu" groups="fleet.fleet_group_manager">
<a t-if="widget.editable" class="dropdown-item" role="menuitem" type="edit">Edit</a>
<a t-if="widget.editable" role="menuitem" class="dropdown-item" name="toggle_active" type="object">
<t t-if="record.active.raw_value">Archive</t>
<t t-if="!record.active.raw_value">Restore</t>
</a>
<a t-if="widget.deletable" role="menuitem" type="delete" class="dropdown-item">Delete</a>
<div role="separator" class="dropdown-divider"/>
<ul class="oe_kanban_colorpicker" data-field="color" role="popup"/>
</div>
</div>
<!-- mb-4 to give some space for the footer-->
<div class="o_kanban_card_content mb-4">
<div class="o_primary col-9">
<h2><strong><field name="name"/></strong></h2>
</div>
<field name="tag_ids" widget="many2many_tags" options="{'color_field': 'color'}"/>
<field name="description"/>
</div>
<!-- margin to try to align with the content -->
<div class="oe_kanban_content position-absolute fixed-bottom mr-2 ml-3 mb-2 o_kanban_fleet_category_footer">
<div class="o_kanban_record_bottom">
<div class="oe_kanban_bottom_left">
<a class="oe_kanban_fleet_vehicle" name="action_view_vehicles" type="object"/>
<strong>
<t t-if="record.car_count.raw_value > 0">
<field name="car_count"/>
<i class="fa fa-car" role="img" aria-label="car" title="Car Icon"/>
</t>
<t t-if="record.bike_count.raw_value > 0">
<field name="bike_count"/>
<i class="fa fa-bicycle" role="img" aria-label="bicycle" title="Bike Icon"/>
</t>
</strong>
</div>
<div class="oe_kanban_bottom_right">
<field class="o_fleet_avatar_user" name="manager_id" widget="many2one_avatar_user"/>
</div>
</div>
</div>
<span class="o_right"><field name="is_favorite" widget="boolean_favorite" nolabel="1" force_save="1"/></span>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<record id="fleet_category_view_search" model="ir.ui.view">
<field name="name">fleet.category.view.search</field>
<field name="model">fleet.category</field>
<field name="arch" type="xml">
<search string="Search Fleet">
<field name="name" string="Fleet"/>
<field name="manager_id" string="Fleet Manager"/>
<filter string="My Favorites" name="my_favorites" domain="[('favorite_user_ids', 'in', uid)]"/>
<separator/>
<filter name="archived" string="Archived" domain="[('active', '=', False)]"/>
</search>
</field>
</record>
</odoo>
@@ -77,9 +77,9 @@
<field name="start_date" />
<field name="expiration_date" widget="remaining_days"/>
<field name="days_left" invisible="1"/>
<field name="vehicle_id" widget="many2one_avatar" />
<field name="vehicle_id"/>
<field name="insurer_id" />
<field name="purchaser_id"/>
<field name="purchaser_id" widget="many2one_avatar"/>
<field name="cost_generated" widget="monetary"/>
<field name="currency_id" invisible="1"/>
<field name="cost_frequency"/>
+4 -7
View File
@@ -68,13 +68,11 @@
<group string="Driver">
<field name="active" invisible="1"/>
<field name="vehicle_type" invisible="1"/>
<field name="fleet_id" widget="many2one_fleet_category"/>
<field name="driver_id" domain="['|', ('company_id', '=', False ), ('company_id', '=', company_id)]"/>
<field name="future_driver_id"/>
<field name="plan_to_change_car" groups="fleet.fleet_group_manager" attrs="{'invisible': [('vehicle_type', '!=', 'car')]}"/>
<field name="plan_to_change_bike" groups="fleet.fleet_group_manager" attrs="{'invisible': [('vehicle_type', '!=', 'bike')]}"/>
<field name="next_assignation_date"/>
<field name="location"/>
</group>
<group string="Vehicle">
<field name="acquisition_date" attrs="{'invisible': [('vehicle_type', '!=', 'car')]}"/>
@@ -85,6 +83,7 @@
<field name="odometer_unit"/>
</div>
<field name="manager_id" domain="[('share', '=', False)]"/>
<field name="location"/>
<field name="company_id" groups="base.group_multi_company"/>
</group>
</group>
@@ -159,7 +158,7 @@
<field name="active" invisible="1"/>
<field name="license_plate" readonly="1"/>
<field name="model_id" widget="many2one_avatar" readonly="1"/>
<field name="fleet_id" widget="many2one_fleet_category" options="{'no_open': True}"/>
<field name="manager_id" optional="hide"/>
<field name="driver_id" widget="many2one_avatar" readonly="1"/>
<field name="future_driver_id" widget="many2one_avatar" readonly="1"/>
<field name="log_drivers" invisible="1"/>
@@ -174,7 +173,6 @@
<field name="contract_state" widget="badge" decoration-info="contract_state == 'open'"
decoration-danger="contract_state == 'expired'" optional="hide"/>
<field name="activity_exception_decoration" widget="activity_exception"/>
<field name="manager_id" optional="hide"/>
</tree>
</field>
</record>
@@ -193,7 +191,6 @@
<field string="Status" name="state_id"/>
<filter string="Available" name="available"
domain="['&amp;', ('future_driver_id', '=', False), '|', ('driver_id', '=', False), '|', '&amp;', ('plan_to_change_car', '=', True), ('vehicle_type', '=', 'car'), '&amp;', ('plan_to_change_bike', '=', True), ('vehicle_type', '=', 'bike')]"/>
<filter name="no_fleet" string="No Fleet" domain="[('fleet_id', '=', False)]"/>
<filter string="Bikes" name="bikes" domain="[('vehicle_type', '=', 'bike')]"/>
<filter string="Cars" name="cars" domain="[('vehicle_type', '=', 'car')]"/>
<filter string="Trailer Hook" name="trailer_hook" domain="[('trailer_hook', '=', True)]"/>
@@ -335,7 +332,7 @@
</record>
<menuitem name="Fleet" parent="menu_root" id="fleet_vehicles" sequence="2" groups="fleet_group_user"/>
<menuitem action="fleet_vehicle_action" parent="fleet_vehicles" name="Search"
<menuitem action="fleet_vehicle_action" parent="fleet_vehicles" name="Fleet"
id="fleet_vehicle_menu" groups="fleet_group_user" sequence="0"/>
<record id='fleet_vehicle_odometer_view_form' model='ir.ui.view'>
@@ -536,7 +533,7 @@
<field name="arch" type="xml">
<tree string="Assignment Logs" editable="bottom">
<field name="vehicle_id" invisible="1"/>
<field name="driver_id"/>
<field name="driver_id" widget="many2one_avatar"/>
<field name="date_start"/>
<field name="date_end"/>
</tree>
+1
View File
@@ -39,6 +39,7 @@
'views/res_config_settings_views.xml',
'views/mail_channel_views.xml',
'views/res_users.xml',
'views/res_partner_views.xml',
'data/hr_data.xml',
],
'demo': [
+20 -2
View File
@@ -1,14 +1,18 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import models
from odoo import fields, models, _
from odoo.exceptions import AccessError
class Partner(models.Model):
_inherit = ['res.partner']
employee_ids = fields.One2many(
'hr.employee', 'address_home_id', string='Employees', groups="hr.group_hr_user",
help="Related employees based on their private address")
employees_count = fields.Integer(compute='_compute_employees_count', groups="hr.group_hr_user")
def name_get(self):
""" Override to allow an employee to see its private address in his profile.
This avoids to relax access rules on `res.parter` and to add an `ir.rule`.
@@ -21,3 +25,17 @@ class Partner(models.Model):
if len(self) == 1 and self in self.env.user.employee_ids.mapped('address_home_id'):
return super(Partner, self.sudo()).name_get()
raise e
def _compute_employees_count(self):
for partner in self:
partner.employees_count = len(partner.employee_ids)
def action_open_employees(self):
self.ensure_one()
return {
'name': _('Related Employees'),
'type': 'ir.actions.act_window',
'res_model': 'hr.employee',
'view_mode': 'kanban,tree,form',
'domain': [('id', 'in', self.employee_ids.ids)],
}
+18
View File
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="res_partner_view_form" model="ir.ui.view">
<field name="name">res.partner.view.form.inherit.hr</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="arch" type="xml">
<div name="button_box" position="inside">
<button name="action_open_employees" type="object" class="oe_stat_button" icon="fa-id-card-o" groups="hr.group_hr_user" attrs="{'invisible': [('employees_count', '=', 0)]}">
<div class="o_field_widget o_stat_info">
<span class="o_stat_value"><field name="employees_count"/></span>
<span class="o_stat_text">Employee(s)</span>
</div>
</button>
</div>
</field>
</record>
</odoo>
-3
View File
@@ -8,15 +8,12 @@
'description': "",
'depends': ['hr', 'fleet'],
'data': [
'data/hr_fleet_data.xml',
'security/ir.model.access.csv',
'security/hr_fleet_security.xml',
'views/employee_views.xml',
'views/fleet_category_views.xml',
'views/fleet_vehicle_views.xml',
'views/fleet_vehicle_cost_views.xml',
'wizard/hr_departure_wizard_views.xml',
'wizard/fleet_convert_wizard_views.xml',
],
'demo': [
'data/hr_fleet_demo.xml',
-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="fleet.fleet_category_internal" model="fleet.category">
<field name="internal" eval="True"/>
</record>
</odoo>
-1
View File
@@ -4,7 +4,6 @@
from . import employee
from . import res_users
from . import fleet_vehicle_assignation_log
from . import fleet_category
from . import fleet_vehicle
from . import fleet_vehicle_log_contract
from . import fleet_vehicle_log_services
-2
View File
@@ -46,7 +46,6 @@ class Employee(models.Model):
no_address = self.filtered(lambda r: not r.address_home_id)
car_ids = self.env['fleet.vehicle'].sudo().search([
('driver_employee_id', 'in', no_address.ids),
('fleet_id.internal', '=', True),
])
# Prevent from removing employee address when linked to a car
if car_ids:
@@ -60,7 +59,6 @@ class Employee(models.Model):
car_ids = self.env['fleet.vehicle'].sudo().search([
('driver_employee_id', 'in', self.ids),
('driver_id', 'in', self.mapped('address_home_id').ids),
('fleet_id.internal', '=', True),
])
if car_ids:
car_ids.write({'driver_id': vals['address_home_id']})
-23
View File
@@ -1,23 +0,0 @@
# -*- coding:utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
class FleetCategory(models.Model):
_inherit = 'fleet.category'
internal = fields.Boolean(default=True)
def action_view_vehicles(self):
res = super().action_view_vehicles()
res['context']['internal'] = self.internal
return res
def write(self, vals):
res = super().write(vals)
if 'internal' in vals and not vals['internal']:
self.vehicle_ids.write({
'driver_employee_id': False,
'future_driver_employee_id': False,
})
return res
+35 -84
View File
@@ -10,15 +10,37 @@ class FleetVehicle(models.Model):
mobility_card = fields.Char(compute='_compute_mobility_card', store=True)
driver_employee_id = fields.Many2one(
'hr.employee', 'Driver (Employee)',
compute='_compute_driver_employee_id', store=True,
domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]",
tracking=True,
)
driver_employee_name = fields.Char(related="driver_employee_id.name")
future_driver_employee_id = fields.Many2one(
'hr.employee', 'Future Driver (Employee)',
compute='_compute_future_driver_employee_id', store=True,
domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]",
tracking=True,
)
fleet_is_internal = fields.Boolean(related='fleet_id.internal')
@api.depends('driver_id')
def _compute_driver_employee_id(self):
for vehicle in self:
if vehicle.driver_id:
vehicle.driver_employee_id = self.env['hr.employee'].search([
('address_home_id', '=', vehicle.driver_id.id),
], limit=1)
else:
vehicle.driver_employee_id = False
@api.depends('future_driver_id')
def _compute_future_driver_employee_id(self):
for vehicle in self:
if vehicle.future_driver_id:
vehicle.future_driver_employee_id = self.env['hr.employee'].search([
('address_home_id', '=', vehicle.future_driver_id.id),
], limit=1)
else:
vehicle.future_driver_employee_id = False
@api.depends('driver_id')
def _compute_mobility_card(self):
@@ -30,57 +52,14 @@ class FleetVehicle(models.Model):
employee = employee.search([('user_id.partner_id', '=', vehicle.driver_id.id)], limit=1)
vehicle.mobility_card = employee.mobility_card
@api.constrains('driver_employee_id', 'future_driver_employee_id')
def _constrain_employee_has_address(self):
wrongs = self.filtered(lambda v: (
(v.driver_employee_id and not v.driver_employee_id.address_home_id) or
(v.future_driver_employee_id and not v.future_driver_employee_id.address_home_id)
))
if wrongs:
raise ValidationError(_(
'The following vehicles have invalid employees: %s\n'
'Please assign addresses to the employees you want to assign a car to.'
) % wrongs.mapped('name'))
def _get_driver_history_data(self, vals):
res = super()._get_driver_history_data(vals)
if self.fleet_is_internal and 'driver_employee_id' in vals:
res['driver_employee_id'] = vals['driver_employee_id']
return res
def action_accept_driver_change(self):
super(FleetVehicle, self.filtered(lambda v: not v.fleet_id.internal)).action_accept_driver_change()
#Unfortunate code duplication to avoid creating unwanted vehicle logs
internals = self.filtered(lambda v: v.fleet_id.internal)
for vehicle in internals:
if vehicle.vehicle_type == 'bike':
vehicle.future_driver_id.sudo().write({'plan_to_change_bike': False})
if vehicle.vehicle_type == 'car':
vehicle.future_driver_id.sudo().write({'plan_to_change_car': False})
vehicle.driver_employee_id = vehicle.future_driver_employee_id
vehicle.future_driver_employee_id = False
def _update_create_write_vals(self, vals):
# wbr NOTE: can't use inverse without breaking history
# We do not want to link vehicles to employee when their fleet is not internal
# but since this method is called within the write function we are not guaranteed
# to always have one record, check if all fleet are internal
# This is an edge case, it is better not to link an employee to a car when they should than to link
# against cars when they shouldn't, usually you will not update multiple vehicles from many different fleets
is_internal = len(self) > 0 and len(self.filtered(lambda v: not v.fleet_id.internal)) == 0
# Use fleet_id in vals if it gets updated
if 'fleet_id' in vals:
fleet_id = self.env['fleet.category'].sudo().browse(vals['fleet_id'])
is_internal = fleet_id.internal
# Compute partner from employee
if 'driver_employee_id' in vals:
partner = False
if vals['driver_employee_id']:
employee = self.env['hr.employee'].sudo().browse(vals['driver_employee_id'])
partner = employee.address_home_id.id
vals['driver_id'] = partner
elif 'driver_id' in vals and is_internal:
elif 'driver_id' in vals:
# Reverse the process if we can find a single employee
employee = False
if vals['driver_id']:
@@ -99,7 +78,7 @@ class FleetVehicle(models.Model):
employee = self.env['hr.employee'].sudo().browse(vals['future_driver_employee_id'])
partner = employee.address_home_id.id
vals['future_driver_id'] = partner
elif 'future_driver_id' in vals and is_internal:
elif 'future_driver_id' in vals:
# Reverse the process if we can find a single employee
employee = False
if vals['future_driver_id']:
@@ -111,44 +90,6 @@ class FleetVehicle(models.Model):
employee = employee_ids[0].id
vals['future_driver_employee_id'] = employee
# Cases when fleet_id changes but driver_id and future_driver_id do not
# 1: not internal -> remove eployee related data
# 2: internal -> compute employee for each vehicle in the case no employee can be computed for the address
# we remove it's current driver_id
if 'fleet_id' in vals:
if not is_internal:
vals['driver_employee_id'] = False
vals['future_driver_employee_id'] = False
else:
# Prefetch all employees we will need
employees = self.env['hr.employee'].search([
('address_home_id', 'in', (self.driver_id | self.future_driver_id).ids),
])
for vehicle in self:
if 'driver_employee_id' not in vals:
driver_id = vals['driver_id'] if 'driver_id' in vals else vehicle.driver_id.id
driver_employee_id = employees.filtered(lambda e: e.address_home_id.id == driver_id)
if len(driver_employee_id) == 1:
vehicle.driver_employee_id = driver_employee_id
else:
vehicle.write({
'driver_id': False,
'driver_employee_id': False,
})
if 'future_driver_employee_id' not in vals:
future_driver_id = vals['future_driver_id'] if 'future_driver_id' in vals else \
vehicle.future_driver_id.id
future_driver_employee_id = employees.filtered(lambda e: (
e.address_home_id.id == future_driver_id
))
if len(future_driver_employee_id) == 1:
vehicle.future_driver_employee_id = future_driver_employee_id
else:
vehicle.write({
'future_driver_id': False,
'future_driver_employee_id': False,
})
@api.model
def create(self, vals):
self._update_create_write_vals(vals)
@@ -165,3 +106,13 @@ class FleetVehicle(models.Model):
partners_to_unsubscribe.append(employee.user_id.partner_id.id)
vehicle.message_unsubscribe(partner_ids=partners_to_unsubscribe)
return super().write(vals)
def action_open_employee(self):
self.ensure_one()
return {
'name': _('Related Employee'),
'type': 'ir.actions.act_window',
'res_model': 'hr.employee',
'view_mode': 'form',
'res_id': self.driver_employee_id.id,
}
@@ -7,8 +7,7 @@ from odoo import fields, models
class FleetVehicleAssignationLog(models.Model):
_inherit = 'fleet.vehicle.assignation.log'
driver_employee_id = fields.Many2one('hr.employee', 'Driver (Employee)')
fleet_is_internal = fields.Boolean(related='vehicle_id.fleet_is_internal')
driver_employee_id = fields.Many2one(related="vehicle_id.driver_employee_id", string='Driver (Employee)')
attachment_number = fields.Integer('Number of Attachments', compute='_compute_attachment_number')
def _compute_attachment_number(self):
@@ -8,6 +8,5 @@ class FleetVehicleLogContract(models.Model):
purchaser_employee_id = fields.Many2one(
related='vehicle_id.driver_employee_id',
string='Current Driver (Employee)',
string='Driver (Employee)',
)
fleet_is_internal = fields.Boolean(related='vehicle_id.fleet_is_internal')
@@ -10,13 +10,12 @@ class FleetVehicleLogServices(models.Model):
'hr.employee', string="Driver (Employee)",
compute='_compute_purchaser_employee_id', readonly=False, store=True,
)
fleet_is_internal = fields.Boolean(related='vehicle_id.fleet_is_internal')
@api.depends('vehicle_id', 'purchaser_employee_id')
def _compute_purchaser_id(self):
internal = self.filtered(lambda r: r.fleet_is_internal)
super(FleetVehicleLogServices, (self - internal))._compute_purchaser_id()
for service in internal:
internals = self.filtered(lambda r: r.purchaser_employee_id)
super(FleetVehicleLogServices, (self - internals))._compute_purchaser_id()
for service in internals:
service.purchaser_id = service.purchaser_employee_id.address_home_id
@api.depends('vehicle_id')
@@ -8,6 +8,6 @@ class FleetVehicleOdometer(models.Model):
_inherit = 'fleet.vehicle.odometer'
driver_employee_id = fields.Many2one(
related='vehicle_id.driver_employee_id', string='Current Driver (Employee)',
related='vehicle_id.driver_employee_id', string='Driver (Employee)',
readonly=True,
)
@@ -1,4 +1,2 @@
id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
hr_fleet_convert_wizard_access_right_user,hr_fleet_convert_wizard_access_right,model_hr_fleet_convert_wizard,fleet.fleet_group_manager,1,1,1,1
hr_fleet_convert_wizard_line_access_right_user,hr_fleet_convert_wizard_line_access_right,model_hr_fleet_convert_wizard_line,fleet.fleet_group_manager,1,1,1,1
hr_fleet_vehicle_access_right_hr_officer,hr_fleet_vehicle_access_right_hr_officer,model_fleet_vehicle,hr.group_hr_user,1,0,0,0
1 id name model_id/id group_id/id perm_read perm_write perm_create perm_unlink
hr_fleet_convert_wizard_access_right_user hr_fleet_convert_wizard_access_right model_hr_fleet_convert_wizard fleet.fleet_group_manager 1 1 1 1
hr_fleet_convert_wizard_line_access_right_user hr_fleet_convert_wizard_line_access_right model_hr_fleet_convert_wizard_line fleet.fleet_group_manager 1 1 1 1
2 hr_fleet_vehicle_access_right_hr_officer hr_fleet_vehicle_access_right_hr_officer model_fleet_vehicle hr.group_hr_user 1 0 0 0
-4
View File
@@ -1,4 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import test_fleet_category
@@ -1,146 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.tests import new_test_user
from odoo.tests.common import tagged, TransactionCase
@tagged('post_install', '-at_install', 'hr_fleet_categories')
class TestFleetCategory(TransactionCase):
def setUp(self):
super().setUp()
self.company = self.env['res.company'].create({
'name': 'Taxi Dermi',
})
self.manager = new_test_user(self.env, login='dermi', groups='fleet.fleet_group_manager', company_id=self.company.id)
self.addresses = self.env['res.partner'].create([
{
'name': 'Address 1',
'company_id': self.company.id,
'type': 'private',
},
{
'name': 'Address 2',
'company_id': self.company.id,
'type': 'private',
},
{
'name': 'Address 3',
'company_id': self.company.id,
'type': 'private',
},
{
'name': 'Address 4',
'company_id': self.company.id,
'type': 'private',
},
])
self.employees = self.env['hr.employee'].create([
{
'name': 'Employee 1',
'company_id': self.company.id,
'address_home_id': self.addresses[0].id,
},
{
'name': 'Employee 2',
'company_id': self.company.id,
'address_home_id': self.addresses[1].id,
},
{
'name': 'Employee 3',
'company_id': self.company.id,
'address_home_id': self.addresses[2].id,
},
{
'name': 'Employee 4',
'company_id': self.company.id,
'address_home_id': self.addresses[3].id,
},
])
self.brand = self.env['fleet.vehicle.model.brand'].create({
'name': 'Test Brand',
})
self.model = self.env['fleet.vehicle.model'].create({
'name': 'Test Model',
'brand_id': self.brand.id,
})
self.external_fleet = self.env['fleet.category'].create({
'name': 'External Fleet',
'company_id': self.company.id,
'internal': False,
})
self.internal_fleet = self.env['fleet.category'].create({
'name': 'Internal Fleet',
'company_id': self.company.id,
'internal': True,
})
vehicle_create_vals = []
for fleet in (self.external_fleet | self.internal_fleet):
for i in range(4):
vehicle_create_vals.append({
'name': 'Test Car ' + str(i) + ' ' + fleet.name,
'fleet_id': fleet.id,
'company_id': self.company.id,
'model_id': self.model.id,
})
self.env['fleet.vehicle'].create(vehicle_create_vals)
def test_fleet_conversion(self):
#Assign employee addresses to the fleet vehicle
for i in range(4):
self.external_fleet.vehicle_ids[i].driver_id = self.employees[i].address_home_id
wizard = self.env['hr.fleet.convert.wizard'].with_context(active_id=self.external_fleet.id).new({})
#Wizard should have a line per vehicle in the fleet
self.assertEqual(len(self.external_fleet.vehicle_ids), len(wizard.line_ids))
#All lines should be considered valid
self.assertFalse(any(wizard.line_ids.filtered('invalid_driver')))
wizard.action_validate()
#Fleet should be internal now
self.assertTrue(self.external_fleet.internal)
#All vehicles should be linked to employees
self.assertFalse(self.external_fleet.vehicle_ids.filtered(lambda v: not v.driver_employee_id))
def test_individual_fleet_change(self):
vehicle_id = self.external_fleet.vehicle_ids[0]
vehicle_id.driver_id = self.addresses[0]
#Also test future_driver
vehicle_id.future_driver_id = self.addresses[1]
#Manually change the fleet of the vehicle, it should compute the employee from the driver
vehicle_id.fleet_id = self.internal_fleet
self.assertEqual(vehicle_id.driver_employee_id, self.employees[0])
self.assertEqual(vehicle_id.future_driver_employee_id, self.employees[1])
#Now change it back, driver should be kept but not the employee
vehicle_id.fleet_id = self.external_fleet
self.assertFalse(vehicle_id.driver_employee_id)
self.assertFalse(vehicle_id.future_driver_employee_id)
def test_partial_conversion(self):
#Assign employee addresses to the fleet vehicle
for i in range(4):
self.external_fleet.vehicle_ids[i].driver_id = self.employees[i].address_home_id
#Remove address from employee, making the vehicle not be able to match the employee
self.employees[2].address_home_id = False
self.external_fleet.vehicle_ids[3].driver_id = False
wizard = self.env['hr.fleet.convert.wizard'].with_context(active_id=self.external_fleet.id).new({})
#Wizard should have a line per vehicle in the fleet except those without drivers
self.assertEqual(len(self.external_fleet.vehicle_ids) - 1, len(wizard.line_ids))
#There should be 2 valid and 1 invalid lines
self.assertEqual(1, len(wizard.line_ids.filtered('invalid_driver')))
self.assertEqual(2, len(wizard.line_ids.filtered(lambda l: not l.invalid_driver)))
wizard.action_validate()
#Fleet should be internal now
self.assertTrue(self.external_fleet.internal)
#There should be 2 vehicles with a driver and 2 without any
self.assertEqual(2, len(self.external_fleet.vehicle_ids.filtered('driver_id')))
self.assertEqual(2, len(self.external_fleet.vehicle_ids.filtered(lambda l: not l.driver_id)))
@@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="fleet_category_view_form_inherit_hr_fleet" model="ir.ui.view">
<field name="name">fleet.category.view.form.inherit.hr.fleet</field>
<field name="model">fleet.category</field>
<field name="inherit_id" ref="fleet.fleet_category_view_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='manager_id']" position="after">
<field name="internal"/>
</xpath>
</field>
</record>
<record id="fleet_category_view_search_inherit_hr_fleet" model="ir.ui.view">
<field name="name">fleet.category.view.search.inherit.hr.fleet</field>
<field name="model">fleet.category</field>
<field name="inherit_id" ref="fleet.fleet_category_view_search"/>
<field name="arch" type="xml">
<xpath expr="//filter[@name='archived']" position="after">
<filter string="Internal" name="filter_internal" domain="[('internal', '=', True)]"/>
</xpath>
</field>
</record>
</odoo>
@@ -6,12 +6,7 @@
<field name="inherit_id" ref="fleet.fleet_vehicle_log_contract_view_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='purchaser_id']" position="after">
<field name="fleet_is_internal" invisible="1"/>
<field name="purchaser_employee_id" string="Driver"
attrs="{'invisible': [('fleet_is_internal', '=', False)]}"/>
</xpath>
<xpath expr="//field[@name='purchaser_id']" position="attributes">
<attribute name="attrs">{'invisible': [('fleet_is_internal', '=', True)]}</attribute>
<field name="purchaser_employee_id" string="Driver" invisible="1"/>
</xpath>
</field>
</record>
@@ -22,9 +17,7 @@
<field name="inherit_id" ref="fleet.fleet_vehicle_log_contract_view_tree"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='purchaser_id']" position="after">
<field name="fleet_is_internal" invisible="1"/>
<field name="purchaser_employee_id"
attrs="{'invisible': [('fleet_is_internal', '=', False)]}"/>
<field name="purchaser_employee_id" optional="hide" widget="many2one_avatar_employee"/>
</xpath>
</field>
</record>
@@ -47,12 +40,7 @@
<field name="inherit_id" ref="fleet.fleet_vehicle_log_services_view_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='purchaser_id']" position="after">
<field name="fleet_is_internal" invisible="1"/>
<field name="purchaser_employee_id" string="Driver"
attrs="{'invisible': [('fleet_is_internal', '=', False)]}"/>
</xpath>
<xpath expr="//field[@name='purchaser_id']" position="attributes">
<attribute name="attrs">{'invisible': [('fleet_is_internal', '=', True)]}</attribute>
<field name="purchaser_employee_id" string="Driver (Employee)" invisible="1"/>
</xpath>
</field>
</record>
@@ -63,9 +51,7 @@
<field name="inherit_id" ref="fleet.fleet_vehicle_log_services_view_tree"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='purchaser_id']" position="after">
<field name="fleet_is_internal" invisible="1"/>
<field name="purchaser_employee_id" readonly="1" widget="many2one_avatar"
attrs="{'invisible': [('fleet_is_internal', '=', False)]}"/>
<field name="purchaser_employee_id" readonly="1" widget="many2one_avatar" optional="hide"/>
</xpath>
</field>
</record>
@@ -75,15 +61,8 @@
<field name="model">fleet.vehicle.log.services</field>
<field name="inherit_id" ref="fleet.fleet_vehicle_log_services_view_kanban"/>
<field name="arch" type="xml">
<xpath expr="//kanban/field[@name='amount']" position="after">
<field name="fleet_is_internal" invisible="1"/>
</xpath>
<xpath expr="//div/field[@name='purchaser_id']" position="after">
<field name="purchaser_employee_id"
attrs="{'invisible': [('fleet_is_internal', '=', False)]}"/>
</xpath>
<xpath expr="//div/field[@name='purchaser_id']" position="attributes">
<attribute name="attrs">{'invisible': [('fleet_is_internal', '=', True)]}</attribute>
<field name="purchaser_employee_id"/>
</xpath>
</field>
</record>
+22 -37
View File
@@ -1,5 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="fleet_vehicle_odometer_view_tree" model="ir.ui.view">
<field name="name">fleet.vehicle.odometer.view.tree.inherit.hr.fleet</field>
<field name="model">fleet.vehicle.odometer</field>
<field name="inherit_id" ref="fleet.fleet_vehicle_odometer_view_tree" />
<field name="arch" type="xml">
<xpath expr="//field[@name='driver_id']" position="after">
<field name="driver_employee_id" widget="many2one_avatar" optional="hide"/>
</xpath>
</field>
</record>
<record id="fleet_vehicle_assignation_log_view_list" model="ir.ui.view">
<field name="name">fleet.vehicle.assignation.log.view.tree.inherit.hr.fleet</field>
<field name="model">fleet.vehicle.assignation.log</field>
@@ -10,9 +21,7 @@
<button name="action_get_attachment_view" string="Attachments" type="object" icon="fa-paperclip"/>
</xpath>
<xpath expr="//field[@name='driver_id']" position="after">
<field name="fleet_is_internal" invisible="1"/>
<field name="driver_employee_id" widget="many2one_avatar"
attrs="{'invisible': [('fleet_is_internal', '=', False)]}"/>
<field name="driver_employee_id" widget="many2one_avatar" optional="hide"/>
</xpath>
</field>
</record>
@@ -23,44 +32,20 @@
<field name="inherit_id" ref="fleet.fleet_vehicle_view_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='driver_id']" position="after">
<field name="fleet_is_internal" invisible="1"/>
<field name="driver_employee_id" string="Driver"
attrs="{'invisible': [('fleet_is_internal', '=', False)]}"/>
<field name="driver_employee_id" invisible="1"/>
<field name="mobility_card" readonly="1"/>
</xpath>
<xpath expr="//field[@name='future_driver_id']" position="after">
<field name="future_driver_employee_id" string="Future Driver"
attrs="{'invisible': [('fleet_is_internal', '=', False)]}"/>
</xpath>
<xpath expr="//field[@name='driver_id']" position="attributes">
<attribute name="attrs">{'invisible': [('fleet_is_internal', '=', True)]}</attribute>
</xpath>
<xpath expr="//field[@name='future_driver_id']" position="attributes">
<attribute name="attrs">{'invisible': [('fleet_is_internal', '=', True)]}</attribute>
</xpath>
</field>
</record>
<record id="fleet_vehicle_view_tree_inherit_hr" model="ir.ui.view">
<field name="name">fleet.vehicle.tree.inherit.hr</field>
<field name="model">fleet.vehicle</field>
<field name="inherit_id" ref="fleet.fleet_vehicle_view_tree"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='driver_id']" position="after">
<field name="fleet_is_internal" invisible="1"/>
<field name="driver_employee_id" widget="many2one_avatar" readonly="1"
invisible="'internal' in context and context.get('internal',False) == False"/>
</xpath>
<xpath expr="//field[@name='future_driver_id']" position="after">
<field name="future_driver_employee_id" widget="many2one_avatar" readonly="1"
invisible="'internal' in context and context.get('internal',False) == False"/>
</xpath>
<xpath expr="//field[@name='driver_id']" position="attributes">
<attribute name="invisible">'internal' in context and context.get('internal',False)</attribute>
</xpath>
<xpath expr="//field[@name='future_driver_id']" position="attributes">
<attribute name="invisible">'internal' in context and context.get('internal',False)</attribute>
<field name="future_driver_employee_id" invisible="1"/>
</xpath>
<button name="open_assignation_logs" position="before">
<button name="action_open_employee" type="object" class="oe_stat_button" icon="fa-id-card-o" groups="hr.group_hr_user" attrs="{'invisible': [('driver_employee_id', '=', False)]}">
<div class="o_field_widget o_stat_info">
<span class="o_stat_value"><field name="driver_employee_name"/></span>
<span class="o_stat_text">Employee</span>
</div>
</button>
</button>
</field>
</record>
-1
View File
@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import fleet_convert_wizard
from . import hr_departure_wizard
@@ -1,95 +0,0 @@
# -*- 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
class FleetConvertWizard(models.TransientModel):
_name = 'hr.fleet.convert.wizard'
_description = 'Convert external fleet to internal'
@api.model
def default_get(self, field_list=None):
if self.env['fleet.category'].browse(self.env.context.get('active_id', None)).internal:
raise UserError(_('This wizard can only be executed if the \'internal\' checkbox is unchecked.'))
return super().default_get(field_list)
def _default_line_ids(self):
fleet_id = self.env['fleet.category'].browse(self.env.context.get('active_id', False))
line_ids = []
if fleet_id:
lines = self.env['hr.fleet.convert.wizard.line']
vehicles = fleet_id.vehicle_ids.filtered(lambda v: v.driver_id and not v.driver_employee_id)
partners = vehicles.driver_id | vehicles.future_driver_id
employees = self.env['hr.employee'].search([
('address_home_id', 'in', partners.ids)
])
partner_employee_map = \
{partner: employees.filtered(lambda e: e.address_home_id == partner) for partner in partners}
for vehicle in vehicles:
lines |= lines.new({
'vehicle_id': vehicle,
'employee_id': partner_employee_map[vehicle.driver_id][0] \
if vehicle.driver_id and partner_employee_map[vehicle.driver_id] else False,
'future_employee_id': partner_employee_map[vehicle.future_driver_id][0] \
if vehicle.future_driver_id and partner_employee_map[vehicle.future_driver_id] else False,
})
# Order invalids first, _order doesn't seem to affect the view
line_ids = lines.sorted(key=lambda l: (
not l.invalid_driver, not l.invalid_future_driver, l.license_plate
)
)
return line_ids
fleet_id = fields.Many2one(
'fleet.category',
string='Fleet',
default=lambda self: self.env.context.get('active_id', None),
readonly=True,
)
line_ids = fields.One2many(
'hr.fleet.convert.wizard.line',
'convert_wizard_id',
default=_default_line_ids,
)
def action_validate(self):
for wizard in self:
lines = wizard.line_ids
wizard.fleet_id.write({'internal': True})
lines.filtered(lambda l: l.invalid_driver).vehicle_id.write({'driver_id': False})
lines.filtered(lambda l: l.invalid_future_driver).vehicle_id.write({'future_driver_id': False})
for line in lines.filtered(lambda l: not l.invalid_driver):
line.vehicle_id.write({
'driver_employee_id': line.employee_id.id,
'future_driver_employee_id': line.future_employee_id.id,
})
return True
class FleetConvertWizardLine(models.TransientModel):
_name = 'hr.fleet.convert.wizard.line'
_description = 'External fleet conversion data'
_order = 'invalid_driver asc'
convert_wizard_id = fields.Many2one('hr.fleet.convert.wizard', required=True, ondelete='cascade')
vehicle_id = fields.Many2one('fleet.vehicle', required=False, ondelete='cascade')
vehicle_name = fields.Char(related='vehicle_id.name', readonly=True)
license_plate = fields.Char(related='vehicle_id.license_plate')
driver_id = fields.Many2one(related='vehicle_id.driver_id')
future_driver_id = fields.Many2one(related='vehicle_id.future_driver_id')
employee_id = fields.Many2one('hr.employee', string='Employee')
future_employee_id = fields.Many2one('hr.employee', string='Future Employee')
invalid_driver = fields.Boolean(compute='_compute_invalid')
invalid_future_driver = fields.Boolean(compute='_compute_invalid')
@api.depends('employee_id', 'future_employee_id')
def _compute_invalid(self):
invalids = self.filtered(lambda r: not r.employee_id or (r.employee_id and not r.employee_id.address_home_id))
valids = (self - invalids)
invalids.write({'invalid_driver': True})
valids.write({'invalid_driver': False})
invalids = self.filtered(lambda r: (r.future_driver_id and not r.future_employee_id) or \
(r.future_employee_id and not r.future_employee_id.address_home_id))
valids = (self - invalids)
invalids.write({'invalid_future_driver': True})
valids.write({'invalid_future_driver': False})
@@ -1,45 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="hr_fleet_convert_wizard_view_form" model="ir.ui.view">
<field name="name">hr.fleet.convert.wizard.view.form</field>
<field name="model">hr.fleet.convert.wizard</field>
<field name="arch" type="xml">
<form string="Convert Fleet">
<sheet>
<group>
<field name="fleet_id" widget="many2one_fleet_category" options="{'no_open': True}"/>
</group>
<field name="line_ids">
<tree decoration-danger="invalid_driver" decoration-warning="invalid_future_driver"
create="false" delete="false" editable="bottom">
<field name="invalid_driver" invisible="1"/>
<field name="invalid_future_driver" invisible="1"/>
<field name="convert_wizard_id" invisible="1"/>
<field name="vehicle_id" invisible="1"/>
<field name="vehicle_name" string="Vehicle"/>
<field name="license_plate"/>
<field name="driver_id" options="{'no_open': True}"/>
<field name="future_driver_id" options="{'no_open': True}"/>
<field name="employee_id" options="{'no_create': True, 'no_open': True}"/>
<field name="future_employee_id" options="{'no_create': True, 'no_open': True}"/>
</tree>
</field>
</sheet>
<footer>
<button name="action_validate" string="Apply" type="object" class="oe_highlight"/>
<button string="Cancel" class="btn-secondary" special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="hr_fleet_convert_wizard_action" model="ir.actions.act_window">
<field name="name">Convert Fleet</field>
<field name="res_model">hr.fleet.convert.wizard</field>
<field name="view_mode">form</field>
<field name="target">new</field>
<field name="binding_model_id" ref="model_fleet_category"/>
<field name="binding_view_types">form</field>
<field name="groups_id" eval="[(4, ref('base.group_no_one'))]"/>
</record>
</odoo>