Files
SIRH/frontend/composables/useEmployeeDetailPage.ts
tristan f493ea237b
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
Ajout des notification + page employé (#6)
| Numéro du ticket | Titre du ticket |
|------------------|-----------------|
|                  |                 |

## Description de la PR

## Modification du .env

## Check list

- [ ] Pas de régression
- [ ] TU/TI/TF rédigée
- [ ] TU/TI/TF OK
- [ ] CHANGELOG modifié

Reviewed-on: #6
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-03-10 12:35:17 +00:00

372 lines
14 KiB
TypeScript

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 { 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 } from '~/utils/contract'
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)
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 employeeContractWorkLabel = computed(() => {
const contract = employee.value?.contract
if (!contract) return '-'
if (contract.type === CONTRACT_TYPES.FORFAIT) return 'Forfait'
if (contract.weeklyHours !== null && contract.weeklyHours !== undefined) return `${contract.weeklyHours} heures`
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 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 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 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()
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)
if (!Number.isInteger(employeeId) || employeeId <= 0) {
return
}
isLoading.value = true
try {
const loadedEmployee = 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),
getEmployeeRttSummary(loadedEmployee.id, rttYear),
...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'
}
} finally {
isLoading.value = false
}
}
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 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 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, minutes: number, rate: '25' | '50') => {
if (!employee.value) return
const year = rttSummary.value?.year ?? undefined
await createRttPayment(employee.value.id, month, minutes, rate, year)
await loadEmployee()
}
watch(requiresCreateContractEndDate, (required) => {
if (!required) {
createContractForm.endDate = ''
}
})
onMounted(async () => {
contracts.value = await listContracts()
await loadEmployee()
})
return {
employee,
isLoading,
activeTab,
contracts,
employeeAbsences,
leaveSummary,
rttSummary,
publicHolidays,
showLeaveTab,
contractHistory,
employeeContractWorkLabel,
contractForm,
createContractForm,
isContractDrawerOpen,
isContractSubmitting,
isCreateContractDrawerOpen,
isCreateContractSubmitting,
canCloseCurrentContract,
canCreateContract,
readonlyFieldClass,
closeContractWorkedHoursLabel,
contractEndDateFieldClass,
showContractEndDateError,
isContractEndDateValid,
createContractNatureFieldClass,
createContractFieldClass,
createContractStartDateFieldClass,
requiresCreateContractEndDate,
createContractEndDateFieldClass,
isCreateContractFormValid,
contractNatureLabel,
contractHistoryLabel,
formatDate,
openCloseContractDrawer,
openCreateContractDrawer,
setContractDrawerOpen,
setCreateContractDrawerOpen,
submitContractUpdate,
submitCreateContract,
submitFractionedDays,
submitRttPayment
}
}