Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c317a2a026 | ||
| 8846e83df1 | |||
|
|
ff824f233a | ||
| c4c9dfceab |
@@ -1,2 +1,2 @@
|
||||
parameters:
|
||||
app.version: '0.1.32'
|
||||
app.version: '0.1.34'
|
||||
|
||||
@@ -1,35 +1,54 @@
|
||||
<template>
|
||||
<section class="flex h-full min-h-0 flex-col overflow-hidden pt-8">
|
||||
<div class="grid grid-cols-4 rounded-md bg-primary-500 text-white text-[20]">
|
||||
<div class="flex flex-col gap-2 jutify-center items-center border-r-4 border-white py-3">
|
||||
<p><strong class="uppercase font-semibold">Année acquis :</strong> {{
|
||||
formatCount(summary?.acquiredDays)
|
||||
}} Jours</p>
|
||||
<p><strong class="uppercase font-semibold">Reste à prendre :</strong>
|
||||
{{ formatCount(summary?.remainingDays) }} Jours</p>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2 jutify-center items-center border-r-4 border-white py-3">
|
||||
<p><span class="uppercase font-semibold">Samedi acquis :</span>
|
||||
{{ formatCount(summary?.acquiredSaturdays) }} Jours</p>
|
||||
<p><span class="uppercase font-semibold">Reste à prendre :</span>
|
||||
{{ formatCount(summary?.remainingSaturdays) }} Jours</p>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2 jutify-center items-center border-r-4 border-white py-3">
|
||||
<p><span class="uppercase font-semibold">Fractionné acquis : </span>{{ formatCount(summary?.fractionedDays) }} Jours</p>
|
||||
<div class="grid grid-cols-4 rounded-md bg-tertiary-500 text-primary-500 text-[18px] border border-primary-500">
|
||||
<p class="col-start-1 p-[10px] border-b border-b-black"><strong class="uppercase font-semibold">Année acquis :</strong> {{
|
||||
formatCount(summary?.acquiredDays)
|
||||
}} Jours
|
||||
</p>
|
||||
<p class="col-start-2 p-[10px] border-b border-b-black"><strong class="uppercase font-semibold">Pris :</strong>
|
||||
{{ formatCount(isForfaitRule ? currentYearTakenDays : summary?.takenDays) }} Jours
|
||||
</p>
|
||||
<p class="col-start-3 p-[10px] border-b border-b-black"><strong class="uppercase font-semibold">Reste à prendre :</strong>
|
||||
{{ formatCount(summary?.remainingDays) }} Jours
|
||||
</p>
|
||||
<p class="col-start-4 p-[10px] border-b border-b-black"><strong class="uppercase font-semibold">En cours d'acquisition :</strong>
|
||||
{{ formatCount(summary?.accruingDays) }} Jours
|
||||
</p>
|
||||
<p v-if="!isForfaitRule" class="col-start-1 p-[10px]"><span class="uppercase font-semibold">Samedi acquis :</span>
|
||||
{{ formatCount(summary?.acquiredSaturdays) }} Jours
|
||||
</p>
|
||||
<p v-else class="col-start-1 p-[10px]"><span class="uppercase font-semibold">Année N-1 acquis :</span>
|
||||
{{ formatCount(summary?.previousYearAcquiredDays) }} Jours
|
||||
</p>
|
||||
<p v-if="!isForfaitRule" class="col-start-2 p-[10px]"><span class="uppercase font-semibold">Pris :</span>
|
||||
{{ formatCount(summary?.takenSaturdays) }} Jours
|
||||
</p>
|
||||
<p v-if="!isForfaitRule" class="col-start-3 p-[10px]"><span class="uppercase font-semibold">Reste à prendre :</span>
|
||||
{{ formatCount(summary?.remainingSaturdays) }} Jours
|
||||
</p>
|
||||
<p v-else class="col-start-2 p-[10px]"><span class="uppercase font-semibold">Pris :</span>
|
||||
{{ formatCount(summary?.previousYearTakenDays) }} Jours
|
||||
</p>
|
||||
<p v-if="isForfaitRule" class="col-start-3 p-[10px]"><span class="uppercase font-semibold">Reste à prendre :</span>
|
||||
{{ formatCount(summary?.previousYearRemainingDays) }} Jours
|
||||
</p>
|
||||
<div v-if="!isForfaitRule" class="col-start-4 p-[10px] flex gap-7 items-center">
|
||||
<div>
|
||||
<span class="uppercase font-semibold">Fractionné acquis : </span>
|
||||
<span>{{ formatCount(summary?.fractionedDays) }} Jours</span>
|
||||
</div>
|
||||
<button
|
||||
class="flex justify-center items-center gap-2 bg-white text-primary-500 font-bold w-[150px] rounded-md py-[1px]"
|
||||
class="flex items-center"
|
||||
@click="openFractionedDrawer"
|
||||
>
|
||||
{{ summary?.fractionedDays === 0 ? '+ Ajouter' : 'Modifier' }}</button>
|
||||
</div>
|
||||
<div class="flex flex-col jutify-center gap-2 items-center py-3">
|
||||
<p><span class="uppercase font-semibold">En cours d'acquisition :</span></p>
|
||||
<p>{{ formatCount(summary?.accruingDays) }} Jours</p>
|
||||
<Icon name="mdi:edit-box" size="24"/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-8 min-h-0 flex-1 overflow-y-auto pr-2">
|
||||
<div class="grid grid-cols-4 gap-10">
|
||||
<div v-for="month in months" :key="month.label" class="rounded-md bg-tertiary-500 text-primary-500 flex flex-col justify-between">
|
||||
<div v-for="month in months" :key="month.label"
|
||||
class="rounded-md bg-tertiary-500 text-primary-500 flex flex-col justify-between">
|
||||
<div class="flex justify-center rounded-t-md bg-primary-500 py-1 font-bold uppercase text-white">
|
||||
{{ month.label }}
|
||||
</div>
|
||||
@@ -54,7 +73,9 @@
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="px-2 py-2 text-center border-t border-primary-500">Jours de présence : {{ summary?.presenceDaysByMonth?.[month.monthKey] ?? 0 }}</div>
|
||||
<div class="px-2 py-2 text-center border-t border-primary-500">Jours de présence :
|
||||
{{ summary?.presenceDaysByMonth?.[month.monthKey] ?? 0 }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -118,7 +139,7 @@ const emit = defineEmits<{
|
||||
}>()
|
||||
|
||||
const isFractionedDrawerOpen = ref(false)
|
||||
const fractionedForm = reactive({ days: 0 })
|
||||
const fractionedForm = reactive({days: 0})
|
||||
|
||||
const openFractionedDrawer = () => {
|
||||
fractionedForm.days = props.summary?.fractionedDays ?? 0
|
||||
@@ -151,6 +172,11 @@ const weekDayLabels = ['L', 'M', 'M', 'J', 'V', 'S', 'D'] as const
|
||||
|
||||
const isForfaitRule = computed(() => props.summary?.ruleCode === 'FORFAIT_218')
|
||||
|
||||
const currentYearTakenDays = computed(() => {
|
||||
if (!props.summary) return null
|
||||
return props.summary.takenDays - (props.summary.previousYearTakenDays ?? 0)
|
||||
})
|
||||
|
||||
const displayedYear = computed(() => {
|
||||
if (props.summary?.year) return props.summary.year
|
||||
const today = new Date()
|
||||
@@ -282,15 +308,15 @@ const getDayStyle = (day: { leave: DayLeaveState | null; isHoliday: boolean }) =
|
||||
if (day.leave) {
|
||||
const color = day.leave.colors[0] ?? '#222783'
|
||||
if (day.leave.am && day.leave.pm) {
|
||||
return { backgroundColor: color }
|
||||
return {backgroundColor: color}
|
||||
}
|
||||
const colorFaded = `${color}60`
|
||||
const backgroundImage = day.leave.am
|
||||
? `linear-gradient(180deg, ${color} 0 50%, ${colorFaded} 50% 100%)`
|
||||
: `linear-gradient(180deg, ${colorFaded} 0 50%, ${color} 50% 100%)`
|
||||
return { backgroundImage, backgroundColor: 'transparent' }
|
||||
return {backgroundImage, backgroundColor: 'transparent'}
|
||||
}
|
||||
if (day.isHoliday) return { backgroundColor: 'rgb(179, 229, 252)' }
|
||||
if (day.isHoliday) return {backgroundColor: 'rgb(179, 229, 252)'}
|
||||
return undefined
|
||||
}
|
||||
|
||||
|
||||
344
frontend/composables/useEmployeeContract.ts
Normal file
344
frontend/composables/useEmployeeContract.ts
Normal file
@@ -0,0 +1,344 @@
|
||||
import type { Ref } from 'vue'
|
||||
import type { Contract } from '~/services/dto/contract'
|
||||
import type { ContractHistoryItem, Employee } from '~/services/dto/employee'
|
||||
import { listContracts } from '~/services/contracts'
|
||||
import { updateEmployee } from '~/services/employees'
|
||||
import { createSuspension, updateSuspension } from '~/services/contractSuspensions'
|
||||
import { formatNullableYmdToFr, getTodayYmd, shiftYmd } from '~/utils/date'
|
||||
import { contractNatureLabel, isContractNature, requiresContractEndDate, showsContractEndDate } from '~/utils/contract'
|
||||
|
||||
type SuspensionForm = {
|
||||
id: number | null
|
||||
startDate: string
|
||||
endDate: string
|
||||
comment: string
|
||||
}
|
||||
|
||||
export const useEmployeeContract = (employee: Ref<Employee | null>, reloadEmployee: () => Promise<void>) => {
|
||||
const toast = useToast()
|
||||
const contracts = ref<Contract[]>([])
|
||||
const isContractDrawerOpen = ref(false)
|
||||
const isContractSubmitting = ref(false)
|
||||
const isCreateContractDrawerOpen = ref(false)
|
||||
const isCreateContractSubmitting = ref(false)
|
||||
const suspensionForms = ref<SuspensionForm[]>([])
|
||||
const isSuspensionSubmitting = ref(false)
|
||||
|
||||
const contractForm = reactive({
|
||||
contractId: '' as number | '',
|
||||
contractName: '',
|
||||
weeklyHours: null as number | null,
|
||||
contractNature: 'CDI' as 'CDI' | 'CDD' | 'INTERIM',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
paidLeaveSettled: false,
|
||||
comment: ''
|
||||
})
|
||||
|
||||
const validationTouched = reactive({
|
||||
endDate: false
|
||||
})
|
||||
|
||||
const createContractForm = reactive({
|
||||
contractId: '' as number | '',
|
||||
contractNature: 'CDI' as 'CDI' | 'CDD' | 'INTERIM',
|
||||
startDate: '',
|
||||
endDate: ''
|
||||
})
|
||||
|
||||
const createValidationTouched = reactive({
|
||||
contractId: false,
|
||||
contractNature: false,
|
||||
startDate: false,
|
||||
endDate: false
|
||||
})
|
||||
|
||||
const contractHistory = computed(() => employee.value?.contractHistory ?? [])
|
||||
|
||||
const formatDate = (value?: string | null) => formatNullableYmdToFr(value)
|
||||
|
||||
const contractHistoryLabel = (item: ContractHistoryItem) => {
|
||||
if (item.weeklyHours !== null && item.weeklyHours !== undefined) {
|
||||
return `${item.weeklyHours} heures`
|
||||
}
|
||||
return item.contractName ?? '-'
|
||||
}
|
||||
|
||||
const currentActiveContractPeriod = computed(() => {
|
||||
const today = getTodayYmd()
|
||||
const history = employee.value?.contractHistory ?? []
|
||||
return history.find((item) => item.startDate <= today && (!item.endDate || item.endDate >= today)) ?? null
|
||||
})
|
||||
|
||||
const currentActiveContractPeriodId = computed<number | null>(() => {
|
||||
const period = currentActiveContractPeriod.value
|
||||
return period?.periodId ?? null
|
||||
})
|
||||
|
||||
const canCloseCurrentContract = computed(() => {
|
||||
const active = currentActiveContractPeriod.value
|
||||
if (!active) return false
|
||||
if (!active.endDate) return true
|
||||
return active.endDate > getTodayYmd()
|
||||
})
|
||||
|
||||
const canCreateContract = computed(() => {
|
||||
const active = currentActiveContractPeriod.value
|
||||
if (!active) return true
|
||||
return !!active.endDate
|
||||
})
|
||||
|
||||
const isContractEndDateValid = computed(() => contractForm.endDate !== '')
|
||||
const showContractEndDateError = computed(() => validationTouched.endDate && !isContractEndDateValid.value)
|
||||
|
||||
const showsCreateContractEndDate = computed(() => showsContractEndDate(createContractForm.contractNature))
|
||||
const requiresCreateContractEndDate = computed(() => requiresContractEndDate(createContractForm.contractNature))
|
||||
const isCreateContractValid = computed(() => createContractForm.contractId !== '')
|
||||
const isCreateContractNatureValid = computed(() => isContractNature(createContractForm.contractNature))
|
||||
const isCreateContractStartDateValid = computed(() => createContractForm.startDate !== '')
|
||||
const isCreateContractEndDateValid = computed(() => !requiresCreateContractEndDate.value || createContractForm.endDate !== '')
|
||||
const isCreateContractFormValid = computed(() =>
|
||||
isCreateContractValid.value &&
|
||||
isCreateContractNatureValid.value &&
|
||||
isCreateContractStartDateValid.value &&
|
||||
isCreateContractEndDateValid.value
|
||||
)
|
||||
|
||||
const baseInputClass =
|
||||
'mt-2 w-full rounded-md border px-3 py-2 text-base text-neutral-900 focus:border-primary-500 focus:outline-none focus:ring-2 focus:ring-secondary-500/20'
|
||||
const readonlyFieldClass = `${baseInputClass} border-neutral-300 bg-neutral-100 text-neutral-700`
|
||||
const contractEndDateFieldClass = computed(() => showContractEndDateError.value ? `${baseInputClass} border-red-500` : `${baseInputClass} border-neutral-300`)
|
||||
const baseSelectClass = 'mt-2 w-full rounded-md border bg-white px-3 py-2 text-md text-neutral-900'
|
||||
const createContractFieldClass = computed(() => createValidationTouched.contractId && !isCreateContractValid.value ? `${baseSelectClass} border-red-500` : `${baseSelectClass} border-neutral-300`)
|
||||
const createContractNatureFieldClass = computed(() => createValidationTouched.contractNature && !isCreateContractNatureValid.value ? `${baseSelectClass} border-red-500` : `${baseSelectClass} border-neutral-300`)
|
||||
const createContractStartDateFieldClass = computed(() => createValidationTouched.startDate && !isCreateContractStartDateValid.value ? `${baseInputClass} border-red-500` : `${baseInputClass} border-neutral-300`)
|
||||
const createContractEndDateFieldClass = computed(() => createValidationTouched.endDate && !isCreateContractEndDateValid.value ? `${baseInputClass} border-red-500` : `${baseInputClass} border-neutral-300`)
|
||||
const closeContractWorkedHoursLabel = computed(() => {
|
||||
if (contractForm.weeklyHours !== null && contractForm.weeklyHours !== undefined) return `${contractForm.weeklyHours} heures`
|
||||
return contractForm.contractName || '-'
|
||||
})
|
||||
|
||||
const resetContractValidation = () => {
|
||||
validationTouched.endDate = false
|
||||
}
|
||||
|
||||
const hydrateSuspensionForms = () => {
|
||||
const current = employee.value?.currentSuspensions ?? []
|
||||
suspensionForms.value = current.map(s => ({
|
||||
id: s.id,
|
||||
startDate: s.startDate,
|
||||
endDate: s.endDate ?? '',
|
||||
comment: s.comment ?? ''
|
||||
}))
|
||||
}
|
||||
|
||||
const hydrateContractFormFromCurrent = () => {
|
||||
const current = employee.value
|
||||
const active = currentActiveContractPeriod.value
|
||||
if (!current || !active) return
|
||||
|
||||
contractForm.contractId = active.contractId ?? current.contract?.id ?? ''
|
||||
contractForm.contractName = active.contractName ?? current.contract?.name ?? ''
|
||||
contractForm.weeklyHours = active.weeklyHours ?? current.contract?.weeklyHours ?? null
|
||||
contractForm.contractNature = active.contractNature
|
||||
contractForm.startDate = active.startDate
|
||||
contractForm.endDate = getTodayYmd()
|
||||
contractForm.paidLeaveSettled = false
|
||||
contractForm.comment = ''
|
||||
}
|
||||
|
||||
const openCloseContractDrawer = () => {
|
||||
if (!employee.value || !canCloseCurrentContract.value) return
|
||||
hydrateContractFormFromCurrent()
|
||||
resetContractValidation()
|
||||
hydrateSuspensionForms()
|
||||
isContractDrawerOpen.value = true
|
||||
}
|
||||
|
||||
const setContractDrawerOpen = (open: boolean) => {
|
||||
isContractDrawerOpen.value = open
|
||||
}
|
||||
|
||||
const resetCreateValidation = () => {
|
||||
createValidationTouched.contractId = false
|
||||
createValidationTouched.contractNature = false
|
||||
createValidationTouched.startDate = false
|
||||
createValidationTouched.endDate = false
|
||||
}
|
||||
|
||||
const openCreateContractDrawer = () => {
|
||||
if (!employee.value || !canCreateContract.value) return
|
||||
createContractForm.contractId = ''
|
||||
createContractForm.contractNature = 'CDI'
|
||||
createContractForm.endDate = ''
|
||||
createContractForm.startDate = currentActiveContractPeriod.value?.endDate
|
||||
? (shiftYmd(currentActiveContractPeriod.value.endDate, 1) ?? currentActiveContractPeriod.value.endDate)
|
||||
: getTodayYmd()
|
||||
resetCreateValidation()
|
||||
isCreateContractDrawerOpen.value = true
|
||||
}
|
||||
|
||||
const setCreateContractDrawerOpen = (open: boolean) => {
|
||||
isCreateContractDrawerOpen.value = open
|
||||
}
|
||||
|
||||
const submitContractUpdate = async () => {
|
||||
if (!employee.value || isContractSubmitting.value || !currentActiveContractPeriod.value) return
|
||||
|
||||
validationTouched.endDate = true
|
||||
if (!isContractEndDateValid.value) return
|
||||
|
||||
if (contractForm.endDate < currentActiveContractPeriod.value.startDate) {
|
||||
toast.error({
|
||||
title: 'Erreur',
|
||||
message: `La date de fin doit être postérieure au ${formatDate(currentActiveContractPeriod.value.startDate)}.`
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
isContractSubmitting.value = true
|
||||
try {
|
||||
await updateEmployee(employee.value.id, {
|
||||
firstName: employee.value.firstName,
|
||||
lastName: employee.value.lastName,
|
||||
siteId: employee.value.site?.id ?? null,
|
||||
contractId: Number(contractForm.contractId),
|
||||
contractEndDate: contractForm.endDate || null,
|
||||
contractPaidLeaveSettled: contractForm.paidLeaveSettled,
|
||||
contractComment: contractForm.comment || null
|
||||
})
|
||||
|
||||
isContractDrawerOpen.value = false
|
||||
await reloadEmployee()
|
||||
} finally {
|
||||
isContractSubmitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const submitCreateContract = async () => {
|
||||
if (!employee.value || isCreateContractSubmitting.value) return
|
||||
|
||||
createValidationTouched.contractId = true
|
||||
createValidationTouched.contractNature = true
|
||||
createValidationTouched.startDate = true
|
||||
createValidationTouched.endDate = true
|
||||
if (!isCreateContractFormValid.value) return
|
||||
|
||||
if (currentActiveContractPeriod.value?.endDate) {
|
||||
const minStartDate = shiftYmd(currentActiveContractPeriod.value.endDate, 1) ?? currentActiveContractPeriod.value.endDate
|
||||
if (createContractForm.startDate < minStartDate) {
|
||||
toast.error({
|
||||
title: 'Erreur',
|
||||
message: `La date de début doit être au moins le ${formatDate(minStartDate)}.`
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
isCreateContractSubmitting.value = true
|
||||
try {
|
||||
await updateEmployee(employee.value.id, {
|
||||
firstName: employee.value.firstName,
|
||||
lastName: employee.value.lastName,
|
||||
siteId: employee.value.site?.id ?? null,
|
||||
contractId: Number(createContractForm.contractId),
|
||||
contractNature: createContractForm.contractNature,
|
||||
contractStartDate: createContractForm.startDate,
|
||||
contractEndDate: createContractForm.endDate || null
|
||||
})
|
||||
isCreateContractDrawerOpen.value = false
|
||||
await reloadEmployee()
|
||||
} finally {
|
||||
isCreateContractSubmitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const submitSuspension = async (index: number) => {
|
||||
const form = suspensionForms.value[index]
|
||||
if (!form || !form.startDate) return
|
||||
|
||||
const periodId = currentActiveContractPeriodId.value
|
||||
if (!periodId) return
|
||||
|
||||
isSuspensionSubmitting.value = true
|
||||
try {
|
||||
if (form.id) {
|
||||
await updateSuspension(form.id, {
|
||||
startDate: form.startDate,
|
||||
endDate: form.endDate || null,
|
||||
comment: form.comment || null
|
||||
})
|
||||
} else {
|
||||
await createSuspension({
|
||||
contractPeriodId: periodId,
|
||||
startDate: form.startDate,
|
||||
endDate: form.endDate || null,
|
||||
comment: form.comment || null
|
||||
})
|
||||
}
|
||||
await reloadEmployee()
|
||||
hydrateSuspensionForms()
|
||||
} finally {
|
||||
isSuspensionSubmitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const addSuspensionForm = () => {
|
||||
suspensionForms.value.push({
|
||||
id: null,
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
comment: ''
|
||||
})
|
||||
}
|
||||
|
||||
const loadContracts = async () => {
|
||||
contracts.value = await listContracts()
|
||||
}
|
||||
|
||||
watch(showsCreateContractEndDate, (shows) => {
|
||||
if (!shows) {
|
||||
createContractForm.endDate = ''
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
contracts,
|
||||
contractHistory,
|
||||
contractForm,
|
||||
createContractForm,
|
||||
isContractDrawerOpen,
|
||||
isContractSubmitting,
|
||||
isCreateContractDrawerOpen,
|
||||
isCreateContractSubmitting,
|
||||
canCloseCurrentContract,
|
||||
canCreateContract,
|
||||
readonlyFieldClass,
|
||||
closeContractWorkedHoursLabel,
|
||||
contractEndDateFieldClass,
|
||||
showContractEndDateError,
|
||||
isContractEndDateValid,
|
||||
createContractNatureFieldClass,
|
||||
createContractFieldClass,
|
||||
createContractStartDateFieldClass,
|
||||
showsCreateContractEndDate,
|
||||
requiresCreateContractEndDate,
|
||||
createContractEndDateFieldClass,
|
||||
isCreateContractFormValid,
|
||||
contractNatureLabel,
|
||||
contractHistoryLabel,
|
||||
formatDate,
|
||||
openCloseContractDrawer,
|
||||
openCreateContractDrawer,
|
||||
setContractDrawerOpen,
|
||||
setCreateContractDrawerOpen,
|
||||
submitContractUpdate,
|
||||
submitCreateContract,
|
||||
suspensionForms,
|
||||
isSuspensionSubmitting,
|
||||
submitSuspension,
|
||||
addSuspensionForm,
|
||||
currentActiveContractPeriodId,
|
||||
loadContracts
|
||||
}
|
||||
}
|
||||
@@ -1,75 +1,13 @@
|
||||
import type { Contract } from '~/services/dto/contract'
|
||||
import type { Absence } from '~/services/dto/absence'
|
||||
import type { EmployeeLeaveSummary } from '~/services/dto/employee-leave-summary'
|
||||
import type { EmployeeRttSummary } from '~/services/dto/employee-rtt-summary'
|
||||
import type { ContractHistoryItem, Employee } from '~/services/dto/employee'
|
||||
import type { Employee } from '~/services/dto/employee'
|
||||
import { CONTRACT_TYPES } from '~/services/dto/contract'
|
||||
import { listAbsences } from '~/services/absences'
|
||||
import { listContracts } from '~/services/contracts'
|
||||
import { getEmployeeLeaveSummary, updateFractionedDays } from '~/services/employee-leave-summary'
|
||||
import { getEmployeeRttSummary, createRttPayment } from '~/services/employee-rtt-summary'
|
||||
import { getEmployee, updateEmployee } from '~/services/employees'
|
||||
import { listPublicHolidays } from '~/services/public-holidays'
|
||||
import { formatNullableYmdToFr, getTodayYmd, shiftYmd } from '~/utils/date'
|
||||
import { contractNatureLabel, isContractNature, requiresContractEndDate, showsContractEndDate } from '~/utils/contract'
|
||||
import { createSuspension, updateSuspension } from '~/services/contractSuspensions'
|
||||
import { getEmployee } from '~/services/employees'
|
||||
|
||||
export const useEmployeeDetailPage = () => {
|
||||
const route = useRoute()
|
||||
const toast = useToast()
|
||||
const employee = ref<Employee | null>(null)
|
||||
const isLoading = ref(false)
|
||||
const activeTab = ref<'contract' | 'leave' | 'rtt'>('contract')
|
||||
const contracts = ref<Contract[]>([])
|
||||
const employeeAbsences = ref<Absence[]>([])
|
||||
const leaveSummary = ref<EmployeeLeaveSummary | null>(null)
|
||||
const rttSummary = ref<EmployeeRttSummary | null>(null)
|
||||
const publicHolidays = ref<Record<string, string>>({})
|
||||
const isContractDrawerOpen = ref(false)
|
||||
const isContractSubmitting = ref(false)
|
||||
const isCreateContractDrawerOpen = ref(false)
|
||||
const isCreateContractSubmitting = ref(false)
|
||||
|
||||
type SuspensionForm = {
|
||||
id: number | null
|
||||
startDate: string
|
||||
endDate: string
|
||||
comment: string
|
||||
}
|
||||
|
||||
const suspensionForms = ref<SuspensionForm[]>([])
|
||||
const isSuspensionSubmitting = ref(false)
|
||||
|
||||
const contractForm = reactive({
|
||||
contractId: '' as number | '',
|
||||
contractName: '',
|
||||
weeklyHours: null as number | null,
|
||||
contractNature: 'CDI' as 'CDI' | 'CDD' | 'INTERIM',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
paidLeaveSettled: false,
|
||||
comment: ''
|
||||
})
|
||||
|
||||
const validationTouched = reactive({
|
||||
endDate: false
|
||||
})
|
||||
|
||||
const createContractForm = reactive({
|
||||
contractId: '' as number | '',
|
||||
contractNature: 'CDI' as 'CDI' | 'CDD' | 'INTERIM',
|
||||
startDate: '',
|
||||
endDate: ''
|
||||
})
|
||||
|
||||
const createValidationTouched = reactive({
|
||||
contractId: false,
|
||||
contractNature: false,
|
||||
startDate: false,
|
||||
endDate: false
|
||||
})
|
||||
|
||||
const contractHistory = computed(() => employee.value?.contractHistory ?? [])
|
||||
const showLeaveTab = computed(() => employee.value?.currentContractNature !== 'INTERIM')
|
||||
const showRttTab = computed(() => employee.value?.contract?.type !== CONTRACT_TYPES.FORFAIT)
|
||||
const employeeContractWorkLabel = computed(() => {
|
||||
@@ -80,133 +18,6 @@ export const useEmployeeDetailPage = () => {
|
||||
return contract.name || '-'
|
||||
})
|
||||
|
||||
const formatDate = (value?: string | null) => formatNullableYmdToFr(value)
|
||||
|
||||
const contractHistoryLabel = (item: ContractHistoryItem) => {
|
||||
if (item.weeklyHours !== null && item.weeklyHours !== undefined) {
|
||||
return `${item.weeklyHours} heures`
|
||||
}
|
||||
return item.contractName ?? '-'
|
||||
}
|
||||
|
||||
const currentActiveContractPeriod = computed(() => {
|
||||
const today = getTodayYmd()
|
||||
const history = employee.value?.contractHistory ?? []
|
||||
return history.find((item) => item.startDate <= today && (!item.endDate || item.endDate >= today)) ?? null
|
||||
})
|
||||
|
||||
const currentActiveContractPeriodId = computed<number | null>(() => {
|
||||
const period = currentActiveContractPeriod.value
|
||||
return period?.periodId ?? null
|
||||
})
|
||||
|
||||
const canCloseCurrentContract = computed(() => {
|
||||
const active = currentActiveContractPeriod.value
|
||||
if (!active) return false
|
||||
if (!active.endDate) return true
|
||||
return active.endDate > getTodayYmd()
|
||||
})
|
||||
|
||||
const canCreateContract = computed(() => {
|
||||
const active = currentActiveContractPeriod.value
|
||||
if (!active) return true
|
||||
return !!active.endDate
|
||||
})
|
||||
|
||||
const isContractEndDateValid = computed(() => contractForm.endDate !== '')
|
||||
const showContractEndDateError = computed(() => validationTouched.endDate && !isContractEndDateValid.value)
|
||||
|
||||
const showsCreateContractEndDate = computed(() => showsContractEndDate(createContractForm.contractNature))
|
||||
const requiresCreateContractEndDate = computed(() => requiresContractEndDate(createContractForm.contractNature))
|
||||
const isCreateContractValid = computed(() => createContractForm.contractId !== '')
|
||||
const isCreateContractNatureValid = computed(() => isContractNature(createContractForm.contractNature))
|
||||
const isCreateContractStartDateValid = computed(() => createContractForm.startDate !== '')
|
||||
const isCreateContractEndDateValid = computed(() => !requiresCreateContractEndDate.value || createContractForm.endDate !== '')
|
||||
const isCreateContractFormValid = computed(() =>
|
||||
isCreateContractValid.value &&
|
||||
isCreateContractNatureValid.value &&
|
||||
isCreateContractStartDateValid.value &&
|
||||
isCreateContractEndDateValid.value
|
||||
)
|
||||
|
||||
const baseInputClass =
|
||||
'mt-2 w-full rounded-md border px-3 py-2 text-base text-neutral-900 focus:border-primary-500 focus:outline-none focus:ring-2 focus:ring-secondary-500/20'
|
||||
const readonlyFieldClass = `${baseInputClass} border-neutral-300 bg-neutral-100 text-neutral-700`
|
||||
const contractEndDateFieldClass = computed(() => showContractEndDateError.value ? `${baseInputClass} border-red-500` : `${baseInputClass} border-neutral-300`)
|
||||
const baseSelectClass = 'mt-2 w-full rounded-md border bg-white px-3 py-2 text-md text-neutral-900'
|
||||
const createContractFieldClass = computed(() => createValidationTouched.contractId && !isCreateContractValid.value ? `${baseSelectClass} border-red-500` : `${baseSelectClass} border-neutral-300`)
|
||||
const createContractNatureFieldClass = computed(() => createValidationTouched.contractNature && !isCreateContractNatureValid.value ? `${baseSelectClass} border-red-500` : `${baseSelectClass} border-neutral-300`)
|
||||
const createContractStartDateFieldClass = computed(() => createValidationTouched.startDate && !isCreateContractStartDateValid.value ? `${baseInputClass} border-red-500` : `${baseInputClass} border-neutral-300`)
|
||||
const createContractEndDateFieldClass = computed(() => createValidationTouched.endDate && !isCreateContractEndDateValid.value ? `${baseInputClass} border-red-500` : `${baseInputClass} border-neutral-300`)
|
||||
const closeContractWorkedHoursLabel = computed(() => {
|
||||
if (contractForm.weeklyHours !== null && contractForm.weeklyHours !== undefined) return `${contractForm.weeklyHours} heures`
|
||||
return contractForm.contractName || '-'
|
||||
})
|
||||
|
||||
const resetContractValidation = () => {
|
||||
validationTouched.endDate = false
|
||||
}
|
||||
|
||||
const hydrateSuspensionForms = () => {
|
||||
const current = employee.value?.currentSuspensions ?? []
|
||||
suspensionForms.value = current.map(s => ({
|
||||
id: s.id,
|
||||
startDate: s.startDate,
|
||||
endDate: s.endDate ?? '',
|
||||
comment: s.comment ?? ''
|
||||
}))
|
||||
}
|
||||
|
||||
const hydrateContractFormFromCurrent = () => {
|
||||
const current = employee.value
|
||||
const active = currentActiveContractPeriod.value
|
||||
if (!current || !active) return
|
||||
|
||||
contractForm.contractId = active.contractId ?? current.contract?.id ?? ''
|
||||
contractForm.contractName = active.contractName ?? current.contract?.name ?? ''
|
||||
contractForm.weeklyHours = active.weeklyHours ?? current.contract?.weeklyHours ?? null
|
||||
contractForm.contractNature = active.contractNature
|
||||
contractForm.startDate = active.startDate
|
||||
contractForm.endDate = getTodayYmd()
|
||||
contractForm.paidLeaveSettled = false
|
||||
contractForm.comment = ''
|
||||
}
|
||||
|
||||
const openCloseContractDrawer = () => {
|
||||
if (!employee.value || !canCloseCurrentContract.value) return
|
||||
hydrateContractFormFromCurrent()
|
||||
resetContractValidation()
|
||||
hydrateSuspensionForms()
|
||||
isContractDrawerOpen.value = true
|
||||
}
|
||||
|
||||
const setContractDrawerOpen = (open: boolean) => {
|
||||
isContractDrawerOpen.value = open
|
||||
}
|
||||
|
||||
const resetCreateValidation = () => {
|
||||
createValidationTouched.contractId = false
|
||||
createValidationTouched.contractNature = false
|
||||
createValidationTouched.startDate = false
|
||||
createValidationTouched.endDate = false
|
||||
}
|
||||
|
||||
const openCreateContractDrawer = () => {
|
||||
if (!employee.value || !canCreateContract.value) return
|
||||
createContractForm.contractId = ''
|
||||
createContractForm.contractNature = 'CDI'
|
||||
createContractForm.endDate = ''
|
||||
createContractForm.startDate = currentActiveContractPeriod.value?.endDate
|
||||
? (shiftYmd(currentActiveContractPeriod.value.endDate, 1) ?? currentActiveContractPeriod.value.endDate)
|
||||
: getTodayYmd()
|
||||
resetCreateValidation()
|
||||
isCreateContractDrawerOpen.value = true
|
||||
}
|
||||
|
||||
const setCreateContractDrawerOpen = (open: boolean) => {
|
||||
isCreateContractDrawerOpen.value = open
|
||||
}
|
||||
|
||||
const loadEmployee = async () => {
|
||||
const idParam = Array.isArray(route.params.id) ? route.params.id[0] : route.params.id
|
||||
const employeeId = Number(idParam)
|
||||
@@ -216,185 +27,42 @@ export const useEmployeeDetailPage = () => {
|
||||
|
||||
isLoading.value = true
|
||||
try {
|
||||
const loadedEmployee = await getEmployee(employeeId)
|
||||
employee.value = loadedEmployee
|
||||
employee.value = await getEmployee(employeeId)
|
||||
|
||||
const now = new Date()
|
||||
const isForfait = loadedEmployee.contract?.type === CONTRACT_TYPES.FORFAIT
|
||||
const leaveYear = isForfait
|
||||
? now.getFullYear()
|
||||
: (now.getMonth() >= 5 ? now.getFullYear() + 1 : now.getFullYear())
|
||||
const rttYear = now.getMonth() >= 5 ? now.getFullYear() + 1 : now.getFullYear()
|
||||
const from = isForfait
|
||||
? `${leaveYear}-01-01`
|
||||
: `${leaveYear - 1}-06-01`
|
||||
const to = isForfait
|
||||
? `${leaveYear}-12-31`
|
||||
: `${leaveYear}-05-31`
|
||||
const holidayYears = isForfait
|
||||
? [leaveYear]
|
||||
: [leaveYear - 1, leaveYear]
|
||||
const [absences, summary, rtt, ...holidayResults] = await Promise.all([
|
||||
listAbsences({
|
||||
from,
|
||||
to,
|
||||
employeeId: loadedEmployee.id
|
||||
}),
|
||||
showLeaveTab.value
|
||||
? getEmployeeLeaveSummary(loadedEmployee.id, leaveYear)
|
||||
: Promise.resolve(null),
|
||||
showRttTab.value
|
||||
? getEmployeeRttSummary(loadedEmployee.id, rttYear)
|
||||
: Promise.resolve(null),
|
||||
...holidayYears.map((y) => listPublicHolidays('metropole', y))
|
||||
])
|
||||
employeeAbsences.value = absences
|
||||
leaveSummary.value = summary
|
||||
rttSummary.value = rtt
|
||||
publicHolidays.value = Object.assign({}, ...holidayResults)
|
||||
if (!showLeaveTab.value && activeTab.value === 'leave') {
|
||||
activeTab.value = 'contract'
|
||||
}
|
||||
if (!showRttTab.value && activeTab.value === 'rtt') {
|
||||
activeTab.value = 'contract'
|
||||
}
|
||||
|
||||
leave.resetLoaded()
|
||||
rtt.resetLoaded()
|
||||
|
||||
if (activeTab.value === 'leave' && showLeaveTab.value) {
|
||||
await leave.loadLeaveData()
|
||||
} else if (activeTab.value === 'rtt' && showRttTab.value) {
|
||||
await rtt.loadRttData()
|
||||
}
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const submitContractUpdate = async () => {
|
||||
if (!employee.value || isContractSubmitting.value || !currentActiveContractPeriod.value) return
|
||||
const contract = useEmployeeContract(employee, loadEmployee)
|
||||
const leave = useEmployeeLeave(employee, loadEmployee)
|
||||
const rtt = useEmployeeRtt(employee, loadEmployee)
|
||||
|
||||
validationTouched.endDate = true
|
||||
if (!isContractEndDateValid.value) return
|
||||
|
||||
if (contractForm.endDate < currentActiveContractPeriod.value.startDate) {
|
||||
toast.error({
|
||||
title: 'Erreur',
|
||||
message: `La date de fin doit être postérieure au ${formatDate(currentActiveContractPeriod.value.startDate)}.`
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
isContractSubmitting.value = true
|
||||
try {
|
||||
await updateEmployee(employee.value.id, {
|
||||
firstName: employee.value.firstName,
|
||||
lastName: employee.value.lastName,
|
||||
siteId: employee.value.site?.id ?? null,
|
||||
contractId: Number(contractForm.contractId),
|
||||
contractEndDate: contractForm.endDate || null,
|
||||
contractPaidLeaveSettled: contractForm.paidLeaveSettled,
|
||||
contractComment: contractForm.comment || null
|
||||
})
|
||||
|
||||
isContractDrawerOpen.value = false
|
||||
await loadEmployee()
|
||||
} finally {
|
||||
isContractSubmitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const submitCreateContract = async () => {
|
||||
if (!employee.value || isCreateContractSubmitting.value) return
|
||||
|
||||
createValidationTouched.contractId = true
|
||||
createValidationTouched.contractNature = true
|
||||
createValidationTouched.startDate = true
|
||||
createValidationTouched.endDate = true
|
||||
if (!isCreateContractFormValid.value) return
|
||||
|
||||
if (currentActiveContractPeriod.value?.endDate) {
|
||||
const minStartDate = shiftYmd(currentActiveContractPeriod.value.endDate, 1) ?? currentActiveContractPeriod.value.endDate
|
||||
if (createContractForm.startDate < minStartDate) {
|
||||
toast.error({
|
||||
title: 'Erreur',
|
||||
message: `La date de début doit être au moins le ${formatDate(minStartDate)}.`
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
isCreateContractSubmitting.value = true
|
||||
try {
|
||||
await updateEmployee(employee.value.id, {
|
||||
firstName: employee.value.firstName,
|
||||
lastName: employee.value.lastName,
|
||||
siteId: employee.value.site?.id ?? null,
|
||||
contractId: Number(createContractForm.contractId),
|
||||
contractNature: createContractForm.contractNature,
|
||||
contractStartDate: createContractForm.startDate,
|
||||
contractEndDate: createContractForm.endDate || null
|
||||
})
|
||||
isCreateContractDrawerOpen.value = false
|
||||
await loadEmployee()
|
||||
} finally {
|
||||
isCreateContractSubmitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const submitSuspension = async (index: number) => {
|
||||
const form = suspensionForms.value[index]
|
||||
if (!form || !form.startDate) return
|
||||
|
||||
const periodId = currentActiveContractPeriodId.value
|
||||
if (!periodId) return
|
||||
|
||||
isSuspensionSubmitting.value = true
|
||||
try {
|
||||
if (form.id) {
|
||||
await updateSuspension(form.id, {
|
||||
startDate: form.startDate,
|
||||
endDate: form.endDate || null,
|
||||
comment: form.comment || null
|
||||
})
|
||||
} else {
|
||||
await createSuspension({
|
||||
contractPeriodId: periodId,
|
||||
startDate: form.startDate,
|
||||
endDate: form.endDate || null,
|
||||
comment: form.comment || null
|
||||
})
|
||||
}
|
||||
await loadEmployee()
|
||||
hydrateSuspensionForms()
|
||||
} finally {
|
||||
isSuspensionSubmitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const addSuspensionForm = () => {
|
||||
suspensionForms.value.push({
|
||||
id: null,
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
comment: ''
|
||||
})
|
||||
}
|
||||
|
||||
const submitFractionedDays = async (days: number) => {
|
||||
if (!employee.value) return
|
||||
const year = leaveSummary.value?.year ?? undefined
|
||||
await updateFractionedDays(employee.value.id, days, year)
|
||||
await loadEmployee()
|
||||
}
|
||||
|
||||
const submitRttPayment = async (month: number, base25Minutes: number, bonus25Minutes: number, base50Minutes: number, bonus50Minutes: number) => {
|
||||
if (!employee.value) return
|
||||
const year = rttSummary.value?.year ?? undefined
|
||||
await createRttPayment(employee.value.id, month, base25Minutes, bonus25Minutes, base50Minutes, bonus50Minutes, year)
|
||||
await loadEmployee()
|
||||
}
|
||||
|
||||
watch(showsCreateContractEndDate, (shows) => {
|
||||
if (!shows) {
|
||||
createContractForm.endDate = ''
|
||||
watch(activeTab, (tab) => {
|
||||
if (tab === 'leave' && !leave.leaveDataLoaded.value && showLeaveTab.value) {
|
||||
leave.loadLeaveData()
|
||||
} else if (tab === 'rtt' && !rtt.rttDataLoaded.value && showRttTab.value) {
|
||||
rtt.loadRttData()
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
contracts.value = await listContracts()
|
||||
await contract.loadContracts()
|
||||
await loadEmployee()
|
||||
})
|
||||
|
||||
@@ -402,50 +70,11 @@ export const useEmployeeDetailPage = () => {
|
||||
employee,
|
||||
isLoading,
|
||||
activeTab,
|
||||
contracts,
|
||||
employeeAbsences,
|
||||
leaveSummary,
|
||||
rttSummary,
|
||||
publicHolidays,
|
||||
showLeaveTab,
|
||||
showRttTab,
|
||||
contractHistory,
|
||||
employeeContractWorkLabel,
|
||||
contractForm,
|
||||
createContractForm,
|
||||
isContractDrawerOpen,
|
||||
isContractSubmitting,
|
||||
isCreateContractDrawerOpen,
|
||||
isCreateContractSubmitting,
|
||||
canCloseCurrentContract,
|
||||
canCreateContract,
|
||||
readonlyFieldClass,
|
||||
closeContractWorkedHoursLabel,
|
||||
contractEndDateFieldClass,
|
||||
showContractEndDateError,
|
||||
isContractEndDateValid,
|
||||
createContractNatureFieldClass,
|
||||
createContractFieldClass,
|
||||
createContractStartDateFieldClass,
|
||||
showsCreateContractEndDate,
|
||||
requiresCreateContractEndDate,
|
||||
createContractEndDateFieldClass,
|
||||
isCreateContractFormValid,
|
||||
contractNatureLabel,
|
||||
contractHistoryLabel,
|
||||
formatDate,
|
||||
openCloseContractDrawer,
|
||||
openCreateContractDrawer,
|
||||
setContractDrawerOpen,
|
||||
setCreateContractDrawerOpen,
|
||||
submitContractUpdate,
|
||||
submitCreateContract,
|
||||
submitFractionedDays,
|
||||
submitRttPayment,
|
||||
suspensionForms,
|
||||
isSuspensionSubmitting,
|
||||
submitSuspension,
|
||||
addSuspensionForm,
|
||||
currentActiveContractPeriodId
|
||||
...contract,
|
||||
...leave,
|
||||
...rtt
|
||||
}
|
||||
}
|
||||
|
||||
70
frontend/composables/useEmployeeLeave.ts
Normal file
70
frontend/composables/useEmployeeLeave.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import type { Ref } from 'vue'
|
||||
import type { Absence } from '~/services/dto/absence'
|
||||
import type { EmployeeLeaveSummary } from '~/services/dto/employee-leave-summary'
|
||||
import type { Employee } from '~/services/dto/employee'
|
||||
import { CONTRACT_TYPES } from '~/services/dto/contract'
|
||||
import { listAbsences } from '~/services/absences'
|
||||
import { getEmployeeLeaveSummary, updateFractionedDays } from '~/services/employee-leave-summary'
|
||||
import { listPublicHolidays } from '~/services/public-holidays'
|
||||
|
||||
export const useEmployeeLeave = (employee: Ref<Employee | null>, reloadEmployee: () => Promise<void>) => {
|
||||
const employeeAbsences = ref<Absence[]>([])
|
||||
const leaveSummary = ref<EmployeeLeaveSummary | null>(null)
|
||||
const publicHolidays = ref<Record<string, string>>({})
|
||||
const isLeaveLoading = ref(false)
|
||||
const leaveDataLoaded = ref(false)
|
||||
|
||||
const getLeaveYear = () => {
|
||||
const now = new Date()
|
||||
const isForfait = employee.value?.contract?.type === CONTRACT_TYPES.FORFAIT
|
||||
return isForfait
|
||||
? now.getFullYear()
|
||||
: (now.getMonth() >= 5 ? now.getFullYear() + 1 : now.getFullYear())
|
||||
}
|
||||
|
||||
const loadLeaveData = async () => {
|
||||
if (!employee.value || isLeaveLoading.value) return
|
||||
isLeaveLoading.value = true
|
||||
try {
|
||||
const isForfait = employee.value.contract?.type === CONTRACT_TYPES.FORFAIT
|
||||
const leaveYear = getLeaveYear()
|
||||
const from = isForfait ? `${leaveYear}-01-01` : `${leaveYear - 1}-06-01`
|
||||
const to = isForfait ? `${leaveYear}-12-31` : `${leaveYear}-05-31`
|
||||
const holidayYears = isForfait ? [leaveYear] : [leaveYear - 1, leaveYear]
|
||||
|
||||
const [absences, summary, ...holidayResults] = await Promise.all([
|
||||
listAbsences({ from, to, employeeId: employee.value.id }),
|
||||
getEmployeeLeaveSummary(employee.value.id, leaveYear),
|
||||
...holidayYears.map((y) => listPublicHolidays('metropole', y))
|
||||
])
|
||||
employeeAbsences.value = absences
|
||||
leaveSummary.value = summary
|
||||
publicHolidays.value = Object.assign({}, ...holidayResults)
|
||||
leaveDataLoaded.value = true
|
||||
} finally {
|
||||
isLeaveLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const resetLoaded = () => {
|
||||
leaveDataLoaded.value = false
|
||||
}
|
||||
|
||||
const submitFractionedDays = async (days: number) => {
|
||||
if (!employee.value) return
|
||||
const year = leaveSummary.value?.year ?? undefined
|
||||
await updateFractionedDays(employee.value.id, days, year)
|
||||
await reloadEmployee()
|
||||
}
|
||||
|
||||
return {
|
||||
employeeAbsences,
|
||||
leaveSummary,
|
||||
publicHolidays,
|
||||
isLeaveLoading,
|
||||
leaveDataLoaded,
|
||||
loadLeaveData,
|
||||
resetLoaded,
|
||||
submitFractionedDays
|
||||
}
|
||||
}
|
||||
42
frontend/composables/useEmployeeRtt.ts
Normal file
42
frontend/composables/useEmployeeRtt.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import type { Ref } from 'vue'
|
||||
import type { EmployeeRttSummary } from '~/services/dto/employee-rtt-summary'
|
||||
import type { Employee } from '~/services/dto/employee'
|
||||
import { getEmployeeRttSummary, createRttPayment } from '~/services/employee-rtt-summary'
|
||||
|
||||
export const useEmployeeRtt = (employee: Ref<Employee | null>, reloadEmployee: () => Promise<void>) => {
|
||||
const rttSummary = ref<EmployeeRttSummary | null>(null)
|
||||
const isRttLoading = ref(false)
|
||||
const rttDataLoaded = ref(false)
|
||||
|
||||
const loadRttData = async () => {
|
||||
if (!employee.value || isRttLoading.value) return
|
||||
isRttLoading.value = true
|
||||
try {
|
||||
const rttYear = new Date().getMonth() >= 5 ? new Date().getFullYear() + 1 : new Date().getFullYear()
|
||||
rttSummary.value = await getEmployeeRttSummary(employee.value.id, rttYear)
|
||||
rttDataLoaded.value = true
|
||||
} finally {
|
||||
isRttLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const resetLoaded = () => {
|
||||
rttDataLoaded.value = false
|
||||
}
|
||||
|
||||
const submitRttPayment = async (month: number, base25Minutes: number, bonus25Minutes: number, base50Minutes: number, bonus50Minutes: number) => {
|
||||
if (!employee.value) return
|
||||
const year = rttSummary.value?.year ?? undefined
|
||||
await createRttPayment(employee.value.id, month, base25Minutes, bonus25Minutes, base50Minutes, bonus50Minutes, year)
|
||||
await reloadEmployee()
|
||||
}
|
||||
|
||||
return {
|
||||
rttSummary,
|
||||
isRttLoading,
|
||||
rttDataLoaded,
|
||||
loadRttData,
|
||||
resetLoaded,
|
||||
submitRttPayment
|
||||
}
|
||||
}
|
||||
@@ -98,15 +98,25 @@
|
||||
:on-add-suspension-form="addSuspensionForm"
|
||||
:current-contract-period-id="currentActiveContractPeriodId"
|
||||
/>
|
||||
<EmployeesLeaveTab
|
||||
v-else-if="showLeaveTab && activeTab === 'leave'"
|
||||
class="h-full"
|
||||
:absences="employeeAbsences"
|
||||
:summary="leaveSummary"
|
||||
:public-holidays="publicHolidays"
|
||||
@update-fractioned-days="submitFractionedDays"
|
||||
/>
|
||||
<EmployeesRttTab v-else-if="showRttTab && activeTab === 'rtt'" class="h-full" :summary="rttSummary" @submit-rtt-payment="submitRttPayment" />
|
||||
<div v-else-if="showLeaveTab && activeTab === 'leave'" class="h-full">
|
||||
<div v-if="isLeaveLoading" class="mt-6 rounded-lg border border-neutral-200 bg-white p-6 text-md text-neutral-600">
|
||||
Chargement...
|
||||
</div>
|
||||
<EmployeesLeaveTab
|
||||
v-else
|
||||
class="h-full"
|
||||
:absences="employeeAbsences"
|
||||
:summary="leaveSummary"
|
||||
:public-holidays="publicHolidays"
|
||||
@update-fractioned-days="submitFractionedDays"
|
||||
/>
|
||||
</div>
|
||||
<div v-else-if="showRttTab && activeTab === 'rtt'" class="h-full">
|
||||
<div v-if="isRttLoading" class="mt-6 rounded-lg border border-neutral-200 bg-white p-6 text-md text-neutral-600">
|
||||
Chargement...
|
||||
</div>
|
||||
<EmployeesRttTab v-else class="h-full" :summary="rttSummary" @submit-rtt-payment="submitRttPayment" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -161,7 +171,9 @@ const {
|
||||
isSuspensionSubmitting,
|
||||
submitSuspension,
|
||||
addSuspensionForm,
|
||||
currentActiveContractPeriodId
|
||||
currentActiveContractPeriodId,
|
||||
isLeaveLoading,
|
||||
isRttLoading
|
||||
} = useEmployeeDetailPage()
|
||||
|
||||
useHead(() => ({
|
||||
|
||||
@@ -10,6 +10,9 @@ export type EmployeeLeaveSummary = {
|
||||
takenSaturdays: number
|
||||
fractionedDays: number
|
||||
accruingDays: number
|
||||
previousYearAcquiredDays: number
|
||||
previousYearTakenDays: number
|
||||
previousYearRemainingDays: number
|
||||
presenceDaysByMonth: Record<string, number>
|
||||
}
|
||||
|
||||
|
||||
@@ -20,17 +20,20 @@ use App\State\EmployeeLeaveSummaryProvider;
|
||||
)]
|
||||
final class EmployeeLeaveSummary
|
||||
{
|
||||
public int $year = 0;
|
||||
public bool $isSupported = false;
|
||||
public string $ruleCode = '';
|
||||
public float $acquiredDays = 0.0;
|
||||
public float $remainingDays = 0.0;
|
||||
public float $takenDays = 0.0;
|
||||
public float $acquiredSaturdays = 0.0;
|
||||
public float $remainingSaturdays = 0.0;
|
||||
public float $takenSaturdays = 0.0;
|
||||
public float $fractionedDays = 0.0;
|
||||
public float $accruingDays = 0.0;
|
||||
public int $year = 0;
|
||||
public bool $isSupported = false;
|
||||
public string $ruleCode = '';
|
||||
public float $acquiredDays = 0.0;
|
||||
public float $remainingDays = 0.0;
|
||||
public float $takenDays = 0.0;
|
||||
public float $acquiredSaturdays = 0.0;
|
||||
public float $remainingSaturdays = 0.0;
|
||||
public float $takenSaturdays = 0.0;
|
||||
public float $fractionedDays = 0.0;
|
||||
public float $accruingDays = 0.0;
|
||||
public float $previousYearAcquiredDays = 0.0;
|
||||
public float $previousYearTakenDays = 0.0;
|
||||
public float $previousYearRemainingDays = 0.0;
|
||||
|
||||
/** @var array<string, float> YYYY-MM => count (0.5 for half-days) */
|
||||
public array $presenceDaysByMonth = [];
|
||||
|
||||
@@ -91,16 +91,19 @@ final readonly class EmployeeLeaveSummaryProvider implements ProviderInterface
|
||||
|
||||
$fractionedDays = $this->resolveFractionedDays($employee, $yearSummary['ruleCode'], $year);
|
||||
|
||||
$summary->isSupported = true;
|
||||
$summary->ruleCode = $yearSummary['ruleCode'];
|
||||
$summary->acquiredDays = $yearSummary['acquiredDays'] + $fractionedDays;
|
||||
$summary->acquiredSaturdays = $yearSummary['acquiredSaturdays'];
|
||||
$summary->fractionedDays = $fractionedDays;
|
||||
$summary->accruingDays = $yearSummary['accruingDays'];
|
||||
$summary->takenDays = $yearSummary['takenDays'];
|
||||
$summary->takenSaturdays = $yearSummary['takenSaturdays'];
|
||||
$summary->remainingDays = $yearSummary['remainingDays'] + $fractionedDays;
|
||||
$summary->remainingSaturdays = $yearSummary['remainingSaturdays'];
|
||||
$summary->isSupported = true;
|
||||
$summary->ruleCode = $yearSummary['ruleCode'];
|
||||
$summary->acquiredDays = $yearSummary['acquiredDays'] + $fractionedDays;
|
||||
$summary->acquiredSaturdays = $yearSummary['acquiredSaturdays'];
|
||||
$summary->fractionedDays = $fractionedDays;
|
||||
$summary->accruingDays = $yearSummary['accruingDays'];
|
||||
$summary->takenDays = $yearSummary['takenDays'];
|
||||
$summary->takenSaturdays = $yearSummary['takenSaturdays'];
|
||||
$summary->remainingDays = $yearSummary['remainingDays'] + $fractionedDays;
|
||||
$summary->remainingSaturdays = $yearSummary['remainingSaturdays'];
|
||||
$summary->previousYearAcquiredDays = $yearSummary['previousYearAcquiredDays'];
|
||||
$summary->previousYearTakenDays = $yearSummary['previousYearTakenDays'];
|
||||
$summary->previousYearRemainingDays = $yearSummary['previousYearRemainingDays'];
|
||||
|
||||
[$periodFrom, $periodTo] = $this->resolvePeriodBounds($employee, $year);
|
||||
$summary->presenceDaysByMonth = $this->computePresenceDaysByMonth($employee, $periodFrom, $periodTo);
|
||||
@@ -117,7 +120,10 @@ final readonly class EmployeeLeaveSummaryProvider implements ProviderInterface
|
||||
* takenDays: float,
|
||||
* takenSaturdays: float,
|
||||
* remainingDays: float,
|
||||
* remainingSaturdays: float
|
||||
* remainingSaturdays: float,
|
||||
* previousYearAcquiredDays: float,
|
||||
* previousYearTakenDays: float,
|
||||
* previousYearRemainingDays: float
|
||||
* }
|
||||
*/
|
||||
private function computeYearSummary(Employee $employee, int $targetYear): ?array
|
||||
@@ -214,6 +220,10 @@ final readonly class EmployeeLeaveSummaryProvider implements ProviderInterface
|
||||
$takenDays += $openingBalance->getTakenDays();
|
||||
$takenSaturdays += $openingBalance->getTakenSaturdays();
|
||||
}
|
||||
$previousYearAcquired = 0.0;
|
||||
$previousYearTaken = 0.0;
|
||||
$previousYearRemaining = 0.0;
|
||||
|
||||
if (LeaveRuleCode::CDI_CDD_NON_FORFAIT->value === $leavePolicy['ruleCode']) {
|
||||
$availableAcquired = max(0.0, $carryDays);
|
||||
$takenFromAcquired = min($availableAcquired, $takenDays);
|
||||
@@ -238,26 +248,37 @@ final readonly class EmployeeLeaveSummaryProvider implements ProviderInterface
|
||||
} else {
|
||||
// Forfait: no "en cours d'acquisition" counter, all rights are in acquired.
|
||||
// Suspensions do not impact forfait 218 leave calculation.
|
||||
$acquiredDays = $carryDays + $leavePolicy['acquiredDays'];
|
||||
// Taken days are first deducted from N-1 carry, then from current year.
|
||||
$previousYearAcquired = $carryDays;
|
||||
$takenFromPrevious = min(max(0.0, $previousYearAcquired), $takenDays);
|
||||
$previousYearTaken = $takenFromPrevious;
|
||||
$takenFromCurrent = $takenDays - $takenFromPrevious;
|
||||
|
||||
$previousYearRemaining = max(0.0, $previousYearAcquired - $takenFromPrevious);
|
||||
|
||||
$acquiredDays = $leavePolicy['acquiredDays'];
|
||||
$accruingDays = 0.0;
|
||||
$remainingDays = max(0.0, $acquiredDays - $takenDays);
|
||||
$remainingDays = max(0.0, $acquiredDays - $takenFromCurrent);
|
||||
$acquiredSaturdays = 0.0;
|
||||
$remainingSaturdays = 0.0;
|
||||
|
||||
$previousRemainingDays = $remainingDays;
|
||||
$previousRemainingDays = $previousYearRemaining + $remainingDays;
|
||||
$previousRemainingSaturdays = 0.0;
|
||||
}
|
||||
|
||||
if ($year === $targetYear) {
|
||||
$targetSummary = [
|
||||
'ruleCode' => $leavePolicy['ruleCode'],
|
||||
'acquiredDays' => $acquiredDays,
|
||||
'acquiredSaturdays' => $acquiredSaturdays,
|
||||
'accruingDays' => $accruingDays,
|
||||
'takenDays' => $takenDays,
|
||||
'takenSaturdays' => $takenSaturdays,
|
||||
'remainingDays' => $remainingDays,
|
||||
'remainingSaturdays' => $remainingSaturdays,
|
||||
'ruleCode' => $leavePolicy['ruleCode'],
|
||||
'acquiredDays' => $acquiredDays,
|
||||
'acquiredSaturdays' => $acquiredSaturdays,
|
||||
'accruingDays' => $accruingDays,
|
||||
'takenDays' => $takenDays,
|
||||
'takenSaturdays' => $takenSaturdays,
|
||||
'remainingDays' => $remainingDays,
|
||||
'remainingSaturdays' => $remainingSaturdays,
|
||||
'previousYearAcquiredDays' => $previousYearAcquired,
|
||||
'previousYearTakenDays' => $previousYearTaken,
|
||||
'previousYearRemainingDays' => $previousYearRemaining,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user