Files
SIRH/frontend/composables/useEmployeeDetailPage.ts
tristan e310f65d81 feat(leave) : prorate forfait work-target days in employee header
The header hardcoded '218 jours' and '218 - presence restants', wrong for a
forfait entered mid-year. Expose forfaitWorkTargetDays = businessDays(period) -
acquiredDays (218 full year, prorated otherwise) and show
'Forfait - {target} jours ({presence} présence · {target-presence} restants)'.
Grégory: 155 jours (11 présence · 144 restants).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 17:51:48 +02:00

148 lines
5.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { Employee } from '~/services/dto/employee'
import { CONTRACT_TYPES } from '~/services/dto/contract'
import { getEmployee } from '~/services/employees'
import { useEmployeeContractPhase } from '~/composables/useEmployeeContractPhase'
export const useEmployeeDetailPage = () => {
const route = useRoute()
const employee = ref<Employee | null>(null)
const isLoading = ref(false)
const activeTab = ref<'contract' | 'leave' | 'rtt' | 'mileage' | 'formation' | 'bonus' | 'observation'>('contract')
const phase = useEmployeeContractPhase(employee)
const showLeaveTab = computed(() => employee.value?.currentContractNature !== 'INTERIM')
const showRttTab = computed(() => phase.selectedPhase.value?.contractType !== CONTRACT_TYPES.FORFAIT)
const isForfait = computed(() => phase.selectedPhase.value?.contractType === CONTRACT_TYPES.FORFAIT)
// Jours à travailler du forfait : prorata exposé par le backend (218 sur année pleine,
// moins sur une entrée en cours d'année). Fallback 218 tant que le récap n'est pas chargé.
const forfaitWorkTargetDays = computed(() => {
const target = leave.leaveSummary.value?.forfaitWorkTargetDays
return (target === null || target === undefined) ? 218 : Math.round(target)
})
const employeeContractWorkLabel = computed(() => {
const contract = employee.value?.contract
if (!contract) return '-'
if (contract.type === CONTRACT_TYPES.FORFAIT) return `Forfait - ${forfaitWorkTargetDays.value} jours`
if (contract.weeklyHours !== null && contract.weeklyHours !== undefined) return `${contract.weeklyHours} heures`
return contract.name || '-'
})
const loadEmployee = async () => {
const idParam = Array.isArray(route.params.id) ? route.params.id[0] : route.params.id
const employeeId = Number(idParam)
if (!Number.isInteger(employeeId) || employeeId <= 0) {
return
}
isLoading.value = true
try {
employee.value = await getEmployee(employeeId)
phase.resetToCurrent()
if (!showLeaveTab.value && activeTab.value === 'leave') {
activeTab.value = 'contract'
}
if (!showRttTab.value && activeTab.value === 'rtt') {
activeTab.value = 'contract'
}
leave.resetLoaded()
rtt.resetLoaded()
mileage.resetLoaded()
formation.resetLoaded()
bonus.resetLoaded()
observation.resetLoaded()
if (activeTab.value === 'leave' && showLeaveTab.value) {
await leave.loadLeaveData()
} else if (activeTab.value === 'rtt' && showRttTab.value) {
await rtt.loadRttData()
} else if (activeTab.value === 'mileage') {
await mileage.loadMileageData()
} else if (activeTab.value === 'formation') {
await formation.loadFormationData()
} else if (activeTab.value === 'bonus') {
await bonus.loadBonusData()
} else if (activeTab.value === 'observation') {
await observation.loadObservationData()
} else if (isForfait.value && showLeaveTab.value) {
// Eager load: needed for the "X jours restants" header label on forfait employees.
await leave.loadLeaveData()
}
} finally {
isLoading.value = false
}
}
const contract = useEmployeeContract(employee, loadEmployee)
const leave = useEmployeeLeave(employee, loadEmployee, phase.selectedPhase)
const forfaitRemainingDaysLabel = computed(() => {
if (!isForfait.value) return ''
const presence = leave.leaveSummary.value?.presenceDaysToToday
if (presence === undefined || presence === null) return ''
const fmt = (n: number) => (Number.isInteger(n) ? String(n) : (Math.round(n * 100) / 100).toFixed(2).replace('.', ','))
// restant à travailler = jours à travailler (prorata) jours de présence déjà effectués
const remaining = forfaitWorkTargetDays.value - presence
return ` (${fmt(presence)} présence · ${fmt(remaining)} restants)`
})
const rtt = useEmployeeRtt(employee, loadEmployee, phase.selectedPhase)
const mileage = useEmployeeMileage(employee, loadEmployee)
const formation = useEmployeeFormation(employee, loadEmployee)
const bonus = useEmployeeBonus(employee, loadEmployee)
const observation = useEmployeeObservation(employee, loadEmployee)
watch(() => phase.selectedPhase.value?.id, (newId, oldId) => {
if (newId === oldId || oldId === undefined) return
// Bascule onglet si on entre dans une phase qui ne supporte plus le tab actuel
if (!showRttTab.value && activeTab.value === 'rtt') {
activeTab.value = 'leave'
}
// Recharger l'onglet courant
if (activeTab.value === 'leave' && showLeaveTab.value) {
leave.loadLeaveData()
} else if (activeTab.value === 'rtt' && showRttTab.value) {
rtt.loadRttData()
}
})
watch(activeTab, (tab) => {
if (tab === 'leave' && !leave.leaveDataLoaded.value && showLeaveTab.value) {
leave.loadLeaveData()
} else if (tab === 'rtt' && !rtt.rttDataLoaded.value && showRttTab.value) {
rtt.loadRttData()
} else if (tab === 'mileage' && !mileage.mileageDataLoaded.value) {
mileage.loadMileageData()
} else if (tab === 'formation' && !formation.formationDataLoaded.value) {
formation.loadFormationData()
} else if (tab === 'bonus' && !bonus.bonusDataLoaded.value) {
bonus.loadBonusData()
} else if (tab === 'observation' && !observation.observationDataLoaded.value) {
observation.loadObservationData()
}
})
onMounted(async () => {
await Promise.all([contract.loadContracts(), contract.loadInterimAgencies()])
await loadEmployee()
})
return {
employee,
isLoading,
activeTab,
showLeaveTab,
showRttTab,
employeeContractWorkLabel,
forfaitRemainingDaysLabel,
...phase,
...contract,
...leave,
...rtt,
...mileage,
...formation,
...bonus,
...observation
}
}