# -*- coding: utf-8 -*- import logging from datetime import datetime, date, timedelta from odoo import _, api, models, fields from odoo.exceptions import ValidationError logger = logging.getLogger(__name__) class HolidaysCalendar(models.Model): _name = "holidays.calendar" name = fields.Char(string="Template name", required=True) year = fields.Selection( string="Year", selection="_compute_years_selection", default=lambda self: str(datetime.today().year), ) global_leave_ids = fields.One2many( "holidays.calendar.leaves", "calendar_id", "Global Leaves", copy="true", ) @api.returns("self", lambda value: value.id) def copy(self, default=None): default = dict(default or {}) if "name" not in default: default["name"] = _("%s (Copy)", self.name) return super().copy(default=default) @api.model def create(self, values): if values: self._validate_years(values) return super(HolidaysCalendar, self).create(values) @api.model def get_all_holidays(self, range_start, range_end): """The function gets all the holiday dates that are specified in the production calendars for the current displayed year""" range_start_date = datetime.strptime(range_start, "%Y-%m-%d").date() range_end_date = datetime.strptime(range_end, "%Y-%m-%d").date() production_calendars = self.env["holidays.calendar"].search( [ ("year", "in", [range_start_date.year, range_end_date.year]), ] ) holidays = [] non_working_days = [] rescheduled_work_days = [] for calendar in production_calendars: self._separate_holidays( calendar, holidays, non_working_days, rescheduled_work_days, range_start_date, range_end_date, ) return holidays, non_working_days, rescheduled_work_days def write(self, values=None): if values: self._validate_years(values) return super(HolidaysCalendar, self).write(values) @staticmethod def _check_year(user_date, year): user_year = ( datetime.strptime(user_date, "%Y-%m-%d").year if isinstance(user_date, str) else user_date.year ) if int(year) != user_year: raise ValidationError( _("The date's year %s mismatch the template year") % user_year ) @api.onchange("year") def _change_years(self): for event in self.global_leave_ids: event.date_to = date( year=int(self.year), month=event.date_to.month, day=event.date_to.day ) event.date_from = date( year=int(self.year), month=event.date_from.month, day=event.date_from.day, ) @staticmethod def _compute_years_selection(): current_year = datetime.today().year result = [ ("{}".format(year), "{}".format(year)) for year in range(current_year - 5, current_year + 10) ] return result def _fill_day_gaps(self, start_date, end_date): """The function fills in the date gaps between the beginning and the end of the period""" current_date = start_date dates_list = [] while current_date <= end_date: dates_list.append(current_date.strftime("%Y-%m-%d")) current_date += timedelta(days=1) return dates_list def _get_days_from_holidays(self, day, range_start_date, range_end_date): """The function gets the value of the start date and the end date of the period returns a list of dates in a string representation with filled intervals""" start_date = day.date_from end_date = day.date_to if range_start_date <= start_date and end_date <= range_end_date: days_list = self._fill_day_gaps(start_date, end_date) elif range_start_date >= start_date and end_date <= range_end_date: days_list = self._fill_day_gaps(range_start_date, end_date) elif range_start_date <= start_date and end_date >= range_end_date: days_list = self._fill_day_gaps(start_date, range_end_date) elif range_start_date >= start_date and end_date >= range_end_date: days_list = self._fill_day_gaps(range_start_date, range_end_date) else: days_list = [] return days_list def _separate_holidays( self, calendar, holidays, non_working_days, rescheduled_work_days, range_start_date, range_end_date, ): """The function sorts the dates in the production calendar according to the category of the day""" days = calendar.global_leave_ids for day in days: days_list = self._get_days_from_holidays( day, range_start_date, range_end_date ) if day.type_transfer_day == "is_holiday": holidays.extend(days_list) elif day.type_transfer_day == "is_day_off": non_working_days.extend(days_list) elif day.type_transfer_day == "is_workday": rescheduled_work_days.extend(days_list) def _validate_years(self, values): if type(values) != list: values = [values] for value in values: year = value.get("year", self.year) events = value.get("global_leave_ids", []) for event in events: changes = event[2] if changes: if changes.get("date_from"): self._check_year(changes.get("date_from"), year) if changes.get("date_to"): self._check_year(changes.get("date_to"), year) class HolidaysCalendarLeaves(models.Model): _name = "holidays.calendar.leaves" _order = "date_from asc" name = fields.Char("Reason") type_transfer_day = fields.Selection( [ ("is_holiday", "Holiday"), ("is_day_off", "Non-working day"), ("is_workday", "Rescheduled work day"), ], string="Type of day", required=True, help="Type of day for right calculate count of working hours and vacation days", ) calendar_id = fields.Many2one("holidays.calendar") date_from = fields.Date("Start Date", required=True) date_to = fields.Date("End Date", required=True) time_type = fields.Selection( [("leave", "Leave"), ("other", "Other")], default="leave", help="Whether this should be computed as a holiday or as work time (eg: formation)", ) @api.constrains("date_from", "date_to") def check_dates(self): if self.filtered(lambda leave: leave.date_from > leave.date_to): raise ValidationError( _("The start date of the leave must be not later than end date.") )