Roster: la garde ne compte PAS comme heures travaillées (mise en dispo)
Garde (on_call) exclue partout des heures travaillées + du coût:
- hub statsByDay: heures = somme des shifts NON-garde ; nouveau compteur on_call/jour (techs en dispo).
- Ops: hoursOf (heures/tech + alerte heures supp) et costByDate/weekCost excluent la garde.
- nouvelle ligne de pied '🛡️ Garde' = nb de techs en disponibilité/jour (si applicable).
Cohérent avec l'occupation (déjà hors-garde) : la garde = réserve d'urgence, ni offerte ni facturée.
Vérifié 8 juin: 112 h travaillées (garde 6 h exclue), garde=1.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
72845e2057
commit
17d8442b98
|
|
@ -137,6 +137,7 @@
|
|||
<tfoot>
|
||||
<tr class="sum"><td class="tech-col">👥 Effectif</td><td v-for="d in dayList" :key="d.iso" :class="{ weekend: d.weekend }">{{ stat(d.iso).staff || '' }}</td></tr>
|
||||
<tr class="sum"><td class="tech-col">⏱ Heures</td><td v-for="d in dayList" :key="d.iso" :class="{ weekend: d.weekend }">{{ stat(d.iso).hours || '' }}</td></tr>
|
||||
<tr v-if="hasOnCall" class="sum oncall-row"><td class="tech-col">🛡️ Garde</td><td v-for="d in dayList" :key="d.iso" :class="{ weekend: d.weekend }">{{ stat(d.iso).on_call || '' }}</td></tr>
|
||||
<tr class="sum"><td class="tech-col">🎫 Tickets</td><td v-for="d in dayList" :key="d.iso" :class="{ weekend: d.weekend }">{{ stat(d.iso).tickets || '' }}</td></tr>
|
||||
<tr v-if="weekCost" class="sum"><td class="tech-col">💲 Coût ({{ Math.round(weekCost) }} $/sem)</td><td v-for="d in dayList" :key="d.iso" :class="{ weekend: d.weekend }">{{ dayCost(d.iso) || '' }}</td></tr>
|
||||
</tfoot>
|
||||
|
|
@ -339,7 +340,7 @@ const visibleTechs = computed(() => {
|
|||
const cellsByTechDay = computed(() => { const m = {}; for (const a of assignments.value) { const t = (m[a.tech] || (m[a.tech] = {})); (t[a.date] || (t[a.date] = [])).push(a) } return m })
|
||||
function cellsOf (techId, iso) { return (cellsByTechDay.value[techId] && cellsByTechDay.value[techId][iso]) || [] }
|
||||
function isPaused (t) { return t.status === 'En pause' }
|
||||
function hoursOf (techId) { let h = 0; for (const a of assignments.value) if (a.tech === techId) h += Number(a.hours) || 0; return h }
|
||||
function hoursOf (techId) { let h = 0; for (const a of assignments.value) { if (a.tech !== techId) continue; const t = tplByName.value[a.shift]; if (t && t.on_call) continue; h += Number(a.hours) || 0 } return h } // garde exclue (mise en dispo, pas travaillée)
|
||||
|
||||
const serverSet = ref(new Set())
|
||||
const currentSet = computed(() => new Set(assignments.value.map(a => a.tech + '|' + a.date + '|' + a.shift)))
|
||||
|
|
@ -357,6 +358,7 @@ function isSelected (techId, iso) { return selSet.value.has(techId + '|' + iso)
|
|||
|
||||
const statByDate = computed(() => Object.fromEntries(dailyStats.value.map(s => [s.date, s])))
|
||||
function stat (iso) { return statByDate.value[iso] || {} }
|
||||
const hasOnCall = computed(() => dailyStats.value.some(s => s.on_call > 0))
|
||||
|
||||
// Micro-timeline 24 h par cellule : fenêtre(s) du shift = bande neutre, jobs pris = trait coloré.
|
||||
const occByTechDay = ref({})
|
||||
|
|
@ -407,7 +409,7 @@ function cellInterval (techId, iso) {
|
|||
|
||||
// coût de main-d'œuvre (coût chargé × heures)
|
||||
const costByTech = computed(() => Object.fromEntries(techs.value.map(t => [t.id, t.cost_h || 0])))
|
||||
const costByDate = computed(() => { const m = {}; for (const a of assignments.value) m[a.date] = (m[a.date] || 0) + (Number(a.hours) || 0) * (costByTech.value[a.tech] || 0); return m })
|
||||
const costByDate = computed(() => { const m = {}; for (const a of assignments.value) { const t = tplByName.value[a.shift]; if (t && t.on_call) continue; m[a.date] = (m[a.date] || 0) + (Number(a.hours) || 0) * (costByTech.value[a.tech] || 0) } return m }) // garde exclue du coût de main-d'œuvre
|
||||
const weekCost = computed(() => Object.values(costByDate.value).reduce((s, v) => s + v, 0))
|
||||
function dayCost (iso) { return Math.round(costByDate.value[iso] || 0) }
|
||||
|
||||
|
|
|
|||
|
|
@ -449,19 +449,25 @@ async function handlePublicBooking (req, res, method, path, url) {
|
|||
return json(res, 404, { error: 'not found' })
|
||||
}
|
||||
|
||||
// Stats par jour : effectif (techs distincts), heures planifiées, tickets dispatch.
|
||||
// Stats par jour : effectif (techs distincts), heures TRAVAILLÉES, tickets dispatch.
|
||||
// La garde (on_call) = mise en disponibilité → exclue des heures travaillées.
|
||||
async function statsByDay (start, days) {
|
||||
const dates = rangeDates(start, days)
|
||||
const asgs = await fetchAssignments(start, days)
|
||||
const templates = await fetchTemplates()
|
||||
const onCall = new Set(templates.filter(t => t.on_call).map(t => t.name))
|
||||
const jobs = await erp.list('Dispatch Job', {
|
||||
filters: [['scheduled_date', 'in', dates]],
|
||||
fields: ['name', 'scheduled_date'], limit: 3000,
|
||||
})
|
||||
const by = {}
|
||||
for (const d of dates) by[d] = { date: d, staff: new Set(), hours: 0, tickets: 0 }
|
||||
for (const a of asgs) { if (a.status === 'Annulé') continue; const x = by[a.date]; if (x) { x.staff.add(a.tech); x.hours += Number(a.hours) || 0 } }
|
||||
for (const d of dates) by[d] = { date: d, staff: new Set(), hours: 0, oncall: new Set(), tickets: 0 }
|
||||
for (const a of asgs) {
|
||||
if (a.status === 'Annulé') continue; const x = by[a.date]; if (!x) continue
|
||||
if (onCall.has(a.shift)) { x.oncall.add(a.tech) } else { x.staff.add(a.tech); x.hours += Number(a.hours) || 0 }
|
||||
}
|
||||
for (const j of jobs) { const x = by[j.scheduled_date]; if (x) x.tickets++ }
|
||||
return dates.map(d => ({ date: d, staff: by[d].staff.size, hours: by[d].hours, tickets: by[d].tickets }))
|
||||
return dates.map(d => ({ date: d, staff: by[d].staff.size, hours: by[d].hours, on_call: by[d].oncall.size, tickets: by[d].tickets }))
|
||||
}
|
||||
|
||||
// Occupation par (technicien, jour) : Σ heures + blocs horaires des Dispatch Jobs planifiés.
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user