1) Suggérer (retour terrain, ex. ticket 254403 en retard auto-dispatché) :
- fenêtre = EXACTEMENT le jour sélectionné ; les chips de date du pool
n'élargissent plus jamais le dispatch (elles filtrent l'affichage)
- suggestJobs filtré à la SOURCE (couvre solveur VRP + heuristique) :
scheduled_date === jour sélectionné ; retards/sans-date exclus —
seule exception : sélection manuelle (lasso)
- libellés du dialogue mis à jour (« N job(s) DUS ce jour · retards exclus »)
2) Jour sélectionné PARTAGÉ (selDay) persisté en sessionStorage (planif_sel_day) :
écrit par bande Tournées / Jour kanban / sélecteur de date / bande mobile /
dialogue Suggérer ; repris au changement de vue (Jour ne retombe plus sur
« aujourd'hui », Tournées suit, Mois ancre son mois dessus)
3) Quarts créés en Semaine/Jour → calendrier mois par tech + vue Mois :
flush du brouillon (debounce 800 ms) avant l'ouverture du dialogue horaire,
flush au passage en vue Mois, et refresh auto des calendriers ouverts quand
une auto-sauvegarde se termine (techSchedRefresh / monthRefresh ;
MonthOverview.refreshKey enfin câblé du parent)
Vérifié (aperçu 9001, lecture seule) : pool 44 jobs dont 8 en retard → dialogue
n'offre que les 2 DUS le jour choisi ; 22/07 suivi Semaine→Jour→Tournées ;
Mois affiche « Août 2026 » pour selDay=2026-08-05 ; build SPA OK.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Duplicate Authentik accounts now surfaced automatically: a "⚠ Doublons (N)"
filter chip + the warning badge itself is clickable to clean up (the 7 dup
people are status=ok, so they were hidden under the default orphan filter).
- "Horaire" button on rows with a tech reuses the SAME Planification schedule
module (TechScheduleDialog: recurring template + congés + pause) via a
/planification?sched=<tech_id> deep-link — no inferior rebuild.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add "Bloquer / job générique…" to the timeline cell menu (opens on empty-cell
click or right-click). Opens a small dialog prefilled with the tech + day:
editable title, full-day toggle (= that cell's shift length) or custom
start/duration, standard priority (default Moyenne), type, and a "generate a
linked ticket" toggle.
New hub POST /roster/filler-job creates a standard-priority assigned Dispatch
Job (+ optional ERPNext Issue via source_issue, best-effort so a ticket failure
never blocks the job). Since timed jobs now count toward occupancy, this removes
the tech from regular dispatch — unlike /reserve which stays soft for urgencies.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extract the per-tech skill editor (chips ordered by priority + per-skill Score
★ + Cadence %) into shared components/planif/SkillCadenceTable.vue, used by BOTH
the click-a-tech popover AND the team tool — one template everywhere.
Rebuild "Équipe — cadence & coût" into "Équipe — compétences, cadence & coût":
each tech is an expandable row showing SkillCadenceTable (per-skill cadence) +
global cadence (default 100%) + cost. Operates on live tech objects so edits
persist via the existing handlers. Folds "Gérer les compétences" (tag catalog:
rename/recolor/delete) in as a collapsible section, and collapses the two menu
entries into one "Équipe — compétences · cadence · coût".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Absences (congé/pause) passent par le modèle local -> Publier (journal,
undo, dirty badge) au lieu d'écrire directement au serveur. Applique à la
grille ET au calendrier par tech (TechScheduleDialog).
- Fix capacité "48h" : l'estimation ignorait l'absence et le patron hebdo.
- Menu de cellule (Jour/Soir/Garde/Absent/heures) extrait en composant
partagé PlanifCellMenu, réutilisé par la grille et le calendrier par tech.
- Raccourci "Jour" = 8-16 (pas 8-17) ; saisie rapide accepte "816".
- Nouvelle vue Mois (MonthOverview) : qui est en quart/absent par jour +
couverture par compétence (heures requises vs heures en quart). Remplace
l'ancien module "Demande - effectif requis par créneau".
Refonte du modèle d'interaction (retour user) + correction du bug d'affichage des assistants.
Bug corrigé (racine = hub) : getJobTeam lisait le doctype-enfant « Dispatch Job Assistant »
via erp.list → PermissionError pour le compte de service → renvoyait [] en silence. Résultat :
chips assistants jamais affichées, tech non exclu de l'autosuggest, et POST add/remove partait
de [] → écrasait les assistants existants. Fix : lire `assistants` VIA LE DOC PARENT (child table
embarquée, sans vérif de perm sur l'enfant). Vérifié : getJobTeam(LEG-253958) renvoie l'assistant.
Nouveau modèle (AssignmentField) :
- UN champ « Ajouter un participant » (techs + compétences en tête, puis tous les utilisateurs).
1er ajouté = assigné/lead ; suivants = assistant par défaut.
- Échelle d'implication par personne (menu sur la chip) : Suiveur ‹ Assistant ‹ Sur place (équipe).
· Suiveur (#) = reçoit les MàJ, hors équipe (/conversations/follow).
· Assistant (CC) = sur l'équipe, SANS bloc horaire (Dispatch Job Assistant pinned=0).
· Sur place = assistant AVEC bloc réservé dans son horaire (pinned=1) — masqué sur ticket (can-onsite=false).
- « Assist » / « Suivre » deviennent des BASCULES sur l'utilisateur connecté (moi), plus des liens d'expansion.
- Suivi keyé par courriel si connu, sinon tech_id (repli).
Câblage : PlanificationPage jdSetAssistant({value,label,onsite}) → addAssistant pinned=onsite?1:0
(le hub `add` remplace la ligne du même tech → sert aussi à changer de niveau). IssueDetail : can-onsite=false.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fusionne l'assignation en UN autosuggest + chips à 3 rôles, réutilisé sur le
volet tâche (PlanificationPage) ET le détail ticket (IssueDetail).
- Nouveau composant partagé AssignmentField.vue : chips distinctes À (assigné /
lead) · CC (assistant / renfort) · # (follower). Autosuggest par défaut =
techniciens ; 1er choisi = assigné, 2e+ = assistant. Mini-liens « Assist » et
« #follow » étendent la liste à TOUS les utilisateurs (assistant ou follower
depuis n'importe qui). Le suivi (follower) est géré dans le composant via
/conversations/follow (identique sur toutes les surfaces).
- TechSelect : props optionnelles searchFn (recherche async « tous les
utilisateurs ») + noOptionLabel — amélioration du composant partagé, pas de fork.
- PlanificationPage : le champ To/Cc devient AssignmentField (assigné→jdAssignTech,
assistant→addAssistant, désassigner→jdUnassign) ; adaptateurs jdAssignee /
jdAssistants / jdFieldAssist.
- IssueDetail : remplace le multiselect _assign par AssignmentField (1er _assign =
À, suivants = CC ; frappe assign_to) ; pool par défaut = utilisateurs assignables.
- Hub (conversation.js) : POST /conversations/follow accepte un courriel cible
(abonner n'importe quel utilisateur) ; nouveau GET /conversations/followers
?doctype&name → liste des abonnés (pastilles #).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- capacité/occupation par jour : retranche les (tech,jour) en congé approuvé /
pause / archivé du dénominateur (dashboard cap_h + bande Planif visStat) —
fini le « 24/150 » gonflé par des techs indisponibles
- archive technicien : store hub durable (data/archived_techs.json) au lieu
d'écrire status=Archivé (ERPNext v16 rejette la valeur hors liste → 500) ;
exclusion partout (roster/solveur/créneaux/occupation) + restauration
- TechScheduleDialog calendrier : jours SANS quart grisés (comme week-end),
jours AVEC quart en blanc + mince barre d'occupation → vraie dispo du tech
- techOccupancy : horizon jusqu'à 42 j (calendrier mois)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le chip Cc/assistant du champ d'assignation To/Cc montre maintenant les initiales de
l'assistant dans une pastille (q-avatar), comme le chip « À » (assigné). Réutilise initials().
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Simplification (panneau détail desktop) — demande 2026-07-07 :
- UN seul champ d'assignation « façon To/Cc » (fusionne « Assigner à… » + section « Équipe/renfort ») :
chips À = assigné (principal, retirable → renvoie au pool) + Cc = assistants (renfort) ;
le champ ajoute : 1er tech = assigné (À), suivants (job natif) = assistants (Cc). Handlers
jdAssignOrAssist (routage) + jdUnassign ; réutilise jdAssignTech/jdAddAssistant/jdRemoveAssistant.
- Timeline de l'assistant GRISÉE (au lieu de hachurée lilas) : .kbb-blk.assist + .tl-blk-assist
(opacité + gris) dans le kanban ET la grille semaine.
- ⚠️ RecipientSelect est COURRIEL-only (pas réutilisable pour des techs) → on réutilise le PATRON
chips+autosuggest avec TechSelect.
- Décodage entités HTML à la SOURCE du pool : capPickups décode subject/customer_name/address/
service_location/legacy_detail (deEnt) → fini « l'église » dans le pool, la feuille, l'assignation.
Vérifié : build + parse OK, 0 erreur console. ⚠️ rendu visuel du panneau détail NON confirmé en
aperçu (l'environnement d'aperçu bloquait l'ouverture du volet ce tour) — à valider sur desktop réel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
L'en-tête de la feuille job affichait les champs bruts → « 424 chemin de l'église ».
Passés par deEnt() (déjà utilisé pour le panneau détail complet) : sujet, customer_name, adresse.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Incident : un employé a envoyé un courriel au client en croyant ajouter un commentaire
privé au fil du ticket. Le composeur de la feuille (jobSheet) s'ouvrait en mode « Réponse ·
courriel » (public) par défaut.
- openJobSheet : `noteMode` défaut = TRUE (commentaire INTERNE) au lieu de false (réponse client) ;
ouverture en mode réponse seulement si `{ reply: true }` explicite (aucun appelant ne le fait)
- bascule réordonnée : « Note interne » (1re, défaut) · « Répondre au client » (2e, explicite) ;
couleur d'alerte (deep-orange) quand la réponse client est sélectionnée
- bouton d'envoi : « Enregistrer la note » (interne, neutre) vs « Envoyer au client » (orange) ;
indice/placeholder clarifiés (« ajoutée au fil, jamais envoyée au client » vs « ⚠ ENVOYÉE au client »)
- répondre au client reste possible mais = choix explicite (aligne le comportement sûr déjà
présent dans le panneau détail complet : case « Envoyer aussi au client » jdReplyPublic par défaut off)
- Vérifié en aperçu sur un job AVEC courriel client : le composeur s'ouvre en « Note interne »
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Heures par tech : la grille affiche 2 semaines (days=14) mais l'avertissement comparait
le TOTAL (≈72h) à la limite hebdo (40h) → faux « surcharge ». Retiré le triangle rouge +
le style rouge ; le total reste affiché en gris avec une infobulle « sur l'étendue affichée
(N j), PAS une limite hebdo ». (Le réglage Max h/sem reste pour le solveur.)
- Colonnes : .tech-col élargie (min 240→300, max 300→360, reste sticky) ; colonnes-jour
min-width 132px → ~1 semaine tient dans la vue, la 2e semaine défile horizontalement
(.grid-wrap overflow-x déjà présent). Vérifié : table 2149px > vue 1550px → défilement,
tech-col 327px, cellules 132px, 0 avertissement rouge.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Début de la décomposition de PlanificationPage (6725 l.). 1re extraction = le panneau
le plus AUTO-CONTENU (survey de couplage) : congés/disponibilités.
- components/planif/LeaveDialog.vue (NOUVEAU, 78 l.) : état privé (leaveRows/leaveFilter/
newLeave) + fonctions (loadLeave/approveLeave/createLeave) déplacés tels quels ; v-model
pour l'ouverture ; techs + techOptions en props LECTURE SEULE ; charge à l'ouverture (watch) ;
CSS .demand-tbl recopié (le scoped ne traverse pas vers l'enfant)
- PlanificationPage : dialogue + état + 4 fonctions retirés (−39 l.) ; menu Outils → `showLeave = true`
- AUCUNE mutation d'état partagé (pas de loadWeek) → ne peut pas désynchroniser la grille
- Vérifié en aperçu : ouvre via le menu, table + formulaire rendus, chargement OK, CSS 11px appliqué
Prochaines extractions sûres (par couplage croissant) : techSync, types de shift, puis
absence/demande (loadWeek), enfin les couplés (team/tags/garde/publish).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Régression corrigée : les 4 sélecteurs de compétences de Planification utilisaient
SkillSelect (chips plates, CSV, sans couleurs ni catalogue) au lieu du composant
riche TagEditor déjà employé pour les compétences d'un technicien.
- jobDetail + feuille du pool (« Compétences requises ») : SkillSelect → TagEditor
(chips COLORÉES, autosuggest depuis tagCatalog, création à la volée + palette 12 couleurs)
- table d'équipe + table de demande de personnel : idem → TagEditor (compact)
- SkillSelect.vue SUPPRIMÉ (0 référence) ; import retiré
- normSkillList() : normalisation unique (TagEditor émet un tableau de libellés/{tag} ;
rétro-compat CSV) → réutilisée par jobDetail/pool/équipe/demande
- demande : d.skills passe de CSV à tableau (migration au chargement + CSV rétabli
à la génération des besoins)
RÉUTILISER les composants partagés (TagEditor pour toute saisie de compétences/tags),
ne pas reconstruire d'équivalent inférieur.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Chaque tech a ses propres toggles SMS/Courriel (cochés selon l'info dispo, SMS préféré) → on peut exclure
quelqu'un (ex. envoi test). Envoi scindé : 1 appel SMS (techs cochés+tél) + 1 appel courriel (cochés+email).
Bouton « Envoyer (N) » = nb réel de destinataires.
- Édition INLINE dans le dialogue : tech sans téléphone/courriel → champ + « Enregistrer » sans quitter
(roster.setTechContact → POST /roster/technician/contact → update Dispatch Technician + invalide le cache).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
jobDetail porte désormais .status ; TicketStatusControl (Dispatch Job) dans l'en-tête du volet job
(Reporter = replanifie scheduled_date +N · Fermer=Completed · Rouvrir=open). Le contrôle est maintenant
présent sur les 3 vues (page ticket · fiche client · Planification). Reste (séquencé) : convergence layout/sections #27.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
jdAddAssistant : dérive le nom du tech chargé OU du libellé de l'option choisie OU l'id (jamais undefined),
coerce tech_id en String → la réservation (bloc hachuré) se crée correctement. Puce : repli techNameById/id/—.
(DB vérifiée : 0 ligne assistant corrompue ; le « undefined » était transitoire à l'ajout.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- WeeklyScheduleEditor (lot) : recherche par nom + chips « par compétence » (sélectionne les techs qui l'ont)
+ boutons Tout / Aucun. Passe t.skills depuis openSchedGenBulk. Avant : liste de chips sans filtre.
- Grille : 2 semaines (days=14) par défaut, sélecteur « Semaine/2 sem. » retiré (évitait les changements d'étendue accidentels).
- (Rappel : onScheduleApply recharge déjà la grille → les quarts générés en lot apparaissent après application.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Vues (Semaine/Jour/Tournées) déplacées À GAUCHE, mises en évidence (view-switch) ; retrait du titre
« Planification » redondant (déjà au-dessus) → moins de déplacement d'éléments.
- « Publier » unifié en split-button : action principale + sous-options (☑ SMS · Publier au legacy).
- Picker « déplacer UNE job vers un autre tech » : filtré aux techs sélectionnés+compétents de la simulation
(+ « Afficher tous »). Avant : le repli montrait tout le monde (techsForJob n'expose pas .skills → covers échouait).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sous l'adresse, quand le job a des coordonnées : lien « Street View » (vue au sol, évaluer la difficulté)
+ « Carte / satellite » (Google Maps). Réutilise le format pano existant (openStreetView).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bug (Louis) : Stephane (réparation rang 1, installation secondaire) recevait 3 installations, William
(installation rang 1/principale) une seule. Cause : versatility_weight=6 > rank_weight=2 → un tech moins
polyvalent mais NON spécialiste rafflait les jobs hors de sa spécialité. Fix : rank_weight=20 (domine),
versatility_weight=3 (léger départage entre techs de même rang → réserve les experts). Testé : William obtient
les installations. eff/AM-PM/distance inchangés.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Retire le sélecteur de stratégies (5 modes) et les champs Coûts (Spécialité/Heures sup/km-h/Calcul s)
mal compris. Méthode unique = solveur VRP, forcée. Description claire des critères pris en compte.
(Backend : brancher l'efficacité par skill + réserve-experts dans le solveur = étape suivante.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le champ « Cadence » global était un FACTEUR brut (step 0.05, bas=rapide) → taper 1000 = facteur 1000
= catastrophiquement lent. Passé en % via effPctOf/factorFromPct (100/x) : 1000 % = facteur 0.1 = 10× plus
rapide, 90 % = plus lent. Renommé « Efficacité % » + tooltip. Le per-skill était déjà en % (inchangé).
NB : l'efficacité n'influe que sur les stratégies heuristiques (Intelligent/Meilleurs/Équilibré/Juste-ce-qu'il-faut),
PAS sur « Optimiser » (VRP OR-Tools) qui optimise routes+compétences+capacité sans facteur d'efficacité.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- nnOrder respecte la fenêtre horaire : bloc AM (8-12) → sans-fenêtre → PM (12-16), plus-proche-voisin
DANS chaque bloc. Avant : NN pur → une job PM pouvait finir 1re, une « premier en AM » 2e (bug signalé).
Utilise le toggle AM/PM explicite OU detectAmpm (texte : « **premier en AM** », « PM Fin de journée »).
- Picker « déplacer vers un autre tech » : n'affiche que les techs SÉLECTIONNÉS + compétents par défaut,
+ « Afficher tous les techniciens… » pour lever le filtre (repli sur tous si aucun sélectionné-compétent).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cause : :routes="suggestRoutes.map(...)" inline créait un NOUVEAU tableau à chaque rendu → watch deep
de RouteMap → draw → emit('metrics') → onRouteMetrics mutait realRoutes → re-rendu → nouveau tableau →
BOUCLE INFINIE (onglet figé ; le remplacement asynchrone OSRM ne finissait jamais → lignes à vol d'oiseau).
Fix : computed CACHÉ suggestMapRoutes (réf stable) + onRouteMetrics idempotent. La géométrie routière OSRM
(vérifiée côté serveur : 200, ~405 pts) s'affiche maintenant.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- 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>
À la publication (vue Tournées), bouton « Notifier » → aperçu par tech
PUIS envoi. Chaque tech reçoit :
- UN lien token personnel (techFieldLink = app terrain, ses billets du
jour à répondre),
- la liste de ses interventions avec ADRESSE + flags cruciaux (AM/PM ·
URGENT · Appeler avant) détectés du titre/notes/priorité.
Hub (lib/roster.js) : composeDispatchNotifs(dates, techIds) + route
POST /roster/notify-dispatch (preview=compose sans envoi | envoi SMS via
twilio + Email via lib/email.js, best-effort par tech, retourne compte +
erreurs). jobFlags() détecte AM/PM/urgent/appeler-avant.
Client : api roster.notifyDispatch ; dialogue APERÇU (toggles SMS/Courriel
+ liste par tech + corps du message, alerte si pas de tel/courriel) →
« Envoyer ». Aperçu d'abord = pas d'envoi accidentel.
Vérifié : hub node --check OK ; client rend le bouton + dialogue + toggles,
0 erreur. ⚠️ Le PREVIEW/envoi nécessite le déploiement HUB (nouvelle
route) — GATÉ + sortant. Faire un test 1 tech avant usage réel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>