From 3979f792833b5bdc7118714e36b9586a3c7cfb36 Mon Sep 17 00:00:00 2001 From: louispaulb Date: Sun, 5 Jul 2026 17:54:46 -0400 Subject: [PATCH] feat(ops): auto-detect AM/PM from ticket title + AM/PM in RDV proposal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Détection auto AM/PM : les tickets legacy « Note AM » / « Note PM » (importés tels quels) sont détectés (detectAmpm : titre + note, tokens AM/PM / avant-midi / après-midi, null si ambigu) et la contrainte horaire est PRÉ-APPLIQUÉE dans « Suggérer » (suggestDlg.jobTime seedé à l'ouverture, marqueur ⚡ auto_awesome sur la puce, modifiable). Le solveur respecte alors la fenêtre 8-12 / 12-16. 2. Fenêtre RDV « 3 dispos du client » : chaque créneau proposé a un sélecteur AM/PM (avant-midi/après-midi) qui règle l'heure représentative (AM=08:00, PM=13:00), heure exacte toujours ajustable. Aligné sur AM/PM du dispatch. Vérifié live : Suggérer affiche auto_awesome + puce AM pré-cochée (jobs « Note AM ») ; RDV → clic PM règle 13:00, AM règle 08:00 ; 0 nouvelle erreur. Co-Authored-By: Claude Opus 4.8 --- apps/ops/src/pages/PlanificationPage.vue | 16 +++++++++++++++- apps/ops/src/pages/RendezVousPage.vue | 12 ++++++++++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/apps/ops/src/pages/PlanificationPage.vue b/apps/ops/src/pages/PlanificationPage.vue index 0756e62..189cd78 100644 --- a/apps/ops/src/pages/PlanificationPage.vue +++ b/apps/ops/src/pages/PlanificationPage.vue @@ -1204,6 +1204,7 @@ + AM/PM détecté automatiquement depuis le titre/note du ticket Avant-midi (8-12) — Ré-optimiser pour appliquerAM Après-midi (12-16) — Ré-optimiser pour appliquerPM @@ -4263,6 +4264,16 @@ function legHome (g, d) { const v = (legsByKey[g.techId + '|' + d.iso] || {}).ho function legReturn (g, d) { const v = (legsByKey[g.techId + '|' + d.iso] || {}).returnTo; return v != null ? v : null } // temps de retour au départ // Contraintes horaires par job (⚡ Optimiser) : AM/PM (fenêtre dure) + « tôt » (urgence → début de journée). Session (clé = jobName, survit aux ré-optimisations). const AMPM_WIN = { am: [480, 720], pm: [720, 960] } // 8-12 / 12-16 (heure d'horloge) +// Détecte une contrainte AM/PM dans le titre/note du ticket (legacy « Note AM » / « Note PM » importé tel quel). +// Renvoie 'am' | 'pm' | null (null si absent ou ambigu = les deux présents). +function detectAmpm (job) { + const t = (String(job && job.subject || '') + ' ' + String(job && (job.legacy_detail || job.notes) || '')).toLowerCase() + const am = /\bam\b|avant.?midi|\bmatin\b/.test(t) + const pm = /\bpm\b|apr[eè]s.?midi/.test(t) + if (am && !pm) return 'am' + if (pm && !am) return 'pm' + return null +} function jobTimeOf (name) { return suggestDlg.jobTime[name] || {} } function toggleJobAmpm (name, v) { const c = suggestDlg.jobTime[name] || {}; suggestDlg.jobTime[name] = { ...c, ampm: c.ampm === v ? null : v } } // 🖥 « SANS DÉPLACEMENT » (à distance, ex. config boîtier tel via ACS) : persistant (store hub), exclu des tournées par l'optimiseur. @@ -4467,7 +4478,10 @@ function openSuggest () { // Pré-sélection par COMPÉTENCE : parmi les dispos, ceux dont les compétences couvrent au moins un skill requis // par les jobs du périmètre (jour + filtres). Toujours modifiable (le dialogue liste tous les techs, cochables) ; // repli sur tous les dispos si aucun skill requis connu ou aucun match. - const needed = new Set((suggestJobs() || []).map(j => j.required_skill).filter(Boolean)) + const scopeJobs = suggestJobs() || [] + // AUTO AM/PM : pré-applique la contrainte horaire détectée dans le titre/note (« Note AM/PM » legacy) si non déjà posée. + for (const j of scopeJobs) { if (!suggestDlg.jobTime[j.name]) { const ap = detectAmpm(j); if (ap) suggestDlg.jobTime[j.name] = { ampm: ap, auto: true } } } + const needed = new Set(scopeJobs.map(j => j.required_skill).filter(Boolean)) const skilled = needed.size ? availPool.filter(t => (t.skills || []).some(s => needed.has(s))) : [] const base = skilled.length ? skilled : availPool const sel = {}; base.forEach(t => { sel[t.id] = true }); suggestDlg.techSel = sel diff --git a/apps/ops/src/pages/RendezVousPage.vue b/apps/ops/src/pages/RendezVousPage.vue index 252c2b9..1aa49b4 100644 --- a/apps/ops/src/pages/RendezVousPage.vue +++ b/apps/ops/src/pages/RendezVousPage.vue @@ -98,7 +98,12 @@
{{ i + 1 }} - + + + +
@@ -142,7 +147,10 @@ const params = reactive({ skill: '', zone: '', duration: 1, start: todayISO(), d // Les 3 dispos sont PRÉ-REMPLIES avec des valeurs réelles (et non des défauts navigateur vides) → // « Vérifier » fonctionne d'emblée ; l'agent ajuste selon ce que dit le client. function plusDaysISO (iso, n) { const a = iso.split('-').map(Number); const d = new Date(Date.UTC(a[0], a[1] - 1, a[2])); d.setUTCDate(d.getUTCDate() + n); return d.toISOString().slice(0, 10) } -function defaultPrefs () { const d1 = plusDaysISO(todayISO(), 1); const d2x = plusDaysISO(todayISO(), 2); return [{ date: d1, start: '09:00' }, { date: d1, start: '13:00' }, { date: d2x, start: '09:00' }] } +function defaultPrefs () { const d1 = plusDaysISO(todayISO(), 1); const d2x = plusDaysISO(todayISO(), 2); return [{ date: d1, start: '08:00', ampm: 'am' }, { date: d1, start: '13:00', ampm: 'pm' }, { date: d2x, start: '08:00', ampm: 'am' }] } +// Choix rapide AM/PM (avant-midi / après-midi) → règle l'heure de départ représentative (fenêtre 8-12 / 12-16) ; +// l'heure exacte reste ajustable. Aligné sur la contrainte AM/PM du dispatch. +function setPrefAmpm (p, v) { p.ampm = v; if (v === 'am') p.start = '08:00'; else if (v === 'pm') p.start = '13:00' } const prefs = ref(defaultPrefs()) const fit = ref(null); const fitting = ref(false) const slots = ref([]); const chosen = ref(null); const finding = ref(false); const searched = ref(false)