[REF] hr_holidays: simplify leave type configuration

Purpose of this commit is to simplify the configuration of leave type and
use the job of HR officers / manager by making options clearer and easier
to use.

Specifications for leave type :

 * display name as header;
 * remove accrual option from leave type. It is now configured directly
   on allocation requests;
 * add a "X days allocated" stmart button. If you set a general allocation
   of 20 days, the stat button will display 20 days. Even if you go update
   the allocation by employee after that. Purpose is to have the value of
   main allocation done in group, not individual cases of some employees;
 * add a "Group leaves" stmart button. It leads to a new leave requests
   in group mode where mode is visible;
 * have a unique allocation type field replacing the setting allowing to make
   leave or allocation as well as limit and negative fields;
 * limit leave duration encoding to two options, either day-based (allowing to
   take full days or half a day), either hours-based (allowing to take full
   days, half a day or some specific hours in a given day);

More technically

 * limit and negative fields are removed. It was confusing as limit set to
   True means you can actually exceed the limit). It can be computed based
   on allocation_type ('no' meaning you can bypass the limit);
 * request_unit is simplified. Day and half are merged into day;
 * accrual and balance limit are moved to allocation model;

Specifications for allocation requests

 * set accrual specific on allocation itself and not coming from the leave
   type configuration anymore. Indeed we now consider accrual to be done
   case by case independently from the leave type;
 * remove negative and balance_limit. Indeed we now consider people have to
   check by hand what is done by accrual;

Leave and allocation are slightly impacted by other changes, such as domain
for default leave types. Changes have been made as small as possible. Future
commits will still improve leave and allocation models and views, going
further in the cleaning process. Some tests are udpated to match the new
configuration of leave type.

This commit is linked to task ID 1876795 and PR #26571.
This commit is contained in:
Thibault Delavallée
2018-09-10 13:08:53 +02:00
parent 2e3b392a6f
commit bd5b2c9466
15 changed files with 221 additions and 218 deletions
+9 -5
View File
@@ -1,11 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<data noupdate="0">
<!-- Casual leave -->
<record id="holiday_status_cl" model="hr.leave.type">
<field name="name" eval="'Legal Leaves '+time.strftime('%Y')"/>
<field name="limit" eval="False"/>
<field name="allocation_type">fixed</field>
<field name="color_name">black</field>
<field name="validity_start" eval="time.strftime('%Y-01-01')"/>
<field name="validity_stop" eval="time.strftime('%Y-12-31')"/>
@@ -14,14 +13,16 @@
<!-- Sick leave -->
<record id="holiday_status_sl" model="hr.leave.type">
<field name="name">Sick Leaves</field>
<field name="limit" eval="True"/>
<field name="allocation_type">no</field>
<field name="color_name">red</field>
<field name="validity_start" eval="time.strftime('%Y-01-01')"/>
</record>
<!-- Compensatory Days -->
<record id="holiday_status_comp" model="hr.leave.type">
<field name="name">Compensatory Days</field>
<field name="limit" eval="False"/>
<field name="allocation_type">fixed_allocation</field>
<field name="request_unit">hour</field>
<field name="color_name">lavender</field>
<field name="validity_start" eval="time.strftime('%Y-01-01')"/>
<field name="validity_stop" eval="time.strftime('%Y-12-31')"/>
@@ -30,8 +31,11 @@
<!--Unpaid Leave -->
<record id="holiday_status_unpaid" model="hr.leave.type">
<field name="name">Unpaid</field>
<field name="allocation_type">fixed_allocation</field>
<field name="validation_type">both</field>
<field name="color_name">brown</field>
<field name="unpaid" eval="True"/>
<field name="validity_start" eval="time.strftime('%Y-01-01')"/>
</record>
</data>
</odoo>
+3 -4
View File
@@ -4,7 +4,7 @@
<record id="hr_holiday_status_hl" model="hr.leave.type">
<field name="name">Overtime Compensation</field>
<field name="limit" eval="False"/>
<field name="allocation_type">fixed_allocation</field>
<field name="color_name">blue</field>
<field name="validity_start" eval="time.strftime('%Y-01-01')"/>
<field name="validity_stop" eval="time.strftime('%Y-12-31')"/>
@@ -13,18 +13,17 @@
<record id="hr_holiday_status_dv" model="hr.leave.type">
<field name="name">Parental Leaves</field>
<field name="limit" eval="True"/>
<field name="allocation_type">fixed</field>
<field name="color_name">brown</field>
<field name="validation_type">both</field>
</record>
<record id="holiday_status_cla" model="hr.leave.type">
<field name="name" eval="'Paid Time Off ' + time.strftime('%Y')"/>
<field name="limit" eval="False"/>
<field name="allocation_type">fixed</field>
<field name="color_name">wheat</field>
<field name="validity_start" eval="time.strftime('%Y-01-01')"/>
<field name="validity_stop" eval="time.strftime('%Y-12-31')"/>
<field name="accrual" eval="True"/>
</record>
<record id="hr_holidays_employee1_allocation_cl" model="hr.leave.allocation">
+1 -2
View File
@@ -104,7 +104,7 @@ class Employee(models.Model):
join hr_leave_type s ON (s.id=h.holiday_status_id)
WHERE
h.state='validate' AND
s.limit=False AND
(s.allocation_type='fixed' OR s.allocation_type='fixed_allocation') AND
h.employee_id in %s
GROUP BY h.employee_id""", (tuple(self.ids),))
return dict((row['employee_id'], row['days']) for row in self._cr.dictfetchall())
@@ -142,7 +142,6 @@ class Employee(models.Model):
def _compute_leaves_count(self):
all_leaves = self.env['hr.leave.report'].read_group([
('employee_id', 'in', self.ids),
('holiday_status_id.limit', '=', False),
('state', '=', 'validate')
], fields=['number_of_days', 'employee_id'], groupby=['employee_id'])
mapping = dict([(leave['employee_id'][0], leave['number_of_days']) for leave in all_leaves])
+13 -36
View File
@@ -59,17 +59,12 @@ class HolidaysRequest(models.Model):
_inherit = ['mail.thread', 'mail.activity.mixin']
_mail_post_access = 'read'
def _default_domain_holiday_status(self):
if self.user_has_groups('hr_holidays.group_hr_holidays_manager'):
return [('valid', '=', True)]
return [('valid', '=', True), ('employee_applicability', 'in', ['leave', 'both'])]
@api.model
def default_get(self, fields_list):
defaults = super(HolidaysRequest, self).default_get(fields_list)
LeaveType = self.env['hr.leave.type'].with_context(employee_id=defaults.get('employee_id'), default_date_from=defaults.get('date_from', fields.Datetime.now()))
lt = LeaveType.search(self._default_domain_holiday_status())
lt = LeaveType.search([('valid', '=', True)])
defaults['holiday_status_id'] = lt[0].id if len(lt) > 0 else defaults.get('holiday_status_id')
return defaults
@@ -103,7 +98,7 @@ class HolidaysRequest(models.Model):
holiday_status_id = fields.Many2one(
"hr.leave.type", string="Leave Type", required=True, readonly=True,
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]},
domain=lambda self: self._default_domain_holiday_status())
domain=[('valid', '=', True)])
leave_type_request_unit = fields.Selection(related='holiday_status_id.request_unit', readonly=True)
validation_type = fields.Selection('Validation Type', related='holiday_status_id.validation_type')
# HR data
@@ -156,13 +151,9 @@ class HolidaysRequest(models.Model):
request_date_to_period = fields.Selection([
('am', 'Morning'), ('pm', 'Afternoon')],
string="Date Period End", default='pm')
# request type + duplicate because we cannot hide some entries of a selection field
request_unit_all = fields.Selection([('half', 'Half-day'),
('day', '1 Day'),
('period', 'Period')], default='day')
request_unit_day = fields.Selection([('day', '1 Day'),
('period', 'Period')], default='day')
# request type
request_unit = fields.Selection([('day', '1 Day'),
('period', 'Period')], default='day')
_sql_constraints = [
('type_value', "CHECK( (holiday_type='employee' AND employee_id IS NOT NULL) or (holiday_type='category' AND category_id IS NOT NULL) or (holiday_type='department' AND department_id IS NOT NULL) )",
"The employee, department or employee category of this request is missing. Please make sure that your user login is linked to an employee."),
@@ -170,7 +161,7 @@ class HolidaysRequest(models.Model):
('date_check', "CHECK ( number_of_days_temp >= 0 )", "If you want to change the number of days you should use the 'period' mode"),
]
@api.onchange('request_unit_all', 'request_date_from_period', 'request_date_to_period',
@api.onchange('request_unit', 'request_date_from_period', 'request_date_to_period',
'holiday_status_id', 'request_date_from', 'request_date_to', 'employee_id')
def _onchange_request_parameters(self):
date_from = False
@@ -205,7 +196,7 @@ class HolidaysRequest(models.Model):
first_day = fields.Date.from_string(date_from)
last_day = fields.Date.from_string(date_to)
if self.request_unit_all in ['day', 'half']:
if self.request_unit in ['day']:
last_day = first_day
# find first attendance coming after first_day
@@ -213,19 +204,11 @@ class HolidaysRequest(models.Model):
# find last attendance coming before last_day
attendance_to = next((att for att in reversed(attendances) if int(att.dayofweek) <= last_day.weekday()), attendances[-1])
if self.request_unit_all == 'day' or (self.request_unit_all == 'period' and self.leave_type_request_unit == 'day'):
if self.request_unit == 'day' or (self.request_unit == 'period' and self.leave_type_request_unit == 'day'):
hour_from = float_to_time(attendance_from.hour_from)
hour_to = float_to_time(attendance_to.hour_to)
elif self.request_unit_all == 'half':
hour_from = float_to_time(attendance_from.hour_from if self.request_date_from_period == 'am' else attendance_to.hour_from)
hour_to = float_to_time(attendance_from.hour_to if self.request_date_from_period == 'am' else attendance_to.hour_to)
elif self.request_unit_all == 'period' and self.leave_type_request_unit == 'half':
hour_from = float_to_time(attendance_from.hour_from if self.request_date_from_period == 'am' else attendance_from.hour_to)
hour_to = float_to_time(attendance_to.hour_from if self.request_date_to_period == 'am' else attendance_to.hour_to)
if self.leave_type_request_unit == 'hour' and self.request_unit_all == 'period':
if self.request_unit == 'period' and self.leave_type_request_unit == 'hour':
date_from = fields.Datetime.from_string(date_from)
date_to = fields.Datetime.from_string(date_to)
else:
@@ -236,16 +219,12 @@ class HolidaysRequest(models.Model):
self.date_from = date_from
self.date_to = date_to
if not (self.leave_type_request_unit == 'hour' and self.request_unit_all == 'period'):
if not (self.request_unit == 'period' and self.leave_type_request_unit == 'hour'):
date_from = date_from
date_to = date_to
self.number_of_days_temp = self._get_number_of_days(date_from, date_to, self.employee_id.id)
@api.onchange('request_unit_day')
def _onchange_request_unit_day(self):
self.request_unit_all = self.request_unit_day
@api.onchange('holiday_type')
def _onchange_type(self):
if self.holiday_type == 'employee' and not self.employee_id:
@@ -276,8 +255,7 @@ class HolidaysRequest(models.Model):
self.request_date_to = date_to.date() if date_to else False
if (date_from and date_to) and (date_from.day < date_to.day):
self.request_unit_all = 'period'
self.request_unit_day = 'period'
self.request_unit = 'period'
# Compute and update the number of days
if (date_to and date_from) and (date_from <= date_to):
@@ -295,8 +273,7 @@ class HolidaysRequest(models.Model):
self.request_date_to = date_to.date() if date_to else False
if (date_from and date_to) and (date_from.day < date_to.day):
self.request_unit_all = 'period'
self.request_unit_day = 'period'
self.request_unit = 'period'
# Compute and update the number of days
if (date_to and date_from) and (date_from <= date_to):
@@ -362,7 +339,7 @@ class HolidaysRequest(models.Model):
@api.constrains('state', 'number_of_days_temp', 'holiday_status_id')
def _check_holidays(self):
for holiday in self:
if holiday.holiday_type != 'employee' or not holiday.employee_id or holiday.holiday_status_id.limit or holiday.holiday_status_id.negative:
if holiday.holiday_type != 'employee' or not holiday.employee_id or holiday.holiday_status_id.allocation_type == 'no':
continue
leave_days = holiday.holiday_status_id.get_days(holiday.employee_id.id)[holiday.holiday_status_id.id]
if float_compare(leave_days['remaining_leaves'], 0, precision_digits=2) == -1 or \
@@ -23,17 +23,11 @@ class HolidaysAllocation(models.Model):
_inherit = ['mail.thread', 'mail.activity.mixin']
_mail_post_access = 'read'
def _default_domain_holiday_status_id(self):
if self.user_has_groups('hr_holidays.group_hr_holidays_manager'):
return [('valid', '=', True), ('limit', '=', False)]
return [('valid', '=', True), ('employee_applicability', 'in', ['allocation', 'both']), ('limit', '=', False)]
def _default_employee(self):
return self.env.context.get('default_employee_id') or self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1)
def _default_holiday_status_id(self):
LeaveType = self.env['hr.leave.type'].with_context(employee_id=self._default_employee().id)
lt = LeaveType.search(self._default_domain_holiday_status_id())
lt = self.env['hr.leave.type'].with_context(employee_id=self._default_employee().id).search([('valid', '=', True)], limit=1)
return lt[:1]
name = fields.Char('Description')
@@ -58,7 +52,7 @@ class HolidaysAllocation(models.Model):
holiday_status_id = fields.Many2one(
"hr.leave.type", string="Leave Type", required=True, readonly=True,
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]},
domain=lambda self: self._default_domain_holiday_status_id(), default=_default_holiday_status_id)
domain=[('valid', '=', True)], default=_default_holiday_status_id)
employee_id = fields.Many2one(
'hr.employee', string='Employee', index=True, readonly=True,
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, default=_default_employee, track_visibility='onchange')
@@ -94,7 +88,10 @@ class HolidaysAllocation(models.Model):
'hr.employee.category', string='Employee Tag', readonly=True,
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, help='Category of Employee')
# accrual configuration
accrual = fields.Boolean("Accrual", related='holiday_status_id.accrual', store=True, readonly=True)
accrual = fields.Boolean(
"Accrual", readonly=True,
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]})
accrual_limit = fields.Integer('Balance limit', default=0, help="Maximum of allocation for accrual; 0 means no maximum.")
number_per_interval = fields.Float("Number of unit per interval", readonly=True, states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, default=1)
interval_number = fields.Integer("Number of unit between two intervals", readonly=True, states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, default=1)
unit_per_interval = fields.Selection([
@@ -171,8 +168,8 @@ class HolidaysAllocation(models.Model):
values['number_of_days_temp'] = holiday.number_of_days_temp + days_to_give * prorata
if holiday.holiday_status_id.balance_limit > 0:
values['number_of_days_temp'] = min(values['number_of_days_temp'], holiday.holiday_status_id.balance_limit)
if holiday.accrual_limit > 0:
values['number_of_days_temp'] = min(values['number_of_days_temp'], holiday.accrual_limit)
values['number_of_hours'] = values['number_of_days_temp'] * holiday.employee_id.resource_calendar_id.hours_per_day or HOURS_PER_DAY
@@ -186,8 +183,8 @@ class HolidaysAllocation(models.Model):
if holiday.type_request_unit == 'hour':
# In this case we need the number of days to reflect the number of hours taken
number_of_days = holiday.number_of_hours / holiday.employee_id.resource_calendar_id.hours_per_day or HOURS_PER_DAY
if holiday.holiday_status_id.balance_limit > 0:
number_of_days = min(number_of_days, holiday.holiday_status_id.balance_limit)
if holiday.accrual_limit > 0:
number_of_days = min(number_of_days, holiday.accrual_limit)
holiday.number_of_days = number_of_days
+70 -43
View File
@@ -3,6 +3,7 @@
# Copyright (c) 2005-2006 Axelor SARL. (http://www.axelor.com)
import datetime
import logging
from odoo import api, fields, models
@@ -42,7 +43,6 @@ class HolidaysType(models.Model):
('wheat', 'Wheat'),
('ivory', 'Ivory')], string='Color in Report', required=True, default='red',
help='This color will be used in the leaves summary located in Reporting > Leaves by Department.')
limit = fields.Boolean('Exceed Allocation', help="If you select this check box, the system will allow the employees to ask for leaves without allocating some beforehand")
active = fields.Boolean('Active', default=True,
help="If the active field is set to false, it will allow you to hide the leave type without removing it.")
max_leaves = fields.Float(compute='_compute_leaves', string='Maximum Allowed',
@@ -56,17 +56,23 @@ class HolidaysType(models.Model):
virtual_remaining_leaves = fields.Float(
compute='_compute_leaves', string='Virtual Remaining Leaves',
help='Maximum Leaves Allowed - Leaves Already Taken - Leaves Waiting Approval')
group_days_allocation = fields.Float(
compute='_compute_group_days_allocation', string='Days Allocated')
group_days_leave = fields.Float(
compute='_compute_group_days_leave', string='Group Leaves')
company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.user.company_id)
validation_type = fields.Selection([
('hr', 'Human Resource officer'),
('manager', 'Employee Manager'),
('both', 'Double Validation')], default='hr', string='Validation By')
employee_applicability = fields.Selection([
('both', 'Both'),
('leave', 'Can be requested'),
('allocation', 'Can be allocated')],
default=lambda self: 'leave' if self.limit else 'both',
string='Mode', help='This leave type will be available on Leave / Allocation request based on selected value')
allocation_type = fields.Selection([
('fixed', 'Fixed by HR'),
('fixed_allocation', 'Fixed by HR + allocation request'),
('no', 'No allocation')],
default='fixed', string='Mode',
help='\tFixed by HR: allocated by HR and cannot be bypassed; users can request leaves;'
'\tFixed by HR + allocation request: allocated by HR and users can request leaves and allocations;'
'\tNo allocation: no allocation by default, users can freely request leaves;')
validity_start = fields.Date("Start Date", default=fields.Date.today,
help='Adding validity to types of leaves so that it cannot be selected outside this time period')
validity_stop = fields.Date("End Date")
@@ -74,18 +80,9 @@ class HolidaysType(models.Model):
time_type = fields.Selection([('leave', 'Leave'), ('other', 'Other')], default='leave', string="Kind of Leave",
help="Whether this should be computed as a holiday or as work time (eg: formation)")
request_unit = fields.Selection([
('day', 'Day'), ('half', 'Half-day'), ('hour', 'Hours')],
('day', 'Day'), ('hour', 'Hours')],
default='day', string='Take Leaves in', required=True)
accrual = fields.Boolean('Accrual', default=False,
help='This option forces this type of leave to be allocated accrually')
unpaid = fields.Boolean('Is Unpaid', default=False)
negative = fields.Boolean('Allow Negative', help="This option allows to take more leaves than allocated")
balance_limit = fields.Float('Max Balance Limit', default=0, help="The maximum quantity of allocated days on this allocation, zero meaning infinite amount")
_sql_constraints = [
('no_negative_balance_limit', "CHECK(balance_limit >= 0)", "The max balance limit cannot be negative"),
('no_accrual_unpaid', 'CHECK(NOT (accrual AND unpaid))', "A leave type cannot be accrual and considered as unpaid leaves")
]
@api.multi
@api.constrains('validity_start', 'validity_stop')
@@ -96,29 +93,7 @@ class HolidaysType(models.Model):
raise ValidationError(_("End of validity period should be greater than start of validity period"))
@api.multi
@api.constrains('balance_limit', 'accrual')
def _check_balance_limit(self):
for leave_type in self:
if not leave_type.accrual and leave_type.balance_limit > 0:
raise ValidationError(_("Max balance limit can only be set for accrual leaves"))
@api.onchange('limit')
def _onchange_limit(self):
if self.limit:
self.employee_applicability = 'leave'
self.accrual = False
@api.onchange('accrual')
def _onchange_accrual(self):
if self.accrual:
self.limit = False
self.employee_applicability = 'both'
else:
self.negative = False
self.balance_limit = 0
@api.multi
@api.depends('validity_start', 'validity_stop', 'limit')
@api.depends('validity_start', 'validity_stop')
def _compute_valid(self):
dt = self._context.get('default_date_from') or fields.Datetime.now()
@@ -193,6 +168,30 @@ class HolidaysType(models.Model):
holiday_status.remaining_leaves = result.get('remaining_leaves', 0)
holiday_status.virtual_remaining_leaves = result.get('virtual_remaining_leaves', 0)
@api.multi
def _compute_group_days_allocation(self):
grouped_res = self.env['hr.leave.allocation'].read_group(
[('holiday_status_id', 'in', self.ids), ('holiday_type', '!=', 'employee'), ('state', '=', 'validate'),
('date_from', '>=', fields.Datetime.to_string(datetime.datetime.now().replace(month=1, day=1, hour=0, minute=0, second=0, microsecond=0)))],
['holiday_status_id', 'number_of_days_temp'],
['holiday_status_id'],
)
grouped_dict = dict((data['holiday_status_id'][0], data['number_of_days_temp']) for data in grouped_res)
for allocation in self:
allocation.group_days_allocation = grouped_dict.get(allocation.id, 0)
@api.multi
def _compute_group_days_leave(self):
grouped_res = self.env['hr.leave'].read_group(
[('holiday_status_id', 'in', self.ids), ('holiday_type', '=', 'employee'), ('state', '=', 'validate'),
('date_from', '>=', fields.Datetime.to_string(datetime.datetime.now().replace(month=1, day=1, hour=0, minute=0, second=0, microsecond=0)))],
['holiday_status_id'],
['holiday_status_id'],
)
grouped_dict = dict((data['holiday_status_id'][0], data['holiday_status_id_count']) for data in grouped_res)
for allocation in self:
allocation.group_days_leave = grouped_dict.get(allocation.id, 0)
@api.multi
def name_get(self):
if not self._context.get('employee_id'):
@@ -201,7 +200,7 @@ class HolidaysType(models.Model):
res = []
for record in self:
name = record.name
if not record.limit:
if record.allocation_type != 'no':
name = "%(name)s (%(count)s)" % {
'name': name,
'count': _('%g remaining out of %g') % (float_round(record.virtual_remaining_leaves, precision_digits=2) or 0.0, float_round(record.max_leaves, precision_digits=2) or 0.0)
@@ -214,7 +213,7 @@ class HolidaysType(models.Model):
""" Override _search to order the results, according to some employee.
The order is the following
- limit (limited leaves first, such as Legal Leaves)
- allocation fixed first, then allowing allocation, then free allocation
- virtual remaining leaves (higher the better, so using reverse on sorted)
This override is necessary because those fields are not stored and depends
@@ -225,6 +224,34 @@ class HolidaysType(models.Model):
leave_ids = super(HolidaysType, self)._search(args, offset=offset, limit=limit, order=order, count=count, access_rights_uid=access_rights_uid)
if not count and not order and self._context.get('employee_id'):
leaves = self.browse(leave_ids)
sort_key = lambda l: (not l.limit, l.virtual_remaining_leaves)
sort_key = lambda l: (l.allocation_type == 'fixed', l.allocation_type == 'fixed_allocation', l.virtual_remaining_leaves)
return leaves.sorted(key=sort_key, reverse=True).ids
return leave_ids
@api.multi
def action_see_days_allocated(self):
self.ensure_one()
action = self.env.ref('hr_holidays.hr_leave_allocation_action_all').read()[0]
action['domain'] = [
('holiday_type', '!=', 'employee'),
('holiday_status_id', '=', self.ids[0]),
('date_from', '>=', fields.Datetime.to_string(datetime.datetime.now().replace(month=1, day=1, hour=0, minute=0, second=0, microsecond=0)))
]
action['context'] = {
'default_holiday_type': 'department',
'default_holiday_status_id': self.ids[0],
}
return action
@api.multi
def action_see_group_leaves(self):
self.ensure_one()
action = self.env.ref('hr_holidays.hr_leave_action_all').read()[0]
action['domain'] = [
('holiday_status_id', '=', self.ids[0]),
('date_from', '>=', fields.Datetime.to_string(datetime.datetime.now().replace(month=1, day=1, hour=0, minute=0, second=0, microsecond=0)))
]
action['context'] = {
'default_holiday_status_id': self.ids[0],
}
return action
@@ -17,8 +17,8 @@ class TestLeavesRights(TestHrHolidaysBase):
super(TestLeavesRights, self).setUp()
self.leave_type = self.env['hr.leave.type'].create({
'name': 'Unlimited',
'limit': True,
'validation_type': 'hr',
'allocation_type': 'no',
})
self.rd_dept.manager_id = False
self.hr_dept.manager_id = False
@@ -316,9 +316,9 @@ class TestMultiCompany(TestHrHolidaysBase):
})
self.leave_type = self.env['hr.leave.type'].create({
'name': 'Unlimited - Company New',
'limit': True,
'company_id': self.new_company.id,
'validation_type': 'hr',
'allocation_type': 'no',
})
self.rd_dept.manager_id = False
self.hr_dept.manager_id = False
@@ -19,13 +19,12 @@ class TestAccrualAllocations(TestHrHolidaysBase):
self.accrual_type = LeaveType.create({
'name': 'accrual',
'limit': False,
'accrual': True,
'allocation_type': 'fixed',
})
self.unpaid_type = LeaveType.create({
'name': 'unpaid',
'limit': True,
'allocation_type': 'no',
'unpaid': True,
})
@@ -45,40 +44,30 @@ class TestAccrualAllocations(TestHrHolidaysBase):
""" % (newdate, id))
def test_accrual_base_no_leaves(self):
"""
Test if we can allocate some leaves accrually to an employee
"""
Allocation = self.env['hr.leave.allocation'].sudo(self.user_hrmanager_id).with_context(tracking_disable=True)
alloc_0 = Allocation.create({
""" Test if we can allocate some leaves accrually to an employee """
alloc = self.env['hr.leave.allocation'].sudo(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Accrual allocation for employee',
'employee_id': self.employee_emp_id,
'holiday_status_id': self.accrual_type.id,
'accrual': True,
'number_of_days_temp': 0,
'number_per_interval': 1,
'interval_number': 1,
'unit_per_interval': 'days',
'interval_unit': 'weeks',
})
alloc.action_approve()
alloc._update_accrual()
alloc_0.action_approve()
Allocation._update_accrual()
self.assertEqual(alloc_0.number_of_days, 1, 'Employee should have been allocated one leave day')
self.assertEqual(alloc.number_of_days, 1, 'Employee should have been allocated one leave day')
def test_accrual_base_leaves(self):
"""
Test if the accrual allocation take the unpaid leaves into account
when allocating leaves
"""
Allocation = self.env['hr.leave.allocation'].sudo(self.user_hrmanager_id).with_context(tracking_disable=True)
Leave = self.env['hr.leave'].sudo(self.user_hrmanager_id).with_context(tracking_disable=True)
alloc_0 = Allocation.create({
""" Test if the accrual allocation take the unpaid leaves into account when allocating leaves """
alloc = self.env['hr.leave.allocation'].sudo(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Accrual allocation for employee with leaves',
'employee_id': self.employee_hruser_id,
'holiday_status_id': self.accrual_type.id,
'accrual': True,
'number_of_days_temp': 0,
'number_per_interval': 1,
'interval_number': 1,
@@ -86,13 +75,13 @@ class TestAccrualAllocations(TestHrHolidaysBase):
'interval_unit': 'weeks',
})
alloc_0.action_approve()
alloc.action_approve()
employee = self.env['hr.employee'].browse(self.employee_hruser_id)
# Getting the previous work date
df = employee.resource_calendar_id.plan_days(-2, fields.Datetime.now()).date()
leave_0 = Leave.create({
leave = self.env['hr.leave'].sudo(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Leave for hruser',
'employee_id': self.employee_hruser_id,
'holiday_status_id': self.unpaid_type.id,
@@ -101,11 +90,11 @@ class TestAccrualAllocations(TestHrHolidaysBase):
'number_of_days_temp': 1,
})
leave_0.action_approve()
leave.action_approve()
Allocation._update_accrual()
alloc._update_accrual()
self.assertEqual(alloc_0.number_of_days, .8, 'As employee took some unpaid leaves last week, he should be allocated only .8 days')
self.assertEqual(alloc.number_of_days, .8, 'As employee took some unpaid leaves last week, he should be allocated only .8 days')
def test_accrual_many(self):
"""
@@ -117,6 +106,7 @@ class TestAccrualAllocations(TestHrHolidaysBase):
'name': '1 day per 2 weeks',
'employee_id': self.employee_emp_id,
'holiday_status_id': self.accrual_type.id,
'accrual': True,
'number_of_days_temp': 0,
'number_per_interval': 1,
'interval_number': 2,
@@ -128,6 +118,7 @@ class TestAccrualAllocations(TestHrHolidaysBase):
'name': '4 hours per week',
'employee_id': self.employee_emp_id,
'holiday_status_id': self.accrual_type.id,
'accrual': True,
'number_of_days_temp': 0,
'number_per_interval': 4,
'interval_number': 1,
@@ -139,6 +130,7 @@ class TestAccrualAllocations(TestHrHolidaysBase):
'name': '2 day per 1 month',
'employee_id': self.employee_emp_id,
'holiday_status_id': self.accrual_type.id,
'accrual': True,
'number_of_days_temp': 0,
'number_per_interval': 2,
'interval_number': 1,
@@ -150,6 +142,7 @@ class TestAccrualAllocations(TestHrHolidaysBase):
'name': '20 days per year',
'employee_id': self.employee_emp_id,
'holiday_status_id': self.accrual_type.id,
'accrual': True,
'number_of_days_temp': 0,
'number_per_interval': 20,
'interval_number': 1,
@@ -179,6 +172,7 @@ class TestAccrualAllocations(TestHrHolidaysBase):
'name': 'one shot one kill',
'employee_id': self.employee_emp_id,
'holiday_status_id': self.accrual_type.id,
'accrual': True,
'number_of_days_temp': 0,
'number_per_interval': 1,
'interval_number': 1,
@@ -193,29 +187,23 @@ class TestAccrualAllocations(TestHrHolidaysBase):
self.assertEqual(alloc_0.number_of_days, 0, 'Employee is new he should not get any accrual leaves')
def test_accrual_multi(self):
"""
Test if the cron does not allocate leaves every time it's called but only
when necessary
"""
Allocation = self.env['hr.leave.allocation'].sudo(self.user_hrmanager_id).with_context(tracking_disable=True)
alloc_0 = Allocation.create({
""" Test if the cron does not allocate leaves every time it's called but only when necessary """
alloc = self.env['hr.leave.allocation'].sudo(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': '2 days per week',
'employee_id': self.employee_emp_id,
'holiday_status_id': self.accrual_type.id,
'accrual': True,
'number_of_days_temp': 0,
'number_per_interval': 1,
'interval_number': 2,
'unit_per_interval': 'days',
'interval_unit': 'weeks',
})
alloc.action_approve()
alloc._update_accrual()
alloc._update_accrual()
alloc_0.action_approve()
Allocation._update_accrual()
Allocation._update_accrual()
self.assertEqual(alloc_0.number_of_days, 1, 'Cron only allocates 1 days every two weeks')
self.assertEqual(alloc.number_of_days, 1, 'Cron only allocates 1 days every two weeks')
def test_accrual_validation(self):
"""
@@ -242,32 +230,20 @@ class TestAccrualAllocations(TestHrHolidaysBase):
self.assertEqual(alloc_0.number_of_days, 0, 'Cron validity passed, should not allocate any leave')
def test_accrual_balance_limit(self):
"""
Test if accrual allocation does not allocate more than the balance limit
"""
Allocation = self.env['hr.leave.allocation'].sudo(self.user_hrmanager_id).with_context(tracking_disable=True)
LeaveType = self.env['hr.leave.type'].sudo(self.user_hrmanager_id).with_context(tracking_disable=True)
accrual_max_five = LeaveType.create({
'name': 'max 5 days',
'limit': False,
'accrual': True,
'balance_limit': 5,
})
allocation = Allocation.create({
""" Test if accrual allocation does not allocate more than the balance limit"""
allocation = self.env['hr.leave.allocation'].sudo(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'accrual 5 max',
'employee_id': self.employee_emp_id,
'holiday_status_id': accrual_max_five.id,
'holiday_status_id': self.accrual_type.id,
'accrual': True,
'accrual_limit': 5,
'number_of_days_temp': 0,
'number_per_interval': 6,
'interval_number': 1,
'unit_per_interval': 'days',
'interval_unit': 'weeks',
})
allocation.action_approve()
Allocation._update_accrual()
allocation._update_accrual()
self.assertEqual(allocation.number_of_days, 5, 'Should have allocated only 5 days as balance limit is 5')
@@ -33,34 +33,34 @@ class TestHolidaysFlow(TestHrHolidaysBase):
with self.assertRaises(AccessError):
HolidaysStatus.sudo(self.user_hruser_id).create({
'name': 'UserCheats',
'limit': True,
'allocation_type': 'no',
})
# HrManager creates some holiday statuses
HolidayStatusManagerGroup = HolidaysStatus.sudo(self.user_hrmanager_id)
HolidayStatusManagerGroup.create({
'name': 'WithMeetingType',
'limit': True,
'allocation_type': 'no',
'categ_id': self.env['calendar.event.type'].sudo(self.user_hrmanager_id).create({'name': 'NotLimitedMeetingType'}).id
})
self.holidays_status_1 = HolidayStatusManagerGroup.create({
'name': 'NotLimitedHR',
'limit': True,
'allocation_type': 'no',
'validation_type': 'hr',
})
self.holidays_status_2 = HolidayStatusManagerGroup.create({
'name': 'Limited',
'limit': False,
'allocation_type': 'fixed',
'validation_type': 'both',
})
self.holidays_status_3 = HolidayStatusManagerGroup.create({
'name': 'NotLimitedManager',
'limit': True,
'allocation_type': 'no',
'validation_type': 'manager',
})
self.holiday_status_4 = HolidayStatusManagerGroup.create({
'name': 'TimeNotLimited',
'limit': True,
'allocation_type': 'no',
'validation_type': 'manager',
'validity_start': fields.Datetime.from_string('2017-01-01 00:00:00'),
'validity_stop': fields.Datetime.from_string('2017-06-01 00:00:00'),
@@ -12,8 +12,8 @@ class TestHrLeaveType(TestHrHolidaysBase):
def test_time_type(self):
leave_type = self.env['hr.leave.type'].create({
'name': 'Legal Leaves',
'limit': True,
'time_type': 'leave',
'allocation_type': 'no',
})
leave_1 = self.env['hr.leave'].create({
@@ -55,7 +55,6 @@
<form string="Allocation Request">
<field name="can_reset" invisible="1"/>
<field name="can_approve" invisible="1"/>
<field name="accrual" invisible="1"/>
<header>
<button string="Confirm" name="action_confirm" states="draft" type="object" class="oe_highlight"/>
<field name="state" widget="statusbar" statusbar_visible="draft,confirm,validate"/>
@@ -68,19 +67,21 @@
<group>
<field name="type_request_unit" invisible="1"/>
<field name="name" attrs="{'readonly':[('state','!=','draft'),('state','!=','confirm')]}"/>
<field name="holiday_status_id" context="{'employee_id':employee_id}"/>
<field name="holiday_status_id" context="{'employee_id':employee_id}"
domain="[('valid', '=', True), ('allocation_type', 'in', ['fixed_allocation', 'no'])]"/>
<label for="number_of_days_temp" string="Duration" attrs="{'readonly': [('state', '=', 'validate')], 'invisible': ['|', ('type_request_unit', '=', 'hour'),('accrual', '=', True)]}"/>
<div name="number_of_days_temp" attrs="{'readonly': [('state', '=', 'validate')], 'invisible': ['|', ('type_request_unit', '=', 'hour'),('accrual', '=', True)]}">
<label for="number_of_days_temp" string="Duration" attrs="{'readonly': [('state', '=', 'validate')], 'invisible': [('type_request_unit', '=', 'hour')]}"/>
<div name="number_of_days_temp" attrs="{'readonly': [('state', '=', 'validate')], 'invisible': [('type_request_unit', '=', 'hour')]}">
<field name="number_of_days_temp" class="oe_inline" nolabel="1" string="Duration"/>
<span class="ml8">Days</span>
</div>
<label for="number_of_hours" string="Duration" attrs="{'readonly': [('state', '=', 'validate')], 'invisible': ['|', ('type_request_unit', '!=', 'hour'),('accrual', '=', True)]}"/>
<div name="number_of_hours" attrs="{'readonly': [('state', '=', 'validate')], 'invisible': ['|', ('type_request_unit', '!=', 'hour'),('accrual', '=', True)]}">
<label for="number_of_hours" string="Duration" attrs="{'readonly': [('state', '=', 'validate')], 'invisible': [('type_request_unit', '!=', 'hour')]}"/>
<div name="number_of_hours" attrs="{'readonly': [('state', '=', 'validate')], 'invisible': [('type_request_unit', '!=', 'hour')]}">
<field name="number_of_hours" class="oe_inline" nolabel="1" string="Duration"/>
<span class="ml8">Hours</span>
</div>
<field name="accrual" invisible="1"/>
</group>
<group name="alloc_right_col">
<field name="employee_id" invisible="1" groups="hr_holidays.group_hr_holidays_user"/>
@@ -114,6 +115,9 @@
<button string="Reset to Draft" name="action_draft" type="object"
attrs="{'invisible': ['|', ('can_reset', '=', False), ('state', 'not in', ['confirm', 'refuse'])]}"/>
</xpath>
<xpath expr="//field[@name='holiday_status_id']" position="attributes">
<attribute name="domain">[('valid', '=', True)]</attribute>
</xpath>
<xpath expr="//field[@name='employee_id']" position="before">
<field name="holiday_type" string="Mode" groups="hr_holidays.group_hr_holidays_user" context="{'employee_id':employee_id}" />
</xpath>
@@ -127,13 +131,28 @@
<xpath expr="//field[@name='department_id']" position="replace">
<field name="department_id" groups="hr_holidays.group_hr_holidays_user" attrs="{'invisible':[('holiday_type','=','category')]}"/>
</xpath>
<xpath expr="//div[@name='number_of_hours']" position="after">
<field name="date_to" string="Run Until" help="If no value set, runs indefinitely" class="oe_inline"/>
<div class="o_row" colspan="2">
Add <field name="number_per_interval" attrs="{'required': [('accrual', '=', True)]}"/>
<field name="unit_per_interval" attrs="{'required': [('accrual', '=', True)]}"/> of leaves every
<field name="interval_number" attrs="{'required': [('accrual', '=', True)]}"/>
<field name="interval_unit" attrs="{'required': [('accrual', '=', True)]}"/>
<xpath expr="//field[@name='accrual']" position="attributes">
<attribute name="invisible">0</attribute>
</xpath>
<xpath expr="//field[@name='accrual']" position="after">
<label for="date_to" string=""
attrs="{'invisible': [('accrual', '=', False)]}"/>
<div attrs="{'invisible': [('accrual', '=', False)]}">
<div class="o_row">
Run until <field name="date_to" string="Run Until" help="If no value set, runs indefinitely"/>
</div>
<div class="o_row">
<span>Add</span>
<field name="number_per_interval" class="ml8"
attrs="{'required': [('accrual', '=', True)]}"/>
<field name="unit_per_interval"
attrs="{'required': [('accrual', '=', True)]}"/>
<span class="ml8">of leaves every</span>
<field name="interval_number" class="ml8"
attrs="{'required': [('accrual', '=', True)]}"/>
<field name="interval_unit"
attrs="{'required': [('accrual', '=', True)]}"/>
</div>
</div>
</xpath>
</field>
@@ -19,27 +19,40 @@
<form string="Leave Type">
<sheet>
<div class="oe_button_box" name="button_box">
<button class="oe_stat_button" type="object" name="action_see_days_allocated" icon="fa-archive">
<div class="o_stat_info">
<field name="group_days_allocation"/>
<span class="o_stat_text">Days Allocated</span>
</div>
</button>
<button class="oe_stat_button" type="object" name="action_see_group_leaves" icon="fa-archive">
<div class="o_stat_info">
<field name="group_days_leave"/>
<span class="o_stat_text">Group Leaves</span>
</div>
</button>
<button class="oe_stat_button" type="object" name="toggle_active" icon="fa-archive">
<field name="active" widget="boolean_button"
options='{"terminology": "archive"}'/>
</button>
</div>
<div class="oe_title">
<h1><field name="name"/></h1>
</div>
<group>
<group name="description" string="Description">
<field name="name"/>
<group name="description" string="Options">
<field name="unpaid"/>
<field name="limit" attrs="{'invisible': [('accrual', '=', True)]}"/>
<field name="request_unit"/>
<field name="sequence" attrs="{'invisible': [('limit', '!=', False)]}" groups="base.group_no_one"/>
<field name="request_unit" widget="radio"/>
<field name="sequence" groups="base.group_no_one"/>
<field name="time_type" groups="base.group_no_one"/>
</group>
<group name="validation" string="Validation">
<field name="validation_type" widget="radio"/>
</group>
<group name="visibility" string="Visibility">
<field name="employee_applicability" widget="radio" attrs="{'readonly': ['|', ('limit', '=', True), ('accrual', '=', True)]}" force_save="1"/>
<group name="visibility" string="Allocation">
<field name="allocation_type" widget="radio" force_save="1"/>
</group>
<group name="validity" string="Validity" attrs="{'invisible': [('employee_applicability', '=', 'allocation')]}">
<group name="validity" string="Validity">
<field name="validity_start"/>
<field name="validity_stop"/>
</group>
@@ -48,11 +61,6 @@
<field name="color_name"/>
<field name="company_id" groups="base.group_multi_company"/>
</group>
<group name="allocation_request" string="Allocation Type" attrs="{'invisible': [('unpaid', '=', True)]}">
<field name="accrual"/>
<field name="negative" attrs="{'invisible': [('accrual', '=', False)]}"/>
<field name="balance_limit" attrs="{'invisible': [('accrual', '=', False)]}"/>
</group>
</group>
</sheet>
</form>
@@ -88,7 +96,7 @@
<tree string="Leave Type">
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="limit"/>
<field name="allocation_type"/>
<field name="validation_type"/>
<field name="validity_start"/>
<field name="validity_stop"/>
+11 -12
View File
@@ -111,24 +111,23 @@
<field name="number_of_hours" nolabel="1" class="oe_inline"/>
<span class="ml8">Hours</span>
</div>
<field name="request_unit_all" widget="radio" attrs="{'readonly': [('state', '=', 'validate')], 'invisible': [('leave_type_request_unit', '=', 'day')]}"/>
<field name="request_unit_day" widget="radio" attrs="{'readonly': [('state', '=', 'validate')], 'invisible': [('leave_type_request_unit', '!=', 'day')]}"/>
<field name="request_unit" widget="radio" attrs="{'readonly': [('state', '=', 'validate')]}"/>
<div class="o_row">
<span attrs="{'invisible': [('request_unit_all', '!=', 'period')]}">From </span>
<field name="request_date_from" attrs="{'readonly': [('state', '=', 'validate')], 'invisible': [('leave_type_request_unit', '=', 'hour'), ('request_unit_all', '=', 'period')]}"/>
<field name="date_from" attrs="{'readonly': [('state', '=', 'validate')], 'invisible': ['|', ('leave_type_request_unit', '!=', 'hour'), ('request_unit_all', '!=', 'period')]}"/>
<span class="ml8" attrs="{'invisible': ['|', '&amp;', ('request_unit_all', '!=', 'half'), ('leave_type_request_unit', '!=', 'half'), '&amp;', ('request_unit_all', '=', 'day'), ('leave_type_request_unit', '=', 'half')]}"/>
<span attrs="{'invisible': [('request_unit', '!=', 'period')]}">From </span>
<field name="request_date_from" attrs="{'readonly': [('state', '=', 'validate')], 'invisible': [('leave_type_request_unit', '=', 'hour'), ('request_unit', '=', 'period')]}"/>
<field name="date_from" attrs="{'readonly': [('state', '=', 'validate')], 'invisible': ['|', ('leave_type_request_unit', '!=', 'hour'), ('request_unit', '!=', 'period')]}"/>
<span class="ml8" attrs="{'invisible': ['|', '&amp;', ('request_unit', '!=', 'half'), ('leave_type_request_unit', '!=', 'half'), '&amp;', ('request_unit', '=', 'day'), ('leave_type_request_unit', '=', 'half')]}"/>
<field name="request_date_from_period" options="{'horizontal': True}"
attrs="{'readonly': [('state', '=', 'validate')], 'invisible': ['|', '|', ('leave_type_request_unit', '=', 'day'), '&amp;', ('leave_type_request_unit', '=', 'half'), ('request_unit_all', '=', 'day'), '&amp;', ('leave_type_request_unit', '=', 'hour'), ('request_unit_all', '!=', 'half')]}"/>
attrs="{'readonly': [('state', '=', 'validate')], 'invisible': ['|', '|', ('leave_type_request_unit', '=', 'day'), '&amp;', ('leave_type_request_unit', '=', 'half'), ('request_unit', '=', 'day'), '&amp;', ('leave_type_request_unit', '=', 'hour'), ('request_unit', '!=', 'half')]}"/>
</div>
<div class="o_row">
<span attrs="{'invisible': [('request_unit_all', '!=', 'period')]}">To </span>
<field name="request_date_to" attrs="{'readonly': [('state', '=', 'validate')], 'invisible': ['|', ('request_unit_all', '!=', 'period'), ('leave_type_request_unit', '=', 'hour')]}"/>
<field name="date_to" attrs="{'readonly': [('state', '=', 'validate')], 'invisible': ['|', ('leave_type_request_unit', '!=', 'hour'), ('request_unit_all', '!=', 'period')], 'required': ['|', ('request_unit_all', '=', 'period'), ('request_unit_day', '=', 'period')]}"/>
<span class="ml8" attrs="{'invisible': ['|', ('leave_type_request_unit', '!=', 'half'), ('request_unit_all', '!=', 'period')]}"/>
<span attrs="{'invisible': [('request_unit', '!=', 'period')]}">To </span>
<field name="request_date_to" attrs="{'readonly': [('state', '=', 'validate')], 'invisible': ['|', ('request_unit', '!=', 'period'), ('leave_type_request_unit', '=', 'hour')]}"/>
<field name="date_to" attrs="{'readonly': [('state', '=', 'validate')], 'invisible': ['|', ('leave_type_request_unit', '!=', 'hour'), ('request_unit', '!=', 'period')], 'required': [('request_unit', '=', 'period')]}"/>
<span class="ml8" attrs="{'invisible': ['|', ('leave_type_request_unit', '!=', 'half'), ('request_unit', '!=', 'period')]}"/>
<field name="request_date_to_period" options="{'horizontal': True}"
attrs="{'readonly': [('state', '=', 'validate')], 'invisible': ['|', ('leave_type_request_unit', '!=', 'half'), ('request_unit_all', '!=', 'period')]}"/>
attrs="{'readonly': [('state', '=', 'validate')], 'invisible': ['|', ('leave_type_request_unit', '!=', 'half'), ('request_unit', '!=', 'period')]}"/>
</div>
</div>
</group>
@@ -16,7 +16,7 @@ class TestTimesheetHolidaysCreate(common.TransactionCase):
"""Ensure that when a status is created, it fullfills the project and task constrains"""
status = self.env['hr.leave.type'].create({
'name': 'A nice Leave Type',
'limit': True
'allocation_type': 'no'
})
company = self.env.user.company_id
@@ -42,14 +42,14 @@ class TestTimesheetHolidays(TestTimesheet):
self.hr_leave_type_with_ts = self.env['hr.leave.type'].create({
'name': 'Leave Type with timesheet generation',
'limit': True,
'allocation_type': 'no',
'timesheet_generate': True,
'timesheet_project_id': self.internal_project.id,
'timesheet_task_id': self.internal_task_leaves.id,
})
self.hr_leave_type_no_ts = self.env['hr.leave.type'].create({
'name': 'Leave Type without timesheet generation',
'limit': True,
'allocation_type': 'no',
'timesheet_generate': False,
'timesheet_project_id': False,
'timesheet_task_id': False,
@@ -6,12 +6,10 @@
<field name="model">hr.leave.type</field>
<field name="inherit_id" ref="hr_holidays.edit_holiday_status_form"/>
<field name="arch" type="xml">
<xpath expr="//group[@name='description']" position="after">
<group name="timesheet" string="Timesheet">
<field name="timesheet_generate"/>
<field name="timesheet_project_id" attrs="{'invisible': [('timesheet_generate', '!=', True)], 'required': [('timesheet_generate', '=', True)]}" context="{'active_test': False}" groups="base.group_no_one"/>
<field name="timesheet_task_id" attrs="{'invisible': [('timesheet_generate', '!=', True)], 'required': [('timesheet_generate', '=', True)]}" context="{'active_test': False}" groups="base.group_no_one"/>
</group>
<xpath expr="//field[@name='unpaid']" position="after">
<field name="timesheet_generate"/>
<field name="timesheet_project_id" attrs="{'invisible': [('timesheet_generate', '!=', True)], 'required': [('timesheet_generate', '=', True)]}" context="{'active_test': False}" groups="base.group_no_one"/>
<field name="timesheet_task_id" attrs="{'invisible': [('timesheet_generate', '!=', True)], 'required': [('timesheet_generate', '=', True)]}" context="{'active_test': False}" groups="base.group_no_one"/>
</xpath>
</field>
</record>