Compare commits

...

4 Commits

Author SHA1 Message Date
gitea-actions
c317a2a026 chore: bump version to v0.1.34
All checks were successful
Auto Tag Develop / tag (push) Successful in 5s
Build Release Artefact / build (push) Successful in 1m20s
2026-03-13 10:59:58 +00:00
8846e83df1 feat : modification de l'affichage des congés
All checks were successful
Auto Tag Develop / tag (push) Successful in 5s
2026-03-13 11:57:02 +01:00
gitea-actions
ff824f233a chore: bump version to v0.1.33
All checks were successful
Auto Tag Develop / tag (push) Successful in 4s
Build Release Artefact / build (push) Successful in 1m15s
2026-03-13 10:03:51 +00:00
c4c9dfceab feat : amélioration des perfs de la page employée en séparant les responsabilités et le chargement par onglet
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
2026-03-13 11:03:41 +01:00
10 changed files with 617 additions and 467 deletions

View File

@@ -1,2 +1,2 @@
parameters: parameters:
app.version: '0.1.32' app.version: '0.1.34'

View File

@@ -1,35 +1,54 @@
<template> <template>
<section class="flex h-full min-h-0 flex-col overflow-hidden pt-8"> <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="grid grid-cols-4 rounded-md bg-tertiary-500 text-primary-500 text-[18px] border border-primary-500">
<div class="flex flex-col gap-2 jutify-center items-center border-r-4 border-white py-3"> <p class="col-start-1 p-[10px] border-b border-b-black"><strong class="uppercase font-semibold">Année acquis :</strong> {{
<p><strong class="uppercase font-semibold">Année acquis :</strong> {{ formatCount(summary?.acquiredDays)
formatCount(summary?.acquiredDays) }} Jours
}} Jours</p> </p>
<p><strong class="uppercase font-semibold">Reste à prendre :</strong> <p class="col-start-2 p-[10px] border-b border-b-black"><strong class="uppercase font-semibold">Pris :</strong>
{{ formatCount(summary?.remainingDays) }} Jours</p> {{ formatCount(isForfaitRule ? currentYearTakenDays : summary?.takenDays) }} Jours
</div> </p>
<div class="flex flex-col gap-2 jutify-center items-center border-r-4 border-white py-3"> <p class="col-start-3 p-[10px] border-b border-b-black"><strong class="uppercase font-semibold">Reste à prendre :</strong>
<p><span class="uppercase font-semibold">Samedi acquis :</span> {{ formatCount(summary?.remainingDays) }} Jours
{{ formatCount(summary?.acquiredSaturdays) }} Jours</p> </p>
<p><span class="uppercase font-semibold">Reste à prendre :</span> <p class="col-start-4 p-[10px] border-b border-b-black"><strong class="uppercase font-semibold">En cours d'acquisition :</strong>
{{ formatCount(summary?.remainingSaturdays) }} Jours</p> {{ formatCount(summary?.accruingDays) }} Jours
</div> </p>
<div class="flex flex-col gap-2 jutify-center items-center border-r-4 border-white py-3"> <p v-if="!isForfaitRule" class="col-start-1 p-[10px]"><span class="uppercase font-semibold">Samedi acquis :</span>
<p><span class="uppercase font-semibold">Fractionné acquis : </span>{{ formatCount(summary?.fractionedDays) }} Jours</p> {{ 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 <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" @click="openFractionedDrawer"
> >
{{ summary?.fractionedDays === 0 ? '+ Ajouter' : 'Modifier' }}</button> <Icon name="mdi:edit-box" size="24"/>
</div> </button>
<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>
</div> </div>
</div> </div>
<div class="mt-8 min-h-0 flex-1 overflow-y-auto pr-2"> <div class="mt-8 min-h-0 flex-1 overflow-y-auto pr-2">
<div class="grid grid-cols-4 gap-10"> <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"> <div class="flex justify-center rounded-t-md bg-primary-500 py-1 font-bold uppercase text-white">
{{ month.label }} {{ month.label }}
</div> </div>
@@ -54,7 +73,9 @@
</div> </div>
</template> </template>
</div> </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> </div>
</div> </div>
@@ -118,7 +139,7 @@ const emit = defineEmits<{
}>() }>()
const isFractionedDrawerOpen = ref(false) const isFractionedDrawerOpen = ref(false)
const fractionedForm = reactive({ days: 0 }) const fractionedForm = reactive({days: 0})
const openFractionedDrawer = () => { const openFractionedDrawer = () => {
fractionedForm.days = props.summary?.fractionedDays ?? 0 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 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(() => { const displayedYear = computed(() => {
if (props.summary?.year) return props.summary.year if (props.summary?.year) return props.summary.year
const today = new Date() const today = new Date()
@@ -282,15 +308,15 @@ const getDayStyle = (day: { leave: DayLeaveState | null; isHoliday: boolean }) =
if (day.leave) { if (day.leave) {
const color = day.leave.colors[0] ?? '#222783' const color = day.leave.colors[0] ?? '#222783'
if (day.leave.am && day.leave.pm) { if (day.leave.am && day.leave.pm) {
return { backgroundColor: color } return {backgroundColor: color}
} }
const colorFaded = `${color}60` const colorFaded = `${color}60`
const backgroundImage = day.leave.am const backgroundImage = day.leave.am
? `linear-gradient(180deg, ${color} 0 50%, ${colorFaded} 50% 100%)` ? `linear-gradient(180deg, ${color} 0 50%, ${colorFaded} 50% 100%)`
: `linear-gradient(180deg, ${colorFaded} 0 50%, ${color} 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 return undefined
} }

View 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
}
}

View File

@@ -1,75 +1,13 @@
import type { Contract } from '~/services/dto/contract' import type { Employee } from '~/services/dto/employee'
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 { CONTRACT_TYPES } from '~/services/dto/contract' import { CONTRACT_TYPES } from '~/services/dto/contract'
import { listAbsences } from '~/services/absences' import { getEmployee } from '~/services/employees'
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'
export const useEmployeeDetailPage = () => { export const useEmployeeDetailPage = () => {
const route = useRoute() const route = useRoute()
const toast = useToast()
const employee = ref<Employee | null>(null) const employee = ref<Employee | null>(null)
const isLoading = ref(false) const isLoading = ref(false)
const activeTab = ref<'contract' | 'leave' | 'rtt'>('contract') 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 showLeaveTab = computed(() => employee.value?.currentContractNature !== 'INTERIM')
const showRttTab = computed(() => employee.value?.contract?.type !== CONTRACT_TYPES.FORFAIT) const showRttTab = computed(() => employee.value?.contract?.type !== CONTRACT_TYPES.FORFAIT)
const employeeContractWorkLabel = computed(() => { const employeeContractWorkLabel = computed(() => {
@@ -80,133 +18,6 @@ export const useEmployeeDetailPage = () => {
return contract.name || '-' 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 loadEmployee = async () => {
const idParam = Array.isArray(route.params.id) ? route.params.id[0] : route.params.id const idParam = Array.isArray(route.params.id) ? route.params.id[0] : route.params.id
const employeeId = Number(idParam) const employeeId = Number(idParam)
@@ -216,185 +27,42 @@ export const useEmployeeDetailPage = () => {
isLoading.value = true isLoading.value = true
try { try {
const loadedEmployee = await getEmployee(employeeId) employee.value = await getEmployee(employeeId)
employee.value = loadedEmployee
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') { if (!showLeaveTab.value && activeTab.value === 'leave') {
activeTab.value = 'contract' activeTab.value = 'contract'
} }
if (!showRttTab.value && activeTab.value === 'rtt') { if (!showRttTab.value && activeTab.value === 'rtt') {
activeTab.value = 'contract' 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 { } finally {
isLoading.value = false isLoading.value = false
} }
} }
const submitContractUpdate = async () => { const contract = useEmployeeContract(employee, loadEmployee)
if (!employee.value || isContractSubmitting.value || !currentActiveContractPeriod.value) return const leave = useEmployeeLeave(employee, loadEmployee)
const rtt = useEmployeeRtt(employee, loadEmployee)
validationTouched.endDate = true watch(activeTab, (tab) => {
if (!isContractEndDateValid.value) return if (tab === 'leave' && !leave.leaveDataLoaded.value && showLeaveTab.value) {
leave.loadLeaveData()
if (contractForm.endDate < currentActiveContractPeriod.value.startDate) { } else if (tab === 'rtt' && !rtt.rttDataLoaded.value && showRttTab.value) {
toast.error({ rtt.loadRttData()
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 = ''
} }
}) })
onMounted(async () => { onMounted(async () => {
contracts.value = await listContracts() await contract.loadContracts()
await loadEmployee() await loadEmployee()
}) })
@@ -402,50 +70,11 @@ export const useEmployeeDetailPage = () => {
employee, employee,
isLoading, isLoading,
activeTab, activeTab,
contracts,
employeeAbsences,
leaveSummary,
rttSummary,
publicHolidays,
showLeaveTab, showLeaveTab,
showRttTab, showRttTab,
contractHistory,
employeeContractWorkLabel, employeeContractWorkLabel,
contractForm, ...contract,
createContractForm, ...leave,
isContractDrawerOpen, ...rtt
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
} }
} }

View 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
}
}

View 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
}
}

View File

@@ -98,15 +98,25 @@
:on-add-suspension-form="addSuspensionForm" :on-add-suspension-form="addSuspensionForm"
:current-contract-period-id="currentActiveContractPeriodId" :current-contract-period-id="currentActiveContractPeriodId"
/> />
<EmployeesLeaveTab <div v-else-if="showLeaveTab && activeTab === 'leave'" class="h-full">
v-else-if="showLeaveTab && activeTab === 'leave'" <div v-if="isLeaveLoading" class="mt-6 rounded-lg border border-neutral-200 bg-white p-6 text-md text-neutral-600">
class="h-full" Chargement...
:absences="employeeAbsences" </div>
:summary="leaveSummary" <EmployeesLeaveTab
:public-holidays="publicHolidays" v-else
@update-fractioned-days="submitFractionedDays" class="h-full"
/> :absences="employeeAbsences"
<EmployeesRttTab v-else-if="showRttTab && activeTab === 'rtt'" class="h-full" :summary="rttSummary" @submit-rtt-payment="submitRttPayment" /> :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> </div>
</div> </div>
@@ -161,7 +171,9 @@ const {
isSuspensionSubmitting, isSuspensionSubmitting,
submitSuspension, submitSuspension,
addSuspensionForm, addSuspensionForm,
currentActiveContractPeriodId currentActiveContractPeriodId,
isLeaveLoading,
isRttLoading
} = useEmployeeDetailPage() } = useEmployeeDetailPage()
useHead(() => ({ useHead(() => ({

View File

@@ -10,6 +10,9 @@ export type EmployeeLeaveSummary = {
takenSaturdays: number takenSaturdays: number
fractionedDays: number fractionedDays: number
accruingDays: number accruingDays: number
previousYearAcquiredDays: number
previousYearTakenDays: number
previousYearRemainingDays: number
presenceDaysByMonth: Record<string, number> presenceDaysByMonth: Record<string, number>
} }

View File

@@ -20,17 +20,20 @@ use App\State\EmployeeLeaveSummaryProvider;
)] )]
final class EmployeeLeaveSummary final class EmployeeLeaveSummary
{ {
public int $year = 0; public int $year = 0;
public bool $isSupported = false; public bool $isSupported = false;
public string $ruleCode = ''; public string $ruleCode = '';
public float $acquiredDays = 0.0; public float $acquiredDays = 0.0;
public float $remainingDays = 0.0; public float $remainingDays = 0.0;
public float $takenDays = 0.0; public float $takenDays = 0.0;
public float $acquiredSaturdays = 0.0; public float $acquiredSaturdays = 0.0;
public float $remainingSaturdays = 0.0; public float $remainingSaturdays = 0.0;
public float $takenSaturdays = 0.0; public float $takenSaturdays = 0.0;
public float $fractionedDays = 0.0; public float $fractionedDays = 0.0;
public float $accruingDays = 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) */ /** @var array<string, float> YYYY-MM => count (0.5 for half-days) */
public array $presenceDaysByMonth = []; public array $presenceDaysByMonth = [];

View File

@@ -91,16 +91,19 @@ final readonly class EmployeeLeaveSummaryProvider implements ProviderInterface
$fractionedDays = $this->resolveFractionedDays($employee, $yearSummary['ruleCode'], $year); $fractionedDays = $this->resolveFractionedDays($employee, $yearSummary['ruleCode'], $year);
$summary->isSupported = true; $summary->isSupported = true;
$summary->ruleCode = $yearSummary['ruleCode']; $summary->ruleCode = $yearSummary['ruleCode'];
$summary->acquiredDays = $yearSummary['acquiredDays'] + $fractionedDays; $summary->acquiredDays = $yearSummary['acquiredDays'] + $fractionedDays;
$summary->acquiredSaturdays = $yearSummary['acquiredSaturdays']; $summary->acquiredSaturdays = $yearSummary['acquiredSaturdays'];
$summary->fractionedDays = $fractionedDays; $summary->fractionedDays = $fractionedDays;
$summary->accruingDays = $yearSummary['accruingDays']; $summary->accruingDays = $yearSummary['accruingDays'];
$summary->takenDays = $yearSummary['takenDays']; $summary->takenDays = $yearSummary['takenDays'];
$summary->takenSaturdays = $yearSummary['takenSaturdays']; $summary->takenSaturdays = $yearSummary['takenSaturdays'];
$summary->remainingDays = $yearSummary['remainingDays'] + $fractionedDays; $summary->remainingDays = $yearSummary['remainingDays'] + $fractionedDays;
$summary->remainingSaturdays = $yearSummary['remainingSaturdays']; $summary->remainingSaturdays = $yearSummary['remainingSaturdays'];
$summary->previousYearAcquiredDays = $yearSummary['previousYearAcquiredDays'];
$summary->previousYearTakenDays = $yearSummary['previousYearTakenDays'];
$summary->previousYearRemainingDays = $yearSummary['previousYearRemainingDays'];
[$periodFrom, $periodTo] = $this->resolvePeriodBounds($employee, $year); [$periodFrom, $periodTo] = $this->resolvePeriodBounds($employee, $year);
$summary->presenceDaysByMonth = $this->computePresenceDaysByMonth($employee, $periodFrom, $periodTo); $summary->presenceDaysByMonth = $this->computePresenceDaysByMonth($employee, $periodFrom, $periodTo);
@@ -117,7 +120,10 @@ final readonly class EmployeeLeaveSummaryProvider implements ProviderInterface
* takenDays: float, * takenDays: float,
* takenSaturdays: float, * takenSaturdays: float,
* remainingDays: float, * remainingDays: float,
* remainingSaturdays: float * remainingSaturdays: float,
* previousYearAcquiredDays: float,
* previousYearTakenDays: float,
* previousYearRemainingDays: float
* } * }
*/ */
private function computeYearSummary(Employee $employee, int $targetYear): ?array private function computeYearSummary(Employee $employee, int $targetYear): ?array
@@ -214,6 +220,10 @@ final readonly class EmployeeLeaveSummaryProvider implements ProviderInterface
$takenDays += $openingBalance->getTakenDays(); $takenDays += $openingBalance->getTakenDays();
$takenSaturdays += $openingBalance->getTakenSaturdays(); $takenSaturdays += $openingBalance->getTakenSaturdays();
} }
$previousYearAcquired = 0.0;
$previousYearTaken = 0.0;
$previousYearRemaining = 0.0;
if (LeaveRuleCode::CDI_CDD_NON_FORFAIT->value === $leavePolicy['ruleCode']) { if (LeaveRuleCode::CDI_CDD_NON_FORFAIT->value === $leavePolicy['ruleCode']) {
$availableAcquired = max(0.0, $carryDays); $availableAcquired = max(0.0, $carryDays);
$takenFromAcquired = min($availableAcquired, $takenDays); $takenFromAcquired = min($availableAcquired, $takenDays);
@@ -238,26 +248,37 @@ final readonly class EmployeeLeaveSummaryProvider implements ProviderInterface
} else { } else {
// Forfait: no "en cours d'acquisition" counter, all rights are in acquired. // Forfait: no "en cours d'acquisition" counter, all rights are in acquired.
// Suspensions do not impact forfait 218 leave calculation. // 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; $accruingDays = 0.0;
$remainingDays = max(0.0, $acquiredDays - $takenDays); $remainingDays = max(0.0, $acquiredDays - $takenFromCurrent);
$acquiredSaturdays = 0.0; $acquiredSaturdays = 0.0;
$remainingSaturdays = 0.0; $remainingSaturdays = 0.0;
$previousRemainingDays = $remainingDays; $previousRemainingDays = $previousYearRemaining + $remainingDays;
$previousRemainingSaturdays = 0.0; $previousRemainingSaturdays = 0.0;
} }
if ($year === $targetYear) { if ($year === $targetYear) {
$targetSummary = [ $targetSummary = [
'ruleCode' => $leavePolicy['ruleCode'], 'ruleCode' => $leavePolicy['ruleCode'],
'acquiredDays' => $acquiredDays, 'acquiredDays' => $acquiredDays,
'acquiredSaturdays' => $acquiredSaturdays, 'acquiredSaturdays' => $acquiredSaturdays,
'accruingDays' => $accruingDays, 'accruingDays' => $accruingDays,
'takenDays' => $takenDays, 'takenDays' => $takenDays,
'takenSaturdays' => $takenSaturdays, 'takenSaturdays' => $takenSaturdays,
'remainingDays' => $remainingDays, 'remainingDays' => $remainingDays,
'remainingSaturdays' => $remainingSaturdays, 'remainingSaturdays' => $remainingSaturdays,
'previousYearAcquiredDays' => $previousYearAcquired,
'previousYearTakenDays' => $previousYearTaken,
'previousYearRemainingDays' => $previousYearRemaining,
]; ];
} }
} }