[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>
@@ -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/**/*',
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
# ------------------------------------------------------------
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -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', '<=', context_today().strftime('%Y-%m-%d')), ('stop_datetime', '>=', 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>
|
||||
|
||||
@@ -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'),
|
||||
'|',
|
||||
'&',
|
||||
('employee_id.user_id', '=', user.id),
|
||||
('state', 'not in', ['validate', 'validate1']),
|
||||
('state', '!=', 'validate'),
|
||||
'&',
|
||||
('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
|
||||
|
||||
|
@@ -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 && timeoff[1]['virtual_remaining_leaves'] > 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>
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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',
|
||||
})
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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']), '&', ('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>
|
||||
|
||||
@@ -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', '<', 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), '&', ('virtual_remaining_leaves', '>', 0), '|', ('allocation_type', 'in', ['fixed_allocation', 'no']),'&',('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'), '&', ('has_valid_allocation', '=', True), '&', ('virtual_remaining_leaves', '>', 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'), '&', ('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>
|
||||
|
||||
@@ -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','<', 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 && !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 && !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 && !need_allocation">
|
||||
<xpath expr="//t[@t-if='requires_allocation']" position="before">
|
||||
<t t-if="timeoff[1]['overtime_deductible'] == true && !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,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
|
||||
|
||||