diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml
deleted file mode 100644
index 3fadc3d..0000000
--- a/.idea/sqldialects.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/doc/functional-rules.md b/doc/functional-rules.md
index 19fd2ff..bdda7fc 100644
--- a/doc/functional-rules.md
+++ b/doc/functional-rules.md
@@ -194,13 +194,14 @@ Tous les filtres checkbox sont cochés par défaut à l'ouverture du drawer.
- Détail employé:
- onglet `Suivi contrat` avec affichage de l'historique des périodes de contrat
- chaque ligne expose: nature (`CDI`/`CDD`/`INTERIM`), contrat/temps de travail, date de début, date de fin (ou "En cours")
- - action `Clôturer`:
- - bouton actif uniquement s'il existe un contrat en cours non déjà clôturé à la date du jour
+ - action `Modifier` (clôture/solde de tout compte):
+ - bouton actif s'il existe un contrat en cours non clôturé, ou si le dernier contrat est terminé (sans contrat actif après)
- ouvre un drawer en lecture seule (type/temps de travail/date de début)
- champs saisissables:
- - `contractEndDate` (prérempli à aujourd'hui)
+ - `contractEndDate` (prérempli à aujourd'hui si contrat en cours, à la date de fin existante si contrat terminé)
- `contractPaidLeaveSettled` (checkbox "Soldé dans le solde de tout compte")
- backend: en mode clôture, le flag `contractPaidLeaveSettled` est persisté sur la période clôturée
+ - cas du contrat déjà terminé: permet de modifier `paidLeaveSettled` et le commentaire sur le dernier contrat terminé (ex: solde de tout compte CDD)
- action `Ajouter`:
- conserve le flux d'ajout d'un nouveau contrat via drawer dédié
- disponible uniquement s'il n'y a pas de contrat en cours, ou si le contrat en cours a déjà une date de fin
@@ -384,3 +385,31 @@ Tous les filtres checkbox sont cochés par défaut à l'ouverture du drawer.
- Une notification est créée uniquement quand un chef de site termine la validation complète:
- condition: plus aucune ligne `work_hours` du site à la date concernée avec `isSiteValid = false`
- destinataires: utilisateurs `ROLE_ADMIN`
+
+## 16) Export PDF des heures annuelles
+
+- Accessible depuis la fiche employé (bouton imprimante à droite du nom)
+- Ouvre un drawer pour choisir l'année (civile, Jan-Déc)
+- Génère un PDF avec le détail jour par jour des heures de l'employé
+- Seuls les jours avec heures saisies ou absence sont affichés
+
+### Colonnes selon le mode de suivi
+
+- **TIME (non-chauffeur)**: Date | Absence | Début matin | Fin matin | Début après-midi | Fin après-midi | Début soir | Fin soir | Total
+- **PRESENCE (forfait)**: Date | Absence | Présence matin | Présence après-midi | Total
+- **Chauffeur**: Date | Absence | Heures jour | Heures nuit | Heures atelier | Total
+
+### Changement de contrat en cours d'année
+
+- Si l'employé change de mode de suivi (TIME/PRESENCE) ou de statut chauffeur en cours d'année, le PDF affiche des sections séparées avec les colonnes adaptées à chaque période
+- Le nom du contrat est affiché en sous-titre de chaque section
+
+### Calcul du total
+
+- TIME non-chauffeur: somme des créneaux matin + après-midi + soir, plus minutes créditées des absences `countAsWorkedHours`
+- Chauffeur: `dayHoursMinutes + nightHoursMinutes + workshopHoursMinutes` + minutes créditées
+- PRESENCE: 0.5 par demi-journée présente (matin/après-midi), max 1.0
+
+### Nom du fichier
+
+- Format: `{nom}_{prenom}_{annee}.pdf`
diff --git a/frontend/components/EmployeeYearlyHoursDrawer.vue b/frontend/components/EmployeeYearlyHoursDrawer.vue
new file mode 100644
index 0000000..d043daf
--- /dev/null
+++ b/frontend/components/EmployeeYearlyHoursDrawer.vue
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
diff --git a/frontend/composables/useEmployeeContract.ts b/frontend/composables/useEmployeeContract.ts
index 85929af..d3f5d4d 100644
--- a/frontend/composables/useEmployeeContract.ts
+++ b/frontend/composables/useEmployeeContract.ts
@@ -71,6 +71,17 @@ export const useEmployeeContract = (employee: Ref, reloadEmploy
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(() => {
const period = currentActiveContractPeriod.value
return period?.periodId ?? null
@@ -78,13 +89,15 @@ export const useEmployeeContract = (employee: Ref, reloadEmploy
const canCloseCurrentContract = computed(() => {
const active = currentActiveContractPeriod.value
- if (!active) return false
- if (!active.endDate) return true
- return active.endDate > getTodayYmd()
+ if (active) {
+ if (!active.endDate) return true
+ return active.endDate > getTodayYmd()
+ }
+ return !!lastEndedContractPeriod.value
})
const canCreateContract = computed(() => {
- const active = currentActiveContractPeriod.value
+ const active = editableContractPeriod.value
if (!active) return true
return !!active.endDate
})
@@ -135,15 +148,15 @@ export const useEmployeeContract = (employee: Ref, reloadEmploy
const hydrateContractFormFromCurrent = () => {
const current = employee.value
- const active = currentActiveContractPeriod.value
- if (!current || !active) return
+ const period = editableContractPeriod.value
+ if (!current || !period) 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.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 = ''
}
@@ -173,8 +186,8 @@ export const useEmployeeContract = (employee: Ref, reloadEmploy
createContractForm.contractNature = 'CDI'
createContractForm.endDate = ''
createContractForm.isDriver = false
- createContractForm.startDate = currentActiveContractPeriod.value?.endDate
- ? (shiftYmd(currentActiveContractPeriod.value.endDate, 1) ?? currentActiveContractPeriod.value.endDate)
+ createContractForm.startDate = editableContractPeriod.value?.endDate
+ ? (shiftYmd(editableContractPeriod.value.endDate, 1) ?? editableContractPeriod.value.endDate)
: getTodayYmd()
resetCreateValidation()
isCreateContractDrawerOpen.value = true
@@ -185,15 +198,16 @@ export const useEmployeeContract = (employee: Ref, reloadEmploy
}
const submitContractUpdate = async () => {
- if (!employee.value || isContractSubmitting.value || !currentActiveContractPeriod.value) return
+ const period = editableContractPeriod.value
+ if (!employee.value || isContractSubmitting.value || !period) return
validationTouched.endDate = true
if (!isContractEndDateValid.value) return
- if (contractForm.endDate < currentActiveContractPeriod.value.startDate) {
+ if (contractForm.endDate < period.startDate) {
toast.error({
title: 'Erreur',
- message: `La date de fin doit être postérieure au ${formatDate(currentActiveContractPeriod.value.startDate)}.`
+ message: `La date de fin doit être postérieure au ${formatDate(period.startDate)}.`
})
return
}
@@ -226,8 +240,8 @@ export const useEmployeeContract = (employee: Ref, reloadEmploy
createValidationTouched.endDate = true
if (!isCreateContractFormValid.value) return
- if (currentActiveContractPeriod.value?.endDate) {
- const minStartDate = shiftYmd(currentActiveContractPeriod.value.endDate, 1) ?? currentActiveContractPeriod.value.endDate
+ if (editableContractPeriod.value?.endDate) {
+ const minStartDate = shiftYmd(editableContractPeriod.value.endDate, 1) ?? editableContractPeriod.value.endDate
if (createContractForm.startDate < minStartDate) {
toast.error({
title: 'Erreur',
diff --git a/frontend/pages/employees/[id].vue b/frontend/pages/employees/[id].vue
index 9adc269..e5cb294 100644
--- a/frontend/pages/employees/[id].vue
+++ b/frontend/pages/employees/[id].vue
@@ -13,7 +13,16 @@
-
{{ employee.firstName }} {{ employee.lastName }}
+
+
{{ employee.firstName }} {{ employee.lastName }}
+
+
Date d'entrée : {{ employee.entryDate ? employee.entryDate.split('-').reverse().join('/') : '-' }}