[MERGE] hr_payroll: Payroll module refactoring for salary configurator
Thanks to: - Nicolas Michaux for the design part and his expertise. - Laura Piraux for all her advices, reviews, ideas and kindness. - Thibaut Delavallée for the technical advices. - My mother for her pies. - Martin Trigaux that did nothing.
This commit is contained in:
@@ -31,7 +31,9 @@ Main Features
|
||||
'data': [
|
||||
'security/fleet_security.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'views/fleet_view.xml',
|
||||
'views/fleet_vehicle_model_views.xml',
|
||||
'views/fleet_vehicle_views.xml',
|
||||
'views/fleet_vehicle_cost_views.xml',
|
||||
'views/fleet_board_view.xml',
|
||||
'data/fleet_cars_data.xml',
|
||||
'data/fleet_data.xml',
|
||||
|
||||
@@ -1 +1,6 @@
|
||||
import fleet
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import fleet_vehicle
|
||||
import fleet_vehicle_cost
|
||||
import fleet_vehicle_model
|
||||
|
||||
@@ -1,658 +0,0 @@
|
||||
# -*- 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, tools, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class FleetVehicleCost(models.Model):
|
||||
_name = 'fleet.vehicle.cost'
|
||||
_description = 'Cost related to a vehicle'
|
||||
_order = 'date desc, vehicle_id asc'
|
||||
|
||||
name = fields.Char(related='vehicle_id.name', string='Name', store=True)
|
||||
vehicle_id = fields.Many2one('fleet.vehicle', 'Vehicle', required=True, help='Vehicle concerned by this log')
|
||||
cost_subtype_id = fields.Many2one('fleet.service.type', 'Type', help='Cost type purchased with this cost')
|
||||
amount = fields.Float('Total Price')
|
||||
cost_type = fields.Selection([('contract', 'Contract'), ('services', 'Services'), ('fuel', 'Fuel'), ('other', 'Other')],
|
||||
'Category of the cost', default="other", help='For internal purpose only', required=True)
|
||||
parent_id = fields.Many2one('fleet.vehicle.cost', 'Parent', help='Parent cost to this current cost')
|
||||
cost_ids = fields.One2many('fleet.vehicle.cost', 'parent_id', 'Included Services')
|
||||
odometer_id = fields.Many2one('fleet.vehicle.odometer', 'Odometer', help='Odometer measure of the vehicle at the moment of this log')
|
||||
odometer = fields.Float(compute="_get_odometer", inverse='_set_odometer', string='Odometer Value', help='Odometer measure of the vehicle at the moment of this log')
|
||||
odometer_unit = fields.Selection(related='vehicle_id.odometer_unit', string="Unit", readonly=True)
|
||||
date = fields.Date(help='Date when the cost has been executed')
|
||||
contract_id = fields.Many2one('fleet.vehicle.log.contract', 'Contract', help='Contract attached to this cost')
|
||||
auto_generated = fields.Boolean('Automatically Generated', readonly=True)
|
||||
|
||||
def _get_odometer(self):
|
||||
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
|
||||
def create(self, data):
|
||||
#make sure that the data are consistent with values of parent and contract records given
|
||||
if 'parent_id' in data and data['parent_id']:
|
||||
parent = self.browse(data['parent_id'])
|
||||
data['vehicle_id'] = parent.vehicle_id.id
|
||||
data['date'] = parent.date
|
||||
data['cost_type'] = parent.cost_type
|
||||
if 'contract_id' in data and data['contract_id']:
|
||||
contract = self.env['fleet.vehicle.log.contract'].browse(data['contract_id'])
|
||||
data['vehicle_id'] = contract.vehicle_id.id
|
||||
data['cost_subtype_id'] = contract.cost_subtype_id.id
|
||||
data['cost_type'] = contract.cost_type
|
||||
if 'odometer' in data and not data['odometer']:
|
||||
#if received value for odometer is 0, then remove it from the data as it would result to the creation of a
|
||||
#odometer log with 0, which is to be avoided
|
||||
del(data['odometer'])
|
||||
return super(FleetVehicleCost, self).create(data)
|
||||
|
||||
class FleetVehicleTag(models.Model):
|
||||
_name = 'fleet.vehicle.tag'
|
||||
|
||||
name = fields.Char(required=True, translate=True)
|
||||
color = fields.Integer('Color Index', default=10)
|
||||
|
||||
_sql_constraints = [('name_uniq', 'unique (name)', "Tag name already exists !")]
|
||||
|
||||
class FleetVehicleState(models.Model):
|
||||
_name = 'fleet.vehicle.state'
|
||||
_order = 'sequence asc'
|
||||
|
||||
name = fields.Char(required=True)
|
||||
sequence = fields.Integer(help="Used to order the note stages")
|
||||
|
||||
_sql_constraints = [('fleet_state_name_unique', 'unique(name)', 'State name already exists')]
|
||||
|
||||
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', 'Make', required=True, help='Make of the vehicle')
|
||||
vendors = fields.Many2many('res.partner', 'fleet_vehicle_model_vendors', 'model_id', 'partner_id', string='Vendors')
|
||||
image = fields.Binary(related='brand_id.image', string="Logo")
|
||||
image_medium = fields.Binary(related='brand_id.image_medium', string="Logo (medium)")
|
||||
image_small = fields.Binary(related='brand_id.image_small', string="Logo (small)")
|
||||
|
||||
@api.multi
|
||||
@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
|
||||
|
||||
@api.onchange('brand_id')
|
||||
def _onchange_brand(self):
|
||||
if self.brand_id:
|
||||
self.image_medium = self.brand_id.image
|
||||
else:
|
||||
self.image_medium = False
|
||||
|
||||
class FleetVehicleModelBrand(models.Model):
|
||||
_name = 'fleet.vehicle.model.brand'
|
||||
_description = 'Brand model of the vehicle'
|
||||
_order = 'name asc'
|
||||
|
||||
name = fields.Char('Make', required=True)
|
||||
image = fields.Binary("Logo", attachment=True,
|
||||
help="This field holds the image used as logo for the brand, limited to 1024x1024px.")
|
||||
image_medium = fields.Binary("Medium-sized image", attachment=True,
|
||||
help="Medium-sized logo of the brand. It is automatically "
|
||||
"resized as a 128x128px image, with aspect ratio preserved. "
|
||||
"Use this field in form views or some kanban views.")
|
||||
image_small = fields.Binary("Small-sized image", attachment=True,
|
||||
help="Small-sized logo of the brand. It is automatically "
|
||||
"resized as a 64x64px image, with aspect ratio preserved. "
|
||||
"Use this field anywhere a small image is required.")
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
tools.image_resize_images(vals)
|
||||
return super(FleetVehicleModelBrand, self).create(vals)
|
||||
|
||||
@api.multi
|
||||
def write(self, vals):
|
||||
tools.image_resize_images(vals)
|
||||
return super(FleetVehicleModelBrand, self).write(vals)
|
||||
|
||||
class FleetVehicle(models.Model):
|
||||
_inherit = 'mail.thread'
|
||||
_name = 'fleet.vehicle'
|
||||
_description = 'Information on a vehicle'
|
||||
_order = 'license_plate asc'
|
||||
|
||||
def _get_default_state(self):
|
||||
state = self.env.ref('fleet.vehicle_state_active', raise_if_not_found=False)
|
||||
return state and state.id or False
|
||||
|
||||
name = fields.Char(compute="_compute_vehicle_name", store=True)
|
||||
active = fields.Boolean(default=True)
|
||||
company_id = fields.Many2one('res.company', 'Company')
|
||||
license_plate = fields.Char(required=True, help='License plate number of the vehicle (i = plate number for a car)')
|
||||
vin_sn = fields.Char('Chassis Number', help='Unique number written on the vehicle motor (VIN/SN number)', copy=False)
|
||||
driver_id = fields.Many2one('res.partner', 'Driver', help='Driver of the vehicle')
|
||||
model_id = fields.Many2one('fleet.vehicle.model', 'Model', required=True, help='Model of the vehicle')
|
||||
log_fuel = fields.One2many('fleet.vehicle.log.fuel', 'vehicle_id', 'Fuel Logs')
|
||||
log_services = fields.One2many('fleet.vehicle.log.services', 'vehicle_id', 'Services Logs')
|
||||
log_contracts = fields.One2many('fleet.vehicle.log.contract', 'vehicle_id', 'Contracts')
|
||||
cost_count = fields.Integer(compute="_compute_count_all", string="Costs")
|
||||
contract_count = fields.Integer(compute="_compute_count_all", string='Contracts')
|
||||
service_count = fields.Integer(compute="_compute_count_all", string='Services')
|
||||
fuel_logs_count = fields.Integer(compute="_compute_count_all", string='Fuel Logs')
|
||||
odometer_count = fields.Integer(compute="_compute_count_all", string='Odometer')
|
||||
acquisition_date = fields.Date('Acquisition Date', required=False, help='Date when the vehicle has been bought')
|
||||
color = fields.Char(help='Color of the vehicle')
|
||||
state_id = fields.Many2one('fleet.vehicle.state', 'State', default=_get_default_state, help='Current state of the vehicle', ondelete="set null")
|
||||
location = fields.Char(help='Location of the vehicle (garage, ...)')
|
||||
seats = fields.Integer('Seats Number', help='Number of seats of the vehicle')
|
||||
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', 'Kilometers'), ('miles', 'Miles')],
|
||||
'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'), ('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 = fields.Binary(related='model_id.image', string="Logo")
|
||||
image_medium = fields.Binary(related='model_id.image_medium', string="Logo (medium)")
|
||||
image_small = fields.Binary(related='model_id.image_small', string="Logo (small)")
|
||||
contract_renewal_due_soon = fields.Boolean(compute='_compute_contract_reminder', search='_search_contract_renewal_due_soon', string='Has Contracts to renew', multi='contract_info')
|
||||
contract_renewal_overdue = fields.Boolean(compute='_compute_contract_reminder', search='_search_get_overdue_contract_reminder', string='Has Contracts Overdue', multi='contract_info')
|
||||
contract_renewal_name = fields.Text(compute='_compute_contract_reminder', string='Name of contract to renew soon', multi='contract_info')
|
||||
contract_renewal_total = fields.Text(compute='_compute_contract_reminder', string='Total of contracts due or overdue minus one', multi='contract_info')
|
||||
car_value = fields.Float(help='Value of the bought vehicle')
|
||||
|
||||
@api.depends('model_id', 'license_plate')
|
||||
def _compute_vehicle_name(self):
|
||||
for record in self:
|
||||
record.name = record.model_id.brand_id.name + '/' + record.model_id.name + '/' + record.license_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']
|
||||
LogFuel = self.env['fleet.vehicle.log.fuel']
|
||||
LogService = self.env['fleet.vehicle.log.services']
|
||||
LogContract = self.env['fleet.vehicle.log.contract']
|
||||
Cost = self.env['fleet.vehicle.cost']
|
||||
for record in self:
|
||||
record.odometer_count = Odometer.search_count([('vehicle_id', '=', record.id)])
|
||||
record.fuel_logs_count = LogFuel.search_count([('vehicle_id', '=', record.id)])
|
||||
record.service_count = LogService.search_count([('vehicle_id', '=', record.id)])
|
||||
record.contract_count = LogContract.search_count([('vehicle_id', '=', record.id)])
|
||||
record.cost_count = Cost.search_count([('vehicle_id', '=', record.id), ('parent_id', '=', False)])
|
||||
|
||||
@api.depends('log_contracts')
|
||||
def _compute_contract_reminder(self):
|
||||
for record in self:
|
||||
overdue = False
|
||||
due_soon = False
|
||||
total = 0
|
||||
name = ''
|
||||
for element in record.log_contracts:
|
||||
if element.state in ('open', 'toclose') 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 < 15 and diff_time >= 0:
|
||||
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', 'toclose'))],
|
||||
limit=1, order='expiration_date asc')
|
||||
if log_contract:
|
||||
#we display only the name of the oldest overdue/due soon contract
|
||||
name = log_contract.cost_subtype_id.name
|
||||
|
||||
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 _search_contract_renewal_due_soon(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)
|
||||
datetime_today = fields.Datetime.from_string(today)
|
||||
limit_date = fields.Datetime.to_string(datetime_today + relativedelta(days=+15))
|
||||
self.env.cr.execute("""SELECT cost.vehicle_id,
|
||||
count(contract.id) AS contract_number
|
||||
FROM fleet_vehicle_cost cost
|
||||
LEFT JOIN fleet_vehicle_log_contract contract ON contract.cost_id = cost.id
|
||||
WHERE contract.expiration_date IS NOT NULL
|
||||
AND contract.expiration_date > %s
|
||||
AND contract.expiration_date < %s
|
||||
AND contract.state IN ('open', 'toclose')
|
||||
GROUP BY cost.vehicle_id""", (today, limit_date))
|
||||
res_ids = [x[0] for x in self.env.cr.fetchall()]
|
||||
res.append(('id', search_operator, res_ids))
|
||||
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)
|
||||
self.env.cr.execute('''SELECT cost.vehicle_id,
|
||||
count(contract.id) AS contract_number
|
||||
FROM fleet_vehicle_cost cost
|
||||
LEFT JOIN fleet_vehicle_log_contract contract ON contract.cost_id = cost.id
|
||||
WHERE contract.expiration_date IS NOT NULL
|
||||
AND contract.expiration_date < %s
|
||||
AND contract.state IN ('open', 'toclose')
|
||||
GROUP BY cost.vehicle_id ''', (today,))
|
||||
res_ids = [x[0] for x in self.env.cr.fetchall()]
|
||||
res.append(('id', search_operator, res_ids))
|
||||
return res
|
||||
|
||||
@api.onchange('model_id')
|
||||
def _onchange_model(self):
|
||||
if self.model_id:
|
||||
self.image_medium = self.model_id.image
|
||||
else:
|
||||
self.image_medium = False
|
||||
|
||||
@api.model
|
||||
def create(self, data):
|
||||
vehicle = super(FleetVehicle, self.with_context(mail_create_nolog=True)).create(data)
|
||||
vehicle.message_post(body=_('%s %s has been added to the fleet!') % (vehicle.model_id.name, vehicle.license_plate))
|
||||
return vehicle
|
||||
|
||||
@api.multi
|
||||
def write(self, vals):
|
||||
"""
|
||||
This function write an entry in the openchatter whenever we change important information
|
||||
on the vehicle like the model, the drive, the state of the vehicle or its license plate
|
||||
"""
|
||||
for vehicle in self:
|
||||
changes = []
|
||||
if 'model_id' in vals and vehicle.model_id.id != vals['model_id']:
|
||||
value = self.env['fleet.vehicle.model'].browse(vals['model_id']).name
|
||||
oldmodel = vehicle.model_id.name or _('None')
|
||||
changes.append(_("Model: from '%s' to '%s'") % (oldmodel, value))
|
||||
if 'driver_id' in vals and vehicle.driver_id.id != vals['driver_id']:
|
||||
value = self.env['res.partner'].browse(vals['driver_id']).name
|
||||
olddriver = (vehicle.driver_id.name) or _('None')
|
||||
changes.append(_("Driver: from '%s' to '%s'") % (olddriver, value))
|
||||
if 'state_id' in vals and vehicle.state_id.id != vals['state_id']:
|
||||
value = self.env['fleet.vehicle.state'].browse(vals['state_id']).name
|
||||
oldstate = vehicle.state_id.name or _('None')
|
||||
changes.append(_("State: from '%s' to '%s'") % (oldstate, value))
|
||||
if 'license_plate' in vals and vehicle.license_plate != vals['license_plate']:
|
||||
old_license_plate = vehicle.license_plate or _('None')
|
||||
changes.append(_("License Plate: from '%s' to '%s'") % (old_license_plate, vals['license_plate']))
|
||||
|
||||
if len(changes) > 0:
|
||||
self.message_post(body=", ".join(changes))
|
||||
|
||||
return super(FleetVehicle, self).write(vals)
|
||||
|
||||
@api.multi
|
||||
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', 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
|
||||
|
||||
@api.multi
|
||||
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()
|
||||
res = self.env['ir.actions.act_window'].for_xml_id('fleet', 'fleet_vehicle_costs_action')
|
||||
res.update(
|
||||
context=dict(self.env.context, default_vehicle_id=self.id, search_default_parent_false=True),
|
||||
domain=[('vehicle_id', '=', self.id)]
|
||||
)
|
||||
return res
|
||||
|
||||
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)
|
||||
|
||||
@api.depends('vehicle_id', 'date')
|
||||
def _compute_vehicle_log_name(self):
|
||||
for record in self:
|
||||
name = record.vehicle_id.name
|
||||
if not name:
|
||||
name = record.date
|
||||
elif record.date:
|
||||
name += ' / ' + record.date
|
||||
self.name = name
|
||||
|
||||
@api.onchange('vehicle_id')
|
||||
def _onchange_vehicle(self):
|
||||
if self.vehicle_id:
|
||||
self.unit = self.vehicle_id.odometer_unit
|
||||
|
||||
class FleetVehicleLogFuel(models.Model):
|
||||
_name = 'fleet.vehicle.log.fuel'
|
||||
_description = 'Fuel log for vehicles'
|
||||
_inherits = {'fleet.vehicle.cost': 'cost_id'}
|
||||
|
||||
@api.model
|
||||
def default_get(self, default_fields):
|
||||
res = super(FleetVehicleLogFuel, self).default_get(default_fields)
|
||||
service = self.env.ref('fleet.type_service_refueling', raise_if_not_found=False)
|
||||
res.update({
|
||||
'date': fields.Date.context_today(self),
|
||||
'cost_subtype_id': service and service.id or False,
|
||||
'cost_type': 'fuel'
|
||||
})
|
||||
return res
|
||||
|
||||
liter = fields.Float()
|
||||
price_per_liter = fields.Float()
|
||||
purchaser_id = fields.Many2one('res.partner', 'Purchaser', domain="['|',('customer','=',True),('employee','=',True)]")
|
||||
inv_ref = fields.Char('Invoice Reference', size=64)
|
||||
vendor_id = fields.Many2one('res.partner', 'Vendor', domain="[('supplier','=',True)]")
|
||||
notes = fields.Text()
|
||||
cost_id = fields.Many2one('fleet.vehicle.cost', 'Cost', required=True, ondelete='cascade')
|
||||
cost_amount = fields.Float(related='cost_id.amount', string='Amount', store=True) # we need to keep this field as a related with store=True because the graph view doesn't support (1) to address fields from inherited table and (2) fields that aren't stored in database
|
||||
|
||||
@api.onchange('vehicle_id')
|
||||
def _onchange_vehicle(self):
|
||||
if self.vehicle_id:
|
||||
self.odometer_unit = self.vehicle_id.odometer_unit
|
||||
self.purchaser_id = self.vehicle_id.driver_id.id
|
||||
|
||||
@api.onchange('liter', 'price_per_liter', 'amount')
|
||||
def _onchange_liter_price_amount(self):
|
||||
#need to cast in float because the value receveid from web client maybe an integer (Javascript and JSON do not
|
||||
#make any difference between 3.0 and 3). This cause a problem if you encode, for example, 2 liters at 1.5 per
|
||||
#liter => total is computed as 3.0, then trigger an onchange that recomputes price_per_liter as 3/2=1 (instead
|
||||
#of 3.0/2=1.5)
|
||||
#If there is no change in the result, we return an empty dict to prevent an infinite loop due to the 3 intertwine
|
||||
#onchange. And in order to verify that there is no change in the result, we have to limit the precision of the
|
||||
#computation to 2 decimal
|
||||
liter = float(self.liter)
|
||||
price_per_liter = float(self.price_per_liter)
|
||||
amount = float(self.amount)
|
||||
if liter > 0 and price_per_liter > 0 and round(liter * price_per_liter, 2) != amount:
|
||||
self.amount = round(liter * price_per_liter, 2)
|
||||
elif amount > 0 and liter > 0 and round(amount / liter, 2) != price_per_liter:
|
||||
self.price_per_liter = round(amount / liter, 2)
|
||||
elif amount > 0 and price_per_liter > 0 and round(amount / price_per_liter, 2) != liter:
|
||||
self.liter = round(amount / price_per_liter, 2)
|
||||
|
||||
class FleetVehicleLogServices(models.Model):
|
||||
_name = 'fleet.vehicle.log.services'
|
||||
_inherits = {'fleet.vehicle.cost': 'cost_id'}
|
||||
_description = 'Services for vehicles'
|
||||
|
||||
@api.model
|
||||
def default_get(self, default_fields):
|
||||
res = super(FleetVehicleLogServices, self).default_get(default_fields)
|
||||
service = self.env.ref('fleet.type_service_service_8', raise_if_not_found=False)
|
||||
res.update({
|
||||
'date': fields.Date.context_today(self),
|
||||
'cost_subtype_id': service and service.id or False,
|
||||
'cost_type': 'services'
|
||||
})
|
||||
return res
|
||||
|
||||
purchaser_id = fields.Many2one('res.partner', 'Purchaser', domain="['|',('customer','=',True),('employee','=',True)]")
|
||||
inv_ref = fields.Char('Invoice Reference')
|
||||
vendor_id = fields.Many2one('res.partner', 'Vendor', domain="[('supplier','=',True)]")
|
||||
# we need to keep this field as a related with store=True because the graph view doesn't support
|
||||
# (1) to address fields from inherited table and (2) fields that aren't stored in database
|
||||
cost_amount = fields.Float(related='cost_id.amount', string='Amount', store=True)
|
||||
notes = fields.Text()
|
||||
cost_id = fields.Many2one('fleet.vehicle.cost', 'Cost', required=True, ondelete='cascade')
|
||||
|
||||
@api.onchange('vehicle_id')
|
||||
def _onchange_vehicle(self):
|
||||
if self.vehicle_id:
|
||||
self.odometer_unit = self.vehicle_id.odometer_unit
|
||||
self.purchaser_id = self.vehicle_id.driver_id.id
|
||||
|
||||
class FleetServiceType(models.Model):
|
||||
_name = 'fleet.service.type'
|
||||
_description = 'Type of services available on a vehicle'
|
||||
|
||||
name = fields.Char(required=True, translate=True)
|
||||
category = fields.Selection([('contract', 'Contract'), ('service', 'Service'), ('both', 'Both')], 'Category',
|
||||
required=True, help='Choose wheter the service refer to contracts, vehicle services or both')
|
||||
|
||||
class FleetVehicleLogContract(models.Model):
|
||||
|
||||
_inherits = {'fleet.vehicle.cost': 'cost_id'}
|
||||
_name = 'fleet.vehicle.log.contract'
|
||||
_description = 'Contract information on a vehicle'
|
||||
_order = 'state desc,expiration_date'
|
||||
|
||||
def compute_next_year_date(self, strdate):
|
||||
oneyear = relativedelta(years=1)
|
||||
start_date = fields.Date.from_string(strdate)
|
||||
return fields.Date.to_string(start_date + oneyear)
|
||||
|
||||
@api.model
|
||||
def default_get(self, default_fields):
|
||||
res = super(FleetVehicleLogContract, self).default_get(default_fields)
|
||||
contract = self.env.ref('fleet.type_contract_leasing', raise_if_not_found=False)
|
||||
res.update({
|
||||
'date': fields.Date.context_today(self),
|
||||
'cost_subtype_id': contract and contract.id or False,
|
||||
'cost_type': 'contract'
|
||||
})
|
||||
return res
|
||||
|
||||
name = fields.Text(compute='_compute_contract_name', store=True)
|
||||
active = fields.Boolean(default=True)
|
||||
start_date = fields.Date('Contract Start Date', default=fields.Date.context_today, help='Date when the coverage of the contract begins')
|
||||
expiration_date = fields.Date('Contract Expiration Date', default=lambda self: self.compute_next_year_date(fields.Date.context_today(self)),
|
||||
help='Date when the coverage of the contract expirates (by default, one year after begin date)')
|
||||
days_left = fields.Integer(compute='_compute_days_left', string='Warning Date')
|
||||
insurer_id = fields.Many2one('res.partner', 'Vendor')
|
||||
purchaser_id = fields.Many2one('res.partner', 'Contractor', default=lambda self: self.env.user.partner_id.id, help='Person to which the contract is signed for')
|
||||
ins_ref = fields.Char('Contract Reference', size=64, copy=False)
|
||||
state = fields.Selection([('open', 'In Progress'), ('toclose', 'To Close'), ('closed', 'Terminated')],
|
||||
'Status', default='open', readonly=True, help='Choose wheter the contract is still valid or not',
|
||||
copy=False)
|
||||
notes = fields.Text('Terms and Conditions', help='Write here all supplementary information relative to this contract', copy=False)
|
||||
cost_generated = fields.Float('Recurring Cost Amount', help="Costs paid at regular intervals, depending on the cost frequency."
|
||||
"If the cost frequency is set to unique, the cost will be logged at the start date")
|
||||
cost_frequency = fields.Selection([('no', 'No'), ('daily', 'Daily'), ('weekly', 'Weekly'), ('monthly', 'Monthly'), ('yearly', 'Yearly')], 'Recurring Cost Frequency',
|
||||
default='no', help='Frequency of the recuring cost', required=True)
|
||||
generated_cost_ids = fields.One2many('fleet.vehicle.cost', 'contract_id', 'Generated Costs')
|
||||
sum_cost = fields.Float(compute='_compute_sum_cost', string='Indicative Costs Total')
|
||||
cost_id = fields.Many2one('fleet.vehicle.cost', 'Cost', required=True, ondelete='cascade')
|
||||
cost_amount = fields.Float(related='cost_id.amount', string='Amount', store=True) # we need to keep this field as a related with store=True because the graph view doesn't support (1) to address fields from inherited table and (2) fields that aren't stored in database
|
||||
|
||||
@api.depends('vehicle_id', 'cost_subtype_id', 'date')
|
||||
def _compute_contract_name(self):
|
||||
for record in self:
|
||||
name = record.vehicle_id.name
|
||||
if record.cost_subtype_id.name:
|
||||
name += ' / ' + record.cost_subtype_id.name
|
||||
if record.date:
|
||||
name += ' / ' + record.date
|
||||
record.name = name
|
||||
|
||||
@api.depends('expiration_date', 'state')
|
||||
def _compute_days_left(self):
|
||||
"""return a dict with as value for each contract an integer
|
||||
if contract is in an open state and is overdue, return 0
|
||||
if contract is in a closed state, return -1
|
||||
otherwise return the number of days before the contract expires
|
||||
"""
|
||||
for record in self:
|
||||
if (record.expiration_date and (record.state == 'open' or record.state == 'toclose')):
|
||||
today = fields.Date.from_string(fields.Date.today())
|
||||
renew_date = fields.Date.from_string(record.expiration_date)
|
||||
diff_time = (renew_date - today).days
|
||||
record.days_left = diff_time > 0 and diff_time or 0
|
||||
else:
|
||||
record.days_left = -1
|
||||
|
||||
@api.depends('cost_ids.amount')
|
||||
def _compute_sum_cost(self):
|
||||
for contract in self:
|
||||
contract.sum_cost = sum(contract.cost_ids.mapped('amount'))
|
||||
|
||||
@api.onchange('vehicle_id')
|
||||
def _onchange_vehicle(self):
|
||||
if self.vehicle_id:
|
||||
self.odometer_unit = self.vehicle_id.odometer_unit
|
||||
|
||||
@api.multi
|
||||
def contract_close(self):
|
||||
for record in self:
|
||||
record.state = 'closed'
|
||||
|
||||
@api.multi
|
||||
def contract_open(self):
|
||||
for record in self:
|
||||
record.state = 'open'
|
||||
|
||||
@api.multi
|
||||
def act_renew_contract(self):
|
||||
assert len(self.ids) == 1, "This operation should only be done for 1 single contract at a time, as it it suppose to open a window as result"
|
||||
for element in self:
|
||||
#compute end date
|
||||
startdate = fields.Date.from_string(element.start_date)
|
||||
enddate = fields.Date.from_string(element.expiration_date)
|
||||
diffdate = (enddate - startdate)
|
||||
default = {
|
||||
'date': fields.Date.context_today(self),
|
||||
'start_date': fields.Date.to_string(fields.Date.from_string(element.expiration_date) + relativedelta(days=1)),
|
||||
'expiration_date': fields.Date.to_string(enddate + diffdate),
|
||||
}
|
||||
newid = element.copy(default).id
|
||||
return {
|
||||
'name': _("Renew Contract"),
|
||||
'view_mode': 'form',
|
||||
'view_id': self.env.ref('fleet.fleet_vehicle_log_contract_view_form').id,
|
||||
'view_type': 'tree,form',
|
||||
'res_model': 'fleet.vehicle.log.contract',
|
||||
'type': 'ir.actions.act_window',
|
||||
'domain': '[]',
|
||||
'res_id': newid,
|
||||
'context': {'active_id': newid},
|
||||
}
|
||||
|
||||
@api.model
|
||||
def scheduler_manage_auto_costs(self):
|
||||
#This method is called by a cron task
|
||||
#It creates costs for contracts having the "recurring cost" field setted, depending on their frequency
|
||||
#For example, if a contract has a reccuring cost of 200 with a weekly frequency, this method creates a cost of 200 on the first day of each week, from the date of the last recurring costs in the database to today
|
||||
#If the contract has not yet any recurring costs in the database, the method generates the recurring costs from the start_date to today
|
||||
#The created costs are associated to a contract thanks to the many2one field contract_id
|
||||
#If the contract has no start_date, no cost will be created, even if the contract has recurring costs
|
||||
VehicleCost = self.env['fleet.vehicle.cost']
|
||||
deltas = {'yearly': relativedelta(years=+1), 'monthly': relativedelta(months=+1), 'weekly': relativedelta(weeks=+1), 'daily': relativedelta(days=+1)}
|
||||
contracts = self.env['fleet.vehicle.log.contract'].search([('state', '!=', 'closed')], offset=0, limit=None, order=None)
|
||||
for contract in contracts:
|
||||
if not contract.start_date or contract.cost_frequency == 'no':
|
||||
continue
|
||||
found = False
|
||||
last_cost_date = contract.start_date
|
||||
if contract.generated_cost_ids:
|
||||
last_autogenerated_cost = VehicleCost.search([('contract_id', '=', contract.id), ('auto_generated', '=', True)], offset=0, limit=1, order='date desc')
|
||||
if last_autogenerated_cost:
|
||||
found = True
|
||||
last_cost_date = last_autogenerated_cost.date
|
||||
startdate = fields.Date.from_string(last_cost_date)
|
||||
if found:
|
||||
startdate += deltas.get(contract.cost_frequency)
|
||||
today = fields.Date.from_string(fields.Date.context_today(self))
|
||||
while (startdate <= today) & (startdate <= fields.Date.from_string(contract.expiration_date)):
|
||||
data = {
|
||||
'amount': contract.cost_generated,
|
||||
'date': fields.Date.context_today(self),
|
||||
'vehicle_id': contract.vehicle_id.id,
|
||||
'cost_subtype_id': contract.cost_subtype_id.id,
|
||||
'contract_id': contract.id,
|
||||
'auto_generated': True
|
||||
}
|
||||
self.env['fleet.vehicle.cost'].create(data)
|
||||
startdate += deltas.get(contract.cost_frequency)
|
||||
return True
|
||||
|
||||
@api.model
|
||||
def scheduler_manage_contract_expiration(self):
|
||||
#This method is called by a cron task
|
||||
#It manages the state of a contract, possibly by posting a message on the vehicle concerned and updating its status
|
||||
date_today = fields.Date.from_string(fields.Date.context_today(self))
|
||||
limit_date = fields.Date.to_string(date_today + relativedelta(days=+15))
|
||||
contracts = self.search([('state', '=', 'open'), ('expiration_date', '<', limit_date)])
|
||||
res = {}
|
||||
for contract in contracts:
|
||||
if contract.vehicle_id.id in res:
|
||||
res[contract.vehicle_id.id] += 1
|
||||
else:
|
||||
res[contract.vehicle_id.id] = 1
|
||||
|
||||
Vehicle = self.env['fleet.vehicle']
|
||||
for vehicle, value in res.items():
|
||||
Vehicle.browse(vehicle).message_post(body=_('%s contract(s) need(s) to be renewed and/or closed!') % value)
|
||||
return contracts.write({'state': 'toclose'})
|
||||
|
||||
@api.model
|
||||
def run_scheduler(self):
|
||||
self.scheduler_manage_auto_costs()
|
||||
self.scheduler_manage_contract_expiration()
|
||||
@@ -0,0 +1,312 @@
|
||||
# -*- 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, _
|
||||
|
||||
|
||||
class FleetVehicle(models.Model):
|
||||
_inherit = 'mail.thread'
|
||||
_name = 'fleet.vehicle'
|
||||
_description = 'Information on a vehicle'
|
||||
_order = 'license_plate asc'
|
||||
|
||||
def _get_default_state(self):
|
||||
state = self.env.ref('fleet.vehicle_state_active', raise_if_not_found=False)
|
||||
return state and state.id or False
|
||||
|
||||
name = fields.Char(compute="_compute_vehicle_name", store=True)
|
||||
active = fields.Boolean(default=True)
|
||||
company_id = fields.Many2one('res.company', 'Company')
|
||||
license_plate = fields.Char(required=True, help='License plate number of the vehicle (i = plate number for a car)')
|
||||
vin_sn = fields.Char('Chassis Number', help='Unique number written on the vehicle motor (VIN/SN number)', copy=False)
|
||||
driver_id = fields.Many2one('res.partner', 'Driver', help='Driver of the vehicle')
|
||||
model_id = fields.Many2one('fleet.vehicle.model', 'Model', required=True, help='Model of the vehicle')
|
||||
log_fuel = fields.One2many('fleet.vehicle.log.fuel', 'vehicle_id', 'Fuel Logs')
|
||||
log_services = fields.One2many('fleet.vehicle.log.services', 'vehicle_id', 'Services Logs')
|
||||
log_contracts = fields.One2many('fleet.vehicle.log.contract', 'vehicle_id', 'Contracts')
|
||||
cost_count = fields.Integer(compute="_compute_count_all", string="Costs")
|
||||
contract_count = fields.Integer(compute="_compute_count_all", string='Contracts')
|
||||
service_count = fields.Integer(compute="_compute_count_all", string='Services')
|
||||
fuel_logs_count = fields.Integer(compute="_compute_count_all", string='Fuel Logs')
|
||||
odometer_count = fields.Integer(compute="_compute_count_all", string='Odometer')
|
||||
acquisition_date = fields.Date('Immatriculation Date', required=False, help='Date when the vehicle has been immatriculated')
|
||||
color = fields.Char(help='Color of the vehicle')
|
||||
state_id = fields.Many2one('fleet.vehicle.state', 'State', default=_get_default_state, help='Current state of the vehicle', ondelete="set null")
|
||||
location = fields.Char(help='Location of the vehicle (garage, ...)')
|
||||
seats = fields.Integer('Seats Number', help='Number of seats of the vehicle')
|
||||
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', 'Kilometers'),
|
||||
('miles', 'Miles')
|
||||
], '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'),
|
||||
('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 = fields.Binary(related='model_id.image', string="Logo")
|
||||
image_medium = fields.Binary(related='model_id.image_medium', string="Logo (medium)")
|
||||
image_small = fields.Binary(related='model_id.image_small', string="Logo (small)")
|
||||
contract_renewal_due_soon = fields.Boolean(compute='_compute_contract_reminder', search='_search_contract_renewal_due_soon',
|
||||
string='Has Contracts to renew', multi='contract_info')
|
||||
contract_renewal_overdue = fields.Boolean(compute='_compute_contract_reminder', search='_search_get_overdue_contract_reminder',
|
||||
string='Has Contracts Overdue', multi='contract_info')
|
||||
contract_renewal_name = fields.Text(compute='_compute_contract_reminder', string='Name of contract to renew soon', multi='contract_info')
|
||||
contract_renewal_total = fields.Text(compute='_compute_contract_reminder', string='Total of contracts due or overdue minus one',
|
||||
multi='contract_info')
|
||||
car_value = fields.Float(string="Catalog Value (VAT incl.)", help='Value of the bought vehicle')
|
||||
residual_value = fields.Float()
|
||||
|
||||
_sql_constraints = [
|
||||
('driver_id_unique', 'UNIQUE(driver_id)', 'Only one car can be assigned to the same employee!')
|
||||
]
|
||||
|
||||
@api.depends('model_id', 'license_plate')
|
||||
def _compute_vehicle_name(self):
|
||||
for record in self:
|
||||
record.name = record.model_id.brand_id.name + '/' + record.model_id.name + '/' + record.license_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']
|
||||
LogFuel = self.env['fleet.vehicle.log.fuel']
|
||||
LogService = self.env['fleet.vehicle.log.services']
|
||||
LogContract = self.env['fleet.vehicle.log.contract']
|
||||
Cost = self.env['fleet.vehicle.cost']
|
||||
for record in self:
|
||||
record.odometer_count = Odometer.search_count([('vehicle_id', '=', record.id)])
|
||||
record.fuel_logs_count = LogFuel.search_count([('vehicle_id', '=', record.id)])
|
||||
record.service_count = LogService.search_count([('vehicle_id', '=', record.id)])
|
||||
record.contract_count = LogContract.search_count([('vehicle_id', '=', record.id), ('state', '=', 'open')])
|
||||
record.cost_count = Cost.search_count([('vehicle_id', '=', record.id), ('parent_id', '=', False)])
|
||||
|
||||
@api.depends('log_contracts')
|
||||
def _compute_contract_reminder(self):
|
||||
for record in self:
|
||||
overdue = False
|
||||
due_soon = False
|
||||
total = 0
|
||||
name = ''
|
||||
for element in record.log_contracts:
|
||||
if element.state in ('open', 'toclose') 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 < 15 and diff_time >= 0:
|
||||
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', 'toclose'))
|
||||
], limit=1, order='expiration_date asc')
|
||||
if log_contract:
|
||||
# we display only the name of the oldest overdue/due soon contract
|
||||
name = log_contract.cost_subtype_id.name
|
||||
|
||||
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 _search_contract_renewal_due_soon(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)
|
||||
datetime_today = fields.Datetime.from_string(today)
|
||||
limit_date = fields.Datetime.to_string(datetime_today + relativedelta(days=+15))
|
||||
self.env.cr.execute("""SELECT cost.vehicle_id,
|
||||
count(contract.id) AS contract_number
|
||||
FROM fleet_vehicle_cost cost
|
||||
LEFT JOIN fleet_vehicle_log_contract contract ON contract.cost_id = cost.id
|
||||
WHERE contract.expiration_date IS NOT NULL
|
||||
AND contract.expiration_date > %s
|
||||
AND contract.expiration_date < %s
|
||||
AND contract.state IN ('open', 'toclose')
|
||||
GROUP BY cost.vehicle_id""", (today, limit_date))
|
||||
res_ids = [x[0] for x in self.env.cr.fetchall()]
|
||||
res.append(('id', search_operator, res_ids))
|
||||
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)
|
||||
self.env.cr.execute('''SELECT cost.vehicle_id,
|
||||
count(contract.id) AS contract_number
|
||||
FROM fleet_vehicle_cost cost
|
||||
LEFT JOIN fleet_vehicle_log_contract contract ON contract.cost_id = cost.id
|
||||
WHERE contract.expiration_date IS NOT NULL
|
||||
AND contract.expiration_date < %s
|
||||
AND contract.state IN ('open', 'toclose')
|
||||
GROUP BY cost.vehicle_id ''', (today,))
|
||||
res_ids = [x[0] for x in self.env.cr.fetchall()]
|
||||
res.append(('id', search_operator, res_ids))
|
||||
return res
|
||||
|
||||
@api.onchange('model_id')
|
||||
def _onchange_model(self):
|
||||
if self.model_id:
|
||||
self.image_medium = self.model_id.image
|
||||
else:
|
||||
self.image_medium = False
|
||||
|
||||
@api.model
|
||||
def create(self, data):
|
||||
vehicle = super(FleetVehicle, self.with_context(mail_create_nolog=True)).create(data)
|
||||
vehicle.message_post(body=_('%s %s has been added to the fleet!') % (vehicle.model_id.name, vehicle.license_plate))
|
||||
return vehicle
|
||||
|
||||
@api.multi
|
||||
def write(self, vals):
|
||||
"""
|
||||
This function write an entry in the openchatter whenever we change important information
|
||||
on the vehicle like the model, the drive, the state of the vehicle or its license plate
|
||||
"""
|
||||
for vehicle in self:
|
||||
changes = []
|
||||
if 'model_id' in vals and vehicle.model_id.id != vals['model_id']:
|
||||
value = self.env['fleet.vehicle.model'].browse(vals['model_id']).name
|
||||
oldmodel = vehicle.model_id.name or _('None')
|
||||
changes.append(_("Model: from '%s' to '%s'") % (oldmodel, value))
|
||||
if 'driver_id' in vals and vehicle.driver_id.id != vals['driver_id']:
|
||||
value = self.env['res.partner'].browse(vals['driver_id']).name
|
||||
olddriver = (vehicle.driver_id.name) or _('None')
|
||||
changes.append(_("Driver: from '%s' to '%s'") % (olddriver, value))
|
||||
if 'state_id' in vals and vehicle.state_id.id != vals['state_id']:
|
||||
value = self.env['fleet.vehicle.state'].browse(vals['state_id']).name
|
||||
oldstate = vehicle.state_id.name or _('None')
|
||||
changes.append(_("State: from '%s' to '%s'") % (oldstate, value))
|
||||
if 'license_plate' in vals and vehicle.license_plate != vals['license_plate']:
|
||||
old_license_plate = vehicle.license_plate or _('None')
|
||||
changes.append(_("License Plate: from '%s' to '%s'") % (old_license_plate, vals['license_plate']))
|
||||
|
||||
if len(changes) > 0:
|
||||
self.message_post(body=", ".join(changes))
|
||||
|
||||
return super(FleetVehicle, self).write(vals)
|
||||
|
||||
@api.multi
|
||||
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', 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
|
||||
|
||||
@api.multi
|
||||
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()
|
||||
res = self.env['ir.actions.act_window'].for_xml_id('fleet', 'fleet_vehicle_costs_action')
|
||||
res.update(
|
||||
context=dict(self.env.context, default_vehicle_id=self.id, search_default_parent_false=True),
|
||||
domain=[('vehicle_id', '=', self.id)]
|
||||
)
|
||||
return res
|
||||
|
||||
|
||||
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")
|
||||
|
||||
@api.depends('vehicle_id', 'date')
|
||||
def _compute_vehicle_log_name(self):
|
||||
for record in self:
|
||||
name = record.vehicle_id.name
|
||||
if not name:
|
||||
name = record.date
|
||||
elif record.date:
|
||||
name += ' / ' + record.date
|
||||
self.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'
|
||||
|
||||
name = fields.Char(required=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'
|
||||
|
||||
name = fields.Char(required=True, translate=True)
|
||||
color = fields.Integer('Color Index', default=10)
|
||||
|
||||
_sql_constraints = [('name_uniq', 'unique (name)', "Tag name already exists !")]
|
||||
|
||||
|
||||
class FleetServiceType(models.Model):
|
||||
_name = 'fleet.service.type'
|
||||
_description = 'Type of services available on a vehicle'
|
||||
|
||||
name = fields.Char(required=True, translate=True)
|
||||
category = fields.Selection([
|
||||
('contract', 'Contract'),
|
||||
('service', 'Service'),
|
||||
('both', 'Both')
|
||||
], 'Category', required=True, help='Choose wheter the service refer to contracts, vehicle services or both')
|
||||
@@ -0,0 +1,358 @@
|
||||
# -*- 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 FleetVehicleCost(models.Model):
|
||||
_name = 'fleet.vehicle.cost'
|
||||
_description = 'Cost related to a vehicle'
|
||||
_order = 'date desc, vehicle_id asc'
|
||||
|
||||
name = fields.Char(related='vehicle_id.name', string='Name', store=True)
|
||||
vehicle_id = fields.Many2one('fleet.vehicle', 'Vehicle', required=True, help='Vehicle concerned by this log')
|
||||
cost_subtype_id = fields.Many2one('fleet.service.type', 'Type', help='Cost type purchased with this cost')
|
||||
amount = fields.Float('Total Price')
|
||||
cost_type = fields.Selection([
|
||||
('contract', 'Contract'),
|
||||
('services', 'Services'),
|
||||
('fuel', 'Fuel'),
|
||||
('other', 'Other')
|
||||
], 'Category of the cost', default="other", help='For internal purpose only', required=True)
|
||||
parent_id = fields.Many2one('fleet.vehicle.cost', 'Parent', help='Parent cost to this current cost')
|
||||
cost_ids = fields.One2many('fleet.vehicle.cost', 'parent_id', 'Included Services', copy=True)
|
||||
odometer_id = fields.Many2one('fleet.vehicle.odometer', 'Odometer', help='Odometer measure of the vehicle at the moment of this log')
|
||||
odometer = fields.Float(compute="_get_odometer", inverse='_set_odometer', string='Odometer Value',
|
||||
help='Odometer measure of the vehicle at the moment of this log')
|
||||
odometer_unit = fields.Selection(related='vehicle_id.odometer_unit', string="Unit", readonly=True)
|
||||
date = fields.Date(help='Date when the cost has been executed')
|
||||
contract_id = fields.Many2one('fleet.vehicle.log.contract', 'Contract', help='Contract attached to this cost')
|
||||
auto_generated = fields.Boolean('Automatically Generated', readonly=True)
|
||||
description = fields.Char("Cost Description")
|
||||
|
||||
def _get_odometer(self):
|
||||
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
|
||||
def create(self, data):
|
||||
# make sure that the data are consistent with values of parent and contract records given
|
||||
if 'parent_id' in data and data['parent_id']:
|
||||
parent = self.browse(data['parent_id'])
|
||||
data['vehicle_id'] = parent.vehicle_id.id
|
||||
data['date'] = parent.date
|
||||
data['cost_type'] = parent.cost_type
|
||||
if 'contract_id' in data and data['contract_id']:
|
||||
contract = self.env['fleet.vehicle.log.contract'].browse(data['contract_id'])
|
||||
data['vehicle_id'] = contract.vehicle_id.id
|
||||
data['cost_subtype_id'] = contract.cost_subtype_id.id
|
||||
data['cost_type'] = contract.cost_type
|
||||
if 'odometer' in data and not data['odometer']:
|
||||
# if received value for odometer is 0, then remove it from the data as it would result to the creation of a
|
||||
# odometer log with 0, which is to be avoided
|
||||
del(data['odometer'])
|
||||
return super(FleetVehicleCost, self).create(data)
|
||||
|
||||
|
||||
class FleetVehicleLogContract(models.Model):
|
||||
|
||||
_inherits = {'fleet.vehicle.cost': 'cost_id'}
|
||||
_name = 'fleet.vehicle.log.contract'
|
||||
_description = 'Contract information on a vehicle'
|
||||
_order = 'state desc,expiration_date'
|
||||
|
||||
def compute_next_year_date(self, strdate):
|
||||
oneyear = relativedelta(years=1)
|
||||
start_date = fields.Date.from_string(strdate)
|
||||
return fields.Date.to_string(start_date + oneyear)
|
||||
|
||||
@api.model
|
||||
def default_get(self, default_fields):
|
||||
res = super(FleetVehicleLogContract, self).default_get(default_fields)
|
||||
contract = self.env.ref('fleet.type_contract_leasing', raise_if_not_found=False)
|
||||
res.update({
|
||||
'date': fields.Date.context_today(self),
|
||||
'cost_subtype_id': contract and contract.id or False,
|
||||
'cost_type': 'contract'
|
||||
})
|
||||
return res
|
||||
|
||||
name = fields.Text(compute='_compute_contract_name', store=True)
|
||||
active = fields.Boolean(default=True)
|
||||
start_date = fields.Date('Contract Start Date', default=fields.Date.context_today, help='Date when the coverage of the contract begins')
|
||||
expiration_date = fields.Date('Contract Expiration Date', default=lambda self: self.compute_next_year_date(fields.Date.context_today(self)),
|
||||
help='Date when the coverage of the contract expirates (by default, one year after begin date)')
|
||||
days_left = fields.Integer(compute='_compute_days_left', string='Warning Date')
|
||||
insurer_id = fields.Many2one('res.partner', 'Vendor')
|
||||
purchaser_id = fields.Many2one('res.partner', 'Contractor', default=lambda self: self.env.user.partner_id.id,
|
||||
help='Person to which the contract is signed for')
|
||||
ins_ref = fields.Char('Contract Reference', size=64, copy=False)
|
||||
state = fields.Selection([
|
||||
('open', 'In Progress'),
|
||||
('toclose', 'To Close'),
|
||||
('closed', 'Terminated')
|
||||
], 'Status', default='open', readonly=True, help='Choose wheter the contract is still valid or not',
|
||||
copy=False)
|
||||
notes = fields.Text('Terms and Conditions', help='Write here all supplementary information relative to this contract', copy=False)
|
||||
cost_generated = fields.Float('Recurring Cost Amount',
|
||||
help="Costs paid at regular intervals, depending on the cost frequency."
|
||||
"If the cost frequency is set to unique, the cost will be logged at the start date")
|
||||
cost_frequency = fields.Selection([
|
||||
('no', 'No'),
|
||||
('daily', 'Daily'),
|
||||
('weekly', 'Weekly'),
|
||||
('monthly', 'Monthly'),
|
||||
('yearly', 'Yearly')
|
||||
], 'Recurring Cost Frequency', default='no', help='Frequency of the recuring cost', required=True)
|
||||
generated_cost_ids = fields.One2many('fleet.vehicle.cost', 'contract_id', 'Generated Costs')
|
||||
sum_cost = fields.Float(compute='_compute_sum_cost', string='Indicative Costs Total')
|
||||
cost_id = fields.Many2one('fleet.vehicle.cost', 'Cost', required=True, ondelete='cascade')
|
||||
# we need to keep this field as a related with store=True because the graph view doesn't support
|
||||
# (1) to address fields from inherited table
|
||||
# (2) fields that aren't stored in database
|
||||
cost_amount = fields.Float(related='cost_id.amount', string='Amount', store=True)
|
||||
odometer = fields.Float(string='Odometer at creation', help='Odometer measure of the vehicle at the moment of the contract creation')
|
||||
|
||||
@api.depends('vehicle_id', 'cost_subtype_id', 'date')
|
||||
def _compute_contract_name(self):
|
||||
for record in self:
|
||||
name = record.vehicle_id.name
|
||||
if record.cost_subtype_id.name:
|
||||
name += ' / ' + record.cost_subtype_id.name
|
||||
if record.date:
|
||||
name += ' / ' + record.date
|
||||
record.name = name
|
||||
|
||||
@api.depends('expiration_date', 'state')
|
||||
def _compute_days_left(self):
|
||||
"""return a dict with as value for each contract an integer
|
||||
if contract is in an open state and is overdue, return 0
|
||||
if contract is in a closed state, return -1
|
||||
otherwise return the number of days before the contract expires
|
||||
"""
|
||||
for record in self:
|
||||
if (record.expiration_date and (record.state == 'open' or record.state == 'toclose')):
|
||||
today = fields.Date.from_string(fields.Date.today())
|
||||
renew_date = fields.Date.from_string(record.expiration_date)
|
||||
diff_time = (renew_date - today).days
|
||||
record.days_left = diff_time > 0 and diff_time or 0
|
||||
else:
|
||||
record.days_left = -1
|
||||
|
||||
@api.depends('cost_ids.amount')
|
||||
def _compute_sum_cost(self):
|
||||
for contract in self:
|
||||
contract.sum_cost = sum(contract.cost_ids.mapped('amount'))
|
||||
|
||||
@api.onchange('vehicle_id')
|
||||
def _onchange_vehicle(self):
|
||||
if self.vehicle_id:
|
||||
self.odometer_unit = self.vehicle_id.odometer_unit
|
||||
|
||||
@api.multi
|
||||
def contract_close(self):
|
||||
for record in self:
|
||||
record.state = 'closed'
|
||||
|
||||
@api.multi
|
||||
def contract_open(self):
|
||||
for record in self:
|
||||
record.state = 'open'
|
||||
|
||||
@api.multi
|
||||
def act_renew_contract(self):
|
||||
assert len(self.ids) == 1, "This operation should only be done for 1 single contract at a time, as it it suppose to open a window as result"
|
||||
for element in self:
|
||||
# compute end date
|
||||
startdate = fields.Date.from_string(element.start_date)
|
||||
enddate = fields.Date.from_string(element.expiration_date)
|
||||
diffdate = (enddate - startdate)
|
||||
default = {
|
||||
'date': fields.Date.context_today(self),
|
||||
'start_date': fields.Date.to_string(fields.Date.from_string(element.expiration_date) + relativedelta(days=1)),
|
||||
'expiration_date': fields.Date.to_string(enddate + diffdate),
|
||||
}
|
||||
newid = element.copy(default).id
|
||||
return {
|
||||
'name': _("Renew Contract"),
|
||||
'view_mode': 'form',
|
||||
'view_id': self.env.ref('fleet.fleet_vehicle_log_contract_view_form').id,
|
||||
'view_type': 'tree,form',
|
||||
'res_model': 'fleet.vehicle.log.contract',
|
||||
'type': 'ir.actions.act_window',
|
||||
'domain': '[]',
|
||||
'res_id': newid,
|
||||
'context': {'active_id': newid},
|
||||
}
|
||||
|
||||
@api.model
|
||||
def scheduler_manage_auto_costs(self):
|
||||
# This method is called by a cron task
|
||||
# It creates costs for contracts having the "recurring cost" field setted, depending on their frequency
|
||||
# For example, if a contract has a reccuring cost of 200 with a weekly frequency, this method creates a cost of 200 on the
|
||||
# first day of each week, from the date of the last recurring costs in the database to today
|
||||
# If the contract has not yet any recurring costs in the database, the method generates the recurring costs from the start_date to today
|
||||
# The created costs are associated to a contract thanks to the many2one field contract_id
|
||||
# If the contract has no start_date, no cost will be created, even if the contract has recurring costs
|
||||
VehicleCost = self.env['fleet.vehicle.cost']
|
||||
deltas = {
|
||||
'yearly': relativedelta(years=+1),
|
||||
'monthly': relativedelta(months=+1),
|
||||
'weekly': relativedelta(weeks=+1),
|
||||
'daily': relativedelta(days=+1)
|
||||
}
|
||||
contracts = self.env['fleet.vehicle.log.contract'].search([('state', '!=', 'closed')], offset=0, limit=None, order=None)
|
||||
for contract in contracts:
|
||||
if not contract.start_date or contract.cost_frequency == 'no':
|
||||
continue
|
||||
found = False
|
||||
last_cost_date = contract.start_date
|
||||
if contract.generated_cost_ids:
|
||||
last_autogenerated_cost = VehicleCost.search([
|
||||
('contract_id', '=', contract.id),
|
||||
('auto_generated', '=', True)
|
||||
], offset=0, limit=1, order='date desc')
|
||||
if last_autogenerated_cost:
|
||||
found = True
|
||||
last_cost_date = last_autogenerated_cost.date
|
||||
startdate = fields.Date.from_string(last_cost_date)
|
||||
if found:
|
||||
startdate += deltas.get(contract.cost_frequency)
|
||||
today = fields.Date.from_string(fields.Date.context_today(self))
|
||||
while (startdate <= today) & (startdate <= fields.Date.from_string(contract.expiration_date)):
|
||||
data = {
|
||||
'amount': contract.cost_generated,
|
||||
'date': fields.Date.context_today(self),
|
||||
'vehicle_id': contract.vehicle_id.id,
|
||||
'cost_subtype_id': contract.cost_subtype_id.id,
|
||||
'contract_id': contract.id,
|
||||
'auto_generated': True
|
||||
}
|
||||
self.env['fleet.vehicle.cost'].create(data)
|
||||
startdate += deltas.get(contract.cost_frequency)
|
||||
return True
|
||||
|
||||
@api.model
|
||||
def scheduler_manage_contract_expiration(self):
|
||||
# This method is called by a cron task
|
||||
# It manages the state of a contract, possibly by posting a message on the vehicle concerned and updating its status
|
||||
date_today = fields.Date.from_string(fields.Date.context_today(self))
|
||||
limit_date = fields.Date.to_string(date_today + relativedelta(days=+15))
|
||||
contracts = self.search([('state', '=', 'open'), ('expiration_date', '<', limit_date)])
|
||||
res = {}
|
||||
for contract in contracts:
|
||||
if contract.vehicle_id.id in res:
|
||||
res[contract.vehicle_id.id] += 1
|
||||
else:
|
||||
res[contract.vehicle_id.id] = 1
|
||||
|
||||
Vehicle = self.env['fleet.vehicle']
|
||||
for vehicle, value in res.items():
|
||||
Vehicle.browse(vehicle).message_post(body=_('%s contract(s) need(s) to be renewed and/or closed!') % value)
|
||||
return contracts.write({'state': 'toclose'})
|
||||
|
||||
@api.model
|
||||
def run_scheduler(self):
|
||||
self.scheduler_manage_auto_costs()
|
||||
self.scheduler_manage_contract_expiration()
|
||||
|
||||
|
||||
class FleetVehicleLogFuel(models.Model):
|
||||
_name = 'fleet.vehicle.log.fuel'
|
||||
_description = 'Fuel log for vehicles'
|
||||
_inherits = {'fleet.vehicle.cost': 'cost_id'}
|
||||
|
||||
@api.model
|
||||
def default_get(self, default_fields):
|
||||
res = super(FleetVehicleLogFuel, self).default_get(default_fields)
|
||||
service = self.env.ref('fleet.type_service_refueling', raise_if_not_found=False)
|
||||
res.update({
|
||||
'date': fields.Date.context_today(self),
|
||||
'cost_subtype_id': service and service.id or False,
|
||||
'cost_type': 'fuel'
|
||||
})
|
||||
return res
|
||||
|
||||
liter = fields.Float()
|
||||
price_per_liter = fields.Float()
|
||||
purchaser_id = fields.Many2one('res.partner', 'Purchaser', domain="['|',('customer','=',True),('employee','=',True)]")
|
||||
inv_ref = fields.Char('Invoice Reference', size=64)
|
||||
vendor_id = fields.Many2one('res.partner', 'Vendor', domain="[('supplier','=',True)]")
|
||||
notes = fields.Text()
|
||||
cost_id = fields.Many2one('fleet.vehicle.cost', 'Cost', required=True, ondelete='cascade')
|
||||
# we need to keep this field as a related with store=True because the graph view doesn't support
|
||||
# (1) to address fields from inherited table
|
||||
# (2) fields that aren't stored in database
|
||||
cost_amount = fields.Float(related='cost_id.amount', string='Amount', store=True)
|
||||
|
||||
@api.onchange('vehicle_id')
|
||||
def _onchange_vehicle(self):
|
||||
if self.vehicle_id:
|
||||
self.odometer_unit = self.vehicle_id.odometer_unit
|
||||
self.purchaser_id = self.vehicle_id.driver_id.id
|
||||
|
||||
@api.onchange('liter', 'price_per_liter', 'amount')
|
||||
def _onchange_liter_price_amount(self):
|
||||
# need to cast in float because the value receveid from web client maybe an integer (Javascript and JSON do not
|
||||
# make any difference between 3.0 and 3). This cause a problem if you encode, for example, 2 liters at 1.5 per
|
||||
# liter => total is computed as 3.0, then trigger an onchange that recomputes price_per_liter as 3/2=1 (instead
|
||||
# of 3.0/2=1.5)
|
||||
# If there is no change in the result, we return an empty dict to prevent an infinite loop due to the 3 intertwine
|
||||
# onchange. And in order to verify that there is no change in the result, we have to limit the precision of the
|
||||
# computation to 2 decimal
|
||||
liter = float(self.liter)
|
||||
price_per_liter = float(self.price_per_liter)
|
||||
amount = float(self.amount)
|
||||
if liter > 0 and price_per_liter > 0 and round(liter * price_per_liter, 2) != amount:
|
||||
self.amount = round(liter * price_per_liter, 2)
|
||||
elif amount > 0 and liter > 0 and round(amount / liter, 2) != price_per_liter:
|
||||
self.price_per_liter = round(amount / liter, 2)
|
||||
elif amount > 0 and price_per_liter > 0 and round(amount / price_per_liter, 2) != liter:
|
||||
self.liter = round(amount / price_per_liter, 2)
|
||||
|
||||
|
||||
class FleetVehicleLogServices(models.Model):
|
||||
_name = 'fleet.vehicle.log.services'
|
||||
_inherits = {'fleet.vehicle.cost': 'cost_id'}
|
||||
_description = 'Services for vehicles'
|
||||
|
||||
@api.model
|
||||
def default_get(self, default_fields):
|
||||
res = super(FleetVehicleLogServices, self).default_get(default_fields)
|
||||
service = self.env.ref('fleet.type_service_service_8', raise_if_not_found=False)
|
||||
res.update({
|
||||
'date': fields.Date.context_today(self),
|
||||
'cost_subtype_id': service and service.id or False,
|
||||
'cost_type': 'services'
|
||||
})
|
||||
return res
|
||||
|
||||
purchaser_id = fields.Many2one('res.partner', 'Purchaser', domain="['|',('customer','=',True),('employee','=',True)]")
|
||||
inv_ref = fields.Char('Invoice Reference')
|
||||
vendor_id = fields.Many2one('res.partner', 'Vendor', domain="[('supplier','=',True)]")
|
||||
# we need to keep this field as a related with store=True because the graph view doesn't support
|
||||
# (1) to address fields from inherited table and (2) fields that aren't stored in database
|
||||
cost_amount = fields.Float(related='cost_id.amount', string='Amount', store=True)
|
||||
notes = fields.Text()
|
||||
cost_id = fields.Many2one('fleet.vehicle.cost', 'Cost', required=True, ondelete='cascade')
|
||||
|
||||
@api.onchange('vehicle_id')
|
||||
def _onchange_vehicle(self):
|
||||
if self.vehicle_id:
|
||||
self.odometer_unit = self.vehicle_id.odometer_unit
|
||||
self.purchaser_id = self.vehicle_id.driver_id.id
|
||||
@@ -0,0 +1,63 @@
|
||||
# -*- 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', 'Make', required=True, help='Make of the vehicle')
|
||||
vendors = fields.Many2many('res.partner', 'fleet_vehicle_model_vendors', 'model_id', 'partner_id', string='Vendors')
|
||||
image = fields.Binary(related='brand_id.image', string="Logo")
|
||||
image_medium = fields.Binary(related='brand_id.image_medium', string="Logo (medium)")
|
||||
image_small = fields.Binary(related='brand_id.image_small', string="Logo (small)")
|
||||
|
||||
@api.multi
|
||||
@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
|
||||
|
||||
@api.onchange('brand_id')
|
||||
def _onchange_brand(self):
|
||||
if self.brand_id:
|
||||
self.image_medium = self.brand_id.image
|
||||
else:
|
||||
self.image_medium = False
|
||||
|
||||
|
||||
class FleetVehicleModelBrand(models.Model):
|
||||
_name = 'fleet.vehicle.model.brand'
|
||||
_description = 'Brand model of the vehicle'
|
||||
_order = 'name asc'
|
||||
|
||||
name = fields.Char('Make', required=True)
|
||||
image = fields.Binary("Logo", attachment=True,
|
||||
help="This field holds the image used as logo for the brand, limited to 1024x1024px.")
|
||||
image_medium = fields.Binary("Medium-sized image", attachment=True,
|
||||
help="Medium-sized logo of the brand. It is automatically "
|
||||
"resized as a 128x128px image, with aspect ratio preserved. "
|
||||
"Use this field in form views or some kanban views.")
|
||||
image_small = fields.Binary("Small-sized image", attachment=True,
|
||||
help="Small-sized logo of the brand. It is automatically "
|
||||
"resized as a 64x64px image, with aspect ratio preserved. "
|
||||
"Use this field anywhere a small image is required.")
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
tools.image_resize_images(vals)
|
||||
return super(FleetVehicleModelBrand, self).create(vals)
|
||||
|
||||
@api.multi
|
||||
def write(self, vals):
|
||||
tools.image_resize_images(vals)
|
||||
return super(FleetVehicleModelBrand, self).write(vals)
|
||||
@@ -0,0 +1,512 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id='fleet_vehicle_cost_view_tree' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.cost.tree</field>
|
||||
<field name="model">fleet.vehicle.cost</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Vehicle Costs">
|
||||
<field name="vehicle_id"/>
|
||||
<field name="description"/>
|
||||
<field name="cost_subtype_id"/>
|
||||
<field name="date"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='fleet_vehicle_cost_view_pivot' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.cost.pivot</field>
|
||||
<field name="model">fleet.vehicle.cost</field>
|
||||
<field name="arch" type="xml">
|
||||
<pivot string="Vehicles costs">
|
||||
<field name="vehicle_id" type="row"/>
|
||||
<field name="cost_type" type="row"/>
|
||||
<field name="date" interval="year" type="col"/>
|
||||
<field name="amount" type="measure"/>
|
||||
</pivot>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='fleet_vehicle_cost_view_graph' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.cost.graph</field>
|
||||
<field name="model">fleet.vehicle.cost</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Vehicles costs">
|
||||
<field name="vehicle_id"/>
|
||||
<field name="date" interval="year"/>
|
||||
<field name="amount" type="measure"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='fleet_vehicle_cost_indicative_view_pivot' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.cost.pivot</field>
|
||||
<field name="model">fleet.vehicle.cost</field>
|
||||
<field name="arch" type="xml">
|
||||
<pivot string="Vehicles costs" >
|
||||
<field name="vehicle_id" type="row"/>
|
||||
<field name="cost_type" type="col"/>
|
||||
<field name="amount" type="measure"/>
|
||||
</pivot>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='fleet_vehicle_cost_indicative_view_graph' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.cost.graph</field>
|
||||
<field name="model">fleet.vehicle.cost</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Vehicles costs">
|
||||
<field name="parent_id" type="row"/>
|
||||
<field name="cost_type" type="col"/>
|
||||
<field name="amount" type="measure"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='fleet_vehicle_costs_view_search' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.cost.search</field>
|
||||
<field name="model">fleet.vehicle.cost</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Vehicles costs" >
|
||||
<field name="vehicle_id" />
|
||||
<field name="cost_subtype_id"/>
|
||||
<field name="date"/>
|
||||
<field name="parent_id"/>
|
||||
<filter name="parent_false" domain="[('parent_id','=',False)]" string="Effective Costs"/>
|
||||
<filter name="parent_true" domain="[('parent_id','!=',False)]" string="Indicative Costs"/>
|
||||
<group expand="1" string="Group By">
|
||||
<filter name="groupby_cost_type" context="{'group_by': 'cost_type'}" string="Cost Type"/>
|
||||
<filter name="groupby_cost_subtype_id" context="{'group_by': 'cost_subtype_id'}" string="Cost Subtype"/>
|
||||
<filter name="groupby_vehicle_id" context="{'group_by': 'vehicle_id'}" string="Vehicle"/>
|
||||
<filter name="groupby_parent_id" context="{'group_by': 'parent_id'}" string="Parent"/>
|
||||
<separator/>
|
||||
<filter name="groupby_date" context="{'group_by': 'date:month'}" string="Month" help="Vehicle Costs by Month"/>
|
||||
<filter name="groupby_year" context="{'group_by': 'date:year'}" string="Year"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='fleet_vehicle_costs_view_form' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.cost.form</field>
|
||||
<field name="model">fleet.vehicle.cost</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Vehicle costs">
|
||||
<sheet>
|
||||
<group col="2" string="Cost Details">
|
||||
<group>
|
||||
<field name="vehicle_id" />
|
||||
<field name="cost_subtype_id"/>
|
||||
<field name="amount"/>
|
||||
<field name="description"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="date"/>
|
||||
<field name="parent_id" groups="base.group_no_one"/>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="fleet_vehicle_costs_view_graph" model="ir.ui.view">
|
||||
<field name="name">fleet.vehicle.cost.graph</field>
|
||||
<field name="model">fleet.vehicle.cost</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Costs Per Month" stacked="True">
|
||||
<field name="date" type="row"/>
|
||||
<field name="vehicle_id" type="col"/>
|
||||
<field name="amount" type="measure"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='fleet_vehicle_costs_action' model='ir.actions.act_window'>
|
||||
<field name="name">Vehicle Costs</field>
|
||||
<field name="res_model">fleet.vehicle.cost</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form,graph</field>
|
||||
<field name="context">{"search_default_parent_false" : True, "search_default_groupby_vehicle_id" : True,}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to create a new cost.
|
||||
</p><p>
|
||||
Odoo helps you managing the costs for your different
|
||||
vehicles. Costs are created automatically from services,
|
||||
contracts (fixed or recurring) and fuel logs.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem action="fleet_vehicle_costs_action" parent="fleet_vehicles" id="fleet_vehicle_costs_menu" groups="fleet_group_manager"/>
|
||||
|
||||
|
||||
<record id='fleet_vehicle_log_contract_view_form' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.log_contract.form</field>
|
||||
<field name="model">fleet.vehicle.log.contract</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Contract logs">
|
||||
<header>
|
||||
<button modifiers="{'invisible': [['state', '=', 'closed']]}" name="contract_close" states="open" type="object" class="oe_highlight" string="Terminate Contract" groups="fleet.fleet_group_manager"/>
|
||||
<button modifiers="{'invisible': [['state', '=', 'closed']]}" name="contract_close" states="toclose" type="object" class="oe_highlight" string="Terminate Contract" groups="fleet.fleet_group_manager"/>
|
||||
<button modifiers="{'invisible': [['state', 'not in', ['closed']]]}" name="contract_open" states="closed" type="object" class="oe_highlight" string="Set Contract In Progress" groups="fleet.fleet_group_manager"/>
|
||||
<button class="oe_highlight" name="act_renew_contract" type="object" string="Renew Contract" help="Create a new contract automatically with all the same informations except for the date that will start at the end of current contract" groups="fleet.fleet_group_manager"/>
|
||||
<field name="state" widget="statusbar" />
|
||||
</header>
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button name="toggle_active" type="object" class="oe_stat_button" icon="fa-archive">
|
||||
<field name="active" widget="boolean_button" options='{"terminology": "archive"}'/>
|
||||
</button>
|
||||
</div>
|
||||
<group col="2">
|
||||
<group string="Contract details">
|
||||
<field name="vehicle_id"/>
|
||||
<field name="cost_subtype_id" required="1" domain="['|',('category','=','contract'),('category','=','both')]"/>
|
||||
<field name="amount" string="Activation Cost" help="Cost that is paid only once at the creation of the contract"/>
|
||||
<label for="cost_generated"/>
|
||||
<div>
|
||||
<field name="cost_generated" class="oe_inline" attrs="{'invisible': [('cost_frequency','=','no')]}" />
|
||||
<field name="cost_frequency" class="oe_inline" />
|
||||
</div>
|
||||
</group>
|
||||
<group string="Odometer details">
|
||||
<label for="odometer"/>
|
||||
<div class="o_row">
|
||||
<field name="odometer"/>
|
||||
<field name="odometer_unit"/>
|
||||
</div>
|
||||
</group>
|
||||
</group>
|
||||
<group col="2">
|
||||
<group>
|
||||
<field name="date" string="Invoice Date"/>
|
||||
<field name="start_date"/>
|
||||
<field name="expiration_date" />
|
||||
</group>
|
||||
<group>
|
||||
<field name="insurer_id" context="{'default_supplier': True}"/>
|
||||
<field name="purchaser_id" />
|
||||
<field name="ins_ref" />
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Included Services">
|
||||
<group>
|
||||
<field name="cost_ids" context="{'vehicle_id': vehicle_id}" nolabel="1">
|
||||
<tree editable="bottom">
|
||||
<field name="cost_subtype_id" string="Service" domain="[('category','=','service')]"/>
|
||||
<field name="description"/>
|
||||
<field name="amount" sum="Price" string="Indicative Cost" />
|
||||
</tree>
|
||||
</field>
|
||||
|
||||
</group>
|
||||
<div class="oe_right">
|
||||
<group>
|
||||
<field name="sum_cost" string="Indicative Costs Total"/>
|
||||
</group>
|
||||
</div>
|
||||
</page>
|
||||
<page string="Generated Recurring Costs">
|
||||
<group>
|
||||
<field name="generated_cost_ids" context="{'vehicle_id': vehicle_id}" nolabel="1" sum="amount">
|
||||
<tree editable="bottom" >
|
||||
<field name="date" />
|
||||
<field name="amount" sum="amount"/>
|
||||
</tree>
|
||||
|
||||
</field>
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
<group string="Terms and Conditions">
|
||||
<field name="notes" nolabel="1" placeholder="Write here all other information relative to this contract" />
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<act_window
|
||||
id="act_renew_contract"
|
||||
name="Renew Contract"
|
||||
res_model="fleet.vehicle.log.contract"
|
||||
src_model="fleet.vehicle.log.contract"
|
||||
view_mode="form"
|
||||
view_type="form"
|
||||
/>
|
||||
|
||||
|
||||
<record id='fleet_vehicle_log_contract_view_tree' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.log.contract.tree</field>
|
||||
<field name="model">fleet.vehicle.log.contract</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Contract logs" decoration-warning="days_left>0 and days_left<15" decoration-danger="days_left==0" decoration-muted="state=='closed'">
|
||||
<field name="active" invisible="1"/>
|
||||
<field name="start_date" />
|
||||
<field name="expiration_date" />
|
||||
<field name="days_left" invisible="1"/>
|
||||
<field name="vehicle_id" />
|
||||
<field name="cost_subtype_id"/>
|
||||
<field name="insurer_id" />
|
||||
<field name="amount" string="Activation Cost"/>
|
||||
<field name="cost_generated"/>
|
||||
<field name="cost_frequency"/>
|
||||
<field name="state" />
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="fleet_vehicle_log_contract_view_graph" model="ir.ui.view">
|
||||
<field name="name">fleet.vehicle.log.contract.graph</field>
|
||||
<field name="model">fleet.vehicle.log.contract</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Contract Costs Per Month" type="bar" stacked="True">
|
||||
<field name="date" type="row"/>
|
||||
<field name="vehicle_id" type="row"/>
|
||||
<field name="cost_amount" type="measure"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="fleet_vehicle_log_contract_view_search" model="ir.ui.view">
|
||||
<field name="name">fleet.vehicle.log.contract.search</field>
|
||||
<field name="model">fleet.vehicle.log.contract</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Vehicles Contracts">
|
||||
<filter string="Archived" name="inactive" domain="[('active','=',False)]"/>
|
||||
<filter string="In Progress" name="open" domain="[('state', '=', 'open')]"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='fleet_vehicle_log_contract_action' model='ir.actions.act_window'>
|
||||
<field name="name">Vehicles Contracts</field>
|
||||
<field name="res_model">fleet.vehicle.log.contract</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form,graph</field>
|
||||
<field name="context">{'search_default_open': 1}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to create a new contract.
|
||||
</p><p>
|
||||
Manage all your contracts (leasing, insurances, etc.) with
|
||||
their related services, costs. Odoo will automatically warn
|
||||
you when some contracts have to be renewed.
|
||||
</p><p>
|
||||
Each contract (e.g.: leasing) may include several services
|
||||
(reparation, insurances, periodic maintenance).
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem action="fleet_vehicle_log_contract_action" parent="fleet_vehicles" id="fleet_vehicle_log_contract_menu" groups="fleet_group_user"/>
|
||||
|
||||
<record id='fleet_vehicle_log_fuel_view_form' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.log.fuel.form</field>
|
||||
<field name="model">fleet.vehicle.log.fuel</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Fuel Logs">
|
||||
<sheet>
|
||||
<group>
|
||||
<group string="Vehicle Details">
|
||||
<field name="vehicle_id"/>
|
||||
</group>
|
||||
<group string="Refueling Details">
|
||||
<field name="liter"/>
|
||||
<field name="price_per_liter"/>
|
||||
<field name="amount"/>
|
||||
</group>
|
||||
</group>
|
||||
<group>
|
||||
<group string="Odometer Details">
|
||||
<label for="odometer"/>
|
||||
<div class="o_row">
|
||||
<field name="odometer"/>
|
||||
<field name="odometer_unit"/>
|
||||
</div>
|
||||
</group>
|
||||
<group string="Additional Details">
|
||||
<field name="date" />
|
||||
<field name="purchaser_id" />
|
||||
<field name="inv_ref" />
|
||||
<field name="vendor_id" context="{'default_supplier': True}"/>
|
||||
</group>
|
||||
</group>
|
||||
<field name="notes" placeholder="Write here any other information"/>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='fleet_vehicle_log_fuel_view_tree' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.log.fuel.tree</field>
|
||||
<field name="model">fleet.vehicle.log.fuel</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Fuel Logs">
|
||||
<field name="date" />
|
||||
<field name="vehicle_id" />
|
||||
<field name="odometer" invisible="1"/>
|
||||
<field name="odometer_unit" invisible="1"/>
|
||||
<field name="purchaser_id" />
|
||||
<field name="inv_ref" invisible="1"/>
|
||||
<field name="vendor_id" invisible="1"/>
|
||||
<field name="liter" />
|
||||
<field name="price_per_liter" invisible="1"/>
|
||||
<field name="amount" sum="Price"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='fleet_vehicle_log_fuel_view_search' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.log.fuel.search</field>
|
||||
<field name="model">fleet.vehicle.log.fuel</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Vehicles Fuel Logs" >
|
||||
<field name="vehicle_id" />
|
||||
<field name="date"/>
|
||||
<field name="purchaser_id" />
|
||||
<field name="liter" />
|
||||
<field name="amount" sum="Price"/>
|
||||
<group expand="0" string="Group By">
|
||||
<filter name="groupby_vehicle" context="{'group_by' : 'vehicle_id'}" string="Vehicle"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="fleet_vehicle_log_fuel_view_graph" model="ir.ui.view">
|
||||
<field name="name">fleet.vehicle.log.fuel.graph</field>
|
||||
<field name="model">fleet.vehicle.log.fuel</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Fuel Costs Per Month">
|
||||
<field name="vehicle_id" type="row"/>
|
||||
<field name="cost_amount" type="measure"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='fleet_vehicle_log_fuel_action' model='ir.actions.act_window'>
|
||||
<field name="name">Vehicles Fuel Logs</field>
|
||||
<field name="res_model">fleet.vehicle.log.fuel</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form,graph</field>
|
||||
<field name="context">{"search_default_groupby_vehicle" : True}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to create a new fuel log.
|
||||
</p><p>
|
||||
Here you can add refuelling entries for all vehicles. You can
|
||||
also filter logs of a particular vehicle using the search
|
||||
field.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem action="fleet_vehicle_log_fuel_action" parent="fleet_vehicles" id="fleet_vehicle_log_fuel_menu" groups="fleet_group_user"/>
|
||||
|
||||
<record id='fleet_vehicle_log_services_view_form' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.log.services.form</field>
|
||||
<field name="model">fleet.vehicle.log.services</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Services Logs">
|
||||
<sheet>
|
||||
<group col="2">
|
||||
<group string="Services Details">
|
||||
<field name="vehicle_id"/>
|
||||
<field name="cost_subtype_id" string="Service Type" domain="['|',('category','=','service'),('category','=','both')]" required="1"/>
|
||||
<field name="amount"/>
|
||||
</group>
|
||||
<group string="Odometer Details">
|
||||
<label for="odometer"/>
|
||||
<div class="o_row">
|
||||
<field name="odometer"/>
|
||||
<field name="odometer_unit"/>
|
||||
</div>
|
||||
</group>
|
||||
</group>
|
||||
<group col="2">
|
||||
<group string="Additional Details">
|
||||
<field name="date" />
|
||||
<field name="purchaser_id" />
|
||||
<field name="vendor_id" context="{'default_supplier': True}"/>
|
||||
<field name="inv_ref" />
|
||||
</group>
|
||||
</group>
|
||||
<group string="Included Services">
|
||||
<field name="cost_ids" nolabel="1">
|
||||
<tree string="Included Services" editable="bottom">
|
||||
<field name="cost_subtype_id" string="Service" domain="[('category','=','service')]"/>
|
||||
<field name="amount" sum="Price" string="Indicative Cost"/>
|
||||
</tree>
|
||||
</field>
|
||||
</group>
|
||||
<group string="Notes">
|
||||
<field nolabel="1" name="notes" placeholder="Write here any other information related to the service completed."/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='fleet_vehicle_log_services_view_tree' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.log.services.tree</field>
|
||||
<field name="model">fleet.vehicle.log.services</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Services Logs">
|
||||
<field name="date" />
|
||||
<field name="vehicle_id" />
|
||||
<field name="cost_subtype_id"/>
|
||||
<field name="purchaser_id"/>
|
||||
<field name="vendor_id" />
|
||||
<field name="inv_ref" />
|
||||
<field name="notes" />
|
||||
<field name="amount" sum="Total"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="fleet_vehicle_log_services_view_graph" model="ir.ui.view">
|
||||
<field name="name">fleet.vehicle.log.services.graph</field>
|
||||
<field name="model">fleet.vehicle.log.services</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Services Costs Per Month" stacked="True">
|
||||
<field name="date" type="row"/>
|
||||
<field name="vehicle_id" type="row"/>
|
||||
<field name="cost_amount" type="measure"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='fleet_vehicle_log_services_view_search' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.log.services.search</field>
|
||||
<field name="model">fleet.vehicle.log.services</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Services Logs" >
|
||||
<field name="vehicle_id"/>
|
||||
<field name="cost_subtype_id"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='fleet_vehicle_log_services_action' model='ir.actions.act_window'>
|
||||
<field name="name">Vehicles Services Logs</field>
|
||||
<field name="res_model">fleet.vehicle.log.services</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form,graph</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to create a new service entry.
|
||||
</p><p>
|
||||
Odoo helps you keeping track of all the services done
|
||||
on your vehicle. Services can be of many type: occasional
|
||||
repair, fixed maintenance, etc.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem action="fleet_vehicle_log_services_action" parent="fleet_vehicles" id="fleet_vehicle_log_services_menu" groups="fleet_group_user"/>
|
||||
|
||||
</odoo>
|
||||
@@ -0,0 +1,158 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<odoo>
|
||||
<record id='fleet_vehicle_model_view_form' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.model.form</field>
|
||||
<field name="model">fleet.vehicle.model</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Model">
|
||||
<sheet>
|
||||
<field name="image_medium" widget='image' class="oe_avatar"/>
|
||||
<div class="oe_title">
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
<h1>
|
||||
<field name="name" placeholder="e.g. Model S"/>
|
||||
</h1>
|
||||
<label for="brand_id" class="oe_edit_only"/>
|
||||
<h2>
|
||||
<field name="brand_id" placeholder="e.g. Tesla"/>
|
||||
</h2>
|
||||
</div>
|
||||
<notebook>
|
||||
<page string="Vendors" name="vendors">
|
||||
<field name="vendors" widget="many2many_kanban">
|
||||
<kanban quick_create="false" create="true">
|
||||
<field name="name"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div style="position: relative">
|
||||
<a t-if="! read_only_mode" type="delete" style="position: absolute; right: 0; padding: 4px; diplay: inline-block">X</a>
|
||||
<div class="oe_module_vignette">
|
||||
<div class="oe_module_desc">
|
||||
<field name="name"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='fleet_vehicle_model_view_tree' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.model.tree</field>
|
||||
<field name="model">fleet.vehicle.model</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Models">
|
||||
<field name="brand_id" />
|
||||
<field name="name" />
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='fleet_vehicle_model_view_search' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.model.search</field>
|
||||
<field name="model">fleet.vehicle.model</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Vehicles costs" >
|
||||
<field name="brand_id" />
|
||||
<group expand="1" string="Group By">
|
||||
<filter name="groupby_brand" context="{'group_by' : 'brand_id'}" string="Make"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='fleet_vehicle_model_action' model='ir.actions.act_window'>
|
||||
<field name="name">Vehicle Model</field>
|
||||
<field name="res_model">fleet.vehicle.model</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="context">{"search_default_groupby_brand" : True,}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to create a new model.
|
||||
</p><p>
|
||||
You can define several models (e.g. A3, A4) for each make (Audi).
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem name="Fleet" id="menu_root" sequence="115" groups="fleet_group_user" web_icon="fleet,static/description/icon.png"/>
|
||||
<menuitem name="Configuration" parent="menu_root" id="fleet_configuration" sequence="100" groups="fleet_group_manager"/>
|
||||
<menuitem action="fleet_vehicle_model_action" parent="fleet_configuration" id="fleet_vehicle_model_menu"/>
|
||||
|
||||
<record id='fleet_vehicle_model_brand_view_tree' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.model.brand.tree</field>
|
||||
<field name="model">fleet.vehicle.model.brand</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Model Make">
|
||||
<field name="name" />
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='fleet_vehicle_model_brand_view_form' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.model.brand.form</field>
|
||||
<field name="model">fleet.vehicle.model.brand</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Model Make">
|
||||
<group>
|
||||
<div>
|
||||
<field name="image_medium" widget="image" class="oe_avatar"/>
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
<h1>
|
||||
<field name="name"/>
|
||||
</h1>
|
||||
</div>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='fleet_vehicle_model_brand_view_kanban' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.model.brandkanban</field>
|
||||
<field name="model">fleet.vehicle.model.brand</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban>
|
||||
<field name="id"/>
|
||||
<field name="name" />
|
||||
<field name="image" />
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div class="oe_kanban_vignette oe_semantic_html_override">
|
||||
<a type="open" href="#" class="oe_kanban_action">
|
||||
<img t-att-src="kanban_image('fleet.vehicle.model.brand', 'image_small', record.id.value)" class="img-responsive" style="width:100px;"/>
|
||||
</a>
|
||||
<div class="text-center">
|
||||
<h4 class="oe_partner_heading">
|
||||
<a type="open">
|
||||
<field name="name"/>
|
||||
</a>
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='fleet_vehicle_model_brand_action' model='ir.actions.act_window'>
|
||||
<field name="name">Model make of Vehicle</field>
|
||||
<field name="res_model">fleet.vehicle.model.brand</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">kanban,tree,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to create a new make.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem action="fleet_vehicle_model_brand_action" parent="fleet_configuration" id="fleet_vehicle_model_brand_menu"/>
|
||||
</odoo>
|
||||
@@ -0,0 +1,400 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id='fleet_vehicle_view_form' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.form</field>
|
||||
<field name="model">fleet.vehicle</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Vehicle">
|
||||
<header>
|
||||
<field name="state_id" widget="statusbar" clickable="True" />
|
||||
</header>
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button name="return_action_to_open"
|
||||
type="object"
|
||||
class="oe_stat_button"
|
||||
icon="fa-book"
|
||||
context="{'xml_id':'fleet_vehicle_log_contract_action', 'search_default_open': 1}"
|
||||
help="show the contract for this vehicle">
|
||||
<field name="contract_count" widget="statinfo" string="Contracts"/>
|
||||
</button>
|
||||
<button name="act_show_log_cost"
|
||||
type="object"
|
||||
class="oe_stat_button"
|
||||
icon="fa-usd"
|
||||
help="show all the costs for this vehicle"
|
||||
groups="fleet.fleet_group_manager">
|
||||
<field name="cost_count" widget="statinfo" string="Costs"/>
|
||||
</button>
|
||||
<button name="return_action_to_open"
|
||||
type="object"
|
||||
class="oe_stat_button"
|
||||
icon="fa-wrench"
|
||||
context="{'xml_id':'fleet_vehicle_log_services_action'}"
|
||||
help="show the services logs for this vehicle" >
|
||||
<field name="service_count" widget="statinfo" string="Services"/>
|
||||
</button>
|
||||
<button name="return_action_to_open"
|
||||
type="object"
|
||||
class="oe_stat_button"
|
||||
icon="fa-flask"
|
||||
context="{'xml_id':'fleet_vehicle_log_fuel_action'}"
|
||||
help="show the fuel logs for this vehicle" >
|
||||
<field name="fuel_logs_count" widget="statinfo" string="Fuel"/>
|
||||
</button>
|
||||
<button name="return_action_to_open"
|
||||
type="object"
|
||||
class="oe_stat_button"
|
||||
icon="fa-tachometer"
|
||||
context="{'xml_id':'fleet_vehicle_odometer_action'}"
|
||||
help="show the odometer logs for this vehicle" >
|
||||
<field name="odometer_count" widget="statinfo" string="Odometer"/>
|
||||
</button>
|
||||
<button name="toggle_active" type="object" class="oe_stat_button" icon="fa-archive">
|
||||
<field name="active" widget="boolean_button" options='{"terminology": "archive"}'/>
|
||||
</button>
|
||||
</div>
|
||||
<field name="image_medium" widget='image' class="oe_avatar"/>
|
||||
<div class="oe_title">
|
||||
<label for="model_id" class="oe_edit_only"/>
|
||||
<h1>
|
||||
<field name="model_id" placeholder="e.g. Model S"/>
|
||||
</h1>
|
||||
<label for="license_plate" class="oe_edit_only"/>
|
||||
<h2>
|
||||
<field name="license_plate" class="oe_inline" placeholder="e.g. PAE 326"/>
|
||||
</h2>
|
||||
<label for="tag_ids" class="oe_edit_only"/>
|
||||
<field name="tag_ids" widget="many2many_tags" options="{'no_create_edit': True}"/>
|
||||
</div>
|
||||
<group col="2" string="General Properties">
|
||||
<group >
|
||||
<field name="driver_id" />
|
||||
<field name="location" />
|
||||
<field name="vin_sn" />
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
</group>
|
||||
<group >
|
||||
<label for="odometer"/>
|
||||
<div class="o_row">
|
||||
<field name="odometer"/>
|
||||
<field name="odometer_unit"/>
|
||||
</div>
|
||||
<field name="acquisition_date" />
|
||||
<field name="car_value" />
|
||||
<field name="residual_value"/>
|
||||
</group>
|
||||
</group>
|
||||
<group col="2">
|
||||
<group string="Additional Properties">
|
||||
<field name="seats" />
|
||||
<field name="doors" />
|
||||
<field name="color" />
|
||||
</group>
|
||||
<group string="Engine Options">
|
||||
<field name="transmission" />
|
||||
<field name="fuel_type" />
|
||||
<label for="co2"/>
|
||||
<div class="o_row" name="co2">
|
||||
<field name="co2"/><span>g/km</span>
|
||||
</div>
|
||||
<field name="horsepower" />
|
||||
<field name="horsepower_tax" />
|
||||
<label for="power"/>
|
||||
<div class="o_row">
|
||||
<field name="power"/><span>kW</span>
|
||||
</div>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" widget="mail_thread" options='{"thread_level": 1}'/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<record id='fleet_vehicle_view_tree' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.tree</field>
|
||||
<field name="model">fleet.vehicle</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Vehicle" decoration-warning="contract_renewal_due_soon and not contract_renewal_overdue" decoration-danger="contract_renewal_overdue">
|
||||
<field name="active" invisible="1"/>
|
||||
<field name="license_plate" />
|
||||
<field name="model_id" />
|
||||
<field name="driver_id" />
|
||||
<field name="vin_sn" />
|
||||
<field name="acquisition_date" />
|
||||
<field name="state_id"/>
|
||||
<field name="odometer"/>
|
||||
<field name="odometer_unit" />
|
||||
<field name="contract_renewal_due_soon" invisible="1"/>
|
||||
<field name="contract_renewal_overdue" invisible="1" />
|
||||
<field name="contract_renewal_total" invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="fleet_vehicle_view_search" model="ir.ui.view">
|
||||
<field name="name">fleet.vehicle.search</field>
|
||||
<field name="model">fleet.vehicle</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="All vehicles">
|
||||
<field name="name" filter_domain="['|', ('name','ilike',self), ('license_plate','ilike',self)]" string="Vehicle"/>
|
||||
<field name="driver_id"/>
|
||||
<field name="tag_ids"/>
|
||||
<field name="location"/>
|
||||
<field name="state_id" />
|
||||
<filter name="alert_true" domain="['|',('contract_renewal_due_soon','=',True),('contract_renewal_overdue','=',True)]" string="Has Alert(s)"/>
|
||||
<separator/>
|
||||
<filter string="Archived" name="inactive" domain="[('active','=',False)]"/>
|
||||
<group expand="1" string="Group By">
|
||||
<filter name="groupby_status" context="{'group_by' : 'state_id'}" string="Status"/>
|
||||
<filter name="groupby_model" context="{'group_by' : 'model_id'}" string="Model"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record id='fleet_vehicle_view_kanban' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.kanban</field>
|
||||
<field name="model">fleet.vehicle</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban>
|
||||
<field name="license_plate" />
|
||||
<field name="model_id" />
|
||||
<field name="driver_id" />
|
||||
<field name="location" />
|
||||
<field name="state_id" />
|
||||
<field name="id" />
|
||||
<field name="image" />
|
||||
<field name="tag_ids" />
|
||||
<field name="contract_renewal_due_soon" />
|
||||
<field name="contract_renewal_overdue" />
|
||||
<field name="contract_renewal_name" />
|
||||
<field name="contract_renewal_total" />
|
||||
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div class="oe_kanban_global_click">
|
||||
<div class="o_kanban_tags_section">
|
||||
<field name="tag_ids"/>
|
||||
</div>
|
||||
<div class="o_kanban_image">
|
||||
<img t-att-src="kanban_image('fleet.vehicle', 'image_small', record.id.value)"/>
|
||||
</div>
|
||||
<div class="oe_kanban_details">
|
||||
<strong>
|
||||
<field name="license_plate"/> : <field name="model_id"/>
|
||||
</strong>
|
||||
<ul>
|
||||
<li>
|
||||
<t t-if="record.driver_id.raw_value"><field name="driver_id"/></t>
|
||||
</li>
|
||||
<li>
|
||||
<t t-if="record.location.raw_value"><field name="location"/></t>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="o_kanban_button">
|
||||
<a t-if="record.contract_renewal_due_soon.raw_value and !record.contract_renewal_overdue.raw_value" data-type="object" data-name="return_action_to_open" href="#" class="oe_kanban_action btn btn-sm btn-warning" data-context='{"xml_id":"fleet_vehicle_log_contract_action"}'>
|
||||
<field name="contract_renewal_name"/> <t t-if="record.contract_renewal_total.raw_value > 0"> and <field name="contract_renewal_total" /> other(s)</t>
|
||||
</a>
|
||||
<a t-if="record.contract_renewal_overdue.raw_value" data-type="object" data-name="return_action_to_open" href="#" class="oe_kanban_action btn btn-sm btn-danger" data-context='{"xml_id":"fleet_vehicle_log_contract_action"}'>
|
||||
<field name="contract_renewal_name"/> <t t-if="record.contract_renewal_total.raw_value > 0"> and <field name="contract_renewal_total" /> other(s)</t>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record id='fleet_vehicle_action' model='ir.actions.act_window'>
|
||||
<field name="name">Vehicles</field>
|
||||
<field name="res_model">fleet.vehicle</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">kanban,tree,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to create a new vehicle.
|
||||
</p><p>
|
||||
You will be able to manage your fleet by keeping track of the
|
||||
contracts, services, fixed and recurring costs, odometers and
|
||||
fuel logs associated to each vehicle.
|
||||
</p><p>
|
||||
Odoo will warn you when services or contract have to be
|
||||
renewed.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem name="Vehicles" parent="menu_root" id="fleet_vehicles" sequence="2" groups="fleet_group_user"/>
|
||||
<menuitem action="fleet_vehicle_action" parent="fleet_vehicles" id="fleet_vehicle_menu" groups="fleet_group_user"/>
|
||||
|
||||
<record id='fleet_vehicle_odometer_view_form' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.odometer.form</field>
|
||||
<field name="model">fleet.vehicle.odometer</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Odometer Logs">
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="vehicle_id"/>
|
||||
<div>
|
||||
<field name="value" class="oe_inline"/>
|
||||
<field name="unit" class="oe_inline"/>
|
||||
</div>
|
||||
<field name="date" />
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='fleet_vehicle_odometer_view_tree' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.odometer.tree</field>
|
||||
<field name="model">fleet.vehicle.odometer</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Odometer Logs" editable="top">
|
||||
<field name="date" />
|
||||
<field name="vehicle_id"/>
|
||||
<field name="driver_id"/>
|
||||
<field name="value" />
|
||||
<field name="unit" />
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='fleet_vehicle_odometer_view_search' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.odometer.search</field>
|
||||
<field name="model">fleet.vehicle.odometer</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Vehicles odometers" >
|
||||
<field name="vehicle_id" />
|
||||
<field name="value"/>
|
||||
<field name="unit"/>
|
||||
<field name="date"/>
|
||||
<group expand="0" string="Group By">
|
||||
<filter name="groupby_vehicle" context="{'group_by' : 'vehicle_id'}" string="Vehicle"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="fleet_vehicle_odometer_view_graph" model="ir.ui.view">
|
||||
<field name="name">fleet.vehicle.odometer.graph</field>
|
||||
<field name="model">fleet.vehicle.odometer</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Odometer Values Per Vehicle">
|
||||
<field name="vehicle_id" type="row"/>
|
||||
<field name="value" type="measure"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='fleet_vehicle_odometer_action' model='ir.actions.act_window'>
|
||||
<field name="name">Vehicles Odometer</field>
|
||||
<field name="res_model">fleet.vehicle.odometer</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,graph</field>
|
||||
<field name="context">{"search_default_groupby_vehicle" : True}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to create a new odometer log.
|
||||
</p>
|
||||
<p>
|
||||
Here you can add various odometer entries for all vehicles.
|
||||
You can also show odometer value for a particular vehicle using
|
||||
the search field.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem action="fleet_vehicle_odometer_action" parent="fleet_vehicles" id="fleet_vehicle_odometer_menu" groups="fleet_group_user"/>
|
||||
|
||||
<record id='fleet_vehicle_service_types_view_tree' model='ir.ui.view'>
|
||||
<field name="name">fleet.service.type.tree</field>
|
||||
<field name="model">fleet.service.type</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Service types" editable="top">
|
||||
<field name="name" />
|
||||
<field name="category"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='fleet_vehicle_service_types_action' model='ir.actions.act_window'>
|
||||
<field name="name">Service Types</field>
|
||||
<field name="res_model">fleet.service.type</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to create a new type of service.
|
||||
</p><p>
|
||||
Each service can used in contracts, as a standalone service or both.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem action="fleet_vehicle_service_types_action" parent="fleet_configuration" id="fleet_vehicle_service_types_menu"/>
|
||||
|
||||
<record id='fleet_vehicle_state_view_tree' model='ir.ui.view'>
|
||||
<field name="name">fleet.vehicle.state.tree</field>
|
||||
<field name="model">fleet.vehicle.state</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="State" editable="bottom">
|
||||
<field name="sequence" widget="handler"/>
|
||||
<field name="name" />
|
||||
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='fleet_vehicle_state_action' model='ir.actions.act_window'>
|
||||
<field name="name">Vehicle Status</field>
|
||||
<field name="res_model">fleet.vehicle.state</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to create a vehicle status.
|
||||
</p><p>
|
||||
You can customize available status to track the evolution of
|
||||
each vehicle. Example: Active, Being Repaired, Sold.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem action="fleet_vehicle_state_action" parent="fleet_configuration" id="fleet_vehicle_state_menu" groups="fleet_group_manager"/>
|
||||
|
||||
<record id="fleet_vehicle_tag_view_view_form" model="ir.ui.view">
|
||||
<field name="name">fleet.vehicle.tag.form</field>
|
||||
<field name="model">fleet.vehicle.tag</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Vehicle Tags">
|
||||
<group>
|
||||
<field name="name"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="fleet_vehicle_tag_action" model="ir.actions.act_window">
|
||||
<field name="name">Vehicle Tags</field>
|
||||
<field name="res_model">fleet.vehicle.tag</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to add a new tag.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="fleet_vehicle_tag_menu" parent="fleet_configuration" action="fleet_vehicle_tag_action" sequence="112"/>
|
||||
|
||||
</odoo>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -118,13 +118,13 @@ class Employee(models.Model):
|
||||
('male', 'Male'),
|
||||
('female', 'Female'),
|
||||
('other', 'Other')
|
||||
], groups="hr.group_hr_user")
|
||||
], groups="hr.group_hr_user", default="male")
|
||||
marital = fields.Selection([
|
||||
('single', 'Single'),
|
||||
('married', 'Married'),
|
||||
('married', 'Married (or similar)'),
|
||||
('widower', 'Widower'),
|
||||
('divorced', 'Divorced')
|
||||
], string='Marital Status', groups="hr.group_hr_user")
|
||||
], string='Marital Status', groups="hr.group_hr_user", default='single')
|
||||
birthday = fields.Date('Date of Birth', groups="hr.group_hr_user")
|
||||
ssnid = fields.Char('SSN No', help='Social Security Number', groups="hr.group_hr_user")
|
||||
sinid = fields.Char('SIN No', help='Social Insurance Number', groups="hr.group_hr_user")
|
||||
@@ -135,6 +135,10 @@ class Employee(models.Model):
|
||||
domain="[('partner_id', '=', address_home_id)]",
|
||||
groups="hr.group_hr_user",
|
||||
help='Employee bank salary account')
|
||||
permit_no = fields.Char('Work Permit No')
|
||||
visa_no = fields.Char('Visa No')
|
||||
visa_expire = fields.Date('Visa Expire Date')
|
||||
|
||||
# image: all image fields are base64 encoded and PIL-supported
|
||||
image = fields.Binary(
|
||||
"Photo", default=_default_image, attachment=True,
|
||||
|
||||
@@ -95,6 +95,11 @@
|
||||
<group string="Birth">
|
||||
<field name="birthday"/>
|
||||
</group>
|
||||
<group string="Work Permit" name="work_permit">
|
||||
<field name="visa_no"/>
|
||||
<field name="permit_no"/>
|
||||
<field name="visa_expire"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
<page name="hr_settings" string="HR Settings" groups="hr.group_hr_user">
|
||||
|
||||
@@ -54,29 +54,31 @@ class Contract(models.Model):
|
||||
_inherit = ['mail.thread']
|
||||
|
||||
name = fields.Char('Contract Reference', required=True)
|
||||
employee_id = fields.Many2one('hr.employee', string='Employee', required=True)
|
||||
employee_id = fields.Many2one('hr.employee', string='Employee')
|
||||
department_id = fields.Many2one('hr.department', string="Department")
|
||||
type_id = fields.Many2one('hr.contract.type', string="Contract Type", required=True, default=lambda self: self.env['hr.contract.type'].search([], limit=1))
|
||||
job_id = fields.Many2one('hr.job', string='Job Title')
|
||||
date_start = fields.Date('Start Date', required=True, default=fields.Date.today)
|
||||
date_end = fields.Date('End Date')
|
||||
trial_date_start = fields.Date('Trial Start Date')
|
||||
trial_date_end = fields.Date('Trial End Date')
|
||||
trial_date_end = fields.Date('End of Trial Period')
|
||||
resource_calendar_id = fields.Many2one(
|
||||
'resource.calendar', 'Working Schedule',
|
||||
default=lambda self: self.env['res.company']._company_default_get().resource_calendar_id.id)
|
||||
wage = fields.Float('Wage', digits=(16, 2), required=True, help="Basic Salary of the employee")
|
||||
wage = fields.Monetary('Wage', digits=(16, 2), required=True, help="The monthly gross wage of the employee.")
|
||||
advantages = fields.Text('Advantages')
|
||||
notes = fields.Text('Notes')
|
||||
permit_no = fields.Char('Work Permit No')
|
||||
visa_no = fields.Char('Visa No')
|
||||
visa_expire = fields.Date('Visa Expire Date')
|
||||
state = fields.Selection([
|
||||
('draft', 'New'),
|
||||
('open', 'Running'),
|
||||
('pending', 'To Renew'),
|
||||
('close', 'Expired'),
|
||||
('cancel', 'Cancelled')
|
||||
], string='Status', track_visibility='onchange', help='Status of the contract', default='draft')
|
||||
company_id = fields.Many2one('res.company', default=lambda self: self.env.user.company_id)
|
||||
currency_id = fields.Many2one(string="Currency", related='company_id.currency_id')
|
||||
permit_no = fields.Char('Work Permit No', related="employee_id.permit_no")
|
||||
visa_no = fields.Char('Visa No', related="employee_id.visa_no")
|
||||
visa_expire = fields.Date('Visa Expire Date', related="employee_id.visa_expire")
|
||||
|
||||
@api.onchange('employee_id')
|
||||
def _onchange_employee_id(self):
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
<field name="state" widget="statusbar" clickable="1"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<div class="oe_title">
|
||||
<div class="oe_title" name="title">
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
<h1>
|
||||
<field name="name" placeholder="Contract Reference"/>
|
||||
@@ -91,41 +91,41 @@
|
||||
<group>
|
||||
<group>
|
||||
<field name="employee_id"/>
|
||||
<field name="department_id"/>
|
||||
<field name="job_id"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="department_id"/>
|
||||
<field name="type_id"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Information" name="information">
|
||||
<group>
|
||||
<group name="salary_and_advantages" string="Salary and Advantages">
|
||||
<field name="wage"/>
|
||||
<field name="advantages" nolabel="1" placeholder="Advantages..." colspan="2"/>
|
||||
<page string="Salary Information" name="information">
|
||||
<group name="main_info">
|
||||
<group name="salary_and_advantages" string="Monthly Advantages in Cash">
|
||||
<label for="wage"/>
|
||||
<div class="o_row" name="wage">
|
||||
<field name="wage" nolabel="1"/>
|
||||
<span>/ month</span>
|
||||
</div>
|
||||
<field name="advantages" nolabel="1" placeholder="Advantages..." colspan="2" invisible="1"/>
|
||||
</group>
|
||||
<group name="duration_group" string="Duration">
|
||||
<label for="trial_date_start" string="Trial Period Duration"/>
|
||||
<div>
|
||||
<field name="trial_date_start" class="oe_inline"/>
|
||||
<span attrs="{'invisible': ['|', ('trial_date_start', '=', False), ('trial_date_end', '=', False)]}"> - </span><field name="trial_date_end" class="oe_inline"/>
|
||||
</div>
|
||||
<label for="date_start" string="Duration"/>
|
||||
<div>
|
||||
<field name="date_start" class="oe_inline"/><span attrs="{'invisible': ['|', ('date_start', '=', False), ('date_end', '=', False)]}"> - </span><field name="date_end" class="oe_inline"/>
|
||||
</group>
|
||||
</page>
|
||||
<page string="Other Information" name="other">
|
||||
<group>
|
||||
<group name="duration_group" string="Contract Terms">
|
||||
<field name="date_start"/>
|
||||
<label for="date_end"/>
|
||||
<div class="o_row">
|
||||
<field name="date_end" nolabel="1"/>
|
||||
<span class="text-muted">(If fixed-term contract)</span>
|
||||
</div>
|
||||
<field name="trial_date_end"/>
|
||||
<field name="resource_calendar_id"/>
|
||||
</group>
|
||||
</group>
|
||||
<separator string="Notes"/>
|
||||
<field name="notes"/>
|
||||
</page>
|
||||
<page string="Work Permit">
|
||||
<group col="4">
|
||||
<field name="visa_no"/>
|
||||
<field name="permit_no"/>
|
||||
<field name="visa_expire"/>
|
||||
<group string="Notes">
|
||||
<field name="notes" nolabel="1"/>
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
|
||||
@@ -30,12 +30,15 @@ Generic Payroll system.
|
||||
'security/hr_payroll_security.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'wizard/hr_payroll_payslips_by_employees_views.xml',
|
||||
'views/hr_payroll_views.xml',
|
||||
'views/hr_contract_views.xml',
|
||||
'views/hr_salary_rule_views.xml',
|
||||
'views/hr_payslip_views.xml',
|
||||
'views/hr_employee_views.xml',
|
||||
'data/hr_payroll_sequence.xml',
|
||||
'views/hr_payroll_report.xml',
|
||||
'data/hr_payroll_data.xml',
|
||||
'wizard/hr_payroll_contribution_register_report_views.xml',
|
||||
'views/res_config_views.xml',
|
||||
'views/hr_payroll_config_settings_views.xml',
|
||||
'views/report_contributionregister_templates.xml',
|
||||
'views/report_payslip_templates.xml',
|
||||
'views/report_payslipdetails_templates.xml',
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
</record>
|
||||
|
||||
<record id="hr_rule_basic" model="hr.salary.rule">
|
||||
<field name="name">Basic</field>
|
||||
<field name="name">Basic Salary</field>
|
||||
<field name="sequence" eval="1"/>
|
||||
<field name="code">BASIC</field>
|
||||
<field name="category_id" ref="hr_payroll.BASIC"/>
|
||||
@@ -57,7 +57,7 @@
|
||||
</record>
|
||||
|
||||
<record id="hr_rule_net" model="hr.salary.rule">
|
||||
<field name="name">Net</field>
|
||||
<field name="name">Net Salary</field>
|
||||
<field name="sequence" eval="200"/>
|
||||
<field name="code">NET</field>
|
||||
<field name="category_id" ref="hr_payroll.NET"/>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#-*- coding:utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import hr_payroll
|
||||
import res_config
|
||||
import hr_contract
|
||||
import hr_employee
|
||||
import hr_payroll_config_settings
|
||||
import hr_salary_rule
|
||||
import hr_payslip
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class HrContract(models.Model):
|
||||
"""
|
||||
Employee contract based on the visa, work permits
|
||||
allows to configure different Salary structure
|
||||
"""
|
||||
_inherit = 'hr.contract'
|
||||
_description = 'Employee Contract'
|
||||
|
||||
struct_id = fields.Many2one('hr.payroll.structure', string='Salary Structure')
|
||||
schedule_pay = fields.Selection([
|
||||
('monthly', 'Monthly'),
|
||||
('quarterly', 'Quarterly'),
|
||||
('semi-annually', 'Semi-annually'),
|
||||
('annually', 'Annually'),
|
||||
('weekly', 'Weekly'),
|
||||
('bi-weekly', 'Bi-weekly'),
|
||||
('bi-monthly', 'Bi-monthly'),
|
||||
], string='Scheduled Pay', index=True, default='monthly')
|
||||
resource_calendar_id = fields.Many2one(required=True)
|
||||
|
||||
@api.multi
|
||||
def get_all_structures(self):
|
||||
"""
|
||||
@return: the structures linked to the given contracts, ordered by hierachy (parent=False first,
|
||||
then first level children and so on) and without duplicata
|
||||
"""
|
||||
structures = self.mapped('struct_id')
|
||||
if not structures:
|
||||
return []
|
||||
# YTI TODO return browse records
|
||||
return list(set(structures._get_parent_structure().ids))
|
||||
|
||||
@api.multi
|
||||
def get_attribute(self, code, attribute):
|
||||
return getattr(self.env['hr.contract.advantage.template'].search([('code', '=', code)], limit=1), attribute)
|
||||
|
||||
@api.multi
|
||||
def set_attribute_value(self, code, active):
|
||||
for contract in self:
|
||||
if active:
|
||||
value = self.env['hr.contract.advantage.template'].search([('code', '=', code)], limit=1).default_value
|
||||
setattr(contract, code, value)
|
||||
else:
|
||||
setattr(contract, code, 0.0)
|
||||
|
||||
|
||||
class HrContractAdvandageTemplate(models.Model):
|
||||
_name = 'hr.contract.advantage.template'
|
||||
_description = "Employee's Advantage on Contract"
|
||||
|
||||
name = fields.Char('Name', required=True)
|
||||
code = fields.Char('Code', required=True)
|
||||
lower_bound = fields.Float('Lower Bound', help="Lower bound authorized by the employer for this advantage")
|
||||
upper_bound = fields.Float('Upper Bound', help="Upper bound authorized by the employer for this advantage")
|
||||
default_value = fields.Float('Default value for this advantage')
|
||||
@@ -0,0 +1,17 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class HrEmployee(models.Model):
|
||||
_inherit = 'hr.employee'
|
||||
_description = 'Employee'
|
||||
|
||||
slip_ids = fields.One2many('hr.payslip', 'employee_id', string='Payslips', readonly=True)
|
||||
payslip_count = fields.Integer(compute='_compute_payslip_count', string='Payslips', groups="hr_payroll.group_hr_payroll_user")
|
||||
|
||||
@api.multi
|
||||
def _compute_payslip_count(self):
|
||||
for employee in self:
|
||||
employee.payslip_count = len(employee.slip_ids)
|
||||
@@ -2,157 +2,15 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime
|
||||
from datetime import time as datetime_time
|
||||
from dateutil import relativedelta
|
||||
|
||||
import babel
|
||||
|
||||
from odoo import api, fields, models, tools, _
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from odoo.tools.safe_eval import safe_eval
|
||||
|
||||
from odoo.addons import decimal_precision as dp
|
||||
|
||||
|
||||
class HrPayrollStructure(models.Model):
|
||||
"""
|
||||
Salary structure used to defined
|
||||
- Basic
|
||||
- Allowances
|
||||
- Deductions
|
||||
"""
|
||||
_name = 'hr.payroll.structure'
|
||||
_description = 'Salary Structure'
|
||||
|
||||
@api.model
|
||||
def _get_parent(self):
|
||||
return self.env.ref('hr_payroll.structure_base', False)
|
||||
|
||||
name = fields.Char(required=True)
|
||||
code = fields.Char(string='Reference', required=True)
|
||||
company_id = fields.Many2one('res.company', string='Company', required=True,
|
||||
copy=False, default=lambda self: self.env['res.company']._company_default_get())
|
||||
note = fields.Text(string='Description')
|
||||
parent_id = fields.Many2one('hr.payroll.structure', string='Parent', default=_get_parent)
|
||||
children_ids = fields.One2many('hr.payroll.structure', 'parent_id', string='Children', copy=True)
|
||||
rule_ids = fields.Many2many('hr.salary.rule', 'hr_structure_salary_rule_rel', 'struct_id', 'rule_id', string='Salary Rules')
|
||||
|
||||
@api.constrains('parent_id')
|
||||
def _check_parent_id(self):
|
||||
if not self._check_recursion():
|
||||
raise ValidationError(_('Error ! You cannot create a recursive Salary Structure.'))
|
||||
|
||||
@api.multi
|
||||
def copy(self, default=None):
|
||||
self.ensure_one()
|
||||
default = dict(default or {}, code=_("%s (copy)") % (self.code))
|
||||
return super(HrPayrollStructure, self).copy(default)
|
||||
|
||||
@api.multi
|
||||
def get_all_rules(self):
|
||||
"""
|
||||
@return: returns a list of tuple (id, sequence) of rules that are maybe to apply
|
||||
"""
|
||||
all_rules = []
|
||||
for struct in self:
|
||||
all_rules += struct.rule_ids._recursive_search_of_rules()
|
||||
return all_rules
|
||||
|
||||
@api.multi
|
||||
def _get_parent_structure(self):
|
||||
parent = self.mapped('parent_id')
|
||||
if parent:
|
||||
parent = parent._get_parent_structure()
|
||||
return parent + self
|
||||
|
||||
|
||||
class HrContract(models.Model):
|
||||
"""
|
||||
Employee contract based on the visa, work permits
|
||||
allows to configure different Salary structure
|
||||
"""
|
||||
_inherit = 'hr.contract'
|
||||
_description = 'Employee Contract'
|
||||
|
||||
struct_id = fields.Many2one('hr.payroll.structure', string='Salary Structure')
|
||||
schedule_pay = fields.Selection([
|
||||
('monthly', 'Monthly'),
|
||||
('quarterly', 'Quarterly'),
|
||||
('semi-annually', 'Semi-annually'),
|
||||
('annually', 'Annually'),
|
||||
('weekly', 'Weekly'),
|
||||
('bi-weekly', 'Bi-weekly'),
|
||||
('bi-monthly', 'Bi-monthly'),
|
||||
], string='Scheduled Pay', index=True, default='monthly')
|
||||
|
||||
@api.multi
|
||||
def get_all_structures(self):
|
||||
"""
|
||||
@return: the structures linked to the given contracts, ordered by hierachy (parent=False first,
|
||||
then first level children and so on) and without duplicata
|
||||
"""
|
||||
structures = self.mapped('struct_id')
|
||||
if not structures:
|
||||
return []
|
||||
# YTI TODO return browse records
|
||||
return list(set(structures._get_parent_structure().ids))
|
||||
|
||||
|
||||
class HrContributionRegister(models.Model):
|
||||
_name = 'hr.contribution.register'
|
||||
_description = 'Contribution Register'
|
||||
|
||||
company_id = fields.Many2one('res.company', string='Company',
|
||||
default=lambda self: self.env['res.company']._company_default_get())
|
||||
partner_id = fields.Many2one('res.partner', string='Partner')
|
||||
name = fields.Char(required=True)
|
||||
register_line_ids = fields.One2many('hr.payslip.line', 'register_id',
|
||||
string='Register Line', readonly=True)
|
||||
note = fields.Text(string='Description')
|
||||
|
||||
|
||||
class HrSalaryRuleCategory(models.Model):
|
||||
_name = 'hr.salary.rule.category'
|
||||
_description = 'Salary Rule Category'
|
||||
|
||||
name = fields.Char(required=True)
|
||||
code = fields.Char(required=True)
|
||||
parent_id = fields.Many2one('hr.salary.rule.category', string='Parent',
|
||||
help="Linking a salary category to its parent is used only for the reporting purpose.")
|
||||
children_ids = fields.One2many('hr.salary.rule.category', 'parent_id', string='Children')
|
||||
note = fields.Text(string='Description')
|
||||
company_id = fields.Many2one('res.company', string='Company',
|
||||
default=lambda self: self.env['res.company']._company_default_get())
|
||||
|
||||
|
||||
class HrPayslipRun(models.Model):
|
||||
_name = 'hr.payslip.run'
|
||||
_description = 'Payslip Batches'
|
||||
|
||||
name = fields.Char(required=True, readonly=True, states={'draft': [('readonly', False)]})
|
||||
slip_ids = fields.One2many('hr.payslip', 'payslip_run_id', string='Payslips', readonly=True,
|
||||
states={'draft': [('readonly', False)]})
|
||||
state = fields.Selection([
|
||||
('draft', 'Draft'),
|
||||
('close', 'Close'),
|
||||
], string='Status', index=True, readonly=True, copy=False, default='draft')
|
||||
date_start = fields.Date(string='Date From', required=True, readonly=True,
|
||||
states={'draft': [('readonly', False)]}, default=time.strftime('%Y-%m-01'))
|
||||
date_end = fields.Date(string='Date To', required=True, readonly=True,
|
||||
states={'draft': [('readonly', False)]},
|
||||
default=str(datetime.now() + relativedelta.relativedelta(months=+1, day=1, days=-1))[:10])
|
||||
credit_note = fields.Boolean(string='Credit Note', readonly=True,
|
||||
states={'draft': [('readonly', False)]},
|
||||
help="If its checked, indicates that all payslips generated from here are refund payslips.")
|
||||
|
||||
@api.multi
|
||||
def draft_payslip_run(self):
|
||||
return self.write({'state': 'draft'})
|
||||
|
||||
@api.multi
|
||||
def close_payslip_run(self):
|
||||
return self.write({'state': 'close'})
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
|
||||
|
||||
class HrPayslip(models.Model):
|
||||
@@ -271,7 +129,7 @@ class HrPayslip(models.Model):
|
||||
raise UserError(_('You cannot delete a payslip which is not draft or cancelled!'))
|
||||
return super(HrPayslip, self).unlink()
|
||||
|
||||
#TODO move this function into hr_contract module, on hr.employee object
|
||||
# TODO move this function into hr_contract module, on hr.employee object
|
||||
@api.model
|
||||
def get_contract(self, employee, date_from, date_to):
|
||||
"""
|
||||
@@ -280,20 +138,20 @@ class HrPayslip(models.Model):
|
||||
@param date_to: date field
|
||||
@return: returns the ids of all the contracts for the given employee that need to be considered for the given dates
|
||||
"""
|
||||
#a contract is valid if it ends between the given dates
|
||||
# a contract is valid if it ends between the given dates
|
||||
clause_1 = ['&', ('date_end', '<=', date_to), ('date_end', '>=', date_from)]
|
||||
#OR if it starts between the given dates
|
||||
# OR if it starts between the given dates
|
||||
clause_2 = ['&', ('date_start', '<=', date_to), ('date_start', '>=', date_from)]
|
||||
#OR if it starts before the date_from and finish after the date_end (or never finish)
|
||||
# OR if it starts before the date_from and finish after the date_end (or never finish)
|
||||
clause_3 = ['&', ('date_start', '<=', date_from), '|', ('date_end', '=', False), ('date_end', '>=', date_to)]
|
||||
clause_final = [('employee_id', '=', employee.id), '|', '|'] + clause_1 + clause_2 + clause_3
|
||||
clause_final = [('employee_id', '=', employee.id), ('state', '=', 'open'), '|', '|'] + clause_1 + clause_2 + clause_3
|
||||
return self.env['hr.contract'].search(clause_final).ids
|
||||
|
||||
@api.multi
|
||||
def compute_sheet(self):
|
||||
for payslip in self:
|
||||
number = payslip.number or self.env['ir.sequence'].next_by_code('salary.slip')
|
||||
#delete old payslip lines
|
||||
# delete old payslip lines
|
||||
payslip.line_ids.unlink()
|
||||
# set the list of contract for which the rules have to be applied
|
||||
# if we don't give the contract, then the rules to apply should be for all current contracts of the employee
|
||||
@@ -304,14 +162,14 @@ class HrPayslip(models.Model):
|
||||
return True
|
||||
|
||||
@api.model
|
||||
def get_worked_day_lines(self, contract_ids, date_from, date_to):
|
||||
def get_worked_day_lines(self, contracts, date_from, date_to):
|
||||
"""
|
||||
@param contract_ids: list of contract id
|
||||
@param contract: Browse record of contracts
|
||||
@return: returns a list of dict containing the input that should be applied for the given contract between date_from and date_to
|
||||
"""
|
||||
res = []
|
||||
# fill only if the contract as a working schedule linked
|
||||
for contract in self.env['hr.contract'].browse(contract_ids).filtered(lambda contract: contract.resource_calendar_id):
|
||||
for contract in contracts.filtered(lambda contract: contract.resource_calendar_id):
|
||||
day_from = datetime.combine(fields.Date.from_string(date_from), datetime_time.min)
|
||||
day_to = datetime.combine(fields.Date.from_string(date_to), datetime_time.max)
|
||||
|
||||
@@ -329,7 +187,7 @@ class HrPayslip(models.Model):
|
||||
'number_of_hours': 0.0,
|
||||
'contract_id': contract.id,
|
||||
})
|
||||
leave_time = (interval[1] - interval[0]).seconds/3600
|
||||
leave_time = (interval[1] - interval[0]).seconds / 3600
|
||||
current_leave_struct['number_of_hours'] += leave_time
|
||||
work_hours = contract.employee_id.get_day_work_hours_count(interval[0].date(), calendar=contract.resource_calendar_id)
|
||||
current_leave_struct['number_of_days'] += leave_time / work_hours
|
||||
@@ -348,12 +206,10 @@ class HrPayslip(models.Model):
|
||||
res += [attendances] + leaves.values()
|
||||
return res
|
||||
|
||||
# YTI TODO contract_ids should be a browse record
|
||||
@api.model
|
||||
def get_inputs(self, contract_ids, date_from, date_to):
|
||||
def get_inputs(self, contracts, date_from, date_to):
|
||||
res = []
|
||||
|
||||
contracts = self.env['hr.contract'].browse(contract_ids)
|
||||
structure_ids = contracts.get_all_structures()
|
||||
rule_ids = self.env['hr.payroll.structure'].browse(structure_ids).get_all_rules()
|
||||
sorted_rule_ids = [id for id, sequence in sorted(rule_ids, key=lambda x:x[1])]
|
||||
@@ -374,9 +230,7 @@ class HrPayslip(models.Model):
|
||||
def _sum_salary_rule_category(localdict, category, amount):
|
||||
if category.parent_id:
|
||||
localdict = _sum_salary_rule_category(localdict, category.parent_id, amount)
|
||||
if category.code in localdict['categories'].dict:
|
||||
amount += localdict['categories'].dict[category.code]
|
||||
localdict['categories'].dict[category.code] = amount
|
||||
localdict['categories'].dict[category.code] = category.code in localdict['categories'].dict and localdict['categories'].dict[category.code] + amount or amount
|
||||
return localdict
|
||||
|
||||
class BrowsableObject(object):
|
||||
@@ -567,8 +421,9 @@ class HrPayslip(models.Model):
|
||||
'struct_id': struct.id,
|
||||
})
|
||||
#computation of the salary input
|
||||
worked_days_line_ids = self.get_worked_day_lines(contract_ids, date_from, date_to)
|
||||
input_line_ids = self.get_inputs(contract_ids, date_from, date_to)
|
||||
contracts = self.env['hr.contract'].browse(contract_ids)
|
||||
worked_days_line_ids = self.get_worked_day_lines(contracts, date_from, date_to)
|
||||
input_line_ids = self.get_inputs(contracts, date_from, date_to)
|
||||
res['value'].update({
|
||||
'worked_days_line_ids': worked_days_line_ids,
|
||||
'input_line_ids': input_line_ids,
|
||||
@@ -601,13 +456,14 @@ class HrPayslip(models.Model):
|
||||
self.struct_id = self.contract_id.struct_id
|
||||
|
||||
#computation of the salary input
|
||||
worked_days_line_ids = self.get_worked_day_lines(contract_ids, date_from, date_to)
|
||||
contracts = self.env['hr.contract'].browse(contract_ids)
|
||||
worked_days_line_ids = self.get_worked_day_lines(contracts, date_from, date_to)
|
||||
worked_days_lines = self.worked_days_line_ids.browse([])
|
||||
for r in worked_days_line_ids:
|
||||
worked_days_lines += worked_days_lines.new(r)
|
||||
self.worked_days_line_ids = worked_days_lines
|
||||
|
||||
input_line_ids = self.get_inputs(contract_ids, date_from, date_to)
|
||||
input_line_ids = self.get_inputs(contracts, date_from, date_to)
|
||||
input_lines = self.input_line_ids.browse([])
|
||||
for r in input_line_ids:
|
||||
input_lines += input_lines.new(r)
|
||||
@@ -621,6 +477,45 @@ class HrPayslip(models.Model):
|
||||
self.with_context(contract=True).onchange_employee()
|
||||
return
|
||||
|
||||
def get_salary_line_total(self, code):
|
||||
self.ensure_one()
|
||||
line = self.line_ids.filtered(lambda line: line.code == code)
|
||||
if line:
|
||||
return line[0].total
|
||||
else:
|
||||
return 0.0
|
||||
|
||||
|
||||
class HrPayslipLine(models.Model):
|
||||
_name = 'hr.payslip.line'
|
||||
_inherit = 'hr.salary.rule'
|
||||
_description = 'Payslip Line'
|
||||
_order = 'contract_id, sequence'
|
||||
|
||||
slip_id = fields.Many2one('hr.payslip', string='Pay Slip', required=True, ondelete='cascade')
|
||||
salary_rule_id = fields.Many2one('hr.salary.rule', string='Rule', required=True)
|
||||
employee_id = fields.Many2one('hr.employee', string='Employee', required=True)
|
||||
contract_id = fields.Many2one('hr.contract', string='Contract', required=True, index=True)
|
||||
rate = fields.Float(string='Rate (%)', digits=dp.get_precision('Payroll Rate'), default=100.0)
|
||||
amount = fields.Float(digits=dp.get_precision('Payroll'))
|
||||
quantity = fields.Float(digits=dp.get_precision('Payroll'), default=1.0)
|
||||
total = fields.Float(compute='_compute_total', string='Total', digits=dp.get_precision('Payroll'), store=True)
|
||||
|
||||
@api.depends('quantity', 'amount', 'rate')
|
||||
def _compute_total(self):
|
||||
for line in self:
|
||||
line.total = float(line.quantity) * line.amount * line.rate / 100
|
||||
|
||||
@api.model
|
||||
def create(self, values):
|
||||
if 'employee_id' not in values or 'contract_id' not in values:
|
||||
payslip = self.env['hr.payslip'].browse(values.get('slip_id'))
|
||||
values['employee_id'] = values.get('employee_id') or payslip.employee_id.id
|
||||
values['contract_id'] = values.get('contract_id') or payslip.contract_id and payslip.contract_id.id
|
||||
if not values['contract_id']:
|
||||
raise UserError(_('You must set a contract to create a payslip line.'))
|
||||
return super(HrPayslipLine, self).create(values)
|
||||
|
||||
|
||||
class HrPayslipWorkedDays(models.Model):
|
||||
_name = 'hr.payslip.worked_days'
|
||||
@@ -653,194 +548,30 @@ class HrPayslipInput(models.Model):
|
||||
help="The contract for which applied this input")
|
||||
|
||||
|
||||
class HrSalaryRule(models.Model):
|
||||
_name = 'hr.salary.rule'
|
||||
class HrPayslipRun(models.Model):
|
||||
_name = 'hr.payslip.run'
|
||||
_description = 'Payslip Batches'
|
||||
|
||||
name = fields.Char(required=True)
|
||||
code = fields.Char(required=True,
|
||||
help="The code of salary rules can be used as reference in computation of other rules. "
|
||||
"In that case, it is case sensitive.")
|
||||
sequence = fields.Integer(required=True, index=True, default=5,
|
||||
help='Use to arrange calculation sequence')
|
||||
quantity = fields.Char(default='1.0',
|
||||
help="It is used in computation for percentage and fixed amount. "
|
||||
"For e.g. A rule for Meal Voucher having fixed amount of "
|
||||
u"1€ per worked day can have its quantity defined in expression "
|
||||
"like worked_days.WORK100.number_of_days.")
|
||||
category_id = fields.Many2one('hr.salary.rule.category', string='Category', required=True)
|
||||
active = fields.Boolean(default=True,
|
||||
help="If the active field is set to false, it will allow you to hide the salary rule without removing it.")
|
||||
appears_on_payslip = fields.Boolean(string='Appears on Payslip', default=True,
|
||||
help="Used to display the salary rule on payslip.")
|
||||
parent_rule_id = fields.Many2one('hr.salary.rule', string='Parent Salary Rule', index=True)
|
||||
company_id = fields.Many2one('res.company', string='Company',
|
||||
default=lambda self: self.env['res.company']._company_default_get())
|
||||
condition_select = fields.Selection([
|
||||
('none', 'Always True'),
|
||||
('range', 'Range'),
|
||||
('python', 'Python Expression')
|
||||
], string="Condition Based on", default='none', required=True)
|
||||
condition_range = fields.Char(string='Range Based on', default='contract.wage',
|
||||
help='This will be used to compute the % fields values; in general it is on basic, '
|
||||
'but you can also use categories code fields in lowercase as a variable names '
|
||||
'(hra, ma, lta, etc.) and the variable basic.')
|
||||
condition_python = fields.Text(string='Python Condition', required=True,
|
||||
default='''
|
||||
# Available variables:
|
||||
#----------------------
|
||||
# payslip: object containing the payslips
|
||||
# employee: hr.employee object
|
||||
# contract: hr.contract object
|
||||
# rules: object containing the rules code (previously computed)
|
||||
# categories: object containing the computed salary rule categories (sum of amount of all rules belonging to that category).
|
||||
# worked_days: object containing the computed worked days
|
||||
# inputs: object containing the computed inputs
|
||||
|
||||
# Note: returned value have to be set in the variable 'result'
|
||||
|
||||
result = rules.NET > categories.NET * 0.10''',
|
||||
help='Applied this rule for calculation if condition is true. You can specify condition like basic > 1000.')
|
||||
condition_range_min = fields.Float(string='Minimum Range', help="The minimum amount, applied for this rule.")
|
||||
condition_range_max = fields.Float(string='Maximum Range', help="The maximum amount, applied for this rule.")
|
||||
amount_select = fields.Selection([
|
||||
('percentage', 'Percentage (%)'),
|
||||
('fix', 'Fixed Amount'),
|
||||
('code', 'Python Code'),
|
||||
], string='Amount Type', index=True, required=True, default='fix', help="The computation method for the rule amount.")
|
||||
amount_fix = fields.Float(string='Fixed Amount', digits=dp.get_precision('Payroll'))
|
||||
amount_percentage = fields.Float(string='Percentage (%)', digits=dp.get_precision('Payroll Rate'),
|
||||
help='For example, enter 50.0 to apply a percentage of 50%')
|
||||
amount_python_compute = fields.Text(string='Python Code',
|
||||
default='''
|
||||
# Available variables:
|
||||
#----------------------
|
||||
# payslip: object containing the payslips
|
||||
# employee: hr.employee object
|
||||
# contract: hr.contract object
|
||||
# rules: object containing the rules code (previously computed)
|
||||
# categories: object containing the computed salary rule categories (sum of amount of all rules belonging to that category).
|
||||
# worked_days: object containing the computed worked days.
|
||||
# inputs: object containing the computed inputs.
|
||||
|
||||
# Note: returned value have to be set in the variable 'result'
|
||||
|
||||
result = contract.wage * 0.10''')
|
||||
amount_percentage_base = fields.Char(string='Percentage based on', help='result will be affected to a variable')
|
||||
child_ids = fields.One2many('hr.salary.rule', 'parent_rule_id', string='Child Salary Rule', copy=True)
|
||||
register_id = fields.Many2one('hr.contribution.register', string='Contribution Register',
|
||||
help="Eventual third party involved in the salary payment of the employees.")
|
||||
input_ids = fields.One2many('hr.rule.input', 'input_id', string='Inputs', copy=True)
|
||||
note = fields.Text(string='Description')
|
||||
name = fields.Char(required=True, readonly=True, states={'draft': [('readonly', False)]})
|
||||
slip_ids = fields.One2many('hr.payslip', 'payslip_run_id', string='Payslips', readonly=True,
|
||||
states={'draft': [('readonly', False)]})
|
||||
state = fields.Selection([
|
||||
('draft', 'Draft'),
|
||||
('close', 'Close'),
|
||||
], string='Status', index=True, readonly=True, copy=False, default='draft')
|
||||
date_start = fields.Date(string='Date From', required=True, readonly=True,
|
||||
states={'draft': [('readonly', False)]}, default=time.strftime('%Y-%m-01'))
|
||||
date_end = fields.Date(string='Date To', required=True, readonly=True,
|
||||
states={'draft': [('readonly', False)]},
|
||||
default=str(datetime.now() + relativedelta.relativedelta(months=+1, day=1, days=-1))[:10])
|
||||
credit_note = fields.Boolean(string='Credit Note', readonly=True,
|
||||
states={'draft': [('readonly', False)]},
|
||||
help="If its checked, indicates that all payslips generated from here are refund payslips.")
|
||||
|
||||
@api.multi
|
||||
def _recursive_search_of_rules(self):
|
||||
"""
|
||||
@return: returns a list of tuple (id, sequence) which are all the children of the passed rule_ids
|
||||
"""
|
||||
children_rules = []
|
||||
for rule in self.filtered(lambda rule: rule.child_ids):
|
||||
children_rules += rule.child_ids._recursive_search_of_rules()
|
||||
return [(rule.id, rule.sequence) for rule in self] + children_rules
|
||||
|
||||
#TODO should add some checks on the type of result (should be float)
|
||||
@api.multi
|
||||
def compute_rule(self, localdict):
|
||||
"""
|
||||
:param localdict: dictionary containing the environement in which to compute the rule
|
||||
:return: returns a tuple build as the base/amount computed, the quantity and the rate
|
||||
:rtype: (float, float, float)
|
||||
"""
|
||||
self.ensure_one()
|
||||
if self.amount_select == 'fix':
|
||||
try:
|
||||
return self.amount_fix, float(safe_eval(self.quantity, localdict)), 100.0
|
||||
except:
|
||||
raise UserError(_('Wrong quantity defined for salary rule %s (%s).') % (self.name, self.code))
|
||||
elif self.amount_select == 'percentage':
|
||||
try:
|
||||
return (float(safe_eval(self.amount_percentage_base, localdict)),
|
||||
float(safe_eval(self.quantity, localdict)),
|
||||
self.amount_percentage)
|
||||
except:
|
||||
raise UserError(_('Wrong percentage base or quantity defined for salary rule %s (%s).') % (self.name, self.code))
|
||||
else:
|
||||
try:
|
||||
safe_eval(self.amount_python_compute, localdict, mode='exec', nocopy=True)
|
||||
return float(localdict['result']), 'result_qty' in localdict and localdict['result_qty'] or 1.0, 'result_rate' in localdict and localdict['result_rate'] or 100.0
|
||||
except:
|
||||
raise UserError(_('Wrong python code defined for salary rule %s (%s).') % (self.name, self.code))
|
||||
def draft_payslip_run(self):
|
||||
return self.write({'state': 'draft'})
|
||||
|
||||
@api.multi
|
||||
def satisfy_condition(self, localdict):
|
||||
"""
|
||||
@param contract_id: id of hr.contract to be tested
|
||||
@return: returns True if the given rule match the condition for the given contract. Return False otherwise.
|
||||
"""
|
||||
self.ensure_one()
|
||||
|
||||
if self.condition_select == 'none':
|
||||
return True
|
||||
elif self.condition_select == 'range':
|
||||
try:
|
||||
result = safe_eval(self.condition_range, localdict)
|
||||
return self.condition_range_min <= result and result <= self.condition_range_max or False
|
||||
except:
|
||||
raise UserError(_('Wrong range condition defined for salary rule %s (%s).') % (self.name, self.code))
|
||||
else: # python code
|
||||
try:
|
||||
safe_eval(self.condition_python, localdict, mode='exec', nocopy=True)
|
||||
return 'result' in localdict and localdict['result'] or False
|
||||
except:
|
||||
raise UserError(_('Wrong python condition defined for salary rule %s (%s).') % (self.name, self.code))
|
||||
|
||||
|
||||
class HrRuleInput(models.Model):
|
||||
_name = 'hr.rule.input'
|
||||
_description = 'Salary Rule Input'
|
||||
|
||||
name = fields.Char(string='Description', required=True)
|
||||
code = fields.Char(required=True, help="The code that can be used in the salary rules")
|
||||
input_id = fields.Many2one('hr.salary.rule', string='Salary Rule Input', required=True)
|
||||
|
||||
|
||||
class HrPayslipLine(models.Model):
|
||||
_name = 'hr.payslip.line'
|
||||
_inherit = 'hr.salary.rule'
|
||||
_description = 'Payslip Line'
|
||||
_order = 'contract_id, sequence'
|
||||
|
||||
slip_id = fields.Many2one('hr.payslip', string='Pay Slip', required=True, ondelete='cascade')
|
||||
salary_rule_id = fields.Many2one('hr.salary.rule', string='Rule', required=True)
|
||||
employee_id = fields.Many2one('hr.employee', string='Employee', required=True)
|
||||
contract_id = fields.Many2one('hr.contract', string='Contract', required=True, index=True)
|
||||
rate = fields.Float(string='Rate (%)', digits=dp.get_precision('Payroll Rate'), default=100.0)
|
||||
amount = fields.Float(digits=dp.get_precision('Payroll'))
|
||||
quantity = fields.Float(digits=dp.get_precision('Payroll'), default=1.0)
|
||||
total = fields.Float(compute='_compute_total', string='Total', digits=dp.get_precision('Payroll'), store=True)
|
||||
|
||||
@api.depends('quantity', 'amount', 'rate')
|
||||
def _compute_total(self):
|
||||
for line in self:
|
||||
line.total = float(line.quantity) * line.amount * line.rate / 100
|
||||
|
||||
@api.model
|
||||
def create(self, values):
|
||||
if 'employee_id' not in values or 'contract_id' not in values:
|
||||
payslip = self.env['hr.payslip'].browse(values.get('slip_id'))
|
||||
values['employee_id'] = values.get('employee_id') or payslip.employee_id.id
|
||||
values['contract_id'] = values.get('contract_id') or payslip.contract_id and payslip.contract_id.id
|
||||
if not values['contract_id']:
|
||||
raise UserError(_('You must set a contract to create a payslip line.'))
|
||||
return super(HrPayslipLine, self).create(values)
|
||||
|
||||
class HrEmployee(models.Model):
|
||||
_inherit = 'hr.employee'
|
||||
_description = 'Employee'
|
||||
|
||||
slip_ids = fields.One2many('hr.payslip', 'employee_id', string='Payslips', readonly=True)
|
||||
payslip_count = fields.Integer(compute='_compute_payslip_count', string='Payslips', groups="hr_payroll.group_hr_payroll_user")
|
||||
|
||||
@api.multi
|
||||
def _compute_payslip_count(self):
|
||||
for employee in self:
|
||||
employee.payslip_count = len(employee.slip_ids)
|
||||
def close_payslip_run(self):
|
||||
return self.write({'state': 'close'})
|
||||
@@ -0,0 +1,236 @@
|
||||
# -*- 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 odoo.tools.safe_eval import safe_eval
|
||||
|
||||
from odoo.addons import decimal_precision as dp
|
||||
|
||||
class HrPayrollStructure(models.Model):
|
||||
"""
|
||||
Salary structure used to defined
|
||||
- Basic
|
||||
- Allowances
|
||||
- Deductions
|
||||
"""
|
||||
_name = 'hr.payroll.structure'
|
||||
_description = 'Salary Structure'
|
||||
|
||||
@api.model
|
||||
def _get_parent(self):
|
||||
return self.env.ref('hr_payroll.structure_base', False)
|
||||
|
||||
name = fields.Char(required=True)
|
||||
code = fields.Char(string='Reference', required=True)
|
||||
company_id = fields.Many2one('res.company', string='Company', required=True,
|
||||
copy=False, default=lambda self: self.env['res.company']._company_default_get())
|
||||
note = fields.Text(string='Description')
|
||||
parent_id = fields.Many2one('hr.payroll.structure', string='Parent', default=_get_parent)
|
||||
children_ids = fields.One2many('hr.payroll.structure', 'parent_id', string='Children', copy=True)
|
||||
rule_ids = fields.Many2many('hr.salary.rule', 'hr_structure_salary_rule_rel', 'struct_id', 'rule_id', string='Salary Rules')
|
||||
|
||||
@api.constrains('parent_id')
|
||||
def _check_parent_id(self):
|
||||
if not self._check_recursion():
|
||||
raise ValidationError(_('Error ! You cannot create a recursive Salary Structure.'))
|
||||
|
||||
@api.multi
|
||||
def copy(self, default=None):
|
||||
self.ensure_one()
|
||||
default = dict(default or {}, code=_("%s (copy)") % (self.code))
|
||||
return super(HrPayrollStructure, self).copy(default)
|
||||
|
||||
@api.multi
|
||||
def get_all_rules(self):
|
||||
"""
|
||||
@return: returns a list of tuple (id, sequence) of rules that are maybe to apply
|
||||
"""
|
||||
all_rules = []
|
||||
for struct in self:
|
||||
all_rules += struct.rule_ids._recursive_search_of_rules()
|
||||
return all_rules
|
||||
|
||||
@api.multi
|
||||
def _get_parent_structure(self):
|
||||
parent = self.mapped('parent_id')
|
||||
if parent:
|
||||
parent = parent._get_parent_structure()
|
||||
return parent + self
|
||||
|
||||
|
||||
class HrContributionRegister(models.Model):
|
||||
_name = 'hr.contribution.register'
|
||||
_description = 'Contribution Register'
|
||||
|
||||
company_id = fields.Many2one('res.company', string='Company',
|
||||
default=lambda self: self.env['res.company']._company_default_get())
|
||||
partner_id = fields.Many2one('res.partner', string='Partner')
|
||||
name = fields.Char(required=True)
|
||||
register_line_ids = fields.One2many('hr.payslip.line', 'register_id',
|
||||
string='Register Line', readonly=True)
|
||||
note = fields.Text(string='Description')
|
||||
|
||||
|
||||
class HrSalaryRuleCategory(models.Model):
|
||||
_name = 'hr.salary.rule.category'
|
||||
_description = 'Salary Rule Category'
|
||||
|
||||
name = fields.Char(required=True)
|
||||
code = fields.Char(required=True)
|
||||
parent_id = fields.Many2one('hr.salary.rule.category', string='Parent',
|
||||
help="Linking a salary category to its parent is used only for the reporting purpose.")
|
||||
children_ids = fields.One2many('hr.salary.rule.category', 'parent_id', string='Children')
|
||||
note = fields.Text(string='Description')
|
||||
company_id = fields.Many2one('res.company', string='Company',
|
||||
default=lambda self: self.env['res.company']._company_default_get())
|
||||
|
||||
|
||||
class HrSalaryRule(models.Model):
|
||||
_name = 'hr.salary.rule'
|
||||
|
||||
name = fields.Char(required=True)
|
||||
code = fields.Char(required=True,
|
||||
help="The code of salary rules can be used as reference in computation of other rules. "
|
||||
"In that case, it is case sensitive.")
|
||||
sequence = fields.Integer(required=True, index=True, default=5,
|
||||
help='Use to arrange calculation sequence')
|
||||
quantity = fields.Char(default='1.0',
|
||||
help="It is used in computation for percentage and fixed amount. "
|
||||
"For e.g. A rule for Meal Voucher having fixed amount of "
|
||||
u"1€ per worked day can have its quantity defined in expression "
|
||||
"like worked_days.WORK100.number_of_days.")
|
||||
category_id = fields.Many2one('hr.salary.rule.category', string='Category', required=True)
|
||||
active = fields.Boolean(default=True,
|
||||
help="If the active field is set to false, it will allow you to hide the salary rule without removing it.")
|
||||
appears_on_payslip = fields.Boolean(string='Appears on Payslip', default=True,
|
||||
help="Used to display the salary rule on payslip.")
|
||||
parent_rule_id = fields.Many2one('hr.salary.rule', string='Parent Salary Rule', index=True)
|
||||
company_id = fields.Many2one('res.company', string='Company',
|
||||
default=lambda self: self.env['res.company']._company_default_get())
|
||||
condition_select = fields.Selection([
|
||||
('none', 'Always True'),
|
||||
('range', 'Range'),
|
||||
('python', 'Python Expression')
|
||||
], string="Condition Based on", default='none', required=True)
|
||||
condition_range = fields.Char(string='Range Based on', default='contract.wage',
|
||||
help='This will be used to compute the % fields values; in general it is on basic, '
|
||||
'but you can also use categories code fields in lowercase as a variable names '
|
||||
'(hra, ma, lta, etc.) and the variable basic.')
|
||||
condition_python = fields.Text(string='Python Condition', required=True,
|
||||
default='''
|
||||
# Available variables:
|
||||
#----------------------
|
||||
# payslip: object containing the payslips
|
||||
# employee: hr.employee object
|
||||
# contract: hr.contract object
|
||||
# rules: object containing the rules code (previously computed)
|
||||
# categories: object containing the computed salary rule categories (sum of amount of all rules belonging to that category).
|
||||
# worked_days: object containing the computed worked days
|
||||
# inputs: object containing the computed inputs
|
||||
|
||||
# Note: returned value have to be set in the variable 'result'
|
||||
|
||||
result = rules.NET > categories.NET * 0.10''',
|
||||
help='Applied this rule for calculation if condition is true. You can specify condition like basic > 1000.')
|
||||
condition_range_min = fields.Float(string='Minimum Range', help="The minimum amount, applied for this rule.")
|
||||
condition_range_max = fields.Float(string='Maximum Range', help="The maximum amount, applied for this rule.")
|
||||
amount_select = fields.Selection([
|
||||
('percentage', 'Percentage (%)'),
|
||||
('fix', 'Fixed Amount'),
|
||||
('code', 'Python Code'),
|
||||
], string='Amount Type', index=True, required=True, default='fix', help="The computation method for the rule amount.")
|
||||
amount_fix = fields.Float(string='Fixed Amount', digits=dp.get_precision('Payroll'))
|
||||
amount_percentage = fields.Float(string='Percentage (%)', digits=dp.get_precision('Payroll Rate'),
|
||||
help='For example, enter 50.0 to apply a percentage of 50%')
|
||||
amount_python_compute = fields.Text(string='Python Code',
|
||||
default='''
|
||||
# Available variables:
|
||||
#----------------------
|
||||
# payslip: object containing the payslips
|
||||
# employee: hr.employee object
|
||||
# contract: hr.contract object
|
||||
# rules: object containing the rules code (previously computed)
|
||||
# categories: object containing the computed salary rule categories (sum of amount of all rules belonging to that category).
|
||||
# worked_days: object containing the computed worked days.
|
||||
# inputs: object containing the computed inputs.
|
||||
|
||||
# Note: returned value have to be set in the variable 'result'
|
||||
|
||||
result = contract.wage * 0.10''')
|
||||
amount_percentage_base = fields.Char(string='Percentage based on', help='result will be affected to a variable')
|
||||
child_ids = fields.One2many('hr.salary.rule', 'parent_rule_id', string='Child Salary Rule', copy=True)
|
||||
register_id = fields.Many2one('hr.contribution.register', string='Contribution Register',
|
||||
help="Eventual third party involved in the salary payment of the employees.")
|
||||
input_ids = fields.One2many('hr.rule.input', 'input_id', string='Inputs', copy=True)
|
||||
note = fields.Text(string='Description')
|
||||
|
||||
@api.multi
|
||||
def _recursive_search_of_rules(self):
|
||||
"""
|
||||
@return: returns a list of tuple (id, sequence) which are all the children of the passed rule_ids
|
||||
"""
|
||||
children_rules = []
|
||||
for rule in self.filtered(lambda rule: rule.child_ids):
|
||||
children_rules += rule.child_ids._recursive_search_of_rules()
|
||||
return [(rule.id, rule.sequence) for rule in self] + children_rules
|
||||
|
||||
#TODO should add some checks on the type of result (should be float)
|
||||
@api.multi
|
||||
def compute_rule(self, localdict):
|
||||
"""
|
||||
:param localdict: dictionary containing the environement in which to compute the rule
|
||||
:return: returns a tuple build as the base/amount computed, the quantity and the rate
|
||||
:rtype: (float, float, float)
|
||||
"""
|
||||
self.ensure_one()
|
||||
if self.amount_select == 'fix':
|
||||
try:
|
||||
return self.amount_fix, float(safe_eval(self.quantity, localdict)), 100.0
|
||||
except:
|
||||
raise UserError(_('Wrong quantity defined for salary rule %s (%s).') % (self.name, self.code))
|
||||
elif self.amount_select == 'percentage':
|
||||
try:
|
||||
return (float(safe_eval(self.amount_percentage_base, localdict)),
|
||||
float(safe_eval(self.quantity, localdict)),
|
||||
self.amount_percentage)
|
||||
except:
|
||||
raise UserError(_('Wrong percentage base or quantity defined for salary rule %s (%s).') % (self.name, self.code))
|
||||
else:
|
||||
try:
|
||||
safe_eval(self.amount_python_compute, localdict, mode='exec', nocopy=True)
|
||||
return float(localdict['result']), 'result_qty' in localdict and localdict['result_qty'] or 1.0, 'result_rate' in localdict and localdict['result_rate'] or 100.0
|
||||
except:
|
||||
raise UserError(_('Wrong python code defined for salary rule %s (%s).') % (self.name, self.code))
|
||||
|
||||
@api.multi
|
||||
def satisfy_condition(self, localdict):
|
||||
"""
|
||||
@param contract_id: id of hr.contract to be tested
|
||||
@return: returns True if the given rule match the condition for the given contract. Return False otherwise.
|
||||
"""
|
||||
self.ensure_one()
|
||||
|
||||
if self.condition_select == 'none':
|
||||
return True
|
||||
elif self.condition_select == 'range':
|
||||
try:
|
||||
result = safe_eval(self.condition_range, localdict)
|
||||
return self.condition_range_min <= result and result <= self.condition_range_max or False
|
||||
except:
|
||||
raise UserError(_('Wrong range condition defined for salary rule %s (%s).') % (self.name, self.code))
|
||||
else: # python code
|
||||
try:
|
||||
safe_eval(self.condition_python, localdict, mode='exec', nocopy=True)
|
||||
return 'result' in localdict and localdict['result'] or False
|
||||
except:
|
||||
raise UserError(_('Wrong python condition defined for salary rule %s (%s).') % (self.name, self.code))
|
||||
|
||||
|
||||
class HrRuleInput(models.Model):
|
||||
_name = 'hr.rule.input'
|
||||
_description = 'Salary Rule Input'
|
||||
|
||||
name = fields.Char(string='Description', required=True)
|
||||
code = fields.Char(required=True, help="The code that can be used in the salary rules")
|
||||
input_id = fields.Many2one('hr.salary.rule', string='Salary Rule Input', required=True)
|
||||
@@ -9,3 +9,4 @@ access_hr_payslip_worked_days_officer,hr.payslip.worked_days.officer,model_hr_pa
|
||||
access_hr_payslip_run,hr.payslip.run,model_hr_payslip_run,hr_payroll.group_hr_payroll_manager,1,1,1,1
|
||||
access_hr_rule_input_officer,hr.rule.input.office,model_hr_rule_input,hr_payroll.group_hr_payroll_user,1,1,1,1
|
||||
access_hr_salary_rule_user,hr.salary.rule.user,model_hr_salary_rule,hr_payroll.group_hr_payroll_user,1,1,1,1
|
||||
access_hr_contract_advantage_template,hr.contract.advantage.template.user,model_hr_contract_advantage_template,hr_payroll.group_hr_payroll_user,1,1,1,1
|
||||
|
||||
|
@@ -0,0 +1,81 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- Root Menus -->
|
||||
|
||||
<menuitem id="menu_hr_payroll_root" name="Payroll" sequence="45" web_icon="hr_payroll,static/description/icon.png"/>
|
||||
<menuitem id="menu_hr_payroll_configuration" name="Configuration" parent="menu_hr_payroll_root"
|
||||
sequence="100" groups="hr_payroll.group_hr_payroll_manager"/>
|
||||
|
||||
<!-- Contract View -->
|
||||
<record id="hr_contract_form_inherit" model="ir.ui.view">
|
||||
<field name="name">hr.contract.view.form.inherit</field>
|
||||
<field name="model">hr.contract</field>
|
||||
<field name="inherit_id" ref="hr_contract.hr_contract_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='type_id']" position="after">
|
||||
<field name="struct_id" required="1"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='type_id']" position="before">
|
||||
<field name="company_id"/>
|
||||
<field name="currency_id" invisible="1"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='resource_calendar_id']" position="after">
|
||||
<field name="schedule_pay"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_contract_advantage_template_view_form" model="ir.ui.view">
|
||||
<field name="name">hr.contract.advantage.template.form</field>
|
||||
<field name="model">hr.contract.advantage.template</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<sheet>
|
||||
<div class="oe_title">
|
||||
<h1>
|
||||
<field name="name" placeholder="Advantage Name"/>
|
||||
</h1>
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
<field name="code"/>
|
||||
</group>
|
||||
</group>
|
||||
<group>
|
||||
<group>
|
||||
<field name="default_value"/>
|
||||
<field name="lower_bound"/>
|
||||
<field name="upper_bound"/>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_contract_advantage_template_view_tree" model="ir.ui.view">
|
||||
<field name="name">hr.contract.advantage.template.tree</field>
|
||||
<field name="model">hr.contract.advantage.template</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree>
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
<field name="lower_bound"/>
|
||||
<field name="upper_bound"/>
|
||||
<field name="default_value"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_contract_advantage_template_action" model="ir.actions.act_window">
|
||||
<field name="name">Contract Advantage Templates</field>
|
||||
<field name="res_model">hr.contract.advantage.template</field>
|
||||
<field name="view_type">form</field>
|
||||
</record>
|
||||
|
||||
<menuitem
|
||||
id="hr_contract_advantage_template_menu_action"
|
||||
action="hr_contract_advantage_template_action"
|
||||
parent="menu_hr_payroll_configuration"
|
||||
sequence="50"/>
|
||||
</odoo>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<!-- Employee View -->
|
||||
<record id="payroll_hr_employee_view_form" model="ir.ui.view">
|
||||
<field name="name">payroll.hr.employee.view.form</field>
|
||||
<field name="model">hr.employee</field>
|
||||
<field name="inherit_id" ref="hr.view_employee_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<button name="toggle_active" position="before">
|
||||
<button name="%(act_hr_employee_payslip_list)d"
|
||||
class="oe_stat_button"
|
||||
icon="fa-money"
|
||||
type="action"
|
||||
groups="hr_payroll.group_hr_payroll_user">
|
||||
<field name="payslip_count" widget="statinfo" string="Payslips"/>
|
||||
</button>
|
||||
</button>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -1,741 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- Root Menus -->
|
||||
|
||||
<menuitem id="menu_hr_payroll_root" name="Payroll" sequence="45" web_icon="hr_payroll,static/description/icon.png"/>
|
||||
<menuitem id="menu_hr_payroll_configuration" name="Configuration" parent="menu_hr_payroll_root"
|
||||
sequence="100" groups="hr_payroll.group_hr_payroll_manager"/>
|
||||
|
||||
<!-- Contract View -->
|
||||
<record id="hr_contract_form_inherit" model="ir.ui.view">
|
||||
<field name="name">hr.contract.view.form.inherit</field>
|
||||
<field name="model">hr.contract</field>
|
||||
<field name="inherit_id" ref="hr_contract.hr_contract_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='wage']" position="after">
|
||||
<field name="struct_id" required="1"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='resource_calendar_id']" position="after">
|
||||
<field name="schedule_pay"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- End Contract View-->
|
||||
|
||||
<!-- Salary structure -->
|
||||
<record id="view_hr_payroll_structure_list_view" model="ir.ui.view">
|
||||
<field name="name">hr.payroll.structure.tree</field>
|
||||
<field name="model">hr.payroll.structure</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Employee Function">
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
<field name="rule_ids"/>
|
||||
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<record id="view_hr_payroll_structure_tree" model="ir.ui.view">
|
||||
<field name="name">hr.payroll.structure.tree</field>
|
||||
<field name="model">hr.payroll.structure</field>
|
||||
<field name="field_parent">children_ids</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Salary Structure">
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_hr_payroll_structure_filter" model="ir.ui.view">
|
||||
<field name="name">hr.payroll.structure.select</field>
|
||||
<field name="model">hr.payroll.structure</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Payroll Structures">
|
||||
<field name="name" string="Payroll Structures" filter_domain="['|',('name','ilike',self),('code','ilike',self)]"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_hr_employee_grade_form" model="ir.ui.view">
|
||||
<field name="name">hr.payroll.structure.form</field>
|
||||
<field name="model">hr.payroll.structure</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Employee Function">
|
||||
<group col="4">
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
<field name="parent_id"/>
|
||||
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
|
||||
</group>
|
||||
<notebook colspan="4">
|
||||
<page string="Salary Rules">
|
||||
<field name="rule_ids" domain="[('parent_rule_id','=',False)]">
|
||||
<tree>
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
<field name="category_id"/>
|
||||
<field name="sequence" invisible="1"/>
|
||||
<field name="register_id"/>
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
</notebook>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_view_hr_payroll_structure_list_form" model="ir.actions.act_window">
|
||||
<field name="name">Salary Structures</field>
|
||||
<field name="res_model">hr.payroll.structure</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_id" ref="view_hr_payroll_structure_list_view"/>
|
||||
</record>
|
||||
<menuitem
|
||||
id="menu_hr_payroll_structure_view"
|
||||
action="action_view_hr_payroll_structure_list_form"
|
||||
parent="menu_hr_payroll_configuration"
|
||||
sequence="2"
|
||||
/>
|
||||
<record id="action_view_hr_payroll_structure_tree" model="ir.actions.act_window">
|
||||
<field name="name">Salary Structures Hierarchy</field>
|
||||
<field name="res_model">hr.payroll.structure</field>
|
||||
<field name="view_type">tree</field>
|
||||
<field name="domain">[('parent_id','=',False)]</field>
|
||||
<field name="view_id" ref="view_hr_payroll_structure_tree"/>
|
||||
</record>
|
||||
<menuitem
|
||||
id="menu_hr_payroll_structure_tree"
|
||||
action="action_view_hr_payroll_structure_tree"
|
||||
parent="menu_hr_payroll_configuration"
|
||||
sequence="2"
|
||||
/>
|
||||
<!-- End Salary structure -->
|
||||
|
||||
<!-- Payslip Line -->
|
||||
<record id="view_hr_payslip_line_tree" model="ir.ui.view">
|
||||
<field name="name">hr.payslip.line.tree</field>
|
||||
<field name="model">hr.payslip.line</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Salary Structure" editable="bottom" decoration-info="total == 0">
|
||||
<field name="category_id"/>
|
||||
<field name="employee_id" invisible="1"/>
|
||||
<field name="sequence"/>
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
<field name="quantity"/>
|
||||
<field name="rate"/>
|
||||
<field name="amount"/>
|
||||
<field name="total"/>
|
||||
<field name="amount_select" invisible="1"/>
|
||||
<field name="register_id" invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<record id="view_hr_payslip_line_form" model="ir.ui.view">
|
||||
<field name="name">hr.payslip.line.form</field>
|
||||
<field name="model">hr.payslip.line</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Payslip Line">
|
||||
<group>
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
<field name="slip_id"/>
|
||||
<field name="employee_id"/>
|
||||
</group>
|
||||
<group string="Calculations">
|
||||
<field name="category_id"/>
|
||||
<field name="amount_select"/>
|
||||
<field name="amount_fix" attrs="{'readonly':[('amount_select','!=','fix')]}"/>
|
||||
<field name="amount_percentage" attrs="{'readonly':[('amount_select','!=','percentage')]}"/>
|
||||
<field name="sequence"/>
|
||||
</group>
|
||||
<field name="note"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_hr_payslip_line_filter" model="ir.ui.view">
|
||||
<field name="name">hr.payslip.line.select</field>
|
||||
<field name="model">hr.payslip.line</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search Payslip Lines">
|
||||
<field name="name" string="Payslip Lines" filter_domain="['|',('name','ilike',self),('code','ilike',self)]"/>
|
||||
<field name="amount_select"/>
|
||||
<field name="slip_id"/>
|
||||
<group col="8" colspan="4" expand="0" string="Group By">
|
||||
<filter string="Salary Rule Category" name="category_id" context="{'group_by':'category_id'}"/>
|
||||
<filter string="Contribution Register" name="register_id" context="{'group_by':'register_id'}"/>
|
||||
<filter string="Amount Type" name="amount_select" context="{'group_by':'amount_select'}"/>
|
||||
<filter string="Employees" name="employee_id" context="{'group_by':'employee_id'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<!-- payslip -->
|
||||
|
||||
<record id="view_hr_payslip_tree" model="ir.ui.view">
|
||||
<field name="name">hr.payslip.tree</field>
|
||||
<field name="model">hr.payslip</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree decoration-info="state in ('confirm','hr_check','accont_check')" decoration-muted="state == 'cancel'" string="Payslips">
|
||||
<field name="number"/>
|
||||
<field name="employee_id"/>
|
||||
<field name="name"/>
|
||||
<field name="date_from"/>
|
||||
<field name="date_to"/>
|
||||
<field name="state"/>
|
||||
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
|
||||
<field name="payslip_run_id" invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<act_window name="Payslip Computation Details"
|
||||
context="{'default_slip_id': active_id,'search_default_slip_id': active_id}"
|
||||
res_model="hr.payslip.line"
|
||||
src_model="hr.payslip"
|
||||
id="act_payslip_lines"/>
|
||||
|
||||
<record id="view_hr_payslip_form" model="ir.ui.view">
|
||||
<field name="name">hr.payslip.form</field>
|
||||
<field name="model">hr.payslip</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Payslip">
|
||||
<header>
|
||||
<button string="Confirm" name="action_payslip_done" type="object" states="draft" class="oe_highlight"/>
|
||||
<button string="Refund" name="refund_sheet" states="confirm,done" type='object' />
|
||||
<button string="Set to Draft" name="action_payslip_draft" type="object" states="cancel"/>
|
||||
<button string="Compute Sheet" name="compute_sheet" type="object" states="draft" class="oe_highlight"/>
|
||||
<button string="Cancel Payslip" name="action_payslip_cancel" type="object" states="draft,hr_check,confirm,verify"/>
|
||||
<field name="state" widget="statusbar" statusbar_visible="draft,confirm"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button name="%(act_payslip_lines)d"
|
||||
class="oe_stat_button"
|
||||
icon="fa-money"
|
||||
type="action">
|
||||
<field name="payslip_count" widget="statinfo" string="Payslip" help="Payslip Computation Details"/>
|
||||
</button>
|
||||
</div>
|
||||
<div class="oe_title">
|
||||
<label for="employee_id" class="oe_edit_only"/>
|
||||
<h1><field name="employee_id" placeholder="Employee"/></h1>
|
||||
</div>
|
||||
<group col="4">
|
||||
<label for="date_from" string="Period"/>
|
||||
<div>
|
||||
<field name="date_from" class="oe_inline"/> - <field name="date_to" class="oe_inline"/>
|
||||
</div>
|
||||
<field name="contract_id" domain="[('employee_id','=',employee_id),('date_start','<=',date_to),'|',('date_end','>=',date_from),('date_end','=',False)]" context="{'default_employee_id': employee_id}"/>
|
||||
<field name="number"/>
|
||||
<field name="struct_id" attrs="{'required':[('contract_id','!=',False)]}"/>
|
||||
<field name="name"/>
|
||||
<field name="credit_note"/>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Worked Days & Inputs">
|
||||
<separator string="Worked Days"/>
|
||||
<field name="worked_days_line_ids">
|
||||
<tree string="Worked Days" editable="bottom">
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
<field name="number_of_days" sum="Total Working Days"/>
|
||||
<field name="number_of_hours"/>
|
||||
<field name="contract_id"/>
|
||||
<field name="sequence" invisible="True"/>
|
||||
</tree>
|
||||
<form string="Worked Day">
|
||||
<group col="4">
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
<field name="sequence"/>
|
||||
<field name="number_of_days"/>
|
||||
<field name="number_of_hours"/>
|
||||
<field name="contract_id"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
<separator string="Other Inputs"/>
|
||||
<field name="input_line_ids" colspan="4" nolabel="1">
|
||||
<tree string="Input Data" editable="bottom">
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
<field name="amount"/>
|
||||
<field name="contract_id"/>
|
||||
<field name="sequence" invisible="True"/>
|
||||
</tree>
|
||||
<form string="Payslip Line">
|
||||
<group col="4">
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
<field name="sequence"/>
|
||||
<field name="amount"/>
|
||||
<field name="contract_id"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</page>
|
||||
<page string="Salary Computation" >
|
||||
<field name="line_ids" colspan="4" nolabel="1">
|
||||
<tree string="Salary Structure" editable="bottom" decoration-info="total == 0">
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
<field name="category_id"/>
|
||||
<field name="sequence" invisible="1"/>
|
||||
<field name="quantity"/>
|
||||
<field name="rate"/>
|
||||
<field name="salary_rule_id"/>
|
||||
<field name="amount"/>
|
||||
<field name="total"/>
|
||||
</tree>
|
||||
<form string="Payslip Line">
|
||||
<group col="4">
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
<field name="category_id"/>
|
||||
<field name="sequence"/>
|
||||
<field name="quantity"/>
|
||||
<field name="rate"/>
|
||||
<field name="amount"/>
|
||||
<field name="total"/>
|
||||
<field name="salary_rule_id"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</page>
|
||||
<page string="Details By Salary Rule Category">
|
||||
<field name="details_by_salary_rule_category" context="{'group_by':'category_id'}" domain="[('appears_on_payslip', '=', True)]">
|
||||
<tree string="Payslip Lines" decoration-info="total == 0">
|
||||
<field name="category_id"/>
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
<field name="total"/>
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
<page string="Accounting Information">
|
||||
<group>
|
||||
<group string="Miscellaneous">
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
<field name="payslip_run_id" domain="[('state','=','draft')]"/>
|
||||
</group>
|
||||
<group name="accounting" string="Accounting">
|
||||
<field name="paid" readonly="1"/>
|
||||
</group>
|
||||
</group>
|
||||
<div colspan="4">
|
||||
<field name="note" placeholder="Add an internal note..."/>
|
||||
</div>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_hr_payslip_filter" model="ir.ui.view">
|
||||
<field name="name">hr.payslip.select</field>
|
||||
<field name="model">hr.payslip</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search Payslips">
|
||||
<field name="name" string="Payslips" filter_domain="['|',('name','ilike',self),('number','ilike',self)]"/>
|
||||
<field name="date_from"/>
|
||||
<filter string="Draft" domain="[('state','=','draft')]" help="Draft Slip"/>
|
||||
<filter string="Done" domain="[('state','=','done')]" help="Done Slip"/>
|
||||
<field name="employee_id"/>
|
||||
<field name="payslip_run_id"/>
|
||||
<group expand="0" string="Group By">
|
||||
<filter string="Employees" name="employee_id" context="{'group_by':'employee_id'}"/>
|
||||
<filter string="PaySlip Batch" name="payslip_run_id" context="{'group_by':'payslip_run_id'}"/>
|
||||
<filter string="Companies" name="company_id" groups="base.group_multi_company" context="{'group_by':'company_id'}"/>
|
||||
<filter string="States" name="state" context="{'group_by':'state'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
<record id="action_view_hr_payslip_form" model="ir.actions.act_window">
|
||||
<field name="name">Employee Payslips</field>
|
||||
<field name="res_model">hr.payslip</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_id" ref="view_hr_payslip_tree"/>
|
||||
<field name="search_view_id" ref="view_hr_payslip_filter"/>
|
||||
</record>
|
||||
|
||||
<menuitem action="action_view_hr_payslip_form" id="menu_department_tree" parent="menu_hr_payroll_root" groups="hr_payroll.group_hr_payroll_user"/>
|
||||
|
||||
<record id="act_hr_employee_payslip_list" model="ir.actions.act_window">
|
||||
<field name="res_model">hr.payslip</field>
|
||||
<field name="name">Payslips</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="context">{'search_default_employee_id': [active_id], 'default_employee_id': active_id}</field>
|
||||
</record>
|
||||
|
||||
<!-- Employee View -->
|
||||
<record id="payroll_hr_employee_view_form" model="ir.ui.view">
|
||||
<field name="name">payroll.hr.employee.view.form</field>
|
||||
<field name="model">hr.employee</field>
|
||||
<field name="inherit_id" ref="hr.view_employee_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<button name="toggle_active" position="before">
|
||||
<button name="%(act_hr_employee_payslip_list)d"
|
||||
class="oe_stat_button"
|
||||
icon="fa-money"
|
||||
type="action"
|
||||
groups="hr_payroll.group_hr_payroll_user">
|
||||
<field name="payslip_count" widget="statinfo" string="Payslips"/>
|
||||
</button>
|
||||
</button>
|
||||
</field>
|
||||
</record>
|
||||
<!-- Salary Category-->
|
||||
<record id="hr_salary_rule_category_form" model="ir.ui.view">
|
||||
<field name="name">hr.salary.rule.category.form</field>
|
||||
<field name="model">hr.salary.rule.category</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Salary Categories">
|
||||
<group col="4">
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
<field name="parent_id"/>
|
||||
</group>
|
||||
<group string="Notes">
|
||||
<field name="note" nolabel="1"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_salary_rule_category_tree" model="ir.ui.view">
|
||||
<field name="name">hr.salary.rule.category.tree</field>
|
||||
<field name="model">hr.salary.rule.category</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Salary Rule Categories">
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
<field name="parent_id" invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<record id="hr_salary_rule_category_tree_view" model="ir.ui.view">
|
||||
<field name="name">hr.salary.rule.category.tree.view</field>
|
||||
<field name="model">hr.salary.rule.category</field>
|
||||
<field name="field_parent">children_ids</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Salary Rule Categories">
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
<field name="parent_id" invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_hr_salary_rule_category_filter" model="ir.ui.view">
|
||||
<field name="name">hr.salary.rule.category.select</field>
|
||||
<field name="model">hr.salary.rule.category</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Salary Rule Categories">
|
||||
<field name="name" string="Salary Rule Categories" filter_domain="['|',('name','ilike',self),('code','ilike',self)]"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_hr_salary_rule_category" model="ir.actions.act_window">
|
||||
<field name="name">Salary Rule Categories</field>
|
||||
<field name="res_model">hr.salary.rule.category</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_id" ref="hr_salary_rule_category_tree"/>
|
||||
<field name="search_view_id" ref="view_hr_salary_rule_category_filter"/>
|
||||
</record>
|
||||
<menuitem
|
||||
id="menu_hr_salary_rule_category"
|
||||
action="action_hr_salary_rule_category"
|
||||
parent="menu_hr_payroll_configuration"
|
||||
sequence="11"
|
||||
groups="base.group_no_one"
|
||||
/>
|
||||
<record id="action_hr_salary_rule_category_tree_view" model="ir.actions.act_window">
|
||||
<field name="name">Salary Rule Categories Hierarchy</field>
|
||||
<field name="res_model">hr.salary.rule.category</field>
|
||||
<field name="view_type">tree</field>
|
||||
<field name="domain">[('parent_id','=',False)]</field>
|
||||
<field name="view_id" ref="hr_salary_rule_category_tree_view"/>
|
||||
</record>
|
||||
<menuitem
|
||||
id="menu_hr_salary_rule_category_tree_view"
|
||||
action="action_hr_salary_rule_category_tree_view"
|
||||
parent="menu_hr_payroll_configuration"
|
||||
sequence="12"
|
||||
groups="base.group_no_one"
|
||||
/>
|
||||
|
||||
<!--
|
||||
Contribution Register
|
||||
-->
|
||||
<record id="hr_contribution_register_tree" model="ir.ui.view">
|
||||
<field name="name">hr.contribution.register.tree</field>
|
||||
<field name="model">hr.contribution.register</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Contribution Registers">
|
||||
<field name="name" />
|
||||
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<record id="hr_contribution_register_filter" model="ir.ui.view">
|
||||
<field name="name">hr.contribution.register.search</field>
|
||||
<field name="model">hr.contribution.register</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Contribution Registers">
|
||||
<field name="name" string="Contribution Registers"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
<record id="hr_contribution_register_form" model="ir.ui.view">
|
||||
<field name="name">hr.contribution.register.form</field>
|
||||
<field name="model">hr.contribution.register</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Contribution">
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}" class="oe_inline"/>
|
||||
</group>
|
||||
<newline/>
|
||||
<group>
|
||||
<separator string="Description"/><newline/>
|
||||
<field name="note" nolabel="1"/></group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<record id="action_contribution_register_form" model="ir.actions.act_window">
|
||||
<field name="name">Contribution Registers</field>
|
||||
<field name="res_model">hr.contribution.register</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to add a new contribution register.
|
||||
</p><p>
|
||||
A contribution register is a third party involved in the salary
|
||||
payment of the employees. It can be the social security, the
|
||||
estate or anyone that collect or inject money on payslips.
|
||||
</p>
|
||||
</field>
|
||||
<field name="view_id" ref="hr_contribution_register_tree"/>
|
||||
</record>
|
||||
<menuitem
|
||||
id="menu_action_hr_contribution_register_form"
|
||||
action="action_contribution_register_form"
|
||||
parent="menu_hr_payroll_configuration"
|
||||
sequence="14"
|
||||
/>
|
||||
|
||||
<!-- Salary Rules -->
|
||||
|
||||
<record id="hr_salary_rule_list" model="ir.ui.view">
|
||||
<field name="name">hr.salary.rule.list</field>
|
||||
<field name="model">hr.salary.rule</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Salary Rules">
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
<field name="category_id"/>
|
||||
<field name="sequence" invisible="1"/>
|
||||
<field name="register_id"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_salary_rule_tree" model="ir.ui.view">
|
||||
<field name="name">hr.salary.rule.tree</field>
|
||||
<field name="model">hr.salary.rule</field>
|
||||
<field name="field_parent">child_ids</field>
|
||||
<field eval="20" name="priority"/>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Salary Rules">
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
<field name="category_id" invisible="True"/>
|
||||
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<record id="hr_salary_rule_form" model="ir.ui.view">
|
||||
<field name="name">hr.salary.rule.form</field>
|
||||
<field name="model">hr.salary.rule</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Salary Rules">
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
<h1><field name="name"/></h1>
|
||||
<label for="category_id" class="oe_edit_only"/>
|
||||
<h2><field name="category_id"/></h2>
|
||||
|
||||
<group col="4">
|
||||
<field name="code"/>
|
||||
<field name="sequence" />
|
||||
<field name="active"/>
|
||||
<field name="appears_on_payslip"/>
|
||||
<field name="company_id" options="{'no_create': True}" groups="base.group_multi_company"/>
|
||||
</group>
|
||||
<notebook colspan="6">
|
||||
<page string="General">
|
||||
<group col="4">
|
||||
<separator colspan="4" string="Conditions"/>
|
||||
<field name="condition_select"/><newline/>
|
||||
<field name="condition_python" attrs="{'invisible':[('condition_select','!=','python')], 'required': [('condition_select','=','python')]}" colspan="4"/> <newline/>
|
||||
<field name="condition_range" attrs="{'invisible':[('condition_select','!=','range')], 'required':[('condition_select','=','range')]}"/><newline/>
|
||||
<field name="condition_range_min" colspan="2" attrs="{'invisible':[('condition_select','!=','range')], 'required':[('condition_select','=','range')]}"/><newline/>
|
||||
<field name="condition_range_max" colspan="2" attrs="{'invisible':[('condition_select','!=','range')], 'required':[('condition_select','=','range')]}"/><newline/>
|
||||
<separator colspan="4" string="Computation"/>
|
||||
<field name="amount_select"/><newline/>
|
||||
<field name="amount_percentage_base" attrs="{'invisible':[('amount_select','!=','percentage')], 'required': [('amount_select','=','percentage')]}"/><newline/>
|
||||
<field name="quantity" attrs="{'invisible':[('amount_select','=','code')], 'required':[('amount_select','!=','code')]}"/><newline/>
|
||||
<field name="amount_fix" attrs="{'invisible':[('amount_select','!=','fix')], 'required':[('amount_select','=','fix')]}"/><newline/>
|
||||
<field colspan="4" name="amount_python_compute" attrs="{'invisible':[('amount_select','!=','code')], 'required':[('amount_select','=','code')]}"/>
|
||||
<field name="amount_percentage" attrs="{'invisible':[('amount_select','!=','percentage')], 'required':[('amount_select','=','percentage')]}"/>
|
||||
<separator colspan="4" string="Company Contribution"/>
|
||||
<field name="register_id"/>
|
||||
</group>
|
||||
</page>
|
||||
<page name="rules" string="Child Rules">
|
||||
<field name="parent_rule_id"/>
|
||||
<separator string="Children Definition"/>
|
||||
<field name="child_ids"/>
|
||||
</page>
|
||||
<page string="Inputs">
|
||||
<field name="input_ids" mode="tree">
|
||||
<tree string="Input Data" editable="bottom">
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
<page string="Description">
|
||||
<field name="note"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_hr_rule_filter" model="ir.ui.view">
|
||||
<field name="name">hr.salary.rule.select</field>
|
||||
<field name="model">hr.salary.rule</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search Salary Rule">
|
||||
<field name="name" string="Salary Rules" filter_domain="['|',('name','ilike',self),('code','ilike',self)]"/>
|
||||
<field name="category_id"/>
|
||||
<field name="condition_range_min"/>
|
||||
<group col="8" colspan="4" expand="0" string="Group By">
|
||||
<filter string="Category" name="head" context="{'group_by':'category_id'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_salary_rule_form" model="ir.actions.act_window">
|
||||
<field name="name">Salary Rules</field>
|
||||
<field name="res_model">hr.salary.rule</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_id" ref="hr_salary_rule_list"/>
|
||||
<field name="domain">[('parent_rule_id','=',False)]</field>
|
||||
<field name="search_view_id" ref="view_hr_rule_filter"/>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_action_hr_salary_rule_form" action="action_salary_rule_form" parent="menu_hr_payroll_configuration" sequence="12"/>
|
||||
|
||||
|
||||
<act_window name="All Children Rules"
|
||||
domain="[('parent_rule_id', '=', active_id)]"
|
||||
res_model="hr.salary.rule"
|
||||
src_model="hr.salary.rule"
|
||||
view_id="hr_salary_rule_list"
|
||||
id="act_children_salary_rules"/>
|
||||
|
||||
<!-- payslip runs -->
|
||||
|
||||
<record id="hr_payslip_run_filter" model="ir.ui.view">
|
||||
<field name="name">hr.payslip.run.search</field>
|
||||
<field name="model">hr.payslip.run</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search Payslip Batches">
|
||||
<field name="name" string="Payslip Batches"/>
|
||||
<field name="date_start"/>
|
||||
<field name="date_end"/>
|
||||
<filter string="Draft" domain="[('state','=','draft')]" help="Draft Payslip Batches"/>
|
||||
<filter name="done_filter" string="Done" domain="[('state','=','close')]" help="Done Payslip Batches"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_payslip_run_tree" model="ir.ui.view">
|
||||
<field name="name">hr.payslip.run.tree</field>
|
||||
<field name="model">hr.payslip.run</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Payslips Batches">
|
||||
<field name="name"/>
|
||||
<field name="date_start"/>
|
||||
<field name="date_end"/>
|
||||
<field name="credit_note"/>
|
||||
<field name="state"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_payslip_run_form" model="ir.ui.view">
|
||||
<field name="name">hr.payslip.run.form</field>
|
||||
<field name="model">hr.payslip.run</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Payslips Batches">
|
||||
<header>
|
||||
<button name="close_payslip_run" type="object" string="Close" states="draft" class="oe_highlight"/>
|
||||
<button name="%(action_hr_payslip_by_employees)d" type="action" states="draft" string="Generate Payslips" class="oe_highlight"/>
|
||||
<button string="Set to Draft" name="draft_payslip_run" type="object" states="close" />
|
||||
<field name="state" widget="statusbar"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
<h1>
|
||||
<field name="name"/>
|
||||
</h1>
|
||||
<group col="4">
|
||||
<label for="date_start" string="Period"/>
|
||||
<div>
|
||||
<field name="date_start" class="oe_inline"/> - <field name="date_end" class="oe_inline"/>
|
||||
</div>
|
||||
<field name="credit_note"/>
|
||||
</group>
|
||||
<separator string="Payslips"/>
|
||||
<field name="slip_ids"/>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_hr_payslip_run_tree" model="ir.actions.act_window">
|
||||
<field name="name">Payslips Batches</field>
|
||||
<field name="res_model">hr.payslip.run</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_id" ref="hr_payslip_run_tree"/>
|
||||
<field name="search_view_id" ref="hr_payslip_run_filter"/>
|
||||
</record>
|
||||
<menuitem action="action_hr_payslip_run_tree" id="menu_hr_payslip_run" parent="menu_hr_payroll_root"/>
|
||||
|
||||
<!-- Shortcuts -->
|
||||
|
||||
<act_window name="Payslip Lines"
|
||||
domain="[('register_id', '=', active_id)]"
|
||||
context="{'default_register_id': active_id, 'search_default_register_id': 1}"
|
||||
res_model="hr.payslip.line"
|
||||
src_model="hr.contribution.register"
|
||||
id="act_contribution_reg_payslip_lines"/>
|
||||
|
||||
</odoo>
|
||||
@@ -0,0 +1,346 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- Payslip Line -->
|
||||
<record id="view_hr_payslip_line_tree" model="ir.ui.view">
|
||||
<field name="name">hr.payslip.line.tree</field>
|
||||
<field name="model">hr.payslip.line</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Salary Structure" editable="bottom" decoration-info="total == 0">
|
||||
<field name="category_id"/>
|
||||
<field name="employee_id" invisible="1"/>
|
||||
<field name="sequence"/>
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
<field name="quantity"/>
|
||||
<field name="rate"/>
|
||||
<field name="amount"/>
|
||||
<field name="total"/>
|
||||
<field name="amount_select" invisible="1"/>
|
||||
<field name="register_id" invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<record id="view_hr_payslip_line_form" model="ir.ui.view">
|
||||
<field name="name">hr.payslip.line.form</field>
|
||||
<field name="model">hr.payslip.line</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Payslip Line">
|
||||
<group>
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
<field name="slip_id"/>
|
||||
<field name="employee_id"/>
|
||||
</group>
|
||||
<group string="Calculations">
|
||||
<field name="category_id"/>
|
||||
<field name="amount_select"/>
|
||||
<field name="amount_fix" attrs="{'readonly':[('amount_select','!=','fix')]}"/>
|
||||
<field name="amount_percentage" attrs="{'readonly':[('amount_select','!=','percentage')]}"/>
|
||||
<field name="sequence"/>
|
||||
</group>
|
||||
<field name="note"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_hr_payslip_line_filter" model="ir.ui.view">
|
||||
<field name="name">hr.payslip.line.select</field>
|
||||
<field name="model">hr.payslip.line</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search Payslip Lines">
|
||||
<field name="name" string="Payslip Lines" filter_domain="['|',('name','ilike',self),('code','ilike',self)]"/>
|
||||
<field name="amount_select"/>
|
||||
<field name="slip_id"/>
|
||||
<group col="8" colspan="4" expand="0" string="Group By">
|
||||
<filter string="Salary Rule Category" name="category_id" context="{'group_by':'category_id'}"/>
|
||||
<filter string="Contribution Register" name="register_id" context="{'group_by':'register_id'}"/>
|
||||
<filter string="Amount Type" name="amount_select" context="{'group_by':'amount_select'}"/>
|
||||
<filter string="Employees" name="employee_id" context="{'group_by':'employee_id'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<!-- payslip -->
|
||||
|
||||
<record id="view_hr_payslip_tree" model="ir.ui.view">
|
||||
<field name="name">hr.payslip.tree</field>
|
||||
<field name="model">hr.payslip</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree decoration-info="state in ('confirm','hr_check','accont_check')" decoration-muted="state == 'cancel'" string="Payslips">
|
||||
<field name="number"/>
|
||||
<field name="employee_id"/>
|
||||
<field name="name"/>
|
||||
<field name="date_from"/>
|
||||
<field name="date_to"/>
|
||||
<field name="state"/>
|
||||
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
|
||||
<field name="payslip_run_id" invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<act_window name="Payslip Computation Details"
|
||||
context="{'default_slip_id': active_id,'search_default_slip_id': active_id}"
|
||||
res_model="hr.payslip.line"
|
||||
src_model="hr.payslip"
|
||||
id="act_payslip_lines"/>
|
||||
|
||||
<record id="view_hr_payslip_form" model="ir.ui.view">
|
||||
<field name="name">hr.payslip.form</field>
|
||||
<field name="model">hr.payslip</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Payslip">
|
||||
<header>
|
||||
<button string="Confirm" name="action_payslip_done" type="object" states="draft" class="oe_highlight"/>
|
||||
<button string="Refund" name="refund_sheet" states="confirm,done" type='object' />
|
||||
<button string="Set to Draft" name="action_payslip_draft" type="object" states="cancel"/>
|
||||
<button string="Compute Sheet" name="compute_sheet" type="object" states="draft" class="oe_highlight"/>
|
||||
<button string="Cancel Payslip" name="action_payslip_cancel" type="object" states="draft,hr_check,confirm,verify"/>
|
||||
<field name="state" widget="statusbar" statusbar_visible="draft,confirm"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button name="%(act_payslip_lines)d"
|
||||
class="oe_stat_button"
|
||||
icon="fa-money"
|
||||
type="action">
|
||||
<field name="payslip_count" widget="statinfo" string="Payslip" help="Payslip Computation Details"/>
|
||||
</button>
|
||||
</div>
|
||||
<div class="oe_title">
|
||||
<label for="employee_id" class="oe_edit_only"/>
|
||||
<h1><field name="employee_id" placeholder="Employee"/></h1>
|
||||
</div>
|
||||
<group col="4">
|
||||
<label for="date_from" string="Period"/>
|
||||
<div>
|
||||
<field name="date_from" class="oe_inline"/> - <field name="date_to" class="oe_inline"/>
|
||||
</div>
|
||||
<field name="contract_id" domain="[('employee_id','=',employee_id),('date_start','<=',date_to),'|',('date_end','>=',date_from),('date_end','=',False)]" context="{'default_employee_id': employee_id}"/>
|
||||
<field name="number"/>
|
||||
<field name="struct_id" attrs="{'required':[('contract_id','!=',False)]}"/>
|
||||
<field name="name"/>
|
||||
<field name="credit_note"/>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Worked Days & Inputs">
|
||||
<separator string="Worked Days"/>
|
||||
<field name="worked_days_line_ids">
|
||||
<tree string="Worked Days" editable="bottom">
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
<field name="number_of_days" sum="Total Working Days"/>
|
||||
<field name="number_of_hours"/>
|
||||
<field name="contract_id"/>
|
||||
<field name="sequence" invisible="True"/>
|
||||
</tree>
|
||||
<form string="Worked Day">
|
||||
<group col="4">
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
<field name="sequence"/>
|
||||
<field name="number_of_days"/>
|
||||
<field name="number_of_hours"/>
|
||||
<field name="contract_id"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
<separator string="Other Inputs"/>
|
||||
<field name="input_line_ids" colspan="4" nolabel="1">
|
||||
<tree string="Input Data" editable="bottom">
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
<field name="amount"/>
|
||||
<field name="contract_id"/>
|
||||
<field name="sequence" invisible="True"/>
|
||||
</tree>
|
||||
<form string="Payslip Line">
|
||||
<group col="4">
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
<field name="sequence"/>
|
||||
<field name="amount"/>
|
||||
<field name="contract_id"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</page>
|
||||
<page string="Salary Computation" >
|
||||
<field name="line_ids" colspan="4" nolabel="1">
|
||||
<tree string="Salary Structure" editable="bottom" decoration-info="total == 0">
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
<field name="category_id"/>
|
||||
<field name="sequence" invisible="1"/>
|
||||
<field name="quantity"/>
|
||||
<field name="rate"/>
|
||||
<field name="salary_rule_id"/>
|
||||
<field name="amount"/>
|
||||
<field name="total"/>
|
||||
</tree>
|
||||
<form string="Payslip Line">
|
||||
<group col="4">
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
<field name="category_id"/>
|
||||
<field name="sequence"/>
|
||||
<field name="quantity"/>
|
||||
<field name="rate"/>
|
||||
<field name="amount"/>
|
||||
<field name="total"/>
|
||||
<field name="salary_rule_id"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</page>
|
||||
<page string="Details By Salary Rule Category">
|
||||
<field name="details_by_salary_rule_category" context="{'group_by':'category_id'}" domain="[('appears_on_payslip', '=', True)]">
|
||||
<tree string="Payslip Lines" decoration-info="total == 0">
|
||||
<field name="category_id"/>
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
<field name="total"/>
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
<page string="Accounting Information">
|
||||
<group>
|
||||
<group string="Miscellaneous">
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
<field name="payslip_run_id" domain="[('state','=','draft')]"/>
|
||||
</group>
|
||||
<group name="accounting" string="Accounting">
|
||||
<field name="paid" readonly="1"/>
|
||||
</group>
|
||||
</group>
|
||||
<div colspan="4">
|
||||
<field name="note" placeholder="Add an internal note..."/>
|
||||
</div>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_hr_payslip_filter" model="ir.ui.view">
|
||||
<field name="name">hr.payslip.select</field>
|
||||
<field name="model">hr.payslip</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search Payslips">
|
||||
<field name="name" string="Payslips" filter_domain="['|',('name','ilike',self),('number','ilike',self)]"/>
|
||||
<field name="date_from"/>
|
||||
<filter string="Draft" domain="[('state','=','draft')]" help="Draft Slip"/>
|
||||
<filter string="Done" domain="[('state','=','done')]" help="Done Slip"/>
|
||||
<field name="employee_id"/>
|
||||
<field name="payslip_run_id"/>
|
||||
<group expand="0" string="Group By">
|
||||
<filter string="Employees" name="employee_id" context="{'group_by':'employee_id'}"/>
|
||||
<filter string="PaySlip Batch" name="payslip_run_id" context="{'group_by':'payslip_run_id'}"/>
|
||||
<filter string="Companies" name="company_id" groups="base.group_multi_company" context="{'group_by':'company_id'}"/>
|
||||
<filter string="States" name="state" context="{'group_by':'state'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
<record id="action_view_hr_payslip_form" model="ir.actions.act_window">
|
||||
<field name="name">Employee Payslips</field>
|
||||
<field name="res_model">hr.payslip</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_id" ref="view_hr_payslip_tree"/>
|
||||
<field name="search_view_id" ref="view_hr_payslip_filter"/>
|
||||
</record>
|
||||
|
||||
<menuitem action="action_view_hr_payslip_form" id="menu_department_tree" parent="menu_hr_payroll_root" groups="hr_payroll.group_hr_payroll_user"/>
|
||||
|
||||
<record id="act_hr_employee_payslip_list" model="ir.actions.act_window">
|
||||
<field name="res_model">hr.payslip</field>
|
||||
<field name="name">Payslips</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="context">{'search_default_employee_id': [active_id], 'default_employee_id': active_id}</field>
|
||||
</record>
|
||||
|
||||
<!-- payslip runs -->
|
||||
|
||||
<record id="hr_payslip_run_filter" model="ir.ui.view">
|
||||
<field name="name">hr.payslip.run.search</field>
|
||||
<field name="model">hr.payslip.run</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search Payslip Batches">
|
||||
<field name="name" string="Payslip Batches"/>
|
||||
<field name="date_start"/>
|
||||
<field name="date_end"/>
|
||||
<filter string="Draft" domain="[('state','=','draft')]" help="Draft Payslip Batches"/>
|
||||
<filter name="done_filter" string="Done" domain="[('state','=','close')]" help="Done Payslip Batches"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_payslip_run_tree" model="ir.ui.view">
|
||||
<field name="name">hr.payslip.run.tree</field>
|
||||
<field name="model">hr.payslip.run</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Payslips Batches">
|
||||
<field name="name"/>
|
||||
<field name="date_start"/>
|
||||
<field name="date_end"/>
|
||||
<field name="credit_note"/>
|
||||
<field name="state"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_payslip_run_form" model="ir.ui.view">
|
||||
<field name="name">hr.payslip.run.form</field>
|
||||
<field name="model">hr.payslip.run</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Payslips Batches">
|
||||
<header>
|
||||
<button name="close_payslip_run" type="object" string="Close" states="draft" class="oe_highlight"/>
|
||||
<button name="%(action_hr_payslip_by_employees)d" type="action" states="draft" string="Generate Payslips" class="oe_highlight"/>
|
||||
<button string="Set to Draft" name="draft_payslip_run" type="object" states="close" />
|
||||
<field name="state" widget="statusbar"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
<h1>
|
||||
<field name="name"/>
|
||||
</h1>
|
||||
<group col="4">
|
||||
<label for="date_start" string="Period"/>
|
||||
<div>
|
||||
<field name="date_start" class="oe_inline"/> - <field name="date_end" class="oe_inline"/>
|
||||
</div>
|
||||
<field name="credit_note"/>
|
||||
</group>
|
||||
<separator string="Payslips"/>
|
||||
<field name="slip_ids"/>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_hr_payslip_run_tree" model="ir.actions.act_window">
|
||||
<field name="name">Payslips Batches</field>
|
||||
<field name="res_model">hr.payslip.run</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_id" ref="hr_payslip_run_tree"/>
|
||||
<field name="search_view_id" ref="hr_payslip_run_filter"/>
|
||||
</record>
|
||||
<menuitem action="action_hr_payslip_run_tree" id="menu_hr_payslip_run" parent="menu_hr_payroll_root"/>
|
||||
|
||||
<!-- Shortcuts -->
|
||||
|
||||
<act_window name="Payslip Lines"
|
||||
domain="[('register_id', '=', active_id)]"
|
||||
context="{'default_register_id': active_id, 'search_default_register_id': 1}"
|
||||
res_model="hr.payslip.line"
|
||||
src_model="hr.contribution.register"
|
||||
id="act_contribution_reg_payslip_lines"/>
|
||||
|
||||
</odoo>
|
||||
@@ -0,0 +1,356 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<!-- Salary structure -->
|
||||
<record id="view_hr_payroll_structure_list_view" model="ir.ui.view">
|
||||
<field name="name">hr.payroll.structure.tree</field>
|
||||
<field name="model">hr.payroll.structure</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Employee Function">
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
<field name="rule_ids"/>
|
||||
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<record id="view_hr_payroll_structure_tree" model="ir.ui.view">
|
||||
<field name="name">hr.payroll.structure.tree</field>
|
||||
<field name="model">hr.payroll.structure</field>
|
||||
<field name="field_parent">children_ids</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Salary Structure">
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_hr_payroll_structure_filter" model="ir.ui.view">
|
||||
<field name="name">hr.payroll.structure.select</field>
|
||||
<field name="model">hr.payroll.structure</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Payroll Structures">
|
||||
<field name="name" string="Payroll Structures" filter_domain="['|',('name','ilike',self),('code','ilike',self)]"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_hr_employee_grade_form" model="ir.ui.view">
|
||||
<field name="name">hr.payroll.structure.form</field>
|
||||
<field name="model">hr.payroll.structure</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Employee Function">
|
||||
<group col="4">
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
<field name="parent_id"/>
|
||||
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
|
||||
</group>
|
||||
<notebook colspan="4">
|
||||
<page string="Salary Rules">
|
||||
<field name="rule_ids" domain="[('parent_rule_id','=',False)]">
|
||||
<tree>
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
<field name="category_id"/>
|
||||
<field name="sequence" invisible="1"/>
|
||||
<field name="register_id"/>
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
</notebook>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_view_hr_payroll_structure_list_form" model="ir.actions.act_window">
|
||||
<field name="name">Salary Structures</field>
|
||||
<field name="res_model">hr.payroll.structure</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_id" ref="view_hr_payroll_structure_list_view"/>
|
||||
</record>
|
||||
<menuitem
|
||||
id="menu_hr_payroll_structure_view"
|
||||
action="action_view_hr_payroll_structure_list_form"
|
||||
parent="menu_hr_payroll_configuration"
|
||||
sequence="2"
|
||||
/>
|
||||
<record id="action_view_hr_payroll_structure_tree" model="ir.actions.act_window">
|
||||
<field name="name">Salary Structures Hierarchy</field>
|
||||
<field name="res_model">hr.payroll.structure</field>
|
||||
<field name="view_type">tree</field>
|
||||
<field name="domain">[('parent_id','=',False)]</field>
|
||||
<field name="view_id" ref="view_hr_payroll_structure_tree"/>
|
||||
</record>
|
||||
<menuitem
|
||||
id="menu_hr_payroll_structure_tree"
|
||||
action="action_view_hr_payroll_structure_tree"
|
||||
parent="menu_hr_payroll_configuration"
|
||||
sequence="2"
|
||||
/>
|
||||
<!-- Salary Category-->
|
||||
<record id="hr_salary_rule_category_form" model="ir.ui.view">
|
||||
<field name="name">hr.salary.rule.category.form</field>
|
||||
<field name="model">hr.salary.rule.category</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Salary Categories">
|
||||
<group col="4">
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
<field name="parent_id"/>
|
||||
</group>
|
||||
<group string="Notes">
|
||||
<field name="note" nolabel="1"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_salary_rule_category_tree" model="ir.ui.view">
|
||||
<field name="name">hr.salary.rule.category.tree</field>
|
||||
<field name="model">hr.salary.rule.category</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Salary Rule Categories">
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
<field name="parent_id" invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<record id="hr_salary_rule_category_tree_view" model="ir.ui.view">
|
||||
<field name="name">hr.salary.rule.category.tree.view</field>
|
||||
<field name="model">hr.salary.rule.category</field>
|
||||
<field name="field_parent">children_ids</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Salary Rule Categories">
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
<field name="parent_id" invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_hr_salary_rule_category_filter" model="ir.ui.view">
|
||||
<field name="name">hr.salary.rule.category.select</field>
|
||||
<field name="model">hr.salary.rule.category</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Salary Rule Categories">
|
||||
<field name="name" string="Salary Rule Categories" filter_domain="['|',('name','ilike',self),('code','ilike',self)]"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_hr_salary_rule_category" model="ir.actions.act_window">
|
||||
<field name="name">Salary Rule Categories</field>
|
||||
<field name="res_model">hr.salary.rule.category</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_id" ref="hr_salary_rule_category_tree"/>
|
||||
<field name="search_view_id" ref="view_hr_salary_rule_category_filter"/>
|
||||
</record>
|
||||
<menuitem
|
||||
id="menu_hr_salary_rule_category"
|
||||
action="action_hr_salary_rule_category"
|
||||
parent="menu_hr_payroll_configuration"
|
||||
sequence="11"
|
||||
groups="base.group_no_one"
|
||||
/>
|
||||
<record id="action_hr_salary_rule_category_tree_view" model="ir.actions.act_window">
|
||||
<field name="name">Salary Rule Categories Hierarchy</field>
|
||||
<field name="res_model">hr.salary.rule.category</field>
|
||||
<field name="view_type">tree</field>
|
||||
<field name="domain">[('parent_id','=',False)]</field>
|
||||
<field name="view_id" ref="hr_salary_rule_category_tree_view"/>
|
||||
</record>
|
||||
<menuitem
|
||||
id="menu_hr_salary_rule_category_tree_view"
|
||||
action="action_hr_salary_rule_category_tree_view"
|
||||
parent="menu_hr_payroll_configuration"
|
||||
sequence="12"
|
||||
groups="base.group_no_one"
|
||||
/>
|
||||
|
||||
<!--
|
||||
Contribution Register
|
||||
-->
|
||||
<record id="hr_contribution_register_tree" model="ir.ui.view">
|
||||
<field name="name">hr.contribution.register.tree</field>
|
||||
<field name="model">hr.contribution.register</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Contribution Registers">
|
||||
<field name="name" />
|
||||
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<record id="hr_contribution_register_filter" model="ir.ui.view">
|
||||
<field name="name">hr.contribution.register.search</field>
|
||||
<field name="model">hr.contribution.register</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Contribution Registers">
|
||||
<field name="name" string="Contribution Registers"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
<record id="hr_contribution_register_form" model="ir.ui.view">
|
||||
<field name="name">hr.contribution.register.form</field>
|
||||
<field name="model">hr.contribution.register</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Contribution">
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}" class="oe_inline"/>
|
||||
</group>
|
||||
<newline/>
|
||||
<group>
|
||||
<separator string="Description"/><newline/>
|
||||
<field name="note" nolabel="1"/></group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<record id="action_contribution_register_form" model="ir.actions.act_window">
|
||||
<field name="name">Contribution Registers</field>
|
||||
<field name="res_model">hr.contribution.register</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to add a new contribution register.
|
||||
</p><p>
|
||||
A contribution register is a third party involved in the salary
|
||||
payment of the employees. It can be the social security, the
|
||||
estate or anyone that collect or inject money on payslips.
|
||||
</p>
|
||||
</field>
|
||||
<field name="view_id" ref="hr_contribution_register_tree"/>
|
||||
</record>
|
||||
<menuitem
|
||||
id="menu_action_hr_contribution_register_form"
|
||||
action="action_contribution_register_form"
|
||||
parent="menu_hr_payroll_configuration"
|
||||
sequence="14"
|
||||
/>
|
||||
|
||||
<!-- Salary Rules -->
|
||||
|
||||
<record id="hr_salary_rule_list" model="ir.ui.view">
|
||||
<field name="name">hr.salary.rule.list</field>
|
||||
<field name="model">hr.salary.rule</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Salary Rules">
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
<field name="category_id"/>
|
||||
<field name="sequence" invisible="1"/>
|
||||
<field name="register_id"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_salary_rule_tree" model="ir.ui.view">
|
||||
<field name="name">hr.salary.rule.tree</field>
|
||||
<field name="model">hr.salary.rule</field>
|
||||
<field name="field_parent">child_ids</field>
|
||||
<field eval="20" name="priority"/>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Salary Rules">
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
<field name="category_id" invisible="True"/>
|
||||
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<record id="hr_salary_rule_form" model="ir.ui.view">
|
||||
<field name="name">hr.salary.rule.form</field>
|
||||
<field name="model">hr.salary.rule</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Salary Rules">
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
<h1><field name="name"/></h1>
|
||||
<label for="category_id" class="oe_edit_only"/>
|
||||
<h2><field name="category_id"/></h2>
|
||||
|
||||
<group col="4">
|
||||
<field name="code"/>
|
||||
<field name="sequence" />
|
||||
<field name="active"/>
|
||||
<field name="appears_on_payslip"/>
|
||||
<field name="company_id" options="{'no_create': True}" groups="base.group_multi_company"/>
|
||||
</group>
|
||||
<notebook colspan="6">
|
||||
<page string="General">
|
||||
<group col="4">
|
||||
<separator colspan="4" string="Conditions"/>
|
||||
<field name="condition_select"/><newline/>
|
||||
<field name="condition_python" attrs="{'invisible':[('condition_select','!=','python')], 'required': [('condition_select','=','python')]}" colspan="4"/> <newline/>
|
||||
<field name="condition_range" attrs="{'invisible':[('condition_select','!=','range')], 'required':[('condition_select','=','range')]}"/><newline/>
|
||||
<field name="condition_range_min" colspan="2" attrs="{'invisible':[('condition_select','!=','range')], 'required':[('condition_select','=','range')]}"/><newline/>
|
||||
<field name="condition_range_max" colspan="2" attrs="{'invisible':[('condition_select','!=','range')], 'required':[('condition_select','=','range')]}"/><newline/>
|
||||
<separator colspan="4" string="Computation"/>
|
||||
<field name="amount_select"/><newline/>
|
||||
<field name="amount_percentage_base" attrs="{'invisible':[('amount_select','!=','percentage')], 'required': [('amount_select','=','percentage')]}"/><newline/>
|
||||
<field name="quantity" attrs="{'invisible':[('amount_select','=','code')], 'required':[('amount_select','!=','code')]}"/><newline/>
|
||||
<field name="amount_fix" attrs="{'invisible':[('amount_select','!=','fix')], 'required':[('amount_select','=','fix')]}"/><newline/>
|
||||
<field colspan="4" name="amount_python_compute" attrs="{'invisible':[('amount_select','!=','code')], 'required':[('amount_select','=','code')]}"/>
|
||||
<field name="amount_percentage" attrs="{'invisible':[('amount_select','!=','percentage')], 'required':[('amount_select','=','percentage')]}"/>
|
||||
<separator colspan="4" string="Company Contribution"/>
|
||||
<field name="register_id"/>
|
||||
</group>
|
||||
</page>
|
||||
<page name="rules" string="Child Rules">
|
||||
<field name="parent_rule_id"/>
|
||||
<separator string="Children Definition"/>
|
||||
<field name="child_ids"/>
|
||||
</page>
|
||||
<page string="Inputs">
|
||||
<field name="input_ids" mode="tree">
|
||||
<tree string="Input Data" editable="bottom">
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
<page string="Description">
|
||||
<field name="note"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_hr_rule_filter" model="ir.ui.view">
|
||||
<field name="name">hr.salary.rule.select</field>
|
||||
<field name="model">hr.salary.rule</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search Salary Rule">
|
||||
<field name="name" string="Salary Rules" filter_domain="['|',('name','ilike',self),('code','ilike',self)]"/>
|
||||
<field name="category_id"/>
|
||||
<field name="condition_range_min"/>
|
||||
<group col="8" colspan="4" expand="0" string="Group By">
|
||||
<filter string="Category" name="head" context="{'group_by':'category_id'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_salary_rule_form" model="ir.actions.act_window">
|
||||
<field name="name">Salary Rules</field>
|
||||
<field name="res_model">hr.salary.rule</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_id" ref="hr_salary_rule_list"/>
|
||||
<field name="domain">[('parent_rule_id','=',False)]</field>
|
||||
<field name="search_view_id" ref="view_hr_rule_filter"/>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_action_hr_salary_rule_form" action="action_salary_rule_form" parent="menu_hr_payroll_configuration" sequence="12"/>
|
||||
|
||||
<act_window name="All Children Rules"
|
||||
domain="[('parent_rule_id', '=', active_id)]"
|
||||
res_model="hr.salary.rule"
|
||||
src_model="hr.salary.rule"
|
||||
view_id="hr_salary_rule_list"
|
||||
id="act_children_salary_rules"/>
|
||||
|
||||
</odoo>
|
||||
@@ -20,6 +20,7 @@ class Job(models.Model):
|
||||
'hr.employee', related='department_id.manager_id', string="Department Manager",
|
||||
readonly=True, store=True)
|
||||
user_id = fields.Many2one('res.users', "Recruitment Responsible", track_visibility='onchange')
|
||||
hr_responsible_id = fields.Many2one('res.users', "HR Responsible", track_visibility='onchange')
|
||||
document_ids = fields.One2many('ir.attachment', compute='_compute_document_ids', string="Applications")
|
||||
documents_count = fields.Integer(compute='_compute_document_ids', string="Documents")
|
||||
alias_id = fields.Many2one(
|
||||
@@ -47,7 +48,7 @@ class Job(models.Model):
|
||||
|
||||
@api.multi
|
||||
def _compute_application_count(self):
|
||||
read_group_result = self.env['hr.applicant'].read_group([('job_id', '=', self.id)], ['job_id'], ['job_id'])
|
||||
read_group_result = self.env['hr.applicant'].read_group([('job_id', 'in', self.ids)], ['job_id'], ['job_id'])
|
||||
result = dict((data['job_id'][0], data['job_id_count']) for data in read_group_result)
|
||||
for job in self:
|
||||
job.application_count = result.get(job.id, 0)
|
||||
|
||||
@@ -327,7 +327,7 @@ class Applicant(models.Model):
|
||||
@api.multi
|
||||
def _track_subtype(self, init_values):
|
||||
record = self[0]
|
||||
if 'emp_id' in init_values and record.emp_id:
|
||||
if 'emp_id' in init_values and record.emp_id and record.emp_id.active:
|
||||
return 'hr_recruitment.mt_applicant_hired'
|
||||
elif 'stage_id' in init_values and record.stage_id and record.stage_id.sequence <= 1:
|
||||
return 'hr_recruitment.mt_applicant_new'
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<h2>Recruitment Process</h2>
|
||||
<div class="row mt16 o_settings_container">
|
||||
<div class="row mt16 o_settings_container" name="recruitment_process_div">
|
||||
<div class="col-xs-12 col-md-6 o_setting_box" title="Use interview forms tailored to each job position during the recruitment process. Select the form to use in the job position detail form. This relies on Survey app.">
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="module_hr_recruitment_survey"/>
|
||||
|
||||
@@ -448,6 +448,7 @@
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='department_id']" position="after">
|
||||
<field name="user_id"/>
|
||||
<field name="hr_responsible_id"/>
|
||||
</xpath>
|
||||
<div name="button_box" position="inside">
|
||||
<button class="oe_stat_button"
|
||||
|
||||
@@ -20,9 +20,8 @@ Belgian Payroll Rules.
|
||||
* Salary Maj, ONSS, Withholding Tax, Child Allowance, ...
|
||||
""",
|
||||
|
||||
'demo': ['data/l10n_be_hr_payroll_demo.xml'],
|
||||
'website': 'https://www.odoo.com/page/accounting',
|
||||
'data':[
|
||||
'data': [
|
||||
'views/l10n_be_hr_payroll_view.xml',
|
||||
'data/l10n_be_hr_payroll_data.xml',
|
||||
'data/hr.salary.rule.csv',
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -44,14 +44,19 @@
|
||||
<field name="code">ONSS</field>
|
||||
<field name="parent_id" ref="hr_payroll.ALW"/>
|
||||
</record>
|
||||
<record id="hr_payroll_head_employment_bonus" model="hr.salary.rule.category">
|
||||
<field name="name">Employment Bonus</field>
|
||||
<field name="code">EmpBonus</field>
|
||||
<field name="parent_id" ref="hr_payroll.ALW"/>
|
||||
</record>
|
||||
<record id="hr_payroll_head_div_impos" model="hr.salary.rule.category">
|
||||
<field name="name">Misc. Taxable</field>
|
||||
<field name="code">DIV. IMPOS</field>
|
||||
<field name="parent_id" ref="hr_payroll.ALW"/>
|
||||
</record>
|
||||
<record id="hr_payroll_head_pp" model="hr.salary.rule.category">
|
||||
<field name="name">Withholding Tax Grid 2011 (Precompte Professionnel Bareme 2011)</field>
|
||||
<field name="code">P.P.</field>
|
||||
<field name="name">Withholding Tax Grid 2017 (Precompte Professionnel Bareme 2017)</field>
|
||||
<field name="code">PP</field>
|
||||
<field name="parent_id" ref="hr_payroll.DED"/>
|
||||
</record>
|
||||
<record id="hr_payroll_head_div_net" model="hr.salary.rule.category">
|
||||
@@ -61,10 +66,24 @@
|
||||
</record>
|
||||
<record id="hr_payroll_head_child_alw" model="hr.salary.rule.category">
|
||||
<field name="name">Child Allowance Belgium</field>
|
||||
<field name="code">Ch.A</field>
|
||||
<field name="code">ChA</field>
|
||||
<field name="parent_id" ref="hr_payroll.DED"/>
|
||||
</record>
|
||||
<record id="hr_salary_rule_category_family_reductions" model="hr.salary.rule.category">
|
||||
<field name="name">Reduction for special family charges</field>
|
||||
<field name="code">FamRed</field>
|
||||
<field name="parent_id" ref="hr_payroll.DED"/>
|
||||
</record>
|
||||
<record id="hr_salary_rule_pp_reductions" model="hr.salary.rule.category">
|
||||
<field name="name">Withholding Tax Reduction</field>
|
||||
<field name="code">PPRed</field>
|
||||
<field name="parent_id" ref="hr_payroll.DED"/>
|
||||
</record>
|
||||
<record id="hr_salary_rule_category_spec_soc_contribution" model="hr.salary.rule.category">
|
||||
<field name="name">Special social contribution</field>
|
||||
<field name="code">SSC</field>
|
||||
<field name="parent_id" ref="hr_payroll.DED"/>
|
||||
</record>
|
||||
|
||||
<!-- HR SALARY RULES-->
|
||||
<record id="hr_payroll_rules_maj" model="hr.salary.rule">
|
||||
<field name="category_id" ref="hr_payroll_head_salary"/>
|
||||
@@ -76,11 +95,12 @@
|
||||
</record>
|
||||
<record id="hr_payroll_rules_employee" model="hr.salary.rule">
|
||||
<field name="category_id" ref="hr_payroll_head_salary"/>
|
||||
<field name="name">Employee Salary</field>
|
||||
<field name="name">Gross Salary</field>
|
||||
<field name="code">SALARY</field>
|
||||
<field name="condition_select">none</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute">result = contract.wage * 1</field>
|
||||
<field name="amount_python_compute">result = contract.wage * 1 + contract.car_atn + contract.mobile + contract.mobile_plus + contract.internet</field>
|
||||
<field name="sequence">20</field>
|
||||
<field name="appears_on_payslip" eval="False"/>
|
||||
</record>
|
||||
<record id="hr_payroll_rules_onss_rule" model="hr.salary.rule">
|
||||
@@ -93,19 +113,87 @@
|
||||
<field name="amount_percentage_base">SALARY</field>
|
||||
<field name="amount_percentage">-13.07</field>
|
||||
<field name="register_id" ref="contrib_register_onss"/>
|
||||
</record>
|
||||
<record id="hr_payroll_rules_p_p_b1" model="hr.salary.rule">
|
||||
<field name="category_id" ref="hr_payroll_head_pp"/>
|
||||
<field name="name">Withholding Tax Grid 2011</field>
|
||||
<field name="code">P.P</field>
|
||||
<field name="sequence">120</field>
|
||||
<field name="amount_select">fix</field>
|
||||
<field name="condition_select">none</field>
|
||||
<field name="appears_on_payslip" eval="False"/>
|
||||
</record>
|
||||
<record id="hr_salary_rule_employment_bonus_employees" model="hr.salary.rule">
|
||||
<field name="category_id" ref="hr_payroll_head_employment_bonus"/>
|
||||
<field name="name">Employment Bonus</field>
|
||||
<field name="code">EmpBonus.1</field>
|
||||
<field name="sequence">42</field>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = categories.BRUT <= 2431.27</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute">
|
||||
salary = categories.BRUT
|
||||
if salary <= 1577.89:
|
||||
result = 193.79
|
||||
elif salary <= 2461.27:
|
||||
result = 193.79 - (0.2194 * (salary - 1577.89))
|
||||
</field>
|
||||
</record>
|
||||
<record id="hr_salary_rule_employment_bonus_workers" model="hr.salary.rule">
|
||||
<field name="category_id" ref="hr_payroll_head_employment_bonus"/>
|
||||
<field name="name">Employment Bonus</field>
|
||||
<field name="code">EmpBonus.2</field>
|
||||
<field name="sequence">42</field>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = categories.BRUT + categories.ONSS <= 2431.27</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute">
|
||||
salary = categories.BRUT
|
||||
if salary <= 1577.89:
|
||||
result = 209.29
|
||||
elif salary <= 2461.27:
|
||||
result = 209.29 - (0.2369 * (salary - 1577.89))
|
||||
</field>
|
||||
</record>
|
||||
<record id="hr_payroll_rules_mis_ex_onss_1" model="hr.salary.rule">
|
||||
<field name="category_id" ref="hr_salary_rule_category_spec_soc_contribution"/>
|
||||
<field name="name">Special social cotisation</field>
|
||||
<field name="code">M.ONSS.1</field>
|
||||
<field name="amount_select">fix</field>
|
||||
<field name="sequence">165</field>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="appears_on_payslip" eval="False"/>
|
||||
<field name="condition_python">result = (((employee.marital=='single') or (employee.marital=='married' and employee.spouse_fiscal_status=='without income')) and (employee.resident_bool!=True))</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute">
|
||||
if contract.wage >= 0.01 and contract.wage <= 1095.09:
|
||||
result = 0.0
|
||||
elif contract.wage >= 1095.10 and contract.wage <= 1945.38:
|
||||
result = 0.0
|
||||
elif contract.wage >= 1945.39 and contract.wage <= 2190.18:
|
||||
result = -min((contract.wage - 1945.38) * 0.076, 18.60)
|
||||
elif contract.wage >= 2190.19 and contract.wage <= 6038.82:
|
||||
result = -min(18.60 + (contract.wage - 2190.18) * 0.011, 60.94)
|
||||
else:
|
||||
result = -60.94
|
||||
</field>
|
||||
</record>
|
||||
<record id="hr_payroll_rules_mis_ex_onss_2" model="hr.salary.rule">
|
||||
<field name="category_id" ref="hr_salary_rule_category_spec_soc_contribution"/>
|
||||
<field name="name">Special social cotisation</field>
|
||||
<field name="code">M.ONSS.2</field>
|
||||
<field name="amount_select">fix</field>
|
||||
<field name="sequence">165</field>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="appears_on_payslip" eval="False"/>
|
||||
<field name="condition_python">result = (((employee.marital=='married' and employee.spouse_fiscal_status=='with income')) and (employee.resident_bool!=True))</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute">
|
||||
if contract.wage >= 0.01 and contract.wage <= 1095.09:
|
||||
result = 0.0
|
||||
elif contract.wage >= 1095.10 and contract.wage <= 1945.38:
|
||||
result = -9.30
|
||||
elif contract.wage >= 1945.39 and contract.wage <= 2190.18:
|
||||
result = -min(max((contract.wage - 1945.38) * 0.076, 9.30), 18.60)
|
||||
elif contract.wage >= 2190.19 and contract.wage <= 6038.82:
|
||||
result = -min(18.60 + (contract.wage - 2190.18) * 0.011, 51.64)
|
||||
else:
|
||||
result = -51.64
|
||||
</field>
|
||||
</record>
|
||||
<record id="hr_payroll_rules_bareme" model="hr.salary.rule">
|
||||
<field name="category_id" ref="hr_payroll_head_pp"/>
|
||||
<field name="parent_rule_id" ref="hr_payroll_rules_p_p_b1"/>
|
||||
<field name="name">Withholding Tax</field>
|
||||
<field name="code">B.I.</field>
|
||||
<field name="amount_select">fix</field>
|
||||
@@ -116,7 +204,6 @@
|
||||
</record>
|
||||
<record id="hr_payroll_rules_baremeII" model="hr.salary.rule">
|
||||
<field name="category_id" ref="hr_payroll_head_pp"/>
|
||||
<field name="parent_rule_id" ref="hr_payroll_rules_p_p_b1"/>
|
||||
<field name="name">Withholding Tax</field>
|
||||
<field name="code">B.II</field>
|
||||
<field name="amount_select">fix</field>
|
||||
@@ -127,7 +214,6 @@
|
||||
</record>
|
||||
<record id="hr_payroll_rules_baremeIII" model="hr.salary.rule">
|
||||
<field name="category_id" ref="hr_payroll_head_pp"/>
|
||||
<field name="parent_rule_id" ref="hr_payroll_rules_p_p_b1"/>
|
||||
<field name="name">Withholding Tax</field>
|
||||
<field name="code">B.III</field>
|
||||
<field name="amount_select">fix</field>
|
||||
@@ -136,17 +222,63 @@
|
||||
<field name="condition_python">result = employee.resident_bool==True</field>
|
||||
<field name="appears_on_payslip" eval="False"/>
|
||||
</record>
|
||||
<record id="hr_salary_rule_pp_reduction_low_salary" model="hr.salary.rule">
|
||||
<field name="category_id" ref="hr_salary_rule_pp_reductions"/>
|
||||
<field name="name">Withholding Tax Reduction for Low Salaries</field>
|
||||
<field name="code">PPRed.0</field>
|
||||
<field name="sequence">131</field>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = categories.GROSS <= 2027.86 and categories.GROSS > 565.02</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute">
|
||||
result = 5.83
|
||||
</field>
|
||||
</record>
|
||||
<record id="hr_salary_rule_pp_reduction_employees" model="hr.salary.rule">
|
||||
<field name="category_id" ref="hr_salary_rule_pp_reductions"/>
|
||||
<field name="name">Withholding Tax Reduction for Low Salaries (ONSS)</field>
|
||||
<field name="code">PPRed.1</field>
|
||||
<field name="sequence">132</field>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = categories.BRUT <= 2431.27</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute">
|
||||
salary = categories.BRUT
|
||||
percentage = 0.2803
|
||||
if salary <= 1577.89:
|
||||
result = 193.79 * percentage
|
||||
elif salary <= 2461.27:
|
||||
result = (193.79 - (0.2194 * (salary - 1577.89))) * percentage
|
||||
</field>
|
||||
</record>
|
||||
<record id="hr_salary_rule_pp_reduction_workers" model="hr.salary.rule">
|
||||
<field name="category_id" ref="hr_salary_rule_pp_reductions"/>
|
||||
<field name="name">Withholding Tax Reduction for Low Salaries (ONSS)</field>
|
||||
<field name="code">PPRed.2</field>
|
||||
<field name="sequence">133</field>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = categories.BRUT <= 2431.27</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute">
|
||||
salary = categories.BRUT
|
||||
percentage = 0.2803
|
||||
if salary <= 1577.89:
|
||||
result = 209.29 * percentage
|
||||
elif salary <= 2461.27:
|
||||
result = (209.29 - (0.2369 * (salary - 1577.89))) * percentage
|
||||
</field>
|
||||
</record>
|
||||
<record id="hr_payroll_rules_child_alw" model="hr.salary.rule">
|
||||
<field name="category_id" ref="hr_payroll.DED"/>
|
||||
<field name="category_id" ref="hr_payroll_head_child_alw"/>
|
||||
<field name="name">Child Allowance Belgium</field>
|
||||
<field name="code">Child.Allowance</field>
|
||||
<field name="sequence">140</field>
|
||||
<field name="amount_select">fix</field>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = bool(employee.children)</field>
|
||||
<field name="condition_python">result = bool(employee.dependent_children)</field>
|
||||
</record>
|
||||
<record id="hr_payroll_rules_child" model="hr.salary.rule">
|
||||
<field name="category_id" ref="hr_payroll.DED"/>
|
||||
<field name="category_id" ref="hr_payroll_head_child_alw"/>
|
||||
<field name="parent_rule_id" ref="hr_payroll_rules_child_alw"/>
|
||||
<field name="name">Child Allowance Belgium</field>
|
||||
<field name="code">Ch.A</field>
|
||||
@@ -160,114 +292,195 @@
|
||||
|
||||
a) Reduction for dependent children</field>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = bool(employee.children)</field>
|
||||
<field name="condition_python">result = bool(employee.dependent_children)</field>
|
||||
</record>
|
||||
<record id="hr_payroll_rules_child_handicap" model="hr.salary.rule">
|
||||
<field name="category_id" ref="hr_payroll.DED"/>
|
||||
<field name="parent_rule_id" ref="hr_payroll_rules_child_alw"/>
|
||||
<field name="name">Child Allowance for the Disabled Belgium</field>
|
||||
<field name="code">Ch.Handicap</field>
|
||||
<field name="amount_select">fix</field>
|
||||
<record id="hr_salary_rule_other_family_charges_reductions" model="hr.salary.rule">
|
||||
<field name="category_id" ref="hr_salary_rule_category_family_reductions"/>
|
||||
<field name="name">Other family charges reductions</field>
|
||||
<field name="code">Oth.Fam.Red</field>
|
||||
<field name="sequence">143</field>
|
||||
<field name="note">"Disabled child" means:
|
||||
|
||||
the child reaches at least 66% of failure or reduced physical or mental capacity
|
||||
the head of one or more conditions;
|
||||
|
||||
|
||||
the child that is established, regardless of age, that due to developments and recognized
|
||||
before age 65</field>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = bool(employee.disabled_children_number)</field>
|
||||
<field name="amount_select">fix</field>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = True</field>
|
||||
</record>
|
||||
<record id="hr_salary_rule_reduction_isolated" model="hr.salary.rule">
|
||||
<field name="category_id" ref='hr_salary_rule_category_family_reductions'/>
|
||||
<field name="parent_rule_id" ref="hr_salary_rule_other_family_charges_reductions"/>
|
||||
<field name="name">Reduction for isolated people</field>
|
||||
<field name="code">Red.Iso</field>
|
||||
<field name="sequence">144</field>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = (employee.marital == 'single') and (categories.PP + categories.PPRed + categories.FamRed + categories.ChA < 0.0)</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute">result = min(25.00, - (categories.PP + categories.PPRed + categories.FamRed + categories.ChA))</field>
|
||||
</record>
|
||||
<record id="hr_salary_rule_reduction_isolated_parent" model="hr.salary.rule">
|
||||
<field name="category_id" ref='hr_salary_rule_category_family_reductions'/>
|
||||
<field name="parent_rule_id" ref="hr_salary_rule_other_family_charges_reductions"/>
|
||||
<field name="name">Reduction for isolated parent</field>
|
||||
<field name="code">Red.Iso.Par</field>
|
||||
<field name="sequence">144</field>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = (employee.marital == 'widower' or (employee.marital in ['divorced', 'single', 'widower'] and employee.dependent_children)) and (categories.PP + categories.PPRed + categories.FamRed + categories.ChA < 0.0)</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute">result = min(35.00, - (categories.PP + categories.PPRed + categories.FamRed + categories.ChA))</field>
|
||||
</record>
|
||||
<record id="hr_salary_rule_reduction_disabled" model="hr.salary.rule">
|
||||
<field name="category_id" ref='hr_salary_rule_category_family_reductions'/>
|
||||
<field name="parent_rule_id" ref="hr_salary_rule_other_family_charges_reductions"/>
|
||||
<field name="name">Reduction for disabled employee</field>
|
||||
<field name="code">Red.Dis</field>
|
||||
<field name="sequence">145</field>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = (employee.disabled) and (categories.PP + categories.PPRed + categories.FamRed + categories.ChA < 0.0)</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute">result = min(35.00, - (categories.PP + categories.PPRed + categories.FamRed + categories.ChA))</field>
|
||||
</record>
|
||||
<record id="hr_salary_rule_reduction_seniors" model="hr.salary.rule">
|
||||
<field name="category_id" ref='hr_salary_rule_category_family_reductions'/>
|
||||
<field name="parent_rule_id" ref="hr_salary_rule_other_family_charges_reductions"/>
|
||||
<field name="name">Reduction for dependent seniors</field>
|
||||
<field name="code">Red.Seniors</field>
|
||||
<field name="sequence">146</field>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = (employee.other_dependent_people and employee.dependent_seniors) and (categories.PP + categories.PPRed + categories.FamRed + categories.ChA < 0.0)</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute">result = min(71.0 * employee.dependent_seniors, - (categories.PP + categories.PPRed + categories.FamRed + categories.ChA))</field>
|
||||
</record>
|
||||
<record id="hr_salary_rule_reduction_juniors" model="hr.salary.rule">
|
||||
<field name="category_id" ref='hr_salary_rule_category_family_reductions'/>
|
||||
<field name="parent_rule_id" ref="hr_salary_rule_other_family_charges_reductions"/>
|
||||
<field name="name">Reduction for dependent juniors</field>
|
||||
<field name="code">Red.Juniors</field>
|
||||
<field name="sequence">147</field>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = (employee.other_dependent_people and employee.dependent_juniors) and (categories.PP + categories.PPRed + categories.FamRed + categories.ChA < 0.0)</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute">result = min(35.0 * employee.dependent_juniors, - (categories.PP + categories.PPRed + categories.FamRed + categories.ChA))</field>
|
||||
</record>
|
||||
<record id="hr_salary_rule_reduction_insufficient_net" model="hr.salary.rule">
|
||||
<field name="category_id" ref='hr_salary_rule_category_family_reductions'/>
|
||||
<field name="parent_rule_id" ref="hr_salary_rule_other_family_charges_reductions"/>
|
||||
<field name="name">Reduction for low spouse revenue</field>
|
||||
<field name="code">Red.Spouse.Net</field>
|
||||
<field name="sequence">148</field>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = (employee.marital == 'married' and employee.spouse_net_revenue <= 221.0) and (categories.PP + categories.PPRed + categories.FamRed + categories.ChA < 0.0)</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute">result = min(110.50, - (categories.PP + categories.PPRed + categories.FamRed + categories.ChA))</field>
|
||||
</record>
|
||||
<record id="hr_salary_rule_reduction_insufficient_other_net" model="hr.salary.rule">
|
||||
<field name="category_id" ref='hr_salary_rule_category_family_reductions'/>
|
||||
<field name="parent_rule_id" ref="hr_salary_rule_other_family_charges_reductions"/>
|
||||
<field name="name">Reduction for low spouse other revenue</field>
|
||||
<field name="code">Red.Spouse.Oth.Net</field>
|
||||
<field name="sequence">149</field>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = (employee.marital == 'married' and not employee.spouse_net_revenue and employee.spouse_other_net_revenue <= 440.0) and (categories.PP + categories.PPRed + categories.FamRed + categories.ChA < 0.0)</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute">result = min(220.0, - (categories.PP + categories.PPRed + categories.FamRed + categories.ChA))</field>
|
||||
</record>
|
||||
<record id="hr_payroll_rules_spouse_handicap" model="hr.salary.rule">
|
||||
<field name="category_id" ref="hr_payroll_head_div_net"/>
|
||||
<field name="parent_rule_id" ref="hr_salary_rule_other_family_charges_reductions"/>
|
||||
<field name="name">Disabled Spouse Allowance</field>
|
||||
<field name="code">Sp.handicap</field>
|
||||
<field name="amount_select">fix</field>
|
||||
<field name="sequence">145</field>
|
||||
<field name="amount_fix">31</field>
|
||||
<field name="sequence">150</field>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = employee.disabled_spouse_bool==True</field>
|
||||
</record>
|
||||
<record id="hr_payroll_rules_company_car_parent" model="hr.salary.rule">
|
||||
<field name="category_id" ref="hr_payroll_head_div_impos"/>
|
||||
<field name="name">Company Car</field>
|
||||
<field name="code">car</field>
|
||||
<field name="amount_select">fix</field>
|
||||
<field name="sequence">15</field>
|
||||
<field name="condition_select">none</field>
|
||||
<field name="appears_on_payslip" eval="False"/>
|
||||
<field name="condition_python">result = (employee.disabled_spouse_bool==True) and (categories.PP + categories.PPRed + categories.FamRed + categories.ChA < 0.0)</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute">min(35.0, - (categories.PP + categories.PPRed + categories.FamRed + categories.ChA))</field>
|
||||
</record>
|
||||
<record id="hr_payroll_rules_parent_company_car" model="hr.salary.rule">
|
||||
<field name="category_id" ref="hr_payroll_head_div_impos"/>
|
||||
<field name="name">Company car</field>
|
||||
<field name="parent_rule_id" ref="hr_payroll_rules_company_car_parent"/>
|
||||
<field name="code">CAR 1</field>
|
||||
<field name="name">ATN Company Car</field>
|
||||
<field name="code">ATN.CAR</field>
|
||||
<field name="sequence">15</field>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = bool(contract.car_employee_deduction)</field>
|
||||
<field name="condition_python">
|
||||
result = bool(contract.transport_mode == 'company_car' and contract.car_atn)
|
||||
</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute">result = contract.car_employee_deduction</field>
|
||||
<field name="amount_python_compute">
|
||||
result = contract.car_atn
|
||||
</field>
|
||||
</record>
|
||||
<record id="hr_salary_rule_atn_internet" model="hr.salary.rule">
|
||||
<field name="category_id" ref="hr_payroll_head_div_impos"/>
|
||||
<field name="name">ATN Internet</field>
|
||||
<field name="code">ATN.INT</field>
|
||||
<field name="sequence">16</field>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = bool(contract.internet)</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute">result = 5.0</field>
|
||||
</record>
|
||||
<record id="hr_salary_rule_atn_mobile" model="hr.salary.rule">
|
||||
<field name="category_id" ref="hr_payroll_head_div_impos"/>
|
||||
<field name="name">ATN Mobile</field>
|
||||
<field name="code">ATN.MOB</field>
|
||||
<field name="sequence">17</field>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = bool(contract.mobile)</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute">result = 12.0</field>
|
||||
</record>
|
||||
<record id="hr_payroll_rules_company_car_2" model="hr.salary.rule">
|
||||
<field name="category_id" ref="hr_payroll_head_div_net"/>
|
||||
<field name="name">Retenue Company car</field>
|
||||
<field name="parent_rule_id" ref="hr_payroll_rules_company_car_parent"/>
|
||||
<field name="code">CAR 2</field>
|
||||
<field name="name">ATN Company Car</field>
|
||||
<field name="code">ATN.CAR.2</field>
|
||||
<field name="sequence">160</field>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = contract.car_employee_deduction</field>
|
||||
<field name="condition_python">
|
||||
result = bool(contract.transport_mode == 'company_car' and contract.car_atn)
|
||||
</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute">result = -contract.car_employee_deduction</field>
|
||||
<field name="amount_python_compute">
|
||||
result = -contract.car_atn
|
||||
</field>
|
||||
</record>
|
||||
<record id="hr_payroll_rules_company_car_emp" model="hr.salary.rule">
|
||||
<field name="category_id" ref="hr_payroll.COMP"/>
|
||||
<field name="name">Company car, Company part</field>
|
||||
<field name="parent_rule_id" ref="hr_payroll_rules_company_car_parent"/>
|
||||
<field name="code">CAR 3</field>
|
||||
<field name="sequence">160</field>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = bool(contract.car_company_amount)</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute">result = -contract.car_company_amount</field>
|
||||
<field name="appears_on_payslip" eval="False"/>
|
||||
</record>
|
||||
<record id="hr_payroll_rules_parent_ch" model="hr.salary.rule">
|
||||
<record id="hr_salary_rule_atn_internet_2" model="hr.salary.rule">
|
||||
<field name="category_id" ref="hr_payroll_head_div_net"/>
|
||||
<field name="name">Meal Voucher</field>
|
||||
<field name="code">MEAL_V</field>
|
||||
<field name="amount_select">fix</field>
|
||||
<field name="sequence">165</field>
|
||||
<field name="condition_select">none</field>
|
||||
<field name="appears_on_payslip" eval="False"/>
|
||||
</record>
|
||||
<record id="hr_payroll_rules_ch_value" model="hr.salary.rule">
|
||||
<field name="category_id" ref="hr_payroll.COMP"/>
|
||||
<field name="parent_rule_id" ref="hr_payroll_rules_parent_ch"/>
|
||||
<field name="name">Meal Voucher, Company Part</field>
|
||||
<field name="code">MEAL_V_COMP</field>
|
||||
<field name="amount_select">fix</field>
|
||||
<field name="sequence">165</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute">result = -(contract.meal_voucher_amount - contract.meal_voucher_employee_deduction) * worked_days.WORK100.number_of_days</field>
|
||||
<field name="name">ATN Internet</field>
|
||||
<field name="code">ATN.INT.2</field>
|
||||
<field name="sequence">161</field>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = bool(contract.meal_voucher_amount)</field>
|
||||
<field name="condition_python">result = contract.internet</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute">result = -5.0</field>
|
||||
</record>
|
||||
<record id="hr_salary_rule_atn_mobile_2" model="hr.salary.rule">
|
||||
<field name="category_id" ref="hr_payroll_head_div_net"/>
|
||||
<field name="name">ATN Mobile</field>
|
||||
<field name="code">ATN.MOB.2</field>
|
||||
<field name="sequence">162</field>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = contract.mobile</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute">result = -12.0</field>
|
||||
</record>
|
||||
<record id="hr_salary_rule_commission_on_target" model="hr.salary.rule">
|
||||
<field name="category_id" ref="hr_payroll_head_div_net"/>
|
||||
<field name="name">Commission on Target</field>
|
||||
<field name="code">COM</field>
|
||||
<field name="sequence">170</field>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = bool(contract.commission_on_target)</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute">result = contract.warrant_value_employee / 12.0</field>
|
||||
<field name="appears_on_payslip" eval="False"/>
|
||||
<field name="register_id" ref="contrib_register_meal_vouchers"/>
|
||||
</record>
|
||||
<record id="hr_payroll_rules_ch_worker" model="hr.salary.rule">
|
||||
<field name="category_id" ref="hr_payroll_head_div_net"/>
|
||||
<field name="name">Retain on Meal Voucher</field>
|
||||
<field name="parent_rule_id" ref="hr_payroll_rules_parent_ch"/>
|
||||
<field name="code">MEAL_V_EMP</field>
|
||||
<field name="sequence">165</field>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = bool(contract.meal_voucher_employee_deduction)</field>
|
||||
<field name="condition_python">result = bool(contract.meal_voucher_amount)</field>
|
||||
<field name="amount_select">percentage</field>
|
||||
<field name="amount_percentage">-100.0</field>
|
||||
<field name="amount_percentage_base">contract.meal_voucher_employee_deduction</field>
|
||||
<field name="quantity">worked_days.WORK100.number_of_days</field>
|
||||
<field name="amount_percentage_base">contract.meal_voucher_amount - contract.meal_voucher_paid_by_employer</field>
|
||||
<field name="quantity">worked_days.WORK100 and worked_days.WORK100.number_of_days</field>
|
||||
<field name="appears_on_payslip" eval="True"/>
|
||||
<field name="register_id" ref="contrib_register_meal_vouchers"/>
|
||||
</record>
|
||||
@@ -278,38 +491,18 @@
|
||||
<field name="amount_select">code</field>
|
||||
<field name="sequence">65</field>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = bool(contract.travel_reimbursement_amount)</field>
|
||||
<field name="amount_python_compute">result = contract.travel_reimbursement_amount</field>
|
||||
<field name="condition_python">result = bool(contract.others_reimbursed_amount)</field>
|
||||
<field name="amount_python_compute">result = contract.others_reimbursed_amount</field>
|
||||
</record>
|
||||
<record id="hr_payroll_rules_mis_ex_onss" model="hr.salary.rule">
|
||||
<field name="category_id" ref="hr_payroll.DED"/>
|
||||
<field name="name">Miscellaneous exempt ONSS</field>
|
||||
<field name="code">M.ONSS</field>
|
||||
<record id="hr_salary_rule_representation_fees" model="hr.salary.rule">
|
||||
<field name="category_id" ref="hr_payroll_head_div_net"/>
|
||||
<field name="name">Representation Fees</field>
|
||||
<field name="code">REP.FEES</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="sequence">165</field>
|
||||
<field name="sequence">171</field>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = bool(contract.misc_onss_deduction)</field>
|
||||
<field name="amount_python_compute">result = -contract.misc_onss_deduction</field>
|
||||
</record>
|
||||
<record id="hr_payroll_rules_insurance" model="hr.salary.rule">
|
||||
<field name="category_id" ref="hr_payroll.DED"/>
|
||||
<field name="name">Insurance</field>
|
||||
<field name="code">Ins</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="sequence">165</field>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = bool(contract.insurance_employee_deduction)</field>
|
||||
<field name="amount_python_compute">result = -contract.insurance_employee_deduction</field>
|
||||
</record>
|
||||
<record id="hr_payroll_rules_advantage" model="hr.salary.rule">
|
||||
<field name="category_id" ref="hr_payroll.ALW"/>
|
||||
<field name="name">Benefits of various nature</field>
|
||||
<field name="code">Ben</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="sequence">10</field>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = bool(contract.misc_advantage_amount)</field>
|
||||
<field name="amount_python_compute">result = contract.misc_advantage_amount</field>
|
||||
<field name="condition_python">result = bool(contract.representation_fees)</field>
|
||||
<field name="amount_python_compute">result = contract.representation_fees</field>
|
||||
</record>
|
||||
<record id="hr_payroll_rules_suppl_net" model="hr.salary.rule">
|
||||
<field name="category_id" ref="hr_payroll_head_div_net"/>
|
||||
@@ -334,15 +527,119 @@
|
||||
<record id="hr_payroll_salary_structure_employee" model="hr.payroll.structure">
|
||||
<field name="code">C.P.218</field>
|
||||
<field name="name">Belgian Employee</field>
|
||||
<field eval="[(6, 0, [ref('hr_payroll_rules_company_car_parent'),ref('hr_payroll_rules_onss_rule'),ref('hr_payroll_rules_employee'),ref('hr_payroll_rules_p_p_b1'),ref('hr_payroll_rules_child_alw'),ref('hr_payroll_rules_parent_ch'),ref('hr_payroll_rules_reim_travel'),ref('hr_payroll_rules_mis_ex_onss'),ref('hr_payroll_rules_insurance'),ref('hr_payroll_rules_advantage'),ref('hr_payroll_rules_suppl_net'),ref('hr_payroll_rules_retained_net')])]" name="rule_ids"/>
|
||||
<field eval="[(6, 0, [
|
||||
ref('hr_payroll_rules_parent_company_car'),
|
||||
ref('hr_payroll_rules_company_car_2'),
|
||||
ref('hr_salary_rule_atn_mobile_2'),
|
||||
ref('hr_salary_rule_atn_internet_2'),
|
||||
ref('hr_salary_rule_atn_internet'),
|
||||
ref('hr_salary_rule_atn_mobile'),
|
||||
ref('hr_payroll_rules_onss_rule'),
|
||||
ref('hr_salary_rule_employment_bonus_employees'),
|
||||
ref('hr_payroll_rules_employee'),
|
||||
ref('hr_payroll_rules_bareme'),
|
||||
ref('hr_payroll_rules_baremeII'),
|
||||
ref('hr_payroll_rules_baremeIII'),
|
||||
ref('hr_salary_rule_other_family_charges_reductions'),
|
||||
ref('hr_payroll_rules_child_alw'),
|
||||
ref('hr_payroll_rules_ch_worker'),
|
||||
ref('hr_payroll_rules_reim_travel'),
|
||||
ref('hr_payroll_rules_mis_ex_onss_1'),
|
||||
ref('hr_payroll_rules_mis_ex_onss_2'),
|
||||
ref('hr_salary_rule_representation_fees'),
|
||||
ref('hr_payroll_rules_suppl_net'),
|
||||
ref('hr_payroll_rules_retained_net'),
|
||||
ref('hr_salary_rule_commission_on_target'),
|
||||
ref('hr_salary_rule_pp_reduction_employees'),
|
||||
ref('hr_salary_rule_pp_reduction_low_salary'),
|
||||
|
||||
])]" name="rule_ids"/>
|
||||
<field name="company_id" ref="base.main_company"/>
|
||||
<field name="parent_id" ref="hr_payroll.structure_base"/>
|
||||
</record>
|
||||
<record id="hr_payroll_salary_structure_worker" model="hr.payroll.structure">
|
||||
<field name="code">C.P.219</field>
|
||||
<field name="name">Belgian Worker</field>
|
||||
<field eval="[(6, 0, [ref('hr_payroll_rules_maj'), ref('hr_payroll_rules_onss_rule'),ref('hr_payroll_rules_p_p_b1'),ref('hr_payroll_rules_child_alw'),ref('hr_payroll_rules_parent_ch'),ref('hr_payroll_rules_reim_travel'),ref('hr_payroll_rules_mis_ex_onss'),ref('hr_payroll_rules_insurance'),ref('hr_payroll_rules_advantage'),ref('hr_payroll_rules_suppl_net'),ref('hr_payroll_rules_retained_net')])]" name="rule_ids"/>
|
||||
<field eval="[(6, 0, [
|
||||
ref('hr_payroll_rules_maj'),
|
||||
ref('hr_payroll_rules_onss_rule'),
|
||||
ref('hr_salary_rule_employment_bonus_workers'),
|
||||
ref('hr_payroll_rules_bareme'),
|
||||
ref('hr_payroll_rules_baremeII'),
|
||||
ref('hr_payroll_rules_baremeIII'),
|
||||
ref('hr_payroll_rules_child_alw'),
|
||||
ref('hr_salary_rule_other_family_charges_reductions'),
|
||||
ref('hr_payroll_rules_ch_worker'),
|
||||
ref('hr_payroll_rules_reim_travel'),
|
||||
ref('hr_payroll_rules_mis_ex_onss_1'),
|
||||
ref('hr_payroll_rules_mis_ex_onss_2'),
|
||||
ref('hr_salary_rule_representation_fees'),
|
||||
ref('hr_payroll_rules_suppl_net'),
|
||||
ref('hr_payroll_rules_retained_net'),
|
||||
ref('hr_salary_rule_commission_on_target'),
|
||||
ref('hr_salary_rule_pp_reduction_workers')
|
||||
])]" name="rule_ids"/>
|
||||
<field name="company_id" ref="base.main_company"/>
|
||||
<field name="parent_id" ref="hr_payroll.structure_base"/>
|
||||
</record>
|
||||
|
||||
<!-- Salary advantages definition -->
|
||||
<record id="hr_contract_advantage_template_meal_voucher_amount" model="hr.contract.advantage.template">
|
||||
<field name="name">Check Value Meal</field>
|
||||
<field name="code">meal_voucher_amount</field>
|
||||
<field name="default_value">7.45</field>
|
||||
</record>
|
||||
<record id="hr_contract_advantage_template_internet" model="hr.contract.advantage.template">
|
||||
<field name="name">Internet</field>
|
||||
<field name="code">internet</field>
|
||||
<field name="default_value">38.0</field>
|
||||
</record>
|
||||
<record id="hr_contract_advantage_template_mobile" model="hr.contract.advantage.template">
|
||||
<field name="name">Mobile</field>
|
||||
<field name="code">mobile</field>
|
||||
<field name="default_value">30.0</field>
|
||||
</record>
|
||||
<record id="hr_contract_advantage_template_mobile_plus" model="hr.contract.advantage.template">
|
||||
<field name="name">Mobile +</field>
|
||||
<field name="code">mobile_plus</field>
|
||||
<field name="default_value">50.0</field>
|
||||
</record>
|
||||
<record id="hr_contract_advantage_template_additional_net_amount" model="hr.contract.advantage.template">
|
||||
<field name="name">Net supplements</field>
|
||||
<field name="code">additional_net_amount</field>
|
||||
</record>
|
||||
<record id="hr_contract_advantage_template_retained_net_amount" model="hr.contract.advantage.template">
|
||||
<field name="name">Net retained</field>
|
||||
<field name="code">retained_net_amount</field>
|
||||
</record>
|
||||
<record id="hr_contract_advantage_template_commission_on_target" model="hr.contract.advantage.template">
|
||||
<field name="name">Commission on target</field>
|
||||
<field name="code">commission_on_target</field>
|
||||
<field name="default_value">1000.0</field>
|
||||
</record>
|
||||
<record id="hr_contract_advantage_template_fuel_card" model="hr.contract.advantage.template">
|
||||
<field name="name">Fuel card</field>
|
||||
<field name="code">fuel_card</field>
|
||||
<field name="default_value">150.0</field>
|
||||
<field name="lower_bound">0.0</field>
|
||||
<field name="upper_bound">500.0</field>
|
||||
</record>
|
||||
<record id="hr_contract_advantage_template_holidays" model="hr.contract.advantage.template">
|
||||
<field name="name">Holidays duration</field>
|
||||
<field name="code">holidays</field>
|
||||
<field name="default_value">20.0</field>
|
||||
<field name="lower_bound">0.0</field>
|
||||
<field name="upper_bound">30.0</field>
|
||||
</record>
|
||||
<record id="hr_contract_advantage_template_representation_fees" model="hr.contract.advantage.template">
|
||||
<field name="name">Representation fees</field>
|
||||
<field name="code">representation_fees</field>
|
||||
<field name="default_value">150.0</field>
|
||||
</record>
|
||||
<record id="hr_contract_advantage_template_eco_checks" model="hr.contract.advantage.template">
|
||||
<field name="name">Eco-checks</field>
|
||||
<field name="code">eco_checks</field>
|
||||
<field name="default_value">250.0</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- Employee Contract -->
|
||||
<record id="hr_payroll.hr_contract_firstcontract1" model="hr.contract">
|
||||
<field name="name">Developer's Contract (Belgium payroll C.P. 218)</field>
|
||||
<field name="struct_id" ref="hr_payroll_salary_structure_employee"/>
|
||||
<field eval="2500.0" name="wage"/>
|
||||
<field eval="6.0" name="meal_voucher_amount"/>
|
||||
<field eval="1.09" name="meal_voucher_employee_deduction"/>
|
||||
<field eval="250.0" name="car_company_amount"/>
|
||||
<field eval="110" name="car_employee_deduction"/>
|
||||
<field eval="50.0" name="additional_net_amount"/>
|
||||
</record>
|
||||
|
||||
<record id="hr_payroll.hr_employee_payroll" model="hr.employee">
|
||||
<field name="marital">single</field>
|
||||
</record>
|
||||
|
||||
<record id="res_partner_onss" model="res.partner">
|
||||
<field name="street">54 Rue Jean Baptiste Colette BP </field>
|
||||
<field name="city">Attiches</field>
|
||||
<field name="zip">59551</field>
|
||||
<field name="country_id" ref="base.fr"/>
|
||||
<field name="email">onss@yourcompany.example.com</field>
|
||||
</record>
|
||||
<record id="res_partner_pp" model="res.partner">
|
||||
<field name="street">Shanghai Branch</field>
|
||||
<field name="street2">Anshun Road 83, Room 1603 - </field>
|
||||
<field name="city">Shanghai</field>
|
||||
<field name="zip">200052</field>
|
||||
<field name="country_id" ref="base.cn"/>
|
||||
<field name="email">fiscal@yourcompany.example.com</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -1,30 +1,246 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models
|
||||
from odoo.addons import decimal_precision as dp
|
||||
from odoo import api, fields, models, _
|
||||
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class HrContract(models.Model):
|
||||
_inherit = 'hr.contract'
|
||||
|
||||
travel_reimbursement_amount = fields.Float(string='Reimbursement of travel expenses', digits=dp.get_precision('Payroll'))
|
||||
car_company_amount = fields.Float(string='Company car employer', digits=dp.get_precision('Payroll'))
|
||||
car_employee_deduction = fields.Float(string='Company Car Deduction for Worker', digits=dp.get_precision('Payroll'))
|
||||
misc_onss_deduction = fields.Float(string='Miscellaneous exempt ONSS', digits=dp.get_precision('Payroll'))
|
||||
meal_voucher_amount = fields.Float(string='Check Value Meal', digits=dp.get_precision('Payroll'))
|
||||
meal_voucher_employee_deduction = fields.Float(string='Check Value Meal - by worker', digits=dp.get_precision('Payroll'))
|
||||
insurance_employee_deduction = fields.Float(string='Insurance Group - by worker', digits=dp.get_precision('Payroll'))
|
||||
misc_advantage_amount = fields.Float(string='Benefits of various nature', digits=dp.get_precision('Payroll'))
|
||||
additional_net_amount = fields.Float(string='Net supplements', digits=dp.get_precision('Payroll'))
|
||||
retained_net_amount = fields.Float('Net retained ', digits=dp.get_precision('Payroll'))
|
||||
transport_mode = fields.Selection([
|
||||
('company_car', 'Company car'),
|
||||
('public_transport', 'Public Transport'),
|
||||
('others', 'Other'),
|
||||
], string="Transport", default='company_car')
|
||||
car_atn = fields.Monetary(string='ATN Company Car')
|
||||
public_transport_employee_amount = fields.Monetary('Paid by the employee (Monthly)')
|
||||
thirteen_month = fields.Monetary(compute='_compute_holidays_advantages', string='13th Month')
|
||||
double_holidays = fields.Monetary(compute='_compute_holidays_advantages', string='Double holidays')
|
||||
warrant_value_employee = fields.Monetary(compute='_compute_warrants_cost', string="Warrant value for the employee")
|
||||
|
||||
# Employer costs fields
|
||||
final_yearly_costs = fields.Monetary(compute='_compute_final_yearly_costs', readonly=False,
|
||||
string='Total Employee Cost', groups="hr.group_hr_manager")
|
||||
monthly_yearly_costs = fields.Monetary(compute='_compute_monthly_yearly_costs', string='Monthly Equivalent Cost', readonly=True)
|
||||
ucm_insurance = fields.Monetary(compute='_compute_ucm_insurance', string="Social Secretary Costs")
|
||||
social_security_contributions = fields.Monetary(compute='_compute_social_security_contributions', string="Social Security Contributions")
|
||||
yearly_cost_before_charges = fields.Monetary(compute='_compute_yearly_cost_before_charges', string="Yearly Costs Before Charges")
|
||||
meal_voucher_paid_by_employer = fields.Monetary(compute='_compute_meal_voucher_paid_by_employer', string="Meal Voucher Paid by Employer")
|
||||
company_car_total_depreciated_cost = fields.Monetary()
|
||||
public_transport_reimbursed_amount = fields.Monetary(string='Reimbursed amount',
|
||||
compute='_compute_public_transport_reimbursed_amount', readonly=False, store=True)
|
||||
others_reimbursed_amount = fields.Monetary(string='Reimbursed amount')
|
||||
transport_employer_cost = fields.Monetary(compute='_compute_transport_employer_cost', string="Employer cost from employee transports")
|
||||
warrants_cost = fields.Monetary(compute='_compute_warrants_cost')
|
||||
|
||||
# Advantages
|
||||
commission_on_target = fields.Monetary(string="Commission on Target", default=1000)
|
||||
fuel_card = fields.Monetary(string="Fuel Card", default=150)
|
||||
internet = fields.Monetary(string="Internet", default=38, help="Your internet subscription will be paid up to this amount.")
|
||||
representation_fees = fields.Monetary(string="Representation Fees", default=150.0)
|
||||
mobile = fields.Monetary(string="Mobile", default=30, help="Your mobile subscription will be paid up to this amount.")
|
||||
mobile_plus = fields.Monetary(string="International Communication", default=50, help="Your international mobile subscription will be paid up to this amount.")
|
||||
meal_voucher_amount = fields.Monetary(string="Meal Vouchers", default=7.45)
|
||||
holidays = fields.Float(string="Holidays", default=20)
|
||||
holidays_compensation = fields.Monetary(compute='_compute_holidays_compensation', string="Holidays Compensation")
|
||||
wage_with_holidays = fields.Monetary(compute='_compute_wage_with_holidays', sting="Wage update with holidays retenues")
|
||||
additional_net_amount = fields.Monetary(string="Net Supplements")
|
||||
retained_net_amount = fields.Monetary(sting="Net Retained")
|
||||
eco_checks = fields.Monetary("Eco Vouchers", default=250)
|
||||
|
||||
@api.depends('holidays', 'wage')
|
||||
def _compute_wage_with_holidays(self):
|
||||
for contract in self:
|
||||
if contract.holidays > 20.0:
|
||||
yearly_cost = contract.final_yearly_costs * (1.0 - (contract.holidays - 20.0) / 220.0)
|
||||
contract.wage_with_holidays = contract._get_gross_from_employer_costs(yearly_cost)
|
||||
else:
|
||||
contract.wage_with_holidays = contract.wage
|
||||
|
||||
@api.depends('transport_mode', 'company_car_total_depreciated_cost',
|
||||
'public_transport_reimbursed_amount', 'others_reimbursed_amount')
|
||||
def _compute_transport_employer_cost(self):
|
||||
for contract in self:
|
||||
if contract.transport_mode == 'company_car':
|
||||
contract.transport_employer_cost = contract.company_car_total_depreciated_cost
|
||||
elif contract.transport_mode == 'public_transport':
|
||||
contract.transport_employer_cost = contract.public_transport_reimbursed_amount
|
||||
elif contract.transport_mode == 'others':
|
||||
contract.transport_employer_cost == contract.others_reimbursed_amount
|
||||
|
||||
@api.depends('commission_on_target')
|
||||
def _compute_warrants_cost(self):
|
||||
for contract in self:
|
||||
contract.warrants_cost = contract.commission_on_target * 1.326 / 1.05 * 12.0
|
||||
contract.warrant_value_employee = contract.warrants_cost * 0.54
|
||||
|
||||
@api.depends('wage', 'fuel_card', 'representation_fees', 'transport_employer_cost',
|
||||
'internet', 'mobile', 'mobile_plus')
|
||||
def _compute_yearly_cost_before_charges(self):
|
||||
for contract in self:
|
||||
contract.yearly_cost_before_charges = 12.0 * (
|
||||
contract.wage * (1.0 + 1.0 / 12.0) +
|
||||
contract.fuel_card +
|
||||
contract.representation_fees +
|
||||
contract.internet +
|
||||
contract.mobile +
|
||||
contract.mobile_plus +
|
||||
contract.transport_employer_cost
|
||||
)
|
||||
|
||||
@api.depends('yearly_cost_before_charges', 'ucm_insurance', 'social_security_contributions',
|
||||
'social_security_contributions', 'double_holidays', 'warrants_cost', 'meal_voucher_paid_by_employer')
|
||||
def _compute_final_yearly_costs(self):
|
||||
for contract in self:
|
||||
contract.final_yearly_costs = (
|
||||
contract.yearly_cost_before_charges +
|
||||
contract.ucm_insurance +
|
||||
contract.social_security_contributions +
|
||||
contract.double_holidays +
|
||||
contract.warrants_cost +
|
||||
(220.0 * contract.meal_voucher_paid_by_employer)
|
||||
)
|
||||
|
||||
@api.depends('holidays', 'final_yearly_costs')
|
||||
def _compute_holidays_compensation(self):
|
||||
for contract in self:
|
||||
if contract.holidays < 20:
|
||||
decrease_amount = contract.final_yearly_costs * (20.0 - contract.holidays) / 220.0
|
||||
contract.holidays_compensation = decrease_amount
|
||||
else:
|
||||
contract.holidays_compensation = 0.0
|
||||
|
||||
@api.onchange('final_yearly_costs')
|
||||
def _onchange_final_yearly_costs(self):
|
||||
self.wage = self._get_gross_from_employer_costs(self.final_yearly_costs)
|
||||
|
||||
@api.depends('meal_voucher_amount')
|
||||
def _compute_meal_voucher_paid_by_employer(self):
|
||||
for contract in self:
|
||||
contract.meal_voucher_paid_by_employer = contract.meal_voucher_amount * (1 - 0.1463)
|
||||
|
||||
@api.depends('wage')
|
||||
def _compute_social_security_contributions(self):
|
||||
for contract in self:
|
||||
total_wage = contract.wage * 13.0
|
||||
contract.social_security_contributions = (total_wage) * 0.3507
|
||||
|
||||
@api.depends('wage')
|
||||
def _compute_ucm_insurance(self):
|
||||
for contract in self:
|
||||
contract.ucm_insurance = (contract.wage * 12.0) * 0.05
|
||||
|
||||
@api.depends('public_transport_employee_amount')
|
||||
def _compute_public_transport_reimbursed_amount(self):
|
||||
for contract in self:
|
||||
contract.public_transport_reimbursed_amount = contract._get_public_transport_reimbursed_amount(contract.public_transport_employee_amount)
|
||||
|
||||
def _get_public_transport_reimbursed_amount(self, amount):
|
||||
return amount * 0.68
|
||||
|
||||
@api.depends('final_yearly_costs')
|
||||
def _compute_monthly_yearly_costs(self):
|
||||
for contract in self:
|
||||
contract.monthly_yearly_costs = contract.final_yearly_costs / 12.0
|
||||
|
||||
@api.depends('wage')
|
||||
def _compute_holidays_advantages(self):
|
||||
for contract in self:
|
||||
contract.double_holidays = contract.wage * 0.92
|
||||
contract.thirteen_month = contract.wage
|
||||
|
||||
@api.onchange('mobile', 'mobile_plus')
|
||||
def _onchange_mobile(self):
|
||||
if self.mobile_plus and not self.mobile:
|
||||
raise ValidationError(_('You should have a mobile subscription to select an international communication amount!'))
|
||||
|
||||
def _get_internet_amount(self, has_internet):
|
||||
if has_internet:
|
||||
return self.get_attribute('internet', 'default_value')
|
||||
else:
|
||||
return 0.0
|
||||
|
||||
def _get_mobile_amount(self, has_mobile, international_communication):
|
||||
if has_mobile and international_communication:
|
||||
return self.get_attribute('mobile', 'default_value') + self.get_attribute('mobile_plus', 'default_value')
|
||||
elif has_mobile:
|
||||
return self.get_attribute('mobile', 'default_value')
|
||||
else:
|
||||
return 0.0
|
||||
|
||||
def _get_gross_from_employer_costs(self, yearly_cost):
|
||||
contract = self
|
||||
remaining_for_gross = yearly_cost \
|
||||
- 12.0 * contract.representation_fees \
|
||||
- 12.0 * contract.fuel_card \
|
||||
- 12.0 * contract.internet \
|
||||
- 12.0 * (contract.mobile + contract.mobile_plus) \
|
||||
- 12.0 * contract.transport_employer_cost \
|
||||
- (1.326 / 1.05 * 12.0) * contract.commission_on_target \
|
||||
- 220.0 * contract.meal_voucher_paid_by_employer
|
||||
gross = remaining_for_gross / (12.0 * 0.05 + 13.0 + 13.0 * 0.3507 + 0.92)
|
||||
return gross
|
||||
|
||||
|
||||
class HrEmployee(models.Model):
|
||||
_inherit = 'hr.employee'
|
||||
|
||||
spouse_fiscal_status = fields.Selection([('without income','Without Income'),('with income','With Income')], string='Tax status for spouse')
|
||||
spouse_fiscal_status = fields.Selection([
|
||||
('without income', 'Without Income'),
|
||||
('with income', 'With Income')
|
||||
], string='Tax status for spouse')
|
||||
disabled = fields.Boolean(string="Disabled", help="If the employee is declared disabled by law")
|
||||
disabled_spouse_bool = fields.Boolean(string='Disabled Spouse', help='if recipient spouse is declared disabled by law')
|
||||
disabled_children_bool = fields.Boolean(string='Disabled Children', help='if recipient children is/are declared disabled by law')
|
||||
resident_bool = fields.Boolean(string='Nonresident', help='if recipient lives in a foreign country')
|
||||
disabled_children_number = fields.Integer('Number of disabled children')
|
||||
dependent_children = fields.Integer(compute='_compute_dependent_children', string='Considered number of dependent children')
|
||||
other_dependent_people = fields.Boolean(string="Other Dependent People", help="If other people are dependent on the employee")
|
||||
other_senior_dependent = fields.Integer('# seniors (>=65)', help="Number of seniors dependent on the employee, including the disabled ones")
|
||||
other_disabled_senior_dependent = fields.Integer('# disabled seniors (>=65)')
|
||||
other_juniors_dependent = fields.Integer('# people (<65)', help="Number of juniors dependent on the employee, including the disabled ones")
|
||||
other_disabled_juniors_dependent = fields.Integer('# disabled people (<65)')
|
||||
dependent_seniors = fields.Integer(compute='_compute_dependent_people', string="Considered number of dependent seniors")
|
||||
dependent_juniors = fields.Integer(compute='_compute_dependent_people', string="Considered number of dependent juniors")
|
||||
spouse_net_revenue = fields.Float(string="Spouse Net Revenue", help="Own professional income, other than pensions, annuities or similar income")
|
||||
spouse_other_net_revenue = fields.Float(string="Spouse Other Net Revenue",
|
||||
help='Own professional income which is exclusively composed of pensions, annuities or similar income')
|
||||
|
||||
@api.constrains('spouse_fiscal_status', 'spouse_net_revenue', 'spouse_other_net_revenue')
|
||||
def _check_spouse_revenue(self):
|
||||
for employee in self:
|
||||
if employee.spouse_fiscal_status == 'with income' and not employee.spouse_net_revenue and not employee.spouse_other_net_revenue:
|
||||
raise ValidationError(_("The revenue for the spouse can't be equal to zero is the fiscal status is 'With Income'."))
|
||||
|
||||
@api.onchange('spouse_fiscal_status')
|
||||
def _onchange_spouse_fiscal_status(self):
|
||||
self.spouse_net_revenue = 0.0
|
||||
self.spouse_other_net_revenue = 0.0
|
||||
|
||||
@api.onchange('disabled_children_bool')
|
||||
def _onchange_disabled_children_bool(self):
|
||||
self.disabled_children_number = 0
|
||||
|
||||
@api.onchange('other_dependent_people')
|
||||
def _onchange_other_dependent_people(self):
|
||||
self.other_senior_dependent = 0.0
|
||||
self.other_disabled_senior_dependent = 0.0
|
||||
self.other_juniors_dependent = 0.0
|
||||
self.other_disabled_juniors_dependent = 0.0
|
||||
|
||||
@api.depends('disabled_children_bool', 'disabled_children_number', 'children')
|
||||
def _compute_dependent_children(self):
|
||||
for employee in self:
|
||||
if employee.disabled_children_bool:
|
||||
employee.dependent_children = employee.children + employee.disabled_children_number
|
||||
else:
|
||||
employee.dependent_children = employee.children
|
||||
|
||||
@api.depends('other_dependent_people', 'other_senior_dependent',
|
||||
'other_disabled_senior_dependent', 'other_juniors_dependent', 'other_disabled_juniors_dependent')
|
||||
def _compute_dependent_people(self):
|
||||
for employee in self:
|
||||
employee.dependent_seniors = employee.other_senior_dependent + employee.other_disabled_senior_dependent
|
||||
employee.dependent_juniors = employee.other_juniors_dependent + employee.other_disabled_juniors_dependent
|
||||
|
||||
@@ -1,27 +1,124 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="hr_contract_form_l10n_be_inherit" model="ir.ui.view">
|
||||
<field name="name">hr.contract.form.inherit</field>
|
||||
<field name="model">hr.contract</field>
|
||||
<field name="priority">20</field>
|
||||
<field name="inherit_id" ref="hr_contract.hr_contract_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<data>
|
||||
<xpath expr="//field[@name='struct_id']" position="after">
|
||||
<field name="travel_reimbursement_amount"/>
|
||||
<field name="car_company_amount"/>
|
||||
<field name="car_employee_deduction"/>
|
||||
<field name="misc_onss_deduction"/>
|
||||
<field name="meal_voucher_amount"/>
|
||||
<field name="meal_voucher_employee_deduction"/>
|
||||
<field name="misc_advantage_amount"/>
|
||||
<field name="additional_net_amount"/>
|
||||
<field name="retained_net_amount"/>
|
||||
<field name="insurance_employee_deduction"/>
|
||||
</xpath>
|
||||
</data>
|
||||
</field>
|
||||
</record>
|
||||
<record id="hr_contract_view_form" model="ir.ui.view">
|
||||
<field name="name">hr.contract.form</field>
|
||||
<field name="model">hr.contract</field>
|
||||
<field name="inherit_id" ref="hr_payroll.hr_contract_form_inherit"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[@name='wage']" position="after">
|
||||
<label for="wage_with_holidays" attrs="{'invisible': [('holidays', '<=', 20.0)]}"/>
|
||||
<div class="o_row" attrs="{'invisible': [('holidays', '<=', 20.0)]}">
|
||||
<field name="wage_with_holidays" nolabel="1"/>
|
||||
<span>/ month</span>
|
||||
</div>
|
||||
<label for="fuel_card"/>
|
||||
<div class="o_row">
|
||||
<field name="fuel_card" nolabel="1"/>
|
||||
<span>/ month</span>
|
||||
</div>
|
||||
<label for="meal_voucher_amount"/>
|
||||
<div class="o_row">
|
||||
<field name="meal_voucher_amount" nolabel="1"/>
|
||||
<span>/ worked day</span>
|
||||
</div>
|
||||
<label for="representation_fees"/>
|
||||
<div class="o_row">
|
||||
<field name="representation_fees" nolabel="1"/>
|
||||
<span>/ month</span>
|
||||
</div>
|
||||
<label for="additional_net_amount"/>
|
||||
<div class="o_row">
|
||||
<field name="additional_net_amount" nolabel="1"/>
|
||||
<span>/ month</span>
|
||||
</div>
|
||||
<label for="retained_net_amount"/>
|
||||
<div class="o_row">
|
||||
<field name="retained_net_amount" nolabel="1"/>
|
||||
<span>/ month</span>
|
||||
</div>
|
||||
<label for="commission_on_target"/>
|
||||
<div class="o_row">
|
||||
<field name="commission_on_target" nolabel="1"/>
|
||||
<span>/ month</span>
|
||||
</div>
|
||||
</xpath>
|
||||
<xpath expr="//group[@name='salary_and_advantages']" position="after">
|
||||
<group string="Employee Costs" groups="hr.group_hr_manager">
|
||||
<field name="yearly_cost_before_charges" invisible="1"/>
|
||||
<field name="social_security_contributions" invisible="1"/>
|
||||
<field name="warrants_cost" invisible="1"/>
|
||||
<field name="warrant_value_employee" invisible="1"/>
|
||||
<field name="ucm_insurance" invisible="1"/>
|
||||
<field name="meal_voucher_paid_by_employer" invisible="1"/>
|
||||
<label for="final_yearly_costs"/>
|
||||
<div class="o_row">
|
||||
<field name="final_yearly_costs" nolabel="1"/>
|
||||
<span>/ year</span>
|
||||
</div>
|
||||
<field name="monthly_yearly_costs"/>
|
||||
</group>
|
||||
<group string="Monthly Advantages in Nature">
|
||||
<field name="transport_mode" widget="radio"/>
|
||||
<field name="car_atn" attrs="{'invisible': [('transport_mode', '!=', 'company_car')]}" invisible="1"/>
|
||||
<field name="transport_employer_cost" invisible="1"/>
|
||||
<label string="Public Transport" attrs="{'invisible': [('transport_mode', '!=', 'public_transport')]}"/>
|
||||
<div attrs="{'invisible': [('transport_mode', '!=', 'public_transport')]}">
|
||||
<div>
|
||||
<label for="public_transport_employee_amount" class="oe_inline"/>
|
||||
<field name="public_transport_employee_amount" class="oe_inline"/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="public_transport_reimbursed_amount" class="oe_inline"/>
|
||||
<field name="public_transport_reimbursed_amount" class="oe_inline"/>
|
||||
</div>
|
||||
</div>
|
||||
<label string="Other" attrs="{'invisible': [('transport_mode', '!=', 'others')]}"/>
|
||||
<div attrs="{'invisible': [('transport_mode', '!=', 'others')]}" class="o_row">
|
||||
<field name="others_reimbursed_amount" class="oe_inline" nolabel="1"/>
|
||||
<span>/ month</span>
|
||||
</div>
|
||||
<label for="internet"/>
|
||||
<div class="o_row">
|
||||
<field name="internet" nolabel="1"/>
|
||||
<span>/ month</span>
|
||||
</div>
|
||||
<label for="mobile"/>
|
||||
<div class="o_row">
|
||||
<field name="mobile" nolabel="1"/>
|
||||
<span>/ month</span>
|
||||
</div>
|
||||
<label for="mobile_plus"/>
|
||||
<div class="o_row">
|
||||
<field name="mobile_plus" nolabel="1"/>
|
||||
<span>/ month</span>
|
||||
</div>
|
||||
</group>
|
||||
<group string="Yearly Advantages">
|
||||
<label for="eco_checks"/>
|
||||
<div class="o_row">
|
||||
<field name="eco_checks" nolabel="1"/>
|
||||
<span>/ year</span>
|
||||
</div>
|
||||
<label for="thirteen_month"/>
|
||||
<div class="o_row">
|
||||
<field name="thirteen_month" nolabel="1"/>
|
||||
<span>/ year</span>
|
||||
</div>
|
||||
<label for="double_holidays"/>
|
||||
<div class="o_row">
|
||||
<field name="double_holidays" nolabel="1"/>
|
||||
<span>/ year</span>
|
||||
</div>
|
||||
<label for="holidays"/>
|
||||
<div class="o_row">
|
||||
<field name="holidays" nolabel="1"/>
|
||||
<span>/ year</span>
|
||||
</div>
|
||||
<field name="holidays_compensation" invisible="1"/>
|
||||
</group>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_employee_form__l10n_be_view_for" model="ir.ui.view">
|
||||
<field name="name">hr.employee.inherit.form</field>
|
||||
@@ -29,7 +126,6 @@
|
||||
<field name="inherit_id" ref="hr_contract.hr_hr_employee_view_form2"/>
|
||||
<field name="priority">30</field>
|
||||
<field name="arch" type="xml">
|
||||
<data>
|
||||
<xpath expr="//field[@name='vehicle_distance']" position="after">
|
||||
<field name="resident_bool" eval="False"/>
|
||||
</xpath>
|
||||
@@ -37,11 +133,23 @@
|
||||
<field name="spouse_fiscal_status" attrs="{'invisible':[('marital','!=','married')],'required':[('marital','=','married')]}" colspan="1" help="if spouse has professionnel income or not"/>
|
||||
<field name="disabled_spouse_bool" attrs="{'invisible':[('marital','!=','married')]}" colspan="1"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='gender']" position="after">
|
||||
<field name="disabled"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='children']" position="after">
|
||||
<field name="disabled_children_bool"/>
|
||||
<field name="disabled_children_number" attrs="{'invisible':[('disabled_children_bool','!=',True)]}" colspan="1" help="number of dependent children declared as disabled"/>
|
||||
<field name="dependent_children" invisible="1"/>
|
||||
<field name="other_dependent_people"/>
|
||||
<field name="other_senior_dependent" attrs="{'invisible':[('other_dependent_people','!=',True)]}"/>
|
||||
<field name="other_disabled_senior_dependent" attrs="{'invisible':[('other_dependent_people','!=',True)]}"/>
|
||||
<field name="other_juniors_dependent" attrs="{'invisible':[('other_dependent_people','!=',True)]}"/>
|
||||
<field name="other_disabled_juniors_dependent" attrs="{'invisible':[('other_dependent_people','!=',True)]}"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='spouse_fiscal_status']" position="after">
|
||||
<field name="spouse_net_revenue" attrs="{'invisible': ['|', ('marital', '!=', 'married'), ('spouse_fiscal_status', '!=', 'with income')]}"/>
|
||||
<field name="spouse_other_net_revenue" attrs="{'invisible': ['|', ('marital', '!=', 'married'), ('spouse_fiscal_status', '!=', 'with income')]}"/>
|
||||
</xpath>
|
||||
</data>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import models
|
||||
@@ -0,0 +1,16 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
{
|
||||
'name': 'Belgium - Payroll - Fleet',
|
||||
'category': 'Localization',
|
||||
'depends': ['l10n_be_hr_payroll', 'fleet'],
|
||||
'description': """
|
||||
""",
|
||||
'data': [
|
||||
'views/fleet_views.xml',
|
||||
'views/fleet_config_settings_views.xml',
|
||||
'views/hr_contract_views.xml',
|
||||
],
|
||||
'auto_install': True,
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import fleet
|
||||
import fleet_config_settings
|
||||
import hr_contract
|
||||
@@ -0,0 +1,150 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import babel.dates
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
from odoo.fields import Datetime
|
||||
|
||||
|
||||
class FleetVehicle(models.Model):
|
||||
_inherit = 'fleet.vehicle'
|
||||
|
||||
co2_fee = fields.Float(compute='_compute_co2_fee', string="CO2 fee (monthly)")
|
||||
total_depreciated_cost = fields.Float(compute='_compute_total_depreciated_cost',
|
||||
string="Total Depreciated Cost", help="This includes all the depreciated costs and the CO2 fee")
|
||||
total_cost = fields.Float(compute='_compute_total_cost', string="Total Cost", help="This include all the costs and the CO2 fee")
|
||||
fuel_type = fields.Selection(required=True, default='diesel')
|
||||
atn = fields.Float(compute='_compute_car_atn')
|
||||
acquisition_date = fields.Date(required=True)
|
||||
|
||||
@api.depends('co2_fee', 'log_contracts', 'log_contracts.state', 'log_contracts.recurring_cost_amount_depreciated')
|
||||
def _compute_total_depreciated_cost(self):
|
||||
for car in self:
|
||||
car.total_depreciated_cost = car.co2_fee + \
|
||||
sum(car.log_contracts.filtered(
|
||||
lambda contract: contract.state == 'open'
|
||||
).mapped('recurring_cost_amount_depreciated'))
|
||||
|
||||
@api.depends('co2_fee', 'log_contracts', 'log_contracts.state', 'log_contracts.cost_generated')
|
||||
def _compute_total_cost(self):
|
||||
for car in self:
|
||||
car.total_cost = car.co2_fee
|
||||
contracts = car.log_contracts.filtered(
|
||||
lambda contract: contract.state == 'open' and contract.cost_frequency != 'no'
|
||||
)
|
||||
for contract in contracts:
|
||||
if contract.cost_frequency == "daily":
|
||||
car.total_cost += contract.cost_generated * 30.0
|
||||
elif contract.cost_frequency == "weekly":
|
||||
car.total_cost += contract.cost_generated * 4.0
|
||||
elif contract.cost_frequency == "monthly":
|
||||
car.total_cost += contract.cost_generated
|
||||
elif contract.cost_frequency == "yearly":
|
||||
car.total_cost += contract.cost_generated / 12.0
|
||||
|
||||
def _get_co2_fee(self, co2):
|
||||
return max((((co2 * 9.0) - 600.0) * 1.2488) / 12.0, 0.0)
|
||||
|
||||
@api.depends('co2')
|
||||
def _compute_co2_fee(self):
|
||||
for car in self:
|
||||
car.co2_fee = self._get_co2_fee(car.co2)
|
||||
|
||||
@api.depends('fuel_type', 'car_value', 'acquisition_date')
|
||||
def _compute_car_atn(self):
|
||||
for car in self:
|
||||
car.atn = car._get_car_atn(car.acquisition_date, car.car_value, car.fuel_type, car.co2)
|
||||
|
||||
@api.depends('model_id', 'license_plate', 'total_depreciated_cost', 'acquisition_date')
|
||||
def _compute_vehicle_name(self):
|
||||
super(FleetVehicle, self)._compute_vehicle_name()
|
||||
for vehicle in self:
|
||||
acquisition_date = self._get_acquisition_date()
|
||||
vehicle.name += u" \u2022 " + str(round(vehicle.total_depreciated_cost, 2)) + u" \u2022 " + acquisition_date
|
||||
|
||||
def _get_acquisition_date(self):
|
||||
self.ensure_one()
|
||||
return babel.dates.format_date(
|
||||
date=Datetime.from_string(self.acquisition_date),
|
||||
format='MMMM y',
|
||||
locale=self._context.get('lang', 'en_US')
|
||||
)
|
||||
|
||||
def _get_car_atn(self, acquisition_date, car_value, fuel_type, co2):
|
||||
# Compute the correction coefficient from the age of the car
|
||||
now = Datetime.from_string(Datetime.now())
|
||||
start = Datetime.from_string(acquisition_date)
|
||||
if start:
|
||||
number_of_month = (now.year - start.year) * 12.0 + now.month - start.month + int(bool(now.day - start.day + 1))
|
||||
if number_of_month <= 12:
|
||||
age_coefficient = 1.00
|
||||
elif number_of_month <= 24:
|
||||
age_coefficient = 0.94
|
||||
elif number_of_month <= 36:
|
||||
age_coefficient = 0.88
|
||||
elif number_of_month <= 48:
|
||||
age_coefficient = 0.82
|
||||
elif number_of_month <= 60:
|
||||
age_coefficient = 0.76
|
||||
else:
|
||||
age_coefficient = 0.70
|
||||
car_value = car_value * age_coefficient
|
||||
# Compute atn value from corrected car_value
|
||||
magic_coeff = 6.0 / 7.0 # Don't ask me why
|
||||
if fuel_type == 'electric':
|
||||
atn = 0.0
|
||||
else:
|
||||
if fuel_type in ['diesel', 'hybrid']:
|
||||
reference = 87.0
|
||||
else:
|
||||
reference = 105.0
|
||||
|
||||
if co2 <= reference:
|
||||
atn = car_value * max(0.04, (0.055 - 0.001 * (reference - co2))) * magic_coeff
|
||||
else:
|
||||
atn = car_value * min(0.18, (0.055 + 0.001 * (co2 - reference))) * magic_coeff
|
||||
return max(1280, atn) / 12.0
|
||||
|
||||
|
||||
class FleetVehicleLogContract(models.Model):
|
||||
_inherit = 'fleet.vehicle.log.contract'
|
||||
|
||||
recurring_cost_amount_depreciated = fields.Float("Recurring Cost Amount (depreciated)")
|
||||
|
||||
class FleetVehicleModel(models.Model):
|
||||
_inherit = 'fleet.vehicle.model'
|
||||
|
||||
default_recurring_cost_amount_depreciated = fields.Float(string="Recurring Cost (Depreciated)",
|
||||
help="Default recurring cost amount that should be applied to a new car from this model")
|
||||
default_co2 = fields.Float(string="CO2 emissions")
|
||||
default_fuel_type = fields.Selection([('gasoline', 'Gasoline'), ('diesel', 'Diesel'), ('electric', 'Electric'), ('hybrid', 'Hybrid')], 'Fuel Type', help='Fuel Used by the vehicle')
|
||||
default_car_value = fields.Float(string="Catalog Value (VAT Incl.)")
|
||||
can_be_requested = fields.Boolean(string="Can be requested", help="Can be requested on a contract as a new car")
|
||||
default_atn = fields.Float(compute='_compute_atn', string="ATN")
|
||||
default_total_depreciated_cost = fields.Float(compute='_compute_default_total_depreciated_cost')
|
||||
|
||||
@api.depends('default_car_value', 'default_co2', 'default_fuel_type')
|
||||
def _compute_atn(self):
|
||||
now = Datetime.now()
|
||||
for model in self:
|
||||
model.default_atn = self.env['fleet.vehicle']._get_car_atn(now, model.default_car_value, model.default_fuel_type, model.default_co2)
|
||||
|
||||
@api.depends('default_co2', 'default_recurring_cost_amount_depreciated')
|
||||
def _compute_default_total_depreciated_cost(self):
|
||||
for model in self:
|
||||
model.default_total_depreciated_cost = model.default_co2 + model.default_recurring_cost_amount_depreciated
|
||||
|
||||
@api.multi
|
||||
@api.depends('name', 'brand_id')
|
||||
def name_get(self):
|
||||
res = super(FleetVehicleModel, self).name_get()
|
||||
new_res = []
|
||||
for res_item in res:
|
||||
model = self.browse(res_item[0])
|
||||
if model.default_total_depreciated_cost != 0.0:
|
||||
new_res.append((res_item[0], res_item[1] + u" \u2022 " + str(model.default_total_depreciated_cost)))
|
||||
else:
|
||||
new_res.append(res_item)
|
||||
return new_res
|
||||
@@ -0,0 +1,22 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class FleetConfigSettings(models.TransientModel):
|
||||
_name = 'fleet.config.settings'
|
||||
_inherit = ['res.config.settings']
|
||||
|
||||
max_unused_cars = fields.Integer(string='Maximum unused cars')
|
||||
|
||||
@api.multi
|
||||
def set_default_max_unused_cars(self):
|
||||
Param = self.env['ir.config_parameter'].sudo()
|
||||
Param.set_param("l10n_be_hr_payroll_fleet.max_unused_cars", self.max_unused_cars)
|
||||
|
||||
@api.model
|
||||
def get_default_max_unused_cars(self, fields):
|
||||
params = self.env['ir.config_parameter'].sudo()
|
||||
max_unused_cars = params.get_param('l10n_be_hr_payroll_fleet.max_unused_cars', default=3)
|
||||
return dict(max_unused_cars=int(max_unused_cars))
|
||||
@@ -0,0 +1,52 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class HrContract(models.Model):
|
||||
_inherit = 'hr.contract'
|
||||
|
||||
car_id = fields.Many2one('fleet.vehicle', string='Car',
|
||||
domain=lambda self: self._get_available_cars_domain(),
|
||||
default=lambda self: self.env['fleet.vehicle'].search([('driver_id', '=', self.employee_id.address_home_id.id)], limit=1))
|
||||
car_atn = fields.Float(compute='_compute_car_atn_and_costs', string='ATN Company Car')
|
||||
company_car_total_depreciated_cost = fields.Float(compute='_compute_car_atn_and_costs')
|
||||
available_cars_amount = fields.Integer(compute='_compute_available_cars_amount', string='Number of available cars')
|
||||
new_car = fields.Boolean('Request a new car')
|
||||
new_car_model_id = fields.Many2one('fleet.vehicle.model', string="Model", domain=lambda self: self._get_possible_model_domain())
|
||||
max_unused_cars = fields.Integer(compute='_compute_max_unused_cars')
|
||||
|
||||
@api.depends('car_id', 'new_car', 'new_car_model_id')
|
||||
def _compute_car_atn_and_costs(self):
|
||||
for contract in self:
|
||||
if contract.car_id:
|
||||
contract.car_atn = contract.car_id.atn
|
||||
contract.company_car_total_depreciated_cost = contract.car_id.total_depreciated_cost
|
||||
elif contract.new_car and contract.new_car_model_id:
|
||||
contract.car_atn = contract.new_car_model_id.default_atn
|
||||
contract.company_car_total_depreciated_cost = contract.new_car_model_id.default_total_depreciated_cost
|
||||
|
||||
@api.depends('name')
|
||||
def _compute_available_cars_amount(self):
|
||||
for contract in self:
|
||||
contract.available_cars_amount = self.env['fleet.vehicle'].search_count([('driver_id', '=', False)])
|
||||
|
||||
@api.depends('name')
|
||||
def _compute_max_unused_cars(self):
|
||||
params = self.env['ir.config_parameter'].sudo()
|
||||
max_unused_cars = params.get_param('l10n_be_hr_payroll_fleet.max_unused_cars', default=3)
|
||||
for contract in self:
|
||||
contract.max_unused_cars = int(max_unused_cars)
|
||||
|
||||
@api.onchange('employee_id')
|
||||
def _onchange_employee_id(self):
|
||||
super(HrContract, self)._onchange_employee_id()
|
||||
self.car_id = self.env['fleet.vehicle'].search([('driver_id', '=', self.employee_id.address_home_id.id)], limit=1)
|
||||
return {'domain': {'car_id': self._get_available_cars_domain()}}
|
||||
|
||||
def _get_available_cars_domain(self):
|
||||
return ['|', ('driver_id', '=', False), ('driver_id', '=', self.employee_id.address_home_id.id)]
|
||||
|
||||
def _get_possible_model_domain(self):
|
||||
return [('can_be_requested', '=', True)]
|
||||
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="fleet_config_settings_view_form" model="ir.ui.view">
|
||||
<field name="name">fleet.config.settings.form</field>
|
||||
<field name="model">fleet.config.settings</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Configure Fleet" class="oe_form_configuration">
|
||||
<header>
|
||||
<button string="Save" type="object" name="execute" class="oe_highlight"/>
|
||||
<button string="Cancel" type="object" name="cancel" class="oe_link"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<h2>Fleet Management</h2>
|
||||
<div class="row mt16 o_settings_container">
|
||||
<div class="col-xs-12 col-md-6 o_setting_box">
|
||||
<div class="o_setting_left_pane"/>
|
||||
<div class="o_setting_right_pane">
|
||||
<label string="New Car Request"/>
|
||||
<div class="text-muted o_row">
|
||||
<span>An employee cannot request a new car there are more than </span>
|
||||
<field name="max_unused_cars"/>
|
||||
<span> existing vehicles availables.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="fleet_config_settings_action" model="ir.actions.act_window">
|
||||
<field name="name">Configure Fleet</field>
|
||||
<field name="res_model">fleet.config.settings</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">inline</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="fleet_config_settings_menu" name="Settings"
|
||||
parent="fleet.fleet_configuration" sequence="0" action="fleet_config_settings_action"
|
||||
groups="base.group_system"/>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1,69 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="fleet_vehicle_view_form" model="ir.ui.view">
|
||||
<field name="name">fleet.vehicle.form</field>
|
||||
<field name="model">fleet.vehicle</field>
|
||||
<field name="inherit_id" ref="fleet.fleet_vehicle_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[@name='co2']" position="after">
|
||||
<field name="co2_fee"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='car_value']" position="after">
|
||||
<label for="total_depreciated_cost"/>
|
||||
<div class="o_row">
|
||||
<field name="total_depreciated_cost" nolabel="1"/>
|
||||
<span>/ month</span>
|
||||
</div>
|
||||
<label for="total_cost"/>
|
||||
<div class="o_row">
|
||||
<field name="total_cost" nolabel="1"/>
|
||||
<span>/ month</span>
|
||||
</div>
|
||||
<field name="atn"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="fleet_vehicle_log_contract_view_form" model="ir.ui.view">
|
||||
<field name="name">fleet.vehicle.log.contract.form</field>
|
||||
<field name="model">fleet.vehicle.log.contract</field>
|
||||
<field name="inherit_id" ref="fleet.fleet_vehicle_log_contract_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='amount']" position="after">
|
||||
<field name="recurring_cost_amount_depreciated"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="fleet_vehicle_model_view_form" model="ir.ui.view">
|
||||
<field name="name">fleet.vehicle.model.form</field>
|
||||
<field name="model">fleet.vehicle.model</field>
|
||||
<field name="inherit_id" ref="fleet.fleet_vehicle_model_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//notebook" position="before">
|
||||
<group>
|
||||
<group>
|
||||
<field name="can_be_requested"/>
|
||||
</group>
|
||||
</group>
|
||||
</xpath>
|
||||
<xpath expr="//page[@name='vendors']" position="after">
|
||||
<page string="Information">
|
||||
<group>
|
||||
<group>
|
||||
<field name="default_car_value"/>
|
||||
<field name="default_co2"/>
|
||||
<field name="default_fuel_type" widget="selection"/>
|
||||
<field name="default_recurring_cost_amount_depreciated"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="default_atn"/>
|
||||
<field name="default_total_depreciated_cost" invisible="1"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="hr_contract_view_form" model="ir.ui.view">
|
||||
<field name="name">hr.contract.form"</field>
|
||||
<field name="model">hr.contract</field>
|
||||
<field name="inherit_id" ref="l10n_be_hr_payroll.hr_contract_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='transport_mode']" position="after">
|
||||
<label for="car_id" attrs="{'invisible': [('transport_mode', '!=', 'company_car')]}"/>
|
||||
<div attrs="{'invisible': [('transport_mode', '!=', 'company_car')]}">
|
||||
<field name="car_id" nolabel="1" attrs="{'invisible': [('new_car', '=', True)]}"/>
|
||||
<field name="available_cars_amount" invisible="1"/>
|
||||
<div>
|
||||
<field name="new_car" attrs="{'invisible': [('available_cars_amount', '>', 3)]}" nolabel="1"/>
|
||||
<label for="new_car"/>
|
||||
</div>
|
||||
<div attrs="{'invisible': [('new_car', '=', False)]}" class="oe_inline">
|
||||
<label for="new_car_model_id" class="oe_inline"/>
|
||||
<field name="new_car_model_id" class="oe_inline"/>
|
||||
</div>
|
||||
</div>
|
||||
<field name="max_unused_cars" invisible="1"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='car_atn']" position="after">
|
||||
<field name="company_car_total_depreciated_cost" invisible="1"/>
|
||||
</xpath>
|
||||
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user