feat : sélecteur d'année sur l'onglet Congés de la fiche employé
Permet de consulter les exercices passés (calendrier + compteurs) sur l'onglet Congés. La plage proposée est bornée par max(début historique contrat, RTT_START_DATE) pour ne pas remonter avant la mise en service du logiciel. Édition des stocks N-1 et fractionnés verrouillée sur exercices clos. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -39,6 +39,8 @@
|
||||
</div>
|
||||
<button
|
||||
class="flex items-center"
|
||||
:class="isHistoricalYear ? 'opacity-40 cursor-not-allowed' : ''"
|
||||
:disabled="isHistoricalYear"
|
||||
@click="openPaidLeaveDrawer"
|
||||
>
|
||||
<Icon name="mdi:edit-box" size="24"/>
|
||||
@@ -51,6 +53,8 @@
|
||||
</div>
|
||||
<button
|
||||
class="flex items-center"
|
||||
:class="isHistoricalYear ? 'opacity-40 cursor-not-allowed' : ''"
|
||||
:disabled="isHistoricalYear"
|
||||
@click="openFractionedDrawer"
|
||||
>
|
||||
<Icon name="mdi:edit-box" size="24"/>
|
||||
@@ -90,6 +94,22 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-6 flex items-center gap-3">
|
||||
<label for="leave-year-select" class="text-md font-semibold text-primary-500 uppercase">
|
||||
{{ isForfaitRule ? 'Année :' : 'Exercice :' }}
|
||||
</label>
|
||||
<select
|
||||
id="leave-year-select"
|
||||
:value="selectedYear ?? ''"
|
||||
:disabled="!availableYears.length"
|
||||
class="border border-primary-500 rounded-md px-3 py-1 text-md font-semibold text-primary-500 bg-white focus:outline-none focus:ring-2 focus:ring-secondary-500/20 disabled:opacity-50"
|
||||
@change="handleYearChange"
|
||||
>
|
||||
<option v-for="option in availableYears" :key="option.value" :value="option.value">
|
||||
{{ option.label }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<AppDrawer v-model="isFractionedDrawerOpen" title="Jours fractionnés">
|
||||
<form class="space-y-4" @submit.prevent="handleSubmitFractioned">
|
||||
@@ -173,17 +193,39 @@ type DayLeaveState = {
|
||||
colors: string[]
|
||||
}
|
||||
|
||||
type LeaveYearOption = {
|
||||
value: number
|
||||
label: string
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
absences: Absence[]
|
||||
summary: EmployeeLeaveSummary | null
|
||||
publicHolidays: Record<string, string>
|
||||
selectedYear: number | null
|
||||
availableYears: LeaveYearOption[]
|
||||
currentYear: number | null
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'update-fractioned-days', days: number): void
|
||||
(event: 'update-paid-leave-days', days: number): void
|
||||
(event: 'update-selected-year', year: number): void
|
||||
}>()
|
||||
|
||||
const isHistoricalYear = computed(() =>
|
||||
props.selectedYear !== null
|
||||
&& props.currentYear !== null
|
||||
&& props.selectedYear !== props.currentYear
|
||||
)
|
||||
|
||||
const handleYearChange = (event: Event) => {
|
||||
const target = event.target as HTMLSelectElement
|
||||
const value = Number(target.value)
|
||||
if (Number.isNaN(value)) return
|
||||
emit('update-selected-year', value)
|
||||
}
|
||||
|
||||
const isFractionedDrawerOpen = ref(false)
|
||||
const fractionedForm = reactive({days: 0})
|
||||
|
||||
@@ -239,6 +281,7 @@ const currentYearTakenDays = computed(() => {
|
||||
})
|
||||
|
||||
const displayedYear = computed(() => {
|
||||
if (props.selectedYear) return props.selectedYear
|
||||
if (props.summary?.year) return props.summary.year
|
||||
const today = new Date()
|
||||
const year = today.getFullYear()
|
||||
|
||||
@@ -7,27 +7,91 @@ import { listAbsences } from '~/services/absences'
|
||||
import { getEmployeeLeaveSummary, updateFractionedDays, updatePaidLeaveDays } from '~/services/employee-leave-summary'
|
||||
import { listPublicHolidays } from '~/services/public-holidays'
|
||||
|
||||
export type LeaveYearOption = {
|
||||
value: number
|
||||
label: string
|
||||
}
|
||||
|
||||
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 selectedLeaveYear = ref<number | null>(null)
|
||||
|
||||
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 isForfaitContract = (emp: Employee | null) =>
|
||||
emp?.contract?.type === CONTRACT_TYPES.FORFAIT
|
||||
|
||||
const computeLeaveYearForDate = (emp: Employee | null, date: Date): number => {
|
||||
if (isForfaitContract(emp)) return date.getFullYear()
|
||||
return date.getMonth() >= 5 ? date.getFullYear() + 1 : date.getFullYear()
|
||||
}
|
||||
|
||||
const currentLeaveYear = computed<number | null>(() => {
|
||||
if (!employee.value) return null
|
||||
return computeLeaveYearForDate(employee.value, new Date())
|
||||
})
|
||||
|
||||
const formatLeaveYearLabel = (year: number, isForfait: boolean): string => {
|
||||
if (isForfait) return String(year)
|
||||
return `Juin ${year - 1} → Mai ${year}`
|
||||
}
|
||||
|
||||
const availableLeaveYears = computed<LeaveYearOption[]>(() => {
|
||||
if (!employee.value || currentLeaveYear.value === null) return []
|
||||
const isForfait = isForfaitContract(employee.value)
|
||||
const current = currentLeaveYear.value
|
||||
|
||||
const startDates: string[] = []
|
||||
for (const period of employee.value.contractHistory ?? []) {
|
||||
if (period.startDate) startDates.push(period.startDate)
|
||||
}
|
||||
if (employee.value.entryDate) startDates.push(employee.value.entryDate)
|
||||
|
||||
let contractFloor = current
|
||||
for (const raw of startDates) {
|
||||
const date = new Date(`${raw.substring(0, 10)}T00:00:00`)
|
||||
if (Number.isNaN(date.getTime())) continue
|
||||
const leaveYear = computeLeaveYearForDate(employee.value, date)
|
||||
if (leaveYear < contractFloor) contractFloor = leaveYear
|
||||
}
|
||||
|
||||
// Hard floor : data-start-date (env RTT_START_DATE) — le logiciel n'a pas
|
||||
// d'historique avant cette date, inutile de proposer des années antérieures.
|
||||
let dataFloor: number | null = null
|
||||
const dataStart = leaveSummary.value?.dataStartDate
|
||||
if (dataStart) {
|
||||
const dataStartDate = new Date(`${dataStart.substring(0, 10)}T00:00:00`)
|
||||
if (!Number.isNaN(dataStartDate.getTime())) {
|
||||
dataFloor = computeLeaveYearForDate(employee.value, dataStartDate)
|
||||
}
|
||||
}
|
||||
|
||||
const minYear = dataFloor !== null ? Math.max(contractFloor, dataFloor) : contractFloor
|
||||
|
||||
const years: LeaveYearOption[] = []
|
||||
for (let y = current; y >= minYear; y -= 1) {
|
||||
years.push({ value: y, label: formatLeaveYearLabel(y, isForfait) })
|
||||
}
|
||||
return years
|
||||
})
|
||||
|
||||
const initSelectedLeaveYear = () => {
|
||||
if (selectedLeaveYear.value !== null) return
|
||||
if (currentLeaveYear.value !== null) {
|
||||
selectedLeaveYear.value = currentLeaveYear.value
|
||||
}
|
||||
}
|
||||
|
||||
const loadLeaveData = async () => {
|
||||
if (!employee.value || isLeaveLoading.value) return
|
||||
initSelectedLeaveYear()
|
||||
if (selectedLeaveYear.value === null) return
|
||||
isLeaveLoading.value = true
|
||||
try {
|
||||
const isForfait = employee.value.contract?.type === CONTRACT_TYPES.FORFAIT
|
||||
const leaveYear = getLeaveYear()
|
||||
const isForfait = isForfaitContract(employee.value)
|
||||
const leaveYear = selectedLeaveYear.value
|
||||
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]
|
||||
@@ -46,8 +110,16 @@ export const useEmployeeLeave = (employee: Ref<Employee | null>, reloadEmployee:
|
||||
}
|
||||
}
|
||||
|
||||
const setSelectedLeaveYear = async (year: number) => {
|
||||
if (selectedLeaveYear.value === year) return
|
||||
selectedLeaveYear.value = year
|
||||
leaveDataLoaded.value = false
|
||||
await loadLeaveData()
|
||||
}
|
||||
|
||||
const resetLoaded = () => {
|
||||
leaveDataLoaded.value = false
|
||||
selectedLeaveYear.value = null
|
||||
}
|
||||
|
||||
const submitFractionedDays = async (days: number) => {
|
||||
@@ -70,6 +142,10 @@ export const useEmployeeLeave = (employee: Ref<Employee | null>, reloadEmployee:
|
||||
publicHolidays,
|
||||
isLeaveLoading,
|
||||
leaveDataLoaded,
|
||||
selectedLeaveYear,
|
||||
currentLeaveYear,
|
||||
availableLeaveYears,
|
||||
setSelectedLeaveYear,
|
||||
loadLeaveData,
|
||||
resetLoaded,
|
||||
submitFractionedDays,
|
||||
|
||||
@@ -457,6 +457,17 @@ export const documentationSections: DocSection[] = [
|
||||
{ type: 'paragraph', content: 'Compteurs visibles sur l\'onglet Congé de la fiche employé : acquis, en cours d\'acquisition, pris, restant.' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'onglet-conges-fiche-employe',
|
||||
title: 'Onglet Congés (fiche employé)',
|
||||
requiredLevel: 'admin',
|
||||
blocks: [
|
||||
{ type: 'paragraph', content: 'L\'onglet "Congés" sur la fiche employé affiche un calendrier annuel des congés posés (12 mois en grille 4×3) ainsi que les compteurs (acquis, pris, reste, en cours d\'acquisition, N-1 ou samedis selon le contrat).' },
|
||||
{ type: 'paragraph', content: 'La période affichée dépend du type de contrat actuel : Janvier → Décembre pour FORFAIT, Juin (N-1) → Mai (N) pour les autres contrats.' },
|
||||
{ type: 'paragraph', content: 'Un sélecteur d\'année est disponible en bas du calendrier (zone scrollable, à gauche). Il permet de consulter les exercices passés. La plage proposée part de l\'exercice courant et remonte jusqu\'au plus récent entre (a) le premier exercice où l\'employé avait un contrat ouvert et (b) l\'exercice de mise en service du logiciel — il est inutile de remonter plus loin, aucune donnée n\'a été saisie avant.' },
|
||||
{ type: 'note', content: 'Sur un exercice passé, les boutons d\'édition "Jours fractionnés" et "Année N-1 payés" sont désactivés. La consultation reste possible, mais on n\'autorise pas la modification rétroactive d\'un exercice clos.' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'ecran-recap-conges',
|
||||
title: 'Écran Récap. congés',
|
||||
|
||||
@@ -160,8 +160,12 @@
|
||||
:absences="employeeAbsences"
|
||||
:summary="leaveSummary"
|
||||
:public-holidays="publicHolidays"
|
||||
:selected-year="selectedLeaveYear"
|
||||
:available-years="availableLeaveYears"
|
||||
:current-year="currentLeaveYear"
|
||||
@update-fractioned-days="submitFractionedDays"
|
||||
@update-paid-leave-days="submitPaidLeaveDays"
|
||||
@update-selected-year="setSelectedLeaveYear"
|
||||
/>
|
||||
</div>
|
||||
<div v-else-if="showRttTab && activeTab === 'rtt'" class="h-full">
|
||||
@@ -253,6 +257,10 @@ const {
|
||||
leaveSummary,
|
||||
rttSummary,
|
||||
publicHolidays,
|
||||
selectedLeaveYear,
|
||||
currentLeaveYear,
|
||||
availableLeaveYears,
|
||||
setSelectedLeaveYear,
|
||||
showLeaveTab,
|
||||
showRttTab,
|
||||
contractHistory,
|
||||
|
||||
@@ -16,5 +16,6 @@ export type EmployeeLeaveSummary = {
|
||||
previousYearPaidDays: number
|
||||
presenceDaysByMonth: Record<string, number>
|
||||
presenceDaysToToday: number
|
||||
dataStartDate: string | null
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user