- {{ pubSummary.added }} quart(s) · {{ pubSummary.absAdded }} congé(s) · {{ pubSummary.absRemoved }} congé(s) retiré(s) · {{ notifySms ? pubSummary.added + ' SMS aux techs' : 'sans SMS' }}
+ {{ pubSummary.added }} quart(s) · {{ pubSummary.removed }} retiré(s) · {{ pubSummary.absAdded }} congé(s) · {{ pubSummary.absRemoved }} congé(s) levé(s) · {{ notifySms ? pubSummary.added + ' SMS aux techs' : 'sans SMS' }}
-
- {{ fmtDueLabel(d.date) }}
- {{ a.name }} · {{ a.shift }}
- {{ r.name }} · {{ r.shift }}
- {{ ab.name }} · {{ ab.op === 'set' ? (ab.type || 'Congé') : 'congé retiré' }}
+
+
+ {{ g.name }}
+
+ {{ a.shift }} · {{ fmtDueLabel(a.date) }}
+ {{ r.shift }} · {{ fmtDueLabel(r.date) }}
+ {{ ab.op === 'set' ? (ab.type || 'Congé') : 'congé levé' }} · {{ fmtDueLabel(ab.date) }}
@@ -4001,23 +4003,38 @@ function isCellDirty (techId, iso) { return dirtyCells.value.has(techId + '|' +
const weekStatus = computed(() => { const st = new Set(unpublished.value.map(a => a.status)); if (st.has('Proposé')) return 'Proposé'; if (st.has('Soumis')) return 'Soumis'; if (st.has('Approuvé')) return 'Approuvé'; return 'Publié' })
// #1 — Sommaire AVANT publication : liste les quarts NON PUBLIÉS (à publier) par jour + nb SMS.
const pubConfirm = ref(false)
+// RÉSUMÉ DE PUBLICATION — regroupé PAR TECHNICIEN. Doit refléter TOUT ce que « Publier » va changer (« au cas où on
+// se trompe ») : quarts à publier (ajouts/modifs), quarts RETIRÉS (le hub supprime tout ce qui n'est plus dans la
+// charge — cf. roster.js publish-week), et congés/absences en attente. Le retrait est calculé contre l'instantané
+// SERVEUR pris au chargement (serverSet) → capte aussi une suppression accidentelle.
const pubSummary = computed(() => {
const nameOf = id => { const t = (techs.value || []).find(x => x.id === id); return (t && t.name) || id }
- const byDate = {}
- const bucket = date => (byDate[date] = byDate[date] || { date, add: [], rem: [], abs: [] })
- for (const a of unpublished.value) {
- bucket(a.date).add.push({ tech: a.tech, date: a.date, shift: a.shift_name || a.shift, name: nameOf(a.tech), status: a.status })
+ const shiftLabel = s => { const t = tplByName.value[s]; return (t && t.template_name) || s }
+ const byTech = {}
+ const bucket = tid => (byTech[tid] = byTech[tid] || { tech: tid, name: nameOf(tid), add: [], rem: [], abs: [] })
+ // Quarts à publier = état courant non Publié (Proposé/Soumis/Approuvé → deviendront Publié)
+ for (const a of unpublished.value) bucket(a.tech).add.push({ date: a.date, shift: a.shift_name || shiftLabel(a.shift) })
+ // Quarts RETIRÉS = présents au chargement mais plus dans l'état courant → seront supprimés au Publier
+ let removed = 0
+ const cur = currentSet.value
+ for (const k of serverSet.value) {
+ if (cur.has(k)) continue
+ const i1 = k.indexOf('|'); const i2 = k.indexOf('|', i1 + 1); if (i1 < 0 || i2 < 0) continue
+ bucket(k.slice(0, i1)).rem.push({ date: k.slice(i1 + 1, i2), shift: shiftLabel(k.slice(i2 + 1)) }); removed++
}
- // CONGÉS / ABSENCES en attente (pendingAbs) : ils comptent dans « non publié » et sont bien envoyés au Publier
- // (flushPendingAbsences) — donc ils DOIVENT figurer dans le résumé (sinon un congé indéfini reste invisible, ex. Josée-Anne).
+ // Congés / absences en attente (pendingAbs) — bien envoyés au Publier (flushPendingAbsences) → doivent figurer (ex. Josée-Anne).
let absAdded = 0; let absRemoved = 0
- for (const [k, v] of Object.entries(pendingAbs.value || {})) {
- const [tid, iso] = k.split('|')
+ for (const [key, v] of Object.entries(pendingAbs.value || {})) {
+ const i1 = key.indexOf('|'); if (i1 < 0) continue
const set = v && v.op === 'set'
- bucket(iso).abs.push({ tech: tid, date: iso, name: nameOf(tid), type: set ? (v.type || 'Congé') : null, op: set ? 'set' : 'remove' })
+ bucket(key.slice(0, i1)).abs.push({ date: key.slice(i1 + 1), type: set ? (v.type || 'Congé') : null, op: set ? 'set' : 'remove' })
if (set) absAdded++; else absRemoved++
}
- return { added: unpublished.value.length, removed: 0, absAdded, absRemoved, days: Object.values(byDate).sort((x, y) => String(x.date).localeCompare(String(y.date))) }
+ const list = Object.values(byTech)
+ const byDate = (x, y) => String(x.date).localeCompare(String(y.date))
+ for (const g of list) { g.add.sort(byDate); g.rem.sort(byDate); g.abs.sort(byDate) }
+ list.sort((a, b) => String(a.name).localeCompare(String(b.name)))
+ return { added: unpublished.value.length, removed, absAdded, absRemoved, techs: list }
})
const holSet = computed(() => new Set(holidays.value))