Merge branch 'rydlab-17.0' into 17.0
This commit is contained in:
commit
f6d0020515
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