Suggérer peut servir de SIMULATION sur des jobs DÉJÀ dispatchés : nouveau
toggle « Ré-optimiser aussi les jobs déjà assignés » dans la config. Quand
activé, le solveur VRP reçoit AUSSI les noms des jobs assignés de la
fenêtre (assignedNamesForWindow, depuis occByTechDay) en plus du pool →
repropose une répartition de TOUT (existant + nouveaux jobs ajoutés).
Force le solveur (l'heuristique locale n'a que le pool en mémoire).
Aperçu seulement — rien n'est écrit avant « Appliquer » (qui réassigne).
Vérifié : toggle rendu dans la config Suggérer, 0 nouvelle erreur. Le
résultat de ré-optimisation (solveur roster-solver) est à confirmer en
prod (comme les autres runs solveur).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cause du « on ne voit qu'une partie du titre, aucun autre détail » :
openJobDetail lisait la forme BLOC (b.customer/skill/detail/legacy_id)
mais reçoit un job BRUT du pool quand ouvert depuis une goutte/carrousel
(customer_name/required_skill/legacy_detail/legacy_ticket_id) → tous les
champs vides sauf le titre. Corrigé : mappe les DEUX formes (client,
compétence, adresse, description, n° ticket, date) + charge le fil du
billet via le bon id → détails + commentaires s'affichent.
- Titre : plus tronqué (ellipsis → retour à la ligne, titre complet).
- Assignation DEPUIS le volet : sélecteur « Assigner à… / Réassigner… »
(jdAssignTech) ; si d'autres tâches à la MÊME adresse/client/jour →
propose de les assigner au même tech (réutilise assignNames +
siblingJobs, comme la grille).
Vérifié live (goutte carrousel, ticket #253649) : titre complet non
tronqué, méta = compétence tv + adresse 44 Carre Morrison + dept + date,
section commentaires + sélecteur d'assignation présents ; 0 erreur.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Retours terrain sur les modales de détail job (carte Tournées) :
- Volet détail 1 job : la CARTE (jd-map + tracé GPS) n'est pas requise pour
lire le ticket → repliée par défaut derrière « Voir la carte / tracé
GPS » (init à la demande, pas au chargement). Détails simples d'abord +
champ « Durée estimée » ajustable (comme « Suggérer ») qui persiste
duration_h (+ override de session). Titre déjà complet.
- Popups « liste des jobs à cette adresse » débordaient hors écran sans
scroll : maxWidth 260 + overflow-wrap + max-height 44vh scroll (carte
jour du panneau À assigner ET popup groupée RouteMap).
Vérifié live : volet 1 job → carte masquée par défaut, bouton déplie +
init canvas OK, champ durée = 2.25h ajustable ; 0 nouvelle erreur.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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 <noreply@anthropic.com>
#3 Regroupement par adresse : routeUnassignedPins groupe les jobs non
assignés d'une même adresse en UNE pastille portant les icônes des types
de jobs (distinctes, max 3) + le TEMPS TOTAL + un compteur ; couleur =
priorité la plus haute. Clic (tap mobile) sur une adresse à >1 job →
popup listant chaque job (sujet + durée), chaque ligne ouvre le détail →
plus besoin de cliquer chaque ticket, info visible d'un coup d'œil.
#4 Chevauchement : les gouttes non assignées rejoignent recluster()/fan()
(déjà éprouvé sur les arrêts assignés) → les pastilles proches s'éclatent
en éventail pour rester distinguables/atteignables. (Le regroupement par
adresse réduit déjà le fouillis ; le fan gère les adresses proches ≠.)
Vérifié par injection du markup (impossible de rendre la carte en dev,
jeu sans coordonnées) : pastille groupée = horizontale 92×22, 2 icônes +
compteur + « 45min » visibles ; popup = lignes cliquables. Compile OK,
0 nouvelle erreur. Rendu carte à confirmer en prod (jobs géocodés).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Retours terrain :
1. « Ramassage d'équipement » = simple collecte chez le client → job court.
Détection (isPickup : titre/type ~ /ramm?ass|récupération/) → durée
plafonnée à 5 min : jobDur(pickup)=5/60 (maths d'occupation/Suggérer) +
capPickups() sur les 3 chargements du pool (est_min affiché=5). Évite de
surallouer le temps même si l'est_min du ticket dit plus long.
2. Icône de visite GÉNÉRIQUE (type non reconnu) : `bolt` (éclair, peu
parlant) → `handyman` (technicien + outils). Fallback de skillIcon →
s'applique partout (carte markerIcon + listes skillSym).
Vérifié live (données réelles) : ticket « Rammassage Équipements #253602 »
(St-Anicet) affiche ≈ 5min ; carrousel : plus de bolt, handyman présent
pour le job générique ; 0 nouvelle erreur.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sur suggestion de l'user (visuel maison+fibre) : l'icône « installation »
passe de construction/tools-ladder à `router` (le routeur/ONT posé au
domicile) — plus parlant pour une installation fibre. Appliqué PARTOUT via
les 2 fonctions d'icône : markerIcon (carte) + skillSym (carrousel,
kanban, blocs jour/semaine). Ligature material `router` → rend aussi bien
dans le <span material-icons> des marqueurs carte que dans les q-icon.
(Le glyphe custom maison+fibre est un SVG → impossible sur les marqueurs
carte en DOM brut ; router = meilleur équivalent material intégré.)
Vérifié live : carrousel affiche router pour les 8 jobs d'install (à côté
de build/live_tv/bolt), 0 nouvelle erreur.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Les jobs non assignés étaient de simples gouttes oranges SANS symbole,
alors que les assignés ont une pastille horizontale avec icône du type de
job (📞/🔧/⚡) + n°. Uniformisé en RÉUTILISANT le rendu éprouvé des arrêts
assignés (≠ tentative q-icon/SVG précédente qui rendait vertical/vide) :
- RouteMap.renderPins : pastille `.rm-pill` HORIZONTALE + icône via
<span class="material-icons"> (ligature material fiable), couleur =
priorité (orange = non assigné), SANS numéro d'ordre. Fini la goutte.
- routeUnassignedPins fournit `icon: markerIcon(required_skill||service_type)`
(markerIcon = ligatures material, celles qui s'affichent hors q-icon).
Vérifié : le markup exact injecté dans la page rend une pastille
HORIZONTALE 30×22 avec glyphe material 14px visible (police « Material
Icons ») — plus le bug vertical/sans-icône. Rendu carte identique à celui
des pastilles assignées (mêmes classes/mécanisme). À confirmer à l'œil en
prod (dev sans jobs géocodés → 0 marqueur).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1. Groupes de techs enregistrés (Suggérer) : sélection manuelle de techs
sauvée comme groupe nommé réutilisable (ex. Installateurs, Épisseurs) —
utile quand la compétence déduite du job est imprécise. Persisté par
utilisateur (useUserPrefs 'techGroups', suit l'usager entre appareils).
Chips « Groupes : » (clic = appliquer, ✕ = supprimer) + bouton
« Enregistrer » (nomme la sélection courante) + chip « Tout
désélectionner ». Complète le sélecteur par compétence existant.
2. Vue Tournées — clic sur un chip technicien = ISOLER sa tournée sur la
carte (masquer les autres) pour voir une route précise ; re-clic sur le
tech déjà seul = tout réafficher ; chip « Tout afficher » si masqués.
(Avant : le clic masquait/affichait un tech à la fois — inversé.)
Vérifié live : groupes save→apply→delete + « Tout désélectionner »
(13→0) OK ; 0 nouvelle erreur. (Les chips de tournée n'apparaissent
qu'avec des jobs géocodés — comportement solo vérifié par la logique,
à confirmer à l'œil en prod.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Les jobs ASSIGNÉS (marqueurs carte, HTML brut) utilisaient markerIcon =
ligatures material génériques (install→construction, monteur→camion
générique), alors que les jobs NON assignés (carrousel, blocs, gouttes)
utilisent skillSym = icônes SVG custom (échelle+outils, camion-nacelle,
casque). Résultat : même compétence, icône différente selon jour passé
(assigné) vs à venir (non assigné). Unifié sur les SVG custom :
- RouteMap : les marqueurs (arrêts assignés ET gouttes non assignées)
rendent l'icône via un VRAI <q-icon> (h+render) → gère les SVG custom
skillSym comme le reste de l'app ; repli material-icons si échec ;
démontage propre dans clearMarkers/clearPins (pas de fuite).
- PlanificationPage : les arrêts (Tournées + revue Suggérer) passent
skillSym(skill) au lieu de markerIcon ; markerIcon retiré (mort).
⚠️ Rendu carte NON vérifiable en préview (jeu dev sans coordonnées → 0
marqueur). Compile OK, carrousel (25 chips) inchangé, 0 nouvelle erreur.
À confirmer à l'œil en prod (jobs géocodés). Reste : unifier la FORME des
puces (chip/pill/carte) selon contexte — passe design dédiée.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Retours terrain :
1. « SLA dépassé » / en-retard se basait sur la CRÉATION du ticket
(jobSlaBadge → creation), ce qui marque en retard des jobs planifiés
plus tard (le client peut demander une visite quelques jours après
l'incident). Désormais l'échéance = la DATE DE VISITE PRÉVUE
(scheduled_date) : badge « En retard » seulement si scheduled_date <
aujourd'hui ; futur/aujourd'hui = à l'heure. Repli SLA-création
uniquement pour les jobs SANS date planifiée (backlog). (Le dispatch
utilisait déjà scheduled_date pour le périmètre — inchangé.)
2. Le carrousel « N à répartir » (Tournées) est plus long à scanner que la
liste par jour du legacy. La pastille « N à répartir » est maintenant
cliquable → ouvre la liste VERTICALE des jobs de ce jour (panneau
« Jobs à assigner » via openDayInPanel, filtré sur le jour) avec
recherche + chips type/compétence + tri par ville. Meilleure lecture,
surtout sur mobile.
Vérifié live (Tournées, 06/07) : clic « à répartir » ouvre le panneau
filtré (25 jobs du jour) ; 0 nouvelle erreur.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le correctif précédent ne couvrait que l'entrée Tournées. Les jobs en
retard (scheduled_date < fenêtre) ou sans date étaient encore balayés sur
le jour choisi via d'autres chemins (bouton SUGGÉRER hors Tournées,
solveur ⚡ Optimiser). Corrigé À LA SOURCE + au placement :
- suggestJobs() (source unique — utilisée aussi par le solveur qui reçoit
job_names) : exclut désormais les jobs hors fenêtre (retard/sans date/
autre jour) SAUF si l'utilisateur coche « ⏰ en retard » / « Sans date »
ou fait une sélection lasso. Sélectionner un seul jour ne dispatche donc
QUE les jobs dus ce jour-là.
- buildSuggestion() : la branche fourre-tout `else cand = win` ne place
plus les retards/sans-date automatiquement — même garde (lasso ou chips
explicites) en défense.
Vérifié live (Tournées, 06/07) : plan de 25 jobs, tous sur « 07-06 »
(aucune date antérieure), 0 nouvelle erreur.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Deux retours terrain :
1. Jobs « en retard » (date < jour sélectionné) apparaissaient dans la
répartition auto. Cause : en vue Tournées, suggestWindow suivait les
chips de date du POOL, pas le jour choisi dans la bande → buildSuggestion
balayait les jobs antérieurs sur le jour (branche `else cand = win`).
Fix : en vue Tournées, `suggestWindow = [routesDay]` (la bande de dates
fait autorité) → seuls les jobs DUS ce jour-là sont répartis, jamais les
retards. (Le balayage des retards reste dispo hors Tournées via les chips.)
2. Revue « Répartition suggérée » trop dense sur mobile, lignes rognées
sans défilement. Fix : dialogue MAXIMISÉ <sm + chaque rangée job
(.suggest-entry) défile horizontalement (nowrap + overflow-x, enfants
non rétrécis, sujet borné) → toute la ligne (badges/AM-PM/actions)
accessible au swipe.
Vérifié live 375px : dialogue plein écran ; 25/25 rangées défilent
(scrollW ~698 > clientW 341) ; plan bâti sur le jour choisi ; 0 nouvelle
erreur console.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sur la carte Tournées, les jobs NON assignés (props.pins) étaient de
simples gouttes oranges sans icône ni gestion du chevauchement, alors que
les arrêts ASSIGNÉS sont des pastilles round-rect (icône compétence + n°)
qui s'éclatent en éventail au survol quand elles se chevauchent
(recluster/fan). Uniformisé :
- renderPins() rend la MÊME pastille .rm-pill (icône de compétence via
pin.icon = skillSym), variante « non assigné » (.rm-pill-un : bordure
pointillée + halo orange), avec hover onEnter/onLeave.
- recluster() opère désormais sur stopMk.concat(pinMk) → arrêts et jobs
non assignés participent au même éclatement au chevauchement.
- routeUnassignedPins fournit `icon` (skillSym du required_skill/service).
⚠️ Visuel (pastille + éventail) NON vérifiable en préview locale : le jeu
de données dev n'a pas de coordonnées sur les jobs → 0 pin rendu. Code =
miroir fidèle du rendu d'arrêt éprouvé ; map s'initialise, 0 nouvelle
erreur console. À vérifier à l'œil en prod (adresses réelles géocodées).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Suite au carrousel : les chips de filtre de la bande « à répartir »
pilotent maintenant AUSSI « Suggérer » (« ce que je filtre = ce que je
répartis »).
- Chips PRIORITÉ (normalisée high/medium/low, via POOL_PRIOS) ajoutés à
côté des secteurs ; filtrent la liste + la carte (routeDayUnassignedView
= jour + compétence + secteur + priorité). Affichés seulement si >1
priorité présente (comme les secteurs).
- suggestJobs() : en vue Tournées, le périmètre = routeDayUnassignedView
EXACTEMENT (corrige l'incohérence : avant, les chips secteur étaient
ignorés par Suggérer alors que l'infobulle disait « du secteur »).
- openSuggest() : pré-sélection des techs par COMPÉTENCE — parmi les
disponibles (quart ce jour), ceux dont t.skills couvre un required_skill
des jobs du périmètre ; repli sur tous les dispos si aucun match.
Toujours modifiable (le dialogue liste les 56 techs, cochables).
Vérifié live (06/07) : secteur Huntingdon → vue 4 jobs → Suggérer affiche
« 4 job » (avant : ignorait le secteur) + 13/56 techs pré-cochés
(compétents+dispos, reste modifiable). 0 nouvelle erreur console.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
La bande « N à répartir » sous le sélecteur de jour (vue Tournées)
retournait à la ligne sur 2 rangées (max-height 70px, scroll vertical) et
PLAFONNAIT à 16 jobs (« +N de plus » cachait le reste). Passée en
carrousel horizontal 1 rangée (flex nowrap + overflow-x auto, scroll-snap,
barre fine) qui montre TOUS les jobs du jour — compact au-dessus de la
carte, tactile/tablette, aligné avec la bande de dates au-dessus.
Vérifié live (Tournées, 06/07 = 25 à répartir) : 25 chips rendus (fini le
plafond 16), 1 rangée nowrap, scrollWidth 5870 > 1214 = défile bien.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
La section Tickets de la fiche utilisait un q-table dont le mode carte
mobile empilait des valeurs non étiquetées (« 0 », n°, sujet, avatars,
date…) = archaïque et peu lisible (cf. capture agent terrain). Nouveau
composant components/customer/TicketCard.vue : sujet en évidence + badges
statut/priorité colorés (édition inline conservée via InlineField) + n°
(legacy ou ISS) + date + avatars ouvreur/assigné empilés ; rangée
responsive (flex-wrap) lisible sur mobile ET desktop. Remplace le
DataTable dans la section Tickets ; imports morts retirés (ticketCols,
ticketStatusClass, priorityClass, staffColor, staffInitials).
Vérifié live (C-LPB4) : 5 cartes, sujet + Open/Medium + #ISS-… + date,
aucun élément > viewport à 375px, rendu propre à 1280px, 0 erreur.
Note : la réutilisation d'icônes de compétences dans les tickets est
reportée — les Issues (surtout legacy) n'ont pas de signal compétence
fiable (0 Dispatch Job correspondant) ; à faire quand un lien
ticket→compétence existe.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le panneau de détail (DetailModal) était un panneau droit fixe 600px/90vw
et la carte réseau d'EquipmentDetail (.topo-row = 4 nœuds fixes 90px + 3
liens ≈528px) débordait/rognait sur mobile (nœud « Internet » coupé, IP/
onglets tronqués). DetailModal : sur <sm → dialogue maximisé pleine
largeur (position bottom) au lieu du panneau droit ; desktop inchangé
(600px droite). EquipmentDetail @<600px : nœuds topo flex (largeur auto),
liens 18px, dv-grid 2 col, dv-row2 en colonne, diag-grid 1 col.
Vérifié live (C-LPB4, EQP-0000011366) : 375px → carte pleine largeur, 0
débordement horizontal (docScrollW=375), 4 nœuds topo visibles
(Internet→OLT→Modem→17 app.), 0 élément > viewport, 0 erreur ; 1280px →
panneau droit 600px inchangé.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
T7 découpage géants — sort le « lieu de service » (en-tête adresse +
DeviceStrip + abonnements mensuels/annuels drag-drop + tickets) dans
components/customer/LocationCard.vue. Les sélecteurs/actions stateful
(locSubs*/toggle*/onSub*/section*) restent liés aux instances de
composables du parent et sont passés en props-fonctions homonymes → le
template les résout inchangé, comportement identique (mêmes closures,
même invalidation de cache useSubscriptionGroups/useSubscriptionActions).
Helpers purs (formatMoney/isRebate/…) et usePermissions importés dans
l'enfant. Styles loc-*/sub-* copiés (scoped). ClientDetailPage -242 l. ;
imports morts retirés (DeviceStrip/locStatusClass/isRebate/subMainLabel/
sectionTotal/annualPrice/locInlineFields).
Vérifié en local sur hub LIVE (C-LPB4) : rendu pixel-identique (carte
691 rue des hirondelles, DeviceStrip live acs-online, ABONNEMENTS 4 =
INTERNET 3 / RABAIS 1, total 0,00 $/m), toggle de section fonctionne via
la prop (INTERNET 0→3 rangées, chevron expand_more), 0 erreur console.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
T7 découpage géants — sort le bandeau d'appareils (chips ONT/routeur +
menu Ajouter) dans components/customer/DeviceStrip.vue. Consomme le
composable singleton useDeviceStatus (couche données ACS/OLT partagée) ;
les helpers de présentation propres aux appareils (signalColor,
formatTimeAgo, doReboot, doRefreshParams) déménagent DANS le composant
(utilisés nulle part ailleurs). Le parent émet open-device + add-*.
ClientDetailPage -108 lignes ; imports/destructures device retirés.
Vérifié en local sur hub LIVE (C-LPB4) : chip vert + point acs-online
réel, tooltip ACS complet (SN TPLGC4160688, OLT oltRem03 1/3/0, En ligne
il y a 4m, Fibre Up, 17 clients WiFi, FW/WAN IP/SSID), 0 warning console.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Les cellules de jour étaient à largeur fixe (52px) → une bande de 7 jours (~400px)
débordait de ~57px sur mobile et défilait légèrement (6,5 jours visibles).
Override mobile `.os-day { flex:1 0 40px; width:auto }` : jusqu'à 7 jours, les
cellules s'étirent pour remplir la largeur (tableau de bord « cette/prochaine
semaine » = 7 tiennent pile). Au-delà (tournées = bande 14 j) elles gardent leur
plancher 40px et la bande défile comme avant.
Vérifié 375px : tableau de bord 7 j = ne défile plus (jour 41px) ; Planif 14 j =
défile toujours (jour 40px). Portée mobile uniquement.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
NetworkPage débordait de ~118px à 375px. Deux causes corrigées :
- Barre d'onglets (4 onglets = 435px) non bornée dans une rangée flex → ne
déclenchait pas le défilement natif de Quasar. Ajout global `.q-tabs { max-width:100% }`
en mobile (app.scss) : Quasar réactive son scroll (aucun effet là où déjà borné).
- Arbre GPON : en-têtes OLT/slot/port en flex non-enroulé (nom·IP·adresse·nb
clients·SNMP·slots·éditer) → débordement. `flex-wrap:wrap` + neutralisation du
`margin-left:auto` des compteurs sur mobile.
- Boutons d'action de l'arbre (éditer OLT, ajouter port, éditer lieu) étaient
révélés au survol (opacity:0) → invisibles au tactile. Toujours visibles < 600px.
Vérifié à 375px : docScrollW 493→375, en-têtes enroulées, crayon éditer visible.
Portée strictement mobile (@media max-width:599px) — desktop inchangé.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Premier découpage de la fiche (2102 l) : bloc « Satisfaction » (étoiles/commentaire + « Demander une évaluation »)
→ components/customer/CustomerSatisfaction.vue (props latest/count, émet 'draft'). Vérifié sur la fiche (rend « Aucune
évaluation » à l'identique, 0 erreur). Extraction DÉLIBÉRÉMENT petite + vérifiable ; les gros blocs couplés
(LocationCard/PaymentSection/DeviceStrip — 10+ méthodes parentes : getDevice/combinedStatus/isOnline… + données ACS live
à vérifier) restent à faire en passe dédiée. Build vert, leak 0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Worklist de rendez-vous : recherche (job/client/lieu) + chips service_type (dynamiques) + presets ('filter.rdv') ;
`filteredJobs` applique le filtre APRÈS la portée `view` (à planifier/à recontacter/tous), qui reste un toggle séparé.
Client-side, réactif. Build vert, leak 0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- CustomerHeader : sous-ligne (nom/legacy/type/langue) était `no-wrap` → débordait sa colonne et
chevauchait le bloc statut/liens en mobile. Fix : wrap + colonne `min-width:0` + le bloc statut/ERPNext/Users
passe pleine largeur en dessous (<sm) via `col-12 col-sm-auto` ; titre en ellipsis.
- Ajouter un assistant : q-select brut → <TechSelect> (recherche typeahead) ; jdTeamOptions = nom · compétences
(searchable par nom OU compétence), CAPABLES d'abord (possèdent la compétence du job), value = id ; jdAddAssistant résout le tech par id.
Vérifié préview mobile (375px) : sous-ligne wrap, 0 débordement (scrollW==clientW) ; build vert, leak 0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Remplace l'icône terrain/distant par le SYMBOLE de compétence (skillSym : live_tv/build/cable/cell_tower/camion-nacelle…, couleur = getTagColor) — cohérent avec les cartes kanban + blocs de tournée. Vérifié préview : chips = live_tv (TV), build (réparation)…
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Batch D — regrouper/situer les non-assignés du jour :
- RouteMap : nouvelle prop `pins` (+ renderPins/clearPins/watch, gouttes oranges couleur=priorité, clic → @pin-click) — miroir de `live`, inclus dans le fit bounds
- Planif Tournées : chips SECTEUR (par ville, comptés, triés) qui filtrent SIMULTANÉMENT la liste « à répartir » ET les gouttes sur la carte ; routeUnassignedPins (coords robustes latitude/lat), clic goutte → détail ; « Suggérer » agit sur le secteur si filtré
Vérifié préview : jour 06/07 → 10 secteurs + 21 gouttes ; clic « STE CLOTILDE » → 4 chips + 4 gouttes (« 4 à répartir / 21 »).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Batch B — le filtre de compétence pilote la carte : dayLivePositions (GPS techs) filtré par visibleTechs
(les tournées l'étaient déjà via visibleTechs) → seuls les techs capables + leurs jobs sur la carte.
Batch C — mise en évidence des non-assignés du jour :
- Badge rouge « dus non assignés » par jour sur la bande (OccupancyStrip.os-unassigned), depuis unassignedByDay
- routeDayUnassigned = jobs dus le jour sélectionné mais non assignés → bande « N à répartir » (chips cliquables → détail) + bouton « Suggérer » ; se met à jour au clic sur la strip
Vérifié préview : positions filtrées par compétence ; badges [1,21,16,15,3,8] sur la bande ; clic 06/07 → bande « 21 à répartir » (14 chips réels).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Bouton « Générer » (haut) → « Suggérer » (openSuggest) basé sur le jour de la vue ACTIVE (strip Tournées / Jour kanban / bande mobile). Le solveur de quarts est déplacé dans Outils (« Générer l'horaire (semaine) », doGenerate conservé).
- Fix recherche tech : le X (clearable) mettait search=null → `search.value.trim()` plantait le computed visibleTechs → le champ « ne se vidait pas ». Rendu null-safe.
- Retrait du sélecteur « Équipe » (groupOptions vide : aucun tech n'a de `group` ; le filtre par compétence suffit).
Vérifié préview (desktop 1400px) : X vide le champ sans planter, Suggérer ouvre le dispatch auto, « Équipe » absent, grille rendue.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
routeStripDays passe de 7 → 14 jours (depuis le lundi affiché), données via /roster/capacity
(14 j, découplé de la fenêtre de charge 7 j du grid → barres réelles en semaine 2). Chargé à
l'entrée de la vue Tournées + au changement de semaine (watch [boardView, start]). Bande pleine
largeur + défilement horizontal ; jour courant marqué.
Vérifié préview : 14 cellules (29/06→12/07), today marqué, scroll.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Tableau de bord : 2 bandes d'occupation (« Cette semaine » / « Semaine prochaine », 14 j depuis lundi), jour courant marqué (liseré indigo dans OccupancyStrip). Clic jour → /planification?day=ISO
- Planif : deep-link `?day=` (cadre la semaine + sélectionne le jour grille/mobile/tournées) & `?skill=` (filtre compétence, match casse-insensible vs allSkills) via focusDay() lu au montage
- Slot-finder (SuggestSlotsDialog) : état « Aucun créneau » → bouton « Planifier des quarts pour « X » » → émet plan-shifts → Planif ouvre la timeline (vue grille) au jour cherché, filtrée sur la compétence, + toast « crée un quart pour ouvrir des créneaux »
Vérifié préview bout-en-bout : 2×7 jours + today, clic → ?day=2026-07-09 cadre 06→12/07, recherche vide → bouton → dialog fermé + grille + toast compétence. Build vert + leak-check 0. Aucun changement hub.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Batch 1 de l'audit d'optimisation. Les libs lourdes étaient statiquement importées
donc livrées même inutilisées → passées en import dynamique / defineAsyncComponent :
- MainLayout : ConversationPanel/PhoneModal/FlowEditor/NewTicket/Orchestrator/
ServiceStatus/OutboxPanel → async (chunk shell 312 KB → 56 KB, chargé sur CHAQUE page)
- TaskGraphPage : TaskGantt (hy-vue-gantt) → async (2.6 MB → 20 KB ; le Gantt ne charge
que dans la vue Gantt)
- NetworkPage : cytoscape → import() dans loadNetworkMap (484 KB → 52 KB)
- ReportAR/Revenu/Taxes : chart.js/auto → import() dans renderChart (async) (~150→~10 KB/page)
Total dist inchangé (~8 M) — le code est DÉFÉRÉ en chunks à la demande, pas supprimé.
Gain réel = ce que l'utilisateur télécharge pour VOIR une page. Build OK, leak-check propre,
shell + route rapport vérifiés (aucune erreur d'import).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le hub compare la position GPS live de chaque tech (Traccar) aux jobs du jour →
détecte les transitions et les journalise par job. Le statut « live » du job est
DÉRIVÉ du journal (compute-on-read, aucune écriture ERPNext — pas de valeur « en
cours » côté doctype). Fiche job : timeline horizontale Assigné → En route → Arrivé
(HH:MM) → Reparti (HH:MM).
- lib/geofence.js : runScan() (rayon 150 m, sortie 230 m hystérésis, séjour 3 min),
store data/geofence.json par job, machine à états ; startScan() scheduler 90 s
(GEOFENCE_SCAN=off pour couper). Vérifié prod : 12 jobs × 14 positions, transitions OK.
- server.js : démarre le scan au boot.
- roster.js : GET /roster/job/:name/geofence (timeline) + /geofence-states + /geofence-scan (test).
- api/roster.js : jobGeofence(), geofenceStates().
- PlanificationPage : openJobDetail charge la timeline ; stepper .jd-geo (scoped).
Suite possible : écrire le vrai statut ERPNext à l'arrivée (nécessite d'ajouter une
valeur « En cours » au doctype Dispatch Job) — non fait pour éviter le risque schéma.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Onglet Tournées : marqueurs « live » de chaque tech (appareil Traccar associé),
rafraîchis ~25 s tant que l'onglet est ouvert. Marqueur = pastille ronde à initiales,
couleur = celle de la tournée du tech, halo pulsé si position fraîche (grisé sans halo
si > 10 min). Tooltip : nom · « il y a X min » · vitesse (km/h) ou « arrêté ».
Bouton « GPS live (N) » pour afficher/masquer. Respecte les chips techs masqués.
- hub roster.js : GET /roster/tech-positions → {positions:[{techId,techName,lat,lon,time,speed}]}
(batch getPositions par appareil). Vérifié prod : 14 positions.
- api/roster.js : techPositions().
- RouteMap.vue : prop `live` + marqueurs .rm-live (halo pulsé, staleness), watch dédié.
- PlanificationPage : polling 25 s (démarré sur l'onglet Tournées, arrêté sinon +
onUnmounted), couleur mappée depuis dayRoutes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sélectionner le 7 juillet ne devait pas tirer les jobs en retard/sans date (souvent
faits mais pas fermés) dans la simulation. Par défaut (aucun chip de date coché),
suggestJobs ne garde que les jobs DATÉS sur le(s) jour(s) de la fenêtre — plus de
pollution par les retards/autres jours. Pour dispatcher les retards : cocher le chip
« ⏰ en retard » (comportement inchangé). Le job_names envoyé au solveur hub ne contient
alors que les jobs du jour → même résultat côté Optimiser.
Vérifié preview : jour 07-07 → 15 jobs, plan = 15 arrêts tous le 07-07, 0 en retard.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
RACINE du « Accès OPS indisponible / menu vide » : le bundle appelait `/hub/...`
au lieu de `/ops/hub/...`. Cause : `.env.local` contient `VITE_HUB_URL=/hub`
(pour le proxy du serveur de dev) et Vite charge .env.local MÊME au build prod
(même fuite que VITE_DEV_USER) → HUB_URL="/hub" inliné. `/hub` (hors préfixe /ops)
est routé vers ERPNext → **HTTP 404** sur /hub/auth/permissions → can()=false →
menu/recherche/avis vides. Preuve navigateur : app `/hub/...`→404, direct
`/ops/hub/...`→200 superuser.
Fix : config/hub.js n'honore VITE_HUB_URL qu'en DEV ; en PROD HUB_URL =
BASE_URL + '/hub' = **/ops/hub** (chemin conçu, proxifié par nginx → hub, vérifié 200).
Bundle vérifié : plus de "/hub" standalone, tout est BASE_URL+"/hub".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
L'overlay disait à tort « pas de permissions » alors que le compte y a droit et que
le fetch /auth/permissions échoue (blocage client). Message corrigé : « OPS n'a pas pu
charger les permissions » + affiche le détail technique (usePermissions.error) + suggère
fenêtre privée/autre navigateur (une extension bloque souvent /auth/permissions). Rend
chaque capture d'écran auto-diagnostique.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>