feat(calendar) : suppression et modification d'une plage de congés d'un coup
Sur le calendrier, une absence est stockée une ligne par jour sans lien entre les jours. La suppression et la modification n'agissaient donc que sur le jour cliqué. - Supprimer (handleDelete) : efface toutes les absences de l'employé comprises dans la plage [début ; fin] du drawer (jours sans absence ignorés, jour validé protégé côté backend). - Modifier (handleSubmit) : remplacement de bloc — supprime l'ancien bloc contigu de même type (vers l'avant depuis le jour cliqué) + les absences recouvertes par la nouvelle plage, puis recrée la plage. Corrige le bug du PATCH qui laissait des jours fantômes (raccourcissement) et des doublons (allongement). updateAbsence n'est plus utilisé sur le calendrier. Backend AbsenceWriteProcessor non touché : les écrans Heures verrouillent les dates du drawer, le PATCH y reste mono-jour. Doc : functional-rules.md, documentation-content.ts (in-app), CLAUDE.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+102
-28
@@ -109,11 +109,11 @@ import type {HalfDay} from '~/services/dto/half-day'
|
||||
import {HALF_DAYS} from '~/services/dto/half-day'
|
||||
import {listEmployees, updateEmployeeOrder} from '~/services/employees'
|
||||
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 type {Formation} from '~/services/dto/formation'
|
||||
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 CalendarGrid from '~/components/CalendarGrid.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.")
|
||||
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) => {
|
||||
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 aEnd = normalizeDate(absence.endDate)
|
||||
if (start > aEnd || end < aStart) return false
|
||||
@@ -701,28 +760,15 @@ 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({
|
||||
employeeId: Number(form.employeeId),
|
||||
typeId: Number(form.typeId),
|
||||
startDate: form.startDate,
|
||||
startHalf: form.startHalf,
|
||||
endDate: form.endDate,
|
||||
endHalf: form.endHalf,
|
||||
comment: form.comment
|
||||
})
|
||||
}
|
||||
await createAbsence({
|
||||
employeeId: Number(form.employeeId),
|
||||
typeId: Number(form.typeId),
|
||||
startDate: form.startDate,
|
||||
startHalf: form.startHalf,
|
||||
endDate: form.endDate,
|
||||
endHalf: form.endHalf,
|
||||
comment: form.comment
|
||||
})
|
||||
|
||||
closeDrawer()
|
||||
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 () => {
|
||||
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
|
||||
|
||||
await deleteAbsence(editingAbsence.value.id)
|
||||
for (const absence of toDelete) {
|
||||
await deleteAbsence(absence.id)
|
||||
}
|
||||
closeDrawer()
|
||||
await loadAbsences()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user