Compare commits

...

3 Commits

25 changed files with 2382 additions and 0 deletions

6
__init__.py Normal file
View 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
View 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
View 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>

View 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>

View 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
View 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
View 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
View 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
View 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

View 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
View 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
View 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))

View 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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_holidays_calendar_manager access.holidays.calendar.manager model_holidays_calendar hr_holidays.group_hr_holidays_manager 1 1 1 1
3 access_holidays_calendar_leaves_manager access.holidays.calendar.leaves.manager model_holidays_calendar_leaves hr_holidays.group_hr_holidays_manager 1 1 1 1
4 access_holidays_calendar_user access.holidays.calendar.user model_holidays_calendar base.group_user 1 0 0 0
5 access_holidays_calendar_leaves_user access.holidays.calendar.leaves.user model_holidays_calendar_leaves base.group_user 1 0 0 0

View 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;
}

View 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')
}
}
}
)

View 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
}
})

View File

@ -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>

View 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')
}
}
}
)

View 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
View 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>

View 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>

View 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
View 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
View File

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import hr_holidays_summary_department

View 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(),
}