planif: Suggérer ne fige plus — timeout solveur + dialogue toujours fermable

- roster API : AbortController (timeout 45s, 90s pour /optimize-plan) → un solveur/hub lent ou
  injoignable lève au lieu de laisser l'await pendre à l'infini (suggestDlg.building coincé à true).
  Au-delà → repli heuristique local, dialogue utilisable.
- q-dialog Suggérer : @hide remet building=false (jamais coincé-ouvert au ré-open).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
louispaulb 2026-07-07 09:56:12 -04:00
parent 77dd426343
commit 8586b9310a
2 changed files with 18 additions and 20 deletions

View File

@ -5,25 +5,23 @@
*/
import { HUB_URL as HUB } from 'src/config/hub'
async function jget (path) {
const r = await fetch(HUB + path)
if (!r.ok) throw new Error('Roster API ' + r.status)
return r.json()
}
async function jpost (path, body) {
const r = await fetch(HUB + path, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body || {}),
})
if (!r.ok) throw new Error('Roster API ' + r.status)
return r.json()
}
async function jput (path, body) {
const r = await fetch(HUB + path, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body || {}) })
if (!r.ok) throw new Error('Roster API ' + r.status)
return r.json()
// Toute requête a un TIMEOUT (AbortController) : un solveur/hub lent ou injoignable ne doit JAMAIS
// laisser l'interface figée (ex. « Suggérer » qui tourne sans fin). Au-delà, on lève → repli local.
async function _fetch (path, init = {}, ms = 45000) {
const ctl = new AbortController()
const timer = setTimeout(() => ctl.abort(), ms)
try {
const r = await fetch(HUB + path, { ...init, signal: ctl.signal })
if (!r.ok) throw new Error('Roster API ' + r.status)
return await r.json()
} catch (e) {
if (e && e.name === 'AbortError') throw new Error('Roster API timeout (' + Math.round(ms / 1000) + 's) — serveur trop lent ou injoignable')
throw e
} finally { clearTimeout(timer) }
}
async function jget (path, ms) { return _fetch(path, {}, ms) }
async function jpost (path, body, ms) { return _fetch(path, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body || {}) }, ms) }
async function jput (path, body, ms) { return _fetch(path, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body || {}) }, ms) }
export const listTechnicians = () => jget('/roster/technicians')
export const listTemplates = () => jget('/roster/templates')
@ -105,7 +103,7 @@ export const unassignedJobs = () => jget('/roster/unassigned-jobs')
// Optimisation de tournées (VRPTW + compétences + temps sur place) via le solveur OR-Tools. payload = { jobs[], vehicles[], ... }
export const optimizeRoutes = (payload) => jpost('/roster/optimize-routes', payload)
// P4 — orchestration COMPLÈTE côté hub (pool+quarts+occupation+règles d'adresse) : { dates?|days?, tech_ids?, opts, dur_overrides, job_windows } → { plan[], unassigned[], assumed_shift }
export const optimizePlanHub = (payload) => jpost('/roster/optimize-plan', payload)
export const optimizePlanHub = (payload) => jpost('/roster/optimize-plan', payload, 90000) // solveur VRP : timeout 90s → sinon repli local (jamais figé)
// Proxys OSRM auto-hébergé (points [[lon,lat],…]) — tracés + matrices SANS Mapbox payant.
export const osrmRoute = (points) => jpost('/roster/osrm-route', { points }) // → { km, mins, geometry, legs[] (temps entre arrêts) }
export const osrmTable = (points) => jpost('/roster/osrm-table', { points }) // → { durations (s), distances (m) } même forme que Mapbox Matrix

View File

@ -1054,7 +1054,7 @@
</div>
<!-- D : revue de la répartition suggérée (surcouche) déplacer/retirer avant d'appliquer -->
<q-dialog v-model="suggestDlg.open" :maximized="$q.screen.lt.sm">
<q-dialog v-model="suggestDlg.open" :maximized="$q.screen.lt.sm" @hide="suggestDlg.building = false">
<q-card :style="$q.screen.lt.sm ? 'width:100%;max-width:100%;height:100%;display:flex;flex-direction:column' : 'width:940px;max-width:96vw;max-height:90vh;display:flex;flex-direction:column'">
<q-card-section class="row items-center q-py-sm" style="border-bottom:1px solid #e2e8f0">
<q-icon name="auto_awesome" color="amber-7" size="22px" class="q-mr-sm" />