# -*- 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'] MONTHS = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'] # Used for displaying the days and reversing selection -> integer 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)))) 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" _order = 'sequence asc' 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) 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)')], 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, 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) frequency = fields.Selection([ ('daily', 'Daily'), ('weekly', 'Weekly'), ('bimonthly', 'Twice a month'), ('monthly', 'Monthly'), ('biyearly', 'Twice a year'), ('yearly', 'Yearly'), ], default='daily', required=True, string="Frequency") week_day = fields.Selection([ ('mon', 'Monday'), ('tue', 'Tuesday'), ('wed', 'Wednesday'), ('thu', 'Thursday'), ('fri', 'Friday'), ('sat', 'Saturday'), ('sun', 'Sunday'), ], default='mon', required=True, string="Allocation on") first_day = fields.Integer(default=1) first_day_display = fields.Selection( _get_selection_days, compute='_compute_days_display', inverse='_inverse_first_day_display') second_day = fields.Integer(default=15) second_day_display = fields.Selection( _get_selection_days, compute='_compute_days_display', inverse='_inverse_second_day_display') first_month_day = fields.Integer(default=1) first_month_day_display = fields.Selection( _get_selection_days, compute='_compute_days_display', inverse='_inverse_first_month_day_display') first_month = fields.Selection([ ('jan', 'January'), ('feb', 'February'), ('mar', 'March'), ('apr', 'April'), ('may', 'May'), ('jun', 'June'), ], default="jan") second_month_day = fields.Integer(default=1) second_month_day_display = fields.Selection( _get_selection_days, compute='_compute_days_display', inverse='_inverse_second_month_day_display') second_month = fields.Selection([ ('jul', 'July'), ('aug', 'August'), ('sep', 'September'), ('oct', 'October'), ('nov', 'November'), ('dec', 'December') ], default="jul") yearly_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") yearly_day = fields.Integer(default=1) yearly_day_display = fields.Selection( _get_selection_days, compute='_compute_days_display', inverse='_inverse_yearly_day_display') 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.") 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') 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.") _sql_constraints = [ ('check_dates', "CHECK( (frequency = 'daily') or" "(week_day IS NOT NULL AND frequency = 'weekly') or " "(first_day > 0 AND second_day > first_day AND first_day <= 31 AND second_day <= 31 AND frequency = 'bimonthly') or " "(first_day > 0 AND first_day <= 31 AND frequency = 'monthly')or " "(first_month_day > 0 AND first_month_day <= 31 AND second_month_day > 0 AND second_month_day <= 31 AND frequency = 'biyearly') or " "(yearly_day > 0 AND yearly_day <= 31 AND frequency = 'yearly'))", "The dates you've set up aren't correct. Please check them."), ('start_count_check', "CHECK( start_count >= 0 )", "You can not start an accrual in the past."), ('added_value_greater_than_zero', 'CHECK(added_value > 0)', 'You must give a rate greater than 0 in accrual plan levels.') ] @api.depends('start_count', 'start_type') def _compute_sequence(self): # Not 100% accurate because of odd months/years, but good enough start_type_multipliers = { 'day': 1, 'month': 30, 'year': 365, } for level in self: level.sequence = level.start_count * start_type_multipliers[level.start_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) for level in self: level.first_day_display = days_select[min(level.first_day - 1, 28)][0] level.second_day_display = days_select[min(level.second_day - 1, 28)][0] level.first_month_day_display = days_select[min(level.first_month_day - 1, 28)][0] 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] def _inverse_first_day_display(self): for level in self: if level.first_day_display == 'last': level.first_day = 31 else: level.first_day = DAY_SELECT_VALUES.index(level.first_day_display) + 1 def _inverse_second_day_display(self): for level in self: if level.second_day_display == 'last': level.second_day = 31 else: level.second_day = DAY_SELECT_VALUES.index(level.second_day_display) + 1 def _inverse_first_month_day_display(self): for level in self: if level.first_month_day_display == 'last': level.first_month_day = 31 else: level.first_month_day = DAY_SELECT_VALUES.index(level.first_month_day_display) + 1 def _inverse_second_month_day_display(self): for level in self: if level.second_month_day_display == 'last': level.second_month_day = 31 else: level.second_month_day = DAY_SELECT_VALUES.index(level.second_month_day_display) + 1 def _inverse_yearly_day_display(self): for level in self: if level.yearly_day_display == 'last': level.yearly_day = 31 else: level.yearly_day = DAY_SELECT_VALUES.index(level.yearly_day_display) + 1 def _get_next_date(self, last_call): """ Returns the next date with the given last call """ self.ensure_one() if self.frequency == 'daily': return last_call + relativedelta(days=1) elif self.frequency == 'weekly': daynames = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'] weekday = daynames.index(self.week_day) return last_call + relativedelta(days=1, weekday=weekday) elif self.frequency == 'bimonthly': first_date = last_call + relativedelta(day=self.first_day) second_date = last_call + relativedelta(day=self.second_day) if last_call < first_date: return first_date elif last_call < second_date: return second_date else: return last_call + relativedelta(months=1, day=self.first_day) elif self.frequency == 'monthly': date = last_call + relativedelta(day=self.first_day) if last_call < date: return date else: return last_call + relativedelta(months=1, day=self.first_day) elif self.frequency == 'biyearly': first_month = MONTHS.index(self.first_month) + 1 second_month = MONTHS.index(self.second_month) + 1 first_date = last_call + relativedelta(month=first_month, day=self.first_month_day) second_date = last_call + relativedelta(month=second_month, day=self.second_month_day) if last_call < first_date: return first_date elif last_call < second_date: return second_date else: return last_call + relativedelta(years=1, month=first_month, day=self.first_month_day) elif self.frequency == 'yearly': month = MONTHS.index(self.yearly_month) + 1 date = last_call + relativedelta(month=month, day=self.yearly_day) if last_call < date: return date else: return last_call + relativedelta(years=1, month=month, day=self.yearly_day) else: return False def _get_previous_date(self, last_call): """ Returns the date a potential previous call would have been at For example if you have a monthly level giving 16/02 would return 01/02 Contrary to `_get_next_date` this function will return the 01/02 if that date is given """ self.ensure_one() if self.frequency == 'daily': return last_call elif self.frequency == 'weekly': daynames = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'] weekday = daynames.index(self.week_day) return last_call + relativedelta(days=-6, weekday=weekday) elif self.frequency == 'bimonthly': second_date = last_call + relativedelta(day=self.second_day) first_date = last_call + relativedelta(day=self.first_day) if last_call >= second_date: return second_date elif last_call >= first_date: return first_date else: return last_call + relativedelta(months=-1, day=self.second_day) elif self.frequency == 'monthly': date = last_call + relativedelta(day=self.first_day) if last_call >= date: return date else: return last_call + relativedelta(months=-1, day=self.first_day) elif self.frequency == 'biyearly': first_month = MONTHS.index(self.first_month) + 1 second_month = MONTHS.index(self.second_month) + 1 first_date = last_call + relativedelta(month=first_month, day=self.first_month_day) second_date = last_call + relativedelta(month=second_month, day=self.second_month_day) if last_call >= second_date: return second_date elif last_call >= first_date: return first_date else: return last_call + relativedelta(years=-1, month=second_month, day=self.second_month_day) elif self.frequency == 'yearly': month = MONTHS.index(self.yearly_month) + 1 year_date = last_call + relativedelta(month=month, day=self.yearly_day) if last_call >= year_date: return year_date else: return last_call + relativedelta(years=-1, month=month, day=self.yearly_day) else: return False