Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 45c32989e5 | |||
| 77ae6820d7 |
@@ -39,6 +39,10 @@
|
|||||||
- **Calendrier des jours validés (vue Jour)** (`WorkHourValidationStatusProvider`, ressource `WorkHourValidationStatus`, endpoint `GET /work-hours/validation-status?from=&to=[&driver=1]`, `ROLE_USER`) : en **vue Jour**, sur les **deux écrans** (Heures **et** Heures Conducteurs), le sélecteur de date est un `MalioDate` (layer `@malio/layer-ui >= 1.7.x` : prop `markedDates` + event `@month-change`) qui peint **en vert** (`markedDates` → `'success'`) les jours **entièrement validés**. **Définition** : un jour est vert ssi il porte ≥1 ligne `WorkHour` du scope ce jour-là **et** aucune n'est `isValid=false` — on se base sur la **seule** colonne `is_valid` (validation admin ; `isSiteValid` ignoré). Jour **sans aucune ligne** → neutre (jamais vert). **Périmètre complet** via `EmployeeRepository::findScoped` (admin = tous sites, chef de site = ses sites), **indépendant du filtre sites** de l'écran. **Scope conducteur inversé** par `?driver=1` : écran Heures → non-conducteurs (défaut), écran Heures Conducteurs → conducteurs (résolu par date via `EmployeeContractResolver::resolveIsDriverForEmployeeAndDate`, mémoïsé ; garde `if ($isDriver !== $driverOnly) continue`). Provider : une requête `WorkHourReadRepositoryInterface::findByDateRangeAndEmployees`, agrégation par jour (`total`/`pending`), plage bornée à 366 j. **Chargement à la volée par mois** (jamais préchargé) : `@month-change {month,year}` (à l'ouverture + nav) → fetch de la **grille visible** (lundi avant le 1er → dimanche après le dernier) → cache `validatedDaysByMonth` (`useHoursPage` / `useDriverHoursPage`, ce dernier passe `{ driver: true }` au service) → `markedDates` réactif. **Rafraîchissement** du mois en cache (`reloadValidationMonth`) après `toggleValidation`/`toggleValidationBulk`/`handleSave`/`refreshAfterAbsenceChange` (pas la validation site). La **vue Semaine** utilise un `MalioDateWeek` (sélecteur de semaine, v-model ISO week `YYYY-Www`, sans coloration). Le **stepper ‹ › du mode jour est remplacé** par le `MalioDate` (raccourcis Hier/Aujourd'hui/Demain conservés) ; `PeriodStepperPicker` reste un fallback de la vue Jour quand `showValidationCalendar` est absent (aucun appelant actuel). Activation par écran via la prop `showValidationCalendar` de `HoursToolbar` (les deux pages la passent à `true` + `markedDates` + `@month-change`). Alignement vertical de la ligne via `lg:items-center` (les champs Malio font `h-12` vs `h-10` des boutons). Doc complète : `doc/hours-validated-days.md`.
|
- **Calendrier des jours validés (vue Jour)** (`WorkHourValidationStatusProvider`, ressource `WorkHourValidationStatus`, endpoint `GET /work-hours/validation-status?from=&to=[&driver=1]`, `ROLE_USER`) : en **vue Jour**, sur les **deux écrans** (Heures **et** Heures Conducteurs), le sélecteur de date est un `MalioDate` (layer `@malio/layer-ui >= 1.7.x` : prop `markedDates` + event `@month-change`) qui peint **en vert** (`markedDates` → `'success'`) les jours **entièrement validés**. **Définition** : un jour est vert ssi il porte ≥1 ligne `WorkHour` du scope ce jour-là **et** aucune n'est `isValid=false` — on se base sur la **seule** colonne `is_valid` (validation admin ; `isSiteValid` ignoré). Jour **sans aucune ligne** → neutre (jamais vert). **Périmètre complet** via `EmployeeRepository::findScoped` (admin = tous sites, chef de site = ses sites), **indépendant du filtre sites** de l'écran. **Scope conducteur inversé** par `?driver=1` : écran Heures → non-conducteurs (défaut), écran Heures Conducteurs → conducteurs (résolu par date via `EmployeeContractResolver::resolveIsDriverForEmployeeAndDate`, mémoïsé ; garde `if ($isDriver !== $driverOnly) continue`). Provider : une requête `WorkHourReadRepositoryInterface::findByDateRangeAndEmployees`, agrégation par jour (`total`/`pending`), plage bornée à 366 j. **Chargement à la volée par mois** (jamais préchargé) : `@month-change {month,year}` (à l'ouverture + nav) → fetch de la **grille visible** (lundi avant le 1er → dimanche après le dernier) → cache `validatedDaysByMonth` (`useHoursPage` / `useDriverHoursPage`, ce dernier passe `{ driver: true }` au service) → `markedDates` réactif. **Rafraîchissement** du mois en cache (`reloadValidationMonth`) après `toggleValidation`/`toggleValidationBulk`/`handleSave`/`refreshAfterAbsenceChange` (pas la validation site). La **vue Semaine** utilise un `MalioDateWeek` (sélecteur de semaine, v-model ISO week `YYYY-Www`, sans coloration). Le **stepper ‹ › du mode jour est remplacé** par le `MalioDate` (raccourcis Hier/Aujourd'hui/Demain conservés) ; `PeriodStepperPicker` reste un fallback de la vue Jour quand `showValidationCalendar` est absent (aucun appelant actuel). Activation par écran via la prop `showValidationCalendar` de `HoursToolbar` (les deux pages la passent à `true` + `markedDates` + `@month-change`). Alignement vertical de la ligne via `lg:items-center` (les champs Malio font `h-12` vs `h-10` des boutons). Doc complète : `doc/hours-validated-days.md`.
|
||||||
- **Export Contingent heures de nuit** (`NightHoursContingentPrintProvider`, endpoint `GET /night-hours-contingent/print?year=YYYY`, `ROLE_USER`) : option « Contingent H.nuit » du drawer Export de la liste employés. PDF **A4 paysage**, lignes = employés **groupés par site** et triés `displayOrder`/nom/prénom (comme le day-export), colonnes = 12 mois civils, chacun avec 2 sous-colonnes **H.nuit** et **N.jours**. Heures de nuit = minutes dans la fenêtre **21h→6h** via le service partagé `App\Service\WorkHours\NightHoursCalculator` (source unique mutualisée avec `WorkHourWeeklySummaryProvider`, `YearlyHoursExportBuilder`, `RttRecoveryComputationService` et `SalaryRecapPrintProvider`). Conducteurs inclus via `WorkHour.nightHoursMinutes`. **N.jours** = nb de jours où minutes de nuit ≥ 240 (4h). **Aucun crédit** absence/férié. Agrégation : `App\Service\WorkHours\NightContingentExportBuilder`. Gabarit `templates/night-hours-contingent/print.html.twig`.
|
- **Export Contingent heures de nuit** (`NightHoursContingentPrintProvider`, endpoint `GET /night-hours-contingent/print?year=YYYY`, `ROLE_USER`) : option « Contingent H.nuit » du drawer Export de la liste employés. PDF **A4 paysage**, lignes = employés **groupés par site** et triés `displayOrder`/nom/prénom (comme le day-export), colonnes = 12 mois civils, chacun avec 2 sous-colonnes **H.nuit** et **N.jours**. Heures de nuit = minutes dans la fenêtre **21h→6h** via le service partagé `App\Service\WorkHours\NightHoursCalculator` (source unique mutualisée avec `WorkHourWeeklySummaryProvider`, `YearlyHoursExportBuilder`, `RttRecoveryComputationService` et `SalaryRecapPrintProvider`). Conducteurs inclus via `WorkHour.nightHoursMinutes`. **N.jours** = nb de jours où minutes de nuit ≥ 240 (4h). **Aucun crédit** absence/férié. Agrégation : `App\Service\WorkHours\NightContingentExportBuilder`. Gabarit `templates/night-hours-contingent/print.html.twig`.
|
||||||
- **Écran Calendrier** : un employé est affiché uniquement si au moins une de ses périodes de contrat (`employee.contractHistory`) intersecte le mois affiché (`[1er ; dernier jour]`). Filtre côté frontend dans `visibleEmployees` (`pages/calendar.vue`). **L'impression PDF des absences applique le même filtre** côté backend (`AbsencePrintProvider::hasContractInRange` sur la période `[from, to]`) : un salarié parti en avril n'apparaît pas sur une impression de mai. **Le récap salaire applique le même filtre** (`SalaryRecapPrintProvider::hasContractInRange` sur le mois imprimé) : un salarié sans contrat sur le mois (ex. parti en février) n'apparaît pas sur le récap de juin.
|
- **Écran Calendrier** : un employé est affiché uniquement si au moins une de ses périodes de contrat (`employee.contractHistory`) intersecte le mois affiché (`[1er ; dernier jour]`). Filtre côté frontend dans `visibleEmployees` (`pages/calendar.vue`). **L'impression PDF des absences applique le même filtre** côté backend (`AbsencePrintProvider::hasContractInRange` sur la période `[from, to]`) : un salarié parti en avril n'apparaît pas sur une impression de mai. **Le récap salaire applique le même filtre** (`SalaryRecapPrintProvider::hasContractInRange` sur le mois imprimé) : un salarié sans contrat sur le mois (ex. parti en février) n'apparaît pas sur le récap de juin.
|
||||||
|
- **Suppression / modification d'une plage de congés** (`calendar.vue`) : une absence = **une ligne par jour** en BDD (aucun lien entre les jours, cf. `expandAbsenceRange`), donc tout se gère côté frontend qui a la plage visible.
|
||||||
|
- **Supprimer** (`handleDelete`) : efface **toutes les absences de l'employé dont le jour tombe dans `[form.startDate ; form.endDate]`** (filtrage sur `absences.value`, boucle `deleteAbsence`), pas seulement le jour cliqué. Flux RH : clic sur un jour → drawer (début = fin) → étendre la date de fin → Supprimer. Jours sans absence ignorés ; jour validé (`isValid`/site) bloque sa propre ligne (backend). Confirmation avec nombre de jours + intervalle (`formatYmdToFr`) dès > 1 jour.
|
||||||
|
- **Modifier** (`handleSubmit`, branche `editingAbsence`) : **remplacement de bloc** — supprime l'**ancien bloc contigu** (jours adjacents de **même type**, en partant du jour cliqué **vers l'avant** via `shiftYmd`) **+** toute absence recouverte par la nouvelle plage, puis **`createAbsence`** sur la nouvelle plage (plus de `PATCH`). Corrige le bug historique : raccourcir ne laisse plus de **jours fantômes**, ré-étendre ne crée plus de **doublons**. Jamais de modification des jours **antérieurs** au jour cliqué ; confirmation « chevauche une autre » seulement si on écrase un **autre type**. La branche **Création** garde sa détection de chevauchement demi-journée (`getSegmentsForDate`).
|
||||||
|
- Backend `AbsenceWriteProcessor` (PATCH) **non touché** : il reste mono-jour en pratique car les écrans Heures/Heures Conducteurs verrouillent les dates du drawer (`lock-dates`), seul le calendrier reshape une plage. `updateAbsence` n'est plus appelé depuis `calendar.vue` (import retiré).
|
||||||
- **Planning jours travaillés** (`EmployeeContractPeriod.workDaysHours` : JSON `{iso_day: minutes}`) : obligatoire pour tout contrat TIME **hors 35h/39h/INTERIM** (ex. 4h, 25h, 28h). Somme = `weeklyHours × 60`. Utilisé par `HolidayVirtualHoursResolver` (crédit férié) et `WorkedHoursCreditPolicy` (crédit absence) pour ne créditer que les jours effectivement travaillés. Validation : `EmployeeContractPeriodValidator::assertWorkDaysHours`.
|
- **Planning jours travaillés** (`EmployeeContractPeriod.workDaysHours` : JSON `{iso_day: minutes}`) : obligatoire pour tout contrat TIME **hors 35h/39h/INTERIM** (ex. 4h, 25h, 28h). Somme = `weeklyHours × 60`. Utilisé par `HolidayVirtualHoursResolver` (crédit férié) et `WorkedHoursCreditPolicy` (crédit absence) pour ne créditer que les jours effectivement travaillés. Validation : `EmployeeContractPeriodValidator::assertWorkDaysHours`.
|
||||||
- Absences: stored per day (auto-split), AM/PM/full day, clear corresponding hour slots
|
- Absences: stored per day (auto-split), AM/PM/full day, clear corresponding hour slots
|
||||||
- Absences with `countAsWorkedHours=true`: credit minutes (TIME) or nothing (PRESENCE)
|
- Absences with `countAsWorkedHours=true`: credit minutes (TIME) or nothing (PRESENCE)
|
||||||
|
|||||||
+1
-1
@@ -1,2 +1,2 @@
|
|||||||
parameters:
|
parameters:
|
||||||
app.version: '0.1.124'
|
app.version: '0.1.125'
|
||||||
|
|||||||
@@ -78,6 +78,17 @@ Documents complementaires:
|
|||||||
- Calendrier congés: fond coloré selon la couleur du type d'absence (`AbsenceType.color`)
|
- Calendrier congés: fond coloré selon la couleur du type d'absence (`AbsenceType.color`)
|
||||||
- demi-journée: dégradé diagonal
|
- demi-journée: dégradé diagonal
|
||||||
- journée complète: fond plein
|
- journée complète: fond plein
|
||||||
|
- Suppression d'une plage depuis le Calendrier:
|
||||||
|
- clic sur un jour d'une plage → le drawer s'ouvre sur ce jour (début = fin = jour cliqué)
|
||||||
|
- on étend la **date de fin** (ou de début) pour couvrir la plage à effacer, puis bouton **Supprimer**
|
||||||
|
- **toutes** les absences de l'employé dont le jour tombe dans la plage sélectionnée sont supprimées (1 ligne/jour en BDD)
|
||||||
|
- les jours de la plage sans absence sont ignorés (aucune erreur) ; un jour validé (`isValid`/site) bloque sa propre suppression
|
||||||
|
- confirmation unique avant suppression ; au-delà de 1 jour le message rappelle le nombre de jours et l'intervalle
|
||||||
|
- Modification d'une plage depuis le Calendrier (bouton **Modifier**):
|
||||||
|
- une absence n'a **aucun lien** entre ses jours en BDD (1 ligne/jour). Modifier réalise donc un **remplacement de bloc** : on supprime l'ancien **bloc contigu** (jours adjacents de **même type**, en partant du jour cliqué **vers l'avant**) puis on **recrée** la nouvelle plage
|
||||||
|
- corrige le bug historique du PATCH : raccourcir une plage ne laisse plus de **jours fantômes** au-delà de la nouvelle fin, et ré-étendre ne crée plus de **doublons**
|
||||||
|
- les jours **antérieurs** au jour cliqué ne sont jamais touchés ; toute absence d'un autre type recouverte par la nouvelle plage déclenche une confirmation « chevauche une autre »
|
||||||
|
- implémenté côté frontend (`calendar.vue::handleSubmit`) car le backend ne peut pas reconstituer le bloc sans identifiant de groupe ; sans danger sur les écrans Heures/Heures Conducteurs où les dates du drawer sont verrouillées (`lock-dates`), donc le PATCH y reste mono-jour
|
||||||
- Visibilité des employés dans le Calendrier:
|
- Visibilité des employés dans le Calendrier:
|
||||||
- un employé est affiché si au moins une de ses périodes de contrat intersecte le mois affiché
|
- un employé est affiché si au moins une de ses périodes de contrat intersecte le mois affiché
|
||||||
- un employé dont toutes les périodes se terminent avant le 1er du mois (ou commencent après la fin du mois) est masqué
|
- un employé dont toutes les périodes se terminent avant le 1er du mois (ou commencent après la fin du mois) est masqué
|
||||||
|
|||||||
@@ -416,6 +416,8 @@ export const documentationSections: DocSection[] = [
|
|||||||
{ type: 'paragraph', content: 'Les absences peuvent être posées depuis la vue jour des heures ou depuis le calendrier.' },
|
{ type: 'paragraph', content: 'Les absences peuvent être posées depuis la vue jour des heures ou depuis le calendrier.' },
|
||||||
{ type: 'list', content: 'Journée complète : efface toutes les plages horaires\nDemi-journée matin (AM) : efface le créneau matin\nDemi-journée après-midi (PM) : efface les créneaux après-midi et soir' },
|
{ type: 'list', content: 'Journée complète : efface toutes les plages horaires\nDemi-journée matin (AM) : efface le créneau matin\nDemi-journée après-midi (PM) : efface les créneaux après-midi et soir' },
|
||||||
{ type: 'paragraph', content: 'Les absences sont stockées par jour : une absence de plusieurs jours est automatiquement découpée en entrées quotidiennes.' },
|
{ type: 'paragraph', content: 'Les absences sont stockées par jour : une absence de plusieurs jours est automatiquement découpée en entrées quotidiennes.' },
|
||||||
|
{ type: 'note', content: 'Supprimer une plage depuis le calendrier : cliquez sur un jour de la plage (le drawer s\'ouvre sur ce jour), étendez la date de fin (ou de début) pour couvrir toute la plage à effacer, puis cliquez sur « Supprimer ». Tous les jours de congé compris dans la plage sélectionnée sont supprimés en une fois ; les jours sans absence dans cette plage sont simplement ignorés. Un jour déjà validé reste protégé.' },
|
||||||
|
{ type: 'note', content: 'Modifier une plage depuis le calendrier : cliquez sur le premier jour de la plage, ajustez la date de fin (pour la raccourcir ou l\'allonger) puis « Modifier ». La plage est remplacée proprement par la nouvelle : plus de jours « fantômes » qui restaient après un raccourcissement, ni de doublons après un allongement. Les jours situés avant le jour cliqué ne sont jamais modifiés.' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
+93
-19
@@ -109,11 +109,11 @@ import type {HalfDay} from '~/services/dto/half-day'
|
|||||||
import {HALF_DAYS} from '~/services/dto/half-day'
|
import {HALF_DAYS} from '~/services/dto/half-day'
|
||||||
import {listEmployees, updateEmployeeOrder} from '~/services/employees'
|
import {listEmployees, updateEmployeeOrder} from '~/services/employees'
|
||||||
import {listAbsenceTypes} from '~/services/absence-types'
|
import {listAbsenceTypes} from '~/services/absence-types'
|
||||||
import {createAbsence, deleteAbsence, listAbsences, updateAbsence} from '~/services/absences'
|
import {createAbsence, deleteAbsence, listAbsences} from '~/services/absences'
|
||||||
import {listFormationsByDateRange} from '~/services/formations'
|
import {listFormationsByDateRange} from '~/services/formations'
|
||||||
import type {Formation} from '~/services/dto/formation'
|
import type {Formation} from '~/services/dto/formation'
|
||||||
import {listPublicHolidays} from '~/services/public-holidays'
|
import {listPublicHolidays} from '~/services/public-holidays'
|
||||||
import {getDaysInMonth, normalizeDate, parseYmd, toYmd} from '~/utils/date'
|
import {formatYmdToFr, getDaysInMonth, normalizeDate, parseYmd, shiftYmd, toYmd} from '~/utils/date'
|
||||||
import {compareEmployeesInSite, sortEmployeesBySiteAndOrder} from '~/utils/employee'
|
import {compareEmployeesInSite, sortEmployeesBySiteAndOrder} from '~/utils/employee'
|
||||||
import CalendarGrid from '~/components/CalendarGrid.vue'
|
import CalendarGrid from '~/components/CalendarGrid.vue'
|
||||||
import AbsenceFormDrawer from '~/components/AbsenceFormDrawer.vue'
|
import AbsenceFormDrawer from '~/components/AbsenceFormDrawer.vue'
|
||||||
@@ -649,9 +649,68 @@ const handleSubmit = async () => {
|
|||||||
window.alert("La demi-journee de fin ne peut pas etre avant la demi-journee de debut.")
|
window.alert("La demi-journee de fin ne peut pas etre avant la demi-journee de debut.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (editingAbsence.value) {
|
||||||
|
// Modification d'une plage : une absence = une ligne par jour, sans lien en BDD.
|
||||||
|
// On remplace donc tout le bloc contigu (même type) partant du jour cliqué par la
|
||||||
|
// nouvelle plage : suppression de l'ancien bloc + recréation. Évite les jours
|
||||||
|
// fantômes (raccourcissement) et les doublons (l'ancien PATCH ne nettoyait rien).
|
||||||
|
const originalEmployeeId = editingAbsence.value.employee.id
|
||||||
|
const newEmployeeId = Number(form.employeeId)
|
||||||
|
const originalTypeId = editingAbsence.value.type.id
|
||||||
|
const clickedDate = normalizeDate(editingAbsence.value.startDate)
|
||||||
|
|
||||||
|
// Bloc contigu (vers l'avant) depuis le jour cliqué, même employé + même type d'origine.
|
||||||
|
// On ne touche jamais aux jours antérieurs au jour cliqué.
|
||||||
|
const sameLeaveDays = new Set(
|
||||||
|
absences.value
|
||||||
|
.filter((absence) => absence.employee?.id === originalEmployeeId && absence.type?.id === originalTypeId)
|
||||||
|
.map((absence) => normalizeDate(absence.startDate))
|
||||||
|
)
|
||||||
|
const blockDates = new Set<string>()
|
||||||
|
let cursor: string | null = clickedDate
|
||||||
|
while (cursor && sameLeaveDays.has(cursor)) {
|
||||||
|
blockDates.add(cursor)
|
||||||
|
cursor = shiftYmd(cursor, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// À supprimer : l'ancien bloc + toute absence recouverte par la nouvelle plage.
|
||||||
|
const toReplace = absences.value.filter((absence) => {
|
||||||
|
const day = normalizeDate(absence.startDate)
|
||||||
|
const inBlock = absence.employee?.id === originalEmployeeId && blockDates.has(day)
|
||||||
|
const inNewRange = absence.employee?.id === newEmployeeId && day >= start && day <= end
|
||||||
|
return inBlock || inNewRange
|
||||||
|
})
|
||||||
|
|
||||||
|
// Confirmation uniquement si on écrase une absence d'un AUTRE type (vrai chevauchement).
|
||||||
|
const replacesForeign = toReplace.some((absence) => absence.type?.id !== originalTypeId)
|
||||||
|
if (replacesForeign) {
|
||||||
|
const confirmReplace = window.confirm(
|
||||||
|
"Cette absence chevauche une autre. Voulez-vous la remplacer ?"
|
||||||
|
)
|
||||||
|
if (!confirmReplace) return
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const absence of toReplace) {
|
||||||
|
await deleteAbsence(absence.id)
|
||||||
|
}
|
||||||
|
await createAbsence({
|
||||||
|
employeeId: newEmployeeId,
|
||||||
|
typeId: Number(form.typeId),
|
||||||
|
startDate: form.startDate,
|
||||||
|
startHalf: form.startHalf,
|
||||||
|
endDate: form.endDate,
|
||||||
|
endHalf: form.endHalf,
|
||||||
|
comment: form.comment
|
||||||
|
})
|
||||||
|
|
||||||
|
closeDrawer()
|
||||||
|
await loadAbsences()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Création : détection de chevauchement (précision demi-journée) puis remplacement.
|
||||||
const overlaps = absences.value.filter((absence) => {
|
const overlaps = absences.value.filter((absence) => {
|
||||||
if (absence.employee?.id !== Number(form.employeeId)) return false
|
if (absence.employee?.id !== Number(form.employeeId)) return false
|
||||||
if (editingAbsence.value && absence.id === editingAbsence.value.id) return false
|
|
||||||
const aStart = normalizeDate(absence.startDate)
|
const aStart = normalizeDate(absence.startDate)
|
||||||
const aEnd = normalizeDate(absence.endDate)
|
const aEnd = normalizeDate(absence.endDate)
|
||||||
if (start > aEnd || end < aStart) return false
|
if (start > aEnd || end < aStart) return false
|
||||||
@@ -701,18 +760,6 @@ const handleSubmit = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (editingAbsence.value) {
|
|
||||||
await updateAbsence({
|
|
||||||
id: editingAbsence.value.id,
|
|
||||||
employeeId: Number(form.employeeId),
|
|
||||||
typeId: Number(form.typeId),
|
|
||||||
startDate: form.startDate,
|
|
||||||
startHalf: form.startHalf,
|
|
||||||
endDate: form.endDate,
|
|
||||||
endHalf: form.endHalf,
|
|
||||||
comment: form.comment
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
await createAbsence({
|
await createAbsence({
|
||||||
employeeId: Number(form.employeeId),
|
employeeId: Number(form.employeeId),
|
||||||
typeId: Number(form.typeId),
|
typeId: Number(form.typeId),
|
||||||
@@ -722,7 +769,6 @@ const handleSubmit = async () => {
|
|||||||
endHalf: form.endHalf,
|
endHalf: form.endHalf,
|
||||||
comment: form.comment
|
comment: form.comment
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
closeDrawer()
|
closeDrawer()
|
||||||
await loadAbsences()
|
await loadAbsences()
|
||||||
@@ -731,14 +777,42 @@ const handleSubmit = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Suppression de l'absence en cours d'édition.
|
// Suppression: efface toutes les absences de l'employé comprises dans la plage
|
||||||
|
// sélectionnée (date début → date fin du drawer). Comme une absence = une ligne
|
||||||
|
// par jour en BDD, on supprime chaque jour existant de la plage ; les jours sans
|
||||||
|
// absence (ex. une date hors plage réelle) sont naturellement ignorés.
|
||||||
const handleDelete = async () => {
|
const handleDelete = async () => {
|
||||||
if (!editingAbsence.value) return
|
if (!editingAbsence.value) return
|
||||||
|
|
||||||
const confirmDelete = window.confirm('Supprimer cette absence ?')
|
const employeeId = editingAbsence.value.employee.id
|
||||||
|
const rangeStart = normalizeDate(form.startDate)
|
||||||
|
const rangeEnd = normalizeDate(form.endDate)
|
||||||
|
if (rangeStart > rangeEnd) {
|
||||||
|
window.alert("La date de fin ne peut pas etre avant la date de debut.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const toDelete = absences.value.filter((absence) => {
|
||||||
|
if (absence.employee?.id !== employeeId) return false
|
||||||
|
const day = normalizeDate(absence.startDate)
|
||||||
|
return day >= rangeStart && day <= rangeEnd
|
||||||
|
})
|
||||||
|
|
||||||
|
if (toDelete.length === 0) {
|
||||||
|
closeDrawer()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmDelete = window.confirm(
|
||||||
|
toDelete.length === 1
|
||||||
|
? 'Supprimer cette absence ?'
|
||||||
|
: `Supprimer ${toDelete.length} jours de congé du ${formatYmdToFr(rangeStart)} au ${formatYmdToFr(rangeEnd)} ?`
|
||||||
|
)
|
||||||
if (!confirmDelete) return
|
if (!confirmDelete) return
|
||||||
|
|
||||||
await deleteAbsence(editingAbsence.value.id)
|
for (const absence of toDelete) {
|
||||||
|
await deleteAbsence(absence.id)
|
||||||
|
}
|
||||||
closeDrawer()
|
closeDrawer()
|
||||||
await loadAbsences()
|
await loadAbsences()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user