From 16325ed96773f22905daae2effedf0da2cd0e09b Mon Sep 17 00:00:00 2001 From: louispaulb Date: Thu, 4 Jun 2026 21:17:25 -0400 Subject: [PATCH] =?UTF-8?q?Garde:=20la=20rotation=20avance=20par=20jour=20?= =?UTF-8?q?de=20garde=20(la=20s=C3=A9quence=20continue=20sur=20les=20jours?= =?UTF-8?q?=20affich=C3=A9s)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Avant: rotation par semaine → dans une vue d'1 semaine, un seul membre apparaissait. Maintenant la rotation avance par OCCURRENCE de garde (occurrenceIndex = nb de jours matching weekdays depuis l'époque) → chaque jour de garde prend le membre suivant de la séquence (A,A,B,C…), visible sur toute la vue. Sélecteur « Rotation : par jour de garde / par semaine » (+ N consécutifs). Défaut = par jour ; les règles existantes (sans unit) basculent en par-jour. Saut d'absent + doublons conservés. Co-Authored-By: Claude Opus 4.8 (1M context) --- apps/ops/src/pages/PlanificationPage.vue | 25 +++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/apps/ops/src/pages/PlanificationPage.vue b/apps/ops/src/pages/PlanificationPage.vue index bf18eb1..62430c8 100644 --- a/apps/ops/src/pages/PlanificationPage.vue +++ b/apps/ops/src/pages/PlanificationPage.vue @@ -270,7 +270,7 @@ {{ r.dept }} · {{ shiftName(r.shift) }} - {{ gardeDowLabel(r) }} · toutes les {{ r.periodWeeks }} sem. · rotation : {{ r.techs.map(id => (techs.find(t => t.id === id) || {}).name || id).join(' → ') }} + {{ gardeDowLabel(r) }} · change tous les {{ r.periodWeeks }} {{ (r.unit || 'occ') === 'week' ? 'sem.' : 'jour(s) de garde' }} · rotation : {{ r.techs.map(id => (techs.find(t => t.id === id) || {}).name || id).join(' → ') }} Modifier (ordre, techs, période) @@ -282,7 +282,8 @@
- 2 = chaque tech fait 2 semaines de suite avant de passer au suivant + + Occurrences consécutives avant de passer au tech suivant (1 = change à chaque {{ newGardeRule.unit === 'week' ? 'semaine' : 'jour de garde' }})
Jours : @@ -378,7 +379,7 @@ const activeCell = ref(null) // dernière case cliquée {id, name, iso} — pour const anchor = ref(null) const demand = ref([]); const holidays = ref([]); const weekTemplates = ref([]) const gardeRules = ref([]); const showGarde = ref(false) -const newGardeRule = reactive({ dept: '', shift: '', weekdays: [], periodWeeks: 1, techs: [] }) +const newGardeRule = reactive({ dept: '', shift: '', weekdays: [], periodWeeks: 1, unit: 'occ', techs: [] }) const GARDE_DOW = [{ v: 1, l: 'L' }, { v: 2, l: 'M' }, { v: 3, l: 'M' }, { v: 4, l: 'J' }, { v: 5, l: 'V' }, { v: 6, l: 'S' }, { v: 0, l: 'D' }] const history = ref([]); const future = ref([]) const search = ref(''); const groupFilter = ref(null); const maxHours = ref(40) @@ -610,14 +611,24 @@ const groupNames = computed(() => [...new Set(techs.value.map(t => t.group).filt const editingGardeId = ref(null); const gardePick = ref(null) function addTechToSeq () { if (gardePick.value) { newGardeRule.techs.push(gardePick.value); gardePick.value = null } } // doublons permis (tours inégaux) function moveTech (i, dir) { const a = newGardeRule.techs; const j = i + dir; if (j < 0 || j >= a.length) return; const x = a[i]; a.splice(i, 1); a.splice(j, 0, x) } -function editGardeRule (r) { Object.assign(newGardeRule, { dept: r.dept || '', shift: r.shift, weekdays: [...r.weekdays], periodWeeks: r.periodWeeks || 1, techs: [...r.techs] }); editingGardeId.value = r.id } +function editGardeRule (r) { Object.assign(newGardeRule, { dept: r.dept || '', shift: r.shift, weekdays: [...r.weekdays], periodWeeks: r.periodWeeks || 1, unit: r.unit || 'occ', techs: [...r.techs] }); editingGardeId.value = r.id } function d2ms (iso) { const a = iso.split('-').map(Number); return Date.UTC(a[0], a[1] - 1, a[2]) } function mondayISO (iso) { return addDaysISO(iso, -((dowOf(iso) + 6) % 7)) } function weekIndex (iso) { return Math.round((d2ms(mondayISO(iso)) - d2ms(GARDE_EPOCH)) / (7 * 86400000)) } -// Tech de garde pour une date : tourne toutes les periodWeeks ; saute un tech absent au profit du suivant. +// Nb de jours de garde (matching weekdays) depuis l'époque, AVANT cette date → fait avancer la séquence jour par jour. +function occurrenceIndex (rule, iso) { + const days = Math.round((d2ms(iso) - d2ms(GARDE_EPOCH)) / 86400000); if (days <= 0) return 0 + const wd = rule.weekdays || []; let cnt = Math.floor(days / 7) * wd.length + const epochDow = dowOf(GARDE_EPOCH) + for (let i = 0; i < days % 7; i++) if (wd.includes((epochDow + i) % 7)) cnt++ + return cnt +} +// Index de rotation : par OCCURRENCE de garde (défaut → la séquence continue chaque jour de garde) ou par SEMAINE (bloc). +function gardeIndex (rule, iso) { return Math.floor(((rule.unit || 'occ') === 'week' ? weekIndex(iso) : occurrenceIndex(rule, iso)) / (rule.periodWeeks || 1)) } +// Tech de garde pour une date ; saute un tech absent au profit du suivant. function rotationTech (rule, iso) { if (!rule.techs || !rule.techs.length) return null - const base = Math.floor(weekIndex(iso) / (rule.periodWeeks || 1)) + const base = gardeIndex(rule, iso) for (let k = 0; k < rule.techs.length; k++) { const id = rule.techs[((base + k) % rule.techs.length + rule.techs.length) % rule.techs.length]; if (!isAbsent(id, iso)) return id } return rule.techs[((base % rule.techs.length) + rule.techs.length) % rule.techs.length] } @@ -625,7 +636,7 @@ function toggleGardeDow (v) { const i = newGardeRule.weekdays.indexOf(v); if (i function saveGarde () { localStorage.setItem(LS_GARDE, JSON.stringify(gardeRules.value)) } function addGardeRule () { if (!newGardeRule.shift || !newGardeRule.techs.length || !newGardeRule.weekdays.length) { $q.notify({ type: 'warning', message: 'Shift, jours et techs requis (département optionnel)' }); return } - const rule = { id: editingGardeId.value || Date.now(), dept: newGardeRule.dept || '—', shift: newGardeRule.shift, weekdays: [...newGardeRule.weekdays], periodWeeks: newGardeRule.periodWeeks || 1, techs: [...newGardeRule.techs] } + const rule = { id: editingGardeId.value || Date.now(), dept: newGardeRule.dept || '—', shift: newGardeRule.shift, weekdays: [...newGardeRule.weekdays], periodWeeks: newGardeRule.periodWeeks || 1, unit: newGardeRule.unit || 'occ', techs: [...newGardeRule.techs] } if (editingGardeId.value) gardeRules.value = gardeRules.value.map(r => r.id === editingGardeId.value ? rule : r) else gardeRules.value = [...gardeRules.value, rule] saveGarde(); editingGardeId.value = null; newGardeRule.techs = []; newGardeRule.weekdays = []