diff --git a/apps/ops/src/pages/PlanificationPage.vue b/apps/ops/src/pages/PlanificationPage.vue
index d3e0835..e10efd5 100644
--- a/apps/ops/src/pages/PlanificationPage.vue
+++ b/apps/ops/src/pages/PlanificationPage.vue
@@ -1038,6 +1038,11 @@
{{ ({ optimize: 'solveur VRP (OR-Tools) : routes réelles minimisées + compétences + temps sur place — regroupe les secteurs, spécialistes d\'abord', smart: 'proximité + compétence + charge', best: 'concentre le travail sur les meilleurs (cascade)', balance: 'répartit également entre tous (round-robin)', enough: 'qualifié mais le moins sur-qualifié → réserve les experts (distance + temps pris en compte)' })[suggestDlg.strategy] }}
+
+
+
+ Inclut les {{ assignedNamesForWindow().length }} job(s) déjà assigné(s) du/des jour(s) → le solveur repropose une répartition de TOUT (existant + nouveaux). Force le solveur VRP. Aperçu seulement — « Appliquer » réassigne.
+
Coûts :
@@ -4242,7 +4247,13 @@ function selectAllSector (city) {
if (assignPanel.showMap) refreshAssignMap()
}
// ── D : SUGGESTION de répartition (glouton proximité + charge + compétence), REVUE en surcouche avant d'appliquer ──
-const suggestDlg = reactive({ open: false, mode: 'config', plan: [], building: false, applying: false, fromSel: false, techSel: {}, makeShifts: true, strategy: 'optimize', view: 'list', jobTime: {} }) // défaut = solveur VRP (1re génération optimise déjà, plus de placeholders) · strategy: optimize|smart|best|balance|enough
+const suggestDlg = reactive({ open: false, mode: 'config', plan: [], building: false, applying: false, fromSel: false, techSel: {}, makeShifts: true, strategy: 'optimize', view: 'list', jobTime: {}, includeAssigned: false }) // défaut = solveur VRP · strategy: optimize|smart|best|balance|enough · includeAssigned = simulation re-optimisant AUSSI les jobs déjà assignés
+// Noms des jobs DÉJÀ assignés sur la fenêtre (pour la simulation « ré-optimiser l'existant ») — depuis l'occupation.
+function assignedNamesForWindow () {
+ const win = new Set(suggestWindow.value); const out = new Set()
+ for (const key in occByTechDay.value) { if (!win.has(key.split('|')[1])) continue; for (const n of (occByTechDay.value[key].jobs || [])) if (n) out.add(n) }
+ return [...out]
+}
const prevOpen = reactive({}) // revue : sections « déjà assigné(s) » dépliées (clé techId|iso)
// ── Revue : détail du ticket au clic + temps révisable + temps de route ENTRE arrêts (OSRM) ──
const durOverride = reactive({}) // jobName → heures révisées par le dispatcher (survit aux Ré-optimiser de la session)
@@ -4637,6 +4648,9 @@ function techQuality (t) {
const suggestTechsRanked = computed(() => [...(visibleTechs.value || [])].sort((a, b) => techQuality(a) - techQuality(b)))
async function runSuggestion () {
if (!suggestSelCount.value) { $q.notify({ type: 'warning', message: 'Choisis au moins un technicien disponible.', timeout: 2500 }); return }
+ // « Ré-optimiser l'existant » : le solveur récupère les données des jobs assignés par leur nom (l'heuristique locale
+ // n'a que le pool en mémoire) → on force le solveur VRP.
+ if (suggestDlg.includeAssigned) suggestDlg.strategy = 'optimize'
suggestDlg.building = true
try {
if (suggestDlg.strategy === 'optimize') await optimizeSuggestion() // solveur VRP OR-Tools (routes + compétences + temps sur place)
@@ -4678,7 +4692,8 @@ async function optimizeSuggestion () {
try {
const r = await roster.optimizePlanHub({
dates: suggestWindow.value, tech_ids: selTechs.map(t => t.id),
- job_names: suggestJobs().map(j => j.name), // ne répartir QUE les jobs des jours cochés (ou la sélection lasso)
+ // job_names = pool filtré (+ jobs DÉJÀ assignés de la fenêtre si « ré-optimiser l'existant ») → le solveur les replace.
+ job_names: [...new Set([...suggestJobs().map(j => j.name), ...(suggestDlg.includeAssigned ? assignedNamesForWindow() : [])])],
opts: { rank_weight: solverOpts.rankWeight, speed_kmh: solverOpts.speedKmh, max_seconds: solverOpts.maxSeconds, overtime_coef: solverOpts.overtimeCoef },
dur_overrides: { ...durOverride }, job_windows,
})