diff --git a/frontend/composables/useEmployeeContract.ts b/frontend/composables/useEmployeeContract.ts new file mode 100644 index 0000000..5d9d3ed --- /dev/null +++ b/frontend/composables/useEmployeeContract.ts @@ -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, reloadEmployee: () => Promise) => { + const toast = useToast() + const contracts = ref([]) + const isContractDrawerOpen = ref(false) + const isContractSubmitting = ref(false) + const isCreateContractDrawerOpen = ref(false) + const isCreateContractSubmitting = ref(false) + const suspensionForms = ref([]) + 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(() => { + 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 + } +} diff --git a/frontend/composables/useEmployeeDetailPage.ts b/frontend/composables/useEmployeeDetailPage.ts index da03f5e..010df49 100644 --- a/frontend/composables/useEmployeeDetailPage.ts +++ b/frontend/composables/useEmployeeDetailPage.ts @@ -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(null) const isLoading = ref(false) const activeTab = ref<'contract' | 'leave' | 'rtt'>('contract') - const contracts = ref([]) - const employeeAbsences = ref([]) - const leaveSummary = ref(null) - const rttSummary = ref(null) - const publicHolidays = ref>({}) - 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([]) - 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(() => { - 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 } } diff --git a/frontend/composables/useEmployeeLeave.ts b/frontend/composables/useEmployeeLeave.ts new file mode 100644 index 0000000..4b250f2 --- /dev/null +++ b/frontend/composables/useEmployeeLeave.ts @@ -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, reloadEmployee: () => Promise) => { + const employeeAbsences = ref([]) + const leaveSummary = ref(null) + const publicHolidays = ref>({}) + 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 + } +} diff --git a/frontend/composables/useEmployeeRtt.ts b/frontend/composables/useEmployeeRtt.ts new file mode 100644 index 0000000..9bf968a --- /dev/null +++ b/frontend/composables/useEmployeeRtt.ts @@ -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, reloadEmployee: () => Promise) => { + const rttSummary = ref(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 + } +} diff --git a/frontend/pages/employees/[id].vue b/frontend/pages/employees/[id].vue index d949671..0683dac 100644 --- a/frontend/pages/employees/[id].vue +++ b/frontend/pages/employees/[id].vue @@ -98,15 +98,25 @@ :on-add-suspension-form="addSuspensionForm" :current-contract-period-id="currentActiveContractPeriodId" /> - - +
+
+ Chargement... +
+ +
+
+
+ Chargement... +
+ +
@@ -161,7 +171,9 @@ const { isSuspensionSubmitting, submitSuspension, addSuspensionForm, - currentActiveContractPeriodId + currentActiveContractPeriodId, + isLeaveLoading, + isRttLoading } = useEmployeeDetailPage() useHead(() => ({