[IMP] hr_holidays: Improve Accruals, dashboards and general UX (Back2basics)

Purpose
=======

Time Off : New Dashboard, Accruals feature, Public holidays, New time off type configuration view.

- Review of Dashboard
- Accrual feature : Currently, when you create an allocation, it's possible to create an
  Accrual, but there is no appraisal Plan. An Appraisal plan is use to define steps of accruals
  for each employee.
  In Belgium, we use accrual Allocation for European Leaves, or compensatory hours, it's a
  simple use case.  But in USA, it's possible to change the calculation mode each year.
  Also, in USA, it's possible to deal your accrual plan when you arrive on the company. It's a
  HR Officer task to create the right Accrual allocation for each new employee.
- Public Holidays :
  Improvement of global time off feature located on Working hours calendar. Now, Time off
  application have to manage Public Holidays.
- Review of Time Off type configuration

COM PR: https://github.com/odoo/odoo/pull/72511
ENT PR: https://github.com/odoo/enterprise/pull/19157
UPG PR: https://github.com/odoo/upgrade/pull/2791
TaskID:2475413

closes odoo/odoo#72511

Related: odoo/enterprise#19157
Related: odoo/upgrade#2791
Signed-off-by: Yannick Tivisse (yti) <yti@odoo.com>
Co-authored-by: William Braeckman <wbr@odoo.com>
Co-authored-by: Xavier BOL (xbo) <xbo@odoo.com>
Co-authored-by: Laurent Stukkens (LTU) <ltu@odoo.com>
Co-authored-by: Yannick Tivisse <yti@odoo.com>
This commit is contained in:
jdoutreloux
2021-09-02 12:57:03 +00:00
committed by William Braeckman
co-authored by William Braeckman Xavier BOL Laurent Stukkens Yannick Tivisse
parent 0e04aeab94
commit 254d6a71e8
111 changed files with 3401 additions and 1312 deletions
+4
View File
@@ -38,6 +38,7 @@ A synchronization with an internal agenda (Meetings of the CRM module) is also p
'views/hr_leave_views.xml',
'views/hr_leave_type_views.xml',
'views/hr_leave_allocation_views.xml',
'views/hr_leave_accrual_views.xml',
'views/mail_activity_views.xml',
'wizard/hr_holidays_summary_employees_views.xml',
@@ -47,6 +48,7 @@ A synchronization with an internal agenda (Meetings of the CRM module) is also p
'report/hr_holidays_reports.xml',
'report/hr_leave_reports.xml',
'report/hr_leave_report_calendar.xml',
'report/hr_leave_employee_type_report.xml',
'views/hr_views.xml',
'views/hr_holidays_views.xml',
@@ -64,10 +66,12 @@ A synchronization with an internal agenda (Meetings of the CRM module) is also p
],
'web.assets_backend': [
'hr_holidays/static/src/js/time_off_calendar.js',
'hr_holidays/static/src/js/radio_image.js',
'hr_holidays/static/src/js/leave_stats_widget.js',
'hr_holidays/static/src/models/*/*.js',
'hr_holidays/static/src/scss/time_off.scss',
'hr_holidays/static/src/components/*/*.scss',
'hr_holidays/static/src/scss/accrual_plan_level.scss',
],
'web.tests_assets': [
'hr_holidays/static/tests/helpers/**/*',
+267 -14
View File
@@ -1,55 +1,308 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="icon_1" model="ir.attachment">
<field name="name">Annual_Time_Off.svg</field>
<field name="res_model">hr.leave.type</field>
<field name="res_field">icon_id</field>
<field name="public" eval="True"/>
<field name="type">url</field>
<field name="url">/hr_holidays/static/src/img/icons/Annual_Time_Off.svg</field>
</record>
<record id="icon_2" model="ir.attachment">
<field name="name">Annual_Time_Off_2.svg</field>
<field name="res_model">hr.leave.type</field>
<field name="res_field">icon_id</field>
<field name="public" eval="True"/>
<field name="type">url</field>
<field name="url">/hr_holidays/static/src/img/icons/Annual_Time_Off_2.svg</field>
</record>
<record id="icon_3" model="ir.attachment">
<field name="name">Annual_Time_Off_3.svg</field>
<field name="res_model">hr.leave.type</field>
<field name="res_field">icon_id</field>
<field name="public" eval="True"/>
<field name="type">url</field>
<field name="url">/hr_holidays/static/src/img/icons/Annual_Time_Off_3.svg</field>
</record>
<record id="icon_4" model="ir.attachment">
<field name="name">Compensatory_Time_Off.svg</field>
<field name="res_model">hr.leave.type</field>
<field name="res_field">icon_id</field>
<field name="public" eval="True"/>
<field name="type">url</field>
<field name="url">/hr_holidays/static/src/img/icons/Compensatory_Time_Off.svg</field>
</record>
<record id="icon_5" model="ir.attachment">
<field name="name">Compensatory_Time_Off_2.svg</field>
<field name="res_model">hr.leave.type</field>
<field name="res_field">icon_id</field>
<field name="public" eval="True"/>
<field name="type">url</field>
<field name="url">/hr_holidays/static/src/img/icons/Compensatory_Time_Off_2.svg</field>
</record>
<record id="icon_6" model="ir.attachment">
<field name="name">Compensatory_Time_Off_3.svg</field>
<field name="res_model">hr.leave.type</field>
<field name="res_field">icon_id</field>
<field name="public" eval="True"/>
<field name="type">url</field>
<field name="url">/hr_holidays/static/src/img/icons/Compensatory_Time_Off_3.svg</field>
</record>
<record id="icon_7" model="ir.attachment">
<field name="name">Credit_Time.svg</field>
<field name="res_model">hr.leave.type</field>
<field name="res_field">icon_id</field>
<field name="public" eval="True"/>
<field name="type">url</field>
<field name="url">/hr_holidays/static/src/img/icons/Credit_Time.svg</field>
</record>
<record id="icon_8" model="ir.attachment">
<field name="name">Credit_Time_2.svg</field>
<field name="res_model">hr.leave.type</field>
<field name="res_field">icon_id</field>
<field name="public" eval="True"/>
<field name="type">url</field>
<field name="url">/hr_holidays/static/src/img/icons/Credit_Time_2.svg</field>
</record>
<record id="icon_9" model="ir.attachment">
<field name="name">Extra_Time_Off.svg</field>
<field name="res_model">hr.leave.type</field>
<field name="res_field">icon_id</field>
<field name="public" eval="True"/>
<field name="type">url</field>
<field name="url">/hr_holidays/static/src/img/icons/Extra_Time_Off.svg</field>
</record>
<record id="icon_10" model="ir.attachment">
<field name="name">Extra_Time_Off_2.svg</field>
<field name="res_model">hr.leave.type</field>
<field name="res_field">icon_id</field>
<field name="public" eval="True"/>
<field name="type">url</field>
<field name="url">/hr_holidays/static/src/img/icons/Extra_Time_Off_2.svg</field>
</record>
<record id="icon_11" model="ir.attachment">
<field name="name">Maternity_Time_Off.svg</field>
<field name="res_model">hr.leave.type</field>
<field name="res_field">icon_id</field>
<field name="public" eval="True"/>
<field name="type">url</field>
<field name="url">/hr_holidays/static/src/img/icons/Maternity_Time_Off.svg</field>
</record>
<record id="icon_12" model="ir.attachment">
<field name="name">Maternity_Time_Off_2.svg</field>
<field name="res_model">hr.leave.type</field>
<field name="res_field">icon_id</field>
<field name="public" eval="True"/>
<field name="type">url</field>
<field name="url">/hr_holidays/static/src/img/icons/Maternity_Time_Off_2.svg</field>
</record>
<record id="icon_13" model="ir.attachment">
<field name="name">Maternity_Time_Off_3.svg</field>
<field name="res_model">hr.leave.type</field>
<field name="res_field">icon_id</field>
<field name="public" eval="True"/>
<field name="type">url</field>
<field name="url">/hr_holidays/static/src/img/icons/Maternity_Time_Off_3.svg</field>
</record>
<record id="icon_14" model="ir.attachment">
<field name="name">Paid_Time_Off.svg</field>
<field name="res_model">hr.leave.type</field>
<field name="res_field">icon_id</field>
<field name="public" eval="True"/>
<field name="type">url</field>
<field name="url">/hr_holidays/static/src/img/icons/Paid_Time_Off.svg</field>
</record>
<record id="icon_15" model="ir.attachment">
<field name="name">Paid_Time_Off_2.svg</field>
<field name="res_model">hr.leave.type</field>
<field name="res_field">icon_id</field>
<field name="public" eval="True"/>
<field name="type">url</field>
<field name="url">/hr_holidays/static/src/img/icons/Paid_Time_Off_2.svg</field>
</record>
<record id="icon_16" model="ir.attachment">
<field name="name">Paid_Time_Off_3.svg</field>
<field name="res_model">hr.leave.type</field>
<field name="res_field">icon_id</field>
<field name="public" eval="True"/>
<field name="type">url</field>
<field name="url">/hr_holidays/static/src/img/icons/Paid_Time_Off_3.svg</field>
</record>
<record id="icon_17" model="ir.attachment">
<field name="name">Parental_Time_Off.svg</field>
<field name="res_model">hr.leave.type</field>
<field name="res_field">icon_id</field>
<field name="public" eval="True"/>
<field name="type">url</field>
<field name="url">/hr_holidays/static/src/img/icons/Parental_Time_Off.svg</field>
</record>
<record id="icon_18" model="ir.attachment">
<field name="name">Parental_Time_Off_2.svg</field>
<field name="res_model">hr.leave.type</field>
<field name="res_field">icon_id</field>
<field name="public" eval="True"/>
<field name="type">url</field>
<field name="url">/hr_holidays/static/src/img/icons/Parental_Time_Off_2.svg</field>
</record>
<record id="icon_19" model="ir.attachment">
<field name="name">Recovery_Bank_Holiday.svg</field>
<field name="res_model">hr.leave.type</field>
<field name="res_field">icon_id</field>
<field name="public" eval="True"/>
<field name="type">url</field>
<field name="url">/hr_holidays/static/src/img/icons/Recovery_Bank_Holiday.svg</field>
</record>
<record id="icon_20" model="ir.attachment">
<field name="name">Recovery_Bank_Holiday_2.svg</field>
<field name="res_model">hr.leave.type</field>
<field name="res_field">icon_id</field>
<field name="public" eval="True"/>
<field name="type">url</field>
<field name="url">/hr_holidays/static/src/img/icons/Recovery_Bank_Holiday_2.svg</field>
</record>
<record id="icon_21" model="ir.attachment">
<field name="name">Sick_Time_Off.svg</field>
<field name="res_model">hr.leave.type</field>
<field name="res_field">icon_id</field>
<field name="public" eval="True"/>
<field name="type">url</field>
<field name="url">/hr_holidays/static/src/img/icons/Sick_Time_Off.svg</field>
</record>
<record id="icon_22" model="ir.attachment">
<field name="name">Sick_Time_Off_2.svg</field>
<field name="res_model">hr.leave.type</field>
<field name="res_field">icon_id</field>
<field name="public" eval="True"/>
<field name="type">url</field>
<field name="url">/hr_holidays/static/src/img/icons/Sick_Time_Off_2.svg</field>
</record>
<record id="icon_23" model="ir.attachment">
<field name="name">Small_Unemployement.svg</field>
<field name="res_model">hr.leave.type</field>
<field name="res_field">icon_id</field>
<field name="public" eval="True"/>
<field name="type">url</field>
<field name="url">/hr_holidays/static/src/img/icons/Small_Unemployement.svg</field>
</record>
<record id="icon_24" model="ir.attachment">
<field name="name">Small_Unemployement_2.svg</field>
<field name="res_model">hr.leave.type</field>
<field name="res_field">icon_id</field>
<field name="public" eval="True"/>
<field name="type">url</field>
<field name="url">/hr_holidays/static/src/img/icons/Small_Unemployement_2.svg</field>
</record>
<record id="icon_25" model="ir.attachment">
<field name="name">Small_Unemployement_3.svg</field>
<field name="res_model">hr.leave.type</field>
<field name="res_field">icon_id</field>
<field name="public" eval="True"/>
<field name="type">url</field>
<field name="url">/hr_holidays/static/src/img/icons/Small_Unemployement_3.svg</field>
</record>
<record id="icon_26" model="ir.attachment">
<field name="name">Training_Time_Off.svg</field>
<field name="res_model">hr.leave.type</field>
<field name="res_field">icon_id</field>
<field name="public" eval="True"/>
<field name="type">url</field>
<field name="url">/hr_holidays/static/src/img/icons/Training_Time_Off.svg</field>
</record>
<record id="icon_27" model="ir.attachment">
<field name="name">Training_Time_Off_2.svg</field>
<field name="res_model">hr.leave.type</field>
<field name="res_field">icon_id</field>
<field name="public" eval="True"/>
<field name="type">url</field>
<field name="url">/hr_holidays/static/src/img/icons/Training_Time_Off_2.svg</field>
</record>
<record id="icon_28" model="ir.attachment">
<field name="name">Unpaid_Time_Off.svg</field>
<field name="res_model">hr.leave.type</field>
<field name="res_field">icon_id</field>
<field name="public" eval="True"/>
<field name="type">url</field>
<field name="url">/hr_holidays/static/src/img/icons/Unpaid_Time_Off.svg</field>
</record>
<record id="icon_29" model="ir.attachment">
<field name="name">Unpaid_Time_Off_2.svg</field>
<field name="res_model">hr.leave.type</field>
<field name="res_field">icon_id</field>
<field name="public" eval="True"/>
<field name="type">url</field>
<field name="url">/hr_holidays/static/src/img/icons/Unpaid_Time_Off_2.svg</field>
</record>
<record id="icon_30" model="ir.attachment">
<field name="name">Work_Accident_Time_Off.svg</field>
<field name="res_model">hr.leave.type</field>
<field name="res_field">icon_id</field>
<field name="public" eval="True"/>
<field name="type">url</field>
<field name="url">/hr_holidays/static/src/img/icons/Work_Accident_Time_Off.svg</field>
</record>
<record id="icon_31" model="ir.attachment">
<field name="name">Work_Accident_Time_Off_2.svg</field>
<field name="res_model">hr.leave.type</field>
<field name="res_field">icon_id</field>
<field name="public" eval="True"/>
<field name="type">url</field>
<field name="url">/hr_holidays/static/src/img/icons/Work_Accident_Time_Off_2.svg</field>
</record>
</data>
<data noupdate="1">
<!-- Casual leave -->
<record id="holiday_status_cl" model="hr.leave.type">
<field name="name">Paid Time Off</field>
<field name="allocation_type">fixed</field>
<field name="requires_allocation">yes</field>
<field name="employee_requests">no</field>
<field name="leave_validation_type">both</field>
<field name="allocation_validation_type">both</field>
<field name="color_name">black</field>
<field name="allocation_validation_type">officer</field>
<field name="leave_notif_subtype_id" ref="mt_leave"/>
<field name="allocation_notif_subtype_id" ref="mt_leave_allocation"/>
<field name="validity_start" eval="time.strftime('%Y-%m-01')"/>
<field name="responsible_id" ref="base.user_admin"/>
<field name="icon_id" ref="hr_holidays.icon_14"/>
<field name="color">2</field>
</record>
<!-- Sick leave -->
<record id="holiday_status_sl" model="hr.leave.type">
<field name="name">Sick Time Off</field>
<field name="allocation_type">no</field>
<field name="requires_allocation">no</field>
<field name="color_name">red</field>
<field name="validity_start" eval="time.strftime('%Y-01-01')"/>
<field name="leave_notif_subtype_id" ref="mt_leave_sick"/>
<field name="responsible_id" ref="base.user_admin"/>
<field name="support_document">True</field>
<field name="icon_id" ref="hr_holidays.icon_21"/>
<field name="color">3</field>
</record>
<!-- Compensatory Days -->
<record id="holiday_status_comp" model="hr.leave.type">
<field name="name">Compensatory Days</field>
<field name="allocation_type">fixed_allocation</field>
<field name="requires_allocation">yes</field>
<field name="employee_requests">yes</field>
<field name="leave_validation_type">manager</field>
<field name="allocation_validation_type">manager</field>
<field name="allocation_validation_type">officer</field>
<field name="request_unit">hour</field>
<field name="color_name">lavender</field>
<field name="validity_start" eval="False"/>
<field name="leave_notif_subtype_id" ref="mt_leave"/>
<field name="responsible_id" ref="base.user_admin"/>
<field name="icon_id" ref="hr_holidays.icon_4"/>
<field name="color">4</field>
</record>
<!--Unpaid Leave -->
<record id="holiday_status_unpaid" model="hr.leave.type">
<field name="name">Unpaid</field>
<field name="allocation_type">no</field>
<field name="requires_allocation">no</field>
<field name="leave_validation_type">both</field>
<field name="allocation_validation_type">both</field>
<field name="color_name">brown</field>
<field name="allocation_validation_type">officer</field>
<field name="request_unit">hour</field>
<field name="unpaid" eval="True"/>
<field name="validity_start" eval="time.strftime('%Y-01-01')"/>
<field name="leave_notif_subtype_id" ref="mt_leave_unpaid"/>
<field name="responsible_id" ref="base.user_admin"/>
<field name="icon_id" ref="hr_holidays.icon_28"/>
<field name="color">5</field>
</record>
</data>
</odoo>
+69 -29
View File
@@ -9,11 +9,11 @@
<!--Leave Type-->
<record id="hr_holiday_status_dv" model="hr.leave.type">
<field name="name">Parental Leaves</field>
<field name="allocation_type">fixed</field>
<field name="requires_allocation">yes</field>
<field name="employee_requests">no</field>
<field name="color_name">brown</field>
<field name="leave_validation_type">both</field>
<field name="allocation_validation_type">both</field>
<field name="validity_start" eval="False"/>
<field name="allocation_validation_type">officer</field>
<field name="responsible_id" ref="base.user_admin"/>
</record>
@@ -25,6 +25,8 @@
<field name="number_of_days">20</field>
<field name="employee_id" ref="hr.employee_admin"/>
<field name="state">validate</field>
<field name="date_from" eval="time.strftime('%Y-1-1')"/>
<field name="date_to" eval="time.strftime('%Y-12-31')"/>
</record>
<record id="hr_holidays_int_tour" model="hr.leave.allocation">
@@ -33,8 +35,10 @@
<field name="number_of_days">7</field>
<field name="employee_id" ref="hr.employee_admin"/>
<field name="state">confirm</field>
<field name="date_from" eval="time.strftime('%Y-1-1')"/>
<field name="date_to" eval="time.strftime('%Y-12-31')"/>
</record>
<function model="hr.leave.allocation" name="action_approve">
<function model="hr.leave.allocation" name="action_validate">
<value eval="ref('hr_holidays.hr_holidays_int_tour')"/>
</function>
@@ -42,7 +46,10 @@
<field name="name">Summer Vacation</field>
<field name="holiday_status_id" ref="holiday_status_unpaid"/>
<field name="number_of_days">7</field>
<field name="state">confirm</field>
<field name="employee_id" ref="hr.employee_admin"/>
<field name="date_from" eval="time.strftime('%Y-1-1')"/>
<field name="date_to" eval="time.strftime('%Y-12-31')"/>
</record>
<record id='hr_holidays_cl_allocation' model="hr.leave.allocation">
@@ -51,30 +58,32 @@
<field name="number_of_days">12</field>
<field name="employee_id" ref="hr.employee_admin"/>
<field name="state">validate</field>
<field name="date_from" eval="time.strftime('%Y-1-1')"/>
<field name="date_to" eval="time.strftime('%Y-12-31')"/>
</record>
<!-- leave request -->
<record id="hr_holidays_cl" model="hr.leave">
<field name="name">Trip with Family</field>
<field name="holiday_status_id" ref="holiday_status_comp"/>
<field eval="(datetime.now() + relativedelta(day=1, weekday=0)).strftime('%Y-%m-%d 08:00:00')" name="date_from"/>
<field eval="(datetime.now() + relativedelta(day=1, weekday=0) + relativedelta(weekday=2)).strftime('%Y-%m-%d 17:00:00')" name="date_to"/>
<field eval="(datetime.now() + relativedelta(day=1, weekday=0)).strftime('%Y-%m-%d 08:00:00')" name="request_date_from"/>
<field eval="(datetime.now() + relativedelta(day=1, weekday=0) + relativedelta(weekday=2)).strftime('%Y-%m-%d 17:00:00')" name="request_date_to"/>
<field eval="(datetime.now() + relativedelta(day=1, weekday=0)).strftime('%Y-%m-%d 04:00:00')" name="date_from"/>
<field eval="(datetime.now() + relativedelta(day=1, weekday=0) + relativedelta(weekday=2)).strftime('%Y-%m-%d 20:00:00')" name="date_to"/>
<field eval="(datetime.now() + relativedelta(day=1, weekday=0)).strftime('%Y-%m-%d 04:00:00')" name="request_date_from"/>
<field eval="(datetime.now() + relativedelta(day=1, weekday=0) + relativedelta(weekday=2)).strftime('%Y-%m-%d 20:00:00')" name="request_date_to"/>
<field name="employee_id" ref="hr.employee_admin"/>
</record>
<record id="hr_holidays_sl" model="hr.leave">
<field name="name">Doctor Appointment</field>
<field name="holiday_status_id" ref="holiday_status_sl"/>
<field eval="(datetime.now() + relativedelta(day=20, weekday=0)).strftime('%Y-%m-%d 08:00:00')" name="date_from"/>
<field eval="(datetime.now() + relativedelta(day=20, weekday=0) + relativedelta(weekday=2)).strftime('%Y-%m-%d 17:00:00')" name="date_to"/>
<field eval="(datetime.now() + relativedelta(day=20, weekday=0)).strftime('%Y-%m-%d 08:00:00')" name="request_date_from"/>
<field eval="(datetime.now() + relativedelta(day=20, weekday=0) + relativedelta(weekday=2)).strftime('%Y-%m-%d 17:00:00')" name="request_date_to"/>
<field eval="(datetime.now() + relativedelta(day=20, weekday=0)).strftime('%Y-%m-%d 04:00:00')" name="date_from"/>
<field eval="(datetime.now() + relativedelta(day=20, weekday=0) + relativedelta(weekday=2)).strftime('%Y-%m-%d 20:00:00')" name="date_to"/>
<field eval="(datetime.now() + relativedelta(day=20, weekday=0)).strftime('%Y-%m-%d 04:00:00')" name="request_date_from"/>
<field eval="(datetime.now() + relativedelta(day=20, weekday=0) + relativedelta(weekday=2)).strftime('%Y-%m-%d 20:00:00')" name="request_date_to"/>
<field name="employee_id" ref="hr.employee_admin"/>
<field name="state">confirm</field>
</record>
<function model="hr.leave" name="action_approve">
<function model="hr.leave" name="action_validate">
<value eval="ref('hr_holidays.hr_holidays_sl')"/>
</function>
@@ -103,6 +112,8 @@
<field name="number_of_days">20</field>
<field name="employee_id" ref="hr.employee_al"/>
<field name="state">validate</field>
<field name="date_from" eval="time.strftime('%Y-1-1')"/>
<field name="date_to" eval="time.strftime('%Y-12-31')"/>
</record>
<record id="hr_holidays_allocation_pl_al" model="hr.leave.allocation">
@@ -111,6 +122,8 @@
<field name="number_of_days">10</field>
<field name="employee_id" ref="hr.employee_al"/>
<field name="state">validate</field>
<field name="date_from" eval="time.strftime('%Y-1-1')"/>
<field name="date_to" eval="time.strftime('%Y-12-31')"/>
</record>
<record id="hr_holidays_vc_al" model="hr.leave.allocation">
@@ -119,6 +132,8 @@
<field name="number_of_days">12</field>
<field name="employee_id" ref="hr.employee_al"/>
<field name="state">validate</field>
<field name="date_from" eval="time.strftime('%Y-1-1')"/>
<field name="date_to" eval="time.strftime('%Y-12-31')"/>
</record>
<!-- leave request -->
@@ -131,21 +146,21 @@
<field eval="time.strftime('%Y-%m-10')" name="request_date_to"/>
<field name="employee_id" ref="hr.employee_al"/>
</record>
<function model="hr.leave" name="action_approve">
<function model="hr.leave" name="action_validate">
<value eval="ref('hr_holidays.hr_holidays_cl_al')"/>
</function>
<record id="hr_holidays_sl_al" model="hr.leave">
<field name="name">Dentist appointment</field>
<field name="holiday_status_id" ref="holiday_status_sl"/>
<field eval="(datetime.now()+relativedelta(months=1, day=17, weekday=0)).strftime('%Y-%m-%d 08:00:00')" name="date_from"/>
<field eval="(datetime.now()+relativedelta(months=1, day=17, weekday=0) + relativedelta(weekday=2)).strftime('%Y-%m-%d 17:00:00')" name="date_to"/>
<field eval="(datetime.now()+relativedelta(months=1, day=17, weekday=0)).strftime('%Y-%m-%d 08:00:00')" name="request_date_from"/>
<field eval="(datetime.now()+relativedelta(months=1, day=17, weekday=0) + relativedelta(weekday=2)).strftime('%Y-%m-%d 17:00:00')" name="request_date_to"/>
<field eval="(datetime.now()+relativedelta(months=1, day=17, weekday=0)).strftime('%Y-%m-%d 04:00:00')" name="date_from"/>
<field eval="(datetime.now()+relativedelta(months=1, day=17, weekday=0) + relativedelta(weekday=2)).strftime('%Y-%m-%d 20:00:00')" name="date_to"/>
<field eval="(datetime.now()+relativedelta(months=1, day=17, weekday=0)).strftime('%Y-%m-%d 04:00:00')" name="request_date_from"/>
<field eval="(datetime.now()+relativedelta(months=1, day=17, weekday=0) + relativedelta(weekday=2)).strftime('%Y-%m-%d 20:00:00')" name="request_date_to"/>
<field name="employee_id" ref="hr.employee_al"/>
<field name="state">confirm</field>
</record>
<function model="hr.leave" name="action_approve">
<function model="hr.leave" name="action_validate">
<value eval="ref('hr_holidays.hr_holidays_sl_al')"/>
</function>
@@ -157,13 +172,18 @@
<field name="number_of_days">20</field>
<field name="employee_id" ref="hr.employee_mit"/>
<field name="state">validate</field>
<field name="date_from" eval="time.strftime('%Y-1-1')"/>
<field name="date_to" eval="time.strftime('%Y-12-31')"/>
</record>
<record id="hr_holidays_vc_mit" model="hr.leave.allocation">
<field name="name">Summer Vacation</field>
<field name="holiday_status_id" ref="holiday_status_unpaid"/>
<field name="number_of_days">7</field>
<field name="state">confirm</field>
<field name="employee_id" ref="hr.employee_mit"/>
<field name="date_from" eval="time.strftime('%Y-1-1')"/>
<field name="date_to" eval="time.strftime('%Y-12-31')"/>
</record>
<!-- leave request -->
@@ -176,17 +196,17 @@
<field eval="time.strftime('%Y-%m-24')" name="request_date_to"/>
<field name="employee_id" ref="hr.employee_mit"/>
</record>
<function model="hr.leave" name="action_approve">
<function model="hr.leave" name="action_validate">
<value eval="ref('hr_holidays.hr_holidays_cl_mit')"/>
</function>
<record id="hr_holidays_cl_mit_2" model="hr.leave">
<field name="name">Trip</field>
<field name="holiday_status_id" ref="holiday_status_cl"/>
<field eval="(datetime.now() + relativedelta(day=5, weekday=0)).strftime('%Y-%m-%d 08:00:00')" name="date_from"/>
<field eval="(datetime.now() + relativedelta(day=5, weekday=0) + relativedelta(weekday=2)).strftime('%Y-%m-%d 17:00:00')" name="date_to"/>
<field eval="(datetime.now() + relativedelta(day=5, weekday=0)).strftime('%Y-%m-%d 08:00:00')" name="request_date_from"/>
<field eval="(datetime.now() + relativedelta(day=5, weekday=0) + relativedelta(weekday=2)).strftime('%Y-%m-%d 17:00:00')" name="request_date_to"/>
<field eval="(datetime.now() + relativedelta(day=5, weekday=0)).strftime('%Y-%m-%d 04:00:00')" name="date_from"/>
<field eval="(datetime.now() + relativedelta(day=5, weekday=0) + relativedelta(weekday=2)).strftime('%Y-%m-%d 20:00:00')" name="date_to"/>
<field eval="(datetime.now() + relativedelta(day=5, weekday=0)).strftime('%Y-%m-%d 04:00:00')" name="request_date_from"/>
<field eval="(datetime.now() + relativedelta(day=5, weekday=0) + relativedelta(weekday=2)).strftime('%Y-%m-%d 20:00:00')" name="request_date_to"/>
<field name="employee_id" ref="hr.employee_mit"/>
</record>
@@ -198,6 +218,8 @@
<field name="number_of_days">20</field>
<field name="employee_id" ref="hr.employee_qdp"/>
<field name="state">validate</field>
<field name="date_from" eval="time.strftime('%Y-1-1')"/>
<field name="date_to" eval="time.strftime('%Y-12-31')"/>
</record>
<record id="hr_holidays_vc_qdp" model="hr.leave.allocation">
@@ -205,8 +227,11 @@
<field name="holiday_status_id" ref="holiday_status_unpaid"/>
<field name="number_of_days">7</field>
<field name="employee_id" ref="hr.employee_qdp"/>
<field name="state">confirm</field>
<field name="date_from" eval="time.strftime('%Y-1-1')"/>
<field name="date_to" eval="time.strftime('%Y-12-31')"/>
</record>
<function model="hr.leave.allocation" name="action_approve">
<function model="hr.leave.allocation" name="action_validate">
<value eval="ref('hr_holidays.hr_holidays_vc_qdp')"/>
</function>
@@ -221,7 +246,7 @@
<field name="employee_id" ref="hr.employee_qdp"/>
<field name="state">confirm</field>
</record>
<function model="hr.leave" name="action_approve">
<function model="hr.leave" name="action_validate">
<value eval="ref('hr_holidays.hr_holidays_cl_qdp')"/>
</function>
@@ -235,7 +260,7 @@
<field name="employee_id" ref="hr.employee_qdp"/>
<field name="state">confirm</field>
</record>
<function model="hr.leave" name="action_approve">
<function model="hr.leave" name="action_validate">
<value eval="ref('hr_holidays.hr_holidays_sl_qdp')"/>
</function>
@@ -247,6 +272,8 @@
<field name="number_of_days">20</field>
<field name="employee_id" ref="hr.employee_fpi"/>
<field name="state">validate</field>
<field name="date_from" eval="time.strftime('%Y-1-1')"/>
<field name="date_to" eval="time.strftime('%Y-12-31')"/>
</record>
<record id="hr_holidays_vc_fpi" model="hr.leave.allocation">
@@ -254,6 +281,9 @@
<field name="holiday_status_id" ref="holiday_status_unpaid"/>
<field name="number_of_days">7</field>
<field name="employee_id" ref="hr.employee_fpi"/>
<field name="state">confirm</field>
<field name="date_from" eval="time.strftime('%Y-1-1')"/>
<field name="date_to" eval="time.strftime('%Y-12-31')"/>
</record>
<!-- ++++++++++++++++++++++ Olivia ++++++++++++++++++++++ -->
@@ -264,6 +294,8 @@
<field name="number_of_days">20</field>
<field name="employee_id" ref="hr.employee_niv"/>
<field name="state">validate</field>
<field name="date_from" eval="time.strftime('%Y-1-1')"/>
<field name="date_to" eval="time.strftime('%Y-12-31')"/>
</record>
<record id="hr_holidays_vc_vad" model="hr.leave.allocation">
@@ -271,8 +303,11 @@
<field name="holiday_status_id" ref="holiday_status_unpaid"/>
<field name="number_of_days">5</field>
<field name="employee_id" ref="hr.employee_niv"/>
<field name="state">confirm</field>
<field name="date_from" eval="time.strftime('%Y-1-1')"/>
<field name="date_to" eval="time.strftime('%Y-12-31')"/>
</record>
<function model="hr.leave.allocation" name="action_approve">
<function model="hr.leave.allocation" name="action_validate">
<value eval="ref('hr_holidays.hr_holidays_vc_vad')"/>
</function>
@@ -298,7 +333,7 @@
<field name="employee_id" ref="hr.employee_niv"/>
<field name="state">confirm</field>
</record>
<function model="hr.leave" name="action_approve">
<function model="hr.leave" name="action_validate">
<value eval="ref('hr_holidays.hr_holidays_sl_vad')"/>
</function>
@@ -310,13 +345,18 @@
<field name="number_of_days">20</field>
<field name="employee_id" ref="hr.employee_jve"/>
<field name="state">confirm</field>
<field name="date_from" eval="time.strftime('%Y-1-1')"/>
<field name="date_to" eval="time.strftime('%Y-12-31')"/>
</record>
<record id="hr_holidays_vc_kim" model="hr.leave.allocation">
<field name="name">Summer Vacation</field>
<field name="holiday_status_id" ref="holiday_status_unpaid"/>
<field name="number_of_days">5</field>
<field name="state">confirm</field>
<field name="employee_id" ref="hr.employee_jve"/>
<field name="date_from" eval="time.strftime('%Y-1-1')"/>
<field name="date_to" eval="time.strftime('%Y-12-31')"/>
</record>
<!-- leave request -->
+2
View File
@@ -7,6 +7,8 @@ from . import hr_department
from . import hr_leave
from . import hr_leave_allocation
from . import hr_leave_type
from . import hr_leave_accrual_plan_level
from . import hr_leave_accrual_plan
from . import mail_message_subtype
from . import res_partner
from . import res_users
+6 -6
View File
@@ -32,6 +32,7 @@ class HrEmployeeBase(models.AbstractModel):
leave_date_to = fields.Date('To Date', compute='_compute_leave_status')
leaves_count = fields.Float('Number of Time Off', compute='_compute_remaining_leaves')
allocation_count = fields.Float('Total number of days allocated.', compute='_compute_allocation_count')
allocations_count = fields.Integer('Total number of allocations', compute="_compute_allocation_count")
allocation_used_count = fields.Float('Total number of days off used', compute='_compute_total_allocation_used')
show_leaves = fields.Boolean('Able to see Remaining Time Off', compute='_compute_show_leaves')
is_absent = fields.Boolean('Absent Today', compute='_compute_leave_status', search='_search_absent_employee')
@@ -40,9 +41,6 @@ class HrEmployeeBase(models.AbstractModel):
hr_icon_display = fields.Selection(selection_add=[('presence_holiday_absent', 'On leave'),
('presence_holiday_present', 'Present but on leave')])
def _get_date_start_work(self):
return self.create_date
def _get_remaining_leaves(self):
""" Helper to compute the remaining leaves for the current employees
:returns dict where the key is the employee id, and the value is the remain leaves
@@ -64,7 +62,7 @@ class HrEmployeeBase(models.AbstractModel):
join hr_leave_type s ON (s.id=h.holiday_status_id)
WHERE
s.active = true AND h.state='validate' AND
(s.allocation_type='fixed' OR s.allocation_type='fixed_allocation') AND
s.requires_allocation='yes' AND
h.employee_id in %s
GROUP BY h.employee_id""", (tuple(self.ids),))
return dict((row['employee_id'], row['days']) for row in self._cr.dictfetchall())
@@ -84,10 +82,12 @@ class HrEmployeeBase(models.AbstractModel):
('holiday_status_id.active', '=', True),
('state', '=', 'validate'),
], ['number_of_days:sum', 'employee_id'], ['employee_id'])
rg_results = dict((d['employee_id'][0], d['number_of_days']) for d in data)
rg_results = dict((d['employee_id'][0], {"employee_id_count": d['employee_id_count'], "number_of_days": d['number_of_days']}) for d in data)
for employee in self:
employee.allocation_count = float_round(rg_results.get(employee.id, 0.0), precision_digits=2)
result = rg_results.get(employee.id)
employee.allocation_count = float_round(result['number_of_days'], precision_digits=2) if result else 0.0
employee.allocation_display = "%g" % employee.allocation_count
employee.allocations_count = result['employee_id_count'] if result else 0.0
def _compute_total_allocation_used(self):
for employee in self:
+200 -103
View File
@@ -5,12 +5,12 @@
import logging
from collections import namedtuple
from collections import namedtuple, defaultdict
from datetime import datetime, timedelta, time
from pytz import timezone, UTC
from odoo import api, fields, models, tools
from odoo import api, fields, models, tools, SUPERUSER_ID
from odoo.addons.base.models.res_partner import _tz_get
from odoo.addons.resource.models.resource import float_to_time, HOURS_PER_DAY
from odoo.exceptions import AccessError, UserError, ValidationError
@@ -72,7 +72,7 @@ class HolidaysRequest(models.Model):
defaults = self._default_get_request_parameters(defaults)
if 'holiday_status_id' in fields_list and not defaults.get('holiday_status_id'):
lt = self.env['hr.leave.type'].search([('valid', '=', True), ('allocation_type', 'in', ['no', 'fixed_allocation'])], limit=1)
lt = self.env['hr.leave.type'].search(['|', ('requires_allocation', '=', 'no'), ('has_valid_allocation', '=', True)], limit=1)
if lt:
defaults['holiday_status_id'] = lt.id
@@ -112,7 +112,6 @@ class HolidaysRequest(models.Model):
private_name = fields.Char('Time Off Description', groups='hr_holidays.group_hr_holidays_user')
state = fields.Selection([
('draft', 'To Submit'),
('cancel', 'Cancelled'), # YTI This state seems to be unused. To remove
('confirm', 'To Approve'),
('refuse', 'Refused'),
('validate1', 'Second Approval'),
@@ -122,7 +121,6 @@ class HolidaysRequest(models.Model):
"\nThe status is 'To Approve', when time off request is confirmed by user." +
"\nThe status is 'Refused', when time off request is refused by manager." +
"\nThe status is 'Approved', when time off request is approved by manager.")
payslip_status = fields.Boolean('Reported in last payslips', help='Green this button when the time off has been taken into account in the payslip.', copy=False)
report_note = fields.Text('HR Comments', copy=False, groups="hr_holidays.group_hr_holidays_manager")
user_id = fields.Many2one('res.users', string='User', related='employee_id.user_id', related_sudo=True, compute_sudo=True, store=True, default=lambda self: self.env.uid, readonly=True)
manager_id = fields.Many2one('hr.employee', compute='_compute_from_employee_id', store=True, readonly=False)
@@ -130,14 +128,18 @@ class HolidaysRequest(models.Model):
holiday_status_id = fields.Many2one(
"hr.leave.type", compute='_compute_from_employee_id', store=True, string="Time Off Type", required=True, readonly=False,
states={'cancel': [('readonly', True)], 'refuse': [('readonly', True)], 'validate1': [('readonly', True)], 'validate': [('readonly', True)]},
domain=[('valid', '=', True)])
domain=['|', ('requires_allocation', '=', 'no'), ('has_valid_allocation', '=', True)])
holiday_allocation_id = fields.Many2one(
'hr.leave.allocation', compute='_compute_from_holiday_status_id', string="Allocation", store=True, readonly=False)
color = fields.Integer("Color", related='holiday_status_id.color')
validation_type = fields.Selection(string='Validation Type', related='holiday_status_id.leave_validation_type', readonly=False)
# HR data
employee_id = fields.Many2one(
'hr.employee', compute='_compute_from_holiday_type', store=True, string='Employee', index=True, readonly=False, ondelete="restrict",
'hr.employee', compute='_compute_from_employee_ids', store=True, string='Employee', index=True, readonly=False, ondelete="restrict",
states={'cancel': [('readonly', True)], 'refuse': [('readonly', True)], 'validate1': [('readonly', True)], 'validate': [('readonly', True)]},
tracking=True)
employee_company_id = fields.Many2one(related='employee_id.company_id', readonly=True, store=True)
active_employee = fields.Boolean(related='employee_id.active', readonly=True)
tz_mismatch = fields.Boolean(compute='_compute_tz_mismatch')
tz = fields.Selection(_tz_get, compute='_compute_tz')
@@ -176,6 +178,12 @@ class HolidaysRequest(models.Model):
string='Allocation Mode', readonly=True, required=True, default='employee',
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]},
help='By Employee: Allocation/Request for individual Employee, By Employee Tag: Allocation/Request for group of employees in category')
employee_ids = fields.Many2many(
'hr.employee', compute='_compute_from_holiday_type', store=True, string='Employees', readonly=False,
states={'cancel': [('readonly', True)], 'refuse': [('readonly', True)], 'validate1': [('readonly', True)], 'validate': [('readonly', True)]})
multi_employee = fields.Boolean(
compute='_compute_from_employee_ids', store=True,
help='Holds whether this allocation concerns more than 1 employee')
category_id = fields.Many2one(
'hr.employee.category', compute='_compute_from_holiday_type', store=True, string='Employee Tag',
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, help='Category of Employee')
@@ -196,6 +204,7 @@ class HolidaysRequest(models.Model):
supported_attachment_ids = fields.Many2many(
'ir.attachment', string="Attach File", compute='_compute_supported_attachment_ids',
inverse='_inverse_supported_attachment_ids')
supported_attachment_ids_count = fields.Integer(compute='_compute_supported_attachment_ids')
# UX fields
leave_type_request_unit = fields.Selection(related='holiday_status_id.request_unit', readonly=True)
leave_type_support_document = fields.Boolean(related="holiday_status_id.support_document")
@@ -263,10 +272,11 @@ class HolidaysRequest(models.Model):
request_unit_custom = fields.Boolean('Days-long custom hours', compute='_compute_request_unit_custom', store=True, readonly=False)
# view
is_hatched = fields.Boolean('Hatched', compute='_compute_is_hatched')
is_striked = fields.Boolean('Striked', compute='_compute_is_hatched')
_sql_constraints = [
('type_value',
"CHECK((holiday_type='employee' AND employee_id IS NOT NULL) or "
"CHECK((holiday_type='employee' AND (employee_id IS NOT NULL OR multi_employee IS TRUE)) or "
"(holiday_type='company' AND mode_company_id IS NOT NULL) or "
"(holiday_type='category' AND category_id IS NOT NULL) or "
"(holiday_type='department' AND department_id IS NOT NULL) )",
@@ -313,8 +323,46 @@ class HolidaysRequest(models.Model):
@api.depends('holiday_status_id')
def _compute_state(self):
for holiday in self:
holiday.state = 'confirm' if holiday.validation_type != 'no_validation' else 'draft'
for leave in self:
leave.state = 'confirm' if leave.validation_type != 'no_validation' else 'draft'
@api.depends('holiday_status_id.requires_allocation', 'validation_type', 'employee_id', 'date_from', 'date_to')
def _compute_from_holiday_status_id(self):
invalid_self = self.filtered(lambda leave: not leave.date_to or not leave.date_from)
if invalid_self:
invalid_self.update({'holiday_allocation_id': False})
self = self - invalid_self
if not self:
return
allocations = self.env['hr.leave.allocation'].search_read(
[
('holiday_status_id', 'in', self.holiday_status_id.ids),
('employee_id', 'in', self.employee_id.ids),
'|',
('date_to', '>=', min(self.mapped('date_to'))),
'&',
('date_to', '=', False),
('date_from', '<=', max(self.mapped('date_from'))),
], ['id', 'date_from', 'date_to', 'holiday_status_id', 'employee_id'], order="date_to, id"
)
allocations_dict = defaultdict(lambda: [])
for allocation in allocations:
allocations_dict[(allocation['holiday_status_id'][0], allocation['employee_id'][0])].append(allocation)
for leave in self:
if leave.holiday_status_id.requires_allocation == 'yes' and leave.date_from and leave.date_to:
found_allocation = False
date_to = leave.date_to.replace(tzinfo=UTC).astimezone(timezone(leave.tz)).date()
date_from = leave.date_from.replace(tzinfo=UTC).astimezone(timezone(leave.tz)).date()
for allocation in allocations_dict[(leave.holiday_status_id.id, leave.employee_id.id)]:
date_to_check = allocation['date_to'] > date_to if allocation['date_to'] else False
date_from_check = allocation['date_to'] is False and allocation['date_from'] <= date_from
if (date_to_check or date_from_check):
found_allocation = allocation['id']
break
leave.holiday_allocation_id = self.env['hr.leave.allocation'].browse(found_allocation) if found_allocation else False
else:
leave.holiday_allocation_id = False
@api.depends('request_date_from_period', 'request_hour_from', 'request_hour_to', 'request_date_from', 'request_date_to',
'request_unit_half', 'request_unit_hours', 'request_unit_custom', 'employee_id')
@@ -409,28 +457,37 @@ class HolidaysRequest(models.Model):
if holiday.holiday_status_id or holiday.request_unit_half or holiday.request_unit_hours:
holiday.request_unit_custom = False
@api.depends('employee_ids')
def _compute_from_employee_ids(self):
for holiday in self:
if len(holiday.employee_ids) == 1:
holiday.employee_id = holiday.employee_ids[0]._origin
else:
holiday.employee_id = False
holiday.multi_employee = (len(holiday.employee_ids) > 1)
@api.depends('holiday_type')
def _compute_from_holiday_type(self):
for holiday in self:
if holiday.holiday_type == 'employee':
if not holiday.employee_id:
holiday.employee_id = self.env.user.employee_id
if not holiday.employee_ids:
holiday.employee_ids = self.env.user.employee_id
holiday.mode_company_id = False
holiday.category_id = False
elif holiday.holiday_type == 'company':
holiday.employee_id = False
holiday.employee_ids = False
if not holiday.mode_company_id:
holiday.mode_company_id = self.env.company.id
holiday.category_id = False
elif holiday.holiday_type == 'department':
holiday.employee_id = False
holiday.employee_ids = False
holiday.mode_company_id = False
holiday.category_id = False
elif holiday.holiday_type == 'category':
holiday.employee_id = False
holiday.employee_ids = False
holiday.mode_company_id = False
else:
holiday.employee_id = self.env.context.get('default_employee_id') or self.env.user.employee_id
holiday.employee_ids = self.env.context.get('default_employee_id') or self.env.user.employee_id
@api.depends('employee_id')
def _compute_from_employee_id(self):
@@ -559,12 +616,14 @@ class HolidaysRequest(models.Model):
@api.depends('state')
def _compute_is_hatched(self):
for holiday in self:
holiday.is_striked = holiday.state == 'refuse'
holiday.is_hatched = holiday.state not in ['refuse', 'validate']
@api.depends('leave_type_support_document', 'attachment_ids')
def _compute_supported_attachment_ids(self):
for holiday in self:
holiday.supported_attachment_ids = holiday.attachment_ids
holiday.supported_attachment_ids_count = len(holiday.attachment_ids.ids)
def _inverse_supported_attachment_ids(self):
for holiday in self:
@@ -590,7 +649,7 @@ class HolidaysRequest(models.Model):
def _check_holidays(self):
mapped_days = self.mapped('holiday_status_id').get_employees_days(self.mapped('employee_id').ids)
for holiday in self:
if holiday.holiday_type != 'employee' or not holiday.employee_id or holiday.holiday_status_id.allocation_type == 'no':
if holiday.holiday_type != 'employee' or not holiday.employee_id or holiday.holiday_status_id.requires_allocation == 'no':
continue
leave_days = mapped_days[holiday.employee_id.id][holiday.holiday_status_id.id]
if float_compare(leave_days['remaining_leaves'], 0, precision_digits=2) == -1 or float_compare(leave_days['virtual_remaining_leaves'], 0, precision_digits=2) == -1:
@@ -664,8 +723,10 @@ class HolidaysRequest(models.Model):
target = leave.department_id.name
elif leave.holiday_type == 'category':
target = leave.category_id.name
else:
elif leave.employee_id:
target = leave.employee_id.name
else:
target = ', '.join(leave.employee_ids.mapped('name'))
if leave.leave_type_request_unit == 'hour':
if self.env.context.get('hide_employee_name') and 'employee_id' in self.env.context.get('group_by', []):
res.append((
@@ -717,34 +778,43 @@ class HolidaysRequest(models.Model):
if employee.user_id:
self.message_subscribe(partner_ids=employee.user_id.partner_id.ids)
@api.constrains('holiday_status_id', 'date_to', 'date_from')
@api.constrains('holiday_allocation_id')
def _check_allocation_id(self):
for leave in self:
if leave.holiday_status_id.requires_allocation == 'yes' and not leave.holiday_allocation_id:
raise ValidationError(_(
'Could not find an allocation of type %(leave_type)s for the requested time period.',
leave_type=leave.holiday_status_id.display_name,
))
@api.constrains('holiday_allocation_id', 'date_to', 'date_from')
def _check_leave_type_validity(self):
for leave in self:
vstart = leave.holiday_status_id.validity_start
vstop = leave.holiday_status_id.validity_stop
vstart = leave.holiday_allocation_id.date_from
vstop = leave.holiday_allocation_id.date_to
dfrom = leave.date_from
dto = leave.date_to
if leave.holiday_status_id.validity_start and leave.holiday_status_id.validity_stop:
if vstart and vstop:
if dfrom and dto and (dfrom.date() < vstart or dto.date() > vstop):
raise ValidationError(_(
'%(leave_type)s are only valid between %(start)s and %(end)s',
leave_type=leave.holiday_status_id.display_name,
start=leave.holiday_status_id.validity_start,
end=leave.holiday_status_id.validity_stop
start=vstart,
end=vstop
))
elif leave.holiday_status_id.validity_start:
elif vstart:
if dfrom and (dfrom.date() < vstart):
raise ValidationError(_(
'%(leave_type)s are only valid starting from %(date)s',
leave_type=leave.holiday_status_id.display_name,
date=leave.holiday_status_id.validity_start
date=vstart
))
elif leave.holiday_status_id.validity_stop:
elif vstop:
if dto and (dto.date() > vstop):
raise ValidationError(_(
'%(leave_type)s are only valid until %(date)s',
leave_type=leave.holiday_status_id.display_name,
date=leave.holiday_status_id.validity_stop
date=vstop
))
def _check_double_validation_rules(self, employees, state):
@@ -788,6 +858,8 @@ class HolidaysRequest(models.Model):
holidays = super(HolidaysRequest, self.with_context(mail_create_nosubscribe=True)).create(vals_list)
holidays.filtered(lambda holiday: not holiday.holiday_allocation_id).with_user(SUPERUSER_ID)._compute_from_holiday_status_id()
for holiday in holidays:
if not self._context.get('leave_fast_create'):
# Everything that is done here must be done using sudo because we might
@@ -1019,94 +1091,107 @@ class HolidaysRequest(models.Model):
raise UserError(_('Time off request must be confirmed in order to approve it.'))
self.write({'state': 'validate'})
self.filtered(lambda holiday: holiday.validation_type == 'both').write({'second_approver_id': current_employee.id})
self.filtered(lambda holiday: holiday.validation_type != 'both').write({'first_approver_id': current_employee.id})
for holiday in self.filtered(lambda holiday: holiday.holiday_type != 'employee'):
if holiday.holiday_type == 'category':
employees = holiday.category_id.employee_ids
elif holiday.holiday_type == 'company':
employees = self.env['hr.employee'].search([('company_id', '=', holiday.mode_company_id.id)])
leaves_second_approver = self.env['hr.leave']
leaves_first_approver = self.env['hr.leave']
for leave in self:
if leave.validation_type == 'both':
leaves_second_approver += leave
else:
employees = holiday.department_id.member_ids
leaves_first_approver += leave
conflicting_leaves = self.env['hr.leave'].with_context(
tracking_disable=True,
mail_activity_automation_skip=True,
leave_fast_create=True
).search([
('date_from', '<=', holiday.date_to),
('date_to', '>', holiday.date_from),
('state', 'not in', ['cancel', 'refuse']),
('holiday_type', '=', 'employee'),
('employee_id', 'in', employees.ids)])
if leave.holiday_type != 'employee' or\
(leave.holiday_type == 'employee' and len(leave.employee_ids) > 1):
if leave.holiday_type == 'employee':
employees = leave.employee_ids
elif leave.holiday_type == 'category':
employees = leave.category_id.employee_ids
elif leave.holiday_type == 'company':
employees = self.env['hr.employee'].search([('company_id', '=', leave.mode_company_id.id)])
else:
employees = leave.department_id.member_ids
if conflicting_leaves:
# YTI: More complex use cases could be managed in master
if holiday.leave_type_request_unit != 'day' or any(l.leave_type_request_unit == 'hour' for l in conflicting_leaves):
raise ValidationError(_('You can not have 2 time off that overlaps on the same day.'))
conflicting_leaves = self.env['hr.leave'].with_context(
tracking_disable=True,
mail_activity_automation_skip=True,
leave_fast_create=True
).search([
('date_from', '<=', leave.date_to),
('date_to', '>', leave.date_from),
('state', 'not in', ['cancel', 'refuse']),
('holiday_type', '=', 'employee'),
('employee_id', 'in', employees.ids)])
# keep track of conflicting leaves states before refusal
target_states = {l.id: l.state for l in conflicting_leaves}
conflicting_leaves.action_refuse()
split_leaves_vals = []
for conflicting_leave in conflicting_leaves:
if conflicting_leave.leave_type_request_unit == 'half_day' and conflicting_leave.request_unit_half:
continue
if conflicting_leaves:
# YTI: More complex use cases could be managed in master
if leave.leave_type_request_unit != 'day' or any(l.leave_type_request_unit == 'hour' for l in conflicting_leaves):
raise ValidationError(_('You can not have 2 time off that overlaps on the same day.'))
# Leaves in days
if conflicting_leave.date_from < holiday.date_from:
before_leave_vals = conflicting_leave.copy_data({
'date_from': conflicting_leave.date_from.date(),
'date_to': holiday.date_from.date() + timedelta(days=-1),
'state': target_states[conflicting_leave.id],
})[0]
before_leave = self.env['hr.leave'].new(before_leave_vals)
before_leave._compute_date_from_to()
# keep track of conflicting leaves states before refusal
target_states = {l.id: l.state for l in conflicting_leaves}
conflicting_leaves.action_refuse()
split_leaves_vals = []
for conflicting_leave in conflicting_leaves:
if conflicting_leave.leave_type_request_unit == 'half_day' and conflicting_leave.request_unit_half:
continue
# Could happen for part-time contract, that time off is not necessary
# anymore.
# Imagine you work on monday-wednesday-friday only.
# You take a time off on friday.
# We create a company time off on friday.
# By looking at the last attendance before the company time off
# start date to compute the date_to, you would have a date_from > date_to.
# Just don't create the leave at that time. That's the reason why we use
# new instead of create. As the leave is not actually created yet, the sql
# constraint didn't check date_from < date_to yet.
if before_leave.date_from < before_leave.date_to:
split_leaves_vals.append(before_leave._convert_to_write(before_leave._cache))
if conflicting_leave.date_to > holiday.date_to:
after_leave_vals = conflicting_leave.copy_data({
'date_from': holiday.date_to.date() + timedelta(days=1),
'date_to': conflicting_leave.date_to.date(),
'state': target_states[conflicting_leave.id],
})[0]
after_leave = self.env['hr.leave'].new(after_leave_vals)
after_leave._compute_date_from_to()
# Could happen for part-time contract, that time off is not necessary
# anymore.
if after_leave.date_from < after_leave.date_to:
split_leaves_vals.append(after_leave._convert_to_write(after_leave._cache))
# Leaves in days
if conflicting_leave.date_from < leave.date_from:
before_leave_vals = conflicting_leave.copy_data({
'date_from': conflicting_leave.date_from.date(),
'date_to': leave.date_from.date() + timedelta(days=-1),
'state': target_states[conflicting_leave.id],
})[0]
before_leave = self.env['hr.leave'].new(before_leave_vals)
before_leave._compute_date_from_to()
split_leaves = self.env['hr.leave'].with_context(
# Could happen for part-time contract, that time off is not necessary
# anymore.
# Imagine you work on monday-wednesday-friday only.
# You take a time off on friday.
# We create a company time off on friday.
# By looking at the last attendance before the company time off
# start date to compute the date_to, you would have a date_from > date_to.
# Just don't create the leave at that time. That's the reason why we use
# new instead of create. As the leave is not actually created yet, the sql
# constraint didn't check date_from < date_to yet.
if before_leave.date_from < before_leave.date_to:
split_leaves_vals.append(before_leave._convert_to_write(before_leave._cache))
if conflicting_leave.date_to > leave.date_to:
after_leave_vals = conflicting_leave.copy_data({
'date_from': leave.date_to.date() + timedelta(days=1),
'date_to': conflicting_leave.date_to.date(),
'state': target_states[conflicting_leave.id],
})[0]
after_leave = self.env['hr.leave'].new(after_leave_vals)
after_leave._compute_date_from_to()
# Could happen for part-time contract, that time off is not necessary
# anymore.
if after_leave.date_from < after_leave.date_to:
split_leaves_vals.append(after_leave._convert_to_write(after_leave._cache))
split_leaves = self.env['hr.leave'].with_context(
tracking_disable=True,
mail_activity_automation_skip=True,
leave_fast_create=True,
leave_skip_state_check=True
).create(split_leaves_vals)
split_leaves.filtered(lambda l: l.state in 'validate')._validate_leave_request()
values = leave._prepare_employees_holiday_values(employees)
leaves = self.env['hr.leave'].with_context(
tracking_disable=True,
mail_activity_automation_skip=True,
leave_fast_create=True,
leave_skip_state_check=True
).create(split_leaves_vals)
leave_skip_state_check=True,
).create(values)
split_leaves.filtered(lambda l: l.state in 'validate')._validate_leave_request()
leaves._validate_leave_request()
values = holiday._prepare_employees_holiday_values(employees)
leaves = self.env['hr.leave'].with_context(
tracking_disable=True,
mail_activity_automation_skip=True,
leave_fast_create=True,
leave_skip_state_check=True,
).create(values)
leaves._validate_leave_request()
leaves_second_approver.write({'second_approver_id': current_employee.id})
leaves_first_approver.write({'first_approver_id': current_employee.id})
employee_requests = self.filtered(lambda hol: hol.holiday_type == 'employee')
employee_requests._validate_leave_request()
@@ -1140,6 +1225,18 @@ class HolidaysRequest(models.Model):
self.activity_update()
return True
def action_documents(self):
domain = [('id', 'in', self.attachment_ids.ids)]
return {
'name': _("Supporting Documents"),
'type': 'ir.actions.act_window',
'res_model': 'ir.attachment',
'context': {'create': False},
'view_mode': 'list',
'domain': domain
}
def _check_approval_update(self, state):
""" Check if target state is achievable. """
if self.env.is_superuser():
@@ -0,0 +1,43 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, _
class AccrualPlan(models.Model):
_name = "hr.leave.accrual.plan"
_description = "Accrual Plan"
name = fields.Char('Name', required=True)
time_off_type_id = fields.Many2one('hr.leave.type', string="Time Off Type")
employees_count = fields.Integer("Employees", compute='_compute_employee_count')
level_ids = fields.One2many('hr.leave.accrual.level', 'accrual_plan_id')
allocation_ids = fields.One2many('hr.leave.allocation', 'accrual_plan_id')
transition_mode = fields.Selection([
('immediately', 'Immediately'),
('end_of_accrual', "After this accrual's period")],
string="Level Transition", default="immediately", required=True,
help="""Immediately: When the date corresponds to the new level, your accrual is automatically computed, granted and you switch to new level
After this accrual's period: When the accrual is complete (a week, a month), and granted, you switch to next level if allocation date corresponds""")
@api.depends('allocation_ids')
def _compute_employee_count(self):
allocations_read_group = self.env['hr.leave.allocation'].read_group(
[('accrual_plan_id', 'in', self.ids)],
['accrual_plan_id', 'employee_count:count_distinct(employee_id)'],
['accrual_plan_id'],
)
allocations_dict = {res['accrual_plan_id'][0]: res['employee_count'] for res in allocations_read_group}
for plan in self:
plan.employees_count = allocations_dict.get(plan.id, 0)
def action_open_accrual_plan_employees(self):
self.ensure_one()
return {
'name': _("Accrual Plan's Employees"),
'type': 'ir.actions.act_window',
'view_mode': 'kanban,tree,form',
'res_model': 'hr.employee',
'domain': [('id', 'in', self.allocation_ids.employee_id.ids)],
}
@@ -0,0 +1,291 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import datetime
import calendar
from dateutil.relativedelta import relativedelta
from odoo import api, fields, models
from odoo.tools.date_utils import get_timedelta
DAYS = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat']
MONTHS = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec']
def _get_date_check_month(year, month, day):
"""
Returns the last day of the month for the given date if the day is outside of the month's range, i.e 30 feb
"""
month_range = calendar.monthrange(year, month)
if day > month_range[1]:
return datetime.date(year, month, min(day, month_range[1])) + relativedelta(days=(day - month_range[1]))
return datetime.date(year, month, day)
class AccrualPlanLevel(models.Model):
_name = "hr.leave.accrual.level"
_description = "Accrual Plan Level"
_order = 'sequence asc'
sequence = fields.Integer(
string='sequence', compute='_compute_sequence', store=True,
help='Sequence is generated automatically by start time delta.')
level = fields.Integer(compute='_compute_level', help='Level computed through the sequence.')
accrual_plan_id = fields.Many2one('hr.leave.accrual.plan', "Accrual Plan", required=True)
start_count = fields.Integer(
"Start after",
help="The accrual starts after a defined period from the employee start date. This field define the number of days, month or years after which accrual is used.", default="1")
start_type = fields.Selection(
[('day', 'day(s)'),
('month', 'month(s)'),
('year', 'year(s)')],
default='day', string=" ", required=True,
help="This field define the unit of time after which the accrual starts.")
is_based_on_worked_time = fields.Boolean("Based on worked time")
# Accrue of
added_value = fields.Float(
"Gain", required=True,
help="The number of days that will be incremented for every period")
added_value_type = fields.Selection(
[('days', 'Days'),
('hours', 'Hours')],
default='days', required=True)
frequency = fields.Selection([
('daily', 'Daily'),
('weekly', 'Weekly'),
('bimonthly', 'Twice a month'),
('monthly', 'Monthly'),
('biyearly', 'Twice a year'),
('yearly', 'Yearly'),
], default='daily', required=True, string="Frequency")
week_day = fields.Selection([
('mon', 'Monday'),
('tue', 'Tuesday'),
('wed', 'Wednesday'),
('thu', 'Thursday'),
('fri', 'Friday'),
('sat', 'Saturday'),
('sun', 'Sunday'),
], default='mon', required=True, string="Allocation on")
first_day = fields.Integer(default=1)
second_day = fields.Integer(default=15)
first_month_day = fields.Integer(default=1)
first_month = fields.Selection([
('jan', 'January'),
('feb', 'February'),
('mar', 'March'),
('apr', 'April'),
('may', 'May'),
('jun', 'June'),
], default="jan")
second_month_day = fields.Integer(default=1)
second_month = fields.Selection([
('jul', 'July'),
('aug', 'Augustus'),
('sep', 'September'),
('oct', 'October'),
('nov', 'November'),
('dec', 'December')
], default="jul")
yearly_month = fields.Selection([
('jan', 'January'),
('feb', 'February'),
('mar', 'March'),
('apr', 'April'),
('may', 'May'),
('jun', 'June'),
('jul', 'July'),
('aug', 'Augustus'),
('sep', 'September'),
('oct', 'October'),
('nov', 'November'),
('dec', 'December')
], default="jan")
yearly_day = fields.Integer(default=1)
maximum_leave = fields.Float(
'Limit to', required=False, default=100,
help="Choose a maximum limit of days for this accrual. 0 means no limit.")
parent_id = fields.Many2one(
'hr.leave.accrual.level', string="Previous Level",
help="If this field is empty, this level is the first one.")
action_with_unused_accruals = fields.Selection(
[('postponed', 'Postponed to next year'),
('lost', 'Lost')],
string="At the end of the year, unused accruals will be",
default='postponed', required='True')
_sql_constraints = [
('check_dates',
"CHECK( (frequency = 'daily') or"
"(week_day IS NOT NULL AND frequency = 'weekly') or "
"(first_day > 0 AND second_day > first_day AND first_day <= 31 AND second_day <= 31 AND frequency = 'bimonthly') or "
"(first_day > 0 AND first_day <= 31 AND frequency = 'monthly')or "
"(first_month_day > 0 AND first_month_day <= 31 AND second_month_day > 0 AND second_month_day <= 31 AND frequency = 'biyearly') or "
"(yearly_day > 0 AND yearly_day <= 31 AND frequency = 'yearly'))",
"The dates you've set up aren't correct. Please check them."),
('start_count_check', "CHECK( start_count >= 1 )", "You must start after more than 0 days."),
('added_value_greater_than_zero', 'CHECK(added_value > 0)', 'You must give the gain greater than 0 in accrual plan levels.')
]
@api.depends('start_count', 'start_type')
def _compute_sequence(self):
# Not 100% accurate because of odd months/years, but good enough
start_type_multipliers = {
'day': 1,
'month': 30,
'year': 365,
}
for level in self:
level.sequence = level.start_count * start_type_multipliers[level.start_type]
@api.depends('sequence', 'accrual_plan_id')
def _compute_level(self):
#Mapped level_ids.ids ordered by sequence per plan
mapped_level_ids = {}
for plan in self.accrual_plan_id:
# We can not use .ids here because we also deal with NewIds
mapped_level_ids[plan] = [level.id for level in plan.level_ids.sorted('sequence')]
for level in self:
if level.accrual_plan_id:
level.level = mapped_level_ids[level.accrual_plan_id].index(level.id) + 1
else:
level.level = 1
def _get_accrual_values(self, allocation_create_date):
"""
This method returns all the accrual linked to their accrual_plan with the updated dynamic parameters depending
on the date.
:return: dict: {accrual_id, accrual_start, accrual_stop, nextcall, sufficient_seniority}
where accrual_start and accrual_stop are start and stop of the current period
"""
today = fields.Date.context_today(self, )
results = []
for accrual in self:
seniority = allocation_create_date + get_timedelta(accrual.start_count, accrual.start_type)
frequency = accrual.frequency
if frequency == 'daily':
accrual_start = max(today, seniority.date())
accrual_stop = accrual_start + relativedelta(days=1)
nextcall = accrual_stop
elif frequency == 'weekly':
min_accrual_date = max(today, seniority.date())
if min_accrual_date.isoweekday() == DAYS.index(accrual.week_day):
accrual_stop = min_accrual_date
else:
accrual_stop = accrual._get_next_weekday(min_accrual_date, accrual.week_day)
accrual_start = accrual_stop - relativedelta(days=7)
nextcall = accrual._get_next_weekday(min_accrual_date, accrual.week_day)
elif frequency == 'bimonthly':
if today.day <= accrual.first_day:
accrual_start = datetime.date(today.year, today.month, accrual.second_day) - relativedelta(months=1)
accrual_stop = datetime.date(today.year, today.month, accrual.first_day)
nextcall = datetime.date(today.year, today.month, accrual.second_day)
else:
if today.day <= accrual.second_day:
accrual_start = datetime.date(today.year, today.month, accrual.first_day)
accrual_stop = datetime.date(today.year, today.month, accrual.second_day)
nextcall = datetime.date(today.year, today.month, accrual.first_day) + relativedelta(months=1)
else:
accrual_start = datetime.date(today.year, today.month, accrual.second_day)
accrual_stop = datetime.date(today.year, today.month, accrual.first_day) + relativedelta(months=1)
nextcall = datetime.date(today.year, today.month, accrual.second_day) + relativedelta(months=1)
elif frequency == 'monthly':
if today.day <= accrual.first_day:
accrual_start = datetime.date(today.year, today.month, accrual.first_day) - relativedelta(months=1)
accrual_stop = datetime.date(today.year, today.month, accrual.first_day)
nextcall = datetime.date(today.year, today.month, accrual.first_day) + relativedelta(months=1)
else:
accrual_start = datetime.date(today.year, today.month, accrual.first_day)
accrual_stop = datetime.date(today.year, today.month, accrual.first_day) + relativedelta(months=1)
nextcall = datetime.date(today.year, today.month, accrual.first_day) + relativedelta(months=2)
elif frequency == 'biyearly':
first_month = MONTHS.index(accrual.first_month) + 1
second_month = MONTHS.index(accrual.second_month) + 1
potential_first_accrual_date = datetime.date(today.year, first_month, accrual.first_month_day)
potential_second_accrual_date = datetime.date(today.year, second_month, accrual.second_month_day)
if today <= potential_first_accrual_date:
accrual_start = potential_second_accrual_date - relativedelta(years=1)
accrual_stop = potential_first_accrual_date
nextcall = potential_second_accrual_date
else:
if today <= potential_second_accrual_date:
accrual_start = potential_first_accrual_date
accrual_stop = potential_second_accrual_date
nextcall = potential_first_accrual_date + relativedelta(years=1)
else:
accrual_start = potential_second_accrual_date
accrual_stop = potential_first_accrual_date + relativedelta(years=1)
nextcall = potential_first_accrual_date + relativedelta(years=1)
elif frequency == 'yearly':
month = MONTHS.index(accrual.yearly_month) + 1
potential_accrual_date = datetime.date(today.year, month, accrual.yearly_day)
if today <= potential_accrual_date:
accrual_start = potential_accrual_date - relativedelta(years=1)
accrual_stop = potential_accrual_date
nextcall = potential_accrual_date + relativedelta(years=1)
else:
accrual_start = potential_accrual_date
accrual_stop = potential_accrual_date + relativedelta(years=1)
nextcall = accrual_stop
results.append({'accrual_level_id': accrual.id,
'start_after': accrual.start_count,
'accrual_start': datetime.datetime.combine(accrual_start, datetime.datetime.min.time()),
'accrual_stop': datetime.datetime.combine(accrual_stop, datetime.datetime.min.time()),
'nextcall': nextcall,
'sufficient_seniority': seniority.date() <= today})
return results
def _get_next_date(self, last_call):
"""
Returns the next date with the given last call
"""
self.ensure_one()
if self.frequency == 'daily':
return last_call + relativedelta(days=1)
elif self.frequency == 'weekly':
return self._get_next_weekday(last_call, self.week_day)
elif self.frequency == 'bimonthly':
if last_call.day < self.first_day:
return _get_date_check_month(last_call.year, last_call.month, self.first_day)
elif last_call.day < self.second_day:
return _get_date_check_month(last_call.year, last_call.month, self.second_day)
else:
return _get_date_check_month(last_call.year, last_call.month, self.first_day) + relativedelta(months=1)
elif self.frequency == 'monthly':
if last_call.day < self.first_day:
return _get_date_check_month(last_call.year, last_call.month, self.first_day)
else:
return _get_date_check_month(last_call.year, last_call.month, self.first_day) + relativedelta(months=1)
elif self.frequency == 'biyearly':
first_month = MONTHS.index(self.first_month) + 1
second_month = MONTHS.index(self.second_month) + 1
if last_call < _get_date_check_month(last_call.year, first_month, self.first_month_day):
return _get_date_check_month(last_call.year, first_month, self.first_month_day)
elif last_call < _get_date_check_month(last_call.year, second_month, self.second_month_day):
return _get_date_check_month(last_call.year, second_month, self.second_month_day)
else:
return _get_date_check_month(last_call.year, first_month, self.first_month_day) + relativedelta(years=1)
elif self.frequency == 'yearly':
month = MONTHS.index(self.yearly_month) + 1
if last_call < _get_date_check_month(last_call.year, month, self.yearly_day):
return _get_date_check_month(last_call.year, month, self.yearly_day)
else:
return _get_date_check_month(last_call.year, month, self.yearly_day) + relativedelta(years=1)
else:
return False
@api.model
def _get_next_weekday(self, day, weekday):
"""
:param day: a datetime object
:param weekday: Weekday as a decimal number, where 0 is Sunday and 6 is Saturday.
:return: datetime of the next weekday
"""
daynames = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat']
weekday = daynames.index(weekday)
days_ahead = weekday - day.isoweekday()
if days_ahead <= 0:
days_ahead += 7
return day + relativedelta(days=days_ahead)
+286 -217
View File
@@ -3,6 +3,7 @@
# Copyright (c) 2005-2006 Axelor SARL. (http://www.axelor.com)
from collections import defaultdict
import logging
from datetime import datetime, time
@@ -13,6 +14,7 @@ from odoo.addons.resource.models.resource import HOURS_PER_DAY
from odoo.exceptions import AccessError, UserError, ValidationError
from odoo.tools.translate import _
from odoo.tools.float_utils import float_round
from odoo.tools.date_utils import get_timedelta
from odoo.osv import expression
_logger = logging.getLogger(__name__)
@@ -28,15 +30,15 @@ class HolidaysAllocation(models.Model):
def _default_holiday_status_id(self):
if self.user_has_groups('hr_holidays.group_hr_holidays_user'):
domain = [('valid', '=', True), ('allocation_type', '!=', 'no')]
domain = [('has_valid_allocation', '=', True), ('requires_allocation', '=', 'yes')]
else:
domain = [('valid', '=', True), ('allocation_type', '=', 'fixed_allocation')]
domain = [('has_valid_allocation', '=', True), ('requires_allocation', '=', 'yes'), ('employee_requests', '=', 'yes')]
return self.env['hr.leave.type'].search(domain, limit=1)
def _holiday_status_id_domain(self):
if self.user_has_groups('hr_holidays.group_hr_holidays_manager'):
return [('valid', '=', True), ('allocation_type', '!=', 'no')]
return [('valid', '=', True), ('allocation_type', '=', 'fixed_allocation')]
def _domain_holiday_status_id(self):
if self.user_has_groups('hr_holidays.group_hr_holidays_user'):
return []
return [('employee_requests', '=', 'yes')]
name = fields.Char('Description', compute='_compute_description', inverse='_inverse_description', search='_search_description', compute_sudo=False)
active = fields.Boolean(default=True)
@@ -46,30 +48,28 @@ class HolidaysAllocation(models.Model):
('cancel', 'Cancelled'),
('confirm', 'To Approve'),
('refuse', 'Refused'),
('validate1', 'Second Approval'),
('validate', 'Approved')
], string='Status', readonly=True, tracking=True, copy=False, default='confirm',
], string='Status', readonly=True, tracking=True, copy=False, default='draft',
help="The status is set to 'To Submit', when an allocation request is created." +
"\nThe status is 'To Approve', when an allocation request is confirmed by user." +
"\nThe status is 'Refused', when an allocation request is refused by manager." +
"\nThe status is 'Approved', when an allocation request is approved by manager.")
date_from = fields.Datetime(
'Start Date', readonly=True, index=True, copy=False, default=fields.Date.context_today,
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, tracking=True)
date_to = fields.Datetime(
'End Date', compute='_compute_from_holiday_status_id', store=True, readonly=False, copy=False, tracking=True,
date_from = fields.Date('Start Date', index=True, copy=False, default=fields.Date.context_today,
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, tracking=True, required=True)
date_to = fields.Date('End Date', copy=False, tracking=True,
states={'cancel': [('readonly', True)], 'refuse': [('readonly', True)], 'validate1': [('readonly', True)], 'validate': [('readonly', True)]})
holiday_status_id = fields.Many2one(
"hr.leave.type", compute='_compute_from_employee_id', store=True, string="Time Off Type", required=True, readonly=False,
"hr.leave.type", compute='_compute_holiday_status_id', store=True, string="Time Off Type", required=True, readonly=False,
states={'cancel': [('readonly', True)], 'refuse': [('readonly', True)], 'validate1': [('readonly', True)], 'validate': [('readonly', True)]},
domain=_holiday_status_id_domain, default=_default_holiday_status_id)
domain=_domain_holiday_status_id,
default=_default_holiday_status_id)
employee_id = fields.Many2one(
'hr.employee', compute='_compute_from_holiday_type', store=True, string='Employee', index=True, readonly=False, ondelete="restrict", tracking=True,
states={'cancel': [('readonly', True)], 'refuse': [('readonly', True)], 'validate1': [('readonly', True)], 'validate': [('readonly', True)]})
employee_company_id = fields.Many2one(related='employee_id.company_id', readonly=True)
'hr.employee', compute='_compute_from_employee_ids', store=True, string='Employee', index=True, readonly=False, ondelete="restrict", tracking=True,
states={'cancel': [('readonly', True)], 'refuse': [('readonly', True)], 'validate': [('readonly', True)]})
employee_company_id = fields.Many2one(related='employee_id.company_id', readonly=True, store=True)
active_employee = fields.Boolean('Active Employee', related='employee_id.active', readonly=True)
manager_id = fields.Many2one('hr.employee', compute='_compute_from_employee_id', store=True, string='Manager')
notes = fields.Html('Reasons', readonly=True, states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]})
manager_id = fields.Many2one('hr.employee', compute='_compute_manager_id', store=True, string='Manager')
notes = fields.Text('Reasons', readonly=True, states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]})
# duration
number_of_days = fields.Float(
'Number of Days', compute='_compute_from_holiday_status_id', store=True, readonly=False, tracking=True, default=1,
@@ -86,12 +86,9 @@ class HolidaysAllocation(models.Model):
# details
parent_id = fields.Many2one('hr.leave.allocation', string='Parent')
linked_request_ids = fields.One2many('hr.leave.allocation', 'parent_id', string='Linked Requests')
first_approver_id = fields.Many2one(
approver_id = fields.Many2one(
'hr.employee', string='First Approval', readonly=True, copy=False,
help='This area is automatically filled by the user who validates the allocation')
second_approver_id = fields.Many2one(
'hr.employee', string='Second Approval', readonly=True, copy=False,
help='This area is automatically filled by the user who validates the allocation with second level (If allocation type need second validation)')
validation_type = fields.Selection(string='Validation Type', related='holiday_status_id.allocation_validation_type', readonly=True)
can_reset = fields.Boolean('Can reset', compute='_compute_can_reset')
can_approve = fields.Boolean('Can Approve', compute='_compute_can_approve')
@@ -108,126 +105,49 @@ class HolidaysAllocation(models.Model):
"\n- By Company: all employees of the specified company"
"\n- By Department: all employees of the specified department"
"\n- By Employee Tag: all employees of the specific employee group category")
employee_ids = fields.Many2many(
'hr.employee', compute='_compute_from_holiday_type', store=True, string='Employees', readonly=False,
states={'cancel': [('readonly', True)], 'refuse': [('readonly', True)], 'validate': [('readonly', True)]})
multi_employee = fields.Boolean(
compute='_compute_from_employee_ids', store=True,
help='Holds whether this allocation concerns more than 1 employee')
mode_company_id = fields.Many2one(
'res.company', compute='_compute_from_holiday_type', store=True, string='Company Mode', readonly=False,
states={'cancel': [('readonly', True)], 'refuse': [('readonly', True)], 'validate1': [('readonly', True)], 'validate': [('readonly', True)]})
states={'cancel': [('readonly', True)], 'refuse': [('readonly', True)], 'validate': [('readonly', True)]})
department_id = fields.Many2one(
'hr.department', compute='_compute_department_id', store=True, string='Department',
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]})
category_id = fields.Many2one(
'hr.employee.category', compute='_compute_from_holiday_type', store=True, string='Employee Tag', readonly=False,
states={'cancel': [('readonly', True)], 'refuse': [('readonly', True)], 'validate1': [('readonly', True)], 'validate': [('readonly', True)]})
states={'cancel': [('readonly', True)], 'refuse': [('readonly', True)], 'validate': [('readonly', True)]})
# accrual configuration
lastcall = fields.Date("Date of the last accrual allocation", readonly=True, default=fields.Date.context_today)
nextcall = fields.Date("Date of the next accrual allocation", default=False, readonly=True)
allocation_type = fields.Selection(
[
('regular', 'Regular Allocation'),
('accrual', 'Accrual Allocation')
], string="Allocation Type", default="regular", required=True, readonly=True,
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]})
accrual_limit = fields.Integer('Balance limit', default=0, help="Maximum of allocation for accrual; 0 means no maximum.")
number_per_interval = fields.Float("Number of unit per interval", compute='_compute_from_holiday_status_id', store=True, readonly=False,
states={'cancel': [('readonly', True)], 'refuse': [('readonly', True)], 'validate1': [('readonly', True)], 'validate': [('readonly', True)]})
interval_number = fields.Integer("Number of unit between two intervals", compute='_compute_from_holiday_status_id', store=True, readonly=False,
states={'cancel': [('readonly', True)], 'refuse': [('readonly', True)], 'validate1': [('readonly', True)], 'validate': [('readonly', True)]})
unit_per_interval = fields.Selection([
('hours', 'Hours'),
('days', 'Days')
], compute='_compute_from_holiday_status_id', store=True, string="Unit of time added at each interval", readonly=False,
states={'cancel': [('readonly', True)], 'refuse': [('readonly', True)], 'validate1': [('readonly', True)], 'validate': [('readonly', True)]})
interval_unit = fields.Selection([
('days', 'Days'),
('weeks', 'Weeks'),
('months', 'Months'),
('years', 'Years')
], compute='_compute_from_holiday_status_id', store=True, string="Unit of time between two intervals", readonly=False,
states={'cancel': [('readonly', True)], 'refuse': [('readonly', True)], 'validate1': [('readonly', True)], 'validate': [('readonly', True)]})
nextcall = fields.Date("Date of the next accrual allocation", default=False, readonly=True)
is_officer = fields.Boolean(compute='_compute_is_officer')
accrual_plan_id = fields.Many2one('hr.leave.accrual.plan', compute="_compute_from_holiday_status_id", store=True, readonly=False, domain="[('time_off_type_id', '=?', holiday_status_id)]", tracking=True)
max_leaves = fields.Float(compute='_compute_leaves')
leaves_taken = fields.Float(compute='_compute_leaves')
taken_leave_ids = fields.One2many('hr.leave', 'holiday_allocation_id', domain="[('state', 'in', ['confirm', 'validate1', 'validate'])]")
_sql_constraints = [
('type_value',
"CHECK( (holiday_type='employee' AND employee_id IS NOT NULL) or "
"CHECK( (holiday_type='employee' AND (employee_id IS NOT NULL OR multi_employee IS TRUE)) or "
"(holiday_type='category' AND category_id IS NOT NULL) or "
"(holiday_type='department' AND department_id IS NOT NULL) or "
"(holiday_type='company' AND mode_company_id IS NOT NULL))",
"The employee, department, company or employee category of this request is missing. Please make sure that your user login is linked to an employee."),
('duration_check', "CHECK ( number_of_days >= 0 )", "The number of days must be greater than 0."),
('number_per_interval_check', "CHECK(number_per_interval > 0)", "The number per interval should be greater than 0"),
('interval_number_check', "CHECK(interval_number > 0)", "The interval number should be greater than 0"),
('duration_check', "CHECK( ( number_of_days > 0 AND allocation_type='regular') or (allocation_type != 'regular'))", "The duration must be greater than 0."),
]
@api.model
def _update_accrual(self):
"""
Method called by the cron task in order to increment the number_of_days when
necessary.
"""
today = fields.Date.from_string(fields.Date.today())
holidays = self.search([('allocation_type', '=', 'accrual'), ('employee_id.active', '=', True), ('state', '=', 'validate'), ('holiday_type', '=', 'employee'),
'|', ('date_to', '=', False), ('date_to', '>', fields.Datetime.now()),
'|', ('nextcall', '=', False), ('nextcall', '<=', today)])
for holiday in holidays:
values = {}
delta = relativedelta(days=0)
if holiday.interval_unit == 'days':
delta = relativedelta(days=holiday.interval_number)
if holiday.interval_unit == 'weeks':
delta = relativedelta(weeks=holiday.interval_number)
if holiday.interval_unit == 'months':
delta = relativedelta(months=holiday.interval_number)
if holiday.interval_unit == 'years':
delta = relativedelta(years=holiday.interval_number)
if holiday.nextcall:
values['nextcall'] = holiday.nextcall + delta
else:
values['nextcall'] = holiday.date_from
while values['nextcall'] <= datetime.combine(today, time(0, 0, 0)):
values['nextcall'] += delta
period_start = datetime.combine(today, time(0, 0, 0)) - delta
period_end = datetime.combine(today, time(0, 0, 0))
# We have to check when the employee has been created
# in order to not allocate him/her too much leaves
start_date = holiday.employee_id._get_date_start_work()
# If employee is created after the period, we cancel the computation
if period_end <= start_date or period_end < holiday.date_from:
holiday.write(values)
continue
# If employee created during the period, taking the date at which he has been created
if period_start <= start_date:
period_start = start_date
employee = holiday.employee_id
worked = employee._get_work_days_data_batch(
period_start, period_end,
domain=[('holiday_id.holiday_status_id.unpaid', '=', True), ('time_type', '=', 'leave')]
)[employee.id]['days']
left = employee._get_leave_days_data_batch(
period_start, period_end,
domain=[('holiday_id.holiday_status_id.unpaid', '=', True), ('time_type', '=', 'leave')]
)[employee.id]['days']
prorata = worked / (left + worked) if worked else 0
days_to_give = holiday.number_per_interval
if holiday.unit_per_interval == 'hours':
# As we encode everything in days in the database we need to convert
# the number of hours into days for this we use the
# mean number of hours set on the employee's calendar
days_to_give = days_to_give / (employee.resource_calendar_id.hours_per_day or HOURS_PER_DAY)
values['number_of_days'] = holiday.number_of_days + days_to_give * prorata
if holiday.accrual_limit > 0:
values['number_of_days'] = min(values['number_of_days'], holiday.accrual_limit)
holiday.write(values)
@api.depends_context('uid')
def _compute_is_officer(self):
self.is_officer = self.user_has_groups("hr_holidays.group_hr_holidays_user")
@api.depends_context('uid')
def _compute_description(self):
@@ -261,9 +181,8 @@ class HolidaysAllocation(models.Model):
@api.depends('employee_id', 'holiday_status_id')
def _compute_leaves(self):
for allocation in self:
leave_type = allocation.holiday_status_id.with_context(employee_id=allocation.employee_id.id)
allocation.max_leaves = leave_type.max_leaves
allocation.leaves_taken = leave_type.leaves_taken
allocation.max_leaves = allocation.number_of_hours_display if allocation.type_request_unit == 'hour' else allocation.number_of_days
allocation.leaves_taken = sum(taken_leave.number_of_hours_display if taken_leave.leave_type_request_unit == 'hour' else taken_leave.number_of_days for taken_leave in allocation.taken_leave_ids)
@api.depends('number_of_days')
def _compute_number_of_days_display(self):
@@ -303,37 +222,44 @@ class HolidaysAllocation(models.Model):
def _compute_can_approve(self):
for allocation in self:
try:
if allocation.state == 'confirm' and allocation.validation_type == 'both':
allocation._check_approval_update('validate1')
else:
if allocation.state == 'confirm' and allocation.validation_type != 'no':
allocation._check_approval_update('validate')
except (AccessError, UserError):
allocation.can_approve = False
else:
allocation.can_approve = True
@api.depends('employee_ids')
def _compute_from_employee_ids(self):
for allocation in self:
if len(allocation.employee_ids) == 1:
allocation.employee_id = allocation.employee_ids[0]._origin
else:
allocation.employee_id = False
allocation.multi_employee = (len(allocation.employee_ids) > 1)
@api.depends('holiday_type')
def _compute_from_holiday_type(self):
for allocation in self:
if allocation.holiday_type == 'employee':
if not allocation.employee_id:
allocation.employee_id = self.env.user.employee_id
if not allocation.employee_ids:
allocation.employee_ids = self.env.user.employee_id
allocation.mode_company_id = False
allocation.category_id = False
if allocation.holiday_type == 'company':
allocation.employee_id = False
allocation.employee_ids = False
if not allocation.mode_company_id:
allocation.mode_company_id = self.env.company
allocation.category_id = False
elif allocation.holiday_type == 'department':
allocation.employee_id = False
allocation.employee_ids = False
allocation.mode_company_id = False
allocation.category_id = False
elif allocation.holiday_type == 'category':
allocation.employee_id = False
allocation.employee_ids = False
allocation.mode_company_id = False
elif not allocation.employee_id and not allocation._origin.employee_id:
allocation.employee_id = self.env.context.get('default_employee_id') or self.env.user.employee_id
else:
allocation.employee_ids = self.env.context.get('default_employee_id') or self.env.user.employee_id
@api.depends('holiday_type', 'employee_id')
def _compute_department_id(self):
@@ -347,47 +273,207 @@ class HolidaysAllocation(models.Model):
allocation.department_id = False
@api.depends('employee_id')
def _compute_from_employee_id(self):
default_holiday_status_id = self._default_holiday_status_id()
def _compute_manager_id(self):
for allocation in self:
allocation.manager_id = allocation.employee_id and allocation.employee_id.parent_id
@api.depends('employee_id', 'accrual_plan_id')
def _compute_holiday_status_id(self):
default_holiday_status_id = None
holiday_status_by_accrual = {accrual.id: accrual.time_off_type_id for accrual in self.accrual_plan_id}
for holiday in self:
holiday.manager_id = holiday.employee_id and holiday.employee_id.parent_id
if holiday.employee_id.user_id != self.env.user and holiday._origin.employee_id != holiday.employee_id:
holiday.holiday_status_id = False
elif not holiday.holiday_status_id and not holiday._origin.holiday_status_id:
holiday.holiday_status_id = default_holiday_status_id
elif not holiday.holiday_status_id:
holiday_status = None
if holiday.accrual_plan_id:
holiday_status = holiday_status_by_accrual.get(holiday.accrual_plan_id.id)
if holiday_status:
holiday.holiday_status_id = holiday_status
elif not holiday._origin.holiday_status_id:
if not default_holiday_status_id: # fetch when we need it
default_holiday_status_id = self._default_holiday_status_id()
holiday.holiday_status_id = default_holiday_status_id
@api.depends('holiday_status_id', 'allocation_type', 'number_of_hours_display', 'number_of_days_display')
@api.depends('holiday_status_id', 'allocation_type', 'number_of_hours_display', 'number_of_days_display', 'date_to')
def _compute_from_holiday_status_id(self):
accrual_allocations = self.filtered(lambda alloc: alloc.allocation_type == 'accrual' and not alloc.accrual_plan_id and alloc.holiday_status_id)
accruals_dict = {}
default_accrual_plan = None
if accrual_allocations:
accruals_read_group = self.env['hr.leave.accrual.plan'].read_group(
[('time_off_type_id', 'in', accrual_allocations.holiday_status_id.ids)],
['time_off_type_id', 'ids:array_agg(id)'],
['time_off_type_id'],
)
accruals_dict = {res['time_off_type_id'][0]: res['ids'] for res in accruals_read_group}
for allocation in self:
allocation.number_of_days = allocation.number_of_days_display
if allocation.type_request_unit == 'hour':
allocation.number_of_days = allocation.number_of_hours_display / (allocation.employee_id.sudo().resource_calendar_id.hours_per_day or HOURS_PER_DAY)
# set default values
if not allocation.interval_number and not allocation._origin.interval_number:
allocation.interval_number = 1
if not allocation.number_per_interval and not allocation._origin.number_per_interval:
allocation.number_per_interval = 1
if not allocation.unit_per_interval and not allocation._origin.unit_per_interval:
allocation.unit_per_interval = 'hours'
if not allocation.interval_unit and not allocation._origin.interval_unit:
allocation.interval_unit = 'weeks'
if allocation.holiday_status_id.validity_stop and allocation.date_to:
new_date_to = datetime.combine(allocation.holiday_status_id.validity_stop, time.max)
if new_date_to < allocation.date_to:
allocation.date_to = new_date_to
if allocation.allocation_type == 'accrual':
if allocation.holiday_status_id.request_unit == 'hour':
allocation.unit_per_interval = 'hours'
if allocation.accrual_plan_id.time_off_type_id != allocation.holiday_status_id:
allocation.accrual_plan_id = False
if allocation.allocation_type == 'accrual' and not allocation.accrual_plan_id:
accrual_plan = None
if allocation.holiday_status_id:
accrual_plan = accruals_dict.get(allocation.holiday_status_id.id, [False])[0]
else:
allocation.unit_per_interval = 'days'
if not default_accrual_plan: # fetch the first accrual plan only when we need
default_accrual_plan = self.env['hr.leave.accrual.plan'].search([], limit=1)
accrual_plan = default_accrual_plan
allocation.accrual_plan_id = accrual_plan
#TODO: remove after impl prorata in _process_accrual_plan_level
def _compute_accrual_days(self, first_day, start_date, end_date):
"""
Increase the number of days depending on the period, the start date of the employee and how many days he worked according to his
work entry.
This method takes also in account if the employee started during the period to apply a prorata
"""
for allocation in self.filtered(lambda i: i.employee_id and i.accrual_plan_id):
start_date_prorata = 1.0
if first_day > start_date:
# The employee was created during the period after the start_date
# This prorata depends on the number of days worked during this period
worked_days = end_date - first_day
period_days = end_date - start_date
start_date_prorata = worked_days.days / period_days.days
# We calculate from the first_day to end_date
start_date = first_day
if end_date <= first_day or end_date < datetime.combine(allocation.date_from, time(0, 0, 0)):
# Stop the calculation if
# * the user was created after the period
# * the period ends before the allocation is valid
return
# The prorata depends if the employee took holidays during the period.
# Unpaid holidays and work_entry_type.leave_right = False decrease the amount of holidays
if allocation.accrual_level_id.is_based_on_worked_time:
worked = allocation.employee_id._get_work_days_data_batch(start_date, end_date, calendar=allocation.employee_id.resource_calendar_id)\
[allocation.employee_id.id]['days']
left = allocation.employee_id.sudo()._get_leave_days_data_batch(start_date, end_date,
domain=[('time_type', '=', 'leave')])[allocation.employee_id.id]['days']
work_entry_prorata = worked / (left + worked) if worked else 0
# Calculate the number of days that could be added
added_value = work_entry_prorata * start_date_prorata * allocation.accrual_level_id.added_value
else:
allocation.interval_number = 1
allocation.interval_unit = 'weeks'
allocation.number_per_interval = 1
allocation.unit_per_interval = 'hours'
added_value = allocation.accrual_level_id.added_value
number_of_days = min(allocation.number_of_days + added_value, allocation.accrual_level_id.maximum_leave)
allocation.write({'number_of_days': number_of_days})
# Do not send a message if the maximum number of days is already reached
def _end_of_year_accrual(self):
# to override in payroll
today = fields.Date.today()
for allocation in self:
current_level = allocation._get_current_accrual_plan_level_id(today)
if current_level and current_level.action_with_unused_accruals == 'lost':
# Allocations are lost but number_of_days should not be lower than leaves_taken
allocation.write({'number_of_days': allocation.leaves_taken, 'lastcall': today, 'nextcall': False})
def _get_current_accrual_plan_level_id(self, date):
"""
Returns the accrual_plan_level for the given date of the record's accrual plan
"""
self.ensure_one()
if not self.accrual_plan_id.level_ids:
return False
# Sort by sequence which should be equivalent to the level
level_ids = self.accrual_plan_id.level_ids.sorted('sequence')
current_level = False
current_level_idx = -1
for idx, level in enumerate(level_ids):
if date >= self.date_from + get_timedelta(level.start_count, level.start_type):
current_level = level
current_level_idx = idx
# If transition_mode is set to `immediately` or we are currently on the first level
# the current_level is simply the first level in the list.
if current_level_idx <= 0 or self.accrual_plan_id.transition_mode == "immediately":
return current_level
# In this case we have to verify that the 'previous level' is not the current one due to `end_of_accrual`
level_start_date = self.date_from + get_timedelta(current_level.start_count, current_level.start_type)
previous_level = level_ids[current_level_idx - 1]
# If the next date from the current level's start date is before the last call of the previous level
# return the previous level
if current_level._get_next_date(level_start_date) < previous_level._get_next_date(level_start_date):
return previous_level
return current_level
def _get_first_accrual_plan_level_start_date(self):
self.ensure_one()
level = self.accrual_plan_id.level_ids.sorted('sequence')[0]
if not level:
return False
return self.date_from + get_timedelta(level.start_count, level.start_type)
def _process_accrual_plan_level(self, level, start_date, end_date):
"""
Returns the added days for that level
"""
self.ensure_one()
#TODO: ask gmf about prorata stuff see `_compute_accrual_days`
if level.is_based_on_worked_time:
start_dt = datetime.combine(start_date, datetime.min.time())
end_dt = datetime.combine(end_date, datetime.min.time())
worked = self.employee_id._get_work_days_data_batch(start_dt, end_dt, calendar=self.employee_id.resource_calendar_id)\
[self.employee_id.id]['days']
left = self.employee_id.sudo()._get_leave_days_data_batch(start_dt, end_dt,
domain=[('time_type', '=', 'leave')])[self.employee_id.id]['days']
work_entry_prorata = worked / (left + worked) if worked else 0
added_value = work_entry_prorata * level.added_value
else:
added_value = level.added_value
# Convert time in hours to time in days in case the level is encoded in hours
if level.added_value_type == 'hours':
added_value = added_value / (self.employee_id.sudo().resource_id.calendar_id.hours_per_day or HOURS_PER_DAY)
return added_value
def _process_accrual_plans(self):
"""
This method is part of the cron's process.
The goal of this method is to retroactively apply accrual plan levels and progress from nextcall to today
"""
today = fields.Date.today()
for allocation in self:
if not allocation.nextcall:
current_level = allocation._get_current_accrual_plan_level_id(today)
if not current_level:
# Accrual plan is not configured properly or has not started
continue
allocation.lastcall = max(allocation.lastcall, allocation.date_from + get_timedelta(current_level.start_count, current_level.start_type))
allocation.nextcall = current_level._get_next_date(allocation.lastcall)
days_added_per_level = defaultdict(lambda: 0)
while allocation.nextcall <= today:
current_level = allocation._get_current_accrual_plan_level_id(allocation.nextcall)
nextcall = current_level._get_next_date(allocation.nextcall)
days_added_per_level[current_level] += allocation._process_accrual_plan_level(current_level, allocation.lastcall, allocation.nextcall)
allocation.lastcall = allocation.nextcall
allocation.nextcall = nextcall
if days_added_per_level:
number_of_days_to_add = 0
for value in days_added_per_level.values():
number_of_days_to_add += value
# Let's assume the limit of the last level is the correct one
allocation.write({'number_of_days': min(allocation.number_of_days + number_of_days_to_add, current_level.maximum_leave)})
@api.model
def _update_accrual(self):
"""
Method called by the cron task in order to increment the number_of_days when
necessary.
"""
# Get the current date to determine the start and end of the accrual period
today = datetime.combine(fields.Date.today(), time(0, 0, 0))
if today.day == 1 and today.month == 1:
end_of_year_allocations = self.search(
[('allocation_type', '=', 'accrual'), ('state', '=', 'validate'), ('accrual_plan_id', '!=', False), ('employee_id', '!=', False),
'|', ('date_to', '=', False), ('date_to', '>', fields.Datetime.now())])
end_of_year_allocations._end_of_year_accrual()
end_of_year_allocations.flush()
allocations = self.search(
[('allocation_type', '=', 'accrual'), ('state', '=', 'validate'), ('accrual_plan_id', '!=', False), ('employee_id', '!=', False),
'|', ('date_to', '=', False), ('date_to', '>', fields.Datetime.now()),
'|', ('nextcall', '=', False), ('nextcall', '<=', today)])
allocations._process_accrual_plans()
####################################################
# ORM Overrides methods
@@ -402,8 +488,10 @@ class HolidaysAllocation(models.Model):
target = allocation.department_id.name
elif allocation.holiday_type == 'category':
target = allocation.category_id.name
elif allocation.employee_id:
target = allocation.employee_id.name
else:
target = allocation.employee_id.sudo().name
target = ', '.join(allocation.employee_ids.sudo().mapped('name'))
res.append(
(allocation.id,
@@ -421,20 +509,6 @@ class HolidaysAllocation(models.Model):
if employee.user_id:
self.message_subscribe(partner_ids=employee.user_id.partner_id.ids)
@api.constrains('holiday_status_id')
def _check_leave_type_validity(self):
for allocation in self:
if allocation.holiday_status_id.validity_stop:
vstop = allocation.holiday_status_id.validity_stop
today = fields.Date.today()
if vstop < today:
raise ValidationError(_(
'You can allocate %(allocation_type)s only before %(date)s.',
allocation_type=allocation.holiday_status_id.display_name,
date=allocation.holiday_status_id.validity_stop
))
@api.model_create_multi
def create(self, vals_list):
""" Override to avoid automatic logging of creation """
@@ -442,6 +516,9 @@ class HolidaysAllocation(models.Model):
employee_id = values.get('employee_id', False)
if not values.get('department_id'):
values.update({'department_id': self.env['hr.employee'].browse(employee_id).department_id.id})
# default `lastcall` to `nextcall`
if 'date_from' in values and 'lastcall' not in values:
values['lastcall'] = values['date_from']
holidays = super(HolidaysAllocation, self.with_context(mail_create_nosubscribe=True)).create(vals_list)
for holiday in holidays:
partners_to_subscribe = set()
@@ -453,6 +530,8 @@ class HolidaysAllocation(models.Model):
holiday.message_subscribe(partner_ids=tuple(partners_to_subscribe))
if not self._context.get('import_file'):
holiday.activity_update()
if holiday.validation_type == 'no':
holiday.state = 'validate'
return holidays
def write(self, values):
@@ -476,9 +555,9 @@ class HolidaysAllocation(models.Model):
# Business methods
####################################################
def _prepare_holiday_values(self, employee):
def _prepare_holiday_values(self, employees):
self.ensure_one()
values = {
return [{
'name': self.name,
'holiday_type': 'employee',
'holiday_status_id': self.holiday_status_id.id,
@@ -486,23 +565,19 @@ class HolidaysAllocation(models.Model):
'number_of_days': self.number_of_days,
'parent_id': self.id,
'employee_id': employee.id,
'state': 'confirm',
'allocation_type': self.allocation_type,
'date_from': self.date_from,
'date_to': self.date_to,
'interval_unit': self.interval_unit,
'interval_number': self.interval_number,
'number_per_interval': self.number_per_interval,
'unit_per_interval': self.unit_per_interval,
}
return values
'accrual_plan_id': self.accrual_plan_id.id,
} for employee in employees]
def action_draft(self):
if any(holiday.state not in ['confirm', 'refuse'] for holiday in self):
raise UserError(_('Allocation request state must be "Refused" or "To Approve" in order to be reset to Draft.'))
self.write({
'state': 'draft',
'first_approver_id': False,
'second_approver_id': False,
'approver_id': False,
})
linked_requests = self.mapped('linked_request_ids')
if linked_requests:
@@ -518,29 +593,14 @@ class HolidaysAllocation(models.Model):
self.activity_update()
return res
def action_approve(self):
# if validation_type == 'both': this method is the first approval approval
# if validation_type != 'both': this method calls action_validate() below
if any(holiday.state != 'confirm' for holiday in self):
raise UserError(_('Allocation request must be confirmed ("To Approve") in order to approve it.'))
current_employee = self.env.user.employee_id
self.filtered(lambda hol: hol.validation_type == 'both').write({'state': 'validate1', 'first_approver_id': current_employee.id})
self.filtered(lambda hol: not hol.validation_type == 'both').action_validate()
self.activity_update()
def action_validate(self):
current_employee = self.env.user.employee_id
for holiday in self:
if holiday.state not in ['confirm', 'validate1']:
if holiday.state != 'confirm':
raise UserError(_('Allocation request must be confirmed in order to approve it.'))
holiday.write({'state': 'validate'})
if holiday.validation_type == 'both':
holiday.write({'second_approver_id': current_employee.id})
else:
holiday.write({'first_approver_id': current_employee.id})
holiday.write({'approver_id': current_employee.id})
holiday._action_validate_create_childs()
self.activity_update()
@@ -548,22 +608,26 @@ class HolidaysAllocation(models.Model):
def _action_validate_create_childs(self):
childs = self.env['hr.leave.allocation']
if self.state == 'validate' and self.holiday_type in ['category', 'department', 'company']:
if self.holiday_type == 'category':
# In the case we are in holiday_type `employee` and there is only one employee we can keep the same allocation
# Otherwise we do need to create an allocation for all employees to have a behaviour that is in line
# with the other holiday_type
if self.state == 'validate' and (self.holiday_type in ['category', 'department', 'company'] or
(self.holiday_type == 'employee' and len(self.employee_ids) > 1)):
if self.holiday_type == 'employee':
employees = self.employee_ids
elif self.holiday_type == 'category':
employees = self.category_id.employee_ids
elif self.holiday_type == 'department':
employees = self.department_id.member_ids
else:
employees = self.env['hr.employee'].search([('company_id', '=', self.mode_company_id.id)])
for employee in employees:
childs += self.with_context(
mail_notify_force_send=False,
mail_activity_automation_skip=True
).create(self._prepare_holiday_values(employee))
# TODO is it necessary to interleave the calls?
childs.action_approve()
if childs and self.validation_type == 'both':
allocation_create_vals = self._prepare_holiday_values(employees)
childs += self.with_context(
mail_notify_force_send=False,
mail_activity_automation_skip=True
).create(allocation_create_vals)
if childs and self.validation_type != 'no':
childs.action_validate()
return childs
@@ -572,9 +636,7 @@ class HolidaysAllocation(models.Model):
if any(holiday.state not in ['confirm', 'validate', 'validate1'] for holiday in self):
raise UserError(_('Allocation request must be confirmed or validated in order to refuse it.'))
validated_holidays = self.filtered(lambda hol: hol.state == 'validate1')
validated_holidays.write({'state': 'refuse', 'first_approver_id': current_employee.id})
(self - validated_holidays).write({'state': 'refuse', 'second_approver_id': current_employee.id})
self.write({'state': 'refuse', 'approver_id': current_employee.id})
# If a category that created several holidays, cancel all related
linked_requests = self.mapped('linked_request_ids')
if linked_requests:
@@ -622,6 +684,13 @@ class HolidaysAllocation(models.Model):
if not is_officer:
raise UserError(_('Only a Time off Approver can apply the second approval on allocation requests.'))
@api.onchange('allocation_type')
def _onchange_allocation_type(self):
if self.allocation_type == 'accrual':
self.number_of_days = 0.0
elif not self.number_of_days_display:
self.number_of_days = 1.0
# ------------------------------------------------------------
# Activity methods
# ------------------------------------------------------------
+91 -66
View File
@@ -9,7 +9,6 @@ import logging
from collections import defaultdict
from odoo import api, fields, models
from odoo.exceptions import ValidationError
from odoo.osv import expression
from odoo.tools.translate import _
from odoo.tools.float_utils import float_round
@@ -26,10 +25,9 @@ class HolidaysType(models.Model):
def _model_sorting_key(self, leave_type):
remaining = leave_type.virtual_remaining_leaves > 0
taken = leave_type.leaves_taken > 0
return -1*leave_type.sequence, leave_type.allocation_type == 'fixed' and remaining, leave_type.allocation_type == 'fixed_allocation' and remaining, taken
return -1*leave_type.sequence, leave_type.employee_requests == 'no' and remaining, leave_type.employee_requests == 'yes' and remaining, taken
name = fields.Char('Time Off Type', required=True, translate=True)
code = fields.Char('Code')
sequence = fields.Integer(default=100,
help='The type with the smallest sequence is the default value in time off request')
create_calendar_meeting = fields.Boolean(string="Display Time Off in Calendar", default=True)
@@ -50,7 +48,9 @@ class HolidaysType(models.Model):
('lavender', 'Lavender'),
('wheat', 'Wheat'),
('ivory', 'Ivory')], string='Color in Report', required=True, default='red',
help='This color will be used in the time off summary located in Reporting > Time off by Department.')
help='This color will be used in the time off summary located in Reporting > Time off by Department.')
color = fields.Integer(string='Color', help="The color selected here will be used in every screen with the time off type.")
icon_id = fields.Many2one('ir.attachment', string='Cover Image', domain="[('res_model', '=', 'hr.leave.type'), ('res_field', '=', 'icon_id')]")
active = fields.Boolean('Active', default=True,
help="If the active field is set to false, it will allow you to hide the time off type without removing it.")
max_leaves = fields.Float(compute='_compute_leaves', string='Maximum Allowed', search='_search_max_leaves',
@@ -72,70 +72,86 @@ class HolidaysType(models.Model):
group_days_leave = fields.Float(
compute='_compute_group_days_leave', string='Group Time Off')
company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company)
responsible_id = fields.Many2one('res.users', 'Responsible',
responsible_id = fields.Many2one(
'res.users', 'Responsible Time Off Officer',
domain=lambda self: [('groups_id', 'in', self.env.ref('hr_holidays.group_hr_holidays_user').id)],
help="This user will be responsible for approving this type of time off. "
"This is only used when validation is 'By Time Off Officer' or 'By Employee's Manager and Time Off Officer'",)
help="Choose the Time Off Officer who will be notified to approve allocation or Time Off request")
leave_validation_type = fields.Selection([
('no_validation', 'No Validation'),
('hr', 'By Time Off Officer'),
('manager', "By Employee's Approver"),
('both', "By Employee's Approver and Time Off Officer")], default='hr', string='Leave Validation')
requires_allocation = fields.Selection([
('yes', 'Yes'),
('no', 'No Limit')], default="yes", required=True, string='Requires allocation')
employee_requests = fields.Selection([
('yes', 'Extra Days Requests Allowed'),
('no', 'Not Allowed')], default="no", required=True, string="Employee Requests")
allocation_validation_type = fields.Selection([
('hr', 'By Time Off Officer'),
('manager', "By Employee's Approver"),
('both', "By Employee's Approver and Time Off Officer")], default='manager', string='Allocation Validation')
allocation_type = fields.Selection([
('no', 'No Limit'),
('fixed_allocation', 'Allow Employees Requests'),
('fixed', 'Set by Time Off Officer')],
default='no', string='Mode',
help='\tNo Limit: no allocation by default, users can freely request time off; '
'\tAllow Employees Requests: allocated by HR and users can request time off and allocations; '
'\tSet by Time Off Officer: allocated by HR and cannot be bypassed; users can request time off;')
validity_start = fields.Date("From",
help='Adding validity to types of time off so that it cannot be selected outside this time period')
validity_stop = fields.Date("To")
valid = fields.Boolean(compute='_compute_valid', search='_search_valid', help='This indicates if it is still possible to use this type of leave')
('no', 'No validation needed'),
('officer', 'Approved by Time Off Officer'),
('set', "Set by Time Off Officer")], default='officer', string='Approval')
has_valid_allocation = fields.Boolean(compute='_compute_valid', search='_search_valid', help='This indicates if it is still possible to use this type of leave')
time_type = fields.Selection([('leave', 'Time Off'), ('other', 'Other')], default='leave', string="Kind of Leave",
help="Whether this should be computed as a holiday or as work time (eg: formation)")
request_unit = fields.Selection([
('day', 'Day'), ('half_day', 'Half Day'), ('hour', 'Hours')],
default='day', string='Take Time Off in', required=True)
('day', 'Day'),
('half_day', 'Half Day'),
('hour', 'Hours')], default='day', string='Take Time Off in', required=True)
unpaid = fields.Boolean('Is Unpaid', default=False)
leave_notif_subtype_id = fields.Many2one('mail.message.subtype', string='Time Off Notification Subtype', default=lambda self: self.env.ref('hr_holidays.mt_leave', raise_if_not_found=False))
allocation_notif_subtype_id = fields.Many2one('mail.message.subtype', string='Allocation Notification Subtype', default=lambda self: self.env.ref('hr_holidays.mt_leave_allocation', raise_if_not_found=False))
support_document = fields.Boolean(string='Supporting Document')
accruals_ids = fields.One2many('hr.leave.accrual.plan', 'time_off_type_id')
accrual_count = fields.Float(compute="_compute_accrual_count", string="Accruals count")
@api.constrains('validity_start', 'validity_stop')
def _check_validity_dates(self):
for leave_type in self:
if leave_type.validity_start and leave_type.validity_stop and \
leave_type.validity_start > leave_type.validity_stop:
raise ValidationError(_("End of validity period should be greater than start of validity period"))
@api.depends('validity_start', 'validity_stop')
def _compute_valid(self):
dt = self._context.get('default_date_from') or fields.Date.context_today(self)
for holiday_type in self:
if holiday_type.validity_start and holiday_type.validity_stop:
holiday_type.valid = ((dt < holiday_type.validity_stop) and (dt > holiday_type.validity_start))
elif holiday_type.validity_start and (dt > holiday_type.validity_start):
holiday_type.valid = False
else:
holiday_type.valid = True
@api.model
def _search_valid(self, operator, value):
dt = self._context.get('default_date_from', False)
""" Returns leave_type ids for which a valid allocation exists
or that don't need an allocation
return [('id', domain_operator, [x['id'] for x in res])]
"""
date_to = self._context.get('default_date_from') or fields.Date.today().strftime('%Y-1-1')
date_from = self._context.get('default_date_to') or fields.Date.today().strftime('%Y-12-31')
if not dt:
return []
signs = ['>=', '<='] if operator == '=' else ['<=', '>=']
if not isinstance(value, bool):
raise ValueError('Invalid value: %s' % (value))
if operator not in ['=', '!=']:
raise ValueError('Invalid operator: %s' % (operator))
new_operator = 'in' if operator == '=' else 'not in'
return ['|', ('validity_stop', operator, False), '&',
('validity_stop', signs[0] if value else signs[1], dt),
('validity_start', signs[1] if value else signs[0], dt)]
query = '''
SELECT
holiday_status_id
FROM
hr_leave_allocation alloc
WHERE
alloc.date_to >= %s OR alloc.date_to IS NULL AND
alloc.date_from <= %s
'''
self._cr.execute(query, (date_to, date_from))
return [('id', new_operator, [x['holiday_status_id'] for x in self._cr.dictfetchall()])]
@api.depends('requires_allocation')
def _compute_valid(self):
date_to = self._context.get('default_date_to', fields.Datetime.today())
date_from = self._context.get('default_date_from', fields.Datetime.today())
for holiday_type in self:
if holiday_type.requires_allocation:
allocation = self.env['hr.leave.allocation'].search([
('holiday_status_id', '=', holiday_type.id),
'|',
('date_to', '>=', date_to),
'&',
('date_to', '=', False),
('date_from', '<=', date_from)])
holiday_type.has_valid_allocation = bool(allocation)
else:
holiday_type.has_valid_allocation = True
def _search_max_leaves(self, operator, value):
value = float(value)
@@ -172,7 +188,7 @@ class HolidaysType(models.Model):
valid_leave_types = self.env['hr.leave.type']
for leave_type in leave_types:
if leave_type.allocation_type != 'no':
if leave_type.requires_allocation == "yes":
if operator == '>' and leave_type.virtual_remaining_leaves > value:
valid_leave_types |= leave_type
elif operator == '<' and leave_type.virtual_remaining_leaves < value:
@@ -250,8 +266,7 @@ class HolidaysType(models.Model):
@api.model
def get_days_all_request(self):
leave_types = sorted(self.search([]).filtered(lambda x: ((not x.validity_stop or x.validity_stop >= fields.Date.today())
and (x.virtual_remaining_leaves or x.max_leaves))), key=self._model_sorting_key, reverse=True)
leave_types = sorted(self.search([]).filtered(lambda x: ((x.virtual_remaining_leaves or x.max_leaves))), key=self._model_sorting_key, reverse=True)
return [lt._get_days_request() for lt in leave_types]
def _get_days_request(self):
@@ -263,7 +278,8 @@ class HolidaysType(models.Model):
'leaves_taken': ('%.2f' % self.leaves_taken).rstrip('0').rstrip('.'),
'virtual_leaves_taken': ('%.2f' % self.virtual_leaves_taken).rstrip('0').rstrip('.'),
'request_unit': self.request_unit,
}, self.allocation_type, self.validity_stop, self.id)
'icon': self.sudo().icon_id.url,
}, self.requires_allocation, self.id)
def _get_contextual_employee_id(self):
if 'employee_id' in self._context:
@@ -290,18 +306,16 @@ class HolidaysType(models.Model):
holiday_status.virtual_leaves_taken = result.get('virtual_leaves_taken', 0)
def _compute_group_days_allocation(self):
date_from = fields.Datetime.to_string(datetime.datetime.now().replace(month=1, day=1, hour=0, minute=0, second=0, microsecond=0))
domain = [
('holiday_status_id', 'in', self.ids),
('holiday_type', '!=', 'employee'),
('state', '=', 'validate'),
]
domain2 = [
'|',
('date_from', '>=', fields.Datetime.to_string(datetime.datetime.now().replace(month=1, day=1, hour=0, minute=0, second=0, microsecond=0))),
('date_from', '>=', date_from),
('date_from', '=', False),
]
grouped_res = self.env['hr.leave.allocation'].read_group(
expression.AND([domain, domain2]),
domain,
['holiday_status_id', 'number_of_days'],
['holiday_status_id'],
)
@@ -311,7 +325,7 @@ class HolidaysType(models.Model):
def _compute_group_days_leave(self):
grouped_res = self.env['hr.leave'].read_group(
[('holiday_status_id', 'in', self.ids), ('holiday_type', '=', 'employee'), ('state', '=', 'validate'),
[('holiday_status_id', 'in', self.ids), ('state', '=', 'validate'),
('date_from', '>=', fields.Datetime.to_string(datetime.datetime.now().replace(month=1, day=1, hour=0, minute=0, second=0, microsecond=0)))],
['holiday_status_id'],
['holiday_status_id'],
@@ -320,6 +334,12 @@ class HolidaysType(models.Model):
for allocation in self:
allocation.group_days_leave = grouped_dict.get(allocation.id, 0)
def _compute_accrual_count(self):
accrual_allocations = self.env['hr.leave.allocation'].read_group([('employee_id', '!=', False), ('accrual_plan_id', '!=', False), ('state', '=', 'validate')], ['id'], ['holiday_status_id'])
mapped_data = dict((data['holiday_status_id'][0], data['holiday_status_id_count']) for data in accrual_allocations)
for leave_type in self:
leave_type.accrual_count = mapped_data.get(leave_type.id, 0)
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
@@ -327,7 +347,7 @@ class HolidaysType(models.Model):
res = []
for record in self:
name = record.name
if record.allocation_type != 'no':
if record.requires_allocation == "yes":
name = "%(name)s (%(count)s)" % {
'name': name,
'count': _('%g remaining out of %g') % (
@@ -362,16 +382,14 @@ class HolidaysType(models.Model):
def action_see_days_allocated(self):
self.ensure_one()
action = self.env["ir.actions.actions"]._for_xml_id("hr_holidays.hr_leave_allocation_action_all")
domain = [
date_from = fields.Datetime.to_string(
datetime.datetime.now().replace(month=1, day=1, hour=0, minute=0, second=0, microsecond=0))
action['domain'] = [
('holiday_status_id', 'in', self.ids),
('holiday_type', '!=', 'employee'),
]
domain2 = [
'|',
('date_from', '>=', fields.Datetime.to_string(datetime.datetime.now().replace(month=1, day=1, hour=0, minute=0, second=0, microsecond=0))),
('date_from', '>=', date_from),
('date_from', '=', False),
]
action['domain'] = expression.AND([domain, domain2])
action['context'] = {
'default_holiday_type': 'department',
'default_holiday_status_id': self.ids[0],
@@ -389,3 +407,10 @@ class HolidaysType(models.Model):
'default_holiday_status_id': self.ids[0],
}
return action
def action_see_accrual_plans(self):
self.ensure_one()
action = self.env["ir.actions.actions"]._for_xml_id("hr.holidays.open_view_accrual_plans")
action['context'] = {
'default_time_off_type_id': self.id,
}
+1
View File
@@ -4,3 +4,4 @@
from . import holidays_summary_report
from . import hr_leave_report
from . import hr_leave_report_calendar
from . import hr_leave_employee_type_report
@@ -0,0 +1,104 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, tools, _
class LeaveReport(models.Model):
_name = "hr.leave.employee.type.report"
_description = 'Time Off Summary / Report'
_auto = False
_order = "date_from DESC, employee_id"
employee_id = fields.Many2one('hr.employee', string="Employee", readonly=True)
active_employee = fields.Boolean(related='employee_id.active', readonly=True)
number_of_days = fields.Float('Number of Days', readonly=True, group_operator="sum")
department_id = fields.Many2one('hr.department', string='Department', readonly=True)
leave_type = fields.Many2one("hr.leave.type", string="Leave Type", readonly=True)
holiday_status = fields.Selection([
('taken', 'Taken'), #taken = validated
('left', 'Left'),
('planned', 'Planned')
])
state = fields.Selection([
('draft', 'To Submit'),
('cancel', 'Cancelled'),
('confirm', 'To Approve'),
('refuse', 'Refused'),
('validate1', 'Second Approval'),
('validate', 'Approved')
], string='Status', readonly=True)
date_from = fields.Datetime('Start Date', readonly=True)
date_to = fields.Datetime('End Date', readonly=True)
company_id = fields.Many2one('res.company', string="Company", readonly=True)
def init(self):
tools.drop_view_if_exists(self._cr, 'hr_leave_employee_type_report')
self._cr.execute("""
CREATE or REPLACE view hr_leave_employee_type_report as (
SELECT row_number() over(ORDER BY leaves.employee_id) as id,
leaves.employee_id as employee_id,
leaves.number_of_days as number_of_days,
leaves.department_id as department_id,
leaves.leave_type as leave_type,
leaves.holiday_status as holiday_status,
leaves.state as state,
leaves.date_from as date_from,
leaves.date_to as date_to,
leaves.company_id as company_id
FROM (SELECT
allocation.employee_id as employee_id,
CASE
WHEN request.number_of_days > 0 THEN allocation.number_of_days - request.number_of_days
ELSE allocation.number_of_days
END as number_of_days,
allocation.department_id as department_id,
allocation.holiday_status_id as leave_type,
allocation.state as state,
allocation.date_from as date_from,
allocation.date_to as date_to,
'left' as holiday_status,
allocation.employee_company_id as company_id
FROM hr_leave_allocation as allocation
LEFT JOIN
(SELECT holiday_status_id, employee_id, sum(number_of_days) as number_of_days
FROM hr_leave GROUP BY holiday_status_id, employee_id) request
on (allocation.employee_id=request.employee_id and allocation.holiday_status_id = request.holiday_status_id)
UNION ALL SELECT
request.employee_id as employee_id,
request.number_of_days as number_of_days,
request.department_id as department_id,
request.holiday_status_id as leave_type,
request.state as state,
request.date_from as date_from,
request.date_to as date_to,
CASE
WHEN request.state IN ('validate1', 'validate') THEN 'taken'
WHEN request.state = 'confirm' THEN 'planned'
END as holiday_status,
request.employee_company_id as company_id
FROM hr_leave as request) leaves
);
""")
@api.model
def action_time_off_analysis(self):
domain = []
if self.env.context.get('active_ids'):
domain = [('employee_id', 'in', self.env.context.get('active_ids', []))]
return {
'name': _('Time Off Analysis'),
'type': 'ir.actions.act_window',
'res_model': 'hr.leave.employee.type.report',
'view_mode': 'pivot',
'search_view_id': [self.env.ref('hr_holidays.view_search_hr_holidays_employee_type_report').id],
'domain': domain,
'context': {
'search_default_year': True,
'search_default_company': True,
'search_default_employee': True,
'group_expand': True,
}
}
@@ -0,0 +1,42 @@
<?xml version="1.0"?>
<odoo>
<record id="view_search_hr_holidays_employee_type_report" model="ir.ui.view">
<field name="name">hr.holidays.filter</field>
<field name="model">hr.leave.employee.type.report</field>
<field name="arch" type="xml">
<search string="Search Time Off">
<field name="date_from"/>
<field name="employee_id"/>
<filter name="year" date="date_from" default_period="this_year" string="Period"/>
<filter string="Company" name="company" context="{'group_by':'company_id'}" groups="base.group_multi_company"/>
<filter string="Employee" name="employee" context="{'group_by':'employee_id'}"/>
</search>
</field>
</record>
<record id="hr_leave_employee_type_report" model="ir.ui.view">
<field name="name">hr.leave.employee.type.report.view.pivot</field>
<field name="model">hr.leave.employee.type.report</field>
<field name="arch" type="xml">
<pivot sample="1" disable_linking="True">
<field name="employee_id" type="row"/>
<field name="number_of_days" type="measure"/>
<field name="leave_type" type="col"/>
<field name="holiday_status" type="col"/>
</pivot>
</field>
</record>
<record id="action_hr_holidays_by_employee_and_type_report" model="ir.actions.server">
<field name="name">Time off Analysis by Employee and Time Off Type</field>
<field name="model_id" ref="hr_holidays.model_hr_leave_employee_type_report"/>
<field name="binding_model_id" ref="hr.model_hr_employee"/>
<field name="state">code</field>
<field name="groups_id" eval="[(4, ref('base.group_user'))]"/>
<field name="code">
action = model.action_time_off_analysis()
</field>
</record>
</odoo>
+6 -6
View File
@@ -36,7 +36,7 @@ class LeaveReport(models.Model):
], string='Allocation Mode', readonly=True)
date_from = fields.Datetime('Start Date', readonly=True)
date_to = fields.Datetime('End Date', readonly=True)
payslip_status = fields.Boolean('Reported in last payslips', readonly=True)
company_id = fields.Many2one('res.company', string="Company", readonly=True)
def init(self):
tools.drop_view_if_exists(self._cr, 'hr_leave_report')
@@ -49,7 +49,7 @@ class LeaveReport(models.Model):
leaves.category_id as category_id, leaves.department_id as department_id,
leaves.holiday_status_id as holiday_status_id, leaves.state as state,
leaves.holiday_type as holiday_type, leaves.date_from as date_from,
leaves.date_to as date_to, leaves.payslip_status as payslip_status
leaves.date_to as date_to, leaves.company_id
from (select
allocation.employee_id as employee_id,
allocation.private_name as name,
@@ -61,8 +61,8 @@ class LeaveReport(models.Model):
allocation.holiday_type,
null as date_from,
null as date_to,
FALSE as payslip_status,
'allocation' as leave_type
'allocation' as leave_type,
allocation.employee_company_id as company_id
from hr_leave_allocation as allocation
union all select
request.employee_id as employee_id,
@@ -75,8 +75,8 @@ class LeaveReport(models.Model):
request.holiday_type,
request.date_from as date_from,
request.date_to as date_to,
request.payslip_status as payslip_status,
'request' as leave_type
'request' as leave_type,
request.employee_company_id as company_id
from hr_leave as request) leaves
);
""")
@@ -19,6 +19,7 @@ class LeaveReportCalendar(models.Model):
duration = fields.Float(string='Duration', readonly=True)
employee_id = fields.Many2one('hr.employee', readonly=True)
department_id = fields.Many2one('hr.department', readonly=True)
job_id = fields.Many2one('hr.job', readonly=True)
company_id = fields.Many2one('res.company', readonly=True)
state = fields.Selection([
('draft', 'To Submit'),
@@ -30,6 +31,7 @@ class LeaveReportCalendar(models.Model):
], readonly=True)
is_hatched = fields.Boolean('Hatched', readonly=True)
is_striked = fields.Boolean('Striked', readonly=True)
def init(self):
tools.drop_view_if_exists(self._cr, 'hr_leave_report_calendar')
@@ -43,11 +45,13 @@ class LeaveReportCalendar(models.Model):
hl.state AS state,
hl.department_id AS department_id,
em.company_id AS company_id,
em.job_id AS job_id,
CASE
WHEN hl.holiday_type = 'employee' THEN rr.tz
ELSE %s
END AS tz,
state != 'validate' as is_hatched
state = 'refuse' as is_striked,
state not in ('validate', 'refuse') as is_hatched
FROM hr_leave hl
LEFT JOIN hr_employee em
ON em.id = hl.employee_id
@@ -1,17 +1,5 @@
<?xml version='1.0' encoding='UTF-8' ?>
<odoo>
<record id="action_hr_holidays_dashboard" model="ir.actions.act_window">
<field name="name">All Time Off</field>
<field name="res_model">hr.leave.report.calendar</field>
<field name="view_mode">calendar</field>
<!-- YTI : Ecrabouille explicitely those fields, as this is deployed on
a stable release. -->
<field name="view_id"/>
<field name="search_view_id"/>
<field name="domain">[('employee_id.active','=',True)]</field>
<field name="context">{'hide_employee_name': 1}</field>
</record>
<record id="hr_leave_report_calendar_view" model="ir.ui.view">
<field name="name">hr.leave.report.calendar.view</field>
<field name="model">hr.leave.report.calendar</field>
@@ -47,17 +35,28 @@
<field name="name"/>
<field name="employee_id"/>
<field name="department_id"/>
<field name="job_id"/>
<filter name="my_team" string="My Team" domain="['|', ('employee_id.user_id', '=', uid), ('employee_id.parent_id.user_id', '=', uid)]"/>
<filter string="My Department" name="department" domain="['|', ('department_id.member_ids.user_id', '=', uid), ('employee_id.user_id', '=', uid)]" help="My Department Time Off"/>
<filter string="People I Manage" name="managed_people" domain="[('employee_id.leave_manager_id', '=', uid)]" help="Time off of people you are manager of"/>
<separator/>
<filter string="Off Today" name="off_today" domain="[('start_datetime', '&lt;=', context_today().strftime('%Y-%m-%d')), ('stop_datetime', '&gt;=', context_today().strftime('%Y-%m-%d'))]" help="My Department Time Off"/>
<separator/>
<filter string="Approved" name="validate" domain="[('state', '=', 'validate')]" help="validate"/>
<filter string="Waiting for Approval" name="approve" domain="[('state','in',('confirm','validate1'))]"/>
<filter name="groupby_job_id" string="Job Position" context="{'group_by': 'job_id'}"/>
<filter name="groupby_company_id" string="Company" context="{'group_by': 'company_id'}" groups="base.group_multi_company"/>
<filter name="groupby_department_id" context="{'group_by': 'department_id'}"/>
</search>
</field>
</record>
<record id="hr_holidays.action_hr_holidays_dashboard" model="ir.actions.act_window">
<record id="action_hr_holidays_dashboard" model="ir.actions.act_window">
<field name="name">All Time Off</field>
<field name="res_model">hr.leave.report.calendar</field>
<field name="view_mode">calendar</field>
<field name="search_view_id" ref="hr_leave_report_calendar_view_search"/>
<field name="domain">[('employee_id.active','=',True)]</field>
<field name="context">{'hide_employee_name': 1}</field>
</record>
</odoo>
+10 -47
View File
@@ -28,6 +28,7 @@
<group expand="0" string="Group By">
<filter name="group_employee" string="Employee" context="{'group_by':'employee_id'}"/>
<filter name="group_type" string="Type" context="{'group_by':'holiday_status_id'}"/>
<filter name="group_company" string="Company" context="{'group_by':'company_id'}" groups="base.group_multi_company"/>
<separator/>
<filter name="group_date_from" string="Start Date" context="{'group_by':'date_from'}"/>
</group>
@@ -52,58 +53,20 @@
</field>
</record>
<!-- TODO: See if we need to keep this -->
<record id="hr_leave_report_kanban" model="ir.ui.view">
<field name="name">report.hr.holidays.report.leave_all.kanban</field>
<record id="hr_leave_report_pivot" model="ir.ui.view">
<field name="name">report.hr.holidays.report.leave_all.pivot</field>
<field name="model">hr.leave.report</field>
<field name="arch" type="xml">
<kanban class="o_kanban_mobile" create="0">
<field name="employee_id"/>
<pivot>
<field name="employee_id" decoration-muted="not active_employee"/>
<field name="number_of_days" type="measure"/>
<field name="leave_type"/>
<field name="date_from"/>
<field name="date_to"/>
<field name="state"/>
<field name="name"/>
<field name="number_of_days"/>
<templates>
<t t-name="kanban-box">
<div t-attf-class="oe_kanban_global_click">
<div>
<span>
<img t-att-src="kanban_image('hr.employee', 'avatar_128', record.employee_id.raw_value)" t-att-title="record.employee_id.value" t-att-alt="record.employee_id.value" class="oe_kanban_avatar o_image_40_cover float-left mr4"/>
</span>
<span>
<div>
<strong class="o_kanban_record_title"><t t-esc="record.employee_id.value"/></strong>
<span class="float-right">
<field name="state" widget="label_selection" options="{'classes': {'draft': 'default', 'validate': 'success','confirm': 'default', 'cancel': 'danger'}}"/>
</span>
</div>
<div class="text-muted o_kanban_record_subtitle">
<t t-esc="record.name.value"/>
</div>
</span>
</div>
<hr class="mt4 mb8"/>
<div class="o_kanban_record_bottom mt8 mb4">
<div t-attf-class="oe_kanban_bottom_left #{record.date_from.value ? 'mt8 mb4': ''}">
<table class="text-right" t-if="record.date_from.value">
<tr>
<td style="padding-bottom:4px"><small class="text-muted">from</small></td>
<td style="padding:0 0 4px 4px"><t t-esc="record.date_from.value"/></td>
</tr>
<tr>
<td><small class="text-muted">to</small></td>
<td style="padding-left:4px"><t t-esc="record.date_to.value"/></td>
</tr>
</table>
</div>
<div t-attf-class="oe_kanban_bottom_right #{record.date_from.value ? 'mt8': ''}">
<span class="badge badge-pill"><t t-esc="record.number_of_days.value"/> days</span>
</div>
</div>
</div>
</t>
</templates>
</kanban>
<field name="active_employee" invisible="1"/>
</pivot>
</field>
</record>
@@ -12,7 +12,7 @@
</record>
<record id="group_hr_holidays_user" model="res.groups">
<field name="name">All Approver</field>
<field name="name">Time Off Officer</field>
<field name="category_id" ref="base.module_category_human_resources_time_off"/>
<field name="implied_ids" eval="[(4, ref('hr_holidays.group_hr_holidays_responsible'))]"/>
</record>
@@ -161,14 +161,15 @@
<field name="name">Allocations: base.group_user create/write</field>
<field name="model_id" ref="model_hr_leave_allocation"/>
<field name="domain_force">[
('holiday_status_id.allocation_type', '=', 'fixed_allocation'),
('holiday_status_id.requires_allocation', '=', 'yes'),
('holiday_status_id.employee_requests', '=', 'yes'),
('holiday_type', '=', 'employee'),
'|',
'&amp;',
('employee_id.user_id', '=', user.id),
('state', 'not in', ['validate', 'validate1']),
('state', '!=', 'validate'),
'&amp;',
('validation_type', 'in', ['manager', 'both']),
('validation_type', 'in', ['officer', 'set']),
('employee_id.leave_manager_id', '=', user.id),
]</field>
<field name="perm_read" eval="False"/>
@@ -16,3 +16,8 @@ access_calendar_attendee_hr_user,calendar.attendee.hr.user,calendar.model_calend
access_mail_activity_type_holidays_manager,mail.activity.type.holidays.manager,mail.model_mail_activity_type,hr_holidays.group_hr_holidays_manager,1,1,1,1
access_hr_holidays_summary_employee,access.hr.holidays.summary.employee,model_hr_holidays_summary_employee,hr_holidays.group_hr_holidays_user,1,1,1,0
access_hr_leave_report_calendar,access_hr_leave_report_calendar,model_hr_leave_report_calendar,base.group_user,1,0,0,0
access_hr_leave_employee_type_report,access_hr_leave_employee_type_report,model_hr_leave_employee_type_report,base.group_user,1,0,0,0
access_hr_leave_accrual_plan_user,hr.leave.accrual.plan.user,model_hr_leave_accrual_plan,hr_holidays.group_hr_holidays_user,1,0,0,0
access_hr_leave_accrual_level_user,hr.leave.accrual.level.user,model_hr_leave_accrual_level,hr_holidays.group_hr_holidays_user,1,0,0,0
access_hr_leave_accrual_plan_manager,hr.leave.accrual.plan.manager,model_hr_leave_accrual_plan,hr_holidays.group_hr_holidays_manager,1,1,1,1
access_hr_leave_accrual_level_manager,hr.leave.accrual.level.manager,model_hr_leave_accrual_level,hr_holidays.group_hr_holidays_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
16 access_mail_activity_type_holidays_manager mail.activity.type.holidays.manager mail.model_mail_activity_type hr_holidays.group_hr_holidays_manager 1 1 1 1
17 access_hr_holidays_summary_employee access.hr.holidays.summary.employee model_hr_holidays_summary_employee hr_holidays.group_hr_holidays_user 1 1 1 0
18 access_hr_leave_report_calendar access_hr_leave_report_calendar model_hr_leave_report_calendar base.group_user 1 0 0 0
19 access_hr_leave_employee_type_report access_hr_leave_employee_type_report model_hr_leave_employee_type_report base.group_user 1 0 0 0
20 access_hr_leave_accrual_plan_user hr.leave.accrual.plan.user model_hr_leave_accrual_plan hr_holidays.group_hr_holidays_user 1 0 0 0
21 access_hr_leave_accrual_level_user hr.leave.accrual.level.user model_hr_leave_accrual_level hr_holidays.group_hr_holidays_user 1 0 0 0
22 access_hr_leave_accrual_plan_manager hr.leave.accrual.plan.manager model_hr_leave_accrual_plan hr_holidays.group_hr_holidays_manager 1 1 1 1
23 access_hr_leave_accrual_level_manager hr.leave.accrual.level.manager model_hr_leave_accrual_level hr_holidays.group_hr_holidays_manager 1 1 1 1
@@ -0,0 +1,21 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<g id="Annual_Time_Off" data-name="Annual Time Off">
<g>
<rect x="13.34" y="24.39" width="73.32" height="59.4" rx="6.37" stroke-width="5" stroke="#875b7b" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
<g>
<path d="M30.5,32.12V16.21a2.5,2.5,0,0,0-5,0V32.12a2.5,2.5,0,0,0,5,0Z" fill="#875b7b"/>
<path d="M52.5,32.12V16.21a2.5,2.5,0,0,0-5,0V32.12a2.5,2.5,0,1,0,5,0Z" fill="#875b7b"/>
<path d="M74.5,32.12V16.21a2.5,2.5,0,0,0-5,0V32.12a2.5,2.5,0,1,0,5,0Z" fill="#875b7b"/>
</g>
<path d="M13.35,43.32h73.3c3.22,0,3.22-5,0-5H13.35c-3.22,0-3.22,5,0,5Z" fill="#875b7b"/>
<g>
<polyline points="28.57 55.08 36.01 55.08 36.01 69.96 28.56 69.96" fill="none" stroke="#875b7b" stroke-linecap="round" stroke-linejoin="round" stroke-width="4"/>
<g>
<line x1="28.56" y1="62.47" x2="35.54" y2="62.47" fill="none" stroke="#875b7b" stroke-linecap="round" stroke-linejoin="round" stroke-width="4"/>
<polyline points="46.29 62.47 53.73 62.47 53.73 69.96 46.29 69.96 46.29 55.08 53.74 55.08" fill="none" stroke="#875b7b" stroke-linecap="round" stroke-linejoin="round" stroke-width="4"/>
<polyline points="71.44 55.08 64 55.08 64 62.47 71.44 62.47 71.44 69.96 64 69.96" fill="none" stroke="#875b7b" stroke-linecap="round" stroke-linejoin="round" stroke-width="4"/>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

@@ -0,0 +1,16 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<g id="Annual_Time_Off" data-name="Annual Time Off">
<g>
<rect x="13.34" y="24.39" width="73.32" height="59.4" rx="6.37" stroke-width="5" stroke="#875b7b" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
<g>
<path d="M30.5,32.12V16.21a2.5,2.5,0,0,0-5,0V32.12a2.5,2.5,0,0,0,5,0Z" fill="#875b7b"/>
<path d="M52.5,32.12V16.21a2.5,2.5,0,0,0-5,0V32.12a2.5,2.5,0,1,0,5,0Z" fill="#875b7b"/>
<path d="M74.5,32.12V16.21a2.5,2.5,0,0,0-5,0V32.12a2.5,2.5,0,1,0,5,0Z" fill="#875b7b"/>
</g>
<g>
<path d="M86.65,40.82H13.35v36.6l3.32,6.21,63.63.16,7.62-3.5ZM38,70a2,2,0,0,1-2,2H28.56a2,2,0,0,1,0-4H34V64.47H28.56a2,2,0,0,1,0-4H34V57.08H28.57a2,2,0,0,1,0-4H36a2,2,0,0,1,2,2Zm15.72-9.49a2,2,0,0,1,2,2V70a2,2,0,0,1-2,2H46.29a2,2,0,0,1-2-2V55.08a2,2,0,0,1,2-2h7.45a2,2,0,0,1,0,4H48.29v3.39Zm17.71,0a2,2,0,0,1,2,2V70a2,2,0,0,1-2,2H64a2,2,0,1,1,0-4h5.44V64.47H64a2,2,0,0,1-2-2V55.08a2,2,0,0,1,2-2h7.44a2,2,0,0,1,0,4H66v3.39Z" fill="#875b7b"/>
<rect x="48.29" y="64.47" width="3.44" height="3.49" fill="#875b7b"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,20 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<g id="Annual_Time_Off" data-name="Annual Time Off">
<g>
<g>
<path d="M30.5,32.12V16.21a2.5,2.5,0,0,0-5,0V32.12a2.5,2.5,0,0,0,5,0Z" fill="#875b7b"/>
<path d="M52.5,32.12V16.21a2.5,2.5,0,0,0-5,0V32.12a2.5,2.5,0,1,0,5,0Z" fill="#875b7b"/>
<path d="M74.5,32.12V16.21a2.5,2.5,0,0,0-5,0V32.12a2.5,2.5,0,1,0,5,0Z" fill="#875b7b"/>
</g>
<g>
<path d="M36,72H28.56a2,2,0,0,1,0-4H34V57.08H28.57a2,2,0,0,1,0-4H36a2,2,0,0,1,2,2V70A2,2,0,0,1,36,72Z" fill="#875b7b"/>
<g>
<path d="M35.54,64.47h-7a2,2,0,0,1,0-4h7a2,2,0,0,1,0,4Z" fill="#875b7b"/>
<path d="M53.73,72H46.29a2,2,0,0,1-2-2V55.08a2,2,0,0,1,2-2h7.45a2,2,0,0,1,0,4H48.29v3.39h5.44a2,2,0,0,1,2,2V70A2,2,0,0,1,53.73,72Zm-5.44-4h3.44V64.47H48.29Z" fill="#875b7b"/>
<path d="M71.44,72H64a2,2,0,1,1,0-4h5.44V64.47H64a2,2,0,0,1-2-2V55.08a2,2,0,0,1,2-2h7.44a2,2,0,0,1,0,4H66v3.39h5.44a2,2,0,0,1,2,2V70A2,2,0,0,1,71.44,72Z" fill="#875b7b"/>
</g>
</g>
<path d="M80.29,21.89H79.5V32.12a7.53,7.53,0,0,1-7.27,7.49h-.32a7.56,7.56,0,0,1-7.41-7.5V21.89h-7V32.12a7.53,7.53,0,0,1-7.27,7.49h-.32a7.56,7.56,0,0,1-7.41-7.5V21.89h-7V32.12a7.53,7.53,0,0,1-7.27,7.49h-.32a7.56,7.56,0,0,1-7.41-7.5V21.89h-.79a8.88,8.88,0,0,0-8.87,8.87V77.42a8.88,8.88,0,0,0,8.87,8.87H80.29a8.88,8.88,0,0,0,8.87-8.87V30.76A8.88,8.88,0,0,0,80.29,21.89Zm3.87,55.53a3.87,3.87,0,0,1-3.87,3.87H19.71a3.87,3.87,0,0,1-3.87-3.87V43.32H84.16Z" fill="#875b7b"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@@ -0,0 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<g id="Compensatory_Time_Off" data-name="Compensatory Time Off">
<g>
<line x1="23.12" y1="26.88" x2="76.88" y2="26.88" fill="none" stroke="#875b7b" stroke-linecap="round" stroke-linejoin="round" stroke-width="5"/>
<line x1="23.12" y1="84.74" x2="76.88" y2="84.74" fill="none" stroke="#875b7b" stroke-linecap="round" stroke-linejoin="round" stroke-width="5"/>
<line x1="50" y1="15.26" x2="50" y2="84.66" fill="none" stroke="#875b7b" stroke-linecap="round" stroke-linejoin="round" stroke-width="5"/>
<line x1="23.45" y1="27.4" x2="11.73" y2="58.24" fill="none" stroke="#875b7b" stroke-linecap="round" stroke-linejoin="round" stroke-width="5"/>
<line x1="35.24" y1="57.87" x2="23.52" y2="27.03" fill="none" stroke="#875b7b" stroke-linecap="round" stroke-linejoin="round" stroke-width="5"/>
<path d="M23.92,75.75a16.87,16.87,0,0,0,16.55-13.6,2.67,2.67,0,0,0-2.6-3.2H10a2.67,2.67,0,0,0-2.6,3.2A16.87,16.87,0,0,0,23.92,75.75Z" fill="none" stroke="#875b7b" stroke-miterlimit="10" stroke-width="5"/>
<line x1="75.62" y1="27.4" x2="63.9" y2="58.24" fill="none" stroke="#875b7b" stroke-linecap="round" stroke-linejoin="round" stroke-width="5"/>
<line x1="87.41" y1="57.87" x2="75.69" y2="27.03" fill="none" stroke="#875b7b" stroke-linecap="round" stroke-linejoin="round" stroke-width="5"/>
<path d="M76.08,75.75a16.87,16.87,0,0,0,16.56-13.6A2.67,2.67,0,0,0,90,59H62.13a2.67,2.67,0,0,0-2.6,3.2A16.87,16.87,0,0,0,76.08,75.75Z" fill="none" stroke="#875b7b" stroke-miterlimit="10" stroke-width="5"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

@@ -0,0 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<g id="Compensatory_Time_Off" data-name="Compensatory Time Off">
<g>
<line x1="23.12" y1="26.88" x2="76.88" y2="26.88" fill="none" stroke="#875b7b" stroke-linecap="round" stroke-linejoin="round" stroke-width="5"/>
<line x1="23.12" y1="84.74" x2="76.88" y2="84.74" fill="none" stroke="#875b7b" stroke-linecap="round" stroke-linejoin="round" stroke-width="5"/>
<line x1="50" y1="15.26" x2="50" y2="84.66" fill="none" stroke="#875b7b" stroke-linecap="round" stroke-linejoin="round" stroke-width="5"/>
<line x1="23.45" y1="27.4" x2="11.73" y2="61.24" fill="none" stroke="#875b7b" stroke-linecap="round" stroke-linejoin="round" stroke-width="5"/>
<line x1="35.24" y1="60.87" x2="23.52" y2="27.03" fill="none" stroke="#875b7b" stroke-linecap="round" stroke-linejoin="round" stroke-width="5"/>
<path d="M23.92,75.75a16.87,16.87,0,0,0,16.55-13.6,2.67,2.67,0,0,0-2.6-3.2H10a2.67,2.67,0,0,0-2.6,3.2A16.87,16.87,0,0,0,23.92,75.75Z" fill="#875b7b"/>
<line x1="75.62" y1="27.4" x2="63.9" y2="61.24" fill="none" stroke="#875b7b" stroke-linecap="round" stroke-linejoin="round" stroke-width="5"/>
<line x1="87.41" y1="60.87" x2="75.69" y2="27.03" fill="none" stroke="#875b7b" stroke-linecap="round" stroke-linejoin="round" stroke-width="5"/>
<path d="M76.08,75.75a16.87,16.87,0,0,0,16.56-13.6A2.67,2.67,0,0,0,90,59H62.13a2.67,2.67,0,0,0-2.6,3.2A16.87,16.87,0,0,0,76.08,75.75Z" fill="#875b7b"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<g id="Compensatory_Time_Off" data-name="Compensatory Time Off">
<g>
<polyline points="23.12 66.91 23.12 29.88 76.88 19.88 76.88 59.29" fill="none" stroke="#875b7b" stroke-linecap="round" stroke-linejoin="round" stroke-width="5"/>
<line x1="23.12" y1="84.74" x2="76.88" y2="84.74" fill="none" stroke="#875b7b" stroke-linecap="round" stroke-linejoin="round" stroke-width="5"/>
<line x1="50" y1="15.26" x2="50" y2="84.66" fill="none" stroke="#875b7b" stroke-linecap="round" stroke-linejoin="round" stroke-width="5"/>
<path d="M23.92,75.75a16.87,16.87,0,0,0,16.55-13.6,2.67,2.67,0,0,0-2.6-3.2H10a2.67,2.67,0,0,0-2.6,3.2A16.87,16.87,0,0,0,23.92,75.75Z" fill="#875b7b"/>
<path d="M76.08,65.75a16.87,16.87,0,0,0,16.56-13.6A2.67,2.67,0,0,0,90,49H62.13a2.67,2.67,0,0,0-2.6,3.2A16.87,16.87,0,0,0,76.08,65.75Z" fill="#875b7b"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 940 B

@@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<g id="Credit_Time" data-name="Credit Time">
<g>
<path d="M50,88.84A36.55,36.55,0,1,1,86.55,52.29,36.59,36.59,0,0,1,50,88.84Zm0-68.09A31.55,31.55,0,1,0,81.55,52.29,31.59,31.59,0,0,0,50,20.75Z" fill="#875b7b"/>
<path d="M41.35,16.66h17.3a2.5,2.5,0,0,0,0-5H41.35a2.5,2.5,0,0,0,0,5Z" fill="#875b7b"/>
<path d="M41.35,13.66h17.3a2.5,2.5,0,0,0,0-5H41.35a2.5,2.5,0,0,0,0,5Z" fill="#875b7b"/>
<path d="M70.66,22.86l2.23,2.23a1.92,1.92,0,0,0,.8.52,2.32,2.32,0,0,0,1.93,0,2,2,0,0,0,.81-.52l.39-.51a2.5,2.5,0,0,0,.34-1.26l-.09-.66a2.5,2.5,0,0,0-.64-1.1l-2.24-2.24a1.83,1.83,0,0,0-.8-.51,2.23,2.23,0,0,0-1.93,0,1.83,1.83,0,0,0-.8.51l-.39.51a2.43,2.43,0,0,0-.34,1.26l.08.67a2.52,2.52,0,0,0,.65,1.1Z" fill="#875b7b"/>
<path d="M60.78,36.56a13.49,13.49,0,0,0-8.6-3.94V29.87c0-3.21-5-3.22-5,0V33A11.57,11.57,0,0,0,38.43,43.3a11.44,11.44,0,0,0,8.75,11.9V68.42a8.48,8.48,0,0,1-4.42-2.36,2.18,2.18,0,0,1-.64-1.55V61.46A2.5,2.5,0,0,0,39.64,59h0a2.51,2.51,0,0,0-2.5,2.49v3a7.25,7.25,0,0,0,2.1,5.11,13.49,13.49,0,0,0,8,3.87v1.24c0,3.22,5,3.22,5,0V73.34a11.59,11.59,0,0,0,9.39-10.49,11.46,11.46,0,0,0-9.39-12V37.64a8.5,8.5,0,0,1,5.06,2.45,2.15,2.15,0,0,1,.64,1.55v3a2.49,2.49,0,0,0,2.49,2.51h0a2.51,2.51,0,0,0,2.5-2.49V41.66A7.21,7.21,0,0,0,60.78,36.56ZM45.16,48.49a6.4,6.4,0,0,1,2-10.24V50A6.52,6.52,0,0,1,45.16,48.49Zm11.42,14a6.5,6.5,0,0,1-4.4,5.63V56a6.49,6.49,0,0,1,4.4,6.58Z" fill="#875b7b"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@@ -0,0 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<g id="Credit_Time" data-name="Credit Time">
<path d="M50,88.84A36.55,36.55,0,1,1,86.55,52.29,36.59,36.59,0,0,1,50,88.84Zm0-68.09A31.55,31.55,0,1,0,81.55,52.29,31.59,31.59,0,0,0,50,20.75Z" fill="#875b7b"/>
<path d="M41.35,16.66h17.3a2.5,2.5,0,0,0,0-5H41.35a2.5,2.5,0,0,0,0,5Z" fill="#875b7b"/>
<path d="M41.35,13.66h17.3a2.5,2.5,0,0,0,0-5H41.35a2.5,2.5,0,0,0,0,5Z" fill="#875b7b"/>
<path d="M70.66,22.86l2.23,2.23a1.92,1.92,0,0,0,.8.52,2.32,2.32,0,0,0,1.93,0,2,2,0,0,0,.81-.52l.39-.51a2.5,2.5,0,0,0,.34-1.26l-.09-.66a2.5,2.5,0,0,0-.64-1.1l-2.24-2.24a1.83,1.83,0,0,0-.8-.51,2.23,2.23,0,0,0-1.93,0,1.83,1.83,0,0,0-.8.51l-.39.51a2.43,2.43,0,0,0-.34,1.26l.08.67a2.52,2.52,0,0,0,.65,1.1Z" fill="#875b7b"/>
<g>
<path d="M43.42,43.62A6.5,6.5,0,0,0,47.18,50V38.25A6.47,6.47,0,0,0,43.42,43.62Z" fill="#875b7b"/>
<path d="M52.18,68.16a6.44,6.44,0,0,0,0-12.21Z" fill="#875b7b"/>
<path d="M50,24.51A27.49,27.49,0,1,0,77.49,52,27.49,27.49,0,0,0,50,24.51Zm12.87,20.2a2.51,2.51,0,0,1-2.5,2.49h0a2.49,2.49,0,0,1-2.49-2.51v-3a2.15,2.15,0,0,0-.64-1.55,8.5,8.5,0,0,0-5.06-2.45V50.81a11.45,11.45,0,0,1,0,22.53v1.37c0,3.22-5,3.22-5,0V73.47a13.49,13.49,0,0,1-8-3.87,7.25,7.25,0,0,1-2.1-5.11v-3A2.51,2.51,0,0,1,39.63,59h0a2.5,2.5,0,0,1,2.49,2.51v3.05a2.18,2.18,0,0,0,.64,1.55,8.48,8.48,0,0,0,4.42,2.36V55.2a11.43,11.43,0,0,1,0-22.23v-3.1c0-3.22,5-3.21,5,0v2.75a13.49,13.49,0,0,1,8.6,3.94,7.21,7.21,0,0,1,2.1,5.1Z" fill="#875b7b"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<g id="Extra_Time_Off" data-name="Extra Time Off">
<g>
<path d="M56.9,12.07a2.73,2.73,0,0,1,2.79,2.63l.46,9.13A2.77,2.77,0,0,0,64.5,26L72,20.75a2.71,2.71,0,0,1,1.58-.52A2.78,2.78,0,0,1,76,24.28l-4.16,8.13a2.77,2.77,0,0,0,2.45,4h.24l9.11-.76.26,0a2.77,2.77,0,0,1,1.47,5.1l-7.67,5a2.77,2.77,0,0,0,.33,4.83l8.26,3.89a2.76,2.76,0,0,1-1,5.26l-9.13.46A2.77,2.77,0,0,0,74,64.5L79.25,72A2.78,2.78,0,0,1,77,76.36,2.82,2.82,0,0,1,75.72,76l-8.13-4.16a2.82,2.82,0,0,0-1.26-.31,2.77,2.77,0,0,0-2.76,3l.76,9.11a2.74,2.74,0,0,1-2.79,3,2.67,2.67,0,0,1-2.29-1.27l-5-7.67A2.72,2.72,0,0,0,52,76.48a2.75,2.75,0,0,0-2.51,1.59l-3.89,8.26a2.66,2.66,0,0,1-2.47,1.6,2.73,2.73,0,0,1-2.79-2.63l-.46-9.13A2.77,2.77,0,0,0,35.5,74L28,79.25a2.71,2.71,0,0,1-1.58.52,2.78,2.78,0,0,1-2.46-4l4.16-8.13a2.77,2.77,0,0,0-2.45-4h-.24l-9.11.76-.26,0a2.77,2.77,0,0,1-1.47-5.1l7.67-5a2.77,2.77,0,0,0-.33-4.83l-8.26-3.89a2.76,2.76,0,0,1,1-5.26l9.13-.46A2.77,2.77,0,0,0,26,35.5L20.75,28A2.78,2.78,0,0,1,23,23.64a2.82,2.82,0,0,1,1.28.32l8.13,4.16a2.76,2.76,0,0,0,4-2.69l-.76-9.11a2.74,2.74,0,0,1,2.79-3,2.67,2.67,0,0,1,2.29,1.27l5,7.67A2.72,2.72,0,0,0,48,23.52a2.75,2.75,0,0,0,2.51-1.59l3.89-8.26a2.66,2.66,0,0,1,2.47-1.6m0-4h0A6.67,6.67,0,0,0,50.81,12l-3,6.26L44.1,12.41a6.69,6.69,0,0,0-5.64-3.09,6.88,6.88,0,0,0-5.05,2.2,6.67,6.67,0,0,0-1.73,5.14l.58,6.89L26.1,20.4a6.67,6.67,0,0,0-3.1-.76,6.78,6.78,0,0,0-6,3.67,6.65,6.65,0,0,0,.46,7L21.41,36l-6.91.35A6.77,6.77,0,0,0,12,49.19l6.26,3L12.41,55.9a6.77,6.77,0,0,0,3.65,12.45l.6,0,6.89-.58L20.4,73.9a6.68,6.68,0,0,0,.22,6.58,6.85,6.85,0,0,0,5.8,3.29,6.75,6.75,0,0,0,3.86-1.23l5.68-4,.35,6.91A6.76,6.76,0,0,0,49.19,88l3-6.26,3.76,5.81a6.69,6.69,0,0,0,5.64,3.09,6.88,6.88,0,0,0,5.05-2.2,6.68,6.68,0,0,0,1.73-5.14l-.58-6.89L73.9,79.6a6.67,6.67,0,0,0,3.1.76,6.78,6.78,0,0,0,6-3.67,6.65,6.65,0,0,0-.46-7l-4-5.68,6.91-.35A6.77,6.77,0,0,0,88,50.81l-6.26-3,5.81-3.76a6.77,6.77,0,0,0-3.65-12.45l-.6,0-6.89.58L79.6,26.1a6.68,6.68,0,0,0-.22-6.58,6.85,6.85,0,0,0-5.8-3.29,6.75,6.75,0,0,0-3.86,1.23L64,21.41l-.35-6.91A6.71,6.71,0,0,0,56.9,8.07Z" fill="#875b7b"/>
<g>
<path d="M34.89,56.11a1.26,1.26,0,0,0,.35-1.73A1.28,1.28,0,0,0,33.5,54l-2.75,1.84a1.23,1.23,0,0,0-.41.49,1.08,1.08,0,0,0-.06,1,1,1,0,0,0,.11.2s0,0,0,0l5.13,7.64a1.17,1.17,0,0,0,1.61.37,1.73,1.73,0,0,0,.24-.1L40,63.75a1.25,1.25,0,0,0-1.39-2.08l-1.72,1.15L35.66,61l1.69-1.13A1.25,1.25,0,0,0,36,57.75l-1.68,1.13-1.09-1.62Z" fill="#875b7b"/>
<path d="M41.05,49.06a1.51,1.51,0,0,0-1,1.85v0h0a1.5,1.5,0,0,0-1.84,2.36l3.22,2.52,1.1,3.93a1.5,1.5,0,0,0,1.85,1,1.48,1.48,0,0,0,1-1.85h0a1.5,1.5,0,0,0,1.76.06,1.32,1.32,0,0,0,.35-.32,1.51,1.51,0,0,0-.26-2.11L44,54,42.9,50.1A1.51,1.51,0,0,0,41.05,49.06Z" fill="#875b7b"/>
<path d="M48.13,44.21l-2.79,1.87A1.26,1.26,0,0,0,45,47.81a1.28,1.28,0,0,0,1.74.35l.31-.22,4.46,6.65a1.25,1.25,0,0,0,2.08-1.39l-4.46-6.65.39-.26a1.25,1.25,0,0,0-1.39-2.08Z" fill="#875b7b"/>
<path d="M58.44,45.63l.82-.55a1.29,1.29,0,0,0,.53-.8,1.25,1.25,0,0,0-.19-.94l-.07-.07-2.36-3.51a1.26,1.26,0,0,0-1.73-.34l-2.58,1.73a1.22,1.22,0,0,0-.63,1.63s0,0,0,0l.08.15,5.11,7.61a1.25,1.25,0,0,0,2.08-1.39l-.42-.63,1.74.65a1.23,1.23,0,0,0,1.6-.74,1.23,1.23,0,0,0-.73-1.6Zm-1.63-1.92-.67.45-1-1.52.66-.45Z" fill="#875b7b"/>
<path d="M62.5,34.6l-2.58,1.73a1.27,1.27,0,0,0-.48.66,1,1,0,0,0,0,.62.44.44,0,0,0,0,.1.76.76,0,0,0,0,.11.85.85,0,0,0,.13.25l5.1,7.6a1.25,1.25,0,0,0,2.08-1.39L64.83,41.4l.72-.49,2,2.93a1.25,1.25,0,0,0,2.08-1.4l-5.17-7.7A1.21,1.21,0,0,0,62.5,34.6Zm.93,4.72-1.08-1.61.73-.49,1.08,1.62Z" fill="#875b7b"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<g id="Extra_Time_Off" data-name="Extra Time Off">
<g>
<path d="M62.35,37.71l1.08,1.61.73-.48-1.08-1.62Z" fill="#875b7b"/>
<path d="M55.13,42.64c.34.5.68,1,1,1.52l.67-.45-1-1.52Z" fill="#875b7b"/>
<path d="M86.33,54.43l-8.26-3.89a2.77,2.77,0,0,1-.33-4.83l7.67-5a2.76,2.76,0,0,0-1.73-5.08l-9.11.76a2.77,2.77,0,0,1-2.69-4L76,24.28a2.77,2.77,0,0,0-4-3.53L64.5,26a2.77,2.77,0,0,1-4.35-2.13l-.46-9.13a2.76,2.76,0,0,0-5.26-1l-3.89,8.26a2.77,2.77,0,0,1-4.83.33l-5-7.67a2.76,2.76,0,0,0-5.08,1.73l.76,9.11a2.77,2.77,0,0,1-4,2.69L24.28,24a2.77,2.77,0,0,0-3.53,4L26,35.5a2.77,2.77,0,0,1-2.13,4.35l-9.13.46a2.76,2.76,0,0,0-1,5.26l8.26,3.89a2.77,2.77,0,0,1,.33,4.83l-7.67,5a2.76,2.76,0,0,0,1.73,5.08l9.11-.76a2.77,2.77,0,0,1,2.69,4L24,75.72a2.77,2.77,0,0,0,4,3.53L35.5,74a2.77,2.77,0,0,1,4.35,2.13l.46,9.13a2.76,2.76,0,0,0,5.26,1l3.89-8.26a2.77,2.77,0,0,1,4.83-.33l5,7.67a2.76,2.76,0,0,0,5.08-1.73l-.76-9.11a2.77,2.77,0,0,1,4-2.69L75.72,76a2.77,2.77,0,0,0,3.53-4L74,64.5a2.77,2.77,0,0,1,2.13-4.35l9.13-.46A2.76,2.76,0,0,0,86.33,54.43ZM40,63.75l-2.64,1.77a1.73,1.73,0,0,1-.24.1,1.17,1.17,0,0,1-1.61-.37L30.4,57.61s0,0,0,0a1,1,0,0,1-.11-.2,1.08,1.08,0,0,1,.06-1,1.23,1.23,0,0,1,.41-.49L33.5,54a1.28,1.28,0,0,1,1.74.34,1.26,1.26,0,0,1-.35,1.73l-1.71,1.15,1.09,1.62L36,57.75a1.25,1.25,0,0,1,1.4,2.08L35.66,61l1.25,1.86,1.72-1.15A1.25,1.25,0,0,1,40,63.75Zm7.47-5.09a1.52,1.52,0,0,1-2.11.26h0a1.5,1.5,0,1,1-2.89.81l-1.1-3.93-3.22-2.52A1.5,1.5,0,0,1,40,50.92h0v0a1.5,1.5,0,1,1,2.89-.81L44,54l3.23,2.51A1.51,1.51,0,0,1,47.49,58.66Zm4-4.07c-1.49-2.21-3-4.43-4.46-6.65l-.31.22A1.28,1.28,0,0,1,45,47.81a1.26,1.26,0,0,1,.34-1.73l2.79-1.87a1.25,1.25,0,0,1,1.39,2.08l-.39.26c1.48,2.22,3,4.43,4.46,6.65A1.25,1.25,0,0,1,51.51,54.59Zm10.92-6.14a1.23,1.23,0,0,1-1.6.74l-1.74-.65.42.63a1.25,1.25,0,0,1-2.08,1.39L52.32,43l-.08-.15v0a1.22,1.22,0,0,1,.63-1.63l2.58-1.73a1.26,1.26,0,0,1,1.73.34l2.36,3.51.07.07a1.25,1.25,0,0,1,.19.94,1.29,1.29,0,0,1-.53.8l-.82.55,3.26,1.22A1.23,1.23,0,0,1,62.43,48.45Zm5.08-4.61-2-2.93-.72.49,1.93,2.88a1.25,1.25,0,0,1-2.08,1.39l-5.1-7.6a.85.85,0,0,1-.13-.25.76.76,0,0,1,0-.11.44.44,0,0,1,0-.1,1,1,0,0,1,0-.62,1.27,1.27,0,0,1,.48-.66L62.5,34.6a1.21,1.21,0,0,1,1.92.14l5.17,7.7A1.25,1.25,0,0,1,67.51,43.84Z" fill="#875b7b"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

@@ -0,0 +1,17 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<g id="Maternity_Time_Off" data-name="Maternity Time Off">
<g>
<g>
<path d="M63,11.81H53.58V38.42H20.75v2.35a24,24,0,0,0,24,24H63a24,24,0,0,0,24-24v-5A24,24,0,0,0,63,11.81Z" fill="none" stroke="#875b7b" stroke-linecap="round" stroke-linejoin="round" stroke-width="5"/>
<path d="M46.1,40.92H86.75c3.21,0,3.22-5,0-5H46.1c-3.22,0-3.23,5,0,5Z" fill="#875b7b"/>
<path d="M23.25,38.52V23.4a2.53,2.53,0,0,0-2.5-2.5H13a2.5,2.5,0,0,0,0,5h7.71l-2.5-2.5V38.52a2.5,2.5,0,0,0,5,0Z" fill="#875b7b"/>
<circle cx="28.65" cy="79.82" r="8.37" fill="none" stroke="#875b7b" stroke-linecap="round" stroke-linejoin="round" stroke-width="5"/>
<circle cx="78.1" cy="79.82" r="8.37" fill="none" stroke="#875b7b" stroke-linecap="round" stroke-linejoin="round" stroke-width="5"/>
<path d="M80.64,71.45V59.26a2.5,2.5,0,0,0-5,0V71.45a2.5,2.5,0,0,0,5,0Z" fill="#875b7b"/>
<path d="M31.25,71.45V59.68a2.5,2.5,0,0,0-5,0V71.45a2.5,2.5,0,0,0,5,0Z" fill="#875b7b"/>
</g>
<path d="M28.66,82.27c3.22,0,3.22-5,0-5s-3.22,5,0,5Z" fill="#875b7b"/>
<path d="M78.08,82.27c3.21,0,3.22-5,0-5s-3.23,5,0,5Z" fill="#875b7b"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<g id="Maternity_Time_Off" data-name="Maternity Time Off">
<g>
<path d="M89.46,40.77v-5A26.51,26.51,0,0,0,63,9.31H53.58a2.5,2.5,0,0,0-2.5,2.5V35.92H23.25V23.4a2.53,2.53,0,0,0-2.5-2.5H13a2.5,2.5,0,0,0,0,5h5.21V40.77a26.41,26.41,0,0,0,8,18.94v9.51a10.89,10.89,0,1,0,5,0V63.55a26.38,26.38,0,0,0,13.48,3.7H63A26.25,26.25,0,0,0,75.64,64v5.22a10.87,10.87,0,1,0,5,0V60.48A26.41,26.41,0,0,0,89.46,40.77ZM63,14.31A21.49,21.49,0,0,1,84.46,35.79v.13H56.08V14.31ZM44.73,62.25A21.5,21.5,0,0,1,23.25,40.92h61.2a21.39,21.39,0,0,1-7.64,16.26,2.89,2.89,0,0,0-.47.38A21.33,21.33,0,0,1,63,62.25Z" fill="#875b7b"/>
<polygon points="21.36 39.59 22.89 51.45 32.66 62.33 55.54 65.12 78.42 60.8 86.94 47.4 86.94 37.63 21.36 39.59" fill="#875b7b"/>
<line x1="54.7" y1="36.38" x2="69.77" y2="13.5" fill="#875b7b" stroke="#875b7b" stroke-miterlimit="10" stroke-width="5"/>
<path d="M28.66,82.27c3.22,0,3.22-5,0-5s-3.22,5,0,5Z" fill="#875b7b"/>
<path d="M78.08,82.27c3.21,0,3.22-5,0-5s-3.23,5,0,5Z" fill="#875b7b"/>
</g>
<line x1="85.12" y1="24.8" x2="54.7" y2="36.38" fill="#875b7b" stroke="#875b7b" stroke-miterlimit="10" stroke-width="5"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1,19 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<g id="Maternity_Time_Off" data-name="Maternity Time Off">
<g>
<g>
<path d="M23.25,38.52V23.4a2.53,2.53,0,0,0-2.5-2.5H13a2.5,2.5,0,0,0,0,5h7.71l-2.5-2.5V38.52a2.5,2.5,0,0,0,5,0Z" fill="#875b7b"/>
<path d="M28.65,90.69A10.87,10.87,0,1,1,39.53,79.82,10.89,10.89,0,0,1,28.65,90.69Zm0-16.74a5.87,5.87,0,1,0,5.88,5.87A5.87,5.87,0,0,0,28.65,74Z" fill="#875b7b"/>
<path d="M78.1,90.69A10.87,10.87,0,1,1,89,79.82,10.89,10.89,0,0,1,78.1,90.69ZM78.1,74A5.87,5.87,0,1,0,84,79.82,5.88,5.88,0,0,0,78.1,74Z" fill="#875b7b"/>
<path d="M80.64,71.45V59.26a2.5,2.5,0,0,0-5,0V71.45a2.5,2.5,0,0,0,5,0Z" fill="#875b7b"/>
<path d="M31.25,71.45V59.68a2.5,2.5,0,0,0-5,0V71.45a2.5,2.5,0,0,0,5,0Z" fill="#875b7b"/>
<g>
<path d="M63,14.31H56.08V35.92H84.46v-.13A21.49,21.49,0,0,0,63,14.31Z" fill="none"/>
<path d="M89.46,35.79A26.51,26.51,0,0,0,63,9.31H53.58a2.5,2.5,0,0,0-2.5,2.5V35.92H20.75a2.5,2.5,0,0,0-2.5,2.5v2.35a26.36,26.36,0,0,0,4.39,14.59,7.65,7.65,0,0,1,5.88-3.17h.32a7.55,7.55,0,0,1,7.41,7.5v6.17a26.41,26.41,0,0,0,8.48,1.4H63a26.13,26.13,0,0,0,7.65-1.14V59.26a7.54,7.54,0,0,1,7.28-7.49h.32a7.49,7.49,0,0,1,6.53,4,26.31,26.31,0,0,0,4.69-15Zm-5,.13H56.08V14.31H63A21.49,21.49,0,0,1,84.46,35.79Z" fill="#875b7b"/>
</g>
</g>
<path d="M28.66,82.27c3.22,0,3.22-5,0-5s-3.22,5,0,5Z" fill="#875b7b"/>
<path d="M78.08,82.27c3.21,0,3.22-5,0-5s-3.23,5,0,5Z" fill="#875b7b"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@@ -0,0 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<g id="Paid_Time_Off" data-name="Paid Time Off">
<g>
<path d="M21.46,61.83a3,3,0,0,1,4.24-.27L39.5,73.7H78.9a3,3,0,0,1,0,6H74.37v4.81a3,3,0,0,1-6,0V79.7H45.48v4.81a3,3,0,0,1-6,0V79.7H38.37a3,3,0,0,1-2-.75L21.73,66.07A3,3,0,0,1,21.46,61.83Z" fill="#875b7b"/>
<g>
<rect x="46.5" y="67.63" width="6" height="0.07" fill="#875b7b"/>
<path d="M78.87,41.86c0-.18-.09-.36-.09-.54,0-32.94-58-32.94-58,0a5.58,5.58,0,0,1-.1.59c-.2,2.4,2.94,3.29,4.29,1.3,2.45-3.62,10.12-3.71,12.86-.29.57.71,1.84.95,2.26.14,1.88-3.62,17.81-3.54,18.69,0,.21.88,1.66.59,2.23-.11,2.83-3.48,10.92-3.39,13.51.24C76,45.18,79.12,44.25,78.87,41.86Z" fill="none" stroke="#875b7b" stroke-miterlimit="10" stroke-width="5"/>
<path d="M46.5,40.62v27h6v-27A23.17,23.17,0,0,0,46.5,40.62Z" fill="#875b7b"/>
<path d="M52.5,11.6a3.08,3.08,0,0,0-2.41-3.06,3,3,0,0,0-3.59,3v3h6Z" fill="#875b7b"/>
</g>
</g>
<path d="M40.13,43.06c-4.26-10.68,0-19.27,9.55-26.59" fill="none" stroke="#875b7b" stroke-miterlimit="10" stroke-width="5"/>
<path d="M59.57,43.33c4.26-10.67,0-19.26-9.55-26.58" fill="none" stroke="#875b7b" stroke-miterlimit="10" stroke-width="5"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<g id="Paid_Time_Off" data-name="Paid Time Off">
<g>
<path d="M21.46,61.83a3,3,0,0,1,4.24-.27L39.5,73.7H78.9a3,3,0,0,1,0,6H74.37v4.81a3,3,0,0,1-6,0V79.7H45.48v4.81a3,3,0,0,1-6,0V79.7H38.37a3,3,0,0,1-2-.75L21.73,66.07A3,3,0,0,1,21.46,61.83Z" fill="#875b7b"/>
<g>
<rect x="46.5" y="67.63" width="6" height="0.07" fill="#875b7b"/>
<path d="M78.87,41.86c0-.18-.09-.36-.09-.54,0-32.94-58-32.94-58,0a5.58,5.58,0,0,1-.1.59c-.2,2.4,2.94,3.29,4.29,1.3,2.45-3.62,10.12-3.71,12.86-.29.57.71,1.84.95,2.26.14,1.88-3.62,17.81-3.54,18.69,0,.21.88,1.66.59,2.23-.11,2.83-3.48,10.92-3.39,13.51.24C76,45.18,79.12,44.25,78.87,41.86Z" fill="none" stroke="#875b7b" stroke-miterlimit="10" stroke-width="5"/>
<path d="M46.5,40.62v27h6v-27A23.17,23.17,0,0,0,46.5,40.62Z" fill="#875b7b"/>
<path d="M52.5,11.6a3.08,3.08,0,0,0-2.41-3.06,3,3,0,0,0-3.59,3v3h6Z" fill="#875b7b"/>
</g>
</g>
<path d="M21.77,43.82c-3.22-18.89,18.49-25.37,27.4-24.71C43.73,22.74,39,25.49,39,40.56,29.28,37.67,26.2,40.61,21.77,43.82Z" fill="#875b7b"/>
<path d="M76,43.91c3.32-19.85-17.83-26.36-26.84-24.8,5.44,3.63,9.12,7.5,9.12,22.57C65.64,39.83,69.39,41.65,76,43.91Z" fill="#875b7b"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<g id="Paid_Time_Off" data-name="Paid Time Off">
<g>
<path d="M21.46,61.83a3,3,0,0,1,4.24-.27L39.5,73.7H78.9a3,3,0,0,1,0,6H74.37v4.81a3,3,0,0,1-6,0V79.7H45.48v4.81a3,3,0,0,1-6,0V79.7H38.37a3,3,0,0,1-2-.75L21.73,66.07A3,3,0,0,1,21.46,61.83Z" fill="#875b7b"/>
<g>
<rect x="46.5" y="67.63" width="6" height="0.07" fill="#875b7b"/>
<path d="M78.78,41.32A29.93,29.93,0,0,0,52.5,19V15.6a3.08,3.08,0,0,0-2.41-3.06,3,3,0,0,0-3.59,3V19A29.92,29.92,0,0,0,20.82,41.32a4.28,4.28,0,0,0-.1.59c-.2,2.4,2.94,3.29,4.29,1.3,2.45-3.62,10.12-3.71,12.86-.29a1.53,1.53,0,0,0,2.26.14c3.79-3.52,14.93-3.52,18.69,0A1.5,1.5,0,0,0,61.05,43c2.83-3.48,10.92-3.39,13.51.24,1.39,2,4.56,1,4.31-1.35A3.38,3.38,0,0,0,78.78,41.32Z" fill="#875b7b"/>
<rect x="46.5" y="38.79" width="6" height="28.84" fill="#875b7b"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 920 B

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<g id="Parental_Time_Off" data-name="Parental Time Off">
<path d="M94.89,52.33,87.17,34.94a3.76,3.76,0,0,0-3.86-2.2,5.87,5.87,0,1,0-7.52.21,3.76,3.76,0,0,0-3.11.75,2,2,0,0,0-1.05,1L58.9,47.42A3.74,3.74,0,0,0,58,49.07l-4,1.81A3.74,3.74,0,0,0,52.19,50a4.28,4.28,0,1,0-5.52,0,3.8,3.8,0,0,0-1.89.72l-2.86-2a3.91,3.91,0,0,0-.82-1.22L27.65,34a3.75,3.75,0,0,0-2.81-1.09l-.19-.05a5.88,5.88,0,1,0-7.64,0,3.77,3.77,0,0,0-4.18,2.16L5.11,52.33A3.77,3.77,0,0,0,12,55.38l.4-.89A5.12,5.12,0,0,0,13,56.62V73.69a3.77,3.77,0,0,0,3.77,3.77h0a3.77,3.77,0,0,0,3.77-3.77V59.29h.15V73.67a3.77,3.77,0,1,0,7.54,0V56.41a5.19,5.19,0,0,0,.54-2.26V45.76l7,7a3.74,3.74,0,0,0,3.74.93l3.75,2.67v9.2a3.69,3.69,0,0,0,.48,1.8v7.44a2.75,2.75,0,1,0,5.49,0v-5.5h.11v5.49a2.75,2.75,0,0,0,5.49,0V67.19a3.64,3.64,0,0,0,.4-1.64V56.34l5.6-2.55a3.75,3.75,0,0,0,3.38-1l8.64-8.64.74,4.59a1.61,1.61,0,0,1-.2,1.07L65.7,63.28a1.86,1.86,0,0,0,1.89,2.65h4v7.76a3.77,3.77,0,0,0,7.54,0V65.93h.15v7.74a3.77,3.77,0,0,0,7.54,0V65.93h3.43a1.85,1.85,0,0,0,1.88-2.65l-5.47-9.66a3.78,3.78,0,0,0-.9-1.57l-1.21-2.14a1.62,1.62,0,0,1-.18-1.14l.22-1.07L88,55.38a3.77,3.77,0,0,0,6.89-3.05Z" fill="none" stroke="#875b7b" stroke-linejoin="round" stroke-width="3"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<g id="Parental_Time_Off" data-name="Parental Time Off">
<path d="M94.89,52.33,87.17,34.94a3.76,3.76,0,0,0-3.86-2.2,5.87,5.87,0,1,0-7.52.21,3.76,3.76,0,0,0-3.11.75,2,2,0,0,0-1.05,1L58.9,47.42A3.74,3.74,0,0,0,58,49.07l-4,1.81A3.74,3.74,0,0,0,52.19,50a4.28,4.28,0,1,0-5.52,0,3.8,3.8,0,0,0-1.89.72l-2.86-2a3.91,3.91,0,0,0-.82-1.22L27.65,34a3.75,3.75,0,0,0-2.81-1.09l-.19-.05a5.88,5.88,0,1,0-7.64,0,3.77,3.77,0,0,0-4.18,2.16L5.11,52.33A3.77,3.77,0,0,0,12,55.38l.4-.89A5.12,5.12,0,0,0,13,56.62V73.69a3.77,3.77,0,0,0,3.77,3.77h0a3.77,3.77,0,0,0,3.77-3.77V59.29h.15V73.67a3.77,3.77,0,1,0,7.54,0V56.41a5.19,5.19,0,0,0,.54-2.26V45.76l7,7a3.74,3.74,0,0,0,3.74.93l3.75,2.67v9.2a3.69,3.69,0,0,0,.48,1.8v7.44a2.75,2.75,0,1,0,5.49,0v-5.5h.11v5.49a2.75,2.75,0,0,0,5.49,0V67.19a3.64,3.64,0,0,0,.4-1.64V56.34l5.6-2.55a3.75,3.75,0,0,0,3.38-1l8.64-8.64.74,4.59a1.61,1.61,0,0,1-.2,1.07L65.7,63.28a1.86,1.86,0,0,0,1.89,2.65h4v7.76a3.77,3.77,0,0,0,7.54,0V65.93h.15v7.74a3.77,3.77,0,0,0,7.54,0V65.93h3.43a1.85,1.85,0,0,0,1.88-2.65l-5.47-9.66a3.78,3.78,0,0,0-.9-1.57l-1.21-2.14a1.62,1.62,0,0,1-.18-1.14l.22-1.07L88,55.38a3.77,3.77,0,0,0,6.89-3.05Z" fill="#875b7b"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<g id="Recovery_Bank_Holiday" data-name="Recovery Bank Holiday">
<g>
<g>
<path d="M57.46,55.29V43.75a2.5,2.5,0,0,0-5,0V55.29a2.5,2.5,0,0,0,5,0Z" fill="#875b7b"/>
<path d="M54.78,53.33l-14.85-.08a2.5,2.5,0,0,0,0,5l14.85.08a2.5,2.5,0,0,0,0-5Z" fill="#875b7b"/>
</g>
<g>
<path d="M40.52,24.65A30.41,30.41,0,1,1,19.6,53.55c0-.36,0-.72,0-1.07" fill="none" stroke="#875b7b" stroke-linecap="round" stroke-linejoin="round" stroke-width="5"/>
<path d="M47.38,14.28l-8.52,8.61a2.53,2.53,0,0,0,.51,3.92l9.37,6.05a2.52,2.52,0,0,0,3.42-.9,2.56,2.56,0,0,0-.9-3.42l-9.37-6,.5,3.92,8.53-8.6a2.5,2.5,0,1,0-3.54-3.54Z" fill="#875b7b"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 771 B

@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<g id="Recovery_Bank_Holiday" data-name="Recovery Bank Holiday">
<g>
<path d="M20,45a7.48,7.48,0,0,1,7.23,7.76c0,.27,0,.53,0,.8A22.91,22.91,0,1,0,57.55,31.86a7.74,7.74,0,0,1-.81,2.43,7.52,7.52,0,0,1-6.62,3.9,7.19,7.19,0,0,1-3.93-1.14l-8.87-5.72a7.44,7.44,0,0,1-4.07-7.59A34.22,34.22,0,0,0,16.93,45.55,7.42,7.42,0,0,1,19.79,45ZM40.1,53.24l12.52.07V43.74a2.5,2.5,0,0,1,5,0V55.28a2.34,2.34,0,0,1-.21,1,2.56,2.56,0,0,1-2.46,2L40.1,58.24a2.5,2.5,0,0,1,0-5Z" fill="#875b7b"/>
<path d="M50.16,20.63c-.65,0-1.3,0-1.95.08,1-1,1.92-1.93,2.87-2.9a2.5,2.5,0,1,0-3.53-3.53l-8.16,8.23a2.48,2.48,0,0,0,.35,4.43l9.16,5.91a2.52,2.52,0,0,0,3.42-.9,2.56,2.56,0,0,0-.89-3.42L47.2,25.81a26.57,26.57,0,0,1,3-.18,27.91,27.91,0,1,1-27.9,27.91c0-.33,0-.65,0-1A2.49,2.49,0,0,0,19.87,50a2.52,2.52,0,0,0-2.59,2.4c0,.39,0,.77,0,1.16a32.91,32.91,0,1,0,32.9-32.91Z" fill="#875b7b"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 956 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><path d="M21.93,55H78.07a5,5,0,0,0,0-10H21.93a5,5,0,0,0,0,10Z" style="fill:#875b7b"/></svg>

After

Width:  |  Height:  |  Size: 153 B

@@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<g id="Sick_Time_Off" data-name="Sick Time Off">
<g>
<path d="M44.38,10.28h6.89a3.1,3.1,0,0,1,3.1,3.1V38.33A21.07,21.07,0,0,1,33.3,59.4h0A21.08,21.08,0,0,1,12.23,38.33V13.39a3.1,3.1,0,0,1,3.11-3.1l6.6,0" fill="none" stroke="#875b7b" stroke-linecap="round" stroke-linejoin="round" stroke-width="5"/>
<path d="M32.63,59.93v7.84c0,29.27,44.79,29.27,44.79,0V57.82" fill="none" stroke="#875b7b" stroke-linecap="round" stroke-linejoin="round" stroke-width="5"/>
<g>
<circle cx="77.16" cy="45.77" r="10.6" fill="none" stroke="#875b7b" stroke-linecap="round" stroke-linejoin="round" stroke-width="5"/>
<path d="M77.19,48.27c3.21,0,3.22-5,0-5s-3.23,5,0,5Z" fill="#875b7b"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 802 B

@@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<g id="Sick_Time_Off" data-name="Sick Time Off">
<g>
<path d="M44.38,10.28h6.89a3.1,3.1,0,0,1,3.1,3.1V38.33A21.07,21.07,0,0,1,33.3,59.4h0A21.08,21.08,0,0,1,12.23,38.33V13.39a3.1,3.1,0,0,1,3.11-3.1l6.6,0" fill="none" stroke="#875b7b" stroke-linecap="round" stroke-linejoin="round" stroke-width="5"/>
<path d="M32.63,59.93v7.84c0,29.27,44.79,29.27,44.79,0V57.82" fill="none" stroke="#875b7b" stroke-linecap="round" stroke-linejoin="round" stroke-width="5"/>
<g>
<circle cx="77.16" cy="45.77" r="10.6" fill="#875b7b" stroke="#875b7b" stroke-linecap="round" stroke-linejoin="round" stroke-width="5"/>
<path d="M77.19,48.27c3.21,0,3.22-5,0-5s-3.23,5,0,5Z" fill="#875b7b"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 805 B

@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<g id="Small_Unemployement" data-name="Small Unemployement">
<path d="M50,86.88A36.88,36.88,0,1,1,86.88,50,36.93,36.93,0,0,1,50,86.88Zm0-68.76A31.88,31.88,0,1,0,81.88,50,31.91,31.91,0,0,0,50,18.12Z" fill="#875b7b"/>
<g>
<path d="M34.9,64.53a2.14,2.14,0,0,1-2.14-2.14V54a49.9,49.9,0,0,0,13.75,3l-7.53-5a45.14,45.14,0,0,1-6.22-2V48l-4-2.63v17a6.14,6.14,0,0,0,6.14,6.14H64l-6.08-4Z" fill="#875b7b"/>
<path d="M45.37,35.47h9.26v3.38H46.16L52.49,43H65.1a2.14,2.14,0,0,1,2.14,2.14V50c-.89.33-1.8.63-2.71.91l6.71,4.41V45.15A6.14,6.14,0,0,0,65.1,39H58.63V33.47a2,2,0,0,0-2-2H43.37a2,2,0,0,0-2,2V35.7l4,2.63Z" fill="#875b7b"/>
</g>
<path d="M78.42,71.53a2.46,2.46,0,0,1-1.37-.41L20.59,34a2.5,2.5,0,0,1,2.74-4.18L79.8,66.94a2.5,2.5,0,0,1-1.38,4.59Z" fill="#875b7b"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 864 B

@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<g id="Small_Unemployement" data-name="Small Unemployement">
<path d="M50,86.88A36.88,36.88,0,1,1,86.88,50,36.92,36.92,0,0,1,50,86.88Zm0-68.76A31.88,31.88,0,1,0,81.88,50,31.91,31.91,0,0,0,50,18.12Z" fill="#875b7b"/>
<g>
<path d="M71.74,45.15a6.65,6.65,0,0,0-6.64-6.64h-6v-5a2.5,2.5,0,0,0-2.5-2.5H43.37a2.5,2.5,0,0,0-2.5,2.5v1.9L71.74,55.66Zm-17.61-6.8H45.87V36h8.26Z" fill="#875b7b"/>
<path d="M28.26,45.15V62.39A6.65,6.65,0,0,0,34.9,69H64.77L28.26,45Z" fill="#875b7b"/>
</g>
<path d="M78.42,71.53a2.46,2.46,0,0,1-1.37-.41L20.59,34a2.5,2.5,0,0,1,2.74-4.18L79.8,66.94a2.5,2.5,0,0,1-1.38,4.59Z" fill="#875b7b"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 717 B

@@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<g id="Small_Unemployement" data-name="Small Unemployement">
<g>
<g>
<path d="M76.31,41.83a8.05,8.05,0,0,0-8-8H61.05v-6.1a3,3,0,0,0-3-3H42a3,3,0,0,0-3,3v6.1h-.74l38.1,25ZM55,33.6H45V30.72H55Z" fill="#875b7b"/>
<path d="M23.69,62.69a8,8,0,0,0,8,8H65.67l-42-27.59Z" fill="#875b7b"/>
</g>
<path d="M82.64,75.33a2.89,2.89,0,0,1-1.59-.47L15.77,31.94A2.89,2.89,0,1,1,19,27.11L84.23,70a2.89,2.89,0,0,1-1.59,5.3Z" fill="#875b7b"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 549 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><path d="M83.13,35.69a7.9,7.9,0,0,0-4.94-7.33L34.33,72.22H47.06L83.13,36.15Z" style="fill:#875b7b"/><path d="M53.39,27.78,16.87,64.3h0a7.9,7.9,0,0,0,5.21,7.43l44-44Z" style="fill:#875b7b"/><path d="M24.78,27.78a7.91,7.91,0,0,0-7.91,7.91V51.57L40.66,27.78Z" style="fill:#875b7b"/><path d="M59.78,72.22H75.22a7.91,7.91,0,0,0,7.91-7.91V48.87Z" style="fill:#875b7b"/></svg>

After

Width:  |  Height:  |  Size: 431 B

@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<g id="Training_Time_Off" data-name="Training Time Off">
<g>
<path d="M47,42.25,8.09,29.61,47.66,17a8.69,8.69,0,0,1,5.22,0l39,12.15L52.44,42.23A8.59,8.59,0,0,1,47,42.25Z" fill="none" stroke="#875b7b" stroke-linecap="round" stroke-linejoin="round" stroke-width="5"/>
<path d="M24.23,37.5v7.42c0,15.69,51.54,15.69,51.54,0V37.31" fill="none" stroke="#875b7b" stroke-linecap="round" stroke-linejoin="round" stroke-width="5"/>
<line x1="35.89" y1="38.87" x2="35.89" y2="71.8" fill="none" stroke="#875b7b" stroke-linecap="round" stroke-linejoin="round" stroke-width="5"/>
<circle cx="35.94" cy="77.78" r="5.68" fill="none" stroke="#875b7b" stroke-linecap="round" stroke-linejoin="round" stroke-width="5"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 816 B

@@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<g id="Training_Time_Off" data-name="Training Time Off">
<g>
<g>
<path d="M21.73,41.93v3c0,2.84,1.25,6.66,6.66,9.72V44.09Z" fill="#875b7b"/>
<path d="M49.7,50.18a16.43,16.43,0,0,1-5-.79L43.39,49v9.88a64.34,64.34,0,0,0,6.61.35c14.07,0,28.27-4.41,28.27-14.27V41.53L54.81,49.35A16.23,16.23,0,0,1,49.7,50.18Z" fill="#875b7b"/>
</g>
<path d="M92.65,26.69l-39-12.14a11.12,11.12,0,0,0-6.73,0L7.33,27.23a2.5,2.5,0,0,0,0,4.76l26.07,8.47V70a8.16,8.16,0,1,0,5,0V42.08l7.86,2.55a11.06,11.06,0,0,0,7,0L92.7,31.45a2.5,2.5,0,0,0,0-4.76Z" fill="#875b7b"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 666 B

@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<g id="Unpaid_Time_Off" data-name="Unpaid Time Off">
<path d="M50,86.55A36.55,36.55,0,1,1,86.55,50,36.6,36.6,0,0,1,50,86.55Zm0-68.1A31.55,31.55,0,1,0,81.55,50,31.59,31.59,0,0,0,50,18.45Z" fill="#875b7b"/>
<path d="M60.78,34.26a13.53,13.53,0,0,0-8.6-3.93V27.58c0-3.22-5-3.22-5,0v3.09A11.57,11.57,0,0,0,38.43,41a11.4,11.4,0,0,0,3.08,8.61,11.55,11.55,0,0,0,5.67,3.29V66.13a8.48,8.48,0,0,1-4.42-2.36,2.2,2.2,0,0,1-.64-1.55v-3a2.5,2.5,0,0,0-2.49-2.51h0a2.51,2.51,0,0,0-2.5,2.49V62.2a7.25,7.25,0,0,0,2.1,5.11,13.55,13.55,0,0,0,8,3.87v1.24c0,3.22,5,3.22,5,0V71.05a11.6,11.6,0,0,0,9.39-10.49,11.46,11.46,0,0,0-9.39-12V35.35a8.5,8.5,0,0,1,5.06,2.45,2.15,2.15,0,0,1,.64,1.55v3a2.49,2.49,0,0,0,2.49,2.51h0a2.51,2.51,0,0,0,2.5-2.49V39.37A7.25,7.25,0,0,0,60.78,34.26ZM45.16,46.2a6.4,6.4,0,0,1,2-10.24v11.7A6.68,6.68,0,0,1,45.16,46.2Zm11.42,14a6.5,6.5,0,0,1-4.4,5.63V53.66a6.38,6.38,0,0,1,2.66,1.71A6.46,6.46,0,0,1,56.58,60.24Z" fill="#875b7b"/>
<g>
<path d="M33.44,40.68a16.67,16.67,0,0,1,.74-3.87L23,29.43a2.49,2.49,0,1,0-2.75,4.15l13.13,8.69C33.4,41.74,33.41,41.21,33.44,40.68Z" fill="#875b7b"/>
<path d="M78.88,66.42,66.5,58.22a16.83,16.83,0,0,1,.06,2.66,15.9,15.9,0,0,1-.5,3l10.08,6.67a2.48,2.48,0,1,0,2.74-4.14Z" fill="#875b7b"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -0,0 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<g id="Unpaid_Time_Off" data-name="Unpaid Time Off">
<g>
<path d="M61.24,34.26a13.53,13.53,0,0,0-8.6-3.93V27.58c0-3.22-5-3.22-5,0v3.09A11.57,11.57,0,0,0,38.89,41a11.44,11.44,0,0,0,8.75,11.9V66.13a8.5,8.5,0,0,1-4.43-2.36,2.19,2.19,0,0,1-.63-1.55v-3a2.5,2.5,0,0,0-2.49-2.51h0a2.5,2.5,0,0,0-2.5,2.49V62.2a7.21,7.21,0,0,0,2.1,5.11,13.52,13.52,0,0,0,8,3.87v1.24c0,3.22,5,3.22,5,0V71.05A11.58,11.58,0,0,0,62,60.56a11.46,11.46,0,0,0-9.38-12V35.35A8.5,8.5,0,0,1,57.7,37.8a2.2,2.2,0,0,1,.64,1.55v3a2.49,2.49,0,0,0,2.48,2.51h0a2.51,2.51,0,0,0,2.5-2.49V39.37A7.25,7.25,0,0,0,61.24,34.26Zm-13.6,13.4a6.56,6.56,0,0,1-2-1.46,6.4,6.4,0,0,1,2-10.24Zm7.66,7.71a6.42,6.42,0,0,1-2.66,10.5V53.66A6.38,6.38,0,0,1,55.3,55.37Z" fill="#875b7b"/>
<g>
<path d="M80.8,64,66.12,54.35A16.47,16.47,0,0,1,67,60.88a15.83,15.83,0,0,1-1.24,5.22l9.53,6.27a4.93,4.93,0,0,0,2.74.82A5,5,0,0,0,80.8,64Z" fill="#875b7b"/>
<path d="M33.9,40.68a16.07,16.07,0,0,1,1.78-6.37L24.42,26.9a5,5,0,0,0-5.5,8.35L34.25,45.34A16.79,16.79,0,0,1,33.9,40.68Z" fill="#875b7b"/>
</g>
<g>
<line x1="78.33" y1="31.08" x2="21.67" y2="68.47" fill="#875b7b"/>
<path d="M21.68,73.47a5,5,0,0,1-2.76-9.17L75.57,26.9a5,5,0,0,1,5.51,8.35L24.43,72.64A5,5,0,0,1,21.68,73.47Z" fill="#875b7b"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<g id="Credit_Time" data-name="Credit Time">
<path d="M90.13,38.78a5.33,5.33,0,0,0-.28-1.44,1,1,0,0,0,0-.16c-.06-.18-.13-.35-.2-.51a6,6,0,0,0-.3-.56L89.21,36a4.82,4.82,0,0,0-4.15-2.26l-.43.06a4.3,4.3,0,0,0-.62,0H71a5,5,0,0,0,0,10H72.3L61.63,54.44,51.26,44.21s-.08-.06-.12-.1l-9-8.88a4.84,4.84,0,0,0-1.78-1.1,4.94,4.94,0,0,0-5.63.93L19.94,49.85v-2.7a5,5,0,0,0-10,0v13.1a3.93,3.93,0,0,0-.07,1,5.33,5.33,0,0,0,.28,1.44,1.09,1.09,0,0,0,0,.17c.06.17.13.34.2.5a6,6,0,0,0,.3.56l.09.14a4.82,4.82,0,0,0,4.15,2.26l.43-.06a4.3,4.3,0,0,0,.62,0H29a5,5,0,0,0,0-10H27.7L38.37,45.56,48.74,55.79s.08.06.12.1l9,8.88a4.84,4.84,0,0,0,1.78,1.1,4.94,4.94,0,0,0,5.63-.93L80.06,50.15v2.7a5,5,0,0,0,10,0V39.75A3.93,3.93,0,0,0,90.13,38.78Z" fill="none" stroke="#875b7b" stroke-miterlimit="10" stroke-width="5"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 869 B

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<g id="Credit_Time" data-name="Credit Time">
<path d="M90.13,38.78a5.33,5.33,0,0,0-.28-1.44,1,1,0,0,0,0-.16c-.06-.18-.13-.35-.2-.51a6,6,0,0,0-.3-.56L89.21,36a4.82,4.82,0,0,0-4.15-2.26l-.43.06a4.3,4.3,0,0,0-.62,0H71a5,5,0,0,0,0,10H72.3L61.63,54.44,51.26,44.21s-.08-.06-.12-.1l-9-8.88a4.84,4.84,0,0,0-1.78-1.1,4.94,4.94,0,0,0-5.63.93L19.94,49.85v-2.7a5,5,0,0,0-10,0v13.1a3.93,3.93,0,0,0-.07,1,5.33,5.33,0,0,0,.28,1.44,1.09,1.09,0,0,0,0,.17c.06.17.13.34.2.5a6,6,0,0,0,.3.56l.09.14a4.82,4.82,0,0,0,4.15,2.26l.43-.06a4.3,4.3,0,0,0,.62,0H29a5,5,0,0,0,0-10H27.7L38.37,45.56,48.74,55.79s.08.06.12.1l9,8.88a4.84,4.84,0,0,0,1.78,1.1,4.94,4.94,0,0,0,5.63-.93L80.06,50.15v2.7a5,5,0,0,0,10,0V39.75A3.93,3.93,0,0,0,90.13,38.78Z" fill="#875b7b"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 815 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><rect x="16.87" y="27.78" width="66.26" height="44.44" rx="7.91" style="fill:#875b7b"/></svg>

After

Width:  |  Height:  |  Size: 155 B

@@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<g id="Work_Accident_Time_Off" data-name="Work Accident Time Off">
<g>
<path d="M39,44.17H61A20.59,20.59,0,0,1,81.63,64.75V89.91a0,0,0,0,1,0,0H27.2a8.82,8.82,0,0,1-8.82-8.82V64.75A20.59,20.59,0,0,1,39,44.17Z" fill="none" stroke="#875b7b" stroke-linecap="round" stroke-linejoin="round" stroke-width="5"/>
<circle cx="50" cy="27.11" r="17.02" fill="none" stroke="#875b7b" stroke-linecap="round" stroke-linejoin="round" stroke-width="5"/>
<path d="M63.63,89.8c11.79,0,11.79-16.12,0-16.12H58.5" fill="none" stroke="#875b7b" stroke-linecap="round" stroke-linejoin="round" stroke-width="5"/>
<line x1="26.87" y1="48.15" x2="37.68" y2="88.75" fill="none" stroke="#875b7b" stroke-linecap="round" stroke-linejoin="round" stroke-width="5"/>
<line x1="38.96" y1="44.17" x2="67.63" y2="89.68" fill="none" stroke="#875b7b" stroke-linecap="round" stroke-linejoin="round" stroke-width="5"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 998 B

@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<g id="Work_Accident_Time_Off" data-name="Work Accident Time Off">
<g>
<path d="M39,44.17H61A20.59,20.59,0,0,1,81.63,64.75V89.91a0,0,0,0,1,0,0H27.2a8.82,8.82,0,0,1-8.82-8.82V64.75A20.59,20.59,0,0,1,39,44.17Z" fill="none" stroke="#875b7b" stroke-linecap="round" stroke-linejoin="round" stroke-width="5"/>
<circle cx="50" cy="27.11" r="17.02" fill="none" stroke="#875b7b" stroke-linecap="round" stroke-linejoin="round" stroke-width="5"/>
<path d="M34,73.68H63.63c11.79,0,11.79,16.12,0,16.12" fill="none" stroke="#875b7b" stroke-linecap="round" stroke-linejoin="round" stroke-width="5"/>
<polygon points="37.68 88.75 26.87 48.15 38.96 44.17 67.64 89.68 37.68 88.75" fill="#875b7b" stroke="#875b7b" stroke-linecap="round" stroke-linejoin="round" stroke-width="5"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 877 B

@@ -0,0 +1,37 @@
/** @odoo-module **/
import { FieldRadio } from 'web.relational_fields';
import FieldRegistry from 'web.field_registry';
import Core from 'web.core';
const QWeb = Core.qweb;
const FieldRadioImage = FieldRadio.extend({
_render() {
const self = this;
let currentValue;
if (this.field.type === 'many2one') {
currentValue = this.value && this.value.data.id;
} else {
currentValue = this.value;
}
this.$el.empty();
this.$el.attr('role', 'radiogroup')
.attr('aria-label', this.string);
_.each(this.values, function (value, index) {
if (self.mode === 'edit' || value[0] === currentValue) {
self.$el.append(QWeb.render('FieldRadioIcon.button', {
checked: value[0] === currentValue,
id: self.unique_id + '_' + value[0],
index: index,
name: self.unique_id,
value: value,
disabled: self.hasReadonlyModifier,
mode: self.mode,
}));
}
});
}
});
FieldRegistry.add('radio_image', FieldRadioImage);
@@ -32,13 +32,6 @@ odoo.define('hr_holidays.dashboard.view_custo', function(require) {
},
});
var TimeoffCalendarModel = CalendarModel.extend({
_getFilterDomain: function() {
return this._super.apply(this, arguments).concat([["state", "!=", "refuse"]]);
},
});
var TimeOffCalendarController = CalendarController.extend({
events: _.extend({}, CalendarController.prototype.events, {
'click .btn-time-off': '_onNewTimeOff',
@@ -69,7 +62,7 @@ odoo.define('hr_holidays.dashboard.view_custo', function(require) {
$(QWeb.render('hr_holidays.dashboard.calendar.button', {
time_off: _t('New Time Off'),
request: _t('New Allocation'),
request: _t('Allocation Request'),
})).appendTo(this.$buttons);
if ($node) {
@@ -90,7 +83,7 @@ odoo.define('hr_holidays.dashboard.view_custo', function(require) {
*/
_onNewTimeOff: function () {
var self = this;
//use formViewId for this view?
this.do_action('hr_holidays.hr_leave_action_my_request', {
on_close: function () {
self.reload();
@@ -118,9 +111,31 @@ odoo.define('hr_holidays.dashboard.view_custo', function(require) {
}
});
},
/**
* @override
*/
_setEventTitle: function () {
return _t('Time Off Request');
},
});
var TimeOffPopoverRenderer = CalendarRenderer.extend({
template: "TimeOff.CalendarView.extend",
/**
* We're overriding this to display the weeknumbers on the year view
*
* @override
* @private
*/
_getFullCalendarOptions: function () {
const oldOptions = this._super(...arguments);
// Parameters
oldOptions.views.dayGridYear.weekNumbers = true;
oldOptions.views.dayGridYear.weekNumbersWithinDays = false;
return oldOptions;
},
config: _.extend({}, CalendarRenderer.prototype.config, {
CalendarPopover: TimeOffCalendarPopover,
}),
@@ -147,20 +162,6 @@ odoo.define('hr_holidays.dashboard.view_custo', function(require) {
var self = this;
return this._super.apply(this, arguments).then(function () {
self.$el.parent().find('.o_calendar_mini').hide();
// Check if there is a filter to display on the sidebar
// If there is no filter, hide the sidebar
const noFilters = !Object.values(self.state.filters).some(f => f.filters.length);
// Remove the no data sidebar
self.$sidebar.find('#o_calendar_filter_no_data').remove();
if (noFilters) {
// Show a special sidebar
self.$sidebar.html(QWeb.render('hr_holidays.calendar.sidebar.nofilter', {
title: 'Time Off Type',
description: '(no data)'
}));
}
});
},
});
@@ -201,7 +202,7 @@ odoo.define('hr_holidays.dashboard.view_custo', function(require) {
config: _.extend({}, CalendarView.prototype.config, {
Controller: TimeOffCalendarController,
Renderer: TimeOffCalendarRenderer,
Model: TimeoffCalendarModel,
Model: CalendarModel,
}),
});
@@ -210,6 +211,7 @@ odoo.define('hr_holidays.dashboard.view_custo', function(require) {
*/
var TimeOffCalendarAllView = CalendarView.extend({
config: _.extend({}, CalendarView.prototype.config, {
Controller: TimeOffCalendarController,
Renderer: TimeOffPopoverRenderer,
}),
});
@@ -0,0 +1,107 @@
$o-hr-holidays-border-color: gray('300');
.o_hr_holidays_hierarchy {
margin-left: -$o-horizontal-padding;
margin-right: -$o-horizontal-padding;
@include media-breakpoint-between(lg, xl, $o-extra-grid-breakpoints) {
margin-left: -$o-horizontal-padding*2;
margin-right: -$o-horizontal-padding*2;
}
margin-bottom:-24px;
padding: 10px 0px 24px 16px;
background-color: #F0F0F0;
box-shadow: 0px 1px 1px rgba(17, 17, 17, 0.23);
overflow-x: auto;
.o_hr_holidays_title {
padding: 0px 0px 0px 86px;
font-size: 18px;
}
.o_hr_holidays_hierarchy_readonly {
padding: 40px 0px 0px 40px;
}
.o_hr_holidays_plan_level_container {
.o-kanban-button-new {
padding: 2px 12px;
margin: 0px 0px 0px 44px;
border-radius: 25px;
}
.o_kanban_view.o_kanban_ungrouped .o_kanban_record {
display: flex;
flex: 0 0 100%;
border: 0px;
padding: 0px 0px 0px 100px;
margin: 0px 0px 0px 0px;
background-color: transparent;
// Timeline Border
&:before {
content: '';
display: block;
@include o-position-absolute;
height: 100%;
margin-left: 8px;
border-left: 1px dashed darken($o-hr-holidays-border-color, 10%);
}
// Whole record
.o_hr_holidays_body {
margin-left: 8px;
padding-top: 20px;
// Left side 'Level'
.o_hr_holidays_timeline {
@include o-position-absolute($top: 32px, $left: 6px);
width: 90px;
padding: 3px 0px;
border-radius: 3px;
background-color: white;
box-shadow: 0 1px 2px rgba(0,0,0,.1);
}
// Actual kanban card
.o_hr_holidays_card {
position: relative;
margin-left: 22px;
margin-right: 2px;
width: 500px;
border-radius: 3px;
background: white;
box-shadow: 0 1px 2px rgba(0,0,0,.1);
// Triangle
&:before {
content: '';
@include o-position-absolute($top: 12px, $left: -17px);
margin-left: 10px;
width: 14px;
height: 14px;
background: white;
border-bottom: 1px solid $o-hr-holidays-border-color;
transform: rotate(45deg);
}
// Circle
&:after {
content: '';
@include o-position-absolute($top: 14px, $left: -28px);
width: 12px;
height: 12px;
border: 2px solid $o-brand-primary;
border-radius: 10px;
background: #F0F0F0
}
.content {
position: relative;
background-color: white;
padding: 5px 7px;
font-size: 14px;
}
}
}
}
}
}
@@ -1,26 +1,37 @@
.o_timeoff_calendar .o_content {
.o_timeoff_container {
height: 6rem;
height: auto;
box-shadow: inset 0 -1px 0 $border-color;
+ .o_calendar_container {
height: calc(100% - 6rem);
}
}
.o_calendar_container {
height: 100%;
height: auto;
}
.o_timeoff_card {
border-right: $border-color solid 2px;
text-align: center;
margin-top: 4px;
div {
line-height: 1;
}
}
.fc-dayGridYear-view {
.fc-bgevent {
border-radius: 25px;
}
> .fc-month-container {
> .fc-month {
.fc-dayGridMonth-view {
.fc-has-event {
background-color: unset;
}
}
}
}
}
.o_timeoff_card_last {
border-right: 0px;
}
@@ -29,6 +40,10 @@
font-size: 15px;
}
.o_timeoff_huge {
font-size: 30px;
}
.o_timeoff_purple {
color: $o-enterprise-color;
}
@@ -42,4 +57,7 @@
font-weight: bold;
}
}
.fc-week-number {
color: #adb5bd;
}
}
@@ -3,37 +3,32 @@
<templates id="template" xml:space="preserve">
<t t-name="hr_holidays.dashboard_calendar_header">
<div class="o_timeoff_container d-flex">
<div t-foreach="timeoffs" t-as="timeoff" t-attf-class="o_timeoff_card flex-grow-1 d-flex flex-column {{ timeoff_last ? 'o_timeoff_card_last' : '' }}">
<t t-set="need_allocation" t-value="timeoff[2] !== 'no'"/>
<div t-foreach="timeoffs" t-as="timeoff" t-attf-class="o_timeoff_card flex-grow-1 d-flex flex-column py-3 {{ timeoff_last ? 'o_timeoff_card_last' : '' }}">
<t t-set="requires_allocation" t-value="timeoff[2] === 'yes'"/>
<t t-set="has_icon" t-value="timeoff[1]['icon'] !== false"/>
<t t-set="cl" t-value="'text-muted'"/>
<t t-if="need_allocation &amp;&amp; timeoff[1]['virtual_remaining_leaves'] &gt; 0">
<t t-set="cl" t-value="'o_timeoff_green'"/>
</t>
<b><span t-esc="timeoff[0]" class="o_timeoff_name o_timeoff_big"/></b>
<div class="mt-1">
<t t-if="need_allocation">
<span t-esc="timeoff[1]['virtual_remaining_leaves']" class="o_timeoff_big o_timeoff_green font-weight-bold"/> / <span t-esc="timeoff[1]['max_leaves']"/> <t t-if="timeoff[1]['request_unit'] == 'hour'">Hours</t><t t-else="">Days</t> <span class="o_timeoff_green">Available </span>
<t t-if="requires_allocation">
<t t-if="has_icon"><img height="30px" t-attf-src="{{timeoff[1]['icon']}}"/></t><span t-esc="timeoff[1]['virtual_remaining_leaves']" class="o_timeoff_huge o_timeoff_purple font-weight-bold align-middle"/><br/>
<t class="o_timeoff_big o_timeoff_purple" t-if="timeoff[1]['request_unit'] == 'hour'"><span class="o_timeoff_purple">HOURS</span></t><t class="o_timeoff_big o_timeoff_purple" t-else=""><span class="o_timeoff_purple">DAYS</span></t> <span class="o_timeoff_purple">AVAILABLE </span>
</t>
<t t-else="">
<span t-esc="timeoff[1]['virtual_leaves_taken']" class="o_timeoff_big o_timeoff_purple font-weight-bold"/> <t t-if="timeoff[1]['request_unit'] == 'hour'">Hours</t><t t-else="">Days</t> <span class="o_timeoff_purple">taken</span>
<t t-if="has_icon"><img height="30px" t-attf-src="{{timeoff[1]['icon']}}"/></t><span t-esc="timeoff[1]['virtual_leaves_taken']" class="o_timeoff_huge o_timeoff_purple font-weight-bold align-middle"/><br/>
<t t-if="timeoff[1]['request_unit'] == 'hour'"><span class="o_timeoff_purple">HOURS</span></t><t t-else=""><span class="o_timeoff_purple">DAYS</span></t> <span class="o_timeoff_purple">TAKEN</span>
</t>
</div>
<span class="mb-4" t-if="need_allocation">
<t t-if="timeoff[3]"> (Expire on <span t-esc="moment(timeoff[3]).format('L')"/>)</t>
</span>
</div>
</div>
</t>
<t t-name="hr_holidays.dashboard_calendar_header_mobile">
<t t-set="need_allocation" t-value="timeoff[2] !== 'no'"/>
<t t-set="requires_allocation" t-value="timeoff[2] === 'yes'"/>
<span class="pull-right">
<span>
<t t-if="need_allocation">
<t t-if="requires_allocation">
<strong t-esc="timeoff[1]['virtual_remaining_leaves']" class="o_timeoff_green"/> / <span t-esc="timeoff[1]['max_leaves']"/>
<t t-if="timeoff[1]['request_unit'] == 'hour'">Hours</t><t t-else="">Days</t><span class="o_timeoff_green ml-2">Available </span>
</t>
@@ -41,9 +36,6 @@
<strong t-esc="timeoff[1]['virtual_leaves_taken']"/> <t t-if="timeoff[1]['request_unit'] == 'hour'">Hours</t><t t-else="">Days</t><span class="o_timeoff_purple ml-2">Taken</span>
</t>
</span>
<span t-if="need_allocation">
<t t-if="timeoff[3]"> (Expire on <span t-esc="moment(timeoff[3]).format('L')"/>)</t>
</span>
</span>
</t>
@@ -93,17 +85,26 @@
</div>
</t>
<t t-name="hr_holidays.calendar.sidebar.nofilter">
<div class="o_calendar_filter" id="o_calendar_filter_no_data">
<h5 t-esc="title"/>
<div class="o_calendar_filter_items">
<div class="w-100 position-relative mb-2">
<p class="d-flex align-items-center m-0">
<t t-if="description" t-esc="description"/>
<t t-else=""></t>
</p>
</div>
</div>
<t t-name="FieldRadioIcon.button">
<div t-if="mode === 'edit'" class="custom-control custom-radio o_radio_item" aria-atomic="true">
<input type="radio" class="custom-control-input o_radio_input" t-att-checked="checked ? true : undefined" t-att-disabled="disabled ? true : undefined"
t-att-name="name" t-att-data-value="value[0]" t-att-data-index="index" t-att-id="id"/>
<label class="custom-control-label o_form_label" t-att-for="id"><img t-attf-src="/hr_holidays/static/src/img/icons/{{value[1]}}" width="48" height="48" /></label>
</div>
<div t-else="">
<img t-attf-src="/hr_holidays/static/src/img/icons/{{value[1]}}" width="48" height="48"/>
</div>
</t>
<t t-name="TimeOff.CalendarView.extend" t-extend="CalendarView">
<t t-jquery='.o_calendar_sidebar_container' t-operation="append">
<div class="o_timeoff_legend">
<h5>Legend</h5>
<img src="/hr_holidays/static/src/img/icons/Validated.svg" width="30px"/><span>Validated</span><br/>
<img src="/hr_holidays/static/src/img/icons/To_Approve.svg" width="30px"/><span>To Approve</span><br/>
<img src="/hr_holidays/static/src/img/icons/Refused.svg" width="30px"/><span>Refused</span><br/>
</div>
</t>
</t>
</templates>
+6 -11
View File
@@ -17,8 +17,7 @@ class TestHrHolidaysAccessRightsCommon(TestHrHolidaysCommon):
self.leave_type = self.env['hr.leave.type'].create({
'name': 'Unlimited',
'leave_validation_type': 'hr',
'allocation_type': 'no',
'validity_start': False,
'requires_allocation': 'no',
})
self.rd_dept.manager_id = False
self.hr_dept.manager_id = False
@@ -36,29 +35,25 @@ class TestHrHolidaysAccessRightsCommon(TestHrHolidaysCommon):
self.lt_no_validation = self.env['hr.leave.type'].create({
'name': 'Validation = no_validation',
'leave_validation_type': 'hr',
'allocation_type': 'no',
'validity_start': False,
'requires_allocation': 'no',
})
self.lt_validation_hr = self.env['hr.leave.type'].create({
'name': 'Validation = HR',
'leave_validation_type': 'hr',
'allocation_type': 'no',
'validity_start': False,
'requires_allocation': 'no',
})
self.lt_validation_manager = self.env['hr.leave.type'].create({
'name': 'Validation = manager',
'leave_validation_type': 'hr',
'allocation_type': 'no',
'validity_start': False,
'requires_allocation': 'no',
})
self.lt_validation_both = self.env['hr.leave.type'].create({
'name': 'Validation = both',
'leave_validation_type': 'hr',
'allocation_type': 'no',
'validity_start': False,
'requires_allocation': 'no',
})
self.draft_status = [
@@ -779,7 +774,7 @@ class TestMultiCompany(TestHrHolidaysCommon):
'name': 'Unlimited - Company New',
'company_id': self.new_company.id,
'leave_validation_type': 'hr',
'allocation_type': 'no',
'requires_allocation': 'no',
})
self.rd_dept.manager_id = False
self.hr_dept.manager_id = False
@@ -1,251 +1,556 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import datetime, time
import datetime
from freezegun import freeze_time
from dateutil.relativedelta import relativedelta
from odoo import fields
from odoo.tools import mute_logger
from odoo.tests import tagged
from odoo.addons.hr_holidays.tests.common import TestHrHolidaysCommon
@tagged('post_install', '-at_install')
class TestAccrualAllocations(TestHrHolidaysCommon):
def setUp(self):
super(TestAccrualAllocations, self).setUp()
# Make sure we have the rights to create, validate and delete the leaves, leave types and allocations
LeaveType = self.env['hr.leave.type'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True)
self.accrual_type = LeaveType.create({
'name': 'accrual',
'allocation_type': 'fixed',
'validity_start': False,
self.leave_type = self.env['hr.leave.type'].create({
'name': 'Paid Time Off',
'time_type': 'leave',
'requires_allocation': 'yes',
})
self.unpaid_type = LeaveType.create({
'name': 'unpaid',
'allocation_type': 'no',
'unpaid': True,
'validity_start': False,
})
self.set_employee_create_date(self.employee_emp_id, '2010-02-03 00:00:00')
self.set_employee_create_date(self.employee_hruser_id, '2010-02-03 00:00:00')
def set_employee_create_date(self, id, newdate):
def setAllocationCreateDate(self, allocation_id, date):
""" This method is a hack in order to be able to define/redefine the create_date
of the employees.
of the allocations.
This is done in SQL because ORM does not allow to write onto the create_date field.
"""
self.env.cr.execute("""
UPDATE
hr_employee
hr_leave_allocation
SET create_date = '%s'
WHERE id = %s
""" % (newdate, id))
""" % (date, allocation_id))
def test_accrual_base_no_leaves(self):
""" Test if we can allocate some leaves accrually to an employee """
alloc = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Accrual allocation for employee',
'employee_id': self.employee_emp_id,
'holiday_status_id': self.accrual_type.id,
'allocation_type': 'accrual',
'number_of_days': 0,
'number_per_interval': 1,
'interval_number': 1,
'unit_per_interval': 'days',
'interval_unit': 'weeks',
def test_frequency_daily(self):
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Accrual Plan For Test',
'level_ids': [(0, 0, {
'start_count': 1,
'start_type': 'day',
'added_value': 1,
'added_value_type': 'days',
'frequency': 'daily',
'maximum_leave': 10000
})],
})
alloc.action_approve()
alloc._update_accrual()
self.assertEqual(alloc.number_of_days, 1, 'Employee should have been allocated one leave day')
def test_accrual_base_leaves(self):
""" Test if the accrual allocation take the unpaid leaves into account when allocating leaves """
alloc = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Accrual allocation for employee with leaves',
'employee_id': self.employee_hruser_id,
'holiday_status_id': self.accrual_type.id,
'allocation_type': 'accrual',
'number_of_days': 0,
'number_per_interval': 1,
'interval_number': 1,
'unit_per_interval': 'days',
'interval_unit': 'weeks',
})
alloc.action_approve()
employee = self.env['hr.employee'].browse(self.employee_hruser_id)
# Getting the previous work date
df = employee.resource_calendar_id.plan_days(-2, fields.Datetime.now()).date()
leave = self.env['hr.leave'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Leave for hruser',
'employee_id': self.employee_hruser_id,
'holiday_status_id': self.unpaid_type.id,
'date_from': datetime.combine(df, time(0, 0, 0)),
'date_to': datetime.combine(df + relativedelta(days=1), time(0, 0, 0)),
'number_of_days': 1,
})
leave.action_approve()
alloc._update_accrual()
self.assertEqual(alloc.number_of_days, .8, 'As employee took some unpaid leaves last week, he should be allocated only .8 days')
def test_accrual_many(self):
"""
Test different configuration of accrual allocations
"""
Allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True)
alloc_0 = Allocation.create({
'name': '1 day per 2 weeks',
'employee_id': self.employee_emp_id,
'holiday_status_id': self.accrual_type.id,
'allocation_type': 'accrual',
'number_of_days': 0,
'number_per_interval': 1,
'interval_number': 2,
'unit_per_interval': 'days',
'interval_unit': 'weeks',
})
alloc_1 = Allocation.create({
'name': '4 hours per week',
'employee_id': self.employee_emp_id,
'holiday_status_id': self.accrual_type.id,
'allocation_type': 'accrual',
'number_of_days': 0,
'number_per_interval': 4,
'interval_number': 1,
'unit_per_interval': 'hours',
'interval_unit': 'weeks',
})
alloc_2 = Allocation.create({
'name': '2 day per 1 month',
'employee_id': self.employee_emp_id,
'holiday_status_id': self.accrual_type.id,
'allocation_type': 'accrual',
'number_of_days': 0,
'number_per_interval': 2,
'interval_number': 1,
'unit_per_interval': 'days',
'interval_unit': 'months',
})
alloc_3 = Allocation.create({
'name': '20 days per year',
'employee_id': self.employee_emp_id,
'holiday_status_id': self.accrual_type.id,
'allocation_type': 'accrual',
'number_of_days': 0,
'number_per_interval': 20,
'interval_number': 1,
'unit_per_interval': 'days',
'interval_unit': 'years',
})
(alloc_0 | alloc_1 | alloc_2 | alloc_3).action_approve()
Allocation._update_accrual()
self.assertEqual(alloc_0.number_of_days, 1)
self.assertEqual(alloc_1.number_of_days, .5)
self.assertEqual(alloc_2.number_of_days, 2)
self.assertEqual(alloc_3.number_of_days, 20)
def test_accrual_new_employee(self):
"""
Test if accrual allocation takes into account the creation date
of an employee
"""
Allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True)
self.set_employee_create_date(self.employee_emp_id, fields.Datetime.to_string(fields.Datetime.now()))
alloc_0 = Allocation.create({
'name': 'one shot one kill',
'employee_id': self.employee_emp_id,
'holiday_status_id': self.accrual_type.id,
'allocation_type': 'accrual',
'number_of_days': 0,
'number_per_interval': 1,
'interval_number': 1,
'unit_per_interval': 'days',
'interval_unit': 'weeks',
})
alloc_0.action_approve()
Allocation._update_accrual()
self.assertEqual(alloc_0.number_of_days, 0, 'Employee is new he should not get any accrual leaves')
def test_accrual_multi(self):
""" Test if the cron does not allocate leaves every time it's called but only when necessary """
alloc = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': '2 days per week',
'employee_id': self.employee_emp_id,
'holiday_status_id': self.accrual_type.id,
'allocation_type': 'accrual',
'number_of_days': 0,
'number_per_interval': 1,
'interval_number': 2,
'unit_per_interval': 'days',
'interval_unit': 'weeks',
})
alloc.action_approve()
alloc._update_accrual()
alloc._update_accrual()
self.assertEqual(alloc.number_of_days, 1, 'Cron only allocates 1 days every two weeks')
def test_accrual_validation(self):
"""
Test if cron does not allocate past it's validity date
"""
Allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True)
alloc_0 = Allocation.create({
'name': '20 days per year',
'employee_id': self.employee_emp_id,
'holiday_status_id': self.accrual_type.id,
'number_of_days': 0,
'number_per_interval': 20,
'interval_number': 1,
'unit_per_interval': 'days',
'interval_unit': 'years',
'date_to': fields.Datetime.from_string('2015-02-03 00:00:00'),
})
alloc_0.action_approve()
Allocation._update_accrual()
self.assertEqual(alloc_0.number_of_days, 0, 'Cron validity passed, should not allocate any leave')
def test_accrual_balance_limit(self):
""" Test if accrual allocation does not allocate more than the balance limit"""
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'accrual 5 max',
'employee_id': self.employee_emp_id,
'holiday_status_id': self.accrual_type.id,
'allocation_type': 'accrual',
'accrual_limit': 5,
'name': 'Accrual allocation for employee',
'accrual_plan_id': accrual_plan.id,
'employee_id': self.employee_emp.id,
'holiday_status_id': self.leave_type.id,
'number_of_days': 0,
'number_per_interval': 6,
'interval_number': 1,
'unit_per_interval': 'days',
'interval_unit': 'weeks',
'allocation_type': 'accrual',
})
allocation.action_approve()
allocation.action_confirm()
allocation.action_validate()
self.assertFalse(allocation.nextcall, 'There should be no nextcall set on the allocation.')
self.assertEqual(allocation.number_of_days, 0, 'There should be no days allocated yet.')
allocation._update_accrual()
tomorrow = datetime.date.today() + relativedelta(days=2)
self.assertEqual(allocation.number_of_days, 0, 'There should be no days allocated yet. The accrual starts tomorrow.')
self.assertEqual(allocation.number_of_days, 5, 'Should have allocated only 5 days as balance limit is 5')
freezer = freeze_time(tomorrow)
freezer.start()
allocation._update_accrual()
nextcall = datetime.date.today() + relativedelta(days=1)
self.assertEqual(allocation.number_of_days, 1, 'There should be 1 day allocated.')
self.assertEqual(allocation.nextcall, nextcall, 'The next call date of the cron should be in 2 days.')
allocation._update_accrual()
self.assertEqual(allocation.number_of_days, 1, 'There should be only 1 day allocated.')
freezer.stop()
def test_frequency_weekly(self):
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Accrual Plan For Test',
'level_ids': [(0, 0, {
'start_count': 1,
'start_type': 'day',
'added_value': 1,
'added_value_type': 'days',
'frequency': 'weekly',
'maximum_leave': 10000
})],
})
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Accrual allocation for employee',
'accrual_plan_id': accrual_plan.id,
'employee_id': self.employee_emp.id,
'holiday_status_id': self.leave_type.id,
'number_of_days': 0,
'allocation_type': 'accrual',
})
allocation.action_confirm()
allocation.action_validate()
self.assertFalse(allocation.nextcall, 'There should be no nextcall set on the allocation.')
self.assertEqual(allocation.number_of_days, 0, 'There should be no days allocated yet.')
allocation._update_accrual()
nextWeek = self.env['hr.leave.accrual.level']._get_next_weekday(datetime.date.today(), 'mon')
self.assertEqual(allocation.number_of_days, 0, 'There should be no days allocated yet. The accrual starts tomorrow.')
freezer = freeze_time(nextWeek)
freezer.start()
allocation._update_accrual()
nextWeek = self.env['hr.leave.accrual.level']._get_next_weekday(datetime.date.today(), 'mon')
self.assertEqual(allocation.number_of_days, 1, 'There should be 1 day allocated.')
self.assertEqual(allocation.nextcall, nextWeek, 'The next call date of the cron should be in 2 weeks')
freezer.stop()
def test_frequency_bimonthly(self):
freezer = freeze_time('2021-09-01')
freezer.start()
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Accrual Plan For Test',
'level_ids': [(0, 0, {
'start_count': 1,
'start_type': 'day',
'added_value': 1,
'added_value_type': 'days',
'frequency': 'bimonthly',
'first_day': 1,
'second_day': 15,
'maximum_leave': 10000,
})],
})
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Accrual allocation for employee',
'accrual_plan_id': accrual_plan.id,
'employee_id': self.employee_emp.id,
'holiday_status_id': self.leave_type.id,
'number_of_days': 0,
'allocation_type': 'accrual',
})
self.setAllocationCreateDate(allocation.id, '2021-09-01 00:00:00')
allocation.action_confirm()
allocation.action_validate()
self.assertFalse(allocation.nextcall, 'There should be no nextcall set on the allocation.')
self.assertEqual(allocation.number_of_days, 0, 'There should be no days allocated yet.')
allocation._update_accrual()
next_date = datetime.date(2021, 9, 15)
self.assertEqual(allocation.number_of_days, 0, 'There should be no days allocated yet. The accrual starts tomorrow.')
freezer.stop()
freezer = freeze_time(next_date)
freezer.start()
next_date = datetime.date(2021, 10, 1)
allocation._update_accrual()
self.assertEqual(allocation.number_of_days, 1, 'There should be 1 day allocated.')
self.assertEqual(allocation.nextcall, next_date, 'The next call date of the cron should be October 1st')
freezer.stop()
def test_frequency_monthly(self):
freezer = freeze_time('2021-09-01')
freezer.start()
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Accrual Plan For Test',
'level_ids': [(0, 0, {
'start_count': 1,
'start_type': 'day',
'added_value': 1,
'added_value_type': 'days',
'frequency': 'monthly',
'maximum_leave': 10000
})],
})
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Accrual allocation for employee',
'accrual_plan_id': accrual_plan.id,
'employee_id': self.employee_emp.id,
'holiday_status_id': self.leave_type.id,
'number_of_days': 0,
'allocation_type': 'accrual',
})
self.setAllocationCreateDate(allocation.id, '2021-09-01 00:00:00')
allocation.action_confirm()
allocation.action_validate()
self.assertFalse(allocation.nextcall, 'There should be no nextcall set on the allocation.')
self.assertEqual(allocation.number_of_days, 0, 'There should be no days allocated yet.')
allocation._update_accrual()
next_date = datetime.date(2021, 10, 1)
self.assertEqual(allocation.number_of_days, 0, 'There should be no days allocated yet. The accrual starts tomorrow.')
freezer.stop()
freezer = freeze_time(next_date)
freezer.start()
next_date = datetime.date(2021, 11, 1)
allocation._update_accrual()
self.assertEqual(allocation.number_of_days, 1, 'There should be 1 day allocated.')
self.assertEqual(allocation.nextcall, next_date, 'The next call date of the cron should be November 1st')
freezer.stop()
def test_frequency_biyearly(self):
freezer = freeze_time('2021-09-01')
freezer.start()
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Accrual Plan For Test',
'level_ids': [(0, 0, {
'start_count': 1,
'start_type': 'day',
'added_value': 1,
'added_value_type': 'days',
'frequency': 'biyearly',
'maximum_leave': 10000,
})],
})
#this sets up an accrual on the 1st of January and the 1st of July
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Accrual allocation for employee',
'accrual_plan_id': accrual_plan.id,
'employee_id': self.employee_emp.id,
'holiday_status_id': self.leave_type.id,
'number_of_days': 0,
'allocation_type': 'accrual',
})
self.setAllocationCreateDate(allocation.id, '2021-09-01 00:00:00')
allocation.action_confirm()
allocation.action_validate()
self.assertFalse(allocation.nextcall, 'There should be no nextcall set on the allocation.')
self.assertEqual(allocation.number_of_days, 0, 'There should be no days allocated yet.')
allocation._update_accrual()
next_date = datetime.date(2022, 1, 1)
self.assertEqual(allocation.number_of_days, 0, 'There should be no days allocated yet. The accrual starts tomorrow.')
freezer.stop()
freezer = freeze_time(next_date)
freezer.start()
next_date = datetime.date(2022, 7, 1)
allocation._update_accrual()
self.assertEqual(allocation.number_of_days, 1, 'There should be 1 day allocated.')
self.assertEqual(allocation.nextcall, next_date, 'The next call date of the cron should be July 1st')
freezer.stop()
def test_frequency_yearly(self):
freezer = freeze_time('2021-09-01')
freezer.start()
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Accrual Plan For Test',
'level_ids': [(0, 0, {
'start_count': 1,
'start_type': 'day',
'added_value': 1,
'added_value_type': 'days',
'frequency': 'yearly',
'maximum_leave': 10000,
})],
})
#this sets up an accrual on the 1st of January
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Accrual allocation for employee',
'accrual_plan_id': accrual_plan.id,
'employee_id': self.employee_emp.id,
'holiday_status_id': self.leave_type.id,
'number_of_days': 0,
'allocation_type': 'accrual',
})
self.setAllocationCreateDate(allocation.id, '2021-09-01 00:00:00')
allocation.action_confirm()
allocation.action_validate()
self.assertFalse(allocation.nextcall, 'There should be no nextcall set on the allocation.')
self.assertEqual(allocation.number_of_days, 0, 'There should be no days allocated yet.')
allocation._update_accrual()
next_date = datetime.date(2022, 1, 1)
self.assertEqual(allocation.number_of_days, 0, 'There should be no days allocated yet. The accrual starts tomorrow.')
freezer.stop()
freezer = freeze_time(next_date)
freezer.start()
next_date = datetime.date(2023, 1, 1)
allocation._update_accrual()
self.assertEqual(allocation.number_of_days, 1, 'There should be 1 day allocated.')
self.assertEqual(allocation.nextcall, next_date, 'The next call date of the cron should be January 1st 2023')
freezer.stop()
def test_check_gain(self):
# 2 accruals, one based on worked time, one not
# check gain
freezer = freeze_time('2021-08-30')
freezer.start()
calendar_emp = self.env['resource.calendar'].create({
'name': '40 Hours',
'tz': self.employee_emp.tz,
'attendance_ids': [
(0, 0, {
'name': '%s_%d' % ('40 Hours', index),
'hour_from': 8,
'hour_to': 12,
'dayofweek': str(index),
'day_period': 'morning'
}, {
'name': '%s_%d' % ('40 Hours', index),
'hour_from': 13,
'hour_to': 18,
'dayofweek': str(index),
'day_period': 'afternoon'
}) for index in range(5)
],
})
self.employee_emp.resource_calendar_id = calendar_emp.id
accrual_plan_not_based_on_worked_time = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Accrual Plan For Test',
'level_ids': [(0, 0, {
'start_count': 1,
'start_type': 'day',
'added_value': 5,
'added_value_type': 'days',
'frequency': 'weekly',
'maximum_leave': 10000,
})],
})
accrual_plan_based_on_worked_time = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Accrual Plan For Test',
'level_ids': [(0, 0, {
'start_count': 1,
'start_type': 'day',
'added_value': 5,
'added_value_type': 'days',
'frequency': 'weekly',
'maximum_leave': 10000,
'is_based_on_worked_time': True,
})],
})
allocation_not_worked_time = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Accrual allocation for employee',
'accrual_plan_id': accrual_plan_not_based_on_worked_time.id,
'employee_id': self.employee_emp.id,
'holiday_status_id': self.leave_type.id,
'number_of_days': 0,
'allocation_type': 'accrual',
'state': 'validate',
})
allocation_worked_time = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Accrual allocation for employee',
'accrual_plan_id': accrual_plan_based_on_worked_time.id,
'employee_id': self.employee_emp.id,
'holiday_status_id': self.leave_type.id,
'number_of_days': 0,
'allocation_type': 'accrual',
'state': 'validate',
})
self.setAllocationCreateDate(allocation_not_worked_time.id, '2021-08-01 00:00:00')
self.setAllocationCreateDate(allocation_worked_time.id, '2021-08-01 00:00:00')
holiday_type = self.env['hr.leave.type'].create({
'name': 'Paid Time Off',
'requires_allocation': 'no',
'responsible_id': self.user_hrmanager_id,
})
leave = self.env['hr.leave'].create({
'name': 'leave',
'employee_id': self.employee_emp.id,
'holiday_status_id': holiday_type.id,
'date_from': '2021-09-02 00:00:00',
'date_to': '2021-09-02 23:59:59',
})
leave.action_validate()
self.assertFalse(allocation_not_worked_time.nextcall, 'There should be no nextcall set on the allocation.')
self.assertFalse(allocation_worked_time.nextcall, 'There should be no nextcall set on the allocation.')
self.assertEqual(allocation_not_worked_time.number_of_days, 0, 'There should be no days allocated yet.')
self.assertEqual(allocation_worked_time.number_of_days, 0, 'There should be no days allocated yet.')
freezer.stop()
next_date = datetime.date(2021, 9, 6)
freezer = freeze_time(next_date)
freezer.start()
# next_date = datetime.date(2021, 9, 13)
self.env['hr.leave.allocation']._update_accrual()
self.assertEqual(allocation_not_worked_time.number_of_days, 5, 'There should be 5 days allocated.')
# 3.75 -> starts 1 day after allocation date -> 31/08-3/09 => 4 days - 1 days time off => (3 / 4) * 5 days
self.assertEqual(allocation_worked_time.number_of_days, 3.75, 'There should be 5 days allocated.')
self.assertEqual(allocation_not_worked_time.nextcall, datetime.date(2021, 9, 13), 'The next call date of the cron should be the September 13th')
self.assertEqual(allocation_worked_time.nextcall, datetime.date(2021, 9, 13), 'The next call date of the cron should be the September 13th')
freezer.stop()
freezer = freeze_time(next_date + relativedelta(days=7))
freezer.start()
next_date = datetime.date(2021, 9, 20)
self.env['hr.leave.allocation']._update_accrual()
self.assertEqual(allocation_not_worked_time.number_of_days, 10, 'There should be 10 days allocated.')
self.assertEqual(allocation_not_worked_time.nextcall, next_date, 'The next call date of the cron should be September 20th')
self.assertEqual(allocation_worked_time.number_of_days, 8.75, 'There should be 9 days allocated.')
self.assertEqual(allocation_worked_time.nextcall, next_date, 'The next call date of the cron should be September 20th')
freezer.stop()
def test_check_max_value(self):
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Accrual Plan For Test',
'level_ids': [(0, 0, {
'start_count': 1,
'start_type': 'day',
'added_value': 1,
'added_value_type': 'days',
'frequency': 'daily',
'maximum_leave': 1,
})],
})
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Accrual allocation for employee',
'accrual_plan_id': accrual_plan.id,
'employee_id': self.employee_emp.id,
'holiday_status_id': self.leave_type.id,
'number_of_days': 0,
'allocation_type': 'accrual',
})
allocation.action_confirm()
allocation.action_validate()
allocation._update_accrual()
tomorrow = datetime.date.today() + relativedelta(days=2)
self.assertEqual(allocation.number_of_days, 0, 'There should be no days allocated yet. The accrual starts tomorrow.')
freezer = freeze_time(tomorrow)
freezer.start()
allocation._update_accrual()
nextcall = datetime.date.today() + relativedelta(days=1)
allocation._update_accrual()
self.assertEqual(allocation.number_of_days, 1, 'There should be only 1 day allocated.')
freezer.stop()
freezer = freeze_time(nextcall)
freezer.start()
allocation._update_accrual()
nextcall = datetime.date.today() + relativedelta(days=1)
#The maximum value is 1 so this shouldn't change anything
allocation._update_accrual()
self.assertEqual(allocation.number_of_days, 1, 'There should be only 1 day allocated.')
freezer.stop()
def test_accrual_transition_immediately(self):
#1 accrual with 2 levels and level transition immediately
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Accrual Plan For Test',
'transition_mode': 'immediately',
'level_ids': [(0, 0, {
'start_count': 1,
'start_type': 'day',
'added_value': 1,
'added_value_type': 'days',
'frequency': 'weekly',
'maximum_leave': 1,
}), (0, 0, {
'start_count': 10,
'start_type': 'day',
'added_value': 1,
'added_value_type': 'days',
'frequency': 'weekly',
'maximum_leave': 1,
})],
})
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Accrual allocation for employee',
'accrual_plan_id': accrual_plan.id,
'employee_id': self.employee_emp.id,
'holiday_status_id': self.leave_type.id,
'number_of_days': 0,
'allocation_type': 'accrual',
})
allocation.action_confirm()
allocation.action_validate()
next_date = datetime.date.today() + relativedelta(days=11)
second_level = self.env['hr.leave.accrual.level'].search([('accrual_plan_id', '=', accrual_plan.id), ('start_count', '=', 10)])
self.assertEqual(allocation._get_current_accrual_plan_level_id(next_date), second_level, 'The second level should be selected')
def test_accrual_transition_after_period(self):
# 1 accrual with 2 levels and level transition after
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Accrual Plan For Test',
'transition_mode': 'end_of_accrual',
'level_ids': [(0, 0, {
'start_count': 1,
'start_type': 'day',
'added_value': 1,
'added_value_type': 'days',
'frequency': 'weekly',
'maximum_leave': 1,
}), (0, 0, {
'start_count': 10,
'start_type': 'day',
'added_value': 1,
'added_value_type': 'days',
'frequency': 'weekly',
'maximum_leave': 1,
})],
})
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Accrual allocation for employee',
'accrual_plan_id': accrual_plan.id,
'employee_id': self.employee_emp.id,
'holiday_status_id': self.leave_type.id,
'number_of_days': 0,
'allocation_type': 'accrual',
})
allocation.action_confirm()
allocation.action_validate()
next_date = datetime.date.today() + relativedelta(days=11)
second_level = self.env['hr.leave.accrual.level'].search([('accrual_plan_id', '=', accrual_plan.id), ('start_count', '=', 10)])
self.assertEqual(allocation._get_current_accrual_plan_level_id(next_date), second_level, 'The second level should be selected')
def test_unused_accrual_lost(self):
#1 accrual with 2 levels and level transition immediately
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Accrual Plan For Test',
'level_ids': [(0, 0, {
'start_count': 1,
'start_type': 'day',
'added_value': 1,
'added_value_type': 'days',
'frequency': 'daily',
'maximum_leave': 1,
'action_with_unused_accruals': 'lost',
})],
})
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Accrual allocation for employee',
'accrual_plan_id': accrual_plan.id,
'employee_id': self.employee_emp.id,
'holiday_status_id': self.leave_type.id,
'number_of_days': 10,
'allocation_type': 'accrual',
})
allocation.action_confirm()
allocation.action_validate()
freezer = freeze_time('2022-01-01')
freezer.start()
allocation._update_accrual()
self.assertEqual(allocation.number_of_days, 0, 'There number of days should be reset')
freezer.stop()
def test_unused_accrual_postponed(self):
# 1 accrual with 2 levels and level transition after
# This also tests retroactivity
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Accrual Plan For Test',
'level_ids': [(0, 0, {
'start_count': 1,
'start_type': 'day',
'added_value': 1,
'added_value_type': 'days',
'frequency': 'daily',
'maximum_leave': 25,
'action_with_unused_accruals': 'postponed',
})],
})
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Accrual allocation for employee',
'accrual_plan_id': accrual_plan.id,
'employee_id': self.employee_emp.id,
'holiday_status_id': self.leave_type.id,
'number_of_days': 10,
'allocation_type': 'accrual',
})
allocation.action_confirm()
allocation.action_validate()
freezer = freeze_time('2022-01-01')
freezer.start()
allocation._update_accrual()
self.assertEqual(allocation.number_of_days, 25, 'The maximum number of days should be reached and kept.')
freezer.stop()
@@ -4,6 +4,7 @@
from odoo import tests
from odoo.addons.hr_holidays.tests.common import TestHrHolidaysCommon
from odoo.exceptions import AccessError, UserError
import time
@tests.tagged('access_rights', 'post_install', '-at_install')
@@ -16,31 +17,33 @@ class TestAllocationRights(TestHrHolidaysCommon):
self.employee_emp.parent_id = False
self.employee_emp.leave_manager_id = False
self.lt_validation_hr = self.env['hr.leave.type'].create({
self.lt_no_validation = self.env['hr.leave.type'].create({
'name': 'Validation = HR',
'allocation_validation_type': 'hr',
'allocation_type': 'fixed_allocation',
'validity_start': False,
'allocation_validation_type': 'officer',
'requires_allocation': 'no',
'employee_requests': 'yes',
})
self.lt_validation_manager = self.env['hr.leave.type'].create({
'name': 'Validation = manager',
'allocation_validation_type': 'manager',
'allocation_type': 'fixed_allocation',
'validity_start': False,
'allocation_validation_type': 'officer',
'requires_allocation': 'yes',
'employee_requests': 'yes',
})
self.lt_validation_both = self.env['hr.leave.type'].create({
'name': 'Validation = both',
'allocation_validation_type': 'both',
'allocation_type': 'fixed_allocation',
'validity_start': False,
self.lt_allocation_manager = self.env['hr.leave.type'].create({
'name': 'Validation = manager',
'allocation_validation_type': 'set',
'requires_allocation': 'yes',
'employee_requests': 'no',
})
def request_allocation(self, user, values={}):
values = dict(values, **{
'name': 'Allocation',
'number_of_days': 1,
'date_from': time.strftime('%Y-01-01'),
'date_to': time.strftime('%Y-12-31'),
})
return self.env['hr.leave.allocation'].with_user(user).create(values)
@@ -51,18 +54,17 @@ class TestAccessRightsSimpleUser(TestAllocationRights):
""" A simple user can request an allocation but not approve it """
values = {
'employee_id': self.employee_emp.id,
'holiday_status_id': self.lt_validation_hr.id,
'holiday_status_id': self.lt_validation_manager.id,
}
allocation = self.request_allocation(self.user_employee.id, values)
with self.assertRaises(UserError):
allocation.action_approve()
allocation.action_validate()
def test_simple_user_request_fixed_allocation(self):
""" A simple user cannot request an allocation if set by HR """
self.lt_validation_hr.allocation_type = 'fixed'
""" A simple user cannot request an allocation if employee requests is not enabled """
values = {
'employee_id': self.employee_emp.id,
'holiday_status_id': self.lt_validation_hr.id,
'holiday_status_id': self.lt_allocation_manager.id,
}
with self.assertRaises(AccessError):
self.request_allocation(self.user_employee.id, values)
@@ -71,12 +73,14 @@ class TestAccessRightsSimpleUser(TestAllocationRights):
""" A simple user can reset to draft only his own allocation """
values = {
'employee_id': self.employee_emp.id,
'holiday_status_id': self.lt_validation_hr.id,
'holiday_status_id': self.lt_validation_manager.id,
}
allocation = self.request_allocation(self.user_employee.id, values)
self.assertEqual(allocation.state, 'confirm')
self.assertEqual(allocation.state, 'draft')
allocation.action_confirm()
self.assertEqual(allocation.state, 'confirm', "It should be confirmed")
allocation.action_draft()
self.assertEqual(allocation.state, 'draft', "It should be reset to draft state")
self.assertEqual(allocation.state, 'draft', "It should have been reset to draft")
class TestAccessRightsEmployeeManager(TestAllocationRights):
@@ -104,7 +108,8 @@ class TestAccessRightsEmployeeManager(TestAllocationRights):
'holiday_status_id': self.lt_validation_manager.id,
}
allocation = self.request_allocation(self.user_employee.id, values)
allocation.action_approve()
allocation.action_confirm()
allocation.action_validate()
self.assertEqual(allocation.state, 'validate', "The allocation should be validated")
def test_manager_refuse_request_allocation(self):
@@ -114,6 +119,7 @@ class TestAccessRightsEmployeeManager(TestAllocationRights):
'holiday_status_id': self.lt_validation_manager.id,
}
allocation = self.request_allocation(self.user_employee.id, values)
allocation.action_confirm()
allocation.action_refuse()
self.assertEqual(allocation.state, 'refuse', "The allocation should be validated")
@@ -134,61 +140,37 @@ class TestAccessRightsEmployeeManager(TestAllocationRights):
'holiday_status_id': self.lt_validation_manager.id,
}
allocation = self.request_allocation(self.user_employee.id, values)
with self.assertRaises(UserError):
allocation.action_approve()
def test_manager_only_first_approval(self):
""" A manager can only do the first approval """
values = {
'employee_id': self.managed_employee.id,
'holiday_status_id': self.lt_validation_both.id,
}
allocation = self.request_allocation(self.user_employee.id, values)
allocation.action_approve()
with self.assertRaises(UserError):
allocation.action_validate()
class TestAccessRightsHolidayUser(TestAllocationRights):
def test_holiday_user_request_allocation(self):
""" A holiday user can request and approve an allocation for any employee """
values = {
'employee_id': self.employee_emp.id,
'holiday_status_id': self.lt_validation_hr.id,
'holiday_status_id': self.lt_validation_manager.id,
}
allocation = self.request_allocation(self.user_hruser.id, values)
allocation.action_approve()
allocation.action_confirm()
allocation.action_validate()
self.assertEqual(allocation.state, 'validate', "It should have been validated")
def test_holiday_user_request_fixed_allocation(self):
""" A holiday user can request and approve an allocation if set by HR """
self.lt_validation_hr.allocation_type = 'fixed'
values = {
'employee_id': self.employee_emp.id,
'holiday_status_id': self.lt_validation_hr.id,
'holiday_status_id': self.lt_allocation_manager.id,
}
allocation = self.request_allocation(self.user_hruser.id, values)
allocation.action_approve()
self.assertEqual(allocation.state, 'validate', "It should have been validated")
def test_holiday_user_both_second_approval(self):
""" A holiday user can only do the second approval when double validation """
values = {
'employee_id': self.employee_emp.id,
'holiday_status_id': self.lt_validation_both.id,
}
allocation = self.request_allocation(self.user_hruser.id, values)
with self.assertRaises(UserError):
allocation.action_approve()
allocation.sudo().action_approve() # First approval by someone else
allocation.action_confirm()
allocation.action_validate()
self.assertEqual(allocation.state, 'validate', "It should have been validated")
def test_holiday_user_batch_allocation(self):
""" A holiday user cannot create a batch allocation """
values = {
'holiday_status_id': self.lt_validation_hr.id,
'holiday_status_id': self.lt_validation_manager.id,
'holiday_type': 'company',
'mode_company_id': self.user_employee.company_id.id,
}
@@ -199,11 +181,12 @@ class TestAccessRightsHolidayUser(TestAllocationRights):
""" A holiday user cannot approve his own allocation """
values = {
'employee_id': self.employee_hruser.id,
'holiday_status_id': self.lt_validation_hr.id,
'holiday_status_id': self.lt_validation_manager.id,
}
allocation = self.request_allocation(self.user_hruser.id, values)
allocation.action_confirm()
with self.assertRaises(UserError):
allocation.action_approve()
allocation.action_validate()
class TestAccessRightsHolidayManager(TestAllocationRights):
@@ -212,32 +195,22 @@ class TestAccessRightsHolidayManager(TestAllocationRights):
""" A holiday manager can approve his own allocation """
values = {
'employee_id': self.employee_hrmanager.id,
'holiday_status_id': self.lt_validation_hr.id,
'holiday_status_id': self.lt_validation_manager.id,
}
allocation = self.request_allocation(self.user_hrmanager.id, values)
allocation.action_approve()
self.assertEqual(allocation.state, 'validate', "It should have been validated")
def test_holiday_manager_both_validation(self):
""" A holiday manager can perform both validation """
values = {
'employee_id': self.employee_emp.id,
'holiday_status_id': self.lt_validation_both.id,
}
allocation = self.request_allocation(self.user_hrmanager.id, values)
allocation.action_approve()
self.assertEqual(allocation.state, 'validate1', "It should have been validated one time")
allocation.action_confirm()
allocation.action_validate()
self.assertEqual(allocation.state, 'validate', "It should have been completely validated")
self.assertEqual(allocation.state, 'validate', "It should have been validated")
def test_holiday_manager_refuse_validated(self):
""" A holiday manager can refuse a validated allocation """
values = {
'employee_id': self.employee_emp.id,
'holiday_status_id': self.lt_validation_hr.id,
'holiday_status_id': self.lt_validation_manager.id,
}
allocation = self.request_allocation(self.user_hrmanager.id, values)
allocation.action_approve()
allocation.action_confirm()
allocation.action_validate()
self.assertEqual(allocation.state, 'validate', "It should have been validated")
allocation.action_refuse()
self.assertEqual(allocation.state, 'refuse', "It should have been refused")
@@ -14,8 +14,7 @@ class TestAutomaticLeaveDates(TestHrHolidaysCommon):
self.leave_type = self.env['hr.leave.type'].create({
'name': 'Automatic Test',
'time_type': 'leave',
'allocation_type': 'no',
'validity_start': False,
'requires_allocation': 'no',
})
def test_no_attendances(self):
@@ -14,8 +14,7 @@ class TestChangeDepartment(TestHrHolidaysCommon):
HolidayStatusManagerGroup = self.env['hr.leave.type'].with_user(self.user_hrmanager_id)
self.holidays_status_1 = HolidayStatusManagerGroup.create({
'name': 'NotLimitedHR',
'allocation_type': 'no',
'validity_start': False,
'requires_allocation': 'no',
})
def create_holiday(name, start, end):
@@ -20,6 +20,7 @@ class TestCompanyLeave(TransactionCase):
'name': 'Bank Holiday',
'responsible_id': cls.env.user.id,
'company_id': cls.company.id,
'requires_allocation': 'no',
})
cls.paid_time_off = cls.env['hr.leave.type'].create({
@@ -27,6 +28,7 @@ class TestCompanyLeave(TransactionCase):
'request_unit': 'day',
'leave_validation_type': 'both',
'company_id': cls.company.id,
'requires_allocation': 'no',
})
cls.employee = cls.env['hr.employee'].create({
@@ -313,7 +315,7 @@ class TestCompanyLeave(TransactionCase):
})
company_leave._compute_date_from_to()
with self.assertQueryCount(__system__=845, admin=865):
with self.assertQueryCount(__system__=846, admin=865):
# Original query count: 1987
# Without tracking/activity context keys: 5154
company_leave.action_validate()
+22 -21
View File
@@ -25,19 +25,17 @@ class TestHolidaysFlow(TestHrHolidaysCommon):
HolidayStatusManagerGroup = HolidaysStatus.with_user(self.user_hrmanager_id)
HolidayStatusManagerGroup.create({
'name': 'WithMeetingType',
'allocation_type': 'no',
'requires_allocation': 'no',
})
self.holidays_status_hr = HolidayStatusManagerGroup.create({
'name': 'NotLimitedHR',
'allocation_type': 'no',
'requires_allocation': 'no',
'leave_validation_type': 'hr',
'validity_start': False,
})
self.holidays_status_manager = HolidayStatusManagerGroup.create({
'name': 'NotLimitedManager',
'allocation_type': 'no',
'requires_allocation': 'no',
'leave_validation_type': 'manager',
'validity_start': False,
})
HolidaysEmployeeGroup = Requests.with_user(self.user_employee_id)
@@ -86,9 +84,10 @@ class TestHolidaysFlow(TestHrHolidaysCommon):
holiday_status_paid_time_off = self.env['hr.leave.type'].create({
'name': 'Paid Time Off',
'allocation_type': 'fixed',
'requires_allocation': 'yes',
'employee_requests': 'no',
'allocation_validation_type': 'set',
'leave_validation_type': 'both',
'validity_start': time.strftime('%Y-%m-01'),
'responsible_id': self.env.ref('base.user_admin').id,
})
@@ -99,12 +98,16 @@ class TestHolidaysFlow(TestHrHolidaysCommon):
'number_of_days': 20,
'employee_id': self.employee_emp_id,
'state': 'validate',
'date_from': time.strftime('%Y-%m-01'),
'date_to': time.strftime('%Y-12-31'),
}, {
'name': 'Paid Time off for David',
'holiday_status_id': holiday_status_paid_time_off.id,
'number_of_days': 20,
'employee_id': self.ref('hr.employee_admin'),
'state': 'validate',
'date_from': time.strftime('%Y-%m-01'),
'date_to': time.strftime('%Y-12-31'),
}
])
@@ -122,16 +125,15 @@ class TestHolidaysFlow(TestHrHolidaysCommon):
HolidayStatusManagerGroup = HolidaysStatus.with_user(self.user_hrmanager_id)
HolidayStatusManagerGroup.create({
'name': 'WithMeetingType',
'allocation_type': 'no',
'validity_start': False,
'requires_allocation': 'no',
})
self.holidays_status_limited = HolidayStatusManagerGroup.create({
'name': 'Limited',
'allocation_type': 'fixed',
'allocation_validation_type': 'both',
'requires_allocation': 'yes',
'employee_requests': 'no',
'allocation_validation_type': 'set',
'leave_validation_type': 'both',
'validity_start': False,
})
HolidaysEmployeeGroup = Requests.with_user(self.user_employee_id)
@@ -141,9 +143,11 @@ class TestHolidaysFlow(TestHrHolidaysCommon):
'employee_id': self.employee_emp_id,
'holiday_status_id': self.holidays_status_limited.id,
'number_of_days': 2,
'state': 'confirm',
'date_from': time.strftime('%Y-%m-01'),
'date_to': time.strftime('%Y-12-31'),
})
# HrUser validates the first step
aloc1_user_group.action_approve()
# HrManager validates the second step
aloc1_user_group.with_user(self.user_hrmanager_id).action_validate()
@@ -165,11 +169,6 @@ class TestHolidaysFlow(TestHrHolidaysCommon):
hol_status_2_employee_group.invalidate_cache()
_check_holidays_status(hol_status_2_employee_group, 2.0, 0.0, 2.0, 1.0)
# HrManager validates the first step
hol2_user_group.with_user(self.user_hrmanager_id).action_approve()
self.assertEqual(hol2.state, 'validate1',
'hr_holidays: first validation should lead to validate1 state')
# HrManager validates the second step
hol2_user_group.with_user(self.user_hrmanager_id).action_validate()
self.assertEqual(hol2.state, 'validate',
@@ -216,7 +215,6 @@ class TestHolidaysFlow(TestHrHolidaysCommon):
hol3.action_confirm()
self.assertEqual(hol3.state, 'confirm', 'hr_holidays: confirming should lead to confirm state')
# I validate the holiday request by clicking on "To Approve" button.
hol3.action_approve()
hol3.action_validate()
self.assertEqual(hol3.state, 'validate', 'hr_holidays: validation should lead to validate state')
# Check left days for casual leave: 19 days left
@@ -243,9 +241,10 @@ class TestHolidaysFlow(TestHrHolidaysCommon):
holiday_status_paid_time_off = self.env['hr.leave.type'].create({
'name': 'Paid Time Off',
'allocation_type': 'fixed',
'requires_allocation': 'yes',
'employee_requests': 'no',
'allocation_validation_type': 'set',
'leave_validation_type': 'both',
'validity_start': time.strftime('%Y-%m-01'),
'responsible_id': self.env.ref('base.user_admin').id,
})
@@ -255,6 +254,8 @@ class TestHolidaysFlow(TestHrHolidaysCommon):
'number_of_days': 20,
'employee_id': self.ref('hr.employee_admin'),
'state': 'validate',
'date_from': time.strftime('%Y-%m-01'),
'date_to': time.strftime('%Y-12-31'),
})
leave_vals = {
@@ -15,8 +15,7 @@ class TestHrLeaveType(TestHrHolidaysCommon):
leave_type = self.env['hr.leave.type'].create({
'name': 'Paid Time Off',
'time_type': 'leave',
'allocation_type': 'no',
'validity_start': False,
'requires_allocation': 'no',
})
leave_1 = self.env['hr.leave'].create({
@@ -39,5 +38,5 @@ class TestHrLeaveType(TestHrHolidaysCommon):
with self.assertRaises(AccessError):
self.env['hr.leave.type'].with_user(self.user_hruser_id).create({
'name': 'UserCheats',
'allocation_type': 'no',
'requires_allocation': 'no',
})
+36 -19
View File
@@ -2,6 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import datetime, date
import time
from dateutil.relativedelta import relativedelta
from pytz import timezone, UTC
@@ -34,22 +35,19 @@ class TestLeaveRequests(TestHrHolidaysCommon):
self.holidays_type_1 = LeaveType.create({
'name': 'NotLimitedHR',
'allocation_type': 'no',
'requires_allocation': 'no',
'leave_validation_type': 'hr',
'validity_start': False,
})
self.holidays_type_2 = LeaveType.create({
'name': 'Limited',
'allocation_type': 'fixed_allocation',
'requires_allocation': 'yes',
'employee_requests': 'yes',
'leave_validation_type': 'hr',
'validity_start': False,
})
self.holidays_type_3 = LeaveType.create({
'name': 'TimeNotLimited',
'allocation_type': 'no',
'requires_allocation': 'no',
'leave_validation_type': 'manager',
'validity_start': fields.Datetime.from_string('2017-01-01 00:00:00'),
'validity_stop': fields.Datetime.from_string('2017-06-01 00:00:00'),
})
self.set_employee_create_date(self.employee_emp_id, '2010-02-03 00:00:00')
@@ -111,8 +109,11 @@ class TestLeaveRequests(TestHrHolidaysCommon):
'employee_id': self.employee_emp_id,
'holiday_status_id': self.holidays_type_2.id,
'number_of_days': 2,
'state': 'confirm',
'date_from': time.strftime('%Y-1-1'),
'date_to': time.strftime('%Y-12-31'),
})
aloc1_user_group.action_approve()
aloc1_user_group.action_validate()
holiday_status = self.holidays_type_2.with_user(self.user_employee_id)
self._check_holidays_status(holiday_status, 2.0, 0.0, 2.0, 2.0)
@@ -137,10 +138,21 @@ class TestLeaveRequests(TestHrHolidaysCommon):
@mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
def test_accrual_validity_time_valid(self):
""" Employee ask leave during a valid validity time """
self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).create({
'name': 'Sick Time Off',
'holiday_status_id': self.holidays_type_2.id,
'employee_id': self.employee_emp.id,
'date_from': fields.Datetime.from_string('2017-01-01 00:00:00'),
'date_to': fields.Datetime.from_string('2017-06-01 00:00:00'),
'number_of_days': 10,
'state': 'validate',
})
self.env['hr.leave'].with_user(self.user_employee_id).create({
'name': 'Valid time period',
'employee_id': self.employee_emp_id,
'holiday_status_id': self.holidays_type_3.id,
'holiday_status_id': self.holidays_type_2.id,
'date_from': fields.Datetime.from_string('2017-03-03 06:00:00'),
'date_to': fields.Datetime.from_string('2017-03-11 19:00:00'),
'number_of_days': 1,
@@ -148,12 +160,12 @@ class TestLeaveRequests(TestHrHolidaysCommon):
@mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
def test_accrual_validity_time_not_valid(self):
""" Employee ask leav during a not valid validity time """
""" Employee ask leave when there's no valid allocation """
with self.assertRaises(ValidationError):
self.env['hr.leave'].with_user(self.user_employee_id).create({
self.env['hr.leave'].with_user(self.user_employee).create({
'name': 'Sick Time Off',
'employee_id': self.employee_emp_id,
'holiday_status_id': self.holidays_type_3.id,
'holiday_status_id': self.holidays_type_2.id,
'date_from': fields.Datetime.from_string('2017-07-03 06:00:00'),
'date_to': fields.Datetime.from_string('2017-07-11 19:00:00'),
'number_of_days': 1,
@@ -182,6 +194,8 @@ class TestLeaveRequests(TestHrHolidaysCommon):
allocation_form = Form(self.env['hr.leave.allocation'].with_user(self.user_employee))
allocation_form.name = 'New Allocation Request'
allocation_form.holiday_status_id = self.holidays_type_2
allocation_form.date_from = date(2019, 5, 6)
allocation_form.date_to = date(2019, 5, 6)
allocation = allocation_form.save()
@mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
@@ -307,8 +321,11 @@ class TestLeaveRequests(TestHrHolidaysCommon):
'holiday_status_id': leave_type.id,
'number_of_days': 20,
'employee_id': employee.id,
'state': 'confirm',
'date_from': time.strftime('2018-1-1'),
'date_to': time.strftime('%Y-1-1'),
})
allocation.action_approve()
allocation.action_validate()
leave1 = self.env['hr.leave'].create({
'name': 'Holiday 1 week',
@@ -374,7 +391,7 @@ class TestLeaveRequests(TestHrHolidaysCommon):
'name': 'Sick',
'request_unit': 'hour',
'leave_validation_type': 'both',
'allocation_type': 'no',
'requires_allocation': 'no',
})
leave1 = self.env['hr.leave'].create({
'name': 'Sick 1 week during christmas snif',
@@ -428,13 +445,13 @@ class TestLeaveRequests(TestHrHolidaysCommon):
# 12 13 14 15 16 17 18
# 19 20 21 22 23 24 25
# 26 27 28 29 30 31
local_date_from = datetime(2019, 1, 1, 7, 0, 0)
local_date_to = datetime(2019, 1, 1, 19, 0, 0)
local_date_from = datetime(2020, 1, 1, 7, 0, 0)
local_date_to = datetime(2020, 1, 1, 19, 0, 0)
for tz in timezones_to_test:
self._test_leave_with_tz(tz, local_date_from, local_date_to, 1)
# We, Th, Fr, Mo, Tu, We => 6 days
local_date_from = datetime(2019, 1, 1, 7, 0, 0)
local_date_to = datetime(2019, 1, 8, 19, 0, 0)
local_date_from = datetime(2020, 1, 1, 7, 0, 0)
local_date_to = datetime(2020, 1, 8, 19, 0, 0)
for tz in timezones_to_test:
self._test_leave_with_tz(tz, local_date_from, local_date_to, 6)
self._test_leave_with_tz(tz, local_date_from, local_date_to, 6)#TODO JUD check why this fails
@@ -18,8 +18,7 @@ class TestOutOfOffice(TestHrHolidaysCommon):
self.leave_type = self.env['hr.leave.type'].create({
'name': 'Legal Leaves',
'time_type': 'leave',
'allocation_type': 'no',
'validity_start': False,
'requires_allocation': 'no',
})
def test_leave_ooo(self):
@@ -66,8 +65,7 @@ class TestOutOfOfficePerformance(TestHrHolidaysCommon, TransactionCaseWithUserDe
self.leave_type = self.env['hr.leave.type'].create({
'name': 'Legal Leaves',
'time_type': 'leave',
'allocation_type': 'no',
'validity_start': False,
'requires_allocation': 'no',
})
self.leave_date_end = (datetime.today() + relativedelta(days=3))
self.leave = self.env['hr.leave'].create({
+30 -5
View File
@@ -28,21 +28,21 @@
<menuitem
id="menu_open_allocation"
name="My Allocation Requests"
name="My Allocations"
parent="menu_hr_holidays_my_leaves"
action="hr_leave_allocation_action_my"
sequence="3"/>
<menuitem
id="menu_hr_holidays_dashboard"
name="Everyone"
name="Overview"
parent="menu_hr_holidays_root"
sequence="2"
action="action_hr_holidays_dashboard"/>
<menuitem
id="menu_hr_holidays_approvals"
name="Managers"
name="Approvals"
parent="menu_hr_holidays_root"
sequence="3"
groups="hr_holidays.group_hr_holidays_responsible"/>
@@ -65,7 +65,7 @@
id="menu_hr_holidays_report"
name="Reporting"
parent="menu_hr_holidays_root"
groups="hr_holidays.group_hr_holidays_manager"
groups="hr_holidays.group_hr_holidays_user"
sequence="4"/>
<menuitem
@@ -82,10 +82,19 @@
action="act_hr_employee_holiday_request"
sequence="3"/>
<menuitem
id="menu_hr_holidays_report_employee_time_off"
name="by Employee and Time Off Type"
parent="menu_hr_holidays_report"
action="action_hr_holidays_by_employee_and_type_report"
groups="base.group_no_one"
sequence="5"/>
<menuitem
id="menu_hr_holidays_configuration"
name="Configuration"
parent="menu_hr_holidays_root"
groups="hr_holidays.group_hr_holidays_manager"
sequence="5"/>
<menuitem
@@ -93,9 +102,25 @@
action="open_view_holiday_status"
name="Time Off Types"
parent="menu_hr_holidays_configuration"
groups="hr_holidays.group_hr_holidays_user"
groups="hr_holidays.group_hr_holidays_manager"
sequence="1"/>
<menuitem
id="hr_holidays_accrual_menu_configuration"
action="open_view_accrual_plans"
name="Accrual Plans"
parent="menu_hr_holidays_configuration"
groups="hr_holidays.group_hr_holidays_manager"
sequence="2"/>
<menuitem
id="hr_holidays_public_time_off_menu_configuration"
action="open_view_public_holiday"
name="Public Holidays"
parent="menu_hr_holidays_configuration"
groups="hr_holidays.group_hr_holidays_manager"
sequence="3"/>
<menuitem id="hr_holidays_menu_config_activity_type"
action="mail_activity_type_action_config_hr_holidays"
parent="menu_hr_holidays_configuration"
@@ -0,0 +1,185 @@
<?xml version='1.0' encoding='UTF-8' ?>
<odoo>
<record id="hr_accrual_level_view_form" model="ir.ui.view">
<field name="name">hr.leave.accrual.level.form</field>
<field name="model">hr.leave.accrual.level</field>
<field name="arch" type="xml">
<form string="Accrual Level">
<sheet>
<group>
<field name="sequence" invisible="1" force_save="1"/>
<label for="start_count"/>
<div class="o_col">
<div class="o_row">
<field name="start_count"/>
<field name="start_type" nolabel="1"/><label for="start_type" string="after allocation date" class="o_form_label"/>
</div>
</div>
<field name="is_based_on_worked_time"/>
<label for="added_value"/>
<div class="o_col">
<div class="o_row">
<field name="added_value"/>
<field name="added_value_type" nolabel="1"/>
</div>
</div>
<label for="frequency"/>
<div class="o_col">
<field name="frequency"/>
<div class="o_row">
<label for="week_day" string="on" attrs="{'invisible': [('frequency', '!=', 'weekly')]}"/><field name="week_day" attrs="{'invisible': [('frequency', '!=', 'weekly')]}"/>
<label for="first_day" string="on the" attrs="{'invisible': [('frequency', '!=', 'monthly')]}"/><field name="first_day" attrs="{'invisible': [('frequency', '!=', 'monthly')]}"/>
<label for="first_day" string="on the" attrs="{'invisible': [('frequency', '!=', 'bimonthly')]}"/><field name="first_day" attrs="{'invisible': [('frequency', '!=', 'bimonthly')]}"/><span attrs="{'invisible': [('frequency', '!=', 'bimonthly')]}">and on the</span><field name="second_day" attrs="{'invisible': [('frequency', '!=', 'bimonthly')]}"/>
<label for="first_month_day" string="on the" attrs="{'invisible': [('frequency', '!=', 'biyearly')]}"/><field name="first_month_day" attrs="{'invisible': [('frequency', '!=', 'biyearly')]}"/><field name="first_month" attrs="{'invisible': [('frequency', '!=', 'biyearly')]}"/><span attrs="{'invisible': [('frequency', '!=', 'biyearly')]}">and on the</span><field name="second_month_day" attrs="{'invisible': [('frequency', '!=', 'biyearly')]}"/><field name="second_month" attrs="{'invisible': [('frequency', '!=', 'biyearly')]}"/>
<label for="yearly_day" string="on" attrs="{'invisible': [('frequency', '!=', 'yearly')]}"/><field name="yearly_day" attrs="{'invisible': [('frequency', '!=', 'yearly')]}"/><field name="yearly_month" attrs="{'invisible': [('frequency', '!=', 'yearly')]}"/>
</div>
</div>
<label for="maximum_leave"/>
<div class="o_col">
<div class="o_row">
<field name="maximum_leave"/>
<field name="added_value_type" nolabel="1" readonly="1"/>
</div>
</div>
<field name="action_with_unused_accruals"/>
</group>
</sheet>
</form>
</field>
</record>
<record id="hr_accrual_plan_view_tree" model="ir.ui.view">
<field name="name">hr.leave.accrual.plan.tree</field>
<field name="model">hr.leave.accrual.plan</field>
<field name="arch" type="xml">
<tree string="Accrual Plans">
<field name="name" class="col-3"/>
<field name="time_off_type_id" class="col-3"/>
<field name="employees_count" class="col-6"/>
</tree>
</field>
</record>
<record id="hr_accrual_plan_view_form" model="ir.ui.view">
<field name="name">hr.leave.accrual.plan.form</field>
<field name="model">hr.leave.accrual.plan</field>
<field name="arch" type="xml">
<form string="Accrual Plan">
<sheet>
<div class="oe_button_box" name="button_box" attrs="{'invisible': [('id', '=', False)]}">
<button name="action_open_accrual_plan_employees" type="object" class="oe_stat_button" icon="fa-users">
<field name="employees_count" widget="statinfo"/>
</button>
</div>
<group>
<group>
<field name="name"/>
</group>
<group>
<field name="time_off_type_id"/>
</group>
<group>
<field name="transition_mode" widget="radio"/>
</group>
</group>
<span class="oe_grey" invisible="1">
</span>
<div class="o_hr_holidays_hierarchy">
<div class="o_hr_holidays_title">Rules</div>
<div class="o_hr_holidays_hierarchy_readonly oe_read_only" attrs="{'invisible': [('level_ids', '!=', [])]}">
<h3>No rules added</h3>
<p>
Click on the 'Edit' button to add new rules.
</p>
</div>
<field name="level_ids" mode="kanban" nolabel="1"
class="o_hr_holidays_plan_level_container o_hr_holidays_plan_level_hierarchy"
add-label="Add a new level"
>
<kanban default_order="level">
<field name="start_count"/>
<field name="start_type"/>
<field name="level"/>
<field name="added_value"/>
<field name="added_value_type"/>
<field name="frequency"/>
<field name="week_day"/>
<field name="first_day"/>
<field name="second_day"/>
<field name="first_month_day"/>
<field name="first_month"/>
<field name="second_month_day"/>
<field name="second_month"/>
<field name="yearly_day"/>
<field name="yearly_month"/>
<field name="maximum_leave"/>
<field name="action_with_unused_accruals"/>
<templates>
<div t-name="kanban-box">
<div class="o_hr_holidays_body oe_kanban_global_click">
<div class="o_hr_holidays_timeline text-center">
Level <field name="level"/>
</div>
<t t-if="!read_only_mode">
<a type="edit" t-attf-class="oe_kanban_action oe_kanban_action_a text-black">
<t t-call="level_content"/>
</a>
</t>
<t t-else="">
<t t-call="level_content"/>
</t>
</div>
</div>
<t t-name="level_content">
<div class="o_hr_holidays_card">
<div class="content">
<div>
<t t-if="record.start_count.value > 0">
Starts <field name="start_count" widget="integer"/> <field name="start_type"/> after allocation start date
</t>
<t t-else="">
Starts immediately after allocation start date
</t>
</div>
<div>
Adds <field name="added_value"/> <field name="added_value_type"/>
<field name="frequency"/>
<t t-if="record.frequency.value == 'weekly'">
on <field name="week_day"/>
</t>
<t t-elif="record.frequency.value === 'bimonthly'">
on the <field name="first_day"/> and on the <field name="second_day"/>
</t>
<t t-elif="record.frequency.value === 'biyearly'">
on the <field name="first_month_day"/> <field name="first_month"/> and on the <field name="second_month_day"/> <field name="second_month"/>
</t>
<t t-elif="record.frequency.value === 'yearly'">
on <field name="yearly_day"/> <field name="yearly_month"/>
</t>
</div>
<div t-if="record.maximum_leave.value">
Limit of <field name="maximum_leave"/> <field name="start_type"/>
</div>
<div t-if="record.action_with_unused_accruals.value">
At the end of the year, unused accruals will be <t t-if="record.action_with_unused_accruals.value == 'postponed'">postponed</t><t t-else="">lost</t>
</div>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</div>
</sheet>
</form>
</field>
</record>
<record id="open_view_accrual_plans" model="ir.actions.act_window">
<field name="name">Accrual Plans</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">hr.leave.accrual.plan</field>
<field name="view_mode">tree,form</field>
</record>
</odoo>
@@ -9,8 +9,7 @@
<field name="employee_id"/>
<field name="name"/>
<field name="allocation_type"/>
<filter domain="[('state','in',('confirm', 'validate1'))]" string="To Approve" name="approve"/>
<filter domain="[('state', '=', 'validate1')]" string="Need Second Approval" name="second_approval"/>
<filter domain="[('state', '=', 'confirm')]" string="To Approve" name="second_approval"/>
<filter string="Approved Allocations" domain="[('state', '=', 'validate')]" name="validated"/>
<filter string="Accrual Allocations" domain="[('allocation_type', '=', 'accrual')]" name="accrual_allocation"/>
<separator/>
@@ -41,11 +40,13 @@
]"/>
<group expand="0" string="Group By">
<filter name="group_employee" string="Employee" context="{'group_by':'employee_id'}"/>
<filter name="group_type" string="Type" context="{'group_by':'holiday_status_id'}"/>
<filter name="group_type" string="Time Off Type" context="{'group_by':'holiday_status_id'}"/>
<filter name="group_allocation_type" string="Allocation Type" context="{'group_by':'allocation_type'}"/>
<filter name="group_state" string="Status" context="{'group_by': 'state'}"/>
</group>
<searchpanel>
<field name="state" string="Status"/>
<field name="department_id" string="Department" icon="fa-users"/>
</searchpanel>
</search>
</field>
@@ -70,9 +71,8 @@
class="oe_stat_button"
icon="fa-calendar"
type="action"
attrs="{'invisible': [('holiday_type', '!=', 'employee')]}"
name="%(hr_leave_action_action_approve_department)d"
context="{'search_default_employee_id': [employee_id], 'search_default_holiday_status_id': [holiday_status_id], 'default_holiday_status_id': holiday_status_id}"
attrs="{'invisible': ['|', '|', ('id', '=', False), ('holiday_type', '!=', 'employee'), ('employee_id', '=', False)]}"
name="%(hr_leave_action_holiday_allocation_id)d"
help="Time off Taken/Total Allocated">
<div class="o_stat_info">
<span class="o_stat_value">
@@ -92,19 +92,31 @@
<field name="type_request_unit" invisible="1"/>
<field name="holiday_status_id" context="{'employee_id':employee_id, 'default_date_from':current_date}"/>
<field name="allocation_type" invisible="1" widget="radio"/>
<field name="allocation_type" invisible="1" widget="radio"
attrs="{'readonly': ['|', ('is_officer', '=', False), ('state', 'not in', ('draft', 'confirm'))]}"/>
<field name="is_officer" invisible="1"/>
<field name="accrual_plan_id" attrs="{'invisible': [('allocation_type', '=', 'regular')], 'readonly': ['|', ('is_officer', '=', False), ('state', 'not in', ('draft', 'confirm'))]}"/>
<div class="o_td_label">
<label for="date_from" string="Validity Period" attrs="{'invisible': [('allocation_type', '=', 'accrual')]}"/>
<label for="date_from" string="Start Date" attrs="{'invisible': [('allocation_type', '=', 'regular')]}"/>
</div>
<div class="o_row" name="validity">
<field name="date_from" widget="date" nolabel="1" readonly="1"/>
<i class="fa fa-long-arrow-right mx-2" aria-label="Arrow icon" title="Arrow" attrs="{'invisible': [('allocation_type', '=', 'accrual')]}"/>
<label class="mx-2" for="date_to" string="Run until" attrs="{'invisible': [('allocation_type', '=', 'regular')]}"/>
<field name="date_to" widget="date" nolabel="1" readonly="1"/>
</div>
<field name="number_of_days_display" invisible="1"/>
<field name="number_of_days" invisible="1"/>
<div class="o_td_label">
<label for="number_of_days" string="Duration" attrs="{'invisible': [('allocation_type', '=', 'accrual')]}"/>
<label for="number_of_days" string="Extra days" attrs="{'invisible': [('allocation_type', '!=', 'accrual')]}"/>
<label for="number_of_days" string="Duration"
attrs="{'readonly': [('allocation_type', '=', 'accrual'), ('state', 'not in', ('draft', 'confirm'))]}"/>
</div>
<div name="duration_display">
<field name="number_of_days_display" class="oe_inline" nolabel="1"
attrs="{'readonly': ['|', ('type_request_unit', '=', 'hour'), ('state', 'not in', ('draft', 'confirm'))], 'invisible': [('type_request_unit', '=', 'hour')]}"/>
attrs="{'readonly': ['|', '|', ('type_request_unit', '=', 'hour'), ('state', 'not in', ('draft', 'confirm')), ('allocation_type', '=', 'accrual')], 'invisible': [('type_request_unit', '=', 'hour')]}"/>
<field name="number_of_hours_display" class="oe_inline" nolabel="1"
attrs="{'readonly': ['|', ('type_request_unit', '!=', 'hour'), ('state', 'not in', ('draft', 'confirm'))], 'invisible': [('type_request_unit', '!=', 'hour')]}"/>
attrs="{'readonly': ['|', '|', ('type_request_unit', '!=', 'hour'), ('state', 'not in', ('draft', 'confirm')), ('allocation_type', '=', 'accrual')], 'invisible': [('type_request_unit', '!=', 'hour')]}"/>
<span class="ml8" attrs="{'invisible': [('type_request_unit', '=', 'hour')]}">Days</span>
<span class="ml8" attrs="{'invisible': [('type_request_unit', '!=', 'hour')]}">Hours</span>
</div>
@@ -133,11 +145,9 @@
<field name="priority">16</field>
<field name="arch" type="xml">
<xpath expr="//button[@name='action_confirm']" position="after">
<button string="Approve" name="action_approve" states="confirm" type="object" class="oe_highlight"
attrs="{'invisible': ['|', ('can_approve', '=', False), ('state', '!=', 'confirm')]}"/>
<button string="Validate" name="action_validate" states="validate1" type="object" class="oe_highlight"/>
<button string="Validate" name="action_validate" states="confirm" type="object" class="oe_highlight"/>
<button string="Refuse" name="action_refuse" type="object"
attrs="{'invisible': ['|', ('can_approve', '=', False), ('state', 'not in', ('confirm','validate1','validate'))]}"/>
attrs="{'invisible': ['|', ('can_approve', '=', False), ('state', 'not in', ('confirm','validate'))]}"/>
<button string="Mark as Draft" name="action_draft" type="object"
attrs="{'invisible': ['|', ('can_reset', '=', False), ('state', 'not in', ['confirm', 'refuse'])]}"/>
</xpath>
@@ -145,8 +155,14 @@
<field name="holiday_type" string="Mode" groups="hr_holidays.group_hr_holidays_user" context="{'employee_id':employee_id}" />
</xpath>
<xpath expr="//field[@name='employee_id']" position="replace">
<field name="multi_employee" invisible="1" force_save="1"/>
<!-- Employee id is only visible if the allocation is created specifically for that user in `_action_validate_create_childs` -->
<field name="employee_id" groups="hr_holidays.group_hr_holidays_user"
attrs="{'required': [('holiday_type', '=', 'employee')], 'invisible': [('holiday_type', '!=', 'employee')]}"/>
attrs="{'invisible': ['|', '|', ('holiday_type', '!=', 'employee'), ('employee_id', '=', False), ('state', 'in', ('draft', 'cancel', 'confirm'))]}"/>
<field name="employee_ids" widget="many2many_tags"
groups="hr_holidays.group_hr_holidays_user"
attrs="{'required': [('holiday_type', '=', 'employee'), ('state', 'in', ('draft', 'cancel', 'confirm'))],
'invisible': ['|', ('holiday_type', '!=', 'employee'), ('state', 'not in', ('draft', 'cancel', 'confirm'))]}"/>
</xpath>
<xpath expr="//field[@name='employee_id']" position="after">
<field name="category_id"
@@ -163,27 +179,41 @@
<xpath expr="//field[@name='allocation_type']" position="attributes">
<attribute name="invisible">0</attribute>
</xpath>
<xpath expr="//field[@name='allocation_type']" position="after">
<label for="date_from" attrs="{'invisible': [('allocation_type', '=', 'regular')]}"/>
<div class="o_row" attrs="{'invisible': [('allocation_type', '=', 'regular')]}">
<field name="date_from" class="mr-2" attrs="{'required': [('allocation_type', '=', 'accrual')]}" />
Run until <field name="date_to" help="If no value set, runs indefinitely" class="ml-2"/>
</div>
<label for="date_from" invisible="1"/>
<div attrs="{'invisible': [('allocation_type', '=', 'regular')]}">
<div class="o_row">
<span>Add</span>
<field name="number_per_interval" class="ml8"
attrs="{'required': [('allocation_type', '=', 'accrual')]}"/>
<field name="unit_per_interval"
attrs="{'required': [('allocation_type', '=', 'accrual')]}"/>
<span class="ml8">of time off every</span>
<field name="interval_number" class="ml8"
attrs="{'required': [('allocation_type', '=', 'accrual')]}"/>
<field name="interval_unit"
attrs="{'required': [('allocation_type', '=', 'accrual')]}"/>
</div>
</div>
<xpath expr="//field[@name='date_from']" position="attributes">
<attribute name="readonly">0</attribute>
<attribute name="attrs">{'readonly': [('allocation_type', '=', 'accrual'), ('state', 'not in', ('draft', 'confirm'))]}</attribute>
</xpath>
<xpath expr="//field[@name='date_to']" position="attributes">
<attribute name="readonly">0</attribute>
<attribute name="attrs">{'readonly': [('allocation_type', '=', 'accrual'), ('state', 'not in', ('draft', 'confirm'))]}</attribute>
</xpath>
</field>
</record>
<record id="hr_leave_allocation_view_form_dashboard" model="ir.ui.view">
<field name="name">hr.leave.view.form.dashboard</field>
<field name="model">hr.leave.allocation</field>
<field name="inherit_id" ref="hr_holidays.hr_leave_allocation_view_form"/>
<field name="mode">primary</field>
<field name="priority">100</field>
<field name="arch" type="xml">
<xpath expr="//header" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<div name="button_box" position="attributes">
<attribute name="invisible">1</attribute>
</div>
<div name="validity" position="attributes">
<attribute name="invisible">1</attribute>
</div>
<label for="date_from" position="attributes">
<attribute name="invisible">1</attribute>
</label>
<xpath expr="//sheet" position="after">
<footer>
<button string="Confirm" name="action_confirm" states="draft" type="object" class="oe_highlight"/>
<button string="Discard" special="cancel" data-hotkey="z" />
</footer>
</xpath>
</field>
</record>
@@ -199,20 +229,19 @@
<field name="holiday_status_id" class="font-weight-bold"/>
<field name="name"/>
<field name="duration_display" string="Duration"/>
<field name="date_from" string="Validity Start" optional="hide"/>
<field name="date_to" string="Validity Stop" optional="hide"/>
<field name="allocation_type"/>
<field name="message_needaction" invisible="1"/>
<field name="active_employee" invisible="1"/>
<field name="state" widget="badge" decoration-info="state == 'draft'" decoration-warning="state in ('confirm','validate1')" decoration-success="state == 'validate'"/>
<button string="Approve" name="action_approve" type="object"
icon="fa-thumbs-up"
states="confirm"
groups="hr_holidays.group_hr_holidays_responsible"/>
<field name="state" widget="badge" decoration-info="state == 'draft'" decoration-warning="state == 'confirm'" decoration-success="state == 'validate'"/>
<button string="Validate" name="action_validate" type="object"
icon="fa-check"
states="validate1"
states="confirm"
groups="hr_holidays.group_hr_holidays_manager"/>
<button string="Refuse" name="action_refuse" type="object"
icon="fa-times"
states="confirm,validate1"
states="confirm"
groups="hr_holidays.group_hr_holidays_manager"/>
<field name="activity_exception_decoration" widget="activity_exception"/>
</tree>
@@ -232,9 +261,6 @@
<xpath expr="//field[@name='department_id']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//button[@name='action_approve']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//button[@name='action_validate']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
@@ -328,13 +354,10 @@
<span t-attf-class="badge badge-pill {{ classname }}"><t t-esc="record.state.value"/></span>
</div>
<div t-if="record.can_approve.raw_value">
<button t-if="record.state.raw_value === 'confirm'" name="action_approve" type="object" class="btn btn-link btn-sm pl-0">
<i class="fa fa-thumbs-up"/> Approve
</button>
<button t-if="record.state.raw_value === 'validate1'" name="action_validate" type="object" class="btn btn-link btn-sm pl-0">
<button t-if="record.state.raw_value === 'confirm'" name="action_validate" type="object" class="btn btn-link btn-sm pl-0">
<i class="fa fa-check"/> Validate
</button>
<button t-if="['confirm', 'validate1'].includes(record.state.raw_value)" name="action_refuse" type="object" class="btn btn-link btn-sm pl-0">
<button t-if="record.state.raw_value === 'confirm'" name="action_refuse" type="object" class="btn btn-link btn-sm pl-0">
<i class="fa fa-times"/> Refuse
</button>
</div>
@@ -438,29 +461,6 @@
<field name="binding_model_id" ref="hr_holidays.model_hr_leave_allocation"/>
<field name="binding_view_types">list</field>
<field name="state">code</field>
<field name="code">action = records.action_approve()</field>
<field name="code">action = records.action_validate()</field>
</record>
<record id="hr_leave_allocation_view_form_dashboard" model="ir.ui.view">
<field name="name">hr.leave.view.form.dashboard</field>
<field name="model">hr.leave.allocation</field>
<field name="inherit_id" ref="hr_holidays.hr_leave_allocation_view_form"/>
<field name="mode">primary</field>
<field name="priority">100</field>
<field name="arch" type="xml">
<xpath expr="//header" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<div name="button_box" position="attributes">
<attribute name="invisible">1</attribute>
</div>
<xpath expr="//sheet" position="after">
<footer>
<button string="Save" special="save" data-hotkey="v" class="oe_highlight" />
<button string="Discard" special="cancel" data-hotkey="z" />
</footer>
</xpath>
</field>
</record>
</odoo>
@@ -7,7 +7,6 @@
<field name="arch" type="xml">
<search string="Search Time Off Type">
<field name="name" string="Time Off Types"/>
<field name="code"/>
<field name="create_calendar_meeting"/>
<separator/>
<filter string="Archived" name="inactive" domain="[('active','=',False)]"/>
@@ -22,16 +21,22 @@
<form string="Time Off Type">
<sheet>
<div class="oe_button_box" name="button_box">
<button class="oe_stat_button" type="object" name="action_see_days_allocated" icon="fa-archive" attrs="{'invisible': [('allocation_type', '=', 'no')]}">
<button class="oe_stat_button" type="object" name="action_see_days_allocated" icon="fa-calendar" attrs="{'invisible': ['|', ('requires_allocation', '=', 'no'), ('id', '=', False)]}">
<div class="o_stat_info">
<field name="group_days_allocation"/>
<span class="o_stat_text">Group Days Allocated</span>
<span class="o_stat_text">Allocations</span>
</div>
</button>
<button class="oe_stat_button" type="object" name="action_see_group_leaves" icon="fa-archive">
<button class="oe_stat_button" type="object" name="action_see_group_leaves" icon="fa-calendar" attrs="{'invisible': [('id', '=', False)]}">
<div class="o_stat_info">
<field name="group_days_leave"/>
<span class="o_stat_text">Group Time Off</span>
<span class="o_stat_text">Time Off</span>
</div>
</button>
<button class="oe_stat_button" type="object" name="action_see_accrual_plans" icon="fa-calendar" attrs="{'invisible': [('id', '=', False)]}">
<div class="o_stat_info">
<field name="accrual_count"/>
<span class="o_stat_text">Accruals</span>
</div>
</button>
</div>
@@ -40,42 +45,33 @@
<h1><field name="name"/></h1>
</div>
<group>
<group name="description" string="Options">
<group name="leave_validation" id="time_off_requests">
<h2>Time Off Requests</h2>
<field name="active" invisible="1"/>
<field name="code" string="Payroll Code"/>
<field name="request_unit" widget="radio"/>
</group>
<group name="validity" string="Validity">
<field name="validity_start"/>
<field name="validity_stop"/>
</group>
<group name="allocation_validation" string="Allocation Requests">
<field name="allocation_type" widget="radio" force_save="1"/>
<field name="allocation_validation_type" string="Approval" widget="radio" attrs="{'invisible': [('allocation_type', '!=', 'fixed_allocation')]}"/>
<field name="leave_validation_type" string="Approval" widget="radio"/>
<field name="responsible_id" domain="[('share', '=', False)]"
attrs="{
'invisible': [('leave_validation_type', 'in', ['no_validation', 'manager']), '|', ('allocation_type', '=', 'no'), ('allocation_validation_type', '=', 'manager')],
'required': ['|', ('leave_validation_type', 'in', ['hr', 'both']), '&amp;', ('allocation_type', 'in', ['fixed_allocation', 'fixed']), ('allocation_validation_type', 'in', ['hr', 'both'])]}"/>
'invisible': [('leave_validation_type', 'in', ['no_validation', 'manager']), '|', ('requires_allocation', '=', 'no'), ('allocation_validation_type', '=', 'officer')]}"/>
<field name="request_unit" widget="radio-inline"/>
<field name="support_document"/>
</group>
<group name="leave_validation" string="Time Off Requests">
<field name="leave_validation_type" string="Approval" widget="radio"/>
</group>
<group name="notification" string="Notification" colspan="2" groups="base.group_no_one">
<group>
<field name="leave_notif_subtype_id"
domain="[('res_model','=','hr.leave')]"
context="{'default_name': name, 'default_res_model': 'hr.leave'}"/>
<field name="allocation_notif_subtype_id"
domain="[('res_model','=','hr.leave.allocation')]"
context="{'default_name': name, 'default_res_model': 'hr.leave.allocation'}"/>
</group>
</group>
<group name="calendar" string="Calendar" groups="base.group_no_one">
<field name="create_calendar_meeting"/>
<field name="color_name"/>
<field name="company_id" groups="base.group_multi_company"/>
</group>
<group name="allocation_validation" id="allocation_requests">
<h2>Allocation Requests</h2>
<field name="requires_allocation" widget="radio" options="{'horizontal':true}"/>
<field name="employee_requests" widget="radio" attrs="{'invisible': [('requires_allocation', '=', 'no')]}"/>
<field name="allocation_validation_type" string="Approval" widget="radio" attrs="{'invisible': [('requires_allocation', '=', 'no')]}"/>
</group>
</group>
<group>
<group id="payroll">
</group>
<group name="visual" id="visual">
<h2>Visual</h2>
<field name="color" widget="color_picker"/>
<field class="d-flex flex-wrap" name="icon_id" widget="radio_image" options="{'horizontal': true}"/>
</group>
</group>
</sheet>
</form>
@@ -111,12 +107,9 @@
<tree string="Time Off Type">
<field name="sequence" widget="handle"/>
<field name="display_name"/>
<field name="code"/>
<field name="allocation_type"/>
<field name="leave_validation_type"/>
<field name="allocation_validation_type"/>
<field name="validity_start"/>
<field name="validity_stop"/>
<field name="requires_allocation" optional="hide"/>
<field name="leave_validation_type" optional="hide"/>
<field name="allocation_validation_type" optional="hide"/>
<field name="company_id" groups="base.group_multi_company"/>
</tree>
</field>
+90 -86
View File
@@ -1,17 +1,6 @@
<?xml version='1.0' encoding='UTF-8' ?>
<odoo>
<record model="ir.actions.server" id="action_report_to_payslip">
<field name="name">Report to Payslip</field>
<field name="model_id" ref="model_hr_leave"/>
<field name="binding_model_id" ref="model_hr_leave" />
<field name="state">code</field>
<field name="code">
if records:
records.write({'payslip_status': True})
</field>
</record>
<record model="ir.actions.server" id="action_manager_approval">
<field name="name">Manager Approval</field>
<field name="model_id" ref="model_hr_leave"/>
@@ -64,13 +53,12 @@
<filter string="My Time Off" name="my_leaves" domain="[('employee_id.user_id', '=', uid)]"/>
<separator/>
<filter string="Active Employee" name="active_employee" domain="[('active_employee','=',True)]"/>
<separator/>
<filter string="To Report in Payslip" name="gray" domain="[('payslip_status', '=', False)]" groups="hr_holidays.group_hr_holidays_user"/>
<separator/>
<filter name="filter_date_from" date="date_from"/>
<separator/>
<filter name="year" string="Active Time Off"
<filter name="active_time_off" string="Active Time Off"
domain="[('holiday_status_id.active', '=', True)]" help="Active Time Off"/>
<filter name="archive" string="Archived Time Off"
domain="[('holiday_status_id.active', '=', False)]" help="Archived Time Off"/>
<filter invisible="1" string="Late Activities" name="activities_overdue"
domain="[('my_activity_date_deadline', '&lt;', context_today().strftime('%Y-%m-%d'))]"
help="Show all records which has next action date is before today"/>
@@ -83,11 +71,13 @@
<filter name="group_employee" string="Employee" context="{'group_by':'employee_id'}"/>
<filter name="group_type" string="Type" context="{'group_by':'holiday_status_id'}"/>
<filter name="group_state" string="Status" context="{'group_by': 'state'}"/>
<filter name="group_company" string="Company" context="{'group_by':'employee_company_id'}" groups="base.group_multi_company"/>
<separator/>
<filter name="group_date_from" string="Start Date" context="{'group_by':'date_from'}"/>
</group>
<searchpanel>
<field name="state" string="Status"/>
<field name="department_id" string="Department" icon="fa-users"/>
</searchpanel>
</search>
</field>
@@ -106,6 +96,7 @@
<field name="can_approve"/>
<field name="holiday_status_id"/>
<field name="state"/>
<field name="supported_attachment_ids_count"/>
<templates>
<t t-name="kanban-box">
<div class="oe_kanban_global_click container">
@@ -150,7 +141,7 @@
</div>
<div class="row no-gutters" t-if="record.can_approve.raw_value">
<div class="col-3"/>
<div class="col=9" t-if="['confirm', 'validate1'].includes(record.state.raw_value)">
<div class="col=6" t-if="['confirm', 'validate1'].includes(record.state.raw_value)">
<button t-if="record.state.raw_value === 'confirm'" name="action_approve" type="object" class="btn btn-link btn-sm pl-0">
<i class="fa fa-thumbs-up"/> Approve
</button>
@@ -161,6 +152,11 @@
<i class="fa fa-times"/> Refuse
</button>
</div>
<div class="col-3 text-right">
<button t-if="record.supported_attachment_ids_count.raw_value > 0" name="action_documents" type="object" class="btn btn-link btn-sm pl-0">
<i class="fa fa-paperclip"> <field name="supported_attachment_ids_count" nolabel="1"/></i>
</button>
</div>
</div>
</div>
</t>
@@ -205,6 +201,7 @@
<form string="Time Off Request">
<field name="can_reset" invisible="1"/>
<field name="can_approve" invisible="1"/>
<field name="holiday_allocation_id" invisible="1" force_save="1"/>
<header>
<button string="Confirm" name="action_confirm" states="draft" type="object" class="oe_highlight"/>
<button string="Approve" name="action_approve" type="object" class="oe_highlight" attrs="{'invisible': ['|', ('can_approve', '=', False), ('state', '!=', 'confirm')]}"/>
@@ -215,7 +212,7 @@
<field name="state" widget="statusbar" statusbar_visible="confirm,validate"/>
</header>
<sheet>
<div class="alert alert-info" role="alert" attrs="{'invisible': ['|', ('tz_mismatch', '=', False), ('holiday_type', '=', 'category')]}">
<div class="alert alert-info" role="alert" attrs="{'invisible': ['|', ('request_unit_hours', '=', False), '|', ('tz_mismatch', '=', False), ('holiday_type', '=', 'category')]}">
<span attrs="{'invisible': [('holiday_type', '!=', 'employee')]}">
The employee has a different timezone than yours! Here dates and times are displayed in the employee's timezone
</span>
@@ -230,21 +227,16 @@
<field name="tz_mismatch" invisible="1"/>
<field name="holiday_type" invisible="1"/>
<field name="leave_type_request_unit" invisible="1"/>
<div class="oe_title" name="title">
<div name="title">
<field name="display_name" invisible="1"/>
<h1>
<field name="employee_id" nolabel="1" readonly="1" force_save="1" invisible="1"/>
</h1>
<h2>
<field name="holiday_status_id" nolabel="1" domain="[('valid', '=', True), '&amp;', ('virtual_remaining_leaves', '&gt;', 0), '|', ('allocation_type', 'in', ['fixed_allocation', 'no']),'&amp;',('allocation_type', '=', 'fixed'), ('max_leaves', '>', '0')]" context="{'employee_id':employee_id, 'default_date_from':date_from}" options="{'no_create': True, 'no_open': True}" class="w-100"/>
</h2>
<field name="employee_id" nolabel="1" readonly="1" force_save="1" invisible="1"/>
</div>
<group>
<group>
<label for="request_date_from" string="Dates"/>
<group name="col_left">
<field name="holiday_status_id" force_save="1" domain="['|', ('requires_allocation', '=', 'no'), '&amp;', ('has_valid_allocation', '=', True), '&amp;', ('virtual_remaining_leaves', '&gt;', 0), ('max_leaves', '>', '0')]" context="{'employee_id':employee_id, 'default_date_from':date_from}" options="{'no_create': True, 'no_open': True}" class="w-100"/>
<label for="request_date_from" string="Dates" id="label_dates"/>
<div>
<field name="date_from" invisible="1"/>
<field name="date_from" invisible="1" widget="daterange"/>
<field name="date_to" invisible="1"/>
<div class="o_row o_row_readonly">
<span class="oe_inline"
@@ -254,7 +246,8 @@
<field name="request_date_from" class="oe_inline" nolabel="1"
attrs="{'readonly': [('state', 'not in', ('draft', 'confirm'))],
'required': ['|', ('date_from', '=', False), ('date_to', '=', False)]
}"/>
}"
widget="daterange" options="{'related_end_date': 'request_date_to'}"/>
<span class="oe_inline"
attrs="{'invisible': ['|', ('request_unit_half', '=', True), ('request_unit_hours', '=', True)]}">
To
@@ -264,7 +257,8 @@
'readonly': [('state', 'not in', ('draft', 'confirm'))],
'invisible': ['|', ('request_unit_half', '=', True), ('request_unit_hours', '=', True)],
'required': ['|', ('date_from', '=', False), ('date_to', '=', False)]
}"/>
}"
widget="daterange" options="{'related_end_date': 'request_date_from'}"/>
<field name="request_date_from_period" class="oe_inline"
string="In"
options="{'horizontal': True}"
@@ -280,17 +274,18 @@
}"/>
<label for="request_unit_half" attrs="{
'invisible': [('leave_type_request_unit', '=', 'day')]
}"/>
}"/>
<field name="request_unit_hours" attrs="{
'readonly': [('state', 'not in', ('draft', 'confirm'))],
'invisible': [('leave_type_request_unit', '!=', 'hour')]
}" class="ml-5"/>
}" class="ml-5"/>
<label for="request_unit_hours" attrs="{
'invisible': [('leave_type_request_unit', '!=', 'hour')]
}"/>
<field name="request_unit_custom" invisible="1" attrs="{
'readonly': [('state', 'not in', ('draft', 'confirm'))],
}"/>
}" class="ml-5"/>
<label for="request_unit_custom" invisible="1"/>
</div>
<div class="o_row o_row_readonly">
@@ -332,7 +327,7 @@
<field name="name" attrs="{'readonly': [('state', 'not in', ('draft', 'confirm'))]}" widget="text"/>
<field name="user_id" invisible="1"/>
<field name="leave_type_support_document" invisible="1"/>
<label for="supported_attachment_ids" string="Supporting Documents"
<label for="supported_attachment_ids" string="Supporting Document"
attrs="{'invisible': ['|', ('leave_type_support_document', '=', False), ('state', 'not in', ('draft', 'confirm', 'validate1'))]}"/>
<field name="supported_attachment_ids" widget="many2many_binary" nolabel="1"
attrs="{'invisible': ['|', ('leave_type_support_document', '=', False), ('state', 'not in', ('draft', 'confirm', 'validate1'))]}"/>
@@ -364,19 +359,6 @@
</field>
</record>
<record id="hr_leave_view_dashboard" model="ir.ui.view">
<field name="name">hr.leave.view.dashboard</field>
<field name="model">hr.leave</field>
<field name="arch" type="xml">
<calendar js_class="time_off_calendar" string="Time Off Request" form_view_id="%(hr_holidays.hr_leave_view_form_dashboard)d" event_open_popup="true" date_start="date_from" date_stop="date_to" mode="month" quick_add="False" show_unusual_days="True" color="holiday_status_id" hide_time="True">
<field name="display_name"/>
<field name="holiday_status_id" filters="1" invisible="1"/>
<field name="state" invisible="1"/>
<field name="is_hatched" invisible="1" />
</calendar>
</field>
</record>
<record id="hr_leave_view_form_dashboard_new_time_off" model="ir.ui.view">
<field name="name">hr.leave.view.form.dashboard.new.time.off</field>
<field name="model">hr.leave</field>
@@ -384,15 +366,26 @@
<field name="mode">primary</field>
<field name="priority">17</field>
<field name="arch" type="xml">
<xpath expr="//sheet" position="after">
<footer>
<button string="Save" special="save" data-hotkey="v" class="oe_highlight" />
<button string="Discard" special="cancel" data-hotkey="z" />
</footer>
<xpath expr="//group[@name='col_left']" position="attributes">
<attribute name="colspan">5</attribute>
</xpath>
</field>
</record>
<record id="hr_leave_view_dashboard" model="ir.ui.view">
<field name="name">hr.leave.view.dashboard</field>
<field name="model">hr.leave</field>
<field name="arch" type="xml">
<calendar js_class="time_off_calendar" string="Time Off Request" form_view_id="%(hr_holidays.hr_leave_view_form_dashboard_new_time_off)d" event_open_popup="true" date_start="date_from" date_stop="date_to" mode="year" quick_add="False" show_unusual_days="True" color="color" hide_time="True">
<field name="display_name"/>
<field name="holiday_status_id" filters="1" invisible="1" color="color"/>
<field name="state" invisible="1"/>
<field name="is_hatched" invisible="1" />
<field name="is_striked" invisible="1"/>
</calendar>
</field>
</record>
<record id="hr_leave_view_form_manager" model="ir.ui.view">
<field name="name">hr.leave.view.form.manager</field>
<field name="model">hr.leave</field>
@@ -400,26 +393,34 @@
<field name="mode">primary</field>
<field name="priority">16</field>
<field name="arch" type="xml">
<div name="title" position="replace">
<field name="display_name" invisible="1"/>
<div class="oe_title" name="title">
<h2>
<field name="holiday_status_id" nolabel="1" options="{'no_create': True, 'no_open': True}"/>
</h2>
</div>
<field name="holiday_status_id" position="replace"/>
<div name="title" position="inside">
<h1 class="d-flex flex-row justify-content-between">
<field name="holiday_status_id" options="{'no_open': True}"/>
</h1>
</div>
<field name="employee_id" position="replace"/>
<label id="label_dates" position="before">
<field name="multi_employee" invisible="1" force_save="1"/>
<field name="employee_id" groups="hr_holidays.group_hr_holidays_user" attrs="{
'invisible': ['|', '|', ('holiday_type', '!=', 'employee'), ('state', '!=', 'validate'), ('employee_id', '=', False)]
}" widget="many2one_avatar_employee"/>
<field name="employee_ids" groups="hr_holidays.group_hr_holidays_user" attrs="{
'required': [('holiday_type', '=', 'employee')],
'invisible': ['|', ('holiday_type', '!=', 'employee'), '&amp;', ('state', '=', 'validate'), ('employee_id', '!=', False)],
}" widget="many2many_tags"/>
</label>
<field name="name" position="replace"/>
<field name="user_id" position="before">
<field name="name"/>
</field>
<xpath expr="//group[@name='col_right']" position="replace">
<group>
<widget name="hr_leave_stats"/>
</group>
<group>
<field name="holiday_type" string="Mode"
groups="hr_holidays.group_hr_holidays_user"/>
<field name="employee_id" groups="hr_holidays.group_hr_holidays_user" attrs="{
'required': [('holiday_type', '=', 'employee')],
'invisible': [('holiday_type', '!=', 'employee')]
}"/>
<field name="mode_company_id" string="Company" groups="hr_holidays.group_hr_holidays_user" attrs="{
'required': [('holiday_type', '=', 'company')],
'invisible': [('holiday_type', '!=', 'company')]
@@ -430,15 +431,8 @@
}"/>
<field name="department_id" groups="hr_holidays.group_hr_holidays_user" attrs="{
'required': [('holiday_type', '=', 'department')],
'invisible': [('holiday_type', 'not in', ('employee', 'department'))]
'invisible': [('holiday_type', '!=', 'department')]
}"/>
<field name="payslip_status" groups="hr_holidays.group_hr_holidays_user" widget="toggle_button"/>
</group>
<group groups="hr_holidays.group_hr_holidays_manager" string="Manager's Comment">
<field name="report_note" placeholder="e.g. Report to the next month..." nolabel="1"/>
</group>
<group>
<widget name="hr_leave_stats"/>
</group>
</xpath>
</field>
@@ -448,11 +442,12 @@
<field name="name">hr.leave.view.calendar</field>
<field name="model">hr.leave</field>
<field name="arch" type="xml">
<calendar js_class="time_off_calendar_all" string="Time Off Request" form_view_id="%(hr_holidays.hr_leave_view_form_dashboard)d" event_open_popup="true" date_start="date_from" date_stop="date_to" mode="month" show_unusual_days="True" quick_add="False" color="employee_id">
<calendar js_class="time_off_calendar_all" string="Time Off Request" form_view_id="%(hr_holidays.hr_leave_view_form_dashboard)d" event_open_popup="true" date_start="date_from" date_stop="date_to" mode="month" show_unusual_days="True" quick_add="False" color="color">
<field name="display_name"/>
<field name="holiday_status_id" filters="1" invisible="1"/>
<field name="holiday_status_id" color="color" filters="1" invisible="1"/>
<field name="employee_id" filters="1" invisible="1"/>
<field name="is_hatched" invisible="1" />
<field name="is_striked" invisible="1"/>
</calendar>
</field>
</record>
@@ -470,7 +465,6 @@
<field name="date_from"/>
<field name="date_to"/>
<field name="duration_display" string="Duration"/>
<field name="payslip_status" widget="toggle_button" options='{"active": "Reported in last payslips", "inactive": "To Report in Payslip"}' groups="hr_holidays.group_hr_holidays_user" nolabel="1"/>
<field name="state" widget="badge" decoration-info="state == 'draft'" decoration-warning="state in ('confirm','validate1')" decoration-success="state == 'validate'"/>
<field name="active_employee" invisible="1"/>
<field name="category_id" invisible="1"/>
@@ -509,9 +503,6 @@
<xpath expr="//field[@name='holiday_type']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//field[@name='payslip_status']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//button[@name='action_approve']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
@@ -544,9 +535,6 @@
<xpath expr="//filter[@name='my_leaves']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//filter[@name='gray']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//filter[@name='group_employee']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
@@ -563,9 +551,6 @@
<xpath expr="//filter[@name='my_leaves']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//filter[@name='gray']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//filter[@name='group_employee']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
@@ -577,7 +562,7 @@
<field name="res_model">hr.leave</field>
<field name="view_mode">calendar,tree,form,activity</field>
<field name="domain">[('user_id', '=', uid)]</field>
<field name="context">{'short_name': 1}</field>
<field name="context">{'short_name': 1, 'search_default_active_time_off': 1}</field>
<field name="search_view_id" ref="hr_holidays.hr_leave_view_search_my"/>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
@@ -610,7 +595,7 @@
</record>
<record id="hr_leave_action_my_request" model="ir.actions.act_window">
<field name="name">Time Off Requests</field>
<field name="name">Time Off Request</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">hr.leave</field>
<field name="view_mode">form</field>
@@ -623,7 +608,7 @@
</record>
<record id="hr_leave_action_my" model="ir.actions.act_window">
<field name="name">My Time Off Requests</field>
<field name="name">My Time Off</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">hr.leave</field>
<field name="view_mode">tree,form,kanban,activity</field>
@@ -662,6 +647,7 @@
'search_default_approve': 1,
'search_default_managed_people': 2,
'search_default_active_employee': 3,
'search_default_active_time_off': 4,
'hide_employee_name': 1}
</field>
<field name="domain">[]</field>
@@ -674,6 +660,24 @@
</field>
</record>
<record id="hr_leave_action_holiday_allocation_id" model="ir.actions.act_window">
<field name="name">Time Off</field>
<field name="res_model">hr.leave</field>
<field name="view_mode">tree,kanban,form,calendar,activity</field>
<field name="search_view_id" ref="hr_holidays.hr_leave_view_search_manager"/>
<field name="context">{
'hide_employee_name': 1}
</field>
<field name="domain">[('holiday_allocation_id', '=', active_id)]</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Meet the time off dashboard.
</p><p>
A great way to keep track on employee’s PTOs, sick days, and approval status.
</p>
</field>
</record>
<record id="action_view_tree_manager_approve" model="ir.actions.act_window.view">
<field name="sequence" eval="1"/>
<field name="view_mode">tree</field>
+18
View File
@@ -168,6 +168,7 @@
</xpath>
<div name="button_box" position="inside">
<field name="show_leaves" invisible="1"/>
<field name="allocations_count" invisible="1"/>
<field name="is_absent" invisible="1"/>
<field name="hr_icon_display" invisible="1"/>
<button disabled="1"
@@ -204,6 +205,23 @@
</span>
</div>
</button>
<button name="%(act_hr_employee_holiday_request)d"
type="action"
class="oe_stat_button"
icon="fa-sun-o"
attrs="{'invisible': [('allocations_count','&lt;', 1)]}"
context="{'search_default_employee_id': active_id}"
groups="base.group_user"
help="Remaining leaves">
<div class="o_field_widget o_stat_info">
<span class="o_stat_value">
<field name="allocations_count"/>
</span>
<span class="o_stat_text">
Allocations
</span>
</div>
</button>
</div>
</field>
</record>
@@ -1,6 +1,16 @@
<?xml version='1.0' encoding='UTF-8' ?>
<odoo>
<!-- Holiday on resource leave -->
<record id="resource_calendar_leaves_view_search_inherit" model="ir.ui.view">
<field name="name">resource.calendar.leaves.search.inherit</field>
<field name="model">resource.calendar.leaves</field>
<field name="inherit_id" ref="resource.view_resource_calendar_leaves_search"/>
<field name="arch" type="xml">
<filter name="resource" position="attributes">
<attribute name="invisible">1</attribute>
</filter>
</field>
</record>
<record id="resource_calendar_leave_form_inherit" model="ir.ui.view">
<field name="name">resource.calendar.leaves.form.inherit</field>
<field name="model">resource.calendar.leaves</field>
@@ -11,4 +21,63 @@
</field>
</field>
</record>
<record id="resource_calendar_leaves_tree_inherit" model="ir.ui.view">
<field name="name">resource.calendar.leaves.tree.inherit</field>
<field name="model">resource.calendar.leaves</field>
<field name="inherit_id" ref="resource.resource_calendar_leave_tree"/>
<field name="mode">primary</field>
<field name="priority">10</field>
<field name="arch" type="xml">
<xpath expr="//tree" position="attributes">
<attribute name="editable">bottom</attribute>
</xpath>
<xpath expr="//field[@name='name']" position="attributes">
<attribute name="string">Name</attribute>
</xpath>
<xpath expr="//field[@name='resource_id']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//field[@name='date_to']" position="after">
<xpath expr="//field[@name='calendar_id']" position="move"/>
</xpath>
</field>
</record>
<record id="resource_calendar_global_leaves_action_from_calendar" model="ir.actions.act_window">
<field name="name">Resource Time Off</field>
<field name="res_model">resource.calendar.leaves</field>
<field name="view_mode">tree</field>
<field name="view_id" ref="resource_calendar_leaves_tree_inherit"/>
<field name="domain">[('resource_id', '=', False)]</field>
<field name="context">{
'default_calendar_id': active_id,
'search_default_calendar_id': active_id,
'search_default_filter_date': True}</field>
<field name="search_view_id" ref="hr_holidays.resource_calendar_leaves_view_search_inherit"/>
</record>
<record id="resource_calendar_form_inherit" model="ir.ui.view">
<field name="name">resource.calendar.form.inherit</field>
<field name="model">resource.calendar</field>
<field name="inherit_id" ref="resource.resource_calendar_form"/>
<field name="arch" type="xml">
<xpath expr="//button[@name='%(resource.resource_calendar_leaves_action_from_calendar)d']" position="before">
<button name="%(resource_calendar_global_leaves_action_from_calendar)d" type="action"
string="Public Time Off" icon="fa-sun-o"
class="oe_stat_button"/>
</xpath>
</field>
</record>
<record id="open_view_public_holiday" model="ir.actions.act_window">
<field name="name">Public Holidays</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">resource.calendar.leaves</field>
<field name="view_mode">tree,form</field>
<field name="domain">[('resource_id', '=', False)]</field>
<field name="view_id" ref="resource_calendar_leaves_tree_inherit"/>
<field name="context">{
'search_default_filter_date': True}</field>
</record>
</odoo>
@@ -4,11 +4,12 @@
<record id="holiday_status_extra_hours" model="hr.leave.type">
<field name="name">Extra Hours</field>
<field name="request_unit">hour</field>
<field name="allocation_type">no</field>
<field name="requires_allocation">no</field>
<field name="leave_validation_type">manager</field>
<field name="overtime_deductible" eval="True"/>
<field name="active" eval="False"/>
<field name="company_id" eval="False"/>
<field name="icon_id" ref="hr_holidays.icon_9"/>
</record>
<!-- The record above should be archived if no company has overtime counting enabled, otherwise enabled -->
@@ -16,7 +16,7 @@ class HRLeave(models.Model):
@api.depends('holiday_status_id')
def _compute_overtime_deductible(self):
for leave in self:
leave.overtime_deductible = leave.holiday_status_id.overtime_deductible and leave.holiday_status_id.allocation_type == 'no'
leave.overtime_deductible = leave.holiday_status_id.overtime_deductible and leave.holiday_status_id.requires_allocation == 'no'
@api.model_create_multi
def create(self, vals_list):
@@ -16,9 +16,9 @@ class HolidaysAllocation(models.Model):
type_operator = '=' if self.env.context.get('deduct_extra_hours_allocation_type') else '!='
type_value = self.env.context.get('deduct_extra_hours_allocation_type', 'no')
leave_type = self.env['hr.leave.type'].search([
('valid', '=', True),
('has_valid_allocation', '=', True),
('overtime_deductible', '=', True),
('allocation_type', type_operator, type_value)], limit=1)
('requires_allocaiton', '=', 'yes')], limit=1)
res['holiday_status_id'] = leave_type.id
return res
@@ -16,7 +16,7 @@ class HRLeaveType(models.Model):
res = super().get_employees_days(employee_ids)
deductible_time_off_type_ids = self.env['hr.leave.type'].search([
('overtime_deductible', '=', True),
('allocation_type', '=', 'no')]).ids
('requires_allocation', '=', 'no')]).ids
for employee_id, allocations in res.items():
for allocation_id in allocations:
if allocation_id in deductible_time_off_type_ids:
@@ -18,8 +18,9 @@ class ResUsers(models.Model):
def _compute_request_overtime(self):
is_holiday_user = self.env.user.has_group('hr_holidays.group_hr_holidays_user')
time_off_types = self.env['hr.leave.type'].search_count([
('valid', '=', True),
('allocation_type', '=', 'fixed_allocation'),
('has_valid_allocation', '=', True),
('requires_allocation', '=', 'yes'),
('employee_requests', '=', 'yes'),
('overtime_deductible', '=', True)
])
for user in self:
@@ -3,28 +3,32 @@
<templates id="template" xml:space="preserve">
<t t-inherit="hr_holidays.dashboard_calendar_header" t-inherit-mode="extension">
<!--inject a first condition-->
<xpath expr="//t[@t-if='need_allocation']" position="before">
<t t-if="timeoff[1]['overtime_deductible'] == true &amp;&amp; !need_allocation">
<span t-esc="timeoff[1]['virtual_remaining_leaves']" class="o_timeoff_big o_timeoff_green font-weight-bold"/> Hours <span class="o_timeoff_green">Available </span>
<xpath expr="//t[@t-if='requires_allocation']" position="before">
<t t-if="timeoff[1]['overtime_deductible'] == true &amp;&amp; !requires_allocation">
<t t-if="has_icon">
<img height="30px" t-attf-src="{{timeoff[1]['icon']}}"/>
</t>
<span t-esc="timeoff[1]['virtual_remaining_leaves']" class="o_timeoff_huge o_timeoff_purple font-weight-bold align-middle"/><br/>
<span class="o_timeoff_big o_timeoff_purple">HOURS AVAILABLE</span>
</t>
</xpath>
<!--change t-if to t-elif-->
<xpath expr="//t[@t-if='need_allocation']" position="attributes">
<xpath expr="//t[@t-if='requires_allocation']" position="attributes">
<attribute name="t-if"/>
<attribute name="t-elif">need_allocation</attribute>
<attribute name="t-elif">requires_allocation</attribute>
</xpath>
</t>
<t t-inherit="hr_holidays.dashboard_calendar_header_mobile" t-inherit-mode="extension">
<!--inject a first condition-->
<xpath expr="//t[@t-if='need_allocation']" position="before">
<t t-if="timeoff[1]['overtime_deductible'] == true &amp;&amp; !need_allocation">
<xpath expr="//t[@t-if='requires_allocation']" position="before">
<t t-if="timeoff[1]['overtime_deductible'] == true &amp;&amp; !requires_allocation">
<strong t-esc="timeoff[1]['virtual_remaining_leaves']" class="o_timeoff_green"/> Hours <span class="o_timeoff_green ml-2">Available </span>
</t>
</xpath>
<!--change t-if to t-elif-->
<xpath expr="//t[@t-if='need_allocation']" position="attributes">
<xpath expr="//t[@t-if='requires_allocation']" position="attributes">
<attribute name="t-if"/>
<attribute name="t-elif">need_allocation</attribute>
<attribute name="t-elif">requires_allocation</attribute>
</xpath>
</t>
</templates>
@@ -6,6 +6,7 @@ from odoo.tests import new_test_user
from odoo.tests.common import TransactionCase, tagged
from odoo.exceptions import AccessError, ValidationError
import time
@tagged('post_install', '-at_install', 'holidays_attendance')
class TestHolidaysOvertime(TransactionCase):
@@ -36,19 +37,22 @@ class TestHolidaysOvertime(TransactionCase):
cls.leave_type_no_alloc = cls.env['hr.leave.type'].create({
'name': 'Overtime Compensation No Allocation',
'company_id': cls.company.id,
'allocation_type': 'no',
'requires_allocation': 'no',
'overtime_deductible': True,
})
cls.leave_type_employee_allocation = cls.env['hr.leave.type'].create({
'name': 'Overtime Compensation Employee Allocation',
'company_id': cls.company.id,
'allocation_type': 'fixed_allocation',
'requires_allocation': 'yes',
'employee_requests': 'yes',
'overtime_deductible': True,
})
cls.leave_type_manager_alloc = cls.env['hr.leave.type'].create({
'name': 'Overtime Compensation Manager Allocation',
'company_id': cls.company.id,
'allocation_type': 'fixed',
'requires_allocation': 'yes',
'employee_requests': 'no',
'allocation_validation_type': 'set',
'overtime_deductible': True,
})
@@ -163,6 +167,8 @@ class TestHolidaysOvertime(TransactionCase):
'employee_id': self.employee.id,
'number_of_days': 1,
'state': 'draft',
'date_from': time.strftime('%Y-1-1'),
'date_to': time.strftime('%Y-12-31'),
})
self.new_attendance(check_in=datetime(2021, 1, 2, 8), check_out=datetime(2021, 1, 2, 16))
@@ -174,6 +180,8 @@ class TestHolidaysOvertime(TransactionCase):
'employee_id': self.employee.id,
'number_of_days': 1,
'state': 'draft',
'date_from': time.strftime('%Y-1-1'),
'date_to': time.strftime('%Y-12-31'),
})
allocation.action_confirm()
self.assertEqual(self.employee.total_overtime, 0)
@@ -181,7 +189,8 @@ class TestHolidaysOvertime(TransactionCase):
leave_type = self.env['hr.leave.type'].sudo().create({
'name': 'Overtime Compensation Employee Allocation',
'company_id': self.company.id,
'allocation_type': 'fixed_allocation',
'requires_allocation': 'yes',
'employee_requests': 'yes',
'overtime_deductible': False,
})
@@ -192,6 +201,8 @@ class TestHolidaysOvertime(TransactionCase):
'employee_id': self.employee.id,
'number_of_days': 1,
'state': 'draft',
'date_from': time.strftime('%Y-1-1'),
'date_to': time.strftime('%Y-12-31'),
})
allocation2.action_confirm()
@@ -206,6 +217,8 @@ class TestHolidaysOvertime(TransactionCase):
'employee_id': self.employee.id,
'number_of_days': 1,
'state': 'draft',
'date_from': time.strftime('%Y-1-1'),
'date_to': time.strftime('%Y-12-31'),
})
self.assertEqual(self.employee.total_overtime, 8)
@@ -31,7 +31,8 @@
<attribute name="invisible">1</attribute>
</div>
<xpath expr="//field[@name='holiday_status_id']" position="attributes">
<attribute name="domain">[('valid', '=', True), ('overtime_deductible', '=', True), ('allocation_type', '=', 'fixed_allocation')]</attribute>
<attribute name="domain">[('has_valid_allocation', '=', True), ('overtime_deductible', '=', True), ('requires_allocation', '=', 'yes'),
('employee_requests', '=', 'no')]</attribute>
<attribute name="options">{'no_create': True, 'no_open': True}</attribute>
</xpath>
<xpath expr="//sheet" position="after">
@@ -50,7 +51,7 @@
<field name="priority">70</field>
<field name="arch" type="xml">
<xpath expr="//field[@name='holiday_status_id']" position="attributes">
<attribute name="domain">[('valid', '=', True), ('overtime_deductible', '=', True), ('allocation_type', '!=', 'no')]</attribute>
<attribute name="domain">[('has_valid_allocation', '=', True), ('overtime_deductible', '=', True), ('requires_allocation', '=', 'yes')]</attribute>
</xpath>
</field>
</record>
@@ -5,7 +5,7 @@
<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='leave_validation_type']" position="after">
<xpath expr="//field[@name='request_unit']" position="after">
<field name="hr_attendance_overtime" invisible="1" />
<field name="overtime_deductible" attrs="{'invisible': [('hr_attendance_overtime', '=', False)]}"/>
</xpath>
@@ -1,5 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="resource_calendar_leaves_view_search_inherit" model="ir.ui.view">
<field name="name">resource.calendar.leaves.search.inherit</field>
<field name="model">resource.calendar.leaves</field>
<field name="inherit_id" ref="resource.view_resource_calendar_leaves_search"/>
<field name="arch" type="xml">
<filter name="resource" position="after">
<field name="work_entry_type_id"/>
<filter name="work_entry" string="Work Entry Type" context="{'group_by':'work_entry_type_id'}"/>
</filter>
</field>
</record>
<record id="resource_calendar_attendance_view_tree" model="ir.ui.view">
<field name="name">resource.calendar.attendance.tree.inherit.hr.work.entry</field>
<field name="model">resource.calendar.attendance</field>
@@ -22,17 +33,6 @@
</field>
</record>
<record id="resource_calendar_view_form" model="ir.ui.view">
<field name="name">resource.calendar.form.inherit.hr.work.entry</field>
<field name="model">resource.calendar</field>
<field name="inherit_id" ref="resource.resource_calendar_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='global_leave_ids']//field[@name='date_to']" position="after">
<field name="work_entry_type_id"/>
</xpath>
</field>
</record>
<record id="resource_calendar_leave_view_form" model="ir.ui.view">
<field name="name">resource.calendar.leaves.form.inherit.hr.work.entry</field>
<field name="model">resource.calendar.leaves</field>
@@ -15,10 +15,8 @@
'data': [
'security/hr_work_entry_security.xml',
'security/ir.model.access.csv',
'wizard/create_company_global_time_off_views.xml',
'data/hr_work_entry_data.xml',
'views/hr_work_entry_views.xml',
'views/resource_views.xml',
'wizard/hr_work_entry_regeneration_wizard_views.xml',
],
'demo': [
@@ -1,3 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_hr_work_entry_regeneration_wizard,access_hr_work_entry_regeneration_wizard,model_hr_work_entry_regeneration_wizard,hr.group_hr_manager,1,1,1,1
access_create_company_global_time_off,access_create_company_global_time_off,model_create_company_global_time_off,hr_contract.group_hr_contract_manager,1,1,1,0
access_hr_work_entry_regeneration_wizard,access_hr_work_entry_regeneration_wizard,model_hr_work_entry_regeneration_wizard,hr.group_hr_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_hr_work_entry_regeneration_wizard access_hr_work_entry_regeneration_wizard model_hr_work_entry_regeneration_wizard hr.group_hr_manager 1 1 1 1
access_create_company_global_time_off access_create_company_global_time_off model_create_company_global_time_off hr_contract.group_hr_contract_manager 1 1 1 0
@@ -1,17 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Calendar View -->
<record id="resource_calendar_view_form" model="ir.ui.view">
<field name="name">resource.calendar.view.form.inherit</field>
<field name="model">resource.calendar</field>
<field name="inherit_id" ref="resource.resource_calendar_form"/>
<field name="arch" type="xml">
<xpath expr="//sheet" position="before">
<header>
<button name="%(action_create_company_global_time_off)d" string="Create Company Public Time Off" type="action" class="oe_highlight"/>
</header>
</xpath>
</field>
</record>
</odoo>
@@ -2,4 +2,3 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import hr_work_entry_regeneration_wizard
from . import create_company_global_time_off
@@ -1,27 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
class HrPayrollIndex(models.TransientModel):
_name = 'create.company.global.time.off'
_description = 'Create Company Public Time Off'
name = fields.Char('Reason', required=True)
company_id = fields.Many2one('res.company', default=lambda self: self.env.company, required=True)
date_from = fields.Datetime('Start Date', required=True)
date_to = fields.Datetime('End Date', required=True)
work_entry_type_id = fields.Many2one('hr.work.entry.type', 'Work Entry Type', required=True)
def action_confirm(self):
self.ensure_one()
self.env['resource.calendar'].search([('company_id', '=', self.company_id.id)]).write({
'leave_ids': [(0, 0, {
'name': self.name,
'date_from': self.date_from,
'date_to': self.date_to,
'time_type': 'leave',
'work_entry_type_id': self.work_entry_type_id.id,
})]
})
@@ -1,34 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="create_company_global_time_off_view_form" model="ir.ui.view">
<field name="name">create.company.global.time.off.view.form</field>
<field name="model">create.company.global.time.off</field>
<field name="arch" type="xml">
<form string="Create Company Public Time Off">
<group>
<field name="name"/>
<label for="date_from" string="Period"/>
<div>
<field name="date_from" class="oe_inline"/>
<span> to </span>
<field name="date_to" class="oe_inline"/>
</div>
<field name="company_id"/>
<field name="work_entry_type_id"/>
</group>
<footer>
<button string="Confirm" name="action_confirm" type="object" class="oe_highlight" data-hotkey="q"/>
<button string="Cancel" class="btn btn-secondary" special="cancel" data-hotkey="z"/>
</footer>
</form>
</field>
</record>
<record id="action_create_company_global_time_off" model="ir.actions.act_window">
<field name="name">Create Company Public Time Off</field>
<field name="res_model">create.company.global.time.off</field>
<field name="view_mode">tree,form</field>
<field name="view_id" ref="create_company_global_time_off_view_form"/>
<field name="target">new</field>
</record>
</odoo>
@@ -16,8 +16,7 @@ class TestWorkEntryHolidaysBase(TestWorkEntryBase):
cls.leave_type = cls.env['hr.leave.type'].create({
'name': 'Legal Leaves',
'time_type': 'leave',
'allocation_type': 'no',
'validity_start': False,
'requires_allocation': 'no',
'work_entry_type_id': cls.work_entry_type_leave.id
})
@@ -13,8 +13,7 @@ class TestWorkEntryHolidaysMultiContract(TestWorkEntryHolidaysBase):
self.leave_type = self.env['hr.leave.type'].create({
'name': 'Legal Leaves',
'time_type': 'leave',
'allocation_type': 'no',
'validity_start': False,
'requires_allocation': 'no',
'work_entry_type_id': self.work_entry_type_leave.id
})
@@ -14,8 +14,7 @@ class TestPayslipHolidaysComputation(TestWorkEntryHolidaysBase):
self.leave_type = self.env['hr.leave.type'].create({
'name': 'Legal Leaves',
'time_type': 'leave',
'allocation_type': 'no',
'validity_start': False,
'requires_allocation': 'no',
'work_entry_type_id': self.work_entry_type_leave.id
})
@@ -31,7 +31,7 @@ class TestWorkEntryHolidaysPerformance(TestWorkEntryHolidaysBase):
self.richard_emp.generate_work_entries(date(2018, 1, 1), date(2018, 1, 2))
leave = self.create_leave(datetime(2018, 1, 1, 7, 0), datetime(2018, 1, 1, 18, 0))
with self.assertQueryCount(__system__=96, admin=97):
with self.assertQueryCount(__system__=98, admin=100):
leave.action_validate()
leave.action_refuse()
@@ -40,14 +40,14 @@ class TestWorkEntryHolidaysPerformance(TestWorkEntryHolidaysBase):
def test_performance_leave_write(self):
leave = self.create_leave(datetime(2018, 1, 1, 7, 0), datetime(2018, 1, 1, 18, 0))
with self.assertQueryCount(__system__=21, admin=29):
with self.assertQueryCount(__system__=25, admin=29):
leave.date_to = datetime(2018, 1, 1, 19, 0)
leave.action_refuse()
@users('__system__', 'admin')
@warmup
def test_performance_leave_create(self):
with self.assertQueryCount(__system__=24, admin=25):
with self.assertQueryCount(__system__=26, admin=30):
leave = self.create_leave(datetime(2018, 1, 1, 7, 0), datetime(2018, 1, 1, 18, 0))
leave.action_refuse()
@@ -58,7 +58,7 @@ class TestWorkEntryHolidaysPerformance(TestWorkEntryHolidaysBase):
leave.action_draft()
with self.assertQueryCount(__system__=22, admin=23):
leave.action_confirm()
leave.state = 'cancel'
leave.state = 'refuse'
@tagged('work_entry_perf')
@@ -74,6 +74,7 @@ class TestWorkEntryHolidaysPerformancesBigData(TestWorkEntryHolidaysBase):
'request_unit': 'day',
'leave_validation_type': 'both',
'company_id': cls.company.id,
'requires_allocation': 'no',
})
cls.employees = cls.env['hr.employee'].create([{
@@ -125,7 +125,7 @@ class TestWorkeEntryHolidaysWorkEntry(TestWorkEntryHolidaysBase):
'name': 'Sick',
'request_unit': 'hour',
'leave_validation_type': 'both',
'allocation_type': 'no',
'requires_allocation': 'no',
'company_id': company.id,
})
leave1 = self.env['hr.leave'].create({
@@ -173,7 +173,7 @@ class TestWorkeEntryHolidaysWorkEntry(TestWorkEntryHolidaysBase):
'name': 'Sick',
'request_unit': 'hour',
'leave_validation_type': 'both',
'allocation_type': 'no',
'requires_allocation': 'no',
})
leave = self.env['hr.leave'].create({
@@ -6,10 +6,9 @@
<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="//group[@name='calendar']" position="after">
<group name="work_entry" string="Work Entries">
<field name="work_entry_type_id"/>
</group>
<xpath expr="//group[@id='payroll']" position="inside">
<h2>Payroll</h2>
<field name="work_entry_type_id"/>
</xpath>
</field>
</record>
@@ -26,17 +25,4 @@
</field>
</record>
<record id="hr_leave_view_search_manager_inherit_work_entry" model="ir.ui.view">
<field name="name">hr.holidays.view.search.manager.work.entry</field>
<field name="model">hr.leave</field>
<field name="inherit_id" ref="hr_holidays.hr_leave_view_search_manager"/>
<field name="mode">primary</field>
<field name="arch" type="xml">
<xpath expr="//filter[@name='gray']" position="attributes">
<attribute name="invisible">0</attribute>
</xpath>
</field>
</record>
</odoo>
@@ -8,6 +8,7 @@ from odoo import fields, SUPERUSER_ID
from odoo.tests import common, new_test_user
from odoo.addons.hr_timesheet.tests.test_timesheet import TestCommonTimesheet
import time
class TestTimesheetHolidaysCreate(common.TransactionCase):
@@ -16,7 +17,7 @@ class TestTimesheetHolidaysCreate(common.TransactionCase):
"""Ensure that when a status is created, it fullfills the project and task constrains"""
status = self.env['hr.leave.type'].create({
'name': 'A nice Leave Type',
'allocation_type': 'no'
'requires_allocation': 'no'
})
company = self.env.company
@@ -53,19 +54,17 @@ class TestTimesheetHolidays(TestCommonTimesheet):
self.hr_leave_type_with_ts = self.env['hr.leave.type'].create({
'name': 'Leave Type with timesheet generation',
'allocation_type': 'no',
'requires_allocation': 'no',
'timesheet_generate': True,
'timesheet_project_id': self.internal_project.id,
'timesheet_task_id': self.internal_task_leaves.id,
'validity_start': False,
})
self.hr_leave_type_no_ts = self.env['hr.leave.type'].create({
'name': 'Leave Type without timesheet generation',
'allocation_type': 'no',
'requires_allocation': 'no',
'timesheet_generate': False,
'timesheet_project_id': False,
'timesheet_task_id': False,
'validity_start': False,
})
# HR Officer allocates some leaves to the employee 1
@@ -76,15 +75,21 @@ class TestTimesheetHolidays(TestCommonTimesheet):
'employee_id': self.empl_employee.id,
'holiday_status_id': self.hr_leave_type_with_ts.id,
'number_of_days': 10,
'state': 'confirm',
'date_from': time.strftime('%Y-01-01'),
'date_to': time.strftime('%Y-12-31'),
})
self.hr_leave_allocation_with_ts.action_approve()
self.hr_leave_allocation_with_ts.action_validate()
self.hr_leave_allocation_no_ts = self.Allocations.sudo().create({
'name': 'Days for limited category without timesheet',
'employee_id': self.empl_employee.id,
'holiday_status_id': self.hr_leave_type_no_ts.id,
'number_of_days': 10,
'state': 'confirm',
'date_from': time.strftime('%Y-01-01'),
'date_to': time.strftime('%Y-12-31'),
})
self.hr_leave_allocation_no_ts.action_approve()
self.hr_leave_allocation_no_ts.action_validate()
def test_validate_with_timesheet(self):
# employee creates a leave request

Some files were not shown because too many files have changed in this diff Show More