198 lines
7.0 KiB
Python
198 lines
7.0 KiB
Python
|
# -*- 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.")
|
||
|
)
|