Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Development 1.1 leave excuse #392

Merged
merged 7 commits into from
Apr 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
186 changes: 113 additions & 73 deletions client/src/components/requests/leaveRequest.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
<v-alert v-if="form?.isValid && isValid" density="compact" class="pa-5 my-5" type="warning">
{{
actualDays.state.value === 0
? 'Actual vacation days requested is zero, Selected days might include weekends or public holidays'
? 'Actual vacation days requested is zero, Selected days might include weekends or public holidays'
: actualDays.state.value === 1 && days < 1 && days > 0 ? 'Actual vacation days requested are ' + days + ' days'
: 'Actual vacation days requested are ' + actualDays.state.value + ' days'
}}
</v-alert>
Expand All @@ -14,32 +15,14 @@
</v-radio-group>
</div>
<div class="mt-3" v-if="selectedOption === 'anotheruser'">
<v-autocomplete
color="info"
item-color="info"
base-color="info"
variant="outlined"
v-model="selectedUser"
:items="officeUsers"
item-title="full_name"
item-value="id"
label="User"
return-object
:rules="requiredRules"
>
<v-autocomplete color="info" item-color="info" base-color="info" variant="outlined" v-model="selectedUser"
:items="officeUsers" item-title="full_name" item-value="id" label="User" return-object :rules="requiredRules">
<template #append-item v-if="reloadMore">
<VContainer>
<VBtn
@click="
() => {
return page++, count--, concatUsers()
}
"
block
color="secondary"
variant="tonal"
prepend-icon="mdi-reload"
>
<VBtn @click="() => {
return page++, count--, concatUsers()
}
" block color="secondary" variant="tonal" prepend-icon="mdi-reload">
Load More Users
</VBtn>
</VContainer>
Expand All @@ -48,55 +31,35 @@
</div>

<div class="mt-3">
<v-text-field
ref="startDateField"
color="info"
item-color="info"
base-color="info"
variant="outlined"
hide-details="auto"
label="From"
v-model="startDate"
type="date"
:rules="[validateDates]"
></v-text-field>
<v-autocomplete color="info" item-color="info" base-color="info" variant="outlined" v-model="leaveReason"
:items="leaveReasons" label="Reason" return-object item-title="name">
</v-autocomplete>
</div>

<div class="mt-3">
<v-text-field
ref="endDateField"
color="info"
item-color="info"
base-color="info"
variant="outlined"
hide-details="auto"
label="To"
v-model="endDate"
type="date"
:rules="[validateDates]"
></v-text-field>
<v-text-field ref="startDateField" color="info" item-color="info" base-color="info" variant="outlined"
hide-details="auto" label="From" v-model="startDate" type="date" :rules="[validateDates]"></v-text-field>
</div>

<div class="mt-3">
<v-autocomplete
color="info"
item-color="info"
base-color="info"
variant="outlined"
v-model="leaveReason"
:items="leaveReasons"
label="Reason"
return-object
item-title="name"
>
</v-autocomplete>
<v-text-field ref="endDateField" color="info" item-color="info" base-color="info" variant="outlined"
hide-details="auto" label="To" v-model="endDate" type="date" :rules="[validateDates]"></v-text-field>
</div>

<div class="mt-3">
<v-text-field ref="excuseStartField" item-color="info" base-color="info" color="info" variant="outlined"
label="Vacation Start Time" v-model="excuseStart" hide-details="auto" type="time" :rules="[validateTimes]" :readonly="startDate !== endDate">
</v-text-field>
</div>

<div class="mt-3">
<v-text-field ref="excuseEndField" item-color="info" base-color="info" color="info" variant="outlined"
label="Vacation End Time" v-model="excuseEnd" hide-details="auto" type="time" :rules="[validateTimes]" :readonly="startDate !== endDate">
</v-text-field>
</div>
<v-row class="pa-4 d-flex justify-end">
<v-btn
color="primary"
type="Submit"
:disabled="!form?.isValid || !isValid || actualDays.state.value === 0 || requesting"
:loading="requesting"
>
<v-row class="mt-3 pa-4 d-flex justify-end">
<v-btn color="primary" type="Submit"
:disabled="!form?.isValid || !isValid || actualDays.state.value === 0 || requesting" :loading="requesting">
Submit
</v-btn>
</v-row>
Expand All @@ -109,6 +72,7 @@ import { requiredRules, listUsers } from '@/utils'
import { useAsyncState } from '@vueuse/core'
import { computed, onMounted, ref, watch } from 'vue'
import { ApiClientBase } from '@/clients/api/base'
import { fieldRequired } from '@/utils'

export default {
name: 'leaveRequest',
Expand All @@ -126,6 +90,8 @@ export default {
const count = ref(0)
const startDateField = ref()
const endDateField = ref()
const excuseStartField = ref()
const excuseEndField = ref()
const startDate = ref<Date>(props.dates.startStr)
const endDate = ref<any>(new Date(props.dates.endStr))
endDate.value.setDate(endDate.value.getDate() - 1)
Expand All @@ -135,6 +101,30 @@ export default {
const leaveReason = ref<Api.LeaveReason>()
const leaveReasons = ref<Api.LeaveReason[]>([])
const requesting = ref<boolean>(false)
const excuseStart = ref('08:00')
const excuseEnd = ref('16:00')
const CORE_HOURS = 8;
const days = ref()

const from_date = computed(() => {
let val = new Date(startDate.value)
if (excuseStart.value) {
const [hours, minutes] = excuseStart.value.split(':').map(Number)
val.setHours(hours, minutes, 0, 0)
}

return val.toISOString()
})
const end_date = computed(() => {
let val = new Date(endDate.value)
if (excuseEnd.value) {
const [hours, minutes] = excuseEnd.value.split(':').map(Number)
val.setHours(hours, minutes, 0, 0)
}

return val.toISOString()
})

const actualDays = useAsyncState(async () => {
return $api.vacations.calculate.list({
start_date: startDate.value,
Expand Down Expand Up @@ -173,6 +163,10 @@ export default {
{
name: `Compensation ${data[0].compensation.reserved} / ∞`,
reason: 'compensation'
},
{
name: `Leave Excuse ${data[0].leave_excuses.reserved} / ${data[0].leave_excuses.all}`,
reason: 'leave_excuses'
}
]
}
Expand Down Expand Up @@ -208,18 +202,40 @@ export default {
setTimeout(async () => {
startDateField.value.validate()
endDateField.value.validate()
if (startDate.value !== endDate.value) {
days.value = 0;
excuseStart.value = '08:00'
excuseEnd.value = '16:00'
}
actualDays.execute()
}, 200)
}
)

watch(
() => [excuseStart.value, excuseEnd.value],
async () => {
setTimeout(async () => {
excuseStartField.value.validate()
excuseEndField.value.validate()
if (startDate.value === endDate.value) {
days.value = calculateTimes()
}
}, 200)
}
)
const validateDates = (value: string | null): string | boolean => {
if (!startDate.value) return 'Please select start date.'
if (!endDate.value) return 'Please select end date.'
if (endDate.value < startDate.value) return 'End date must be after start date.'
return true
}

const validateTimes = (value: string | null): string | boolean => {
if (!excuseEnd.value) return 'Please select end time.'
if (excuseEnd.value < excuseStart.value) return 'End time must be after start time.'
return true
}
async function concatUsers() {
const {
page: currentPage,
Expand Down Expand Up @@ -248,15 +264,32 @@ export default {
return false
})

function timeStringToHours(time: string): number {
const [hours, minutes] = time.split(':').map(Number);
return hours + minutes / 60;
}


function calculateTimes() {
const startTimeInHours = timeStringToHours(excuseStart.value);
const endTimeInHours = timeStringToHours(excuseEnd.value);
const days = (endTimeInHours - startTimeInHours) / CORE_HOURS
if( days === 0.25 || days === 0.50 || days === 0.75){
return days
}
return 1

}

async function createLeave() {
requesting.value = true
if (leaveReason.value) {
if (selectedOption.value === Selection.ANOTHERUSER) {
await useAsyncState(
$api.vacations.admin.create(selectedUser.value.id, {
reason: leaveReason.value.reason,
from_date: startDate.value,
end_date: endDate.value
from_date: from_date.value,
end_date: end_date.value
}),
null,
{
Expand All @@ -269,8 +302,8 @@ export default {
await useAsyncState(
$api.vacations.create({
reason: leaveReason.value.reason,
from_date: startDate.value,
end_date: endDate.value
from_date: from_date.value,
end_date: end_date.value
}),
undefined,
{
Expand Down Expand Up @@ -303,9 +336,16 @@ export default {
requesting,
count,
selectedOption,
fieldRequired,
excuseStart,
excuseEnd,
days,
excuseStartField,
excuseEndField,
concatUsers,
createLeave,
validateDates
validateDates,
validateTimes
}
}
}
Expand Down
5 changes: 3 additions & 2 deletions server/cshr/serializers/vacations.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""This file will containes all vacation serializers."""
from rest_framework.serializers import (
ModelSerializer,
DateTimeField,
SerializerMethodField,
)
from cshr.models.users import User
Expand Down Expand Up @@ -254,5 +255,5 @@ class VacationBalanceAdjustmentSerializer(serializers.Serializer):

class AdminApplyVacationForUserSerializer(serializers.Serializer):
reason = serializers.CharField()
from_date = serializers.DateField()
end_date = serializers.DateField()
from_date = serializers.DateTimeField()
end_date = serializers.DateTimeField()
Loading