Initial commit: Hr packages

This commit is contained in:
Ernad Husremovic 2025-08-29 15:20:50 +02:00
commit 62531cd146
2820 changed files with 1432848 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

View file

@ -0,0 +1,24 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="70" height="70" viewBox="0 0 70 70">
<defs>
<path id="icon-a" d="M4,5.35309892e-14 C36.4160122,9.87060235e-15 58.0836068,-3.97961823e-14 65,5.07020818e-14 C69,6.733808e-14 70,1 70,5 C70,43.0488877 70,62.4235458 70,65 C70,69 69,70 65,70 C61,70 9,70 4,70 C1,70 7.10542736e-15,69 7.10542736e-15,65 C7.25721566e-15,62.4676575 3.83358709e-14,41.8005206 3.60818146e-14,5 C-1.13686838e-13,1 1,5.75716207e-14 4,5.35309892e-14 Z"/>
<linearGradient id="icon-c" x1="100%" x2="0%" y1="0%" y2="100%">
<stop offset="0%" stop-color="#CDC484"/>
<stop offset="100%" stop-color="#B5AA59"/>
</linearGradient>
<path id="icon-d" d="M27.2777778,16.2348485 C32.4714536,16.2348485 36.681713,20.506348 36.681713,25.7755682 C36.681713,31.0447884 32.4714536,35.3162879 27.2777778,35.3162879 C22.0841019,35.3162879 17.8738426,31.0447884 17.8738426,25.7755682 C17.8738426,20.506348 22.0841019,16.2348485 27.2777778,16.2348485 Z M32,36 C32,35.5868456 24.1175854,37.5736818 20.6967864,35.0773525 L16.5053937,36.1404272 C13.9936026,36.7775087 12.2314815,39.0671622 12.2314815,41.6939608 L12.2314815,43.9029356 C12.2314815,45.4837136 13.4945475,46.7651515 15.052662,46.7651515 C25.363233,46.7651515 30.5185185,46.7651515 30.5185185,46.7651515 C28.6666667,40.4242424 32,36.4131544 32,36 Z M48.5116455,29.4292211 L49.8821338,32.7804044 C50.108418,33.3336802 50.7775488,33.5511019 51.2858154,33.2365172 L54.3643115,31.3309087 C55.1962939,30.8159288 56.2338818,31.569761 56.0011523,32.5201752 L55.140166,36.0368953 C54.9979932,36.61751 55.4115674,37.186738 56.0077051,37.230942 L59.6183691,37.4987982 C60.5941357,37.5712005 60.9904688,38.790921 60.2436182,39.4230444 L57.4800293,41.7619923 C57.0237549,42.1481736 57.0237549,42.8517324 57.4800293,43.2379137 L60.2436719,45.5769153 C60.9905762,46.2090387 60.5942432,47.4287592 59.6184229,47.5011615 L56.0077588,47.7690177 C55.4116211,47.8132217 54.9980469,48.3824497 55.1402197,48.9630644 L56.0012061,52.4797846 C56.2338818,53.4301987 55.1962939,54.1840309 54.3643652,53.669051 L51.2858691,51.7634425 C50.7776025,51.4488041 50.108418,51.6662258 49.8821875,52.2195553 L48.5116992,55.5707386 C48.1413086,56.4764115 46.8587988,56.4764115 46.4884082,55.5707386 L45.1179199,52.2195553 C44.8916357,51.6662795 44.2225049,51.4488578 43.7142383,51.7634425 L40.6356885,53.669051 C39.8037061,54.1840309 38.7661182,53.4301987 38.9988477,52.4797846 L39.859834,48.9630644 C40.0020068,48.3824497 39.5884326,47.8132217 38.9922949,47.7690177 L35.3816309,47.5011615 C34.4058643,47.4287592 34.0095313,46.2090387 34.7563818,45.5769153 L37.5199707,43.2379674 C37.9762451,42.8517862 37.9762451,42.1482273 37.5199707,41.762046 L34.7563281,39.4230444 C34.0094238,38.790921 34.4057568,37.5712005 35.3815771,37.4987982 L38.9922412,37.230942 C39.5883789,37.186738 40.0019531,36.61751 39.8597803,36.0368953 L38.9987939,32.5201752 C38.7661182,31.569761 39.8037061,30.8159288 40.6356348,31.3309087 L43.7141846,33.2365172 C44.2224512,33.5511556 44.891582,33.3337339 45.1178662,32.7804044 L46.4883545,29.4292211 C46.8587451,28.5236019 48.1412549,28.5236019 48.5116455,29.4292211 Z M54.8046875,42.4999799 C54.8046875,38.4721469 51.5277832,35.1952995 47.5,35.1952995 C43.4721631,35.1952995 40.1953125,38.4721469 40.1953125,42.4999799 C40.1953125,46.5278128 43.4721631,49.8046602 47.5,49.8046602 C51.5277832,49.8046602 54.8046875,46.5278128 54.8046875,42.4999799 Z M53.0859375,42.4999799 C53.0859375,45.5800843 50.5801074,48.0859119 47.5,48.0859119 C44.4198926,48.0859119 41.9140625,45.5800843 41.9140625,42.4999799 C41.9140625,39.4198754 44.4198926,36.9140478 47.5,36.9140478 C50.5801074,36.9140478 53.0859375,39.4198754 53.0859375,42.4999799 Z"/>
<path id="icon-e" d="M27.2777778,14.2348485 C32.4714536,14.2348485 36.681713,18.506348 36.681713,23.7755682 C36.681713,29.0447884 32.4714536,33.3162879 27.2777778,33.3162879 C22.0841019,33.3162879 17.8738426,29.0447884 17.8738426,23.7755682 C17.8738426,18.506348 22.0841019,14.2348485 27.2777778,14.2348485 Z M32,34 C32,33.5868456 24.1175854,35.5736818 20.6967864,33.0773525 L16.5053937,34.1404272 C13.9936026,34.7775087 12.2314815,37.0671622 12.2314815,39.6939608 L12.2314815,41.9029356 C12.2314815,43.4837136 13.4945475,44.7651515 15.052662,44.7651515 C25.363233,44.7651515 30.5185185,44.7651515 30.5185185,44.7651515 C28.6666667,38.4242424 32,34.4131544 32,34 Z M48.5116455,27.4292211 L49.8821338,30.7804044 C50.108418,31.3336802 50.7775488,31.5511019 51.2858154,31.2365172 L54.3643115,29.3309087 C55.1962939,28.8159288 56.2338818,29.569761 56.0011523,30.5201752 L55.140166,34.0368953 C54.9979932,34.61751 55.4115674,35.186738 56.0077051,35.230942 L59.6183691,35.4987982 C60.5941357,35.5712005 60.9904688,36.790921 60.2436182,37.4230444 L57.4800293,39.7619923 C57.0237549,40.1481736 57.0237549,40.8517324 57.4800293,41.2379137 L60.2436719,43.5769153 C60.9905762,44.2090387 60.5942432,45.4287592 59.6184229,45.5011615 L56.0077588,45.7690177 C55.4116211,45.8132217 54.9980469,46.3824497 55.1402197,46.9630644 L56.0012061,50.4797846 C56.2338818,51.4301987 55.1962939,52.1840309 54.3643652,51.669051 L51.2858691,49.7634425 C50.7776025,49.4488041 50.108418,49.6662258 49.8821875,50.2195553 L48.5116992,53.5707386 C48.1413086,54.4764115 46.8587988,54.4764115 46.4884082,53.5707386 L45.1179199,50.2195553 C44.8916357,49.6662795 44.2225049,49.4488578 43.7142383,49.7634425 L40.6356885,51.669051 C39.8037061,52.1840309 38.7661182,51.4301987 38.9988477,50.4797846 L39.859834,46.9630644 C40.0020068,46.3824497 39.5884326,45.8132217 38.9922949,45.7690177 L35.3816309,45.5011615 C34.4058643,45.4287592 34.0095313,44.2090387 34.7563818,43.5769153 L37.5199707,41.2379674 C37.9762451,40.8517862 37.9762451,40.1482273 37.5199707,39.762046 L34.7563281,37.4230444 C34.0094238,36.790921 34.4057568,35.5712005 35.3815771,35.4987982 L38.9922412,35.230942 C39.5883789,35.186738 40.0019531,34.61751 39.8597803,34.0368953 L38.9987939,30.5201752 C38.7661182,29.569761 39.8037061,28.8159288 40.6356348,29.3309087 L43.7141846,31.2365172 C44.2224512,31.5511556 44.891582,31.3337339 45.1178662,30.7804044 L46.4883545,27.4292211 C46.8587451,26.5236019 48.1412549,26.5236019 48.5116455,27.4292211 Z M54.8046875,40.4999799 C54.8046875,36.4721469 51.5277832,33.1952995 47.5,33.1952995 C43.4721631,33.1952995 40.1953125,36.4721469 40.1953125,40.4999799 C40.1953125,44.5278128 43.4721631,47.8046602 47.5,47.8046602 C51.5277832,47.8046602 54.8046875,44.5278128 54.8046875,40.4999799 Z M53.0859375,40.4999799 C53.0859375,43.5800843 50.5801074,46.0859119 47.5,46.0859119 C44.4198926,46.0859119 41.9140625,43.5800843 41.9140625,40.4999799 C41.9140625,37.4198754 44.4198926,34.9140478 47.5,34.9140478 C50.5801074,34.9140478 53.0859375,37.4198754 53.0859375,40.4999799 Z"/>
</defs>
<g fill="none" fill-rule="evenodd">
<mask id="icon-b" fill="#fff">
<use xlink:href="#icon-a"/>
</mask>
<g mask="url(#icon-b)">
<rect width="70" height="70" fill="url(#icon-c)"/>
<path fill="#FFF" fill-opacity=".383" d="M4,1.8 L65,1.8 C67.6666667,1.8 69.3333333,1.13333333 70,-0.2 C70,2.46666667 70,3.46666667 70,2.8 L1.10547097e-14,2.8 C-1.65952376e-14,3.46666667 -2.9161925e-14,2.46666667 -2.66453526e-14,-0.2 C0.666666667,1.13333333 2,1.8 4,1.8 Z" transform="matrix(1 0 0 -1 0 2.8)"/>
<path fill="#393939" d="M43.6444444,52 L4,52 C2,52 -7.10542736e-15,51.8514286 0,47.84 L2.21121142e-16,23.3197384 L20,0 L36,9.36 L30.6694253,16.3853185 L31.9690606,16.0436983 L28.4526012,19.3069352 L28.4526012,24.5134324 L39.2011791,11.8378465 L42.1902641,14.2434656 L46.8367307,9.36 L51,14.56 L60.3723121,27.0292074 L43.6444444,52 Z" opacity=".324" transform="translate(0 18)"/>
<path fill="#000" fill-opacity=".383" d="M4,4 L65,4 C67.6666667,4 69.3333333,3 70,1 C70,3.66666667 70,5 70,5 L1.77635684e-15,5 C1.77635684e-15,5 1.77635684e-15,3.66666667 1.77635684e-15,1 C0.666666667,3 2,4 4,4 Z" transform="translate(0 65)"/>
<use fill="#000" fill-rule="nonzero" opacity=".3" xlink:href="#icon-d"/>
<use fill="#FFF" fill-rule="nonzero" xlink:href="#icon-e"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8 KiB

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-inherit="mail.PersonaImStatusIcon" t-inherit-mode="extension">
<xpath expr="//*[@name='root']" position="inside">
<t t-if="personaImStatusIconView.persona.im_status === 'leave_online'">
<i class="o_PersonaImStatusIcon_icon o-online fa fa-plane fa-stack-1x text-primary" title="Online" role="img" aria-label="User is online"/>
</t>
<t t-if="personaImStatusIconView.persona.im_status === 'leave_away'">
<i class="o_PersonaImStatusIcon_icon o-away fa fa-plane fa-stack-1x text-warning" title="Away" role="img" aria-label="User is away"/>
</t>
<t t-if="personaImStatusIconView.persona.im_status === 'leave_offline'">
<i class="o_PersonaImStatusIcon_icon o-offline fa fa-plane fa-stack-1x text-700" title="Out of office" role="img" aria-label="User is out of office"/>
</t>
</xpath>
</t>
</templates>

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-inherit="mail.ThreadIcon" t-inherit-mode="extension">
<xpath expr="//*[@name='noImStatusCondition']" position="before">
<t t-elif="thread.channel.correspondent.im_status === 'leave_online'">
<div class="o_ThreadIcon_online fa fa-fw fa-plane" title="Online"/>
</t>
<t t-elif="thread.channel.correspondent.im_status === 'leave_away'">
<div class="o_ThreadIcon_away fa fa-fw fa-plane text-warning" title="Away"/>
</t>
<t t-elif="thread.channel.correspondent.im_status === 'leave_offline'">
<div class="o_ThreadIcon_offline fa fa-fw fa-plane" title="Out of office"/>
</t>
</xpath>
</t>
</templates>

View file

@ -0,0 +1,8 @@
// -----------------------------------------------------------------------------
// Layout
// -----------------------------------------------------------------------------
.o_ThreadView_outOfOffice {
margin-top: 0;
margin-bottom: 0;
}

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-inherit="mail.ThreadView" t-inherit-mode="extension">
<xpath expr="//*[@name='loadingCondition']" position="before">
<t t-if="threadView.thread.channel and threadView.thread.channel.correspondent and threadView.thread.channel.correspondent.outOfOfficeText">
<div class="o_ThreadView_outOfOffice alert alert-primary" t-esc="threadView.thread.channel.correspondent.outOfOfficeText" role="alert"/>
</t>
</xpath>
</t>
</templates>

View file

@ -0,0 +1,6 @@
// = HR Holidays
// ============================================================================
// No CSS hacks, variables overrides only
.o_timeoff_card, .o_time_off_icon_types {
--timeOffCard-icon-filter: invert(1) brightness(2);
}

View file

@ -0,0 +1,21 @@
/* @odoo-module */
import Popover from "web.Popover";
const { Component } = owl;
export class TimeOffCardPopover extends Component {}
TimeOffCardPopover.components = { Popover };
TimeOffCardPopover.template = 'hr_holidays.TimeOffCardPopover';
TimeOffCardPopover.props = ['allocated', 'approved', 'planned', 'left', 'usable'];
export class TimeOffCard extends Component {}
TimeOffCard.components = { TimeOffCardPopover };
TimeOffCard.template = 'hr_holidays.TimeOffCard';
TimeOffCard.props = ['name', 'id', 'data', 'requires_allocation'];
export class TimeOffCardMobile extends TimeOffCard {}
TimeOffCardMobile.template = 'hr_holidays.TimeOffCardMobile';

View file

@ -0,0 +1,74 @@
.o_timeoff_card {
flex-direction: column;
flex-grow: 1;
display: flex;
text-align: center;
&:not(:last-child) {
border-right: 2px solid $border-color;
}
span {
line-height: 1;
}
.o_timeoff_name {
color: $body-color;
font-size: 15px;
margin-bottom: 3px;
}
.o_timeoff_duration {
font-size: 30px;
font-weight: bold;
img {
height: 30px;
filter: var(--timeOffCard-icon-filter);
}
}
.o_timeoff_validity {
font-size: 10px;
}
.o_timeoff_info {
display: inline-block;
margin-right: -10px;
span {
cursor: pointer;
padding-right: 10px;
}
}
}
.o_timeoff_card_mobile {
.o_timeoff_green {
color: $o-enterprise-primary-color;
}
}
.o_timeoff_popover {
background-color: $o-view-background-color;
font-size: 12px;
ul {
list-style-type: none;
margin-bottom: 0;
padding: 0;
}
li {
display: flex;
justify-content: space-between;
span {
padding-left: 3px;
}
}
}
.o_time_off_icon_types img {
filter: var(--timeOffCard-icon-filter);
}

View file

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<div t-name="hr_holidays.TimeOffCard" owl="1" class="o_timeoff_card py-3 text-odoo">
<t t-set="data" t-value="props.data"/>
<t t-set="duration" t-value="props.requires_allocation ? data.virtual_remaining_leaves : data.virtual_leaves_taken"/>
<t t-set="show_popover" t-value="true"/>
<strong class="o_timeoff_name"><t t-esc="props.name"/></strong>
<span class="o_timeoff_duration">
<t t-if="data and data.icon">
<img t-att-src="data.icon" />
</t>
<t t-esc="duration"/>
</span>
<div class="text-uppercase">
<t t-if="data.request_unit == 'hour'" name="duration_unit">hours</t>
<t t-else="">days</t>
<t t-if="props.requires_allocation" name="duration_type">
available
</t>
<t t-else="">
taken
</t>
<t t-if="show_popover">
<TimeOffCardPopover
allocated="data.max_leaves"
approved="data.leaves_approved"
planned="data.leaves_requested"
left="data.virtual_remaining_leaves"
usable="data.usable_remaining_leaves" />
</t>
</div>
<span t-if="props.requires_allocation and data.closest_allocation_expire !== false" class="text-uppercase o_timeoff_validity">
<t t-if="data.closest_allocation_remaining != data.virtual_remaining_leaves">
(<t t-esc="data.closest_allocation_remaining"/> <t t-if="data.request_unit == 'hour'">hours</t><t t-else="">days</t>
valid until <span t-esc="data.closest_allocation_expire"/>)
</t>
<t t-else="">
(valid until <span t-esc="data.closest_allocation_expire"/>)
</t>
</span>
</div>
<t t-name="hr_holidays.TimeOffCardMobile" owl="1">
<t t-set="data" t-value="props.data"/>
<t t-set="duration" t-value="props.requires_allocation ? data.virtual_remaining_leaves : data.virtual_leaves_taken" />
<span class="float-end o_timeoff_card_mobile">
<t t-if="props.requires_allocation" name="duration_type">
<strong t-esc="duration" class="o_timeoff_green"/> / <span t-esc="data.max_leaves"/> <t t-if="data.request_unit == 'hour'">Hours</t><t t-else="">Days</t> <span class="o_timeoff_green">Available</span>
</t>
<t t-else="">
<strong t-esc="duration"/> <t t-if="data.request_unit == 'hour'">Hours</t><t t-else="">Days</t> <span class="text-odoo">Taken</span>
</t>
</span>
</t>
<t t-name="hr_holidays.TimeOffCardPopover" owl="1">
<div class="o_timeoff_info">
<Popover position="'right'" popoverClass="'o_timeoff_popover'">
<span class="fa fa-question-circle-o"/>
<t t-set-slot="opened">
<ul>
<li>Allocated: <span t-esc="props.allocated"/></li>
<li>Approved: <span t-esc="props.approved"/></li>
<li style="border-bottom: 1px solid gray;">Planned: <span t-esc="props.planned"/></li>
<li>Left: <span t-esc="props.left"/></li>
<li t-if="props.left != props.usable">Usable: <span t-esc="props.usable"/></li>
</ul>
</t>
</Popover>
</div>
</t>
</templates>

View file

@ -0,0 +1,42 @@
/* @odoo-module */
import { TimeOffCard } from './time_off_card';
import { useBus, useService } from "@web/core/utils/hooks";
const { Component, useState, onWillStart } = owl;
export class TimeOffDashboard extends Component {
setup() {
this.orm = useService("orm");
this.state = useState({
holidays: [],
});
useBus(this.env.timeOffBus, 'update_dashboard', async () => {
await this.loadDashboardData()
});
onWillStart(async () => {
await this.loadDashboardData();
});
}
async loadDashboardData() {
const context = {};
if (this.props.employeeId !== null) {
context['employee_id'] = this.props.employeeId;
}
this.state.holidays = await this.orm.call(
'hr.leave.type',
'get_days_all_request',
[],
{
context: context
}
);
}
}
TimeOffDashboard.components = { TimeOffCard };
TimeOffDashboard.template = 'hr_holidays.TimeOffDashboard';
TimeOffDashboard.props = ['employeeId'];

View file

@ -0,0 +1,11 @@
.o_timeoff_dashboard {
display: flex;
justify-content: space-between;
height: auto;
box-shadow: inset 0 -1px 0 $border-color;
position: sticky;
top: 0;
z-index: 100;
background-color: $o-webclient-background-color;
}

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<div t-name="hr_holidays.TimeOffDashboard" owl="1" class="o_timeoff_dashboard">
<t t-foreach="state.holidays" t-as="holiday" t-key="holiday[3]">
<TimeOffCard name="holiday[0]" data="holiday[1]" requires_allocation="holiday[2] == 'yes'" id="holiday[3]"/>
</t>
</div>
</templates>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,17 @@
/** @odoo-module **/
import FieldRegistry from 'web.field_registry';
import basic_fields from 'web.basic_fields';
var FieldFloat = basic_fields.FieldFloat;
var FloatWithoutTrailingZeros = FieldFloat.extend({
_renderReadonly: function () {
var value = this._formatValue(this.value);
var parsed_value = parseFloat(value);
value = parsed_value.toString().replace(/\.0+$/, '');
this.$el.text(value);
}
});
FieldRegistry.add('float_without_trailing_zeros', FloatWithoutTrailingZeros);

View file

@ -0,0 +1,105 @@
/* @odoo-module */
import { useService } from '@web/core/utils/hooks';
import { registry } from '@web/core/registry'
import { formatDate } from "@web/core/l10n/dates";
const { Component, useState, onWillStart, onWillUpdateProps } = owl;
const { DateTime } = luxon;
export class LeaveStatsComponent extends Component {
setup() {
this.orm = useService('orm');
this.state = useState({
leaves: [],
departmentLeaves: [],
});
this.date = this.props.record.data.date_from || DateTime.now();
this.department = this.props.record.data.department_id;
this.employee = this.props.record.data.employee_id;
onWillStart(async () => {
await this.loadLeaves(this.date, this.employee);
await this.loadDepartmentLeaves(this.date, this.department, this.employee);
});
onWillUpdateProps(async (nextProps) => {
const dateFrom = nextProps.record.data.date_from || DateTime.now();
const dateChanged = this.date !== dateFrom;
const employee = nextProps.record.data.employee_id;
const department = nextProps.record.data.department_id;
if (dateChanged || employee && (this.employee && this.employee[0]) !== employee[0]) {
await this.loadLeaves(dateFrom, employee);
}
if (dateChanged || department && (this.department && this.department[0]) !== department[0]) {
await this.loadDepartmentLeaves(dateFrom, department, employee);
}
this.date = dateFrom;
this.employee = employee;
this.department = department;
})
}
get thisYear() {
return this.date.toFormat('yyyy');
}
async loadDepartmentLeaves(date, department, employee) {
if (!(department && employee && date)) {
this.state.departmentLeaves = [];
return;
}
const dateFrom = date.startOf('month');
const dateTo = date.endOf('month');
const departmentLeaves = await this.orm.searchRead(
'hr.leave',
[
['department_id', '=', department[0]],
['state', '=', 'validate'],
['holiday_type', '=', 'employee'],
['date_from', '<=', dateTo],
['date_to', '>=', dateFrom],
],
['employee_id', 'date_from', 'date_to', 'number_of_days'],
);
this.state.departmentLeaves = departmentLeaves.map((leave) => {
return Object.assign({}, leave, {
dateFrom: formatDate(DateTime.fromSQL(leave.date_from, { zone: 'utc' }).toLocal()),
dateTo: formatDate(DateTime.fromSQL(leave.date_to, { zone: 'utc' }).toLocal()),
sameEmployee: leave.employee_id[0] === employee[0],
});
});
}
async loadLeaves(date, employee) {
if (!(employee && date)) {
this.state.leaves = [];
return;
}
const dateFrom = date.startOf('year');
const dateTo = date.endOf('year');
this.state.leaves = await this.orm.readGroup(
'hr.leave',
[
['employee_id', '=', employee[0]],
['state', '=', 'validate'],
['date_from', '<=', dateTo],
['date_to', '>=', dateFrom]
],
['holiday_status_id', 'number_of_days:sum'],
['holiday_status_id'],
);
}
}
LeaveStatsComponent.template = 'hr_holidays.LeaveStatsComponent';
registry.category('view_widgets').add('hr_leave_stats', LeaveStatsComponent);

View file

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<div t-name="hr_holidays.LeaveStatsComponent" owl="1" class="o_leave_stats">
<div t-if="employee" id="o_leave_stats_employee">
<div class="o_hr_leave_subtitle">
<t t-esc="employee[1]"/> in <t t-esc="thisYear"/>
</div>
<div t-if="state.leaves.length === 0">
None
</div>
<div t-foreach="state.leaves" t-as="leave" t-key="leave_index" class="d-flex flex-row justify-content-between">
<span t-esc="leave.holiday_status_id[1]"/>
<span><t t-esc="leave.number_of_days"/> day(s)</span>
</div>
</div>
<div t-if="department" id="o_leave_stats_department">
<div class="o_horizontal_separator o_hr_leave_subtitle">
<t t-esc="department[1]"/>
</div>
<div t-if="state.departmentLeaves.length === 0">
None
</div>
<div t-foreach="state.departmentLeaves" t-as="leave" t-key="leave_index" t-attf-class="d-flex flex-row justify-content-between {{leave.sameEmployee ? 'fw-bold': ''}}">
<span><t t-esc="leave.employee_id[1]"/>: <t t-esc="leave.number_of_days"/> day(s)</span>
<span><t t-esc="leave.dateFrom"/> - <t t-esc="leave.dateTo"/></span>
</div>
</div>
</div>
</templates>

View file

@ -0,0 +1,52 @@
/** @odoo-module **/
import { registerPatch } from '@mail/model/model_core';
import { attr } from '@mail/model/model_field';
import { clear } from '@mail/model/model_field_command';
import { str_to_date } from 'web.time';
registerPatch({
name: 'Partner',
fields: {
isOnline: {
compute() {
if (['leave_online', 'leave_away'].includes(this.im_status)) {
return true;
}
return this._super();
},
},
/**
* Date of end of the out of office period of the partner as string.
* String is expected to use Odoo's date string format
* (examples: '2011-12-01' or '2011-12-01').
*/
out_of_office_date_end: attr(),
/**
* Text shown when partner is out of office.
*/
outOfOfficeText: attr({
compute() {
if (!this.out_of_office_date_end) {
return clear();
}
if (!this.messaging.locale || !this.messaging.locale.language) {
return clear();
}
const currentDate = new Date();
const date = str_to_date(this.out_of_office_date_end);
const options = { day: 'numeric', month: 'short' };
if (currentDate.getFullYear() !== date.getFullYear()) {
options.year = 'numeric';
}
let localeCode = this.messaging.locale.language.replace(/_/g, '-');
if (localeCode === "sr@latin") {
localeCode = "sr-Latn-RS";
}
const formattedDate = date.toLocaleDateString(localeCode, options);
return _.str.sprintf(this.env._t("Out of office until %s"), formattedDate);
},
}),
},
});

View file

@ -0,0 +1,14 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { RadioField, preloadRadio } from "@web/views/fields/radio/radio_field";
class RadioImageField extends RadioField {}
RadioImageField.template = "hr_holidays.RadioImageField";
registry.category("fields").add("hr_holidays_radio_image", RadioImageField);
registry.category("preloadedData").add("hr_holidays_radio_image", {
loadOnTypes: ["many2one"],
preload: preloadRadio,
});

View file

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="hr_holidays.RadioImageField" owl="1">
<div role="radiogroup" class="d-flex flex-wrap" t-att-aria-label="string">
<t t-if="props.readonly">
<t t-if="value !== false">
<div>
<img t-attf-src="/hr_holidays/static/src/img/icons/{{items.find((item) => item[0] === value)[1]}}" width="48" height="48" />
</div>
</t>
</t>
<t t-else="">
<t t-foreach="items" t-as="item" t-key="item[0]">
<div class="form-check o_radio_item" aria-atomic="true">
<input
type="radio"
class="form-check-input o_radio_input"
t-att-checked="item[0] === value"
t-att-disabled="props.readonly"
t-att-name="id"
t-att-data-value="item[0]"
t-att-data-index="item_index"
t-att-id="`${id}_${item[0]}`"
t-on-change="() => this.onChange(item)"
/>
<label class="form-check-label o_form_label" t-att-for="`${id}_${item[0]}`">
<img t-attf-src="/hr_holidays/static/src/img/icons/{{item[1]}}" width="48" height="48" />
</label>
</div>
</t>
</t>
</div>
</t>
</templates>

View file

@ -0,0 +1,115 @@
$o-hr-holidays-border-color: map-get($grays, '300');
.o_hr_holidays_hierarchy {
margin-left: -$o-horizontal-padding;
margin-right: -$o-horizontal-padding;
@include media-breakpoint-up(lg, $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: rgba(128, 128, 128, 0.15);
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 {
counter-reset: o-hr-holidays-accrual-plan-level-counter;
.o-kanban-button-new {
padding: 2px 12px;
margin: 0px 0px 0px 44px;
border-radius: 25px;
}
.o_legacy_kanban_view.o_kanban_ungrouped .o_kanban_record,
.o_kanban_renderer.o_kanban_ungrouped .o_kanban_record {
counter-increment: o-hr-holidays-accrual-plan-level-counter;
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%);
}
.o_hr_holidays_plan_level_level::before {
content: counter(o-hr-holidays-accrual-plan-level-counter);
}
// 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: $o-btn-secondary-bg;
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-color: $o-btn-secondary-bg;
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-color: $o-btn-secondary-bg;
border-bottom: 1px solid $o-hr-holidays-border-color;
transform: rotate(45deg);
}
// Circle
&:after {
content: '';
@include o-position-absolute($top: 14px, $left: -36px);
width: 12px;
height: 12px;
border: 2px solid $o-brand-primary;
border-radius: 10px;
background: $o-btn-secondary-bg;
}
.content {
position: relative;
background-color: $o-btn-secondary-bg;
padding: 5px 7px;
font-size: 14px;
}
}
}
}
}
}

View file

@ -0,0 +1,50 @@
.o_hr_leave_form {
.o_form_sheet {
padding: 0!important;
overflow: hidden;
.ribbon-top-right {
top: 16px;
right: -8px;
}
}
.o_hr_leave_content {
margin: 0;
.o_group {
margin: 0;
}
.o_hr_leave_title, .o_hr_leave_subtitle {
line-height: 1.2;
font-weight: 500;
margin-bottom: 8px;
* {
margin-bottom: 0;
}
}
.o_hr_leave_title {
font-size: 1.8rem;
}
.o_hr_leave_subtitle {
font-size: 1.2rem;
}
.o_hr_leave_column {
padding: 16px;
}
.col_right {
background-color: map-get($grays, '200');
padding-top: 28px;
}
.o_hr_leave_date {
display: flex;
flex-direction: row;
@include media-breakpoint-down(lg) {
flex-direction: column;
}
@include media-breakpoint-down(md) {
flex-direction: row;
}
}
.o_leave_stats {
margin-bottom: 16px;
}
}
}

View file

@ -0,0 +1,89 @@
/** @odoo-module **/
import tour from 'web_tour.tour';
import { _t } from 'web.core';
const leaveType = "NotLimitedHR";
const leaveDateFrom = "01/17/2022";
const leaveDateTo = "01/17/2022";
const description = 'Days off';
tour.register('hr_holidays_tour', {
url: '/web',
rainbowManMessage: _t("Congrats, we can see that your request has been validated."),
test: false
}, [
tour.stepUtils.showAppsMenuItem(),
{
trigger: '.o_app[data-menu-xmlid="hr_holidays.menu_hr_holidays_root"]',
content: _t("Let's discover the Time Off application"),
position: 'bottom',
},
{
trigger: 'button.btn-time-off',
content: _t("Click on any date or on this button to request a time-off"),
position: 'bottom',
},
{
trigger: 'div[name="holiday_status_id"] input',
content: _t("Let's try to create a Sick Time Off, select it in the list"),
run: `text ${leaveType}`,
},
{
trigger: `.ui-autocomplete .ui-menu-item a:contains("${leaveType}")`,
run: "click",
auto: true,
in_modal: false,
},
{
trigger: '.o_field_widget[name="request_date_from"] input',
content: _t("You can select the period you need to take off, from start date to end date"),
position: 'right',
run: `text ${leaveDateFrom}`,
},
{
trigger: '.o_field_widget[name="request_date_to"] input',
content: _t("You can select the period you need to take off, from start date to end date"),
position: 'right',
run: `text ${leaveDateTo}`,
},
{
trigger: 'div[name="name"] textarea',
content: _t("Add some description for the people that will validate it"),
run: `text ${description}`,
position: 'right'
},
{
trigger: `button:contains(${_t('Save')})`,
content: _t("Submit your request"),
position: 'bottom',
},
{
trigger: 'button[data-menu-xmlid="hr_holidays.menu_hr_holidays_approvals"]',
content: _t("Let's go validate it"),
position: 'bottom'
},
{
trigger: 'a[data-menu-xmlid="hr_holidays.menu_open_department_leave_approve"]',
content: _t("Select Time Off"),
position: 'right'
},
{
trigger: 'table.o_list_table',
content: _t("Select the request you just created"),
position: 'bottom',
run: function(actions) {
const rows = this.$anchor.find('tr.o_data_row');
actions.click(rows[0]);
}
},
{
trigger: 'button[name="action_approve"]',
content: _t("Let's approve it"),
position: 'bottom'
},
{
trigger: 'a[data-menu-xmlid="hr_holidays.menu_hr_holidays_root"]',
content: _t("State is now confirmed. We can go back to the calendar"),
position: 'bottom'
}
]);

View file

@ -0,0 +1,124 @@
/** @odoo-module */
import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
import { CalendarController } from '@web/views/calendar/calendar_controller';
import { FormViewDialog } from '@web/views/view_dialogs/form_view_dialog';
import { serializeDate } from "@web/core/l10n/dates";
import { TimeOffCalendarFilterPanel } from './filter_panel/calendar_filter_panel';
import { TimeOffFormViewDialog } from '../view_dialog/form_view_dialog';
import { useLeaveCancelWizard } from '../hooks';
const { EventBus, useSubEnv } = owl;
export class TimeOffCalendarController extends CalendarController {
setup() {
super.setup();
useSubEnv({
timeOffBus: new EventBus(),
});
this.leaveCancelWizard = useLeaveCancelWizard();
}
get employeeId() {
return this.model.employeeId;
}
get filterPanelProps() {
return {
...super.filterPanelProps,
employee_id: this.employeeId,
};
}
newTimeOffRequest() {
const context = {};
if (this.employeeId) {
context['default_employee_id'] = this.employeeId;
}
if (this.model.meta.scale == 'day') {
context['default_date_from'] = serializeDate(
this.model.data.range.start.set({ hours: 7 }), "datetime"
);
context['default_date_to'] = serializeDate(
this.model.data.range.end.set({ hours: 19 }), "datetime"
);
}
this.displayDialog(FormViewDialog, {
resModel: 'hr.leave',
title: this.env._t('New Time Off'),
viewId: this.model.formViewId,
onRecordSaved: () => {
this.model.load();
this.env.timeOffBus.trigger('update_dashboard');
},
context: context,
});
}
newAllocationRequest() {
const context = {
'form_view_ref': 'hr_holidays.hr_leave_allocation_view_form_dashboard',
};
if (this.employeeId) {
context['default_employee_id'] = this.employeeId;
context['default_employee_ids'] = [this.employeeId];
context['form_view_ref'] = 'hr_holidays.hr_leave_allocation_view_form_manager_dashboard';
}
this.displayDialog(FormViewDialog, {
resModel: 'hr.leave.allocation',
title: this.env._t('New Allocation'),
context: context,
});
}
deleteRecord(record) {
if (!record.can_cancel) {
this.displayDialog(ConfirmationDialog, {
title: this.env._t("Confirmation"),
body: this.env._t("Are you sure you want to delete this record ?"),
confirm: async () => {
await this.model.unlinkRecord(record.id);
this.env.timeOffBus.trigger('update_dashboard');
},
cancel: () => {},
});
} else {
this.leaveCancelWizard(record.id, () => {
this.model.load();
this.env.timeOffBus.trigger('update_dashboard');
});
}
}
async editRecord(record, context = {}, shouldFetchFormViewId = true) {
const onDialogClosed = () => {
this.model.load();
this.env.timeOffBus.trigger('update_dashboard');
};
return new Promise((resolve) => {
this.displayDialog(
TimeOffFormViewDialog, {
resModel: this.model.resModel,
resId: record.id || false,
context,
title: this.env._t("Time Off Request"),
viewId: this.model.formViewId,
onRecordSaved: onDialogClosed,
onRecordDeleted: (record) => this.deleteRecord(record),
onLeaveCancelled: onDialogClosed,
},
{ onClose: () => resolve() }
);
});
}
}
TimeOffCalendarController.template = "hr_holidays.CalendarController";
TimeOffCalendarController.components = {
...TimeOffCalendarController.components,
FilterPanel: TimeOffCalendarFilterPanel,
}

View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="hr_holidays.CalendarController" t-inherit="web.CalendarController" t-inherit-mode="primary" owl="1">
<xpath expr="//DatePicker" position="replace"/>
</t>
<t t-name="hr_holidays.CalendarController.controlButtons" t-inherit="web.CalendarController.controlButtons" t-inherit-mode="primary" owl="1">
<xpath expr="//span[hasclass('o_calendar_scale_buttons')]" position="after">
<span class="o_timeoff_buttons">
<button class="btn btn-primary btn-time-off mx-1" t-on-click="newTimeOffRequest" type="button">
New Time Off
</button>
<button class="btn btn-secondary" t-on-click="newAllocationRequest" type="button">
<t t-if="employeeId">New</t> Allocation Request
</button>
</span>
</xpath>
</t>
</templates>

View file

@ -0,0 +1,75 @@
/** @odoo-module */
import { CalendarModel } from '@web/views/calendar/calendar_model';
import { deserializeDateTime, serializeDate, serializeDateTime } from "@web/core/l10n/dates";
export class TimeOffCalendarModel extends CalendarModel {
setup(params, services) {
super.setup(params, services);
this.data.stressDays = {};
if (this.env.isSmall) {
this.meta.scale = 'month';
}
}
/**
* @override
*/
normalizeRecord(rawRecord) {
let result = super.normalizeRecord(...arguments);
if (rawRecord.employee_id) {
const employee = rawRecord.employee_id[1];
result.title = [employee, result.title].join(' ');
}
return result;
}
makeContextDefaults(record) {
const { scale } = this.meta;
const context = super.makeContextDefaults(record);
if (this.employeeId) {
context['default_employee_id'] = this.employeeId;
}
if(['day', 'week'].includes(scale)) {
if ('default_date_from' in context) {
context['default_date_from'] = serializeDateTime(deserializeDateTime(context['default_date_from']).set({ hours: 7 }));
}
if ('default_date_to' in context) {
context['default_date_to'] = serializeDateTime(deserializeDateTime(context['default_date_from']).set({ hours: 19 }));
}
}
return context;
}
async updateData(data) {
await super.updateData(data);
data.stressDays = await this.fetchStressDays(data);
}
async fetchStressDays(data) {
return this.orm.call("hr.employee", "get_stress_days", [
this.employeeId,
serializeDate(data.range.start, "datetime"),
serializeDate(data.range.end, "datetime"),
]);
}
get stressDays() {
return this.data.stressDays;
}
get employeeId() {
return this.meta.context.employee_id && this.meta.context.employee_id[0] || null;
}
fetchRecords(data) {
const { fieldNames, resModel } = this.meta;
const context = {};
if (!this.employeeId) {
context['short_name'] = 1;
}
return this.orm.searchRead(resModel, this.computeDomain(data), fieldNames, { context });
}
}

View file

@ -0,0 +1,33 @@
/** @odoo-module */
import { CalendarRenderer } from '@web/views/calendar/calendar_renderer';
import { TimeOffCalendarCommonRenderer } from './common/calendar_common_renderer';
import { TimeOffCalendarYearRenderer } from './year/calendar_year_renderer';
import { TimeOffDashboard } from '../../dashboard/time_off_dashboard';
export class TimeOffCalendarRenderer extends CalendarRenderer {
get employeeId() {
return this.props.model.employeeId;
}
get showDashboard() {
return false;
}
}
TimeOffCalendarRenderer.template = 'hr_holidays.CalendarRenderer';
TimeOffCalendarRenderer.components = {
...TimeOffCalendarRenderer.components,
day: TimeOffCalendarCommonRenderer,
week: TimeOffCalendarCommonRenderer,
month: TimeOffCalendarCommonRenderer,
year: TimeOffCalendarYearRenderer,
TimeOffDashboard,
};
export class TimeOffDashboardCalendarRenderer extends TimeOffCalendarRenderer {
get showDashboard() {
return !this.env.isSmall;
}
}

View file

@ -0,0 +1,20 @@
.o_timeoff_calendar {
.o_calendar_renderer {
height: unset;
@for $size from 1 through length($o-colors) {
.o_calendar_widget {
.hr_stress_day_top_#{$size - 1}:not(.fc-disabled-day) {
.fc-day-number {
color: nth($o-colors, $size) !important;
font-weight: 600;
}
}
}
}
.fc-bgevent {
border-radius: 25px;
}
}
}

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="hr_holidays.CalendarRenderer" owl="1">
<div class="o_timeoff_calendar">
<TimeOffDashboard t-if="showDashboard" employeeId="employeeId"/>
<t t-call="web.CalendarRenderer"/>
</div>
</t>
</templates>

View file

@ -0,0 +1,25 @@
/** @odoo-module */
import { calendarView } from '@web/views/calendar/calendar_view';
import { TimeOffCalendarController } from './calendar_controller';
import { TimeOffCalendarModel } from './calendar_model';
import { TimeOffCalendarRenderer, TimeOffDashboardCalendarRenderer } from './calendar_renderer';
import { registry } from '@web/core/registry';
const TimeOffCalendarView = {
...calendarView,
Controller: TimeOffCalendarController,
Renderer: TimeOffCalendarRenderer,
Model: TimeOffCalendarModel,
buttonTemplate: "hr_holidays.CalendarController.controlButtons",
}
registry.category('views').add('time_off_calendar', TimeOffCalendarView);
registry.category('views').add('time_off_calendar_dashboard', {
...TimeOffCalendarView,
Renderer: TimeOffDashboardCalendarRenderer,
});

View file

@ -0,0 +1,25 @@
/** @odoo-module */
import { CalendarCommonPopover } from "@web/views/calendar/calendar_common/calendar_common_popover";
import { useService } from "@web/core/utils/hooks";
export class TimeOffCalendarCommonPopover extends CalendarCommonPopover {
setup() {
super.setup();
this.dialog = useService('dialog');
this.action = useService('action');
}
get isEventDeletable() {
const record = this.props.record.rawRecord;
const state = record.state;
return record.can_cancel || state && !['validate', 'refuse'].includes(state);
}
get isEventEditable() {
const state = this.props.record.rawRecord.state;
return state !== undefined;
}
}

View file

@ -0,0 +1,23 @@
/** @odoo-module */
import { CalendarCommonRenderer } from '@web/views/calendar/calendar_common/calendar_common_renderer';
import { useStressDays } from '../../hooks';
import { TimeOffCalendarCommonPopover } from './calendar_common_popover';
export class TimeOffCalendarCommonRenderer extends CalendarCommonRenderer {
setup() {
super.setup();
this.stressDays = useStressDays(this.props);
}
onDayRender(info) {
super.onDayRender(info);
this.stressDays(info);
}
}
TimeOffCalendarCommonRenderer.components = {
...TimeOffCalendarCommonRenderer,
Popover: TimeOffCalendarCommonPopover,
}

View file

@ -0,0 +1,8 @@
// = HR Holidays
// ============================================================================
// No CSS hacks, variables overrides only
.o_calendar_filter {
--calendarFilter-icon--plain: url("/hr/static/src/img/icons/plain_dark.svg");
--calendarFilter-icon--hatched: url("/hr/static/src/img/icons/hatched_dark.svg");
--calendarFilter-icon--line: url("/hr/static/src/img/icons/line_dark.svg");
}

View file

@ -0,0 +1,79 @@
/** @odoo-module */
import { CalendarFilterPanel } from "@web/views/calendar/filter_panel/calendar_filter_panel";
import { TimeOffCardMobile } from "../../../dashboard/time_off_card";
import { getFormattedDateSpan } from '@web/views/calendar/utils';
import { useService } from "@web/core/utils/hooks";
import { serializeDate } from "@web/core/l10n/dates";
const { useState, onWillStart, onWillUpdateProps } = owl;
export class TimeOffCalendarFilterPanel extends CalendarFilterPanel {
setup() {
super.setup();
this.orm = useService('orm');
this.getFormattedDateSpan = getFormattedDateSpan;
this.leaveState = useState({
holidays: [],
stressDays: [],
bankHolidays: [],
});
onWillStart(async () => {
await this.loadFilterData();
await this.updateSpecialDays();
});
onWillUpdateProps(this.updateSpecialDays);
}
async updateSpecialDays() {
const context = {
'employee_id': this.props.employee_id,
}
const specialDays = await this.orm.call(
'hr.employee', 'get_special_days_data', [
serializeDate(this.props.model.rangeStart, "datetime"),
serializeDate(this.props.model.rangeEnd, "datetime"),
],
{
'context': context,
},
);
specialDays['bankHolidays'].forEach(bankHoliday => {
bankHoliday.start = luxon.DateTime.fromISO(bankHoliday.start)
bankHoliday.end = luxon.DateTime.fromISO(bankHoliday.end)
});
specialDays['stressDays'].forEach(stressDay => {
stressDay.start = luxon.DateTime.fromISO(stressDay.start)
stressDay.end = luxon.DateTime.fromISO(stressDay.end)
});
this.leaveState.bankHolidays = specialDays['bankHolidays'];
this.leaveState.stressDays = specialDays['stressDays'];
}
async loadFilterData() {
if(!this.env.isSmall) {
return;
}
const filterData = {};
const data = await this.orm.call(
'hr.leave.type', 'get_days_all_request', [],
);
data.forEach((leave) => {
filterData[leave[3]] = leave;
});
this.leaveState.holidays = filterData;
}
}
TimeOffCalendarFilterPanel.template = 'hr_holidays.CalendarFilterPanel';
TimeOffCalendarFilterPanel.components = {
...TimeOffCalendarFilterPanel.components,
TimeOffCardMobile,
}
TimeOffCalendarFilterPanel.subTemplates = {
filter: "hr_holidays.CalendarFilterPanel.filter",
}

View file

@ -0,0 +1,45 @@
.o_calendar_filter {
span {
vertical-align: middle;
}
& img {
width:30px;
&.o_calendar_filter_plain {
content:var(--calendarFilter-icon--plain);
}
&.o_calendar_filter_hatched {
content:var(--calendarFilter-icon--hatched);
}
&.o_calendar_filter_line {
content:var(--calendarFilter-icon--line);
}
}
.o_timeoff_legend {
display: inline-block;
width: 24px;
height: 30px;
margin: 0 3px;
padding: 3px 0;
text-align: center;
&_bankholiday {
background-color: $gray-200;
}
&_stressday {
font-weight: 600;
}
}
@for $size from 1 through length($o-colors) {
.hr_stress_day_#{$size - 1}:not(.fc-disabled-day) {
color: nth($o-colors, $size) !important;
}
}
}

View file

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="hr_holidays.CalendarFilterPanel" t-inherit="web.CalendarFilterPanel" t-inherit-mode="primary" owl="1">
<xpath expr="//t[@t-foreach='props.model.filterSections']" position="after">
<div class="o_calendar_filter">
<h5>Legend</h5>
<div class="d-flex flex-column">
<span><img class="o_calendar_filter_plain" src="/hr/static/src/img/icons/plain.svg"/> Validated</span>
<span><img class="o_calendar_filter_hatched" src="/hr/static/src/img/icons/hatched.svg"/> To Approve</span>
<span><img class="o_calendar_filter_line" src="/hr/static/src/img/icons/line.svg"/> Refused</span>
<span><span class="o_timeoff_legend o_timeoff_legend_bankholiday">13</span> Public Holiday</span>
<span><span class="o_timeoff_legend o_timeoff_legend_stressday text-odoo">13</span> Stress Day</span>
</div>
<div class="d-flex flex-column mt-4" t-if="leaveState.stressDays.length">
<h5>Stress Days</h5>
<ul class="ps-2">
<li t-foreach="leaveState.stressDays" t-as="stressDay" t-key="stressDay.id" class="mt-2 list-unstyled">
<strong
t-esc="getFormattedDateSpan(stressDay.start, stressDay.end)"
t-att-class="'hr_stress_day_'+stressDay.colorIndex"/>
: <t t-esc="stressDay.title"/>
</li>
</ul>
</div>
<div class="d-flex flex-column mt-4" t-if="leaveState.bankHolidays.length">
<h5>Public Holidays</h5>
<ul class="ps-2">
<li t-foreach="leaveState.bankHolidays" t-as="bankHoliday" t-key="bankHoliday.id" class="mt-2 list-unstyled">
<strong
t-esc="getFormattedDateSpan(bankHoliday.start, bankHoliday.end)"/>
: <t t-esc="bankHoliday.title"/>
</li>
</ul>
</div>
</div>
</xpath>
</t>
<t t-name="hr_holidays.CalendarFilterPanel.filter" t-inherit="web.CalendarFilterPanel.filter" t-inherit-mode="primary" owl="1">
<xpath expr="//span[@t-esc='filter.label']" position="replace">
<span class="o_cw_filter_title text-truncate flex-grow">
<t t-esc="filter.label"/>
<t t-if="env.isSmall">
<t t-set="holiday" t-value="leaveState.holidays[filter.value]"/>
<TimeOffCardMobile t-if="holiday" name="holiday[0]" id="holiday[3]" data="holiday[1]" requires_allocation="holiday[2] === 'yes'" />
</t>
</span>
</xpath>
</t>
</templates>

View file

@ -0,0 +1,13 @@
/** @odoo-module **/
import { Dialog } from "@web/core/dialog/dialog";
import { CalendarYearPopover } from "@web/views/calendar/calendar_year/calendar_year_popover";
export class TimeOffCalendarYearPopover extends CalendarYearPopover {}
TimeOffCalendarYearPopover.components = { Dialog };
TimeOffCalendarYearPopover.template = "web.CalendarYearPopover";
TimeOffCalendarYearPopover.subTemplates = {
...CalendarYearPopover.subTemplates,
body: "hr_holidays.StressDayCalendarYearPopover.body",
};

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="hr_holidays.StressDayCalendarYearPopover.body" owl="1">
<t t-foreach="recordGroups" t-as="recordGroup" t-key="recordGroup.title">
<div class="fw-bold mt-2" t-esc="recordGroup.title" />
<t t-foreach="recordGroup.records" t-as="record" t-key="record.id">
<t t-if="record.id &lt; 0">
<p class="">
<t t-if="record.startHour"><t t-esc="record.startHour" /> </t>
<t t-esc="record.title"/>
</p>
</t>
<t t-else="">
<t t-call="{{ constructor.subTemplates.record }}" />
</t>
</t>
</t>
</t>
</templates>

View file

@ -0,0 +1,71 @@
/** @odoo-module */
import { CalendarYearRenderer } from '@web/views/calendar/calendar_year/calendar_year_renderer';
import { useService } from "@web/core/utils/hooks";
import { useStressDays } from '../../hooks';
import { useCalendarPopover } from '@web/views/calendar/hooks';
import { TimeOffCalendarYearPopover } from './calendar_year_popover';
const { useEffect } = owl;
export class TimeOffCalendarYearRenderer extends CalendarYearRenderer {
setup() {
super.setup();
this.orm = useService("orm");
this.stressDays = useStressDays(this.props);
this.stressDaysList = [];
this.stressDayPopover = useCalendarPopover(TimeOffCalendarYearPopover);
useEffect((el) => {
for (const week of el) {
const row = week.parentElement;
// Remove the week number if the week is empty.
// FullCalendar always displays 6 weeks even when empty.
if (!row.children[1].classList.length &&
!row.children[row.children.length - 1].classList.length) {
row.remove();
}
}
}, () => [this.rootRef.el && this.rootRef.el.querySelectorAll('.fc-content-skeleton td.fc-week-number')]);
}
get options() {
return Object.assign(super.options, {
weekNumbers: true,
weekNumbersWithinDays: false,
weekLabel: this.env._t('Week'),
});
}
/** @override **/
async onDateClick(info) {
const is_stress_day = [...info.dayEl.classList].some(elClass => elClass.startsWith('hr_stress_day_'))
this.stressDayPopover.close();
if (is_stress_day && !this.env.isSmall) {
this.popover.close();
const date = luxon.DateTime.fromISO(info.dateStr);
const target = info.dayEl;
const stress_days_data = await this.orm.call("hr.employee", "get_stress_days_data", [date, date]);
stress_days_data.forEach(stress_day_data => {
stress_day_data['start'] = luxon.DateTime.fromISO(stress_day_data['start'])
stress_day_data['end'] = luxon.DateTime.fromISO(stress_day_data['end'])
});
const records = Object.values(this.props.model.records).filter((r) =>
luxon.Interval.fromDateTimes(r.start.startOf("day"), r.end.endOf("day")).contains(date)
);
const props = this.getPopoverProps(date, records)
props['records'] = stress_days_data.concat(props['records'])
this.stressDayPopover.open(target, props, "o_cw_popover");
}
else {
super.onDateClick(info);
}
}
onDayRender(info) {
super.onDayRender(info);
this.stressDaysList = this.stressDays(info);
}
}

View file

@ -0,0 +1,14 @@
.o_timeoff_calendar {
.o_calendar_widget {
.fc-dayGridYear-view {
.fc-week-number {
color: #adb5bd;
font-size: 0.85rem;
cursor: default;
vertical-align: middle;
line-height: unset;
}
}
}
}

View file

@ -0,0 +1,43 @@
/** @odoo-module */
import { useService } from "@web/core/utils/hooks";
const { useEnv } = owl;
export function useStressDays(props) {
return (info) => {
const date = luxon.DateTime.fromJSDate(info.date).toISODate();
const stressDay = props.model.stressDays[date];
if (stressDay) {
const dayNumberElTop = info.view.el.querySelector(`.fc-day-top[data-date="${info.el.dataset.date }"]`)
const dayNumberEl = info.view.el.querySelector(`.fc-day[data-date="${info.el.dataset.date }"]`)
if (dayNumberElTop) {
dayNumberElTop.classList.add(`hr_stress_day_top_${stressDay}`);
}
if (dayNumberEl) {
dayNumberEl.classList.add(`hr_stress_day_${stressDay}`);
}
}
return props.model.stressDays;
}
}
export function useLeaveCancelWizard() {
const action = useService('action');
const env = useEnv();
return (leaveId, callback) => {
action.doAction({
name: env._t('Delete Confirmation'),
type: "ir.actions.act_window",
res_model: "hr.holidays.cancel.leave",
target: "new",
views: [[false, "form"]],
context: {
default_leave_id: leaveId,
}
}, {
onClose: callback,
});
}
}

View file

@ -0,0 +1,70 @@
/** @odoo-module */
import { FormViewDialog } from "@web/views/view_dialogs/form_view_dialog";
import { registry } from '@web/core/registry';
import { formView } from '@web/views/form/form_view';
import { FormController } from '@web/views/form/form_controller';
import { useLeaveCancelWizard } from '../hooks';
export class TimeOffDialogFormController extends FormController {
setup() {
super.setup();
this.leaveCancelWizard = useLeaveCancelWizard();
}
deleteRecord() {
const record = this.model.root.data
this.props.onRecordDeleted(record)
this.props.onCancelLeave();
if (record.can_cancel) {
this.leaveCancelWizard(record.id, () => {
this.props.onLeaveCancelled();
});
}
}
get canDelete() {
const record = this.model.root.data;
return !this.model.root.isNew && (record.can_cancel || record.state && ['confirm', 'validate', 'validate1'].includes(record.state));
}
}
TimeOffDialogFormController.props = {
...FormController.props,
onCancelLeave: Function,
onRecordDeleted: Function,
onLeaveCancelled: Function,
}
registry.category('views').add('timeoff_dialog_form', {
...formView,
Controller: TimeOffDialogFormController,
});
export class TimeOffFormViewDialog extends FormViewDialog {
setup() {
super.setup();
this.viewProps = Object.assign(this.viewProps, {
type: "timeoff_dialog_form",
buttonTemplate: 'hr_holidays.FormViewDialog.buttons',
onCancelLeave: () => {
this.props.close();
},
onRecordDeleted: (record) => {
this.props.onRecordDeleted(record)
},
onLeaveCancelled: this.props.onLeaveCancelled.bind(this),
})
}
}
TimeOffFormViewDialog.props = {
...TimeOffFormViewDialog.props,
onRecordDeleted: Function,
onLeaveCancelled: Function,
}

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="hr_holidays.FormViewDialog.buttons" t-inherit="web.FormViewDialog.ToOne.buttons" owl="1">
<xpath expr="//button[contains(@class, 'o_form_button_save')]" position="replace">
<button class="btn btn-primary o_form_button_save" t-on-click="saveButtonClicked" data-hotkey="c">Save</button>
</xpath>
<xpath expr="//button[contains(@class, 'o_form_button_cancel')]" position="after">
<button class="btn btn-secondary" t-if="canDelete" t-on-click="deleteRecord" data-hotkey="x">Delete</button>
</xpath>
</t>
</templates>

View file

@ -0,0 +1,27 @@
/** @odoo-module **/
import '@mail/../tests/helpers/mock_server'; // ensure mail overrides are applied first
import { patch } from "@web/core/utils/patch";
import { MockServer } from "@web/../tests/helpers/mock_server";
patch(MockServer.prototype, 'hr_holidays', {
/**
* Overrides to add out of office to employees.
*
* @override
*/
_mockResPartnerMailPartnerFormat(ids) {
const partnerFormats = this._super(...arguments);
const partners = this.getRecords(
'res.partner',
[['id', 'in', ids]],
{ active_test: false },
);
for (const partner of partners) {
// Not a real field but ease the testing
partnerFormats.get(partner.id).out_of_office_date_end = partner.out_of_office_date_end;
}
return partnerFormats;
},
});

View file

@ -0,0 +1,7 @@
/** @odoo-module **/
import { insertModelFields } from '@bus/../tests/helpers/model_definitions_helpers';
insertModelFields('res.partner', {
out_of_office_date_end: { type: 'date' },
});

View file

@ -0,0 +1,114 @@
/** @odoo-module **/
import { UPDATE_BUS_PRESENCE_DELAY } from '@bus/im_status_service';
import { start, startServer } from '@mail/../tests/helpers/test_utils';
QUnit.module('hr_holidays', {}, function () {
QUnit.module('components', {}, function () {
QUnit.module('persona_im_status_icon_tests.js');
QUnit.test('on leave & online', async function (assert) {
assert.expect(2);
const pyEnv = await startServer();
const partnerId = pyEnv['res.partner'].create({ im_status: 'leave_online' });
const mailChannelId = pyEnv['mail.channel'].create({});
pyEnv['mail.message'].create({
author_id: partnerId,
body: 'not empty',
model: 'mail.channel',
res_id: mailChannelId,
});
const { advanceTime, afterNextRender, openDiscuss } = await start({
discuss: {
params: {
default_active_id: mailChannelId,
},
},
hasTimeControl: true,
});
await openDiscuss();
await afterNextRender(() => advanceTime(UPDATE_BUS_PRESENCE_DELAY));
assert.hasClass(
document.querySelector('.o_PersonaImStatusIcon_icon'),
'o-online',
"persona IM status icon should have online status rendering"
);
assert.hasClass(
document.querySelector('.o_PersonaImStatusIcon_icon'),
'fa-plane',
"persona IM status icon should have leave status rendering"
);
});
QUnit.test('on leave & away', async function (assert) {
assert.expect(2);
const pyEnv = await startServer();
const partnerId = pyEnv['res.partner'].create({ im_status: 'leave_away' });
const mailChannelId = pyEnv['mail.channel'].create({});
pyEnv['mail.message'].create({
author_id: partnerId,
body: 'not empty',
model: 'mail.channel',
res_id: mailChannelId,
});
const { advanceTime, afterNextRender, openDiscuss } = await start({
discuss: {
params: {
default_active_id: mailChannelId,
},
},
hasTimeControl: true,
});
await openDiscuss();
await afterNextRender(() => advanceTime(UPDATE_BUS_PRESENCE_DELAY));
assert.hasClass(
document.querySelector('.o_PersonaImStatusIcon_icon'),
'o-away',
"persona IM status icon should have away status rendering"
);
assert.hasClass(
document.querySelector('.o_PersonaImStatusIcon_icon'),
'fa-plane',
"persona IM status icon should have leave status rendering"
);
});
QUnit.test('on leave & offline', async function (assert) {
assert.expect(2);
const pyEnv = await startServer();
const partnerId = pyEnv['res.partner'].create({ im_status: 'leave_offline' });
const mailChannelId = pyEnv['mail.channel'].create({});
pyEnv['mail.message'].create({
author_id: partnerId,
body: 'not empty',
model: 'mail.channel',
res_id: mailChannelId,
});
const { advanceTime, afterNextRender, openDiscuss } = await start({
discuss: {
params: {
default_active_id: mailChannelId,
},
},
hasTimeControl: true,
});
await openDiscuss();
await afterNextRender(() => advanceTime(UPDATE_BUS_PRESENCE_DELAY));
assert.hasClass(
document.querySelector('.o_PersonaImStatusIcon_icon'),
'o-offline',
"persona IM status icon should have offline status rendering"
);
assert.hasClass(
document.querySelector('.o_PersonaImStatusIcon_icon'),
'fa-plane',
"persona IM status icon should have leave status rendering"
);
});
});
});

View file

@ -0,0 +1,103 @@
/** @odoo-module **/
import {
start,
startServer,
} from '@mail/../tests/helpers/test_utils';
QUnit.module('mail', {}, function () {
QUnit.module('components', {}, function () {
QUnit.module('thread_icon_tests.js');
QUnit.test('thread icon of a chat when correspondent is on leave & online', async function (assert) {
assert.expect(2);
const pyEnv = await startServer();
const resPartnerId1 = pyEnv['res.partner'].create({
im_status: 'leave_online',
name: 'Demo',
});
pyEnv['mail.channel'].create({
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: resPartnerId1 }],
],
channel_type: 'chat',
});
const { openDiscuss } = await start();
await openDiscuss();
assert.containsOnce(
document.body,
'.o_ThreadIcon_online',
"thread icon should have online status rendering"
);
assert.hasClass(
document.querySelector('.o_ThreadIcon_online'),
'fa-plane',
"thread icon should have leave status rendering"
);
});
QUnit.test('thread icon of a chat when correspondent is on leave & away', async function (assert) {
assert.expect(2);
const pyEnv = await startServer();
const resPartnerId1 = pyEnv['res.partner'].create({
im_status: 'leave_away',
name: 'Demo',
});
pyEnv['mail.channel'].create({
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: resPartnerId1 }],
],
channel_type: 'chat',
});
const { openDiscuss } = await start();
await openDiscuss();
assert.containsOnce(
document.body,
'.o_ThreadIcon_away',
"thread icon should have away status rendering"
);
assert.hasClass(
document.querySelector('.o_ThreadIcon_away'),
'fa-plane',
"thread icon should have leave status rendering"
);
});
QUnit.test('thread icon of a chat when correspondent is on leave & offline', async function (assert) {
assert.expect(2);
const pyEnv = await startServer();
const resPartnerId1 = pyEnv['res.partner'].create({
im_status: 'leave_offline',
name: 'Demo',
});
pyEnv['mail.channel'].create({
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: resPartnerId1 }],
],
channel_type: 'chat',
});
const { openDiscuss } = await start();
await openDiscuss();
assert.containsOnce(
document.body,
'.o_ThreadIcon_offline',
"thread icon should have offline status rendering"
);
assert.hasClass(
document.querySelector('.o_ThreadIcon_offline'),
'fa-plane',
"thread icon should have leave status rendering"
);
});
});
});

View file

@ -0,0 +1,51 @@
/** @odoo-module **/
import { start, startServer } from '@mail/../tests/helpers/test_utils';
QUnit.module('hr_holidays', {}, function () {
QUnit.module('components', {}, function () {
QUnit.module('thread_view_tests.js');
QUnit.test('out of office message on direct chat with out of office partner', async function (assert) {
assert.expect(2);
// Returning date of the out of office partner, simulates he'll be back in a month
const returningDate = moment.utc().add(1, 'month');
const pyEnv = await startServer();
// Needed partner & user to allow simulation of message reception
const resPartnerId1 = pyEnv['res.partner'].create({
name: "Foreigner partner",
out_of_office_date_end: returningDate.format("YYYY-MM-DD"),
});
const mailChannelId1 = pyEnv['mail.channel'].create({
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: resPartnerId1 }],
],
channel_type: 'chat',
});
const { openDiscuss, messaging } = await start({
discuss: {
params: {
default_active_id: `mail.channel_${mailChannelId1}`,
},
},
});
await openDiscuss();
assert.containsOnce(
document.body,
'.o_ThreadView_outOfOffice',
"should have an out of office alert on thread view"
);
const formattedDate = returningDate.toDate().toLocaleDateString(
messaging.locale.language.replace(/_/g, '-'),
{ day: 'numeric', month: 'short' }
);
assert.ok(
document.querySelector('.o_ThreadView_outOfOffice').textContent.includes(formattedDate),
"out of office message should mention the returning date"
);
});
});
});

View file

@ -0,0 +1,59 @@
/** @odoo-module **/
import { click, clickSave, getFixture } from "@web/../tests/helpers/utils";
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
let serverData;
let target;
QUnit.module("Fields", (hooks) => {
hooks.beforeEach(() => {
serverData = {
models: {
partner: {
fields: {
product_id: { string: "Product", type: "many2one", relation: "product" },
},
records: [{ id: 1, product_id: false }],
},
product: {
fields: {
name: { string: "Product Name", type: "char" },
},
records: [
{ id: 1, display_name: "a" },
{ id: 2, display_name: "b" },
{ id: 3, display_name: "c" },
],
},
},
};
target = getFixture();
setupViewRegistries();
});
QUnit.module("RadioImageField");
QUnit.test("field is correctly renderered", async function (assert) {
await makeView({
type: "form",
resModel: "partner",
resId: 1,
serverData,
arch: '<form><field name="product_id" widget="hr_holidays_radio_image"/></form>',
});
assert.containsOnce(target, ".o_field_widget.o_field_hr_holidays_radio_image");
assert.containsN(target, ".o_radio_input", 3);
assert.containsNone(target, ".o_radio_input:checked");
assert.containsN(target, "img", 3);
await click(target.querySelector("img"));
assert.containsOnce(target, ".o_radio_input:checked");
await clickSave(target);
assert.containsOnce(target, ".o_field_widget.o_field_hr_holidays_radio_image");
assert.containsN(target, ".o_radio_input", 3);
assert.containsN(target, "img", 3);
});
});

View file

@ -0,0 +1,159 @@
/** @odoo-module */
import { selectDropdownItem, editInput, getFixture } from '@web/../tests/helpers/utils';
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
let serverData;
let target;
QUnit.module('leave_stats_widget', (hooks) => {
hooks.beforeEach(() => {
setupViewRegistries();
target = getFixture();
serverData = {
models: {
department: {
fields: {
name: { string: "Name", type: "char" },
},
records: [{id:11, name: "R&D"}],
},
employee: {
fields: {
name: { string: "Name", type: "char" },
department_id: { string: "Department", type: "many2one", relation: 'department' },
},
records: [{
id: 100,
name: "Richard",
department_id: 11,
},{
id: 200,
name: "Jesus",
department_id: 11,
}],
},
'hr.leave.type': {
fields: {
name: { string: "Name", type: "char" }
},
records: [{
id: 55,
name: "Legal Leave",
}]
},
'hr.leave': {
fields: {
employee_id: { string: "Employee", type: "many2one", relation: 'employee' },
department_id: { string: "Department", type: "many2one", relation: 'department' },
date_from: { string: "From", type: "datetime" },
date_to: { string: "To", type: "datetime" },
holiday_status_id: { string: "Leave type", type: "many2one", relation: 'hr.leave.type' },
state: { string: "State", type: "char" },
holiday_type: { string: "Holiday Type", type: "char" },
number_of_days: { string: "State", type: "integer" },
},
records: [{
id: 12,
employee_id: 100,
department_id: 11,
date_from: "2016-10-20 09:00:00",
date_to: "2016-10-25 18:00:00",
holiday_status_id: 55,
state: 'validate',
number_of_days: 5,
holiday_type: 'employee',
},{
id: 13,
employee_id: 100,
department_id: 11,
date_from: "2016-10-02 09:00:00",
date_to: "2016-10-02 18:00:00",
holiday_status_id: 55,
state: 'validate',
number_of_days: 1,
holiday_type: 'employee',
},{
id: 14,
employee_id: 200,
department_id: 11,
date_from: "2016-10-15 09:00:00",
date_to: "2016-10-20 18:00:00",
holiday_status_id: 55,
state: 'validate',
number_of_days: 8,
holiday_type: 'employee',
}]
}
}
};
});
QUnit.test('leave stats renders correctly', async (assert) => {
assert.expect(5);
await makeView({
serverData,
type: "form",
resModel: 'hr.leave',
arch: '<form string="Leave">' +
'<field name="employee_id"/>' +
'<field name="department_id"/>' +
'<field name="date_from"/>' +
'<widget name="hr_leave_stats"/>' +
'</form>',
resId: 12,
mockRPC(route, args) {
if (args.model === 'hr.leave' && args.method === 'search') {
return Promise.resolve(this.data['hr.leave'].records.map(function (record) { return record.id; }));
}
},
});
const $leaveTypeBody = target.querySelector('.o_leave_stats #o_leave_stats_employee');
const $leavesDepartmentBody = target.querySelector('.o_leave_stats #o_leave_stats_department');
assert.containsOnce($leaveTypeBody, 'span:contains(Legal Leave)', "it should have leave type");
assert.containsOnce($leaveTypeBody, 'span:contains(6)', "it should have 6 days");
assert.containsN($leavesDepartmentBody, 'span:contains(Richard)', 2, "it should have 2 leaves for Richard");
assert.containsOnce($leavesDepartmentBody, 'span:contains(Jesus)', "it should have 1 leaves for Jesus");
assert.containsOnce($leavesDepartmentBody, 'div.o_horizontal_separator:contains(R&D)', "it should have R&D title");
});
QUnit.test('leave stats reload when employee/department changes', async (assert) => {
assert.expect(3);
await makeView({
serverData,
type: "form",
resModel: 'hr.leave',
mode: 'edit',
arch: '<form string="Leave">' +
'<field name="employee_id"/>' +
'<field name="department_id"/>' +
'<field name="date_from"/>' +
'<widget name="hr_leave_stats"/>' +
'</form>',
mockRPC(route, args) {
if (args.model === 'hr.leave' && args.method === 'search_read') {
assert.ok(_.some(args.kwargs.domain, _.matcher(['department_id', '=', 11])), "It should load department's leaves data");
}
if (args.model === 'hr.leave' && args.method === 'read_group') {
assert.ok(_.some(args.kwargs.domain, _.matcher(['employee_id', '=', 200])), "It should load employee's leaves data");
}
},
});
// Set date => shouldn't load data yet (no employee nor department defined)
await editInput(
target,
"div[name='date_from'] .o_datepicker_input",
"2016-10-12 09:00:00"
);
// Set employee => should load employee's date
await selectDropdownItem(target, "employee_id", "Jesus");
// Set department => should load department's data
await selectDropdownItem(target, "department_id", "R&D");
});
});

View file

@ -0,0 +1,47 @@
odoo.define('hr_holidays.tour_time_off_request_calendar_view', function (require) {
'use strict';
var tour = require('web_tour.tour');
tour.register('time_off_request_calendar_view', {
test: true,
url: '/web',
},
[
tour.stepUtils.showAppsMenuItem(),
{
content: "Open Time Off app",
trigger: '.o_app[data-menu-xmlid="hr_holidays.menu_hr_holidays_root"]',
},
{
content: "Click on the first Thursday of the year",
trigger: '.fc-day-top.fc-thu',
run: () => {
const el = document.querySelector('.fc-day-top.fc-thu').firstChild;
el.scrollIntoView();
const fromPosition = el.getBoundingClientRect();
fromPosition.x += el.offsetWidth / 2;
fromPosition.y += el.offsetHeight / 2;
el.dispatchEvent(new MouseEvent("mousedown", {
bubbles: true,
which: 1,
button: 0,
clientX: fromPosition.x,
clientY: fromPosition.y}));
el.dispatchEvent(new MouseEvent("mouseup", {
bubbles: true,
which: 1,
button: 0,
clientX: fromPosition.x,
clientY: fromPosition.y }));
}
},
{
content: "Save the leave",
trigger: '.btn:contains("Save")',
run: 'click',
}
]);
});