fix(ops): slot finder respects untimed load (humanly possible)
suggestSlots built gaps only from timed jobs, so a tech loaded with untimed (no start_time) legacy jobs still looked free and got offered slots. Now the day is skipped when timed + untimed committed hours + the requested duration exceed the shift. Also matches assigned_tech by technician_id OR docname. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
7df6ad30e3
commit
04fb52426f
|
|
@ -207,12 +207,12 @@ async function suggestSlots ({ duration_h = 1, latitude, longitude, after_date,
|
|||
const shift = winBy[tech.technician_id + '|' + dateStr] || winBy[tech.name + '|' + dateStr]
|
||||
if (!shift) continue
|
||||
|
||||
// Day's pinned jobs (only those with a real start_time — floating jobs
|
||||
// without a time are ignored since we don't know when they'll land).
|
||||
// Day's pinned jobs (only those with a real start_time) — servent à découper les trous.
|
||||
// Match par technician_id OU docname (les jobs legacy portent parfois l'un ou l'autre).
|
||||
const belongs = (j) => (j.assigned_tech === tech.technician_id || j.assigned_tech === tech.name) &&
|
||||
j.scheduled_date === dateStr && !(ignoreReserved && j.job_type === 'Réservation')
|
||||
const dayJobs = allJobs
|
||||
.filter(j => j.assigned_tech === tech.technician_id &&
|
||||
j.scheduled_date === dateStr && j.start_time &&
|
||||
!(ignoreReserved && j.job_type === 'Réservation')) // mode urgence/réparation → les blocs RÉSERVÉS (soft) ne bloquent pas
|
||||
.filter(j => belongs(j) && j.start_time)
|
||||
.map(j => {
|
||||
const s = timeToHours(j.start_time)
|
||||
return {
|
||||
|
|
@ -223,6 +223,16 @@ async function suggestSlots ({ duration_h = 1, latitude, longitude, after_date,
|
|||
})
|
||||
.sort((a, b) => a.start_h - b.start_h)
|
||||
|
||||
// « Humainement possible » : un tech ne peut pas dépasser son quart. Les jobs assignés SANS heure fixée
|
||||
// (legacy osTicket) occupent quand même la journée → on retranche leur charge + celle déjà placée ; si la
|
||||
// capacité restante ne suffit pas pour cette durée, PAS de créneau ce jour (sinon on suroffre un tech déjà plein).
|
||||
const shiftH = shift.end_h - shift.start_h
|
||||
const clampD = (j) => Math.max(0, Math.min(j.end_h, shift.end_h) - Math.max(j.start_h, shift.start_h))
|
||||
const timedBusy = dayJobs.reduce((a, j) => a + clampD(j), 0)
|
||||
const untimedLoad = allJobs.filter(j => belongs(j) && !j.start_time)
|
||||
.reduce((a, j) => a + (parseFloat(j.duration_h) || 1), 0)
|
||||
if (timedBusy + untimedLoad + duration > shiftH + 0.01) continue
|
||||
|
||||
// Build gaps bounded by shift_start/end (shift = quart réel du tech ce jour, résolu ci-dessus).
|
||||
const gaps = []
|
||||
let cursor = shift.start_h, prevCoords = homeCoords
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user