Compare commits
3 Commits
1445afc1d5
...
f6d0020515
Author | SHA1 | Date | |
---|---|---|---|
f6d0020515 | |||
|
ad845e7ad8 | ||
0718e859bd |
6
__init__.py
Normal file
6
__init__.py
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from odoo import api, SUPERUSER_ID
|
||||||
|
|
||||||
|
from . import models
|
||||||
|
from . import wizard
|
36
__manifest__.py
Normal file
36
__manifest__.py
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
{
|
||||||
|
"name": "hr_holidays_ru",
|
||||||
|
"summary": """
|
||||||
|
Adds possibility to count leaves in calendar days as is customary in Russian Federation.
|
||||||
|
""",
|
||||||
|
"description": """
|
||||||
|
In Russian Federation leaves are counted in calendar days instead of working days.
|
||||||
|
Also there are not working days and public holidays which are taken into account in different ways.
|
||||||
|
This module implements these features to correctly count leaves in RF.
|
||||||
|
""",
|
||||||
|
"author": "RYDLAB",
|
||||||
|
"website": "http://rydlab.ru",
|
||||||
|
"license": "Other proprietary",
|
||||||
|
"category": "Localization/Payroll",
|
||||||
|
"version": "17.0.1.3",
|
||||||
|
"depends": ["base", "hr_holidays", "resource", "calendar", "web"],
|
||||||
|
"data": [
|
||||||
|
"security/ir.model.access.csv",
|
||||||
|
"data/holidays_template.xml",
|
||||||
|
"data/holidays_cron.xml",
|
||||||
|
"data/holidays_notification_template.xml",
|
||||||
|
"views/hr_holidays_views.xml",
|
||||||
|
"views/hr_leave_views.xml",
|
||||||
|
"views/hr_holidays_menus_view.xml",
|
||||||
|
"views/hr_leave_report_calendar_view.xml",
|
||||||
|
"views/hr_leave_allocation_views.xml",
|
||||||
|
],
|
||||||
|
"assets": {
|
||||||
|
"web.assets_backend": [
|
||||||
|
"hr_holidays_ru/static/src/css/*.css",
|
||||||
|
"hr_holidays_ru/static/src/views/calendar/**/*.xml",
|
||||||
|
"hr_holidays_ru/static/src/views/calendar/**/*.js",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
14
data/holidays_cron.xml
Normal file
14
data/holidays_cron.xml
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<odoo>
|
||||||
|
<record id="ir_cron_notification_about_paid_holidays" model="ir.cron">
|
||||||
|
<field name="name">HR Holidays: Notify about employee holiday</field>
|
||||||
|
<field name="model_id" ref="model_hr_leave"/>
|
||||||
|
<field name="state">code</field>
|
||||||
|
<field name="code">model._check_users_holidays()</field>
|
||||||
|
<field name="interval_number">1</field>
|
||||||
|
<field name="interval_type">days</field>
|
||||||
|
<field name="numbercall">-1</field>
|
||||||
|
<field name="active">True</field>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
15
data/holidays_notification_template.xml
Normal file
15
data/holidays_notification_template.xml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<record id="holidays_notification_template" model="mail.template">
|
||||||
|
<field name="name">HR holidays: Holidays start notification</field>
|
||||||
|
<field name="email_to">{{ object.holiday_status_id.responsible_employee_to_notify_id.work_email }}</field>
|
||||||
|
<field name="subject">Reminder of the holidays start</field>
|
||||||
|
<field name="model_id" ref="hr_holidays_ru.model_hr_leave"/>
|
||||||
|
<field name="lang">{{ object.holiday_status_id.responsible_employee_to_notify_id.lang }}</field>
|
||||||
|
<field name="body_html" type="html">
|
||||||
|
<p>Dear <t t-out="object.holiday_status_id.responsible_employee_to_notify_id.name or ''"/>,</p>
|
||||||
|
<p>A reminder that the employee <t t-out="object.employee_id.name"/> begins his vacation in <t t-out="object.holiday_status_id.days_before_holidays"/> days.</p>
|
||||||
|
<p>A vacation from <t t-out="object.date_from.strftime('%d.%m.%Y')"/> to <t t-out="object.date_to.strftime('%d.%m.%Y')"/>. Duration of holidays: <t t-out="(object.date_to.date() - object.date_from.date()).days + 1"/> day(s). </p>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
63
data/holidays_template.xml
Normal file
63
data/holidays_template.xml
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<record id="holidays_template_2022" model="holidays.calendar">
|
||||||
|
<field name="name">Weekends and holidays 2022</field>
|
||||||
|
<field name="year">2022</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="new_year_2022" model="holidays.calendar.leaves">
|
||||||
|
<field name="name">New Year</field>
|
||||||
|
<field name="date_from">2022-01-01</field>
|
||||||
|
<field name="date_to">2022-01-08</field>
|
||||||
|
<field name="type_transfer_day">is_holiday</field>
|
||||||
|
<field name="calendar_id" ref="holidays_template_2022"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="23_feb_2022" model="holidays.calendar.leaves">
|
||||||
|
<field name="name">23 February</field>
|
||||||
|
<field name="date_from">2022-02-23</field>
|
||||||
|
<field name="date_to">2022-02-23</field>
|
||||||
|
<field name="type_transfer_day">is_holiday</field>
|
||||||
|
<field name="calendar_id" ref="holidays_template_2022"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="8_march_2022" model="holidays.calendar.leaves">
|
||||||
|
<field name="name">8 March</field>
|
||||||
|
<field name="date_from">2022-03-08</field>
|
||||||
|
<field name="date_to">2022-03-08</field>
|
||||||
|
<field name="type_transfer_day">is_holiday</field>
|
||||||
|
<field name="calendar_id" ref="holidays_template_2022"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="1_may_2022" model="holidays.calendar.leaves">
|
||||||
|
<field name="name">1 May</field>
|
||||||
|
<field name="date_from">2022-05-01</field>
|
||||||
|
<field name="date_to">2022-05-01</field>
|
||||||
|
<field name="type_transfer_day">is_holiday</field>
|
||||||
|
<field name="calendar_id" ref="holidays_template_2022"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="9_may_2022" model="holidays.calendar.leaves">
|
||||||
|
<field name="name">9 May</field>
|
||||||
|
<field name="date_from">2022-05-09</field>
|
||||||
|
<field name="date_to">2022-05-09</field>
|
||||||
|
<field name="type_transfer_day">is_holiday</field>
|
||||||
|
<field name="calendar_id" ref="holidays_template_2022"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="12_june_2022" model="holidays.calendar.leaves">
|
||||||
|
<field name="name">12 June</field>
|
||||||
|
<field name="date_from">2022-06-12</field>
|
||||||
|
<field name="date_to">2022-06-12</field>
|
||||||
|
<field name="type_transfer_day">is_holiday</field>
|
||||||
|
<field name="calendar_id" ref="holidays_template_2022"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="4_nov_2022" model="holidays.calendar.leaves">
|
||||||
|
<field name="name">4 November</field>
|
||||||
|
<field name="date_from">2022-11-04</field>
|
||||||
|
<field name="date_to">2022-11-04</field>
|
||||||
|
<field name="type_transfer_day">is_holiday</field>
|
||||||
|
<field name="calendar_id" ref="holidays_template_2022"/>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
507
i18n/ru.po
Normal file
507
i18n/ru.po
Normal file
|
@ -0,0 +1,507 @@
|
||||||
|
# Translation of Odoo Server.
|
||||||
|
# This file contains the translation of the following modules:
|
||||||
|
# * hr_holidays_ru
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: Odoo Server 17.0-20241029\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2024-11-20 10:45+0000\n"
|
||||||
|
"PO-Revision-Date: 2024-11-20 10:45+0000\n"
|
||||||
|
"Last-Translator: \n"
|
||||||
|
"Language-Team: \n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: \n"
|
||||||
|
"Plural-Forms: \n"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#. odoo-python
|
||||||
|
#: code:addons/hr_holidays_ru/models/hr_leave_type.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid " days"
|
||||||
|
msgstr "дней"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#. odoo-python
|
||||||
|
#: code:addons/hr_holidays_ru/models/hr_leave_type.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid " hours"
|
||||||
|
msgstr "часов"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#. odoo-python
|
||||||
|
#: code:addons/hr_holidays_ru/models/hr_leave_type.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid "%g remaining "
|
||||||
|
msgstr "%g осталось "
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#. odoo-python
|
||||||
|
#: code:addons/hr_holidays_ru/models/holidays.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid "%s (Copy)"
|
||||||
|
msgstr "%s (Копия)"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#. odoo-python
|
||||||
|
#: code:addons/hr_holidays_ru/models/hr_leave.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid "%s : %.2f Calendar day(s)"
|
||||||
|
msgstr "%s : %.2f Календарных дней"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#. odoo-python
|
||||||
|
#: code:addons/hr_holidays_ru/models/hr_leave.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid "%s : %.2f Working day(s)"
|
||||||
|
msgstr "%s : %.2f Рабочих дней"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#. odoo-python
|
||||||
|
#: code:addons/hr_holidays_ru/models/hr_leave.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid "%s : %.2f hour(s)"
|
||||||
|
msgstr "%s : %.2f часов"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#. odoo-python
|
||||||
|
#: code:addons/hr_holidays_ru/models/hr_leave.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid "%s on %s : %.2f Calendar day(s)"
|
||||||
|
msgstr "%s из %s : %.2f Календарных дней"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#. odoo-python
|
||||||
|
#: code:addons/hr_holidays_ru/models/hr_leave.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid "%s on %s : %.2f Working day(s)"
|
||||||
|
msgstr "%s из %s : %.2f Рабочих дней"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#. odoo-python
|
||||||
|
#: code:addons/hr_holidays_ru/models/hr_leave.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid "%s on %s : %.2f hour(s)"
|
||||||
|
msgstr "%s из %s : %.2f часов"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model:mail.template,body_html:hr_holidays_ru.holidays_notification_template
|
||||||
|
msgid ""
|
||||||
|
"<p>Dear <t t-out=\"object.holiday_status_id.responsible_employee_to_notify_id.name or ''\"></t>,</p>\n"
|
||||||
|
" <p>A reminder that the employee <t t-out=\"object.employee_id.name\"></t> begins his vacation in <t t-out=\"object.holiday_status_id.days_before_holidays\"></t> days.</p>\n"
|
||||||
|
" <p>A vacation from <t t-out=\"object.date_from.strftime('%d.%m.%Y')\"></t> to <t t-out=\"object.date_to.strftime('%d.%m.%Y')\"></t>. Duration of holidays: <t t-out=\"(object.date_to.date() - object.date_from.date()).days + 1\"></t> day(s). </p>\n"
|
||||||
|
" "
|
||||||
|
msgstr ""
|
||||||
|
"<p>Уважаемый(ая) <t "
|
||||||
|
"t-out=\"object.holiday_status_id.responsible_employee_to_notify_id.name or "
|
||||||
|
"''\"></t>,</p><p>Напоминаем вам о том, что сотрудник <t "
|
||||||
|
"t-out=\"object.employee_id.name\"></t> уходит в отпуск через <t "
|
||||||
|
"t-out=\"object.holiday_status_id.days_before_holidays\"></t> "
|
||||||
|
"дня/дней.</p><p>Отпуск с <t "
|
||||||
|
"t-out=\"object.date_from.strftime('%d.%m.%Y')\"></t> по <t "
|
||||||
|
"t-out=\"object.date_to.strftime('%d.%m.%Y')\"></t>. Продолжительность "
|
||||||
|
"отпуска: <t t-out=\"(object.date_to.date() - object.date_from.date()).days +"
|
||||||
|
" 1\"></t> дня/дней. </p>"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model_terms:ir.ui.view,arch_db:hr_holidays_ru.hr_leave_allocation_view_form_inherit
|
||||||
|
msgid ""
|
||||||
|
"<span class=\"ml8\" invisible=\"type_request_unit != 'c_day'\">Calendar days</span>\n"
|
||||||
|
" <span class=\"ml8\" invisible=\"type_request_unit != 'day'\">Working days</span>\n"
|
||||||
|
" <span class=\"ml8\" invisible=\"type_request_unit != 'hour'\">Hours</span>"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model_terms:ir.ui.view,arch_db:hr_holidays_ru.hr_leave_view_form_inherit
|
||||||
|
msgid "<span class=\"ml8\">Calendar days</span>"
|
||||||
|
msgstr "<span class=\"ml8\">Календарных дней</span>"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model_terms:ir.ui.view,arch_db:hr_holidays_ru.hr_leave_view_form_inherit
|
||||||
|
msgid "<span class=\"ml8\">Hours</span>"
|
||||||
|
msgstr "<span class=\"ml8\">Часов</span>"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model_terms:ir.ui.view,arch_db:hr_holidays_ru.hr_leave_view_form_inherit
|
||||||
|
msgid "<span class=\"ml8\">Working days</span>"
|
||||||
|
msgstr "<span class=\"ml8\">Рабочих дней</span>"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model_terms:ir.ui.view,arch_db:hr_holidays_ru.hr_leave_view_form_inh
|
||||||
|
msgid "<span>Days</span>"
|
||||||
|
msgstr "<span>Дней</span>"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model:ir.model.fields,field_description:hr_holidays_ru.field_holidays_calendar_leaves__calendar_id
|
||||||
|
msgid "Calendar"
|
||||||
|
msgstr "Календарь"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model:ir.model.fields.selection,name:hr_holidays_ru.selection__hr_leave_allocation__type_request_unit__c_day
|
||||||
|
msgid "Calendar Days"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model:ir.model.fields.selection,name:hr_holidays_ru.selection__hr_leave_type__request_unit__c_day
|
||||||
|
#: model_terms:ir.ui.view,arch_db:hr_holidays_ru.hr_leave_view_kanban_inherit
|
||||||
|
msgid "Calendar days"
|
||||||
|
msgstr "Календарные дни"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model:ir.model.fields,help:hr_holidays_ru.field_hr_leave_type__exclude_holidays
|
||||||
|
msgid "Check this box to exclude holidays from calendar days amount."
|
||||||
|
msgstr ""
|
||||||
|
"Установите этот флажок, чтобы исключить праздники из количества календарных "
|
||||||
|
"дней."
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model:ir.model.fields,field_description:hr_holidays_ru.field_holidays_calendar__create_uid
|
||||||
|
#: model:ir.model.fields,field_description:hr_holidays_ru.field_holidays_calendar_leaves__create_uid
|
||||||
|
msgid "Created by"
|
||||||
|
msgstr "Кем создано"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model:ir.model.fields,field_description:hr_holidays_ru.field_holidays_calendar__create_date
|
||||||
|
#: model:ir.model.fields,field_description:hr_holidays_ru.field_holidays_calendar_leaves__create_date
|
||||||
|
msgid "Created on"
|
||||||
|
msgstr "Когда создано"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model:ir.model.fields,field_description:hr_holidays_ru.field_hr_leave_type__days_before_holidays
|
||||||
|
msgid "Days before holidays"
|
||||||
|
msgstr "Дней до отпуска"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model:ir.model.fields,field_description:hr_holidays_ru.field_holidays_calendar__display_name
|
||||||
|
#: model:ir.model.fields,field_description:hr_holidays_ru.field_holidays_calendar_leaves__display_name
|
||||||
|
msgid "Display Name"
|
||||||
|
msgstr "Отображаемое имя"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model:ir.model.fields,field_description:hr_holidays_ru.field_hr_leave_type__responsible_employee_to_notify_id
|
||||||
|
msgid "Employee to notify"
|
||||||
|
msgstr "Сотрудник, которого необходимо уведомить"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model:ir.model.fields,field_description:hr_holidays_ru.field_holidays_calendar_leaves__date_to
|
||||||
|
msgid "End Date"
|
||||||
|
msgstr "Дата окончания"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model:ir.model.fields,field_description:hr_holidays_ru.field_hr_leave_type__exclude_holidays
|
||||||
|
msgid "Exclude holidays"
|
||||||
|
msgstr "Исключить праздники"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#. odoo-python
|
||||||
|
#: code:addons/hr_holidays_ru/wizard/hr_holidays_summary_department.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid "Form content is missing, this report cannot be printed."
|
||||||
|
msgstr "Содержимое формы отсутствует, этот отчет не может быть распечатан."
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model:ir.model.fields,field_description:hr_holidays_ru.field_holidays_calendar__global_leave_ids
|
||||||
|
#: model_terms:ir.ui.view,arch_db:hr_holidays_ru.open_view_holiday_settings_form
|
||||||
|
msgid "Global Leaves"
|
||||||
|
msgstr "Глобальные отпуска"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model:ir.actions.server,name:hr_holidays_ru.ir_cron_notification_about_paid_holidays_ir_actions_server
|
||||||
|
msgid "HR Holidays: Notify about employee holiday"
|
||||||
|
msgstr "HR holidays: Уведомить об отпуске сотрудника"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model:mail.template,name:hr_holidays_ru.holidays_notification_template
|
||||||
|
msgid "HR holidays: Holidays start notification"
|
||||||
|
msgstr "HR holidays: уведомление о начале каникул"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model:ir.model.fields.selection,name:hr_holidays_ru.selection__holidays_calendar_leaves__type_transfer_day__is_holiday
|
||||||
|
msgid "Holiday"
|
||||||
|
msgstr "Праздник"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model:ir.model,name:hr_holidays_ru.model_report_hr_holidays_report_holidayssummary
|
||||||
|
msgid "Holidays Summary Report"
|
||||||
|
msgstr "Сводный отчет о праздниках"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model:ir.actions.act_window,name:hr_holidays_ru.open_view_holiday_settings
|
||||||
|
#: model:ir.ui.menu,name:hr_holidays_ru.hr_holidays_status_menu_configuration
|
||||||
|
msgid "Holidays and non-working days"
|
||||||
|
msgstr "Праздничные и нерабочие дни"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model:ir.model.fields.selection,name:hr_holidays_ru.selection__hr_leave_type__request_unit__hour
|
||||||
|
msgid "Hours"
|
||||||
|
msgstr "Часов"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model:ir.model.fields,help:hr_holidays_ru.field_hr_leave_type__days_before_holidays
|
||||||
|
msgid ""
|
||||||
|
"How many days before it is necessary to notify the responsible employee "
|
||||||
|
"about the start of the holidays."
|
||||||
|
msgstr ""
|
||||||
|
"За сколько дней до начала отпуска необходимо уведомить ответственного "
|
||||||
|
"сотрудника?"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model:ir.model.fields,field_description:hr_holidays_ru.field_holidays_calendar__id
|
||||||
|
#: model:ir.model.fields,field_description:hr_holidays_ru.field_holidays_calendar_leaves__id
|
||||||
|
msgid "ID"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model:ir.model.fields,help:hr_holidays_ru.field_hr_leave_type__notify_before_start_holidays
|
||||||
|
msgid ""
|
||||||
|
"Is it necessary to notify the responsible employee before the start of the "
|
||||||
|
"holidays?"
|
||||||
|
msgstr "Нужно ли уведомлять ответственного сотрудника о начале отпуска?"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model:ir.model.fields,field_description:hr_holidays_ru.field_holidays_calendar__write_uid
|
||||||
|
#: model:ir.model.fields,field_description:hr_holidays_ru.field_holidays_calendar_leaves__write_uid
|
||||||
|
msgid "Last Updated by"
|
||||||
|
msgstr "Кем обновлено"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model:ir.model.fields,field_description:hr_holidays_ru.field_holidays_calendar__write_date
|
||||||
|
#: model:ir.model.fields,field_description:hr_holidays_ru.field_holidays_calendar_leaves__write_date
|
||||||
|
msgid "Last Updated on"
|
||||||
|
msgstr "Когда обновлено"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model:ir.model.fields.selection,name:hr_holidays_ru.selection__holidays_calendar_leaves__time_type__leave
|
||||||
|
msgid "Leave"
|
||||||
|
msgstr "Отгул"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#. odoo-javascript
|
||||||
|
#: code:addons/hr_holidays_ru/static/src/views/calendar/filter_panel/calendar_filter_panel.xml:0
|
||||||
|
#: model:ir.model.fields.selection,name:hr_holidays_ru.selection__holidays_calendar_leaves__type_transfer_day__is_day_off
|
||||||
|
#, python-format
|
||||||
|
msgid "Non-working day"
|
||||||
|
msgstr "Нерабочий день"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model_terms:ir.ui.view,arch_db:hr_holidays_ru.edit_holiday_status_form_inherit
|
||||||
|
msgid "Notification about holidays"
|
||||||
|
msgstr "Уведомления о начале отпуска"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model:ir.model.fields,field_description:hr_holidays_ru.field_hr_leave_type__notify_before_start_holidays
|
||||||
|
msgid "Notify about start holidays"
|
||||||
|
msgstr "Уведомлять о начале отпуска?"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#. odoo-javascript
|
||||||
|
#: code:addons/hr_holidays_ru/static/src/views/calendar/filter_panel/calendar_filter_panel.xml:0
|
||||||
|
#, python-format
|
||||||
|
msgid "Number of days"
|
||||||
|
msgstr "Количество дней"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model:ir.model.fields.selection,name:hr_holidays_ru.selection__holidays_calendar_leaves__time_type__other
|
||||||
|
msgid "Other"
|
||||||
|
msgstr "Другое"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#. odoo-javascript
|
||||||
|
#: code:addons/hr_holidays_ru/static/src/views/calendar/filter_panel/calendar_filter_panel.xml:0
|
||||||
|
#, python-format
|
||||||
|
msgid "Public Holiday"
|
||||||
|
msgstr "Праздничный день"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model:ir.model.fields,field_description:hr_holidays_ru.field_holidays_calendar_leaves__name
|
||||||
|
msgid "Reason"
|
||||||
|
msgstr "Причина"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model:mail.template,subject:hr_holidays_ru.holidays_notification_template
|
||||||
|
msgid "Reminder of the holidays start"
|
||||||
|
msgstr "Напоминание о начале отпуска"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model:ir.model.fields.selection,name:hr_holidays_ru.selection__holidays_calendar_leaves__type_transfer_day__is_workday
|
||||||
|
msgid "Rescheduled work day"
|
||||||
|
msgstr "Перенесенный рабочий день"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model:ir.model,name:hr_holidays_ru.model_resource_mixin
|
||||||
|
msgid "Resource Mixin"
|
||||||
|
msgstr "Смешанный ресурс"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model:ir.model,name:hr_holidays_ru.model_resource_calendar
|
||||||
|
msgid "Resource Working Time"
|
||||||
|
msgstr "Рабочее время"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model:ir.model.fields,field_description:hr_holidays_ru.field_holidays_calendar_leaves__date_from
|
||||||
|
msgid "Start Date"
|
||||||
|
msgstr "Дата начала"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model:ir.model.fields,field_description:hr_holidays_ru.field_hr_leave_type__request_unit
|
||||||
|
msgid "Take Leaves in"
|
||||||
|
msgstr "Отпуск берется в"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model:ir.model.fields,field_description:hr_holidays_ru.field_holidays_calendar__name
|
||||||
|
msgid "Template name"
|
||||||
|
msgstr "Имя шаблона"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#. odoo-python
|
||||||
|
#: code:addons/hr_holidays_ru/models/holidays.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid "The date's year %s mismatch the template year"
|
||||||
|
msgstr "Год даты %s не соответствует году шаблона"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model:ir.model.fields,help:hr_holidays_ru.field_hr_leave_type__responsible_employee_to_notify_id
|
||||||
|
msgid "The employee who needs to be notified"
|
||||||
|
msgstr "Сотрудник, который должен быть уведомлен"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#. odoo-python
|
||||||
|
#: code:addons/hr_holidays_ru/models/holidays.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid "The start date of the leave must be not later than end date."
|
||||||
|
msgstr "Дата начала отпуска должна быть не позднее даты окончания."
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model:ir.model,name:hr_holidays_ru.model_hr_leave
|
||||||
|
msgid "Time Off"
|
||||||
|
msgstr "Отпуск"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model:ir.model,name:hr_holidays_ru.model_hr_leave_allocation
|
||||||
|
msgid "Time Off Allocation"
|
||||||
|
msgstr "Распределение отпусков"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model:ir.model,name:hr_holidays_ru.model_hr_leave_type
|
||||||
|
msgid "Time Off Type"
|
||||||
|
msgstr "Тип отсутсвия"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model:ir.model.fields,field_description:hr_holidays_ru.field_holidays_calendar_leaves__time_type
|
||||||
|
msgid "Time Type"
|
||||||
|
msgstr "Тип времени"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model:ir.model.fields,field_description:hr_holidays_ru.field_hr_leave_allocation__type_request_unit
|
||||||
|
msgid "Type Request Unit"
|
||||||
|
msgstr "Тип Запрос Единица измерения"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model:ir.model.fields,field_description:hr_holidays_ru.field_holidays_calendar_leaves__type_transfer_day
|
||||||
|
msgid "Type of day"
|
||||||
|
msgstr "Тип дня"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model:ir.model.fields,help:hr_holidays_ru.field_holidays_calendar_leaves__type_transfer_day
|
||||||
|
msgid ""
|
||||||
|
"Type of day for right calculate count of working hours and vacation days"
|
||||||
|
msgstr "Тип дня для правильного подсчета рабочих часов и дней отпуска"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model_terms:ir.ui.view,arch_db:hr_holidays_ru.open_view_holiday_settings_tree
|
||||||
|
msgid "Weekends"
|
||||||
|
msgstr "Выходные"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model:ir.model.fields,help:hr_holidays_ru.field_holidays_calendar_leaves__time_type
|
||||||
|
msgid ""
|
||||||
|
"Whether this should be computed as a holiday or as work time (eg: formation)"
|
||||||
|
msgstr "Должен ли он рассчитываться как выходной или как рабочее время"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model_terms:ir.ui.view,arch_db:hr_holidays_ru.open_view_holiday_settings_form
|
||||||
|
#: model_terms:ir.ui.view,arch_db:hr_holidays_ru.open_view_holiday_settings_tree
|
||||||
|
msgid "Working Time"
|
||||||
|
msgstr "Рабочее время"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model:ir.model.fields.selection,name:hr_holidays_ru.selection__hr_leave_type__request_unit__day
|
||||||
|
#: model_terms:ir.ui.view,arch_db:hr_holidays_ru.hr_leave_view_kanban_inherit
|
||||||
|
msgid "Working days"
|
||||||
|
msgstr "День"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model:ir.model.fields,field_description:hr_holidays_ru.field_hr_leave__working_hours_display
|
||||||
|
msgid "Working hours"
|
||||||
|
msgstr "Кол-во рабочих часов"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model:ir.model.fields,help:hr_holidays_ru.field_hr_leave__working_hours_display
|
||||||
|
msgid "Working hours amount in selected period"
|
||||||
|
msgstr "Сумма рабочих часов за выбранный период"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model:ir.model.fields,field_description:hr_holidays_ru.field_holidays_calendar__year
|
||||||
|
msgid "Year"
|
||||||
|
msgstr "Год"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#. odoo-python
|
||||||
|
#: code:addons/hr_holidays_ru/models/hr_leave.py:0
|
||||||
|
#: code:addons/hr_holidays_ru/models/hr_leave_allocation.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid "calendar day(s)"
|
||||||
|
msgstr "кол-во календарных дней"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#. odoo-javascript
|
||||||
|
#: code:addons/hr_holidays_ru/static/src/views/calendar/filter_panel/calendar_filter_panel.xml:0
|
||||||
|
#, python-format
|
||||||
|
msgid "continuous days:"
|
||||||
|
msgstr "календарных:"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model:ir.model,name:hr_holidays_ru.model_holidays_calendar
|
||||||
|
msgid "holidays.calendar"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#: model:ir.model,name:hr_holidays_ru.model_holidays_calendar_leaves
|
||||||
|
msgid "holidays.calendar.leaves"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#. odoo-python
|
||||||
|
#: code:addons/hr_holidays_ru/models/hr_leave.py:0
|
||||||
|
#: code:addons/hr_holidays_ru/models/hr_leave_allocation.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid "hour(s)"
|
||||||
|
msgstr "кол-во часов"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#. odoo-javascript
|
||||||
|
#: code:addons/hr_holidays_ru/static/src/views/calendar/filter_panel/calendar_filter_panel.xml:0
|
||||||
|
#, python-format
|
||||||
|
msgid "non-working days and holidays:"
|
||||||
|
msgstr "вых./праздн.:"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#. odoo-python
|
||||||
|
#: code:addons/hr_holidays_ru/models/hr_leave.py:0
|
||||||
|
#: code:addons/hr_holidays_ru/models/hr_leave_allocation.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid "working day(s)"
|
||||||
|
msgstr "кол-во рабочих дней"
|
||||||
|
|
||||||
|
#. module: hr_holidays_ru
|
||||||
|
#. odoo-javascript
|
||||||
|
#: code:addons/hr_holidays_ru/static/src/views/calendar/filter_panel/calendar_filter_panel.xml:0
|
||||||
|
#, python-format
|
||||||
|
msgid "working days:"
|
||||||
|
msgstr "кол-во рабочих дней"
|
||||||
|
|
||||||
|
#. module: hr_holidays
|
||||||
|
#: model:ir.ui.menu,name:hr_holidays.hr_holidays_menu_manager_approve_allocations
|
||||||
|
msgid "Allocations"
|
||||||
|
msgstr "Начисления дней отпуска"
|
7
models/__init__.py
Normal file
7
models/__init__.py
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import hr_leave
|
||||||
|
from . import hr_leave_allocation
|
||||||
|
from . import hr_leave_type
|
||||||
|
from . import holidays
|
||||||
|
from . import resource
|
197
models/holidays.py
Normal file
197
models/holidays.py
Normal file
|
@ -0,0 +1,197 @@
|
||||||
|
# -*- 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.")
|
||||||
|
)
|
259
models/hr_leave.py
Normal file
259
models/hr_leave.py
Normal file
|
@ -0,0 +1,259 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import logging
|
||||||
|
from datetime import datetime, timedelta, date
|
||||||
|
|
||||||
|
from odoo import models, fields, api
|
||||||
|
from odoo.addons.resource.models.utils import HOURS_PER_DAY
|
||||||
|
from odoo.tools.float_utils import float_round
|
||||||
|
from odoo.tools.translate import _
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
DEFAULT_DAYS = 7
|
||||||
|
|
||||||
|
|
||||||
|
class HolidayRequest(models.Model):
|
||||||
|
_inherit = "hr.leave"
|
||||||
|
|
||||||
|
def _get_duration(self, check_leave_type=True, resource_calendar=None):
|
||||||
|
if self.leave_type_request_unit == "c_day":
|
||||||
|
return self.employee_id.get_all_days_data(
|
||||||
|
self.date_from, self.date_to, self.holiday_status_id.exclude_holidays
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return super()._get_duration(check_leave_type, resource_calendar)
|
||||||
|
|
||||||
|
working_hours_display = fields.Float(
|
||||||
|
"Working hours",
|
||||||
|
help="Working hours amount in selected period",
|
||||||
|
compute="_compute_working_hours_display",
|
||||||
|
copy=False,
|
||||||
|
readonly=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def compute_work_weekends(date_from, date_to, collection):
|
||||||
|
days = set()
|
||||||
|
|
||||||
|
for event in collection:
|
||||||
|
if (
|
||||||
|
date_from
|
||||||
|
and event.date_to < date_from.date()
|
||||||
|
or date_to
|
||||||
|
and event.date_from > date_to.date()
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
|
||||||
|
start = max(event.date_from, date_from.date())
|
||||||
|
until = min(event.date_to, date_to.date())
|
||||||
|
|
||||||
|
if event.type_transfer_day == "is_workday":
|
||||||
|
for i in range((until - start + timedelta(1)).days):
|
||||||
|
today = start + timedelta(i)
|
||||||
|
days.add(today)
|
||||||
|
else:
|
||||||
|
for i in range((until - start + timedelta(1)).days):
|
||||||
|
today = start + timedelta(i)
|
||||||
|
# weekends check
|
||||||
|
if today.weekday() != 5 and today.weekday() != 6:
|
||||||
|
days.add(today)
|
||||||
|
return timedelta(len(days)).days
|
||||||
|
|
||||||
|
@api.depends("number_of_days", "date_from", "date_to")
|
||||||
|
def _compute_working_hours_display(self):
|
||||||
|
for holiday in self:
|
||||||
|
calendar = (
|
||||||
|
holiday.employee_id.resource_calendar_id
|
||||||
|
or self.env.user.company_id.resource_calendar_id
|
||||||
|
)
|
||||||
|
count_work_weekends = holiday.compute_work_weekends(
|
||||||
|
holiday.date_from,
|
||||||
|
holiday.date_to,
|
||||||
|
self.env["holidays.calendar.leaves"].search(
|
||||||
|
[
|
||||||
|
("type_transfer_day", "=", "is_workday"),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
)
|
||||||
|
count_non_working_days = holiday.compute_work_weekends(
|
||||||
|
holiday.date_from,
|
||||||
|
holiday.date_to,
|
||||||
|
self.env["holidays.calendar.leaves"].search(
|
||||||
|
[
|
||||||
|
("type_transfer_day", "=", "is_day_off"),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
)
|
||||||
|
count_holidays = holiday.compute_work_weekends(
|
||||||
|
holiday.date_from,
|
||||||
|
holiday.date_to,
|
||||||
|
self.env["holidays.calendar.leaves"].search(
|
||||||
|
[
|
||||||
|
("type_transfer_day", "=", "is_holiday"),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
if holiday.date_from and holiday.date_to:
|
||||||
|
date_start = datetime(
|
||||||
|
year=holiday.date_from.year,
|
||||||
|
month=holiday.date_from.month,
|
||||||
|
day=holiday.date_from.day,
|
||||||
|
hour=0,
|
||||||
|
minute=0,
|
||||||
|
second=0,
|
||||||
|
)
|
||||||
|
date_end = datetime(
|
||||||
|
year=holiday.date_to.year,
|
||||||
|
month=holiday.date_to.month,
|
||||||
|
day=holiday.date_to.day,
|
||||||
|
hour=23,
|
||||||
|
minute=59,
|
||||||
|
second=59,
|
||||||
|
)
|
||||||
|
number_of_hours = calendar.get_work_hours_count(
|
||||||
|
date_start, date_end, compute_leaves=False
|
||||||
|
)
|
||||||
|
holiday.working_hours_display = number_of_hours + HOURS_PER_DAY * (
|
||||||
|
count_work_weekends - count_non_working_days - count_holidays
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
holiday.working_hours_display = 0
|
||||||
|
|
||||||
|
@api.depends("number_of_hours_display", "number_of_days_display")
|
||||||
|
def _compute_duration_display(self):
|
||||||
|
for leave in self:
|
||||||
|
amount = (
|
||||||
|
float_round(leave.number_of_hours_display, precision_digits=2)
|
||||||
|
if leave.leave_type_request_unit == "hour"
|
||||||
|
else float_round(leave.number_of_days_display, precision_digits=2)
|
||||||
|
)
|
||||||
|
if leave.leave_type_request_unit == "c_day":
|
||||||
|
units = _("calendar day(s)")
|
||||||
|
elif leave.leave_type_request_unit == "hour":
|
||||||
|
units = _("hour(s)")
|
||||||
|
else:
|
||||||
|
units = _("working day(s)")
|
||||||
|
leave.duration_display = "%g %s" % (amount, units)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def notify_before(leave):
|
||||||
|
"""Checks whether days_before_holidays is set, if it's not, use DEFAULT_DAYS"""
|
||||||
|
return leave.holiday_status_id.days_before_holidays or DEFAULT_DAYS
|
||||||
|
|
||||||
|
def _check_users_holidays(self):
|
||||||
|
"""
|
||||||
|
Method calls every day and checks the planned holidays.
|
||||||
|
If needed, an email notification to the responsible employee will be sent.
|
||||||
|
"""
|
||||||
|
leaves = self.env["hr.leave"].search(
|
||||||
|
[
|
||||||
|
("holiday_status_id.notify_before_start_holidays", "=", True),
|
||||||
|
("holiday_status_id.responsible_employee_to_notify_id", "!=", None),
|
||||||
|
("date_from", ">=", date.today()),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
for leave in leaves:
|
||||||
|
days_before_holidays = self.notify_before(leave)
|
||||||
|
holidays_start_date = leave.date_from
|
||||||
|
|
||||||
|
if (
|
||||||
|
holidays_start_date - timedelta(days=days_before_holidays)
|
||||||
|
).date() == date.today():
|
||||||
|
template_name = "hr_holidays_ru.holidays_notification_template"
|
||||||
|
template = self.env.ref(template_name)
|
||||||
|
template.send_mail(leave.id)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def append_number_by_type(leave, res):
|
||||||
|
if leave.leave_type_request_unit == "c_day":
|
||||||
|
res.append(
|
||||||
|
(
|
||||||
|
leave.id,
|
||||||
|
_("%s : %.2f Calendar day(s)")
|
||||||
|
% (
|
||||||
|
leave.name or leave.holiday_status_id.name,
|
||||||
|
leave.number_of_days,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif leave.leave_type_request_unit == "hour":
|
||||||
|
res.append(
|
||||||
|
(
|
||||||
|
leave.id,
|
||||||
|
_("%s : %.2f hour(s)")
|
||||||
|
% (
|
||||||
|
leave.name or leave.holiday_status_id.name,
|
||||||
|
leave.number_of_hours_display,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
res.append(
|
||||||
|
(
|
||||||
|
leave.id,
|
||||||
|
_("%s : %.2f Working day(s)")
|
||||||
|
% (
|
||||||
|
leave.name or leave.holiday_status_id.name,
|
||||||
|
leave.number_of_days,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def append_number_by_type_without_short_name(leave, res):
|
||||||
|
if leave.holiday_type == "company":
|
||||||
|
target = leave.mode_company_id.name
|
||||||
|
elif leave.holiday_type == "department":
|
||||||
|
target = leave.department_id.name
|
||||||
|
elif leave.holiday_type == "category":
|
||||||
|
target = leave.category_id.name
|
||||||
|
else:
|
||||||
|
target = leave.employee_id.name
|
||||||
|
|
||||||
|
if leave.leave_type_request_unit == "c_day":
|
||||||
|
res.append(
|
||||||
|
(
|
||||||
|
leave.id,
|
||||||
|
_("%s on %s : %.2f Calendar day(s)")
|
||||||
|
% (
|
||||||
|
target,
|
||||||
|
leave.holiday_status_id.name,
|
||||||
|
leave.number_of_days,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif leave.leave_type_request_unit == "hour":
|
||||||
|
res.append(
|
||||||
|
(
|
||||||
|
leave.id,
|
||||||
|
_("%s on %s : %.2f hour(s)")
|
||||||
|
% (
|
||||||
|
target,
|
||||||
|
leave.holiday_status_id.name,
|
||||||
|
leave.number_of_hours_display,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
res.append(
|
||||||
|
(
|
||||||
|
leave.id,
|
||||||
|
_("%s on %s : %.2f Working day(s)")
|
||||||
|
% (
|
||||||
|
target,
|
||||||
|
leave.holiday_status_id.name,
|
||||||
|
leave.number_of_days,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def name_get(self):
|
||||||
|
res = []
|
||||||
|
for leave in self:
|
||||||
|
if self.env.context.get("short_name"):
|
||||||
|
self.append_number_by_type(leave, res)
|
||||||
|
else:
|
||||||
|
self.append_number_by_type_without_short_name(leave, res)
|
||||||
|
return res
|
31
models/hr_leave_allocation.py
Normal file
31
models/hr_leave_allocation.py
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from odoo import models, api, fields
|
||||||
|
from odoo.tools.float_utils import float_round
|
||||||
|
from odoo.tools.translate import _
|
||||||
|
|
||||||
|
|
||||||
|
class HolidaysAllocation(models.Model):
|
||||||
|
_inherit = "hr.leave.allocation"
|
||||||
|
|
||||||
|
type_request_unit = fields.Selection(
|
||||||
|
selection_add=[
|
||||||
|
("c_day", "Calendar Days"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
@api.depends("number_of_hours_display", "number_of_days_display")
|
||||||
|
def _compute_duration_display(self):
|
||||||
|
for allocation in self:
|
||||||
|
amount = (
|
||||||
|
float_round(allocation.number_of_hours_display, precision_digits=2)
|
||||||
|
if allocation.type_request_unit == "hour"
|
||||||
|
else float_round(allocation.number_of_days_display, precision_digits=2)
|
||||||
|
)
|
||||||
|
if allocation.type_request_unit == "c_day":
|
||||||
|
units = _("calendar day(s)")
|
||||||
|
elif allocation.type_request_unit == "hour":
|
||||||
|
units = _("hour(s)")
|
||||||
|
else:
|
||||||
|
units = _("working day(s)")
|
||||||
|
allocation.duration_display = "%g %s" % (amount, units)
|
63
models/hr_leave_type.py
Normal file
63
models/hr_leave_type.py
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
import logging
|
||||||
|
from collections import defaultdict
|
||||||
|
from datetime import time, timedelta
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
from odoo import api, fields, models
|
||||||
|
from odoo.tools.translate import _
|
||||||
|
from odoo.tools.float_utils import float_round
|
||||||
|
from odoo.addons.resource.models.utils import Intervals
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class HolidaysType(models.Model):
|
||||||
|
_inherit = "hr.leave.type"
|
||||||
|
|
||||||
|
request_unit = fields.Selection(
|
||||||
|
[
|
||||||
|
("c_day", "Calendar days"),
|
||||||
|
("day", "Working days"),
|
||||||
|
("hour", "Hours"),
|
||||||
|
],
|
||||||
|
default="c_day",
|
||||||
|
string="Take Leaves in",
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
exclude_holidays = fields.Boolean(
|
||||||
|
"Exclude holidays",
|
||||||
|
help="Check this box to exclude holidays from calendar days amount.",
|
||||||
|
)
|
||||||
|
|
||||||
|
notify_before_start_holidays = fields.Boolean(
|
||||||
|
"Notify about start holidays",
|
||||||
|
help="Is it necessary to notify the responsible employee before the start of the holidays?",
|
||||||
|
)
|
||||||
|
responsible_employee_to_notify_id = fields.Many2one(
|
||||||
|
"res.users",
|
||||||
|
string="Employee to notify",
|
||||||
|
help="The employee who needs to be notified",
|
||||||
|
)
|
||||||
|
days_before_holidays = fields.Integer(
|
||||||
|
string="Days before holidays",
|
||||||
|
help="How many days before it is necessary to notify the responsible employee about the start of the holidays.",
|
||||||
|
)
|
||||||
|
|
||||||
|
def name_get(self):
|
||||||
|
if not self._context.get("employee_id"):
|
||||||
|
# leave counts is based on employee_id, would be inaccurate if not based on correct employee
|
||||||
|
return super(HolidaysType, self).name_get()
|
||||||
|
res = []
|
||||||
|
for record in self:
|
||||||
|
name = record.name
|
||||||
|
name = "%(name)s (%(count)s)" % {
|
||||||
|
"name": name,
|
||||||
|
"count": _("%g remaining ")
|
||||||
|
% (
|
||||||
|
float_round(record.virtual_remaining_leaves, precision_digits=2)
|
||||||
|
or 0.0,
|
||||||
|
)
|
||||||
|
+ (_(" hours") if record.request_unit == "hour" else _(" days")),
|
||||||
|
}
|
||||||
|
res.append((record.id, name))
|
||||||
|
return res
|
390
models/resource.py
Normal file
390
models/resource.py
Normal file
|
@ -0,0 +1,390 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import logging
|
||||||
|
from datetime import datetime
|
||||||
|
from datetime import timedelta
|
||||||
|
from pytz import timezone, utc
|
||||||
|
from datetime import datetime, time
|
||||||
|
from collections import defaultdict
|
||||||
|
from dateutil.relativedelta import relativedelta
|
||||||
|
import pytz
|
||||||
|
|
||||||
|
from odoo import models, fields
|
||||||
|
from odoo.addons.resource.models.utils import HOURS_PER_DAY
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Employee(models.Model):
|
||||||
|
_inherit = "hr.employee"
|
||||||
|
|
||||||
|
def _get_consumed_leaves(self, leave_types, target_date=False, ignore_future=False):
|
||||||
|
employees = self or self._get_contextual_employee()
|
||||||
|
leaves_domain = [
|
||||||
|
("holiday_status_id", "in", leave_types.ids),
|
||||||
|
("employee_id", "in", employees.ids),
|
||||||
|
("state", "in", ["confirm", "validate1", "validate"]),
|
||||||
|
]
|
||||||
|
if self.env.context.get("ignored_leave_ids"):
|
||||||
|
leaves_domain.append(
|
||||||
|
("id", "not in", self.env.context.get("ignored_leave_ids"))
|
||||||
|
)
|
||||||
|
|
||||||
|
if not target_date:
|
||||||
|
target_date = fields.Date.today()
|
||||||
|
if ignore_future:
|
||||||
|
leaves_domain.append(("date_from", "<=", target_date))
|
||||||
|
leaves = self.env["hr.leave"].search(leaves_domain)
|
||||||
|
leaves_per_employee_type = defaultdict(
|
||||||
|
lambda: defaultdict(lambda: self.env["hr.leave"])
|
||||||
|
)
|
||||||
|
for leave in leaves:
|
||||||
|
leaves_per_employee_type[leave.employee_id][
|
||||||
|
leave.holiday_status_id
|
||||||
|
] |= leave
|
||||||
|
|
||||||
|
allocations = (
|
||||||
|
self.env["hr.leave.allocation"]
|
||||||
|
.with_context(active_test=False)
|
||||||
|
.search(
|
||||||
|
[
|
||||||
|
("employee_id", "in", employees.ids),
|
||||||
|
("holiday_status_id", "in", leave_types.ids),
|
||||||
|
("state", "=", "validate"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
.filtered(lambda al: al.active or not al.employee_id.active)
|
||||||
|
)
|
||||||
|
allocations_per_employee_type = defaultdict(
|
||||||
|
lambda: defaultdict(lambda: self.env["hr.leave.allocation"])
|
||||||
|
)
|
||||||
|
for allocation in allocations:
|
||||||
|
allocations_per_employee_type[allocation.employee_id][
|
||||||
|
allocation.holiday_status_id
|
||||||
|
] |= allocation
|
||||||
|
|
||||||
|
# _get_consumed_leaves returns a tuple of two dictionnaries.
|
||||||
|
# 1) The first is a dictionary to map the number of days/hours of leaves taken per allocation
|
||||||
|
# The structure is the following:
|
||||||
|
# - KEYS:
|
||||||
|
# allocation_leaves_consumed
|
||||||
|
# |--employee_id
|
||||||
|
# |--holiday_status_id
|
||||||
|
# |--allocation
|
||||||
|
# |--virtual_leaves_taken
|
||||||
|
# |--leaves_taken
|
||||||
|
# |--virtual_remaining_leaves
|
||||||
|
# |--remaining_leaves
|
||||||
|
# |--max_leaves
|
||||||
|
# |--accrual_bonus
|
||||||
|
# - VALUES:
|
||||||
|
# Integer representing the number of (virtual) remaining leaves, (virtual) leaves taken or max leaves
|
||||||
|
# for each allocation.
|
||||||
|
# leaves_taken and remaining_leaves only take into account validated leaves, while the "virtual" equivalent are
|
||||||
|
# also based on leaves in "confirm" or "validate1" state.
|
||||||
|
# Accrual bonus gives the amount of additional leaves that will have been granted at the given
|
||||||
|
# target_date in comparison to today.
|
||||||
|
# The unit is in hour or days depending on the leave type request unit
|
||||||
|
# 2) The second is a dictionary mapping the remaining days per employee and per leave type that are either
|
||||||
|
# not taken into account by the allocations, mainly because accruals don't take future leaves into account.
|
||||||
|
# This is used to warn the user if the leaves they takes bring them above their available limit.
|
||||||
|
# - KEYS:
|
||||||
|
# allocation_leaves_consumed
|
||||||
|
# |--employee_id
|
||||||
|
# |--holiday_status_id
|
||||||
|
# |--to_recheck_leaves
|
||||||
|
# |--excess_days
|
||||||
|
# |--exceeding_duration
|
||||||
|
# - VALUES:
|
||||||
|
# "to_recheck_leaves" stores every leave that is not yet taken into account by the "allocation_leaves_consumed" dictionary.
|
||||||
|
# "excess_days" represents the excess amount that somehow isn't taken into account by the first dictionary.
|
||||||
|
# "exceeding_duration" sum up the to_recheck_leaves duration and compares it to the maximum allocated for that time period.
|
||||||
|
allocations_leaves_consumed = defaultdict(
|
||||||
|
lambda: defaultdict(lambda: defaultdict(lambda: defaultdict(lambda: 0)))
|
||||||
|
)
|
||||||
|
|
||||||
|
to_recheck_leaves_per_leave_type = defaultdict(
|
||||||
|
lambda: defaultdict(
|
||||||
|
lambda: {
|
||||||
|
"excess_days": defaultdict(
|
||||||
|
lambda: {
|
||||||
|
"amount": 0,
|
||||||
|
"is_virtual": True,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
"exceeding_duration": 0,
|
||||||
|
"to_recheck_leaves": self.env["hr.leave"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
for allocation in allocations:
|
||||||
|
allocation_data = allocations_leaves_consumed[allocation.employee_id][
|
||||||
|
allocation.holiday_status_id
|
||||||
|
][allocation]
|
||||||
|
future_leaves = 0
|
||||||
|
if allocation.allocation_type == "accrual":
|
||||||
|
future_leaves = allocation._get_future_leaves_on(target_date)
|
||||||
|
max_leaves = (
|
||||||
|
allocation.number_of_hours_display
|
||||||
|
if allocation.type_request_unit in ["hour"]
|
||||||
|
else allocation.number_of_days_display
|
||||||
|
)
|
||||||
|
max_leaves += future_leaves
|
||||||
|
allocation_data.update(
|
||||||
|
{
|
||||||
|
"max_leaves": max_leaves,
|
||||||
|
"accrual_bonus": future_leaves,
|
||||||
|
"virtual_remaining_leaves": max_leaves,
|
||||||
|
"remaining_leaves": max_leaves,
|
||||||
|
"leaves_taken": 0,
|
||||||
|
"virtual_leaves_taken": 0,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
for employee in employees:
|
||||||
|
for leave_type in leave_types:
|
||||||
|
allocations_with_date_to = self.env["hr.leave.allocation"]
|
||||||
|
allocations_without_date_to = self.env["hr.leave.allocation"]
|
||||||
|
for leave_allocation in allocations_per_employee_type[employee][
|
||||||
|
leave_type
|
||||||
|
]:
|
||||||
|
if leave_allocation.date_to:
|
||||||
|
allocations_with_date_to |= leave_allocation
|
||||||
|
else:
|
||||||
|
allocations_without_date_to |= leave_allocation
|
||||||
|
sorted_leave_allocations = (
|
||||||
|
allocations_with_date_to.sorted(key="date_to")
|
||||||
|
+ allocations_without_date_to
|
||||||
|
)
|
||||||
|
|
||||||
|
if leave_type.request_unit in ["c_day", "day", "half_day"]:
|
||||||
|
leave_duration_field = "number_of_days"
|
||||||
|
leave_unit = "days"
|
||||||
|
else:
|
||||||
|
leave_duration_field = "number_of_hours_display"
|
||||||
|
leave_unit = "hours"
|
||||||
|
|
||||||
|
leave_type_data = allocations_leaves_consumed[employee][leave_type]
|
||||||
|
for leave in leaves_per_employee_type[employee][leave_type].sorted(
|
||||||
|
"date_from"
|
||||||
|
):
|
||||||
|
leave_duration = leave[leave_duration_field]
|
||||||
|
skip_excess = False
|
||||||
|
|
||||||
|
if (
|
||||||
|
sorted_leave_allocations.filtered(
|
||||||
|
lambda alloc: alloc.allocation_type == "accrual"
|
||||||
|
)
|
||||||
|
and leave.date_from.date() > target_date
|
||||||
|
):
|
||||||
|
to_recheck_leaves_per_leave_type[employee][leave_type][
|
||||||
|
"to_recheck_leaves"
|
||||||
|
] |= leave
|
||||||
|
skip_excess = True
|
||||||
|
continue
|
||||||
|
|
||||||
|
if leave_type.requires_allocation == "yes":
|
||||||
|
for allocation in sorted_leave_allocations:
|
||||||
|
# We don't want to include future leaves linked to accruals into the total count of available leaves.
|
||||||
|
# However, we'll need to check if those leaves take more than what will be accrued in total of those days
|
||||||
|
# to give a warning if the total exceeds what will be accrued.
|
||||||
|
if allocation.date_from > leave.date_to.date() or (
|
||||||
|
allocation.date_to
|
||||||
|
and allocation.date_to < leave.date_from.date()
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
interval_start = max(
|
||||||
|
leave.date_from,
|
||||||
|
datetime.combine(allocation.date_from, time.min),
|
||||||
|
)
|
||||||
|
interval_end = min(
|
||||||
|
leave.date_to,
|
||||||
|
datetime.combine(allocation.date_to, time.max)
|
||||||
|
if allocation.date_to
|
||||||
|
else leave.date_to,
|
||||||
|
)
|
||||||
|
duration = leave[leave_duration_field]
|
||||||
|
if (
|
||||||
|
leave.date_from != interval_start
|
||||||
|
or leave.date_to != interval_end
|
||||||
|
):
|
||||||
|
duration_info = employee._get_calendar_attendances(
|
||||||
|
interval_start.replace(tzinfo=pytz.UTC),
|
||||||
|
interval_end.replace(tzinfo=pytz.UTC),
|
||||||
|
)
|
||||||
|
duration = duration_info[
|
||||||
|
"hours" if leave_unit == "hours" else "days"
|
||||||
|
]
|
||||||
|
max_allowed_duration = min(
|
||||||
|
duration,
|
||||||
|
leave_type_data[allocation]["virtual_remaining_leaves"],
|
||||||
|
)
|
||||||
|
|
||||||
|
if not max_allowed_duration:
|
||||||
|
continue
|
||||||
|
|
||||||
|
allocated_time = min(max_allowed_duration, leave_duration)
|
||||||
|
leave_type_data[allocation][
|
||||||
|
"virtual_leaves_taken"
|
||||||
|
] += allocated_time
|
||||||
|
leave_type_data[allocation][
|
||||||
|
"virtual_remaining_leaves"
|
||||||
|
] -= allocated_time
|
||||||
|
if leave.state == "validate":
|
||||||
|
leave_type_data[allocation][
|
||||||
|
"leaves_taken"
|
||||||
|
] += allocated_time
|
||||||
|
leave_type_data[allocation][
|
||||||
|
"remaining_leaves"
|
||||||
|
] -= allocated_time
|
||||||
|
|
||||||
|
leave_duration -= allocated_time
|
||||||
|
if not leave_duration:
|
||||||
|
break
|
||||||
|
if round(leave_duration, 2) > 0 and not skip_excess:
|
||||||
|
to_recheck_leaves_per_leave_type[employee][leave_type][
|
||||||
|
"excess_days"
|
||||||
|
][leave.date_to.date()] = {
|
||||||
|
"amount": leave_duration,
|
||||||
|
"is_virtual": leave.state != "validate",
|
||||||
|
"leave_id": leave.id,
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
if leave_unit == "hours":
|
||||||
|
allocated_time = leave.number_of_hours_display
|
||||||
|
else:
|
||||||
|
allocated_time = leave.number_of_days_display
|
||||||
|
leave_type_data[False]["virtual_leaves_taken"] += allocated_time
|
||||||
|
leave_type_data[False]["virtual_remaining_leaves"] = 0
|
||||||
|
leave_type_data[False]["remaining_leaves"] = 0
|
||||||
|
if leave.state == "validate":
|
||||||
|
leave_type_data[False]["leaves_taken"] += allocated_time
|
||||||
|
|
||||||
|
for employee in to_recheck_leaves_per_leave_type:
|
||||||
|
for leave_type in to_recheck_leaves_per_leave_type[employee]:
|
||||||
|
content = to_recheck_leaves_per_leave_type[employee][leave_type]
|
||||||
|
consumed_content = allocations_leaves_consumed[employee][leave_type]
|
||||||
|
if content["to_recheck_leaves"]:
|
||||||
|
date_to_simulate = max(
|
||||||
|
content["to_recheck_leaves"].mapped("date_from")
|
||||||
|
).date()
|
||||||
|
latest_accrual_bonus = 0
|
||||||
|
date_accrual_bonus = 0
|
||||||
|
virtual_remaining = 0
|
||||||
|
additional_leaves_duration = 0
|
||||||
|
for allocation in consumed_content:
|
||||||
|
latest_accrual_bonus += (
|
||||||
|
allocation
|
||||||
|
and allocation._get_future_leaves_on(date_to_simulate)
|
||||||
|
)
|
||||||
|
date_accrual_bonus += consumed_content[allocation][
|
||||||
|
"accrual_bonus"
|
||||||
|
]
|
||||||
|
virtual_remaining += consumed_content[allocation][
|
||||||
|
"virtual_remaining_leaves"
|
||||||
|
]
|
||||||
|
for leave in content["to_recheck_leaves"]:
|
||||||
|
additional_leaves_duration += (
|
||||||
|
leave.number_of_hours
|
||||||
|
if leave_type.request_unit == "hours"
|
||||||
|
else leave.number_of_days
|
||||||
|
)
|
||||||
|
latest_remaining = (
|
||||||
|
virtual_remaining - date_accrual_bonus + latest_accrual_bonus
|
||||||
|
)
|
||||||
|
content["exceeding_duration"] = round(
|
||||||
|
min(0, latest_remaining - additional_leaves_duration), 2
|
||||||
|
)
|
||||||
|
|
||||||
|
return (allocations_leaves_consumed, to_recheck_leaves_per_leave_type)
|
||||||
|
|
||||||
|
|
||||||
|
class ResourceMixin(models.AbstractModel):
|
||||||
|
_inherit = "resource.mixin"
|
||||||
|
|
||||||
|
def get_all_days_data(self, date_from, date_to, exclude_holidays):
|
||||||
|
"""
|
||||||
|
Returns days amount as float, like 'get_work_days_data()'
|
||||||
|
"""
|
||||||
|
# Set timezone if no timezone is explicitly given
|
||||||
|
user_tz = timezone(self.env.context.get("tz") or self.tz)
|
||||||
|
if not user_tz:
|
||||||
|
user_tz = utc
|
||||||
|
if not date_from.tzinfo:
|
||||||
|
date_from = user_tz.localize(date_from)
|
||||||
|
if not date_to.tzinfo:
|
||||||
|
date_to = user_tz.localize(date_to)
|
||||||
|
# Bring booth dates to the same timezone
|
||||||
|
if date_from.tzinfo != date_to.tzinfo:
|
||||||
|
date_to = date_to.astimezone(date_from.tzinfo)
|
||||||
|
# Handle holidays in selected period
|
||||||
|
holidays_dur = timedelta(0)
|
||||||
|
if exclude_holidays:
|
||||||
|
holidays_dur = (
|
||||||
|
self.env.user.company_id.resource_calendar_id.get_holidays_duration(
|
||||||
|
date_from, date_to
|
||||||
|
)
|
||||||
|
)
|
||||||
|
duration = date_to.date() - date_from.date() + timedelta(1) - holidays_dur
|
||||||
|
days = float(duration.days)
|
||||||
|
hours = days * HOURS_PER_DAY
|
||||||
|
return (days, hours)
|
||||||
|
|
||||||
|
|
||||||
|
class ResourceCalendar(models.Model):
|
||||||
|
_inherit = "resource.calendar"
|
||||||
|
|
||||||
|
def get_holidays_duration(self, start_dt, end_dt):
|
||||||
|
"""
|
||||||
|
Returns timedelta object which is duration of all holidays included into
|
||||||
|
specified period.
|
||||||
|
"""
|
||||||
|
days = set()
|
||||||
|
holidays_ids = self.env["holidays.calendar.leaves"].search(
|
||||||
|
[("type_transfer_day", "=", "is_holiday")]
|
||||||
|
)
|
||||||
|
|
||||||
|
for holiday in holidays_ids:
|
||||||
|
# Transformation date to datetime
|
||||||
|
date_from = datetime(
|
||||||
|
year=holiday.date_from.year,
|
||||||
|
month=holiday.date_from.month,
|
||||||
|
day=holiday.date_from.day,
|
||||||
|
hour=0,
|
||||||
|
minute=0,
|
||||||
|
second=0,
|
||||||
|
)
|
||||||
|
date_to = datetime(
|
||||||
|
year=holiday.date_to.year,
|
||||||
|
month=holiday.date_to.month,
|
||||||
|
day=holiday.date_to.day,
|
||||||
|
hour=23,
|
||||||
|
minute=59,
|
||||||
|
second=59,
|
||||||
|
)
|
||||||
|
# Dates in database are without timezones, and time in fact for UTC timezone,
|
||||||
|
# so it should be added explicitly.
|
||||||
|
holiday_date_from = utc.localize(date_from) if date_from else None
|
||||||
|
holiday_date_to = utc.localize(date_to) if date_to else None
|
||||||
|
# Bring booth dates to the holiday timezone
|
||||||
|
if holiday_date_from and start_dt.tzinfo != holiday_date_from.tzinfo:
|
||||||
|
start_dt = start_dt.astimezone(holiday_date_from.tzinfo)
|
||||||
|
if holiday_date_to and end_dt.tzinfo != holiday_date_to.tzinfo:
|
||||||
|
end_dt = end_dt.astimezone(holiday_date_to.tzinfo)
|
||||||
|
# Check periods intersection
|
||||||
|
if (
|
||||||
|
holiday_date_from
|
||||||
|
and end_dt < holiday_date_from
|
||||||
|
or holiday_date_to
|
||||||
|
and start_dt > holiday_date_to
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
# Handle this holiday
|
||||||
|
start = start_dt.date()
|
||||||
|
if holiday_date_from:
|
||||||
|
start = max(start, holiday_date_from.date())
|
||||||
|
until = end_dt.date()
|
||||||
|
if holiday_date_to:
|
||||||
|
until = min(until, holiday_date_to.date())
|
||||||
|
for i in range((until - start + timedelta(1)).days):
|
||||||
|
days.add(start + timedelta(i))
|
||||||
|
return timedelta(len(days))
|
5
security/ir.model.access.csv
Normal file
5
security/ir.model.access.csv
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||||
|
access_holidays_calendar_manager,access.holidays.calendar.manager,model_holidays_calendar,hr_holidays.group_hr_holidays_manager,1,1,1,1
|
||||||
|
access_holidays_calendar_leaves_manager,access.holidays.calendar.leaves.manager,model_holidays_calendar_leaves,hr_holidays.group_hr_holidays_manager,1,1,1,1
|
||||||
|
access_holidays_calendar_user,access.holidays.calendar.user,model_holidays_calendar,base.group_user,1,0,0,0
|
||||||
|
access_holidays_calendar_leaves_user,access.holidays.calendar.leaves.user,model_holidays_calendar_leaves,base.group_user,1,0,0,0
|
|
63
static/src/css/calendar_renderer.css
Normal file
63
static/src/css/calendar_renderer.css
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
.fc-today-day-number-year {
|
||||||
|
position: relative;
|
||||||
|
color: black !important;
|
||||||
|
}
|
||||||
|
.fc-today-day-number-year::before {
|
||||||
|
content: "\f1db";
|
||||||
|
position: absolute;
|
||||||
|
font-family: FontAwesome;
|
||||||
|
font-size: 2.1em;
|
||||||
|
color: red;
|
||||||
|
line-height: 0.75em;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-today-day-number-month {
|
||||||
|
position: relative;
|
||||||
|
color: black !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-today-day-number-month::before {
|
||||||
|
content: "\f1db";
|
||||||
|
position: absolute;
|
||||||
|
font-family: FontAwesome;
|
||||||
|
font-size: 2em;
|
||||||
|
color: red;
|
||||||
|
line-height: 0.8em;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.o_holidays_legend {
|
||||||
|
vertical-align: middle;
|
||||||
|
background-color: #e9ecef;
|
||||||
|
font-weight: 600;
|
||||||
|
display: inline-block;
|
||||||
|
width: 24px;
|
||||||
|
height: 30px;
|
||||||
|
margin: 0 3px;
|
||||||
|
padding: 3px 0;
|
||||||
|
text-align: center;
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.o_non_working_legend {
|
||||||
|
vertical-align: middle;
|
||||||
|
background-color: #e9ecef;
|
||||||
|
display: inline-block;
|
||||||
|
width: 24px;
|
||||||
|
height: 30px;
|
||||||
|
margin: 0 3px;
|
||||||
|
padding: 3px 0;
|
||||||
|
text-align: center;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.o_holidays_week_days {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
.hr_holiday_calendar {
|
||||||
|
font-weight: bold;
|
||||||
|
color: red !important;
|
||||||
|
}
|
71
static/src/views/calendar/common/calendar_common_renderer.js
Normal file
71
static/src/views/calendar/common/calendar_common_renderer.js
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
/** @odoo-module **/
|
||||||
|
|
||||||
|
import { patch } from '@web/core/utils/patch'
|
||||||
|
|
||||||
|
import { TimeOffCalendarCommonRenderer } from '@hr_holidays/views/calendar/common/calendar_common_renderer'
|
||||||
|
import { onWillStart } from '@odoo/owl'
|
||||||
|
import { useService } from '@web/core/utils/hooks'
|
||||||
|
import { serializeDate } from '@web/core/l10n/dates'
|
||||||
|
|
||||||
|
patch(
|
||||||
|
TimeOffCalendarCommonRenderer.prototype,
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Adding onWillStart to load data from the holidays.calendar model to get holidays, non-working days and rescheduled working days.
|
||||||
|
*/
|
||||||
|
setup() {
|
||||||
|
super.setup(...arguments)
|
||||||
|
this.orm = useService('orm')
|
||||||
|
onWillStart(async () => {
|
||||||
|
const rangeStart = serializeDate(
|
||||||
|
this.props.model.rangeStart,
|
||||||
|
'datetime'
|
||||||
|
)
|
||||||
|
const rangeEnd = serializeDate(this.props.model.rangeEnd, 'datetime')
|
||||||
|
const [holidaysList, nonWorkingDaysList, rescheduledWorkDaysList] =
|
||||||
|
await this.orm.call('holidays.calendar', 'get_all_holidays', [
|
||||||
|
rangeStart,
|
||||||
|
rangeEnd
|
||||||
|
])
|
||||||
|
this.holidays = holidaysList
|
||||||
|
this.nonWorkingDays = nonWorkingDaysList
|
||||||
|
this.rescheduledWorkDays = rescheduledWorkDaysList
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redefining onDayRender, adding additional functionality to it to display holidays, non-working days and postponed working days.
|
||||||
|
*/
|
||||||
|
onDayRender(info) {
|
||||||
|
const date = luxon.DateTime.fromJSDate(info.date).toISODate();
|
||||||
|
if (this.props.model.unusualDays.includes(date)) {
|
||||||
|
info.el.classList.add("o_calendar_disabled");
|
||||||
|
}
|
||||||
|
// New code
|
||||||
|
if (this.nonWorkingDays.includes(date)) {
|
||||||
|
info.el.classList.add('o_calendar_disabled')
|
||||||
|
}
|
||||||
|
const element = document.querySelector(
|
||||||
|
`.fc-content-skeleton [data-date=${CSS.escape(date)}]`
|
||||||
|
)
|
||||||
|
if (element && element.classList.contains('fc-today')) {
|
||||||
|
element.classList.remove('fc-today')
|
||||||
|
element.classList.add('fc-today-day-number-month')
|
||||||
|
}
|
||||||
|
if (this.holidays.includes(date)) {
|
||||||
|
if (element) {
|
||||||
|
const day_number = element.querySelector('.fc-day-number')
|
||||||
|
day_number.classList.add('hr_holiday_calendar')
|
||||||
|
info.el.classList.add("o_calendar_disabled");
|
||||||
|
} else {
|
||||||
|
const days_in_week_element = document.querySelector(`.fc-head-container [data-date=${CSS.escape(date)}]`)
|
||||||
|
info.el.classList.add("o_calendar_disabled");
|
||||||
|
days_in_week_element.classList.add('o_holidays_week_days')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.rescheduledWorkDays.includes(date)) {
|
||||||
|
info.el.classList.remove('o_calendar_disabled')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
122
static/src/views/calendar/filter_panel/calendar_filter_panel.js
Normal file
122
static/src/views/calendar/filter_panel/calendar_filter_panel.js
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
/** @odoo-module **/
|
||||||
|
|
||||||
|
import { patch } from '@web/core/utils/patch'
|
||||||
|
|
||||||
|
import { TimeOffCalendarFilterPanel } from '@hr_holidays/views/calendar/filter_panel/calendar_filter_panel'
|
||||||
|
import { serializeDate } from '@web/core/l10n/dates'
|
||||||
|
import { onWillStart, onWillUpdateProps } from '@odoo/owl'
|
||||||
|
|
||||||
|
patch(TimeOffCalendarFilterPanel.prototype, {
|
||||||
|
/**
|
||||||
|
* Add onWillStart to load data from the holidays.calendar model to the dates displayed in the calendar, add onWillUpdateProps to update data when switching month, week or year
|
||||||
|
*/
|
||||||
|
setup() {
|
||||||
|
super.setup(...arguments)
|
||||||
|
onWillStart(this.countHolidays)
|
||||||
|
onWillUpdateProps(this.countHolidays)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The function counts the number of working days and days off
|
||||||
|
*/
|
||||||
|
async countHolidays() {
|
||||||
|
const rangeStart = this.props.model.rangeStart
|
||||||
|
const rangeEnd = this.props.model.rangeEnd
|
||||||
|
const firstDay = await this.getFirstMonthDay(rangeStart, rangeEnd)
|
||||||
|
if (!this.weekScale) {
|
||||||
|
const lastDay = await this.getLastMonthDay(firstDay, rangeStart, rangeEnd)
|
||||||
|
this.daysTotalAmount =
|
||||||
|
(new Date(lastDay) - new Date(firstDay)) / (1000 * 60 * 60 * 24) + 1
|
||||||
|
const unusualDays = this.getUnusualDaysList(firstDay, lastDay)
|
||||||
|
await this.getAllHolidays(firstDay, lastDay)
|
||||||
|
const nonWorkingDaysAndHolidaysSet = new Set([
|
||||||
|
...unusualDays,
|
||||||
|
...this.nonWorkingDays,
|
||||||
|
...this.holidays
|
||||||
|
])
|
||||||
|
const nonWorkingDaysAndHolidaysSetWithoutRescheduled = new Set(
|
||||||
|
[...nonWorkingDaysAndHolidaysSet].filter(
|
||||||
|
value => !new Set(this.rescheduledWorkDays).has(value)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
this.workingDaysAmount =
|
||||||
|
this.daysTotalAmount -
|
||||||
|
nonWorkingDaysAndHolidaysSetWithoutRescheduled.size
|
||||||
|
this.nonWorkingDaysAndHolidays =
|
||||||
|
nonWorkingDaysAndHolidaysSetWithoutRescheduled.size
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The function counts the number of working days, holidays and non-working days
|
||||||
|
*/
|
||||||
|
async getAllHolidays(firstDay, lastDay) {
|
||||||
|
const [holidaysList, nonWorkingDaysList, rescheduledWorkDaysList] =
|
||||||
|
await this.orm.call('holidays.calendar', 'get_all_holidays', [
|
||||||
|
firstDay,
|
||||||
|
lastDay
|
||||||
|
])
|
||||||
|
this.holidays = holidaysList
|
||||||
|
this.nonWorkingDays = nonWorkingDaysList
|
||||||
|
this.rescheduledWorkDays = rescheduledWorkDaysList
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The function defines the first day of the month
|
||||||
|
*/
|
||||||
|
getFirstMonthDay(rangeStart, rangeEnd) {
|
||||||
|
if ((rangeEnd - rangeStart) / (1000 * 60 * 60 * 24) > 7) {
|
||||||
|
this.weekScale = false
|
||||||
|
let newRangeStartMonth = 1
|
||||||
|
let newRangeStartYear = rangeStart.year
|
||||||
|
if (rangeStart.month + 1 <= 12) {
|
||||||
|
newRangeStartMonth =
|
||||||
|
rangeStart.month + 1 > 9
|
||||||
|
? `${rangeStart.month + 1}`
|
||||||
|
: `0${rangeStart.month + 1}`
|
||||||
|
} else {
|
||||||
|
newRangeStartMonth = '01'
|
||||||
|
newRangeStartYear += 1
|
||||||
|
}
|
||||||
|
return rangeStart.day === 1
|
||||||
|
? serializeDate(rangeStart, 'datetime')
|
||||||
|
: `${newRangeStartYear}-${newRangeStartMonth}-01`
|
||||||
|
} else {
|
||||||
|
this.weekScale = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The function determines the last day of the month
|
||||||
|
*/
|
||||||
|
getLastMonthDay(firstDay, rangeStart, rangeEnd) {
|
||||||
|
let lastDay = new Date(firstDay)
|
||||||
|
// 42 is the number of days displayed on the calendar on a monthly scale.
|
||||||
|
// In this case, we check that the scale of the year is displayed
|
||||||
|
if ((rangeEnd - rangeStart) / (1000 * 60 * 60 * 24) > 42) {
|
||||||
|
return serializeDate(rangeEnd, 'datetime')
|
||||||
|
} else {
|
||||||
|
lastDay.setMonth(lastDay.getMonth() + 1)
|
||||||
|
lastDay.setDate(0)
|
||||||
|
const newLastDayMonth =
|
||||||
|
lastDay.getMonth() + 1 > 9
|
||||||
|
? `${lastDay.getMonth() + 1}`
|
||||||
|
: `0${lastDay.getMonth() + 1}`
|
||||||
|
return `${lastDay.getFullYear()}-${newLastDayMonth}-${lastDay.getDate()}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The function gets a list of days off in a given period
|
||||||
|
*/
|
||||||
|
getUnusualDaysList(firstDay, lastDay) {
|
||||||
|
const newFirstDay = new Date(firstDay)
|
||||||
|
const newLastDay = new Date(lastDay)
|
||||||
|
const unusualDaysList = this.props.model.unusualDays
|
||||||
|
const unusualDaysFiltered = unusualDaysList.filter(day => {
|
||||||
|
const newDate = new Date(day)
|
||||||
|
return (newDate >= newFirstDay) & (newDate <= newLastDay)
|
||||||
|
})
|
||||||
|
return unusualDaysFiltered
|
||||||
|
}
|
||||||
|
})
|
|
@ -0,0 +1,38 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<templates>
|
||||||
|
<t t-inherit="hr_holidays.CalendarFilterPanel" t-inherit-mode="extension" owl="1">
|
||||||
|
<xpath expr="//div[@class='d-flex flex-column']" position="inside">
|
||||||
|
<span><span class="o_holidays_legend">13</span> Public Holiday</span>
|
||||||
|
</xpath>
|
||||||
|
|
||||||
|
<xpath expr="//div[@class='d-flex flex-column']" position="inside">
|
||||||
|
<span><span class="o_non_working_legend">13</span> Non-working day</span>
|
||||||
|
</xpath>
|
||||||
|
|
||||||
|
<xpath expr="//div[@class='d-flex flex-column mt-4']" position="replace">
|
||||||
|
</xpath>
|
||||||
|
|
||||||
|
<xpath expr="//div[@class='d-flex flex-column mt-4']" position="replace">
|
||||||
|
</xpath>
|
||||||
|
|
||||||
|
<xpath expr="//div[@class='o_calendar_filter mt-4']/div[last()]" position="after">
|
||||||
|
<div t-if="!weekScale" class="d-flex flex-column mt-4">
|
||||||
|
<h5>Number of days</h5>
|
||||||
|
<ul class="ps-2">
|
||||||
|
<li class="mt-2 list-unstyled">
|
||||||
|
<p>
|
||||||
|
<span><span class="text-odoo">continuous days: </span><t t-out="daysTotalAmount"/></span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span><span class="text-odoo">working days: </span><t t-out="workingDaysAmount"/></span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span><span class="text-odoo">non-working days and holidays: </span><t t-out="nonWorkingDaysAndHolidays"/></span>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</xpath>
|
||||||
|
</t>
|
||||||
|
</templates>
|
66
static/src/views/calendar/year/calendar_year_renderer.js
Normal file
66
static/src/views/calendar/year/calendar_year_renderer.js
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
/** @odoo-module **/
|
||||||
|
|
||||||
|
import { patch } from '@web/core/utils/patch'
|
||||||
|
|
||||||
|
import { TimeOffCalendarYearRenderer } from '@hr_holidays/views/calendar/year/calendar_year_renderer'
|
||||||
|
import { onWillStart } from '@odoo/owl'
|
||||||
|
import { serializeDate } from '@web/core/l10n/dates'
|
||||||
|
|
||||||
|
patch(
|
||||||
|
TimeOffCalendarYearRenderer.prototype,
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* We add onWillStart to load data from the holidays.calendar model to get holidays, non-working days and rescheduled working days.
|
||||||
|
*/
|
||||||
|
setup() {
|
||||||
|
super.setup(...arguments)
|
||||||
|
onWillStart(async () => {
|
||||||
|
const rangeStart = serializeDate(
|
||||||
|
this.props.model.rangeStart,
|
||||||
|
'datetime'
|
||||||
|
)
|
||||||
|
const rangeEnd = serializeDate(this.props.model.rangeEnd, 'datetime')
|
||||||
|
const [holidaysList, nonWorkingDaysList, rescheduledWorkDaysList] =
|
||||||
|
await this.orm.call('holidays.calendar', 'get_all_holidays', [
|
||||||
|
rangeStart,
|
||||||
|
rangeEnd
|
||||||
|
])
|
||||||
|
this.holidays = holidaysList
|
||||||
|
this.nonWorkingDays = nonWorkingDaysList
|
||||||
|
this.rescheduledWorkDay = rescheduledWorkDaysList
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redefining onDayRender, adding additional functionality to it to display holidays, non-working days and postponed working days.
|
||||||
|
*/
|
||||||
|
onDayRender(info) {
|
||||||
|
const date = luxon.DateTime.fromJSDate(info.date).toISODate()
|
||||||
|
if (this.props.model.unusualDays.includes(date)) {
|
||||||
|
info.el.classList.add('o_calendar_disabled')
|
||||||
|
}
|
||||||
|
|
||||||
|
// New code
|
||||||
|
if (this.nonWorkingDays.includes(date)) {
|
||||||
|
info.el.classList.add('o_calendar_disabled')
|
||||||
|
}
|
||||||
|
const element = document.querySelector(
|
||||||
|
`.fc-content-skeleton [data-date=${CSS.escape(date)}]`
|
||||||
|
)
|
||||||
|
if (element && element.classList.contains('fc-today')) {
|
||||||
|
element.classList.remove('fc-today')
|
||||||
|
element.classList.add('fc-today-day-number-year')
|
||||||
|
}
|
||||||
|
if (this.holidays.includes(date)) {
|
||||||
|
if (element) {
|
||||||
|
const day_number = element.querySelector('.fc-day-number')
|
||||||
|
day_number.classList.add('hr_holiday_calendar')
|
||||||
|
info.el.classList.add("o_calendar_disabled");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.rescheduledWorkDay.includes(date)) {
|
||||||
|
info.el.classList.remove('o_calendar_disabled')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
12
views/hr_holidays_menus_view.xml
Normal file
12
views/hr_holidays_menus_view.xml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version='1.0' encoding='UTF-8' ?>
|
||||||
|
<odoo>
|
||||||
|
<menuitem id="hr_holidays.hr_holidays_public_time_off_menu_configuration"
|
||||||
|
active="False"/>
|
||||||
|
|
||||||
|
<menuitem id="hr_holidays.hr_holidays_mandatory_day_menu_configuration"
|
||||||
|
active="False"/>
|
||||||
|
|
||||||
|
<record id="hr_holidays.menu_open_allocation" model="ir.ui.menu">
|
||||||
|
<field name="groups_id" eval="[(4,ref('hr_holidays.group_hr_holidays_user'))]" />
|
||||||
|
</record>
|
||||||
|
</odoo>
|
170
views/hr_holidays_views.xml
Normal file
170
views/hr_holidays_views.xml
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<record id="open_view_holiday_settings_tree" model="ir.ui.view">
|
||||||
|
<field name="name">hr.holidays.settings_tree</field>
|
||||||
|
<field name="model">holidays.calendar</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<tree string="Working Time">
|
||||||
|
<field name="name" string="Weekends"/>
|
||||||
|
<field name="year"/>
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="open_view_holiday_settings_form" model="ir.ui.view">
|
||||||
|
<field name="name">hr.holidays.settings_form</field>
|
||||||
|
<field name="model">holidays.calendar</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Working Time">
|
||||||
|
<sheet string="Working Time">
|
||||||
|
<h1>
|
||||||
|
<field name="name"/>
|
||||||
|
</h1>
|
||||||
|
<group>
|
||||||
|
<field name="year"/>
|
||||||
|
</group>
|
||||||
|
<notebook>
|
||||||
|
<page string="Global Leaves">
|
||||||
|
<field name="global_leave_ids">
|
||||||
|
<tree editable="top">
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="date_from"/>
|
||||||
|
<field name="date_to"/>
|
||||||
|
<field name="type_transfer_day"/>
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</page>
|
||||||
|
</notebook>
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Your Action Window Definition -->
|
||||||
|
<record id="open_view_holiday_settings" model="ir.actions.act_window">
|
||||||
|
<field name="name">Holidays and non-working days</field>
|
||||||
|
<field name="res_model">holidays.calendar</field>
|
||||||
|
<field name="type">ir.actions.act_window</field>
|
||||||
|
<field name="view_mode">tree,form</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Adds "Calendar days" and "Working days" units of measures in Leave form. -->
|
||||||
|
<record id="hr_leave_view_form_inherit" model="ir.ui.view">
|
||||||
|
<field name="name">hr_holidays_ru.hr_leave_view_form_inherit</field>
|
||||||
|
<field name="model">hr.leave</field>
|
||||||
|
<field name="inherit_id" ref="hr_holidays.hr_leave_view_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//sheet/div/div/group" position="before">
|
||||||
|
<div>
|
||||||
|
<div invisible="leave_type_request_unit != 'hour'">
|
||||||
|
<field name="number_of_hours_display" nolabel="1" class="oe_inline"/>
|
||||||
|
<span class="ml8">Hours</span>
|
||||||
|
</div>
|
||||||
|
<div invisible="leave_type_request_unit != 'c_day'">
|
||||||
|
<field name="number_of_days_display" nolabel="1" class="oe_inline"/>
|
||||||
|
<span class="ml8">Calendar days</span>
|
||||||
|
</div>
|
||||||
|
<div invisible="leave_type_request_unit != 'day'">
|
||||||
|
<field name="number_of_days_display" nolabel="1" class="oe_inline"/>
|
||||||
|
<span class="ml8">Working days</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//label[@for='request_date_from']" position="before">
|
||||||
|
<field name="working_hours_display" invisible="leave_type_request_unit != 'c_day'"/>
|
||||||
|
</xpath>
|
||||||
|
|
||||||
|
<xpath expr="//field[@name='request_unit_half']" position="replace">
|
||||||
|
<field
|
||||||
|
name="request_unit_half"
|
||||||
|
readonly="state not in ('draft', 'confirm')"
|
||||||
|
invisible="leave_type_request_unit == 'c_day'"
|
||||||
|
/>
|
||||||
|
</xpath>
|
||||||
|
|
||||||
|
<xpath expr="//label[@for='request_unit_half']" position="replace">
|
||||||
|
<label for="request_unit_half"
|
||||||
|
invisible="leave_type_request_unit == 'c_day'"
|
||||||
|
/>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Adds "Calendar days" and "Working days" units of measures in Leaves kanban view -->
|
||||||
|
<record id="hr_leave_view_kanban_inherit" model="ir.ui.view">
|
||||||
|
<field name="name">hr_holidays_ru.hr_leave_view_kanban_inherit</field>
|
||||||
|
<field name="model">hr.leave</field>
|
||||||
|
<field name="inherit_id" ref="hr_holidays.hr_leave_view_kanban"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//span[hasclass('rounded-pill')]" position="replace">
|
||||||
|
<field name="leave_type_request_unit" invisible="1"/>
|
||||||
|
<span class="badge badge-pill float-right mt4 mr16" invisible="leave_type_request_unit != 'c_day'">
|
||||||
|
<t t-esc="record.number_of_days.value"/> Calendar days
|
||||||
|
</span>
|
||||||
|
<span class="badge badge-pill float-right mt4 mr16" invisible="leave_type_request_unit == 'c_day'">
|
||||||
|
<t t-esc="record.number_of_days.value"/> Working days
|
||||||
|
</span>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="hr_leave_allocation_view_form_inherit" model="ir.ui.view">
|
||||||
|
<field name="name">hr_holidays_ru.hr_leave_allocation_view_form_inherit</field>
|
||||||
|
<field name="model">hr.leave.allocation</field>
|
||||||
|
<field name="inherit_id" ref="hr_holidays.hr_leave_allocation_view_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//div[@name='duration_display']" position="replace">
|
||||||
|
<div>
|
||||||
|
<field name="number_of_days_display" class="oe_inline" nolabel="1"
|
||||||
|
readonly="state not in ('draft', 'confirm')"
|
||||||
|
invisible="type_request_unit == 'hour'"/>
|
||||||
|
<field name="number_of_hours_display" class="oe_inline" nolabel="1"
|
||||||
|
readonly="state not in ('draft', 'confirm')"
|
||||||
|
invisible="type_request_unit != 'hour'"/>
|
||||||
|
<span class="ml8" invisible="type_request_unit != 'c_day'">Calendar days</span>
|
||||||
|
<span class="ml8" invisible="type_request_unit != 'day'">Working days</span>
|
||||||
|
<span class="ml8" invisible="type_request_unit != 'hour'">Hours</span>
|
||||||
|
</div>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Adds "Exclude holidays" checkbox in Leave type form. -->
|
||||||
|
<!-- Adds notification for holidays settings -->
|
||||||
|
<record id="edit_holiday_status_form_inherit" model="ir.ui.view">
|
||||||
|
<field name="name">hr_holidays_ru.edit_holiday_status_form_inherit</field>
|
||||||
|
<field name="model">hr.leave.type</field>
|
||||||
|
<field name="inherit_id" ref="hr_holidays.edit_holiday_status_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//field[@name='request_unit']" position="after">
|
||||||
|
<field name="exclude_holidays" invisible="request_unit != 'c_day'"/>
|
||||||
|
</xpath>
|
||||||
|
|
||||||
|
<xpath expr="//group[@id='visual']" position="before">
|
||||||
|
<group name="notifications" id="notifications">
|
||||||
|
<group string="Notification about holidays">
|
||||||
|
<field name="notify_before_start_holidays"/>
|
||||||
|
<field name="responsible_employee_to_notify_id" invisible="notify_before_start_holidays == False"/>
|
||||||
|
<field name="days_before_holidays" invisible="notify_before_start_holidays == False"/>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
</xpath>
|
||||||
|
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<menuitem
|
||||||
|
action="open_view_holiday_settings"
|
||||||
|
id="hr_holidays_status_menu_configuration"
|
||||||
|
parent="hr_holidays.menu_hr_holidays_configuration"
|
||||||
|
name="Holidays and non-working days"
|
||||||
|
groups="hr_holidays.group_hr_holidays_manager"
|
||||||
|
sequence="4"/>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</odoo>
|
7
views/hr_leave_allocation_views.xml
Normal file
7
views/hr_leave_allocation_views.xml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version='1.0' encoding='UTF-8' ?>
|
||||||
|
<odoo>
|
||||||
|
<record id="hr_holidays.hr_leave_allocation_action_approve_department"
|
||||||
|
model="ir.actions.act_window">
|
||||||
|
<field name="context">{'search_default_approve': 1}</field>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
7
views/hr_leave_report_calendar_view.xml
Normal file
7
views/hr_leave_report_calendar_view.xml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version='1.0' encoding='UTF-8' ?>
|
||||||
|
<odoo>
|
||||||
|
<record id="hr_holidays.action_hr_holidays_dashboard"
|
||||||
|
model="ir.actions.act_window">
|
||||||
|
<field name="context">{'hide_employee_name': 1, 'search_default_department': 1}</field>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
50
views/hr_leave_views.xml
Normal file
50
views/hr_leave_views.xml
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
<?xml version='1.0' encoding='UTF-8' ?>
|
||||||
|
<odoo>
|
||||||
|
<record id="hr_leave_view_form_inh" model="ir.ui.view">
|
||||||
|
<field name="name">hr.leave.view.form.inh</field>
|
||||||
|
<field name="model">hr.leave</field>
|
||||||
|
<field name="inherit_id" ref="hr_holidays.hr_leave_view_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//group/label[@for='request_unit_half']" position="replace">
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//label[@for='number_of_days_display']" position="replace">
|
||||||
|
<field name="number_of_days_display" invisible="1" />
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//div[@name='duration_display']" position="replace">
|
||||||
|
<div name="duration_display">
|
||||||
|
<div class="o_row">
|
||||||
|
<div groups="!hr_holidays.group_hr_holidays_manager" invisible="1">
|
||||||
|
<field name="number_of_days_display" nolabel="1" readonly="1" class="oe_inline"/>
|
||||||
|
<span>Days</span>
|
||||||
|
</div>
|
||||||
|
<div groups="hr_holidays.group_hr_holidays_manager" class="o_row" invisible="1">
|
||||||
|
<field name="number_of_days" nolabel="1" class="oe_inline"/>
|
||||||
|
<span>Days</span>
|
||||||
|
</div>
|
||||||
|
<div invisible="1" class="o_row">
|
||||||
|
<field name="number_of_hours_text" nolabel="1" class="oe_inline"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="hr_holidays.hr_leave_action_action_approve_department"
|
||||||
|
model="ir.actions.act_window">
|
||||||
|
<field name="context">{
|
||||||
|
'hide_employee_name': 1,
|
||||||
|
'holiday_status_name_get': False}</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="hr_leave_view_tree_inh" model="ir.ui.view">
|
||||||
|
<field name="name">hr.leave.view.tree.inh</field>
|
||||||
|
<field name="model">hr.leave</field>
|
||||||
|
<field name="inherit_id" ref="hr_holidays.hr_leave_view_tree"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//field[@name='employee_id']" position="replace">
|
||||||
|
<field name="employee_id" widget="many2one_avatar"/>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
3
wizard/__init__.py
Normal file
3
wizard/__init__.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import hr_holidays_summary_department
|
180
wizard/hr_holidays_summary_department.py
Normal file
180
wizard/hr_holidays_summary_department.py
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
|
from dateutil.relativedelta import relativedelta
|
||||||
|
|
||||||
|
from odoo import api, fields, models, _
|
||||||
|
from odoo.exceptions import UserError
|
||||||
|
|
||||||
|
|
||||||
|
class HrHolidaySummaryReport(models.AbstractModel):
|
||||||
|
_inherit = "report.hr_holidays.report_holidayssummary"
|
||||||
|
|
||||||
|
def _get_header_info(self, start_date, end_date, holiday_type):
|
||||||
|
st_date = fields.Date.from_string(start_date)
|
||||||
|
end_date_ = (
|
||||||
|
fields.Date.from_string(end_date)
|
||||||
|
if end_date
|
||||||
|
else fields.Date.from_string(start_date + relativedelta(days=59))
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"start_date": fields.Date.to_string(st_date),
|
||||||
|
"end_date": fields.Date.to_string(end_date_),
|
||||||
|
"holiday_type": "Confirmed and Approved"
|
||||||
|
if holiday_type == "both"
|
||||||
|
else holiday_type,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _get_months(self, start_date, end_date):
|
||||||
|
# it works for geting month name between two dates.
|
||||||
|
res = []
|
||||||
|
start_date = fields.Date.from_string(start_date)
|
||||||
|
# end_date = start_date + relativedelta(days=59)
|
||||||
|
end_date_ = (
|
||||||
|
fields.Date.from_string(end_date)
|
||||||
|
if end_date
|
||||||
|
else fields.Date.from_string(start_date + relativedelta(days=59))
|
||||||
|
)
|
||||||
|
while start_date <= end_date_:
|
||||||
|
last_date = start_date + relativedelta(day=1, months=+1, days=-1)
|
||||||
|
if last_date > end_date_:
|
||||||
|
last_date = end_date_
|
||||||
|
month_days = (last_date - start_date).days + 1
|
||||||
|
res.append({"month_name": start_date.strftime("%B"), "days": month_days})
|
||||||
|
start_date += relativedelta(day=1, months=+1)
|
||||||
|
return res
|
||||||
|
|
||||||
|
def _get_day(self, start_date, end_date):
|
||||||
|
res = []
|
||||||
|
start_date = fields.Date.from_string(start_date)
|
||||||
|
end_date_ = fields.Date.from_string(end_date)
|
||||||
|
for x in range((end_date_ - start_date).days + 1):
|
||||||
|
color = "#ababab" if self._date_is_day_off(start_date) else ""
|
||||||
|
res.append(
|
||||||
|
{
|
||||||
|
"day_str": start_date.strftime("%a"),
|
||||||
|
"day": start_date.day,
|
||||||
|
"color": color,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
start_date = start_date + relativedelta(days=1)
|
||||||
|
return res
|
||||||
|
|
||||||
|
def _get_data_from_report(self, data):
|
||||||
|
res = []
|
||||||
|
Employee = self.env["hr.employee"]
|
||||||
|
if "depts" in data:
|
||||||
|
for department in self.env["hr.department"].browse(data["depts"]):
|
||||||
|
res.append(
|
||||||
|
{
|
||||||
|
"dept": department.name,
|
||||||
|
"data": [],
|
||||||
|
"color": self._get_day(data["date_from"], data["date_to"]),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
for emp in Employee.search([("department_id", "=", department.id)]):
|
||||||
|
res[len(res) - 1]["data"].append(
|
||||||
|
{
|
||||||
|
"emp": emp.name,
|
||||||
|
"display": self._get_leaves_summary(
|
||||||
|
data["date_from"],
|
||||||
|
data["date_to"],
|
||||||
|
emp.id,
|
||||||
|
data["holiday_type"],
|
||||||
|
),
|
||||||
|
"sum": self.sum,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
elif "emp" in data:
|
||||||
|
res.append({"data": []})
|
||||||
|
for emp in Employee.browse(data["emp"]):
|
||||||
|
res[0]["data"].append(
|
||||||
|
{
|
||||||
|
"emp": emp.name,
|
||||||
|
"display": self._get_leaves_summary(
|
||||||
|
data["date_from"],
|
||||||
|
data["date_to"],
|
||||||
|
emp.id,
|
||||||
|
data["holiday_type"],
|
||||||
|
),
|
||||||
|
"sum": self.sum,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return res
|
||||||
|
|
||||||
|
def _get_leaves_summary(self, start_date, end_date, empid, holiday_type):
|
||||||
|
res = []
|
||||||
|
count = 0
|
||||||
|
start_date = fields.Date.from_string(start_date)
|
||||||
|
end_date_ = (
|
||||||
|
fields.Date.from_string(end_date)
|
||||||
|
if end_date
|
||||||
|
else fields.Date.from_string(start_date + relativedelta(days=59))
|
||||||
|
)
|
||||||
|
for index in range((end_date_ - start_date).days + 1):
|
||||||
|
current = start_date + timedelta(index)
|
||||||
|
res.append({"day": current.day, "color": ""})
|
||||||
|
if self._date_is_day_off(current):
|
||||||
|
res[index]["color"] = "#ababab"
|
||||||
|
# count and get leave summary details.
|
||||||
|
holiday_type = (
|
||||||
|
["confirm", "validate"]
|
||||||
|
if holiday_type == "both"
|
||||||
|
else ["confirm"]
|
||||||
|
if holiday_type == "Confirmed"
|
||||||
|
else ["validate"]
|
||||||
|
)
|
||||||
|
holidays = self.env["hr.leave"].search(
|
||||||
|
[
|
||||||
|
("employee_id", "=", empid),
|
||||||
|
("state", "in", holiday_type),
|
||||||
|
("date_from", "<=", str(end_date_)),
|
||||||
|
("date_to", ">=", str(start_date)),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
for holiday in holidays:
|
||||||
|
# Convert date to user timezone, otherwise the report will not be consistent with the
|
||||||
|
# value displayed in the interface.
|
||||||
|
date_from = fields.Datetime.from_string(holiday.date_from)
|
||||||
|
date_from = fields.Datetime.context_timestamp(holiday, date_from).date()
|
||||||
|
date_to = fields.Datetime.from_string(holiday.date_to)
|
||||||
|
date_to = fields.Datetime.context_timestamp(holiday, date_to).date()
|
||||||
|
for index in range(0, ((date_to - date_from).days + 1)):
|
||||||
|
if date_from >= start_date and date_from <= end_date_:
|
||||||
|
res[(date_from - start_date).days][
|
||||||
|
"color"
|
||||||
|
] = holiday.holiday_status_id.color_name
|
||||||
|
date_from += timedelta(1)
|
||||||
|
count += holiday.number_of_days
|
||||||
|
self.sum = count
|
||||||
|
return res
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _get_report_values(self, docids, data=None):
|
||||||
|
if not data.get("form"):
|
||||||
|
raise UserError(
|
||||||
|
_("Form content is missing, this report cannot be printed.")
|
||||||
|
)
|
||||||
|
|
||||||
|
holidays_report = self.env["ir.actions.report"]._get_report_from_name(
|
||||||
|
"hr_holidays.report_holidayssummary"
|
||||||
|
)
|
||||||
|
holidays = self.env["hr.leave"].browse(self.ids)
|
||||||
|
return {
|
||||||
|
"doc_ids": self.ids,
|
||||||
|
"doc_model": holidays_report.model,
|
||||||
|
"docs": holidays,
|
||||||
|
"get_header_info": self._get_header_info(
|
||||||
|
data["form"]["date_from"],
|
||||||
|
data["form"]["date_to"],
|
||||||
|
data["form"]["holiday_type"],
|
||||||
|
),
|
||||||
|
"get_day": self._get_day(
|
||||||
|
data["form"]["date_from"], data["form"]["date_to"]
|
||||||
|
),
|
||||||
|
"get_months": self._get_months(
|
||||||
|
data["form"]["date_from"], data["form"]["date_to"]
|
||||||
|
),
|
||||||
|
"get_data_from_report": self._get_data_from_report(data["form"]),
|
||||||
|
"get_holidays_status": self._get_holidays_status(),
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user