[IMP] hr_holiday: accrual refactoring.
Refactoring of the accrual computation methods and views. There are also 2 new features: custom carry-over date and allocation time: Custom carry-over date: Previously, an accrual plan was always evaluated on the 1st of january to decide if days were lost or postponed to next year. Now the user can choose any recurring date in the year for that evaluation Allocation time: Previously the days were given at the end of the accrual period, meaning an employee had to work the whole period to get his days. Now there is a choice between accruing at the start or at the end of a given period. Simulation: On an allocation of type accrual, changing date_from or accrual_plan launches the calculation of the days to allocate. This lets the user know how many days the allocation would give to an employee and lets him eventually change that number. After saving the record the flow resumes like before. There is no more need to create fake allocations. Additional tests written and some existing tests have corrected values / parameters to still test same feature but be correct with the new functioning. task 3060724 closes odoo/odoo#112383 Related: odoo/upgrade#4597 Signed-off-by: Sofie Gvaladze (sgv) <sgv@odoo.com>
This commit is contained in:
@@ -3,6 +3,11 @@
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
|
||||
from odoo.addons.hr_holidays.models.hr_leave_accrual_plan_level import _get_selection_days
|
||||
|
||||
DAY_SELECT_VALUES = [str(i) for i in range(1, 29)] + ['last']
|
||||
DAY_SELECT_SELECTION_NO_LAST = tuple(zip(DAY_SELECT_VALUES, (str(i) for i in range(1, 29))))
|
||||
|
||||
|
||||
class AccrualPlan(models.Model):
|
||||
_name = "hr.leave.accrual.plan"
|
||||
@@ -15,19 +20,48 @@ class AccrualPlan(models.Model):
|
||||
help="""Specify if this accrual plan can only be used with this Time Off Type.
|
||||
Leave empty if this accrual plan can be used with any Time Off Type.""")
|
||||
employees_count = fields.Integer("Employees", compute='_compute_employee_count')
|
||||
level_ids = fields.One2many('hr.leave.accrual.level', 'accrual_plan_id', copy=True)
|
||||
level_ids = fields.One2many('hr.leave.accrual.level', 'accrual_plan_id', copy=True, string="Milestone")
|
||||
allocation_ids = fields.One2many('hr.leave.allocation', 'accrual_plan_id')
|
||||
company_id = fields.Many2one('res.company', string='Company',
|
||||
compute="_compute_company_id", store="True", readonly=False)
|
||||
transition_mode = fields.Selection([
|
||||
('immediately', 'Immediately'),
|
||||
('end_of_accrual', "After this accrual's period")],
|
||||
string="Level Transition", default="immediately", required=True,
|
||||
string="Milestone Transition", default="immediately", required=True,
|
||||
help="""Specify what occurs if a level transition takes place in the middle of a pay period.\n
|
||||
'Immediately' will switch the employee to the new accrual level on the exact date during the ongoing pay period.\n
|
||||
'After this accrual's period' will keep the employee on the same accrual level until the ongoing pay period is complete.
|
||||
After it is complete, the new level will take effect when the next pay period begins.""")
|
||||
show_transition_mode = fields.Boolean(compute='_compute_show_transition_mode')
|
||||
is_based_on_worked_time = fields.Boolean("Based on worked time", compute="_compute_is_based_on_worked_time", store=True, readonly=False,
|
||||
help="If checked, the accrual period will be calculated according to the work days, not calendar days.")
|
||||
accrued_gain_time = fields.Selection([
|
||||
("start", "At the start of the accrual period"),
|
||||
("end", "At the end of the accrual period")],
|
||||
default="end", required=True)
|
||||
carryover_date = fields.Selection([
|
||||
("year_start", "At the start of the year"),
|
||||
("allocation", "At the allocation date"),
|
||||
("other", "Other")],
|
||||
default="year_start", required=True, string="Carry-Over Time")
|
||||
carryover_day = fields.Integer(default=1)
|
||||
carryover_day_display = fields.Selection(
|
||||
_get_selection_days, compute='_compute_carryover_day_display', inverse='_inverse_carryover_day_display')
|
||||
carryover_month = fields.Selection([
|
||||
("jan", "January"),
|
||||
("feb", "February"),
|
||||
("mar", "March"),
|
||||
("apr", "April"),
|
||||
("may", "May"),
|
||||
("jun", "June"),
|
||||
("jul", "July"),
|
||||
("aug", "August"),
|
||||
("sep", "September"),
|
||||
("oct", "October"),
|
||||
("nov", "November"),
|
||||
("dec", "December")
|
||||
], default="jan")
|
||||
added_value_type = fields.Selection([('day', 'Days'), ('hour', 'Hours')], compute='_compute_added_value_type', store=True)
|
||||
|
||||
@api.depends('level_ids')
|
||||
def _compute_show_transition_mode(self):
|
||||
@@ -66,6 +100,31 @@ class AccrualPlan(models.Model):
|
||||
else:
|
||||
accrual_plan.company_id = self.env.company
|
||||
|
||||
@api.depends("accrued_gain_time")
|
||||
def _compute_is_based_on_worked_time(self):
|
||||
for plan in self:
|
||||
if plan.accrued_gain_time == "start":
|
||||
plan.is_based_on_worked_time = False
|
||||
|
||||
@api.depends("level_ids")
|
||||
def _compute_added_value_type(self):
|
||||
for plan in self:
|
||||
if plan.level_ids:
|
||||
plan.added_value_type = plan.level_ids[0].added_value_type
|
||||
|
||||
@api.depends("carryover_day")
|
||||
def _compute_carryover_day_display(self):
|
||||
days_select = _get_selection_days(self)
|
||||
for plan in self:
|
||||
plan.carryover_day_display = days_select[min(plan.carryover_day - 1, 28)][0]
|
||||
|
||||
def _inverse_carryover_day_display(self):
|
||||
for plan in self:
|
||||
if plan.carryover_day_display == 'last':
|
||||
plan.carryover_day = 31
|
||||
else:
|
||||
plan.carryover_day = DAY_SELECT_VALUES.index(plan.carryover_day_display) + 1
|
||||
|
||||
def action_open_accrual_plan_employees(self):
|
||||
self.ensure_one()
|
||||
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import datetime
|
||||
import calendar
|
||||
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.tools.date_utils import get_timedelta
|
||||
|
||||
|
||||
DAYS = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat']
|
||||
@@ -19,6 +15,7 @@ DAY_SELECT_SELECTION_NO_LAST = tuple(zip(DAY_SELECT_VALUES, (str(i) for i in ran
|
||||
def _get_selection_days(self):
|
||||
return DAY_SELECT_SELECTION_NO_LAST + (("last", _("last day")),)
|
||||
|
||||
|
||||
class AccrualPlanLevel(models.Model):
|
||||
_name = "hr.leave.accrual.level"
|
||||
_description = "Accrual Plan Level"
|
||||
@@ -27,27 +24,25 @@ class AccrualPlanLevel(models.Model):
|
||||
sequence = fields.Integer(
|
||||
string='sequence', compute='_compute_sequence', store=True,
|
||||
help='Sequence is generated automatically by start time delta.')
|
||||
accrual_plan_id = fields.Many2one('hr.leave.accrual.plan', "Accrual Plan", required=True)
|
||||
accrual_plan_id = fields.Many2one('hr.leave.accrual.plan', "Accrual Plan", required=True, ondelete="cascade")
|
||||
start_count = fields.Integer(
|
||||
"Start after",
|
||||
help="The accrual starts after a defined period from the allocation start date. This field defines the number of days, months or years after which accrual is used.", default="1")
|
||||
start_type = fields.Selection(
|
||||
[('day', 'day(s)'),
|
||||
('month', 'month(s)'),
|
||||
('year', 'year(s)')],
|
||||
[('day', 'Days'),
|
||||
('month', 'Months'),
|
||||
('year', 'Years')],
|
||||
default='day', string=" ", required=True,
|
||||
help="This field defines the unit of time after which the accrual starts.")
|
||||
is_based_on_worked_time = fields.Boolean("Based on worked time",
|
||||
help="If checked, the rate will be prorated on time off type where type is set on Working Time in the configuration.")
|
||||
|
||||
# Accrue of
|
||||
added_value = fields.Float(
|
||||
"Rate", digits=(16, 5), required=True,
|
||||
"Rate", digits=(16, 5), required=True, default=1,
|
||||
help="The number of hours/days that will be incremented in the specified Time Off Type for every period")
|
||||
added_value_type = fields.Selection(
|
||||
[('days', 'Days'),
|
||||
('hours', 'Hours')],
|
||||
default='days', required=True)
|
||||
added_value_type = fields.Selection([
|
||||
('day', 'Days'),
|
||||
('hour', 'Hours')
|
||||
], compute="_compute_added_value_type", store=True, required=True, default="day")
|
||||
frequency = fields.Selection([
|
||||
('daily', 'Daily'),
|
||||
('weekly', 'Weekly'),
|
||||
@@ -110,19 +105,19 @@ class AccrualPlanLevel(models.Model):
|
||||
yearly_day = fields.Integer(default=1)
|
||||
yearly_day_display = fields.Selection(
|
||||
_get_selection_days, compute='_compute_days_display', inverse='_inverse_yearly_day_display')
|
||||
cap_accrued_time = fields.Boolean("Cap accrued time", default=True)
|
||||
maximum_leave = fields.Float(
|
||||
'Limit to', required=False, default=100,
|
||||
help="Choose a cap for this accrual. 0 means no cap.")
|
||||
parent_id = fields.Many2one(
|
||||
'hr.leave.accrual.level', string="Previous Level",
|
||||
help="If this field is empty, this level is the first one.")
|
||||
'Limit to', digits=(16, 2), compute="_compute_maximum_leave", readonly=False, store=True,
|
||||
help="Choose a cap for this accrual.")
|
||||
action_with_unused_accruals = fields.Selection(
|
||||
[('postponed', 'Transferred to the next year'),
|
||||
('lost', 'Lost')],
|
||||
string="At the end of the calendar year, unused accruals will be",
|
||||
default='postponed', required=True)
|
||||
[('lost', 'None. Accrued time reset to 0'),
|
||||
('all', 'All accrued time carried over'),
|
||||
('maximum', 'Carry over with a maximum')],
|
||||
string="Carry over",
|
||||
default='all', required=True)
|
||||
postpone_max_days = fields.Integer("Maximum amount of accruals to transfer",
|
||||
help="Set a maximum of days an allocation keeps at the end of the year. 0 for no limit.")
|
||||
help="Set a maximum of accruals an allocation keeps at the end of the year.")
|
||||
can_modify_value_type = fields.Boolean(compute="_compute_can_modify_value_type")
|
||||
|
||||
_sql_constraints = [
|
||||
('check_dates',
|
||||
@@ -148,6 +143,19 @@ class AccrualPlanLevel(models.Model):
|
||||
for level in self:
|
||||
level.sequence = level.start_count * start_type_multipliers[level.start_type]
|
||||
|
||||
@api.depends('accrual_plan_id', 'accrual_plan_id.level_ids', 'accrual_plan_id.time_off_type_id')
|
||||
def _compute_can_modify_value_type(self):
|
||||
for level in self:
|
||||
level.can_modify_value_type = not level.accrual_plan_id.time_off_type_id and level.accrual_plan_id.level_ids and level.accrual_plan_id.level_ids[0] == level
|
||||
|
||||
@api.depends('accrual_plan_id', 'accrual_plan_id.level_ids', 'accrual_plan_id.time_off_type_id')
|
||||
def _compute_added_value_type(self):
|
||||
for level in self:
|
||||
if level.accrual_plan_id.time_off_type_id:
|
||||
level.added_value_type = "day" if level.accrual_plan_id.time_off_type_id.request_unit in ["day", "half_day"] else "hour"
|
||||
elif level.accrual_plan_id.level_ids and level.accrual_plan_id.level_ids[0] != level:
|
||||
level.added_value_type = level.accrual_plan_id.level_ids[0]._origin.added_value_type
|
||||
|
||||
@api.depends('first_day', 'second_day', 'first_month_day', 'second_month_day', 'yearly_day')
|
||||
def _compute_days_display(self):
|
||||
days_select = _get_selection_days(self)
|
||||
@@ -158,6 +166,11 @@ class AccrualPlanLevel(models.Model):
|
||||
level.second_month_day_display = days_select[min(level.second_month_day - 1, 28)][0]
|
||||
level.yearly_day_display = days_select[min(level.yearly_day - 1, 28)][0]
|
||||
|
||||
@api.depends('cap_accrued_time')
|
||||
def _compute_maximum_leave(self):
|
||||
for level in self:
|
||||
level.maximum_leave = 100 if level.cap_accrued_time else 0
|
||||
|
||||
def _inverse_first_day_display(self):
|
||||
for level in self:
|
||||
if level.first_day_display == 'last':
|
||||
|
||||
@@ -3,23 +3,19 @@
|
||||
|
||||
# Copyright (c) 2005-2006 Axelor SARL. (http://www.axelor.com)
|
||||
|
||||
from collections import defaultdict
|
||||
import logging
|
||||
|
||||
from datetime import datetime, time
|
||||
from datetime import datetime, date, time
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.addons.resource.models.utils import HOURS_PER_DAY
|
||||
from odoo.addons.hr_holidays.models.hr_leave import get_employee_from_context
|
||||
from odoo.exceptions import AccessError, UserError, ValidationError
|
||||
from odoo.tools.translate import _
|
||||
from odoo.exceptions import AccessError, UserError
|
||||
from odoo.tools.float_utils import float_round
|
||||
from odoo.tools.date_utils import get_timedelta
|
||||
from odoo.osv import expression
|
||||
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
MONTHS_TO_INTEGER = {"jan": 1, "feb": 2, "mar": 3, "apr": 4, "may": 5, "jun": 6, "jul": 7, "aug": 8, "sep": 9, "oct": 10, "nov": 11, "dec": 12}
|
||||
|
||||
class HolidaysAllocation(models.Model):
|
||||
""" Allocation Requests Access specifications: similar to leave requests """
|
||||
@@ -75,11 +71,10 @@ class HolidaysAllocation(models.Model):
|
||||
help='Duration in days. Reference field to use when necessary.')
|
||||
number_of_days_display = fields.Float(
|
||||
'Duration (days)', compute='_compute_number_of_days_display',
|
||||
readonly=False,
|
||||
help="If Accrual Allocation: Number of days allocated in addition to the ones you will get via the accrual' system.")
|
||||
help="For an Accrual Allocation, this field contains the theorical amount of time given to the employee, due to a previous start date, on the first run of the plan. This can be manually edited.")
|
||||
number_of_hours_display = fields.Float(
|
||||
'Duration (hours)', compute='_compute_number_of_hours_display',
|
||||
help="If Accrual Allocation: Number of hours allocated in addition to the ones you will get via the accrual' system.")
|
||||
help="For an Accrual Allocation, this field contains the theorical amount of time given to the employee, due to a previous start date, on the first run of the plan. This can be manually edited.")
|
||||
duration_display = fields.Char('Allocated (Days/Hours)', compute='_compute_duration_display',
|
||||
help="Field allowing to see the allocation duration in days or hours depending on the type_request_unit")
|
||||
# details
|
||||
@@ -91,7 +86,11 @@ class HolidaysAllocation(models.Model):
|
||||
validation_type = fields.Selection(string='Validation Type', related='holiday_status_id.allocation_validation_type', readonly=True)
|
||||
can_reset = fields.Boolean('Can reset', compute='_compute_can_reset')
|
||||
can_approve = fields.Boolean('Can Approve', compute='_compute_can_approve')
|
||||
type_request_unit = fields.Selection(related='holiday_status_id.request_unit', readonly=True)
|
||||
type_request_unit = fields.Selection([
|
||||
('hour', 'Hours'),
|
||||
('half_day', 'Half Day'),
|
||||
('day', 'Day'),
|
||||
], compute="_compute_type_request_unit")
|
||||
# mode
|
||||
holiday_type = fields.Selection([
|
||||
('employee', 'By Employee'),
|
||||
@@ -117,12 +116,12 @@ class HolidaysAllocation(models.Model):
|
||||
'hr.employee.category', compute='_compute_from_holiday_type', store=True, string='Employee Tag', readonly=False)
|
||||
# accrual configuration
|
||||
lastcall = fields.Date("Date of the last accrual allocation", readonly=True, default=fields.Date.context_today)
|
||||
nextcall = fields.Date("Date of the next accrual allocation", default=False, readonly=True)
|
||||
allocation_type = fields.Selection(
|
||||
[
|
||||
('regular', 'Regular Allocation'),
|
||||
('accrual', 'Accrual Allocation')
|
||||
], string="Allocation Type", default="regular", required=True, readonly=False)
|
||||
nextcall = fields.Date("Date of the next accrual allocation", readonly=True, default=False)
|
||||
already_accrued = fields.Boolean()
|
||||
allocation_type = fields.Selection([
|
||||
('regular', 'Regular Allocation'),
|
||||
('accrual', 'Accrual Allocation')
|
||||
], string="Allocation Type", default="regular", required=True, readonly=True)
|
||||
is_officer = fields.Boolean(compute='_compute_is_officer')
|
||||
accrual_plan_id = fields.Many2one('hr.leave.accrual.plan', compute="_compute_from_holiday_status_id", store=True, readonly=False, domain="['|', ('time_off_type_id', '=', False), ('time_off_type_id', '=', holiday_status_id)]", tracking=True)
|
||||
max_leaves = fields.Float(compute='_compute_leaves')
|
||||
@@ -222,7 +221,6 @@ class HolidaysAllocation(models.Model):
|
||||
|
||||
allocation.number_of_hours_display = allocation.number_of_days * (allocation_calendar.hours_per_day or HOURS_PER_DAY)
|
||||
|
||||
|
||||
@api.depends('number_of_hours_display', 'number_of_days_display')
|
||||
def _compute_duration_display(self):
|
||||
for allocation in self:
|
||||
@@ -336,37 +334,42 @@ class HolidaysAllocation(models.Model):
|
||||
if allocation.holiday_status_id:
|
||||
allocation.accrual_plan_id = accruals_dict.get(allocation.holiday_status_id.id, [False])[0]
|
||||
|
||||
def _end_of_year_accrual(self):
|
||||
# to override in payroll
|
||||
today = fields.Date.today()
|
||||
last_day_last_year = today + relativedelta(years=-1, month=12, day=31)
|
||||
first_day_this_year = today + relativedelta(month=1, day=1)
|
||||
@api.depends("allocation_type", "holiday_status_id", "accrual_plan_id")
|
||||
def _compute_type_request_unit(self):
|
||||
for allocation in self:
|
||||
current_level = allocation._get_current_accrual_plan_level_id(first_day_this_year)[0]
|
||||
if not current_level:
|
||||
continue
|
||||
# lastcall has two cases:
|
||||
# 1. The period was fully ran until the last day of last year
|
||||
# 2. The period was not fully ran until the last day of last year
|
||||
# For case 2, we need to prorata the number of days so need to check if the lastcall within the current level period
|
||||
lastcall = current_level._get_previous_date(last_day_last_year) if allocation.lastcall < current_level._get_previous_date(last_day_last_year) else allocation.lastcall
|
||||
nextcall = current_level._get_next_date(last_day_last_year)
|
||||
if current_level.action_with_unused_accruals == 'lost':
|
||||
# Allocations are lost but number_of_days should not be lower than leaves_taken
|
||||
allocation.write({'number_of_days': allocation.leaves_taken, 'lastcall': lastcall, 'nextcall': nextcall})
|
||||
elif current_level.action_with_unused_accruals == 'postponed' and current_level.postpone_max_days:
|
||||
# Make sure the period was ran until the last day of last year
|
||||
if allocation.nextcall:
|
||||
allocation.nextcall = first_day_this_year
|
||||
# date_to should be first day of this year so the prorata amount is computed correctly
|
||||
allocation._process_accrual_plans(first_day_this_year, True)
|
||||
number_of_days = min(allocation.number_of_days - allocation.leaves_taken, current_level.postpone_max_days) + allocation.leaves_taken
|
||||
allocation.write({'number_of_days': number_of_days, 'lastcall': lastcall, 'nextcall': nextcall})
|
||||
if allocation.allocation_type == "accrual" and allocation.accrual_plan_id:
|
||||
allocation.type_request_unit = allocation.accrual_plan_id.added_value_type
|
||||
elif allocation.allocation_type == "regular":
|
||||
allocation.type_request_unit = allocation.holiday_status_id.request_unit
|
||||
else:
|
||||
allocation.type_request_unit = "day"
|
||||
|
||||
def _get_carryover_date(self, date_from):
|
||||
self.ensure_one()
|
||||
carryover_time = self.accrual_plan_id.carryover_date
|
||||
accrual_plan = self.accrual_plan_id
|
||||
carryover_date = False
|
||||
if carryover_time == 'year_start':
|
||||
carryover_date = date(date_from.year, 1, 1)
|
||||
elif carryover_time == 'allocation':
|
||||
carryover_date = date(date_from.year, self.date_from.month, self.date_from.day)
|
||||
else:
|
||||
carryover_date = date(date_from.year, MONTHS_TO_INTEGER[accrual_plan.carryover_month], accrual_plan.carryover_day)
|
||||
if date_from > carryover_date:
|
||||
carryover_date += relativedelta(years=1)
|
||||
return carryover_date
|
||||
|
||||
def _add_days_to_allocation(self, current_level, current_level_maximum_leave, leaves_taken, period_start, period_end):
|
||||
days_to_add = self._process_accrual_plan_level(
|
||||
current_level, period_start, self.lastcall, period_end, self.nextcall)
|
||||
self.number_of_days += days_to_add
|
||||
if current_level.cap_accrued_time:
|
||||
self.number_of_days = min(self.number_of_days, current_level_maximum_leave + leaves_taken)
|
||||
|
||||
def _get_current_accrual_plan_level_id(self, date, level_ids=False):
|
||||
"""
|
||||
Returns a pair (accrual_plan_level, idx) where accrual_plan_level is the level for the given date
|
||||
and idx is the index for the plan in the ordered set of levels
|
||||
and idx is the index for the plan in the ordered set of levels
|
||||
"""
|
||||
self.ensure_one()
|
||||
if not self.accrual_plan_id.level_ids:
|
||||
@@ -398,14 +401,15 @@ class HolidaysAllocation(models.Model):
|
||||
Returns the added days for that level
|
||||
"""
|
||||
self.ensure_one()
|
||||
if level.is_based_on_worked_time:
|
||||
start_dt = datetime.combine(start_date, datetime.min.time())
|
||||
end_dt = datetime.combine(end_date, datetime.min.time())
|
||||
if level.accrual_plan_id.is_based_on_worked_time:
|
||||
datetime_min_time = datetime.min.time()
|
||||
start_dt = datetime.combine(start_date, datetime_min_time)
|
||||
end_dt = datetime.combine(end_date, datetime_min_time)
|
||||
worked = self.employee_id._get_work_days_data_batch(start_dt, end_dt, calendar=self.employee_id.resource_calendar_id)\
|
||||
[self.employee_id.id]['hours']
|
||||
if start_period != start_date or end_period != end_date:
|
||||
start_dt = datetime.combine(start_period, datetime.min.time())
|
||||
end_dt = datetime.combine(end_period, datetime.min.time())
|
||||
start_dt = datetime.combine(start_period, datetime_min_time)
|
||||
end_dt = datetime.combine(end_period, datetime_min_time)
|
||||
planned_worked = self.employee_id._get_work_days_data_batch(start_dt, end_dt, calendar=self.employee_id.resource_calendar_id)\
|
||||
[self.employee_id.id]['hours']
|
||||
else:
|
||||
@@ -417,10 +421,10 @@ class HolidaysAllocation(models.Model):
|
||||
else:
|
||||
added_value = level.added_value
|
||||
# Convert time in hours to time in days in case the level is encoded in hours
|
||||
if level.added_value_type == 'hours':
|
||||
if level.added_value_type == 'hour':
|
||||
added_value = added_value / (self.employee_id.sudo().resource_id.calendar_id.hours_per_day or HOURS_PER_DAY)
|
||||
period_prorata = 1
|
||||
if (start_period != start_date or end_period != end_date) and not level.is_based_on_worked_time:
|
||||
if (start_period != start_date or end_period != end_date) and not level.accrual_plan_id.is_based_on_worked_time:
|
||||
period_days = (end_period - start_period)
|
||||
call_days = (end_date - start_date)
|
||||
period_prorata = min(1, call_days / period_days) if period_days else 1
|
||||
@@ -438,83 +442,94 @@ class HolidaysAllocation(models.Model):
|
||||
level_ids = allocation.accrual_plan_id.level_ids.sorted('sequence')
|
||||
if not level_ids:
|
||||
continue
|
||||
# "cache" leaves taken, as it gets recomputed every time allocation.number_of_days is assigned to. Without this,
|
||||
# every loop will take 1+ second. It can be removed if computes don't chain in a way to always reassign accrual plan
|
||||
# even if the value doesn't change. This is the best performance atm.
|
||||
first_level = level_ids[0]
|
||||
first_level_start_date = allocation.date_from + get_timedelta(first_level.start_count, first_level.start_type)
|
||||
leaves_taken = allocation.leaves_taken if first_level.added_value_type == "day" else allocation.leaves_taken / (self.employee_id.sudo().resource_id.calendar_id.hours_per_day or HOURS_PER_DAY)
|
||||
# first time the plan is run, initialize nextcall and take carryover / level transition into account
|
||||
if not allocation.nextcall:
|
||||
first_level = level_ids[0]
|
||||
first_level_start_date = allocation.date_from + get_timedelta(first_level.start_count, first_level.start_type)
|
||||
# Accrual plan is not configured properly or has not started
|
||||
if date_to < first_level_start_date:
|
||||
# Accrual plan is not configured properly or has not started
|
||||
continue
|
||||
allocation.lastcall = max(allocation.lastcall, first_level_start_date)
|
||||
allocation.nextcall = first_level._get_next_date(allocation.lastcall)
|
||||
# adjust nextcall for carryover
|
||||
carryover_date = allocation._get_carryover_date(allocation.nextcall)
|
||||
allocation.nextcall = min(carryover_date, allocation.nextcall)
|
||||
# adjust nextcall for level_transition
|
||||
if len(level_ids) > 1:
|
||||
second_level_start_date = allocation.date_from + get_timedelta(level_ids[1].start_count, level_ids[1].start_type)
|
||||
allocation.nextcall = min(second_level_start_date, allocation.nextcall)
|
||||
allocation._message_log(body=first_allocation)
|
||||
days_added_per_level = defaultdict(lambda: 0)
|
||||
(current_level, current_level_idx) = (False, 0)
|
||||
current_level_maximum_leave = 0.0
|
||||
# all subsequent runs, at every loop:
|
||||
# get current level and normal period boundaries, then set nextcall, adjusted for level transition and carryover
|
||||
# add days, trimmed if there is a maximum_leave
|
||||
while allocation.nextcall <= date_to:
|
||||
(current_level, current_level_idx) = allocation._get_current_accrual_plan_level_id(allocation.nextcall)
|
||||
if not current_level:
|
||||
break
|
||||
current_level_maximum_leave = current_level.maximum_leave if current_level.added_value_type == "days" else current_level.maximum_leave / (allocation.employee_id.sudo().resource_id.calendar_id.hours_per_day or HOURS_PER_DAY)
|
||||
if current_level.cap_accrued_time:
|
||||
current_level_maximum_leave = current_level.maximum_leave if current_level.added_value_type == "day" else current_level.maximum_leave / (allocation.employee_id.sudo().resource_id.calendar_id.hours_per_day or HOURS_PER_DAY)
|
||||
nextcall = current_level._get_next_date(allocation.nextcall)
|
||||
# Since _get_previous_date returns the given date if it corresponds to a call date
|
||||
# this will always return lastcall except possibly on the first call
|
||||
# this is used to prorate the first number of days given to the employee
|
||||
period_start = current_level._get_previous_date(allocation.lastcall)
|
||||
period_end = current_level._get_next_date(allocation.lastcall)
|
||||
# Also prorate this accrual in the event that we are passing from one level to another
|
||||
# There are 2 cases where nextcall could be closer than the normal period:
|
||||
# 1. Passing from one level to another, if mode is set to 'immediately'
|
||||
if current_level_idx < (len(level_ids) - 1) and allocation.accrual_plan_id.transition_mode == 'immediately':
|
||||
next_level = level_ids[current_level_idx + 1]
|
||||
current_level_last_date = allocation.date_from + get_timedelta(next_level.start_count, next_level.start_type)
|
||||
if allocation.nextcall != current_level_last_date:
|
||||
nextcall = min(nextcall, current_level_last_date)
|
||||
# We have to check for end of year actions if it is within our period
|
||||
# since we can create retroactive allocations.
|
||||
if allocation.lastcall.year < allocation.nextcall.year and\
|
||||
current_level.action_with_unused_accruals == 'postponed' and\
|
||||
current_level.postpone_max_days > 0:
|
||||
# Compute number of days kept
|
||||
allocation_days = allocation.number_of_days - allocation.leaves_taken
|
||||
allowed_to_keep = max(0, current_level.postpone_max_days - allocation_days)
|
||||
number_of_days = min(allocation_days, current_level.postpone_max_days)
|
||||
allocation.number_of_days = number_of_days + allocation.leaves_taken
|
||||
total_gained_days = sum(days_added_per_level.values())
|
||||
days_added_per_level.clear()
|
||||
days_added_per_level[current_level] = min(total_gained_days, allowed_to_keep)
|
||||
gained_days = allocation._process_accrual_plan_level(
|
||||
current_level, period_start, allocation.lastcall, period_end, allocation.nextcall)
|
||||
days_added_per_level[current_level] += gained_days
|
||||
if current_level_maximum_leave > 0 and sum(days_added_per_level.values()) > current_level_maximum_leave:
|
||||
days_added_per_level[current_level] -= sum(days_added_per_level.values()) - current_level_maximum_leave
|
||||
# 2. On carry-over date
|
||||
carryover_date = allocation._get_carryover_date(allocation.nextcall)
|
||||
if allocation.nextcall < carryover_date < nextcall:
|
||||
nextcall = min(nextcall, carryover_date)
|
||||
if not allocation.already_accrued:
|
||||
allocation._add_days_to_allocation(current_level, current_level_maximum_leave, leaves_taken, period_start, period_end)
|
||||
# if it's the carry-over date, adjust days using current level's carry-over policy, then continue
|
||||
if allocation.nextcall == carryover_date:
|
||||
if current_level.action_with_unused_accruals in ['lost', 'maximum']:
|
||||
allocation_days = allocation.number_of_days + leaves_taken
|
||||
allocation_max_days = current_level.postpone_max_days + leaves_taken
|
||||
allocation.number_of_days = min(allocation_days, allocation_max_days)
|
||||
|
||||
allocation.lastcall = allocation.nextcall
|
||||
allocation.nextcall = nextcall
|
||||
allocation.already_accrued = False
|
||||
if force_period and allocation.nextcall > date_to:
|
||||
allocation.nextcall = date_to
|
||||
force_period = False
|
||||
|
||||
if days_added_per_level:
|
||||
number_of_days_to_add = allocation.number_of_days + sum(days_added_per_level.values())
|
||||
max_allocation_days = current_level_maximum_leave + (allocation.leaves_taken if allocation.type_request_unit != "hour" else allocation.leaves_taken / (allocation.employee_id.sudo().resource_id.calendar_id.hours_per_day or HOURS_PER_DAY))
|
||||
# Let's assume the limit of the last level is the correct one
|
||||
allocation.number_of_days = min(number_of_days_to_add, max_allocation_days) if current_level_maximum_leave > 0 else number_of_days_to_add
|
||||
# if plan.accrued_gain_time == 'start', process next period and set flag 'already_accrued', this will skip adding days
|
||||
# once, preventing double allocation.
|
||||
if allocation.accrual_plan_id.accrued_gain_time == 'start':
|
||||
# check that we are at the start of a period, not on a carry-over or level transition date
|
||||
current_level = current_level or allocation.accrual_plan_id.level_ids[0]
|
||||
period_start = current_level._get_previous_date(allocation.lastcall)
|
||||
if allocation.lastcall != period_start:
|
||||
continue
|
||||
if current_level.cap_accrued_time:
|
||||
current_level_maximum_leave = current_level.maximum_leave if current_level.added_value_type == "day" else current_level.maximum_leave / (allocation.employee_id.sudo().resource_id.calendar_id.hours_per_day or HOURS_PER_DAY)
|
||||
allocation._add_days_to_allocation(current_level, current_level_maximum_leave, leaves_taken, allocation.lastcall, allocation.nextcall)
|
||||
allocation.already_accrued = True
|
||||
|
||||
@api.model
|
||||
def _update_accrual(self):
|
||||
"""
|
||||
Method called by the cron task in order to increment the number_of_days when
|
||||
necessary.
|
||||
Method called by the cron task in order to increment the number_of_days when
|
||||
necessary.
|
||||
"""
|
||||
# Get the current date to determine the start and end of the accrual period
|
||||
today = datetime.combine(fields.Date.today(), time(0, 0, 0))
|
||||
this_year_first_day = (today + relativedelta(day=1, month=1)).date()
|
||||
end_of_year_allocations = self.search(
|
||||
[('allocation_type', '=', 'accrual'), ('state', '=', 'validate'), ('accrual_plan_id', '!=', False), ('employee_id', '!=', False),
|
||||
'|', ('date_to', '=', False), ('date_to', '>', fields.Datetime.now()), ('lastcall', '<', this_year_first_day)])
|
||||
end_of_year_allocations._end_of_year_accrual()
|
||||
end_of_year_allocations.flush_model()
|
||||
allocations = self.search(
|
||||
[('allocation_type', '=', 'accrual'), ('state', '=', 'validate'), ('accrual_plan_id', '!=', False), ('employee_id', '!=', False),
|
||||
allocations = self.search([
|
||||
('allocation_type', '=', 'accrual'), ('state', '=', 'validate'),
|
||||
('accrual_plan_id', '!=', False), ('employee_id', '!=', False),
|
||||
'|', ('date_to', '=', False), ('date_to', '>', fields.Datetime.now()),
|
||||
'|', ('nextcall', '=', False), ('nextcall', '<=', today)])
|
||||
allocations._process_accrual_plans()
|
||||
@@ -757,6 +772,22 @@ class HolidaysAllocation(models.Model):
|
||||
elif not self.number_of_days_display:
|
||||
self.number_of_days = 1.0
|
||||
|
||||
# Allows user to simulate how many days an accrual plan would give from a certain start date.
|
||||
# it uses the actual computation function but resets values of lastcall, nextcall and nbr of days
|
||||
# before every run, as if it was run from date_from, after an optional change in the allocation value
|
||||
# the user can simply confirm and validate the allocation. The record is in correct state for the next
|
||||
# call of the cron job.
|
||||
@api.onchange('date_from', 'accrual_plan_id')
|
||||
def _onchange_date_from(self):
|
||||
now = date.today()
|
||||
if self.allocation_type != 'accrual' or self.state == 'validate' or not self.accrual_plan_id\
|
||||
or not self.employee_id or not (not self.date_to or self.date_to > now):
|
||||
return
|
||||
self.lastcall = self.date_from
|
||||
self.nextcall = False
|
||||
self.number_of_days_display = 0.0
|
||||
self._process_accrual_plans()
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Activity methods
|
||||
# ------------------------------------------------------------
|
||||
|
||||
@@ -101,7 +101,6 @@ class HolidaysType(models.Model):
|
||||
accruals_ids = fields.One2many('hr.leave.accrual.plan', 'time_off_type_id')
|
||||
accrual_count = fields.Float(compute="_compute_accrual_count", string="Accruals count")
|
||||
|
||||
|
||||
@api.model
|
||||
def _search_valid(self, operator, value):
|
||||
""" Returns leave_type ids for which a valid allocation exists
|
||||
@@ -134,7 +133,6 @@ class HolidaysType(models.Model):
|
||||
|
||||
return [('id', new_operator, [x['holiday_status_id'] for x in self._cr.dictfetchall()])]
|
||||
|
||||
|
||||
@api.depends('requires_allocation')
|
||||
def _compute_valid(self):
|
||||
date_to = self._context.get('default_date_to', fields.Datetime.today())
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { X2ManyField, x2ManyField } from "@web/views/fields/x2many/x2many_field";
|
||||
|
||||
export class AccrualLevelsX2ManyField extends X2ManyField {};
|
||||
AccrualLevelsX2ManyField.template = "hr_holidays.AccrualLevelsX2ManyField";
|
||||
|
||||
export const accrualLevelsX2ManyField = {
|
||||
...x2ManyField,
|
||||
component: AccrualLevelsX2ManyField,
|
||||
};
|
||||
|
||||
registry.category("fields").add("accrual_levels_one2many", accrualLevelsX2ManyField);
|
||||
@@ -112,3 +112,6 @@ $o-hr-holidays-border-color: map-get($grays, '300');
|
||||
}
|
||||
}
|
||||
}
|
||||
.o_radio_item .o_form_label {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="hr_holidays.AccrualLevelsX2ManyField" t-inherit="web.X2ManyField" t-inherit-mode="primary" owl="1">
|
||||
<xpath expr="//div[hasclass('o_x2m_control_panel')]/.." position="before">
|
||||
<xpath expr="//ListRenderer" position="move"/>
|
||||
<xpath expr="//KanbanRenderer" position="move"/>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -21,6 +21,13 @@ class TestAccrualAllocations(TestHrHolidaysCommon):
|
||||
'requires_allocation': 'yes',
|
||||
'allocation_validation_type': 'officer',
|
||||
})
|
||||
cls.leave_type_hour = cls.env['hr.leave.type'].create({
|
||||
'name': 'Paid Time Off',
|
||||
'time_type': 'leave',
|
||||
'requires_allocation': 'yes',
|
||||
'allocation_validation_type': 'officer',
|
||||
'request_unit': 'hour',
|
||||
})
|
||||
|
||||
def setAllocationCreateDate(self, allocation_id, date):
|
||||
""" This method is a hack in order to be able to define/redefine the create_date
|
||||
@@ -35,15 +42,16 @@ class TestAccrualAllocations(TestHrHolidaysCommon):
|
||||
""" % (date, allocation_id))
|
||||
|
||||
def test_frequency_daily(self):
|
||||
with freeze_time(datetime.date(2017, 12, 5)):
|
||||
with freeze_time("2017-12-5"):
|
||||
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
|
||||
'name': 'Accrual Plan For Test',
|
||||
'level_ids': [(0, 0, {
|
||||
'start_count': 1,
|
||||
'start_type': 'day',
|
||||
'added_value': 1,
|
||||
'added_value_type': 'days',
|
||||
'added_value_type': 'day',
|
||||
'frequency': 'daily',
|
||||
'cap_accrued_time': True,
|
||||
'maximum_leave': 10000
|
||||
})],
|
||||
})
|
||||
@@ -72,15 +80,16 @@ class TestAccrualAllocations(TestHrHolidaysCommon):
|
||||
self.assertEqual(allocation.number_of_days, 1, 'There should be only 1 day allocated.')
|
||||
|
||||
def test_frequency_weekly(self):
|
||||
with freeze_time(datetime.date(2017, 12, 5)):
|
||||
with freeze_time("2017-12-5"):
|
||||
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
|
||||
'name': 'Accrual Plan For Test',
|
||||
'level_ids': [(0, 0, {
|
||||
'added_value_type': 'day',
|
||||
'start_count': 1,
|
||||
'start_type': 'day',
|
||||
'added_value': 1,
|
||||
'added_value_type': 'days',
|
||||
'frequency': 'weekly',
|
||||
'cap_accrued_time': True,
|
||||
'maximum_leave': 10000
|
||||
})],
|
||||
})
|
||||
@@ -105,7 +114,7 @@ class TestAccrualAllocations(TestHrHolidaysCommon):
|
||||
with freeze_time(nextWeek):
|
||||
allocation._update_accrual()
|
||||
nextWeek = datetime.date.today() + relativedelta(days=1, weekday=0)
|
||||
#Prorated
|
||||
# Prorated
|
||||
self.assertAlmostEqual(allocation.number_of_days, 0.2857, 4, 'There should be 0.2857 day allocated.')
|
||||
self.assertEqual(allocation.nextcall, nextWeek, 'The next call date of the cron should be in 2 weeks')
|
||||
|
||||
@@ -120,13 +129,14 @@ class TestAccrualAllocations(TestHrHolidaysCommon):
|
||||
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
|
||||
'name': 'Accrual Plan For Test',
|
||||
'level_ids': [(0, 0, {
|
||||
'added_value_type': 'day',
|
||||
'start_count': 1,
|
||||
'start_type': 'day',
|
||||
'added_value': 1,
|
||||
'added_value_type': 'days',
|
||||
'frequency': 'bimonthly',
|
||||
'first_day': 1,
|
||||
'second_day': 15,
|
||||
'cap_accrued_time': True,
|
||||
'maximum_leave': 10000,
|
||||
})],
|
||||
})
|
||||
@@ -151,13 +161,13 @@ class TestAccrualAllocations(TestHrHolidaysCommon):
|
||||
with freeze_time(next_date):
|
||||
next_date = datetime.date(2021, 10, 1)
|
||||
allocation._update_accrual()
|
||||
#Prorated
|
||||
# Prorated
|
||||
self.assertAlmostEqual(allocation.number_of_days, 0.7857, 4, 'There should be 0.7857 day allocated.')
|
||||
self.assertEqual(allocation.nextcall, next_date, 'The next call date of the cron should be October 1st')
|
||||
|
||||
with freeze_time(next_date):
|
||||
allocation._update_accrual()
|
||||
#Not Prorated
|
||||
# Not Prorated
|
||||
self.assertAlmostEqual(allocation.number_of_days, 1.7857, 4, 'There should be 1.7857 day allocated.')
|
||||
|
||||
def test_frequency_monthly(self):
|
||||
@@ -165,11 +175,12 @@ class TestAccrualAllocations(TestHrHolidaysCommon):
|
||||
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
|
||||
'name': 'Accrual Plan For Test',
|
||||
'level_ids': [(0, 0, {
|
||||
'added_value_type': 'day',
|
||||
'start_count': 1,
|
||||
'start_type': 'day',
|
||||
'added_value': 1,
|
||||
'added_value_type': 'days',
|
||||
'frequency': 'monthly',
|
||||
'cap_accrued_time': True,
|
||||
'maximum_leave': 10000
|
||||
})],
|
||||
})
|
||||
@@ -203,15 +214,16 @@ class TestAccrualAllocations(TestHrHolidaysCommon):
|
||||
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
|
||||
'name': 'Accrual Plan For Test',
|
||||
'level_ids': [(0, 0, {
|
||||
'added_value_type': 'day',
|
||||
'start_count': 1,
|
||||
'start_type': 'day',
|
||||
'added_value': 1,
|
||||
'added_value_type': 'days',
|
||||
'frequency': 'biyearly',
|
||||
'cap_accrued_time': True,
|
||||
'maximum_leave': 10000,
|
||||
})],
|
||||
})
|
||||
#this sets up an accrual on the 1st of January and the 1st of July
|
||||
# this sets up an accrual on the 1st of January and the 1st of July
|
||||
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
|
||||
'name': 'Accrual allocation for employee',
|
||||
'accrual_plan_id': accrual_plan.id,
|
||||
@@ -246,15 +258,16 @@ class TestAccrualAllocations(TestHrHolidaysCommon):
|
||||
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
|
||||
'name': 'Accrual Plan For Test',
|
||||
'level_ids': [(0, 0, {
|
||||
'added_value_type': 'day',
|
||||
'start_count': 1,
|
||||
'start_type': 'day',
|
||||
'added_value': 1,
|
||||
'added_value_type': 'days',
|
||||
'frequency': 'yearly',
|
||||
'cap_accrued_time': True,
|
||||
'maximum_leave': 10000,
|
||||
})],
|
||||
})
|
||||
#this sets up an accrual on the 1st of January
|
||||
# this sets up an accrual on the 1st of January
|
||||
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
|
||||
'name': 'Accrual allocation for employee',
|
||||
'accrual_plan_id': accrual_plan.id,
|
||||
@@ -319,24 +332,26 @@ class TestAccrualAllocations(TestHrHolidaysCommon):
|
||||
accrual_plan_not_based_on_worked_time = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
|
||||
'name': 'Accrual Plan For Test',
|
||||
'level_ids': [(0, 0, {
|
||||
'added_value_type': 'day',
|
||||
'start_count': 1,
|
||||
'start_type': 'day',
|
||||
'added_value': 5,
|
||||
'added_value_type': 'days',
|
||||
'frequency': 'weekly',
|
||||
'cap_accrued_time': True,
|
||||
'maximum_leave': 10000,
|
||||
})],
|
||||
})
|
||||
accrual_plan_based_on_worked_time = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
|
||||
'name': 'Accrual Plan For Test',
|
||||
'is_based_on_worked_time': True,
|
||||
'level_ids': [(0, 0, {
|
||||
'added_value_type': 'day',
|
||||
'start_count': 1,
|
||||
'start_type': 'day',
|
||||
'added_value': 5,
|
||||
'added_value_type': 'days',
|
||||
'frequency': 'weekly',
|
||||
'cap_accrued_time': True,
|
||||
'maximum_leave': 10000,
|
||||
'is_based_on_worked_time': True,
|
||||
})],
|
||||
})
|
||||
allocation_not_worked_time = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
|
||||
@@ -401,15 +416,16 @@ class TestAccrualAllocations(TestHrHolidaysCommon):
|
||||
self.assertEqual(allocation_worked_time.nextcall, next_date, 'The next call date of the cron should be September 20th')
|
||||
|
||||
def test_check_max_value(self):
|
||||
with freeze_time(datetime.date(2017, 12, 5)):
|
||||
with freeze_time("2017-12-5"):
|
||||
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
|
||||
'name': 'Accrual Plan For Test',
|
||||
'level_ids': [(0, 0, {
|
||||
'added_value_type': 'day',
|
||||
'start_count': 1,
|
||||
'start_type': 'day',
|
||||
'added_value': 1,
|
||||
'added_value_type': 'days',
|
||||
'frequency': 'daily',
|
||||
'cap_accrued_time': True,
|
||||
'maximum_leave': 1,
|
||||
})],
|
||||
})
|
||||
@@ -436,20 +452,21 @@ class TestAccrualAllocations(TestHrHolidaysCommon):
|
||||
with freeze_time(nextcall):
|
||||
allocation._update_accrual()
|
||||
nextcall = datetime.date.today() + relativedelta(days=1)
|
||||
#The maximum value is 1 so this shouldn't change anything
|
||||
# The maximum value is 1 so this shouldn't change anything
|
||||
allocation._update_accrual()
|
||||
self.assertEqual(allocation.number_of_days, 1, 'There should be only 1 day allocated.')
|
||||
|
||||
def test_check_max_value_hours(self):
|
||||
with freeze_time(datetime.date(2017, 12, 5)):
|
||||
with freeze_time("2017-12-5"):
|
||||
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
|
||||
'name': 'Accrual Plan For Test',
|
||||
'level_ids': [(0, 0, {
|
||||
'added_value_type': 'hour',
|
||||
'start_count': 1,
|
||||
'start_type': 'day',
|
||||
'added_value': 1,
|
||||
'added_value_type': 'hours',
|
||||
'frequency': 'daily',
|
||||
'cap_accrued_time': True,
|
||||
'maximum_leave': 4,
|
||||
})],
|
||||
})
|
||||
@@ -476,29 +493,30 @@ class TestAccrualAllocations(TestHrHolidaysCommon):
|
||||
with freeze_time(nextcall):
|
||||
allocation._update_accrual()
|
||||
nextcall = datetime.date.today() + relativedelta(days=1)
|
||||
#The maximum value is 1 so this shouldn't change anything
|
||||
# The maximum value is 1 so this shouldn't change anything
|
||||
allocation._update_accrual()
|
||||
self.assertEqual(allocation.number_of_days, 0.5, 'There should be only 0.5 days allocated.')
|
||||
|
||||
def test_accrual_transition_immediately(self):
|
||||
with freeze_time(datetime.date(2017, 12, 5)):
|
||||
#1 accrual with 2 levels and level transition immediately
|
||||
with freeze_time("2017-12-5"):
|
||||
# 1 accrual with 2 levels and level transition immediately
|
||||
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
|
||||
'name': 'Accrual Plan For Test',
|
||||
'transition_mode': 'immediately',
|
||||
'level_ids': [(0, 0, {
|
||||
'added_value_type': 'day',
|
||||
'start_count': 1,
|
||||
'start_type': 'day',
|
||||
'added_value': 1,
|
||||
'added_value_type': 'days',
|
||||
'frequency': 'weekly',
|
||||
'cap_accrued_time': True,
|
||||
'maximum_leave': 1,
|
||||
}), (0, 0, {
|
||||
'start_count': 10,
|
||||
'start_type': 'day',
|
||||
'added_value': 1,
|
||||
'added_value_type': 'days',
|
||||
'frequency': 'weekly',
|
||||
'cap_accrued_time': True,
|
||||
'maximum_leave': 1,
|
||||
})],
|
||||
})
|
||||
@@ -517,24 +535,25 @@ class TestAccrualAllocations(TestHrHolidaysCommon):
|
||||
self.assertEqual(allocation._get_current_accrual_plan_level_id(next_date)[0], second_level, 'The second level should be selected')
|
||||
|
||||
def test_accrual_transition_after_period(self):
|
||||
with freeze_time(datetime.date(2017, 12, 5)):
|
||||
with freeze_time("2017-12-5"):
|
||||
# 1 accrual with 2 levels and level transition after
|
||||
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
|
||||
'name': 'Accrual Plan For Test',
|
||||
'transition_mode': 'end_of_accrual',
|
||||
'level_ids': [(0, 0, {
|
||||
'added_value_type': 'day',
|
||||
'start_count': 1,
|
||||
'start_type': 'day',
|
||||
'added_value': 1,
|
||||
'added_value_type': 'days',
|
||||
'frequency': 'weekly',
|
||||
'cap_accrued_time': True,
|
||||
'maximum_leave': 1,
|
||||
}), (0, 0, {
|
||||
'start_count': 10,
|
||||
'start_type': 'day',
|
||||
'added_value': 1,
|
||||
'added_value_type': 'days',
|
||||
'frequency': 'weekly',
|
||||
'cap_accrued_time': True,
|
||||
'maximum_leave': 1,
|
||||
})],
|
||||
})
|
||||
@@ -553,16 +572,17 @@ class TestAccrualAllocations(TestHrHolidaysCommon):
|
||||
self.assertEqual(allocation._get_current_accrual_plan_level_id(next_date)[0], second_level, 'The second level should be selected')
|
||||
|
||||
def test_unused_accrual_lost(self):
|
||||
#1 accrual with 2 levels and level transition immediately
|
||||
with freeze_time('2021-09-01'):
|
||||
# 1 accrual with 2 levels and level transition immediately
|
||||
with freeze_time('2021-12-15'):
|
||||
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
|
||||
'name': 'Accrual Plan For Test',
|
||||
'level_ids': [(0, 0, {
|
||||
'added_value_type': 'day',
|
||||
'start_count': 1,
|
||||
'start_type': 'day',
|
||||
'added_value': 1,
|
||||
'added_value_type': 'days',
|
||||
'frequency': 'daily',
|
||||
'cap_accrued_time': True,
|
||||
'maximum_leave': 1,
|
||||
'action_with_unused_accruals': 'lost',
|
||||
})],
|
||||
@@ -580,25 +600,26 @@ class TestAccrualAllocations(TestHrHolidaysCommon):
|
||||
|
||||
# Reset the cron's lastcall
|
||||
accrual_cron = self.env['ir.cron'].sudo().env.ref('hr_holidays.hr_leave_allocation_cron_accrual')
|
||||
accrual_cron.lastcall = datetime.date(2021, 9, 1)
|
||||
accrual_cron.lastcall = datetime.date(2021, 12, 15)
|
||||
with freeze_time('2022-01-01'):
|
||||
allocation._update_accrual()
|
||||
self.assertEqual(allocation.number_of_days, 1, 'The number of days should be reset')
|
||||
self.assertEqual(allocation.number_of_days, 0, 'The number of days should be reset')
|
||||
|
||||
def test_unused_accrual_postponed(self):
|
||||
# 1 accrual with 2 levels and level transition after
|
||||
# This also tests retroactivity
|
||||
with freeze_time('2021-09-01'):
|
||||
with freeze_time('2021-12-15'):
|
||||
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
|
||||
'name': 'Accrual Plan For Test',
|
||||
'level_ids': [(0, 0, {
|
||||
'added_value_type': 'day',
|
||||
'start_count': 1,
|
||||
'start_type': 'day',
|
||||
'added_value': 1,
|
||||
'added_value_type': 'days',
|
||||
'frequency': 'daily',
|
||||
'cap_accrued_time': True,
|
||||
'maximum_leave': 25,
|
||||
'action_with_unused_accruals': 'postponed',
|
||||
'action_with_unused_accruals': 'all',
|
||||
})],
|
||||
})
|
||||
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
|
||||
@@ -614,22 +635,24 @@ class TestAccrualAllocations(TestHrHolidaysCommon):
|
||||
|
||||
# Reset the cron's lastcall
|
||||
accrual_cron = self.env['ir.cron'].sudo().env.ref('hr_holidays.hr_leave_allocation_cron_accrual')
|
||||
accrual_cron.lastcall = datetime.date(2021, 9, 1)
|
||||
accrual_cron.lastcall = datetime.date(2021, 12, 15)
|
||||
with freeze_time('2022-01-01'):
|
||||
allocation._update_accrual()
|
||||
self.assertEqual(allocation.number_of_days, 25, 'The maximum number of days should be reached and kept.')
|
||||
|
||||
def test_unused_accrual_postponed_2(self):
|
||||
with freeze_time('2021-01-01'):
|
||||
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
|
||||
'name': 'Accrual Plan For Test',
|
||||
'level_ids': [(0, 0, {
|
||||
'added_value_type': 'day',
|
||||
'start_count': 0,
|
||||
'start_type': 'day',
|
||||
'added_value': 2,
|
||||
'added_value_type': 'days',
|
||||
'frequency': 'yearly',
|
||||
'cap_accrued_time': True,
|
||||
'maximum_leave': 100,
|
||||
'action_with_unused_accruals': 'postponed',
|
||||
'action_with_unused_accruals': 'maximum',
|
||||
'postpone_max_days': 10,
|
||||
})],
|
||||
})
|
||||
@@ -649,22 +672,24 @@ class TestAccrualAllocations(TestHrHolidaysCommon):
|
||||
accrual_cron.lastcall = datetime.date(2021, 1, 1)
|
||||
with freeze_time('2023-01-26'):
|
||||
allocation._update_accrual()
|
||||
self.assertEqual(allocation.number_of_days, 6, 'The maximum number of days should be reached and kept.')
|
||||
self.assertEqual(allocation.number_of_days, 4, 'The maximum number of days should be reached and kept.')
|
||||
|
||||
def test_unused_accrual_postponed_limit(self):
|
||||
# 1 accrual with 2 levels and level transition after
|
||||
# This also tests retroactivity
|
||||
with freeze_time('2021-09-01'):
|
||||
with freeze_time('2021-12-15'):
|
||||
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
|
||||
'name': 'Accrual Plan For Test',
|
||||
'accrued_gain_time': 'start',
|
||||
'level_ids': [(0, 0, {
|
||||
'added_value_type': 'day',
|
||||
'start_count': 1,
|
||||
'start_type': 'day',
|
||||
'added_value': 1,
|
||||
'added_value_type': 'days',
|
||||
'frequency': 'daily',
|
||||
'cap_accrued_time': True,
|
||||
'maximum_leave': 25,
|
||||
'action_with_unused_accruals': 'postponed',
|
||||
'action_with_unused_accruals': 'maximum',
|
||||
'postpone_max_days': 15,
|
||||
})],
|
||||
})
|
||||
@@ -681,22 +706,24 @@ class TestAccrualAllocations(TestHrHolidaysCommon):
|
||||
|
||||
# Reset the cron's lastcall
|
||||
accrual_cron = self.env['ir.cron'].sudo().env.ref('hr_holidays.hr_leave_allocation_cron_accrual')
|
||||
accrual_cron.lastcall = datetime.date(2021, 9, 1)
|
||||
accrual_cron.lastcall = datetime.date(2021, 12, 15)
|
||||
with freeze_time('2022-01-01'):
|
||||
allocation._update_accrual()
|
||||
self.assertEqual(allocation.number_of_days, 16, 'The maximum number of days should be reached and kept.')
|
||||
|
||||
def test_unused_accrual_postponed_limit_2(self):
|
||||
with freeze_time('2021-01-01'):
|
||||
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
|
||||
'name': 'Accrual Plan For Test',
|
||||
'level_ids': [(0, 0, {
|
||||
'added_value_type': 'day',
|
||||
'start_count': 0,
|
||||
'start_type': 'day',
|
||||
'added_value': 15,
|
||||
'added_value_type': 'days',
|
||||
'frequency': 'yearly',
|
||||
'cap_accrued_time': True,
|
||||
'maximum_leave': 100,
|
||||
'action_with_unused_accruals': 'postponed',
|
||||
'action_with_unused_accruals': 'maximum',
|
||||
'postpone_max_days': 7,
|
||||
})],
|
||||
})
|
||||
@@ -716,7 +743,7 @@ class TestAccrualAllocations(TestHrHolidaysCommon):
|
||||
accrual_cron.lastcall = datetime.date(2021, 1, 1)
|
||||
with freeze_time('2023-01-26'):
|
||||
allocation._update_accrual()
|
||||
self.assertEqual(allocation.number_of_days, 22, 'The maximum number of days should be reached and kept.')
|
||||
self.assertEqual(allocation.number_of_days, 7, 'The maximum number of days should be reached and kept.')
|
||||
|
||||
def test_accrual_skipped_period(self):
|
||||
# Test that when an allocation is made in the past and the second level is technically reached
|
||||
@@ -724,21 +751,22 @@ class TestAccrualAllocations(TestHrHolidaysCommon):
|
||||
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
|
||||
'name': 'Accrual Plan For Test',
|
||||
'level_ids': [(0, 0, {
|
||||
'added_value_type': 'day',
|
||||
'start_count': 0,
|
||||
'start_type': 'day',
|
||||
'added_value': 15,
|
||||
'added_value_type': 'days',
|
||||
'frequency': 'biyearly',
|
||||
'cap_accrued_time': True,
|
||||
'maximum_leave': 100,
|
||||
'action_with_unused_accruals': 'postponed',
|
||||
'action_with_unused_accruals': 'all',
|
||||
}), (0, 0, {
|
||||
'start_count': 4,
|
||||
'start_type': 'month',
|
||||
'added_value': 10,
|
||||
'added_value_type': 'days',
|
||||
'frequency': 'biyearly',
|
||||
'cap_accrued_time': True,
|
||||
'maximum_leave': 500,
|
||||
'action_with_unused_accruals': 'postponed',
|
||||
'action_with_unused_accruals': 'all',
|
||||
})],
|
||||
})
|
||||
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
|
||||
@@ -760,31 +788,32 @@ class TestAccrualAllocations(TestHrHolidaysCommon):
|
||||
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
|
||||
'name': 'Accrual Plan For Test',
|
||||
'level_ids': [(0, 0, {
|
||||
'added_value_type': 'day',
|
||||
'start_count': 2,
|
||||
'start_type': 'month',
|
||||
'added_value': 3,
|
||||
'added_value_type': 'days',
|
||||
'frequency': 'monthly',
|
||||
'cap_accrued_time': True,
|
||||
'maximum_leave': 3,
|
||||
'action_with_unused_accruals': 'postponed',
|
||||
'action_with_unused_accruals': 'all',
|
||||
'first_day': 31,
|
||||
}), (0, 0, {
|
||||
'start_count': 3,
|
||||
'start_type': 'month',
|
||||
'added_value': 6,
|
||||
'added_value_type': 'days',
|
||||
'frequency': 'monthly',
|
||||
'cap_accrued_time': True,
|
||||
'maximum_leave': 6,
|
||||
'action_with_unused_accruals': 'postponed',
|
||||
'action_with_unused_accruals': 'all',
|
||||
'first_day': 31,
|
||||
}), (0, 0, {
|
||||
'start_count': 4,
|
||||
'start_type': 'month',
|
||||
'added_value': 1,
|
||||
'added_value_type': 'days',
|
||||
'frequency': 'monthly',
|
||||
'cap_accrued_time': True,
|
||||
'maximum_leave': 100,
|
||||
'action_with_unused_accruals': 'postponed',
|
||||
'action_with_unused_accruals': 'all',
|
||||
'first_day': 31,
|
||||
})],
|
||||
})
|
||||
@@ -814,11 +843,12 @@ class TestAccrualAllocations(TestHrHolidaysCommon):
|
||||
'name': 'Accrual Plan For Test',
|
||||
'level_ids': [
|
||||
(0, 0, {
|
||||
'added_value_type': 'day',
|
||||
'start_count': 0,
|
||||
'start_type': 'day',
|
||||
'added_value': 1,
|
||||
'added_value_type': 'days',
|
||||
'frequency': 'monthly',
|
||||
'cap_accrued_time': True,
|
||||
'maximum_leave': 12,
|
||||
'action_with_unused_accruals': 'lost',
|
||||
}),
|
||||
@@ -826,8 +856,8 @@ class TestAccrualAllocations(TestHrHolidaysCommon):
|
||||
'start_count': 1,
|
||||
'start_type': 'year',
|
||||
'added_value': 1,
|
||||
'added_value_type': 'days',
|
||||
'frequency': 'monthly',
|
||||
'cap_accrued_time': True,
|
||||
'maximum_leave': 12,
|
||||
'action_with_unused_accruals': 'lost',
|
||||
}),
|
||||
@@ -846,18 +876,20 @@ class TestAccrualAllocations(TestHrHolidaysCommon):
|
||||
allocation.action_validate()
|
||||
with freeze_time('2022-4-4'):
|
||||
allocation._update_accrual()
|
||||
self.assertEqual(allocation.number_of_days, 4, "Invalid number of days")
|
||||
self.assertEqual(allocation.number_of_days, 3, "Invalid number of days")
|
||||
|
||||
def test_accrual_lost_first_january(self):
|
||||
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
|
||||
'name': 'Accrual Plan For Test',
|
||||
'accrued_gain_time': 'start',
|
||||
'level_ids': [
|
||||
(0, 0, {
|
||||
'added_value_type': 'day',
|
||||
'start_count': 0,
|
||||
'start_type': 'day',
|
||||
'added_value': 3,
|
||||
'added_value_type': 'days',
|
||||
'frequency': 'yearly',
|
||||
'cap_accrued_time': True,
|
||||
'maximum_leave': 12,
|
||||
'action_with_unused_accruals': 'lost',
|
||||
})
|
||||
@@ -882,11 +914,12 @@ class TestAccrualAllocations(TestHrHolidaysCommon):
|
||||
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
|
||||
'name': 'Accrual Plan For Test',
|
||||
'level_ids': [(0, 0, {
|
||||
'added_value_type': 'day',
|
||||
'start_count': 1,
|
||||
'start_type': 'day',
|
||||
'added_value': 1,
|
||||
'added_value_type': 'days',
|
||||
'frequency': 'daily',
|
||||
'cap_accrued_time': True,
|
||||
'maximum_leave': 5,
|
||||
})],
|
||||
})
|
||||
@@ -900,7 +933,7 @@ class TestAccrualAllocations(TestHrHolidaysCommon):
|
||||
'date_from': '2021-09-03',
|
||||
})
|
||||
|
||||
with freeze_time(datetime.date(2021, 10, 3)):
|
||||
with freeze_time("2021-10-3"):
|
||||
allocation.action_confirm()
|
||||
allocation.action_validate()
|
||||
allocation._update_accrual()
|
||||
@@ -911,12 +944,12 @@ class TestAccrualAllocations(TestHrHolidaysCommon):
|
||||
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
|
||||
'name': 'Accrual Plan For Test',
|
||||
'level_ids': [(0, 0, {
|
||||
'added_value_type': 'day',
|
||||
'start_count': 1,
|
||||
'start_type': 'day',
|
||||
'added_value': 1,
|
||||
'added_value_type': 'days',
|
||||
'frequency': 'daily',
|
||||
'maximum_leave': 0,
|
||||
'cap_accrued_time': False,
|
||||
})],
|
||||
})
|
||||
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
|
||||
@@ -929,7 +962,7 @@ class TestAccrualAllocations(TestHrHolidaysCommon):
|
||||
'date_from': '2021-09-03',
|
||||
})
|
||||
|
||||
with freeze_time(datetime.date(2021, 10, 3)):
|
||||
with freeze_time("2021-10-3"):
|
||||
allocation.action_confirm()
|
||||
allocation.action_validate()
|
||||
allocation._update_accrual()
|
||||
@@ -940,12 +973,13 @@ class TestAccrualAllocations(TestHrHolidaysCommon):
|
||||
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
|
||||
'name': 'Accrual Plan For Test',
|
||||
'level_ids': [(0, 0, {
|
||||
'added_value_type': 'day',
|
||||
'start_count': 0,
|
||||
'start_type': 'day',
|
||||
'added_value': 1,
|
||||
'added_value_type': 'days',
|
||||
'frequency': 'weekly',
|
||||
'week_day': 'mon',
|
||||
'cap_accrued_time': True,
|
||||
'maximum_leave': 5,
|
||||
})],
|
||||
})
|
||||
@@ -961,7 +995,7 @@ class TestAccrualAllocations(TestHrHolidaysCommon):
|
||||
allocation.action_confirm()
|
||||
allocation.action_validate()
|
||||
|
||||
with freeze_time(datetime.date(2022, 3, 2)):
|
||||
with freeze_time("2022-3-2"):
|
||||
allocation._update_accrual()
|
||||
|
||||
self.assertEqual(allocation.number_of_days, 5, "Maximum of 5 days accrued")
|
||||
@@ -975,20 +1009,21 @@ class TestAccrualAllocations(TestHrHolidaysCommon):
|
||||
})
|
||||
leave.action_validate()
|
||||
|
||||
with freeze_time(datetime.date(2022, 6, 1)):
|
||||
with freeze_time("2022-6-1"):
|
||||
allocation._update_accrual()
|
||||
self.assertEqual(allocation.number_of_days, 10, "Should accrue 5 additional days")
|
||||
|
||||
def test_accrual_leaves_taken_maximum_hours(self):
|
||||
def test_aaaccrual_leaves_taken_maximum_hours(self):
|
||||
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
|
||||
'name': 'Accrual Plan For Test',
|
||||
'level_ids': [(0, 0, {
|
||||
'added_value_type': 'hour',
|
||||
'start_count': 0,
|
||||
'start_type': 'day',
|
||||
'added_value': 1,
|
||||
'added_value_type': 'hours',
|
||||
'frequency': 'weekly',
|
||||
'week_day': 'mon',
|
||||
'cap_accrued_time': True,
|
||||
'maximum_leave': 10,
|
||||
})],
|
||||
})
|
||||
@@ -996,7 +1031,7 @@ class TestAccrualAllocations(TestHrHolidaysCommon):
|
||||
'name': 'Accrual allocation for employee',
|
||||
'accrual_plan_id': accrual_plan.id,
|
||||
'employee_id': self.employee_emp.id,
|
||||
'holiday_status_id': self.leave_type.id,
|
||||
'holiday_status_id': self.leave_type_hour.id,
|
||||
'number_of_days': 0,
|
||||
'allocation_type': 'accrual',
|
||||
'date_from': '2022-01-01',
|
||||
@@ -1012,7 +1047,7 @@ class TestAccrualAllocations(TestHrHolidaysCommon):
|
||||
leave = self.env['hr.leave'].create({
|
||||
'name': 'leave',
|
||||
'employee_id': self.employee_emp.id,
|
||||
'holiday_status_id': self.leave_type.id,
|
||||
'holiday_status_id': self.leave_type_hour.id,
|
||||
'date_from': '2022-03-07 00:00:00',
|
||||
'date_to': '2022-03-07 23:59:59'
|
||||
})
|
||||
@@ -1021,3 +1056,287 @@ class TestAccrualAllocations(TestHrHolidaysCommon):
|
||||
with freeze_time(datetime.date(2022, 6, 1)):
|
||||
allocation._update_accrual()
|
||||
self.assertEqual(allocation.number_of_days, 18 / self.hours_per_day, "Should accrue 8 additional hours")
|
||||
|
||||
def test_accrual_period_start(self):
|
||||
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
|
||||
'name': 'Accrual Plan For Test',
|
||||
'accrued_gain_time': 'end',
|
||||
'level_ids': [(0, 0, {
|
||||
'added_value_type': 'day',
|
||||
'start_count': 0,
|
||||
'start_type': 'day',
|
||||
'added_value': 1,
|
||||
'frequency': 'weekly',
|
||||
'week_day': 'mon',
|
||||
'cap_accrued_time': True,
|
||||
'maximum_leave': 5,
|
||||
})],
|
||||
})
|
||||
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
|
||||
'name': 'Accrual allocation for employee',
|
||||
'accrual_plan_id': accrual_plan.id,
|
||||
'employee_id': self.employee_emp.id,
|
||||
'holiday_status_id': self.leave_type.id,
|
||||
'number_of_days': 0,
|
||||
'allocation_type': 'accrual',
|
||||
'date_from': '2023-04-24',
|
||||
})
|
||||
allocation.action_confirm()
|
||||
allocation.action_validate()
|
||||
|
||||
with freeze_time("2023-4-24"):
|
||||
allocation._update_accrual()
|
||||
|
||||
self.assertEqual(allocation.number_of_days, 0, "Should accrue 0 days, because the period is not done yet.")
|
||||
|
||||
accrual_plan.accrued_gain_time = 'start'
|
||||
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
|
||||
'name': 'Accrual allocation for employee',
|
||||
'accrual_plan_id': accrual_plan.id,
|
||||
'employee_id': self.employee_emp.id,
|
||||
'holiday_status_id': self.leave_type.id,
|
||||
'number_of_days': 0,
|
||||
'allocation_type': 'accrual',
|
||||
'date_from': '2023-04-24',
|
||||
})
|
||||
allocation.action_confirm()
|
||||
allocation.action_validate()
|
||||
|
||||
with freeze_time("2023-4-24"):
|
||||
allocation._update_accrual()
|
||||
|
||||
self.assertEqual(allocation.number_of_days, 1, "Should accrue 1 day, at the start of the period.")
|
||||
|
||||
def test_aaaccrual_period_start_multiple_runs(self):
|
||||
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
|
||||
'name': 'Accrual Plan For Test',
|
||||
'accrued_gain_time': 'start',
|
||||
'level_ids': [(0, 0, {
|
||||
'added_value_type': 'day',
|
||||
'start_count': 0,
|
||||
'start_type': 'day',
|
||||
'added_value': 1.5,
|
||||
'frequency': 'monthly',
|
||||
'first_day': 13,
|
||||
'cap_accrued_time': True,
|
||||
'maximum_leave': 15,
|
||||
})],
|
||||
})
|
||||
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
|
||||
'name': 'Accrual allocation for employee',
|
||||
'accrual_plan_id': accrual_plan.id,
|
||||
'employee_id': self.employee_emp.id,
|
||||
'holiday_status_id': self.leave_type.id,
|
||||
'number_of_days': 0,
|
||||
'allocation_type': 'accrual',
|
||||
'date_from': '2023-04-13',
|
||||
})
|
||||
allocation.action_confirm()
|
||||
allocation.action_validate()
|
||||
|
||||
with freeze_time("2023-4-13"):
|
||||
allocation._update_accrual()
|
||||
|
||||
self.assertAlmostEqual(allocation.number_of_days, 1.5, 2)
|
||||
|
||||
with freeze_time("2023-9-13"):
|
||||
allocation._update_accrual()
|
||||
|
||||
self.assertAlmostEqual(allocation.number_of_days, 9, 2)
|
||||
|
||||
def test_accrual_period_start_level_transfer(self):
|
||||
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
|
||||
'name': 'Accrual Plan For Test',
|
||||
'accrued_gain_time': 'start',
|
||||
'level_ids': [
|
||||
(0, 0, {
|
||||
'added_value_type': 'day',
|
||||
'start_count': 0,
|
||||
'start_type': 'day',
|
||||
'added_value': 1,
|
||||
'frequency': 'weekly',
|
||||
'week_day': 'wed',
|
||||
'cap_accrued_time': True,
|
||||
'maximum_leave': 10,
|
||||
}),
|
||||
(0, 0, {
|
||||
'start_count': 3,
|
||||
'start_type': 'month',
|
||||
'added_value': 2,
|
||||
'frequency': 'weekly',
|
||||
'week_day': 'wed',
|
||||
'cap_accrued_time': True,
|
||||
'maximum_leave': 5,
|
||||
})
|
||||
],
|
||||
})
|
||||
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
|
||||
'name': 'Accrual allocation for employee',
|
||||
'accrual_plan_id': accrual_plan.id,
|
||||
'employee_id': self.employee_emp.id,
|
||||
'holiday_status_id': self.leave_type.id,
|
||||
'number_of_days': 0,
|
||||
'allocation_type': 'accrual',
|
||||
'date_from': '2023-04-26',
|
||||
})
|
||||
allocation.action_confirm()
|
||||
allocation.action_validate()
|
||||
|
||||
with freeze_time("2023-4-26"):
|
||||
allocation._update_accrual()
|
||||
self.assertEqual(allocation.number_of_days, 1, "Should accrue 1 day, at the start of the period.")
|
||||
|
||||
with freeze_time("2023-7-5"):
|
||||
allocation._update_accrual()
|
||||
self.assertEqual(allocation.number_of_days, 10, "Should accrue 10 days, days received, but not over limit.")
|
||||
|
||||
# first wednesday at the second level
|
||||
with freeze_time("2023-8-02"):
|
||||
allocation._update_accrual()
|
||||
self.assertEqual(allocation.number_of_days, 5, "Should accrue 5 days, after level transfer 10 are cut to 5")
|
||||
|
||||
def test_accrual_carryover_at_allocation(self):
|
||||
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
|
||||
'name': 'Accrual Plan For Test',
|
||||
'accrued_gain_time': 'start',
|
||||
'carryover_date': 'allocation',
|
||||
'level_ids': [(0, 0, {
|
||||
'added_value_type': 'day',
|
||||
'start_count': 0,
|
||||
'start_type': 'day',
|
||||
'added_value': 1,
|
||||
'frequency': 'monthly',
|
||||
'first_day': 27,
|
||||
'cap_accrued_time': False,
|
||||
'action_with_unused_accruals': 'lost',
|
||||
})],
|
||||
})
|
||||
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
|
||||
'name': 'Accrual allocation for employee',
|
||||
'accrual_plan_id': accrual_plan.id,
|
||||
'employee_id': self.employee_emp.id,
|
||||
'holiday_status_id': self.leave_type.id,
|
||||
'number_of_days': 0,
|
||||
'allocation_type': 'accrual',
|
||||
'date_from': '2023-04-26',
|
||||
})
|
||||
allocation.action_confirm()
|
||||
allocation.action_validate()
|
||||
|
||||
with freeze_time("2023-4-26"):
|
||||
allocation._update_accrual()
|
||||
self.assertEqual(allocation.number_of_days, 0, "Should accrue 0 days, days are added on 27th.")
|
||||
|
||||
with freeze_time("2023-4-27"):
|
||||
allocation._update_accrual()
|
||||
self.assertAlmostEqual(allocation.number_of_days, 1.03, 2, "Should accrue 1 day, days are added on 27th.")
|
||||
|
||||
with freeze_time("2023-12-27"):
|
||||
allocation._update_accrual()
|
||||
self.assertAlmostEqual(allocation.number_of_days, 9.03, 2, "Should accrue 9 day, after 8 months.")
|
||||
|
||||
with freeze_time("2024-04-26"):
|
||||
allocation._update_accrual()
|
||||
self.assertEqual(allocation.number_of_days, 0, "Allocations not lost on 1st of January, but on allocation date.")
|
||||
|
||||
with freeze_time("2024-04-27"):
|
||||
allocation._update_accrual()
|
||||
self.assertAlmostEqual(allocation.number_of_days, 1.03, 2, "Allocations lost, then 1 accrued.")
|
||||
|
||||
def test_accrual_carryover_at_other(self):
|
||||
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
|
||||
'name': 'Accrual Plan For Test',
|
||||
'accrued_gain_time': 'start',
|
||||
'carryover_date': 'other',
|
||||
'carryover_day': 20,
|
||||
'carryover_month': 'apr',
|
||||
'level_ids': [(0, 0, {
|
||||
'added_value_type': 'day',
|
||||
'start_count': 0,
|
||||
'start_type': 'day',
|
||||
'added_value': 10,
|
||||
'frequency': 'monthly',
|
||||
'first_day': 11,
|
||||
'cap_accrued_time': False,
|
||||
'action_with_unused_accruals': 'maximum',
|
||||
'postpone_max_days': 69,
|
||||
})],
|
||||
})
|
||||
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
|
||||
'name': 'Accrual allocation for employee',
|
||||
'accrual_plan_id': accrual_plan.id,
|
||||
'employee_id': self.employee_emp.id,
|
||||
'holiday_status_id': self.leave_type.id,
|
||||
'number_of_days': 0,
|
||||
'allocation_type': 'accrual',
|
||||
'date_from': '2023-04-20',
|
||||
})
|
||||
allocation.action_confirm()
|
||||
allocation.action_validate()
|
||||
|
||||
with freeze_time("2024-04-20"):
|
||||
allocation._update_accrual()
|
||||
self.assertEqual(allocation.number_of_days, 69, "Carryover at other date, level's maximum leave is 69.")
|
||||
|
||||
def test_accrual_carrover_other_period_end_multi_level(self):
|
||||
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
|
||||
'name': 'Accrual Plan For Test',
|
||||
'accrued_gain_time': 'end',
|
||||
'carryover_date': 'other',
|
||||
'carryover_day': 5,
|
||||
'carryover_month': 'jun',
|
||||
'level_ids': [
|
||||
(0, 0, {
|
||||
'added_value_type': 'day',
|
||||
'start_count': 5,
|
||||
'start_type': 'day',
|
||||
'added_value': 1,
|
||||
'frequency': 'monthly',
|
||||
'first_day': 9,
|
||||
'cap_accrued_time': True,
|
||||
'maximum_leave': 15,
|
||||
'action_with_unused_accruals': 'maximum',
|
||||
'postpone_max_days': 13,
|
||||
}),
|
||||
(0, 0, {
|
||||
'start_count': 9,
|
||||
'start_type': 'month',
|
||||
'added_value': 2,
|
||||
'frequency': 'biyearly',
|
||||
'first_month_day': 17,
|
||||
'first_month': 'feb',
|
||||
'second_month_day': 29,
|
||||
'second_month': 'oct',
|
||||
'cap_accrued_time': True,
|
||||
'maximum_leave': 10,
|
||||
'action_with_unused_accruals': 'maximum',
|
||||
'postpone_max_days': 20,
|
||||
}),
|
||||
(0, 0, {
|
||||
'start_count': 17,
|
||||
'start_type': 'month',
|
||||
'added_value': 12,
|
||||
'frequency': 'yearly',
|
||||
'yearly_month': 'jul',
|
||||
'yearly_day': 15,
|
||||
'cap_accrued_time': True,
|
||||
'maximum_leave': 21,
|
||||
'action_with_unused_accruals': 'lost',
|
||||
}),
|
||||
],
|
||||
})
|
||||
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
|
||||
'name': 'Accrual allocation for employee',
|
||||
'accrual_plan_id': accrual_plan.id,
|
||||
'employee_id': self.employee_emp.id,
|
||||
'holiday_status_id': self.leave_type.id,
|
||||
'number_of_days': 9,
|
||||
'allocation_type': 'accrual',
|
||||
'date_from': '2023-04-4',
|
||||
})
|
||||
allocation.action_confirm()
|
||||
allocation.action_validate()
|
||||
|
||||
with freeze_time("2026-08-01"):
|
||||
allocation._update_accrual()
|
||||
self.assertAlmostEqual(allocation.number_of_days, 1.315, 3)
|
||||
|
||||
@@ -6,49 +6,73 @@
|
||||
<field name="arch" type="xml">
|
||||
<form string="Accrual Level">
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="sequence" invisible="1" force_save="1"/>
|
||||
<label for="start_count"/>
|
||||
<div class="o_col">
|
||||
<div class="o_row">
|
||||
<field name="start_count"/>
|
||||
<field name="start_type" nolabel="1"/><label for="start_type" string="after allocation date" class="o_form_label"/>
|
||||
</div>
|
||||
<group name="accrue" col="1" width="800px">
|
||||
<div class="o_td_label">
|
||||
<label for="added_value" string="Employee accrue"/>
|
||||
</div>
|
||||
<field name="is_based_on_worked_time"/>
|
||||
<label for="added_value"/>
|
||||
<div class="o_col">
|
||||
<div class="o_row">
|
||||
<field name="added_value"/>
|
||||
<field name="added_value_type" nolabel="1"/>
|
||||
</div>
|
||||
<div>
|
||||
<field name="can_modify_value_type" invisible="1"/>
|
||||
<field name="added_value" widget="FloatWithoutTrailingZeros" style="width: 4rem" class="me-1"/>
|
||||
<field name="added_value_type" style="width: 3.4rem" nolabel="1" readonly="not can_modify_value_type"/>
|
||||
</div>
|
||||
<label for="frequency"/>
|
||||
<div class="o_col">
|
||||
<field name="frequency" class="w-25"/>
|
||||
<div class="o_row" invisible="frequency != 'weekly'">
|
||||
<label for="week_day" string="on"/><field name="week_day"/>
|
||||
</div>
|
||||
<div class="o_row" invisible="frequency != 'monthly'">
|
||||
<label for="first_day_display" string="on the"/><field name="first_day_display" required="1"/> of the month
|
||||
</div>
|
||||
<div class="o_row" invisible="frequency != 'bimonthly'">
|
||||
<label for="first_day_display" string="on the"/><field name="first_day_display" required="1"/> and on the <field name="second_day_display" required="1"/> of the month
|
||||
</div>
|
||||
<div class="o_row" invisible="frequency != 'biyearly'">
|
||||
<label for="first_month_day_display" string="on the"/><field name="first_month_day_display" required="1"/> of <field name="first_month"/> and on the <field name="second_month_day_display" required="1"/> of <field name="second_month"/>
|
||||
</div>
|
||||
<div class="o_row" invisible="frequency != 'yearly'">
|
||||
<label for="yearly_day_display" string="on the"/><field name="yearly_day_display" required="1"/> of <field name="yearly_month" class="w-25"/>
|
||||
</div>
|
||||
<div style="width: 5rem"/>
|
||||
<div name="daily" invisible="frequency != 'daily'">
|
||||
<field name="frequency" style="width: 5rem"/>
|
||||
</div>
|
||||
<label for="maximum_leave"/>
|
||||
<div class="o_col">
|
||||
<div class="o_row">
|
||||
<field name="maximum_leave"/>
|
||||
<!-- force_save is required here since it would otherwise not be saved due to the readonly
|
||||
there is an issue when the field is present twice in the view. -->
|
||||
<field name="added_value_type" nolabel="1" readonly="1" force_save="1"/>
|
||||
<div name="weekly" invisible="frequency != 'weekly'">
|
||||
<field name="frequency" style="width: 4.5rem;"/>
|
||||
<label for="week_day" string="on" class="me-1"/><field name="week_day" style="width: 6.6rem"/>
|
||||
</div>
|
||||
<div name="monthly" invisible="frequency != 'monthly'">
|
||||
<field name="frequency" style="width: 4.5rem"/>
|
||||
<label for="first_day_display" string="on the" class="me-1"/>
|
||||
<field name="first_day_display" required="1" style="width: 4rem"/>
|
||||
of the month
|
||||
</div>
|
||||
<div name="bimonthly" invisible="frequency != 'bimonthly'" style="width: 100%">
|
||||
<field name="frequency" style="width: 7rem"/>
|
||||
<label for="first_day_display" string="on the" class="me-1"/><field name="first_day_display" required="1" style="width: 4rem"/>
|
||||
<label for="second_day_display" string="and on the" class="me-1"/><field name="second_day_display" required="1" style="width: 4.1rem"/>
|
||||
of the month
|
||||
</div>
|
||||
<div name="biyearly" invisible="frequency != 'biyearly'">
|
||||
<field name="frequency" style="width: 6rem"/>
|
||||
<label for="first_month_day_display" string="on the" class="me-1"/>
|
||||
<field name="first_month_day_display" required="1" style="width: 4.1rem"/>of <field name="first_month" required="1" style="width: 4.55rem"/>
|
||||
<label for="second_month_day_display" string="and" class="me-1"/>
|
||||
<field name="second_month_day_display" required="1" style="width: 4.1rem"/>of <field name="second_month" required="1" style="width: 5.4rem"/>
|
||||
</div>
|
||||
<div name="yearly" invisible="frequency != 'yearly'">
|
||||
<field name="frequency" style="width: 4rem"/>
|
||||
<label for="yearly_day_display" string="on the" class="me-1"/>
|
||||
<field name="yearly_day_display" required="1" style="width: 4rem"/> of <field name="yearly_month" required="1" style="width: 5.4rem"/>
|
||||
</div>
|
||||
</group>
|
||||
<group name="maximum_leave">
|
||||
<field name="cap_accrued_time" style="width: 9.35rem"/>
|
||||
<div style="width: 9.35rem"/>
|
||||
<div invisible="not cap_accrued_time">
|
||||
<field name="maximum_leave" style="width: 5rem" widget="FloatWithoutTrailingZeros"/>
|
||||
<field name="added_value_type" style="white-space: nowrap;width: 5rem"/>
|
||||
</div>
|
||||
</group>
|
||||
<group name="milestone">
|
||||
<div class="o_td_label">
|
||||
<label for="start_count" string="Milestone reached"/>
|
||||
</div>
|
||||
<div>
|
||||
<field name="start_count" style="width: 2rem"/>
|
||||
<field name="start_type" style="width: 4.75rem"/> after allocation start date
|
||||
</div>
|
||||
<div class="o_td_label">
|
||||
<label for="action_with_unused_accruals" string="Carry over"/>
|
||||
</div>
|
||||
<div>
|
||||
<field name="action_with_unused_accruals" class="o_light_label" style="font-weight: 0" widget="radio"/><br/>
|
||||
<div invisible="action_with_unused_accruals != 'maximum'">
|
||||
<label for="postpone_max_days" string="Up to"/>
|
||||
<field name="postpone_max_days" style="width: 3rem"/>
|
||||
<field name="added_value_type" nolabel="1" readonly="1" force_save="1" style="width: 5rem"/>
|
||||
</div>
|
||||
</div>
|
||||
<field name="action_with_unused_accruals" class="w-25"/>
|
||||
@@ -78,8 +102,8 @@
|
||||
<field name="active" invisible="1"/>
|
||||
<field name="show_transition_mode" invisible="1" />
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button name="action_open_accrual_plan_employees" type="object" class="oe_stat_button" icon="fa-users">
|
||||
<div class="oe_button_box" name="button_box" invisible="not id" >
|
||||
<button name="action_open_accrual_plan_employees" type="object" class="oe_stat_button" icon="fa-users" invisible="employees_count == 0">
|
||||
<field name="employees_count" widget="statinfo"/>
|
||||
</button>
|
||||
</div>
|
||||
@@ -87,15 +111,18 @@
|
||||
<group>
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="accrued_gain_time" widget="radio"/>
|
||||
<field name="carryover_date" widget="radio"/>
|
||||
<label for="carryover_month" string="Carry-Over Date" invisible="carryover_date != 'other'"/>
|
||||
<div name="carryover" initially="carryover_date != 'other'">
|
||||
<field name="carryover_day" invisible="1"/>
|
||||
<field name="carryover_day_display" required="1" style="width: 4.75rem;"/> of
|
||||
<field name="carryover_month" required="1" style="width: 6rem;"/>
|
||||
</div>
|
||||
</group>
|
||||
<group>
|
||||
<field name="company_id" invisible="1"/>
|
||||
<field name="time_off_type_id"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="is_based_on_worked_time" readonly="accrued_gain_time == 'start'"/>
|
||||
<field name="transition_mode" widget="radio" invisible="not show_transition_mode"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
|
||||
</group>
|
||||
</group>
|
||||
@@ -109,8 +136,9 @@
|
||||
</p>
|
||||
</div>
|
||||
<field name="level_ids" mode="kanban" nolabel="1"
|
||||
class="o_hr_holidays_plan_level_container o_hr_holidays_plan_level_hierarchy"
|
||||
add-label="Add a new level"
|
||||
class="o_hr_holidays_plan_level_container"
|
||||
widget="accrual_levels_one2many"
|
||||
add-label="New Milestone"
|
||||
>
|
||||
<kanban default_order="sequence">
|
||||
<field name="sequence"/>
|
||||
@@ -130,12 +158,17 @@
|
||||
<field name="yearly_month"/>
|
||||
<field name="maximum_leave"/>
|
||||
<field name="action_with_unused_accruals"/>
|
||||
<field name="is_based_on_worked_time"/>
|
||||
<field name="cap_accrued_time"/>
|
||||
<templates>
|
||||
<div t-name="kanban-box" class="border-0 bg-transparent">
|
||||
<div class="o_hr_holidays_body oe_kanban_global_click">
|
||||
<div class="o_hr_holidays_timeline text-center">
|
||||
Level <span class="o_hr_holidays_plan_level_level"/>
|
||||
<t t-if="record.start_count.value > 0">
|
||||
after <t t-esc="record.start_count.value"/> <t t-esc="record.start_type.value"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
initially
|
||||
</t>
|
||||
</div>
|
||||
<t t-if="!read_only_mode">
|
||||
<a type="edit" t-attf-class="oe_kanban_action oe_kanban_action_a text-black">
|
||||
@@ -149,42 +182,52 @@
|
||||
</div>
|
||||
<t t-name="level_content">
|
||||
<div class="o_hr_holidays_card">
|
||||
<div class="content">
|
||||
<div>
|
||||
<t t-if="record.start_count.value > 0">
|
||||
Starts <field name="start_count" widget="integer"/> <field name="start_type"/> after allocation start date
|
||||
</t>
|
||||
<t t-else="">
|
||||
Starts immediately after allocation start date
|
||||
</t>
|
||||
<div class="content container" style="width: 560px;">
|
||||
<div class="row w-100">
|
||||
<div class="pe-0 me-0" style="width: 6rem;">
|
||||
<field name="added_value" widget="FloatWithoutTrailingZeros"/> <field name="added_value_type"/>,
|
||||
</div>
|
||||
<div class="col-auto m-0 p-0">
|
||||
<field name="frequency" class="ms-1"/>
|
||||
<t t-if="record.frequency.raw_value === 'weekly'">
|
||||
on <field name="week_day"/>
|
||||
</t>
|
||||
<t t-elif="record.frequency.raw_value === 'monthly'">
|
||||
on the <field name="first_day"/> day of the month
|
||||
</t>
|
||||
<t t-elif="record.frequency.raw_value === 'bimonthly'">
|
||||
on the <field name="first_day"/> and on the <field name="second_day"/> days of the months
|
||||
</t>
|
||||
<t t-elif="record.frequency.raw_value === 'biyearly'">
|
||||
on the <field name="first_month_day"/> <field name="first_month"/> and on the <field name="second_month_day"/> <field name="second_month"/>
|
||||
</t>
|
||||
<t t-elif="record.frequency.raw_value === 'yearly'">
|
||||
on <field name="yearly_day"/> <field name="yearly_month"/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
Adds <field name="added_value" widget="float_without_trailing_zeros"/> <field name="added_value_type"/>
|
||||
<t t-if="record.is_based_on_worked_time.raw_value">(based on worked time)</t>
|
||||
<div class="row text-muted">
|
||||
<div class="pe-0 me-0" style="width: 6rem;">
|
||||
Cap:
|
||||
</div>
|
||||
<div class="col-3 m-0 ps-1">
|
||||
<t t-if="record.cap_accrued_time.value && record.maximum_leave.value > 0">
|
||||
<field name="maximum_leave" widget="FloatWithoutTrailingZeros"/> <field name="added_value_type"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
Unlimited
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<field name="frequency"/>
|
||||
<t t-if="record.frequency.raw_value == 'weekly'">
|
||||
on <field name="week_day"/>
|
||||
</t>
|
||||
<t t-elif="record.frequency.raw_value === 'monthly'">
|
||||
on the <field name="first_day"/> day of the month
|
||||
</t>
|
||||
<t t-elif="record.frequency.raw_value === 'bimonthly'">
|
||||
on the <field name="first_day"/> and on the <field name="second_day"/> days of the months
|
||||
</t>
|
||||
<t t-elif="record.frequency.raw_value === 'biyearly'">
|
||||
on the <field name="first_month_day"/> <field name="first_month"/> and on the <field name="second_month_day"/> <field name="second_month"/>
|
||||
</t>
|
||||
<t t-elif="record.frequency.raw_value === 'yearly'">
|
||||
on <field name="yearly_day"/> <field name="yearly_month"/>
|
||||
</t>
|
||||
</div>
|
||||
<div t-if="record.maximum_leave.value">
|
||||
Limit of <field name="maximum_leave" widget="float_without_trailing_zeros"/> <field name="added_value_type"/>
|
||||
</div>
|
||||
<div t-if="record.action_with_unused_accruals.raw_value">
|
||||
At the end of the year, unused accruals will be <t t-if="record.action_with_unused_accruals.raw_value == 'postponed'">postponed</t><t t-else="">lost</t>
|
||||
<div class="row text-muted">
|
||||
<div class="pe-0 me-0" style="width: 6rem;">
|
||||
Carry over:
|
||||
</div>
|
||||
<div class="col-3 m-0 ps-1">
|
||||
<t t-if="record.action_with_unused_accruals.raw_value === 'all'">all</t>
|
||||
<t t-elif="record.action_with_unused_accruals.raw_value == 'maximum'">up to <field name="postpone_max_days" /> <t t-esc="record.added_value_type.raw_value" /></t>
|
||||
<t t-else="">no</t>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
<field name="can_approve" invisible="1"/>
|
||||
<field name="holiday_type" invisible="1" readonly="state not in ['confirm', 'draft']"/>
|
||||
<header>
|
||||
<button string="Confirm" name="action_confirm" invisible="state != 'draft'" type="object" class="oe_highlight"/>
|
||||
<button string="Confirm" name="action_confirm" invisible="state != 'draft' and 'allocation_type' != 'accrual' and not employee_id" type="object" class="oe_highlight"/>
|
||||
<field name="state" widget="statusbar" statusbar_visible="draft,confirm,validate"/>
|
||||
</header>
|
||||
<sheet>
|
||||
@@ -101,19 +101,18 @@
|
||||
<field name="date_to" widget="date" nolabel="1" placeholder="No Limit" readonly="1" invisible="allocation_type == 'regular' and state not in ('draft', 'confirm')"/>
|
||||
<div id="no_limit_label" class="oe_read_only" invisible="not id or date_to or state not in ('draft', 'confirm')">No limit</div>
|
||||
</div>
|
||||
|
||||
<field name="number_of_days" invisible="1"/>
|
||||
<div class="o_td_label">
|
||||
<label for="number_of_days_display" string="Amount"
|
||||
<label for="number_of_days_display" string="Allocation"
|
||||
readonly="allocation_type == 'accrual' and state not in ('draft', 'confirm')"/>
|
||||
</div>
|
||||
<div name="duration_display">
|
||||
<field name="number_of_days_display" nolabel="1" style="width: 5rem;"
|
||||
invisible="type_request_unit == 'hour'"
|
||||
readonly="type_request_unit == 'hour' or state not in ('draft', 'confirm') or allocation_type == 'accrual'"/>
|
||||
readonly="state not in ('draft', 'confirm')"/>
|
||||
<field name="number_of_hours_display" nolabel="1" style="width: 5rem;"
|
||||
invisible="type_request_unit != 'hour'"
|
||||
readonly="type_request_unit != 'hour' or state not in ('draft', 'confirm') or allocation_type == 'accrual'"/>
|
||||
readonly="state not in ('draft', 'confirm')"/>
|
||||
<span class="ml8" invisible="type_request_unit == 'hour'">Days</span>
|
||||
<span class="ml8" invisible="type_request_unit != 'hour'">Hours</span>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user