Files
SIRH/frontend/composables/useEmployeeContract.ts
tristan 1fe7f2cdde
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
feat : agence d'intérim sur les contrats INTERIM + renommage Types d'absence en Types de statut + colonne Absence en Statut
- Nouvelle entité InterimAgency (table interim_agencies, API lecture seule)
- Sélecteur agence conditionnel dans les formulaires création employé et ajout contrat
- Affichage "Intérim (NomAgence)" sur la liste employés et l'historique contrat
- Date de fin obligatoire côté frontend pour CDD et INTERIM (aligné backend)
- Renommage "Types d'absence" → "Types de statut" (sidebar, page, titre)
- Renommage en-tête "Absence" → "Statut" sur les vues jour heures et conducteurs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 11:47:14 +02:00

409 lines
15 KiB
TypeScript

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 { listInterimAgencies, type InterimAgency } from '~/services/interim-agencies'
import { formatNullableYmdToFr, getTodayYmd, shiftYmd } from '~/utils/date'
import { contractNatureLabel, formatWorkDaysHoursSummary, isContractNature, requiresContractEndDate, requiresWorkDaysHours, 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 interimAgencies = ref<InterimAgency[]>([])
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: '',
workDaysHours: null as Record<number, number> | null
})
const validationTouched = reactive({
endDate: false
})
const createContractForm = reactive({
contractId: '' as number | '',
contractNature: 'CDI' as 'CDI' | 'CDD' | 'INTERIM',
startDate: '',
endDate: '',
isDriver: false,
workDaysHours: null as Record<number, number> | null,
interimAgencyId: '' as number | ''
})
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) => {
const base = item.weeklyHours !== null && item.weeklyHours !== undefined
? `${item.weeklyHours} heures`
: (item.contractName ?? '-')
const scheduleSummary = formatWorkDaysHoursSummary(item.workDaysHours)
return scheduleSummary ? `${base} (${scheduleSummary})` : base
}
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 lastEndedContractPeriod = computed(() => {
if (currentActiveContractPeriod.value) return null
const today = getTodayYmd()
const history = employee.value?.contractHistory ?? []
const ended = history.filter((item) => item.endDate && item.endDate < today)
if (ended.length === 0) return null
return ended.reduce((latest, item) => (item.endDate! > latest.endDate! ? item : latest))
})
const editableContractPeriod = computed(() => currentActiveContractPeriod.value ?? lastEndedContractPeriod.value)
const currentActiveContractPeriodId = computed<number | null>(() => {
const period = currentActiveContractPeriod.value
return period?.periodId ?? null
})
const canCloseCurrentContract = computed(() => {
const active = currentActiveContractPeriod.value
if (active) {
if (!active.endDate) return true
return active.endDate > getTodayYmd()
}
return !!lastEndedContractPeriod.value
})
const canCreateContract = computed(() => {
const active = editableContractPeriod.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 selectedCreateContract = computed<Contract | null>(() =>
contracts.value.find((c) => c.id === Number(createContractForm.contractId)) ?? null
)
const requiresCreateWorkDaysHours = computed(() =>
requiresWorkDaysHours(selectedCreateContract.value, createContractForm.contractNature)
)
const createScheduleTotalMinutes = computed(() => {
const raw = createContractForm.workDaysHours ?? {}
return Object.values(raw).reduce((s, n) => s + (Number(n) || 0), 0)
})
const isCreateScheduleValid = computed(() => {
if (!requiresCreateWorkDaysHours.value) return true
const expected = (selectedCreateContract.value?.weeklyHours ?? 0) * 60
return expected > 0 && createScheduleTotalMinutes.value === expected
})
const isCreateContractFormValid = computed(() =>
isCreateContractValid.value &&
isCreateContractNatureValid.value &&
isCreateContractStartDateValid.value &&
isCreateContractEndDateValid.value &&
isCreateScheduleValid.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 period = editableContractPeriod.value
if (!current || !period) return
contractForm.contractId = period.contractId ?? current.contract?.id ?? ''
contractForm.contractName = period.contractName ?? current.contract?.name ?? ''
contractForm.weeklyHours = period.weeklyHours ?? current.contract?.weeklyHours ?? null
contractForm.contractNature = period.contractNature
contractForm.startDate = period.startDate
contractForm.endDate = period.endDate ?? getTodayYmd()
contractForm.paidLeaveSettled = false
contractForm.comment = ''
contractForm.workDaysHours = period.workDaysHours ?? null
}
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.isDriver = false
createContractForm.workDaysHours = null
createContractForm.interimAgencyId = ''
createContractForm.startDate = editableContractPeriod.value?.endDate
? (shiftYmd(editableContractPeriod.value.endDate, 1) ?? editableContractPeriod.value.endDate)
: getTodayYmd()
resetCreateValidation()
isCreateContractDrawerOpen.value = true
}
const setCreateContractDrawerOpen = (open: boolean) => {
isCreateContractDrawerOpen.value = open
}
const submitContractUpdate = async () => {
const period = editableContractPeriod.value
if (!employee.value || isContractSubmitting.value || !period) return
validationTouched.endDate = true
if (!isContractEndDateValid.value) return
if (contractForm.endDate < period.startDate) {
toast.error({
title: 'Erreur',
message: `La date de fin doit être postérieure au ${formatDate(period.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 (editableContractPeriod.value?.endDate) {
const minStartDate = shiftYmd(editableContractPeriod.value.endDate, 1) ?? editableContractPeriod.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,
isDriverInput: createContractForm.isDriver,
workDaysHoursInput: requiresCreateWorkDaysHours.value ? createContractForm.workDaysHours : null,
interimAgencyId: createContractForm.contractNature === 'INTERIM' && createContractForm.interimAgencyId !== '' ? Number(createContractForm.interimAgencyId) : 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()
}
const loadInterimAgencies = async () => {
interimAgencies.value = await listInterimAgencies()
}
watch(() => createContractForm.contractNature, (nature) => {
if (nature !== 'INTERIM') {
createContractForm.interimAgencyId = ''
}
})
watch(showsCreateContractEndDate, (shows) => {
if (!shows) {
createContractForm.endDate = ''
}
})
watch(requiresCreateWorkDaysHours, (required) => {
if (!required) {
createContractForm.workDaysHours = null
}
})
return {
contracts,
contractHistory,
contractForm,
createContractForm,
isContractDrawerOpen,
isContractSubmitting,
isCreateContractDrawerOpen,
isCreateContractSubmitting,
canCloseCurrentContract,
canCreateContract,
readonlyFieldClass,
closeContractWorkedHoursLabel,
contractEndDateFieldClass,
showContractEndDateError,
isContractEndDateValid,
createContractNatureFieldClass,
createContractFieldClass,
createContractStartDateFieldClass,
showsCreateContractEndDate,
requiresCreateContractEndDate,
createContractEndDateFieldClass,
isCreateContractFormValid,
requiresCreateWorkDaysHours,
selectedCreateContract,
contractNatureLabel,
contractHistoryLabel,
formatDate,
openCloseContractDrawer,
openCreateContractDrawer,
setContractDrawerOpen,
setCreateContractDrawerOpen,
submitContractUpdate,
submitCreateContract,
suspensionForms,
isSuspensionSubmitting,
submitSuspension,
addSuspensionForm,
currentActiveContractPeriodId,
interimAgencies,
loadContracts,
loadInterimAgencies
}
}