Commit Graph

173 Commits

Author SHA1 Message Date
louispaulb
ade7f312ec feat(planif): garde l'encadré sur la cellule tant que son menu d'édition de quart est ouvert
Quand on ouvre le menu contextuel d'édition de quart sur une cellule, la cellule reste maintenant
encadrée (contour bleu + fond léger) tant que le menu est ouvert → on voit clairement quelle cellule
on édite. Classe .cell-editing pilotée par menu.show + menu.tech/menu.day.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 22:09:13 -04:00
louispaulb
0d4996e240 fix(ui): icône corbeille pour les suppressions (au lieu d'un X ambigu avec « fermer »)
Un X sur une action de SUPPRESSION prête à confusion avec le X de fermeture d'un menu/fenêtre.
Toutes les commandes qui SUPPRIMENT/retirent un élément passent de icon="close" à icon="delete"
(corbeille) : WeeklyScheduleEditor (retirer quart), ConversationPanel (supprimer note),
PlanifCellMenu (retirer quart), FunctionCatalogDialog (supprimer rôle), TechScanPage (retirer
code-barres), EventsPage (retirer d'une liste/pool/programme/note), PlanificationPage (étape de
garde, deleteDayAsg ×2). Les 2 actions « retour au pool » (removeFromPush/removeFromDay) passent à
icon="inbox" (renvoi au pool — PAS une destruction). Les clears de filtre/champ libellés « Effacer »
restent inchangés (ce ne sont pas des suppressions d'élément).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 21:57:43 -04:00
louispaulb
53cf52bb13 feat(planif): fenêtre usuelle APPRISE + persistée par tech (préréglage sur jours vides)
Un jour VIDE (ou une semaine future non planifiée) ne voyait aucun quart → le préréglage retombait
sur 8–16 générique. Désormais on APPREND la fenêtre la plus fréquente de chaque tech depuis ses vrais
quarts à chaque loadWeek et on la PERSISTE (localStorage roster-usual-win-v1). Sur un jour sans quart,
le préréglage propose donc le « quart normal le plus probable » du tech (ex. Houssam → 8–18), même
sur une semaine future. Priorité : perso enregistré > usuel appris > patron hebdo > 8–16.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 20:48:03 -04:00
louispaulb
d988becfba feat(planif): préréglage Jour pré-rempli avec la fenêtre USUELLE du tech (P8)
Le préréglage « Jour » n'est plus le générique 8–16 : par défaut = la fenêtre la plus probable
du tech (la plus fréquente de son patron hebdo weekly_schedule, sinon de ses quarts réguliers
déjà assignés), ex. Houssam → 8–18. Toujours surchargeable via ✎ (perso enregistré prioritaire).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 11:55:30 -04:00
louispaulb
9e91ba6791 fix(planif): colonne Ressource gelée AU-DESSUS des calques de cellule (A6, z-index)
Vraie cause : les calques de cellule-jour (.ovl-warn triangle, .tl-fn-in rôle, .office-blk
« sans déplacement » = z-index 3 ; .drop-badge = 6) passaient PAR-DESSUS la colonne gelée
(.tech-col z-index 2) au défilement → ils recouvraient le nom du tech. Colonne gelée montée
à z-index 8 (en-tête 9) → elle recouvre désormais tout le contenu de cellule qui glisse dessous.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 11:38:57 -04:00
louispaulb
1ebf182c29 fix(planif): clippe le triangle de surcharge + l'alerte hors-quart dans la cellule (A6)
`.ovl-warn` (triangle surcharge) et `.offshift-warn` vivent hors de `.tl` (enfants directs
de `.cell`, sans overflow) → ils débordaient. `.cell { overflow:hidden }` les confine comme
les blocs de quart. `.drop-badge` repositionné à top:1px (au lieu de -8px) pour survivre au clip.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 11:32:23 -04:00
louispaulb
94da547b1c fix(planif): l'œil n'est plus poussé/clippé par un long tag groupe (A6)
Régression du lot précédent : `.tech-col { width:360px; overflow:hidden }` coupait l'œil
quand le contenu dépassait 360px (ex. groupe « Vente porte-à-porte », flex-shrink:0 sans max-width).
- retire la largeur fixe + overflow:hidden sur .tech-col (la colonne s'ajuste, l'œil reste visible)
- .grp tronque (max-width 90px + ellipsis, rétrécissable) → ne pousse plus l'œil
- .hide-eye épinglé (flex-shrink:0) au bout de la rangée

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 11:19:09 -04:00
louispaulb
71285e4ef8 feat(dispatch): lot d'améliorations Planification (doc « Remarques » + captures)
Bug Gigafibre.ca — corrigés/ajoutés :
- P1 réassignation « Non assignés » (le menu placeholder n'exige plus l'union des compétences → assignable à la main, ⚠ par job)
- P2 tech en congé/vacances exclu du pool de dispatch (garde suggestUnavailable + pastille congé)
- P3 cadence MASQUÉE par défaut (tous 100 %), révélée seulement si nettement plus lent/rapide
- P5 l'auto-save (draft) ne supprime plus un quart PUBLIÉ (cause du « publié puis à republier »)
- P6 récap jobs simplifié (type·ville·rue·AM/PM·#) dans la modale Techniciens disponibles
- P8 plages perso Jour/Soir PAR TECH (presets personnalisables, ex. 7:30–17:30)
- A1 re-clic sur un tech (q-menu no-parent-event) · A4 type de job au survol · A5 « Vider » visible
- A3 modem/ONU/remplacement → réparation (skill-resolver = source unique ; copie legacy périmée supprimée)
- A6 clip colonne Ressource + label de rôle (barre de défilement horizontale proportionnelle)
- A7 techs par compétence (survol) · A8 « Donner à » trié par compétences en commun

Note : ces fichiers portaient déjà des changements déployés-non-commités ; ce commit les RÉCONCILIE
avec le lot ci-dessus (co-édition — cf. reference_coedit_uncommitted_audit).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 11:03:24 -04:00
louispaulb
788d969090 chore(reconcile): commit MES hunks déployés dans les fichiers co-édités (split via git apply --cached)
Réconciliation de l'audit : isole et commite UNIQUEMENT mon travail déployé-non-commité dans 7 fichiers co-édités,
sans toucher au working tree (donc sans capturer le travail en cours des autres sessions, qui reste non commité) :
- server.js : SSE event 'ready' + retry:3000 ; routes /olt/onu/plan|run + /olt/wifi-clients.
- conversation.js : endpoint /conversations/my-inbox-counts (accueil : en attente / suivis / en retard).
- useConversations.js : armSSE (reconnexion+resync+repli poll 25s) + cleanup.
- PlanificationPage.vue : chips géofence (Lane 1c) + sélecteur « Générer l'horaire » (Move 3) + isLate (arrivée en retard).
- SettingsPage.vue : section « Thème & couleurs » (ThemeEditor).
- IssueDetail.vue + ConversationPanel.vue : props source-issue/customer/service-location/phone (« Proposer au client »).
EXCLUS (restent non commités, propriété d'autres sessions) : /service-location, /conversations/msg-visibility, retrait
champ Mapbox, fix conv-message 'belongs', hiérarchie parent_incident, JobMediaModule, etc. Vérifié : 0 marqueur étranger
dans le staged. Tout est déjà LIVE ; ceci n'aligne que le dépôt.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 20:06:47 -04:00
louispaulb
ef7b51399d refactor(planif): « Bloquer du temps » = UN job/ticket qui occupe la timeline, priorité = curseur dur/souple
Suite retour Louis : plus de mode soft/hard (toggle du commit précédent retiré). Un
seul concept — un job/ticket interne qui OCCUPE la timeline (formation · réunion ·
projet · entretien) → dé-priorise le tech au dispatch. La PRIORITÉ est le curseur :
Moyenne/Basse = les urgences/réparations peuvent quand même prendre le tech ; Urgent
(high) = bloc dur, non traversé.

Front : dialogue unique « Bloquer du temps » (Intitulé · journée complète · date ·
début · durée · Priorité [défaut Moyenne] · ticket optionnel), info adaptée à la
priorité. Un seul submit `doBlockTime` → /roster/filler-job. Retiré : bascule de mode,
branche réserve, `roster.reserveTech`. Les 2 points d'entrée (bouton volet réglages +
menu de cellule) → même dialogue, relabellés « Bloquer du temps… ».

Hub (dispatch.js suggestSlots) : la traversée urgence passe du flag `job_type ===
'Réservation'` à la PRIORITÉ — `isSoftBlock` = bloc interne (Interne/Réservation) de
priorité ≤ moyenne → traversé en mode urgent ; high résiste ; les vrais jobs client ne
sont JAMAIS traversés (pas de double-booking). Ajout du champ `priority` au fetch.
(/roster/reserve laissé en place, inutilisé — retrait ultérieur.)

Vérifié : test unitaire pierce (medium/low traversé · high résiste · job client jamais)
+ aperçu (dialogue unique, priorité Moyenne, info souple, pas de toggle). Build OK.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 18:38:17 -04:00
louispaulb
ba6967adc6 refactor(planif): fusion « Réserver du temps » + « Bloquer / job générique » en UN dialogue
Deux dialogues faisaient la même chose (rendre un tech indisponible pour un bloc)
avec des champs ~80% identiques, des points d'entrée différents (bouton volet
réglages vs menu de cellule) et aucune explication de leur différence → lus comme
un doublon. Seul vrai écart : bloc DUR (hors dispatch, ticket optionnel) vs réserve
SOUPLE (dé-priorise, urgences passent).

→ UN dialogue « Réserver / bloquer du temps » avec bascule de MODE (segmented
control : « Hors dispatch (bloc) » / « Urgences OK (souple) »). Champs communs
(tech · date · début · durée · journée complète · motif/intitulé) ; priorité/type/
ticket seulement en mode bloc. Soumission `doBlockOrReserve` → /roster/reserve OU
/roster/filler-job selon le mode (endpoints inchangés). Les 2 points d'entrée
(bouton « Réserver / bloquer… » du volet réglages [souple par défaut] · item de menu
de cellule [bloc par défaut, pré-rempli]) ouvrent le MÊME dialogue.

Supprimé : resvDlg + doReserve (le dialogue « Réserver » séparé). Aperçu vérifié :
bascule de mode montre/masque priorité/type/ticket + change libellé/info/bouton.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 14:52:12 -04:00
louispaulb
e8fd69c6f8 feat(planif): pause indéfinie STAGÉE → visible tout de suite, commise au Publier (retire quarts futurs + relève jobs)
Une mise en pause indéfinie ne s'applique plus immédiatement : elle est mise en
attente (pendingPause, localStorage — comme les congés), prend effet VISUELLEMENT
(nom barré + puce « pause à publier » sur la rangée, compteur « non publié »), et
n'est COMMISE qu'au Publier — au cas où on se trompe.

- TechScheduleDialog : le toggle « Pause indéfinie » émet `stage-pause` (plus
  d'écriture roster directe) ; l'état reflète serveur OU en attente (prop pendingPaused).
- Parent onStagePause : stage/dé-stage ; réactivation d'un tech DÉJÀ en pause reste
  immédiate (Disponible + re-matérialisation du patron).
- Résumé de Publier (groupé par tech) : ligne « Pause indéfinie · motif · N quart(s)
  futur(s) retiré(s) » + chaque quart futur listé en retrait ; en-tête « · N pause(s) ».
- doPublishConfirmed : exclut les quarts futurs (aujourd'hui→) des techs en pause de la
  charge → publish-week les SUPPRIME ; applique le statut En pause ; puis relève des jobs
  via l'impact IROPS existant (checkAbsenceImpact). Quarts passés conservés ; patron
  récurrent intact (réactivation re-matérialise).

Vérifié en aperçu (client-only, RIEN écrit en prod) : Philippe Bourdon → puce +
nom barré ; résumé « Pause indéfinie · Arrêt maladie · 14 quart(s) futur(s) retiré(s) »
+ 14 retraits, à côté du quart de Nathan Morrisseau ; annulé + nettoyé. Build OK.
⚠️ Chemin de COMMIT au Publier (suppression quarts + pause + relève jobs) non exercé en
prod (écriture réelle) — réutilise publishWeek/pauseTechnician/checkAbsenceImpact éprouvés.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 13:33:08 -04:00
louispaulb
947595f086 feat(planif): résumé de Publier complet + groupé par technicien
« Toute modification doit être résumée avant d'être publiée (au cas où on se
trompe). » Le résumé était incomplet et groupé par date :
- retraits de quarts INVISIBLES (pubSummary.removed codé en dur à 0) alors que
  le hub SUPPRIME au Publier tout quart absent de la charge (publish-week) —
  une suppression accidentelle passait donc inaperçue ;
- groupé par date, difficile de voir ce qui change POUR UN TECH.

pubSummary regroupe désormais PAR TECHNICIEN (avatar + nom) et couvre tout ce
que Publier va appliquer : quarts à publier (ajouts/modifs), quarts RETIRÉS
(diff contre l'instantané serveur du chargement, serverSet → capte les
suppressions), et congés/absences en attente. En-tête : « N quart(s) · R
retiré(s) · A congé(s) · L levé(s) ». Un tech sans changement n'apparaît pas.
(La garde a son propre bouton « Publier la garde » — hors de ce flux.)

Vérifié : repro logique (modif = −ancien/+nouveau, retrait, congés, tech
inchangé omis) + aperçu live (Josée-Anne : Congé 14/07 + Maladie 15/07 ;
Nathan Morrisseau : quart 8h–16h) — rien écrit en prod (annulé). Build OK.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 13:06:05 -04:00
louispaulb
9d568986fc fix(planif): afficher les congés en attente dans le résumé de Publier
Le résumé de « Publier l'horaire » (pubSummary) était construit UNIQUEMENT à
partir des quarts non publiés (unpublished) et ignorait pendingAbs. Un congé
mis en attente (ex. Josée-Anne, congé indéterminé) allumait bien le badge
« non publié » ET était bien envoyé au Publier (flushPendingAbsences), mais
n'apparaissait PAS dans le dialogue de confirmation — on n'y voyait que le
changement de quart (Nathan Morrisseau). Le répartiteur ne pouvait donc pas
réviser le congé avant de confirmer.

pubSummary intègre désormais pendingAbs, groupé par date, avec compteurs
absAdded/absRemoved ; l'en-tête affiche « N quart(s) · M congé(s) » et chaque
jour liste les congés (event_busy orange = ajout · event_available teal =
retrait) à côté des quarts.

Vérifié (aperçu, client-only, rien écrit) : congé indéfini Josée-Anne 13-17/07
→ résumé « 1 quart(s) · 5 congé(s) », lignes Congé par jour à côté du quart de
Nathan Morrisseau.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 12:29:31 -04:00
louispaulb
c7c9f81364 fix(planif): Suggérer = jobs DUS le jour sélectionné · jour partagé entre vues (session) · quarts brouillon reflétés au calendrier mois
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>
2026-07-14 22:17:12 -04:00
louispaulb
48edede793 refactor(planif): désencombrer l'en-tête selon UX_REFACTORING_PROMPT (PL1/PL2/PL4/PL5/PL6)
- PL5 : sous-menu « Modèles de semaine » (menu-dans-menu) → dialogue dédié
  components/planif/WeekTemplatesDialog.vue (props données / emits save·apply·
  set-default·delete ; état + mutations restent dans la page, EmptyState réutilisé)
- PL2 : menu Outils 13 items + sous-menu → 11 items à plat ; retrait des 2 entrées
  redondantes avec la barre (« À assigner », vue Mois)
- PL1/PL4 : barre allégée — bouton ★ modèle par défaut déplacé dans le ⋮ (groupé)
  + ⌘K (planif-templates, planif-apply-default) ; HelpHint « Suggérer » retiré
  (l'explication vit déjà dans le bandeau du dialogue)
- PL6 : champ orphelin « Max h/sem » supprimé (ref jamais consommée = code mort)
- docs : statuts PL1/PL2/PL4/PL5/PL6 mis à jour dans UX_REFACTORING_PROMPT.md

Vérifié : build SPA OK · aperçu 9001 — Outils/⋮/dialogue exercés, chaîne emit
save → prompt parent OK, grille Semaine + Tournées intactes, 0 nouvelle erreur
console (les « Anchor: target null » préexistent au baseline).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 20:56:34 -04:00
louispaulb
d0f899cf6d feat(ops): staff console — surface dup accounts + per-user schedule deep-link
- 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>
2026-07-10 13:44:56 -04:00
louispaulb
9c49054b8d feat(ops): quick filler job from cell menu (blocks tech from dispatch)
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>
2026-07-09 19:58:18 -04:00
louispaulb
9145624949 feat(ops): unify tech skill/cadence editor + merge team tools
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>
2026-07-09 14:57:01 -04:00
louispaulb
3489576212 Planif: congés publish-required, menu de cellule partagé, vue Mois
- 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".
2026-07-09 11:32:54 -04:00
louispaulb
893ac8cd4a Déclutter: primitives réutilisables + palette ⌘K + Planif épurée
Ajoute 3 primitives partagées pour standardiser le désencombrement
(philosophie 3 paliers : coup d'œil → survol → détail) :
- OverflowMenu.vue : « ⋮ » standardisé (une action primaire visible,
  le secondaire dans le menu).
- DisclosureSection.vue : section repliée par défaut (résumé + corps
  révélé), état persisté par utilisateur (useUserPrefs).
- CommandPalette.vue + useCommandPalette.js : palette globale ⌘K
  (actions rapides + pages + actions contextuelles de page + recherche
  clients/équipe), registre d'actions par page = escape hatch.

Câblage MainLayout : palette montée + raccourci Cmd/Ctrl+K + indice ⌘K
dans la recherche desktop.

Adoptions :
- RapportsPage : PageHeader + stubs « Opérations » repliés + rapports
  enregistrés dans la palette.
- DashboardPage : bloc admin replié (DisclosureSection) + PPA rétrogradé
  dans un OverflowMenu (anti clic accidentel) + actions dans la palette.
- PlanificationPage : le panneau « Jobs à assigner » n'ouvre plus au
  chargement ; alerte « N à assigner » par jour (clic → panneau filtré) ;
  undo/redo/refresh regroupés dans un OverflowMenu ; actions dans la palette.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 06:53:24 -04:00
louispaulb
f4a6d07a8b feat(ops): reason → skill → skill-aware availability module
Bridge reason → required skill → availability so the occupancy denominator
reflects the skill a job needs ("70 h installation" vs "150 h all techs").

Hub:
- lib/skill-resolver.js (NEW, SOURCE UNIQUE): shared techHasSkill(s) predicate,
  deptToSkill (moved here, re-exported from roster.js — no cycle),
  DEPARTMENT_SKILLS bridge, resolveSkills({text,department,jobType,useAI})
  (chip → rule → Gemini fallback, reusing classifyEmail pattern).
- roster.js: capacityByDay(start,days,skills) filters denominator to qualified
  techs (+ guards used/due). New POST /roster/resolve-skills, /roster/capacity?skill=.
  Backward-compatible (no skill = prior behavior).
- dispatch.js: techOccupancy/suggestSlots reuse the shared predicate.

SPA:
- config/departments.js: skills[]+dur per department + skillsForDepartment();
  added Réparation reason.
- components/shared/OccupancyBands.vue (NEW): vertical-strip grid extracted from
  SuggestSlotsDialog (now reuses it).
- components/shared/AvailabilityByReason.vue (NEW): reason chips + dictate→AI →
  resolved skill → skill-filtered bands + adjusted-denominator summary.
  Mounted in ClientDetailPage, IssueDetail, ConversationPanel.
- api/roster.js: getCapacity(start,days,skill) + resolveSkills().
- Planif + Dashboard capacity bands pass skill to /roster/capacity.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 18:33:30 -04:00
louispaulb
6bf323b18e fix(ops): champ d'assignation — échelle d'implication + bascules « moi » + fix lecture équipe
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>
2026-07-08 17:40:36 -04:00
louispaulb
2f81cff892 feat(ops): champ d'assignation MULTIFONCTION (À / CC / #) partagé
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>
2026-07-08 16:49:57 -04:00
louispaulb
844eff9a49 fix: capacité réelle (hors congé/pause/archivé), archive via store hub, calendrier horaire tech
- 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>
2026-07-08 14:46:21 -04:00
louispaulb
13039be301 feat: suivi généralisé, occupation ressources, horaire tech, revert journal
Follow / subscribe (au-delà des tickets)
- hub: store généralisé /conversations/follow {doctype,name} (follows.json,
  migre ticket_follows.json sous Issue) ; ticket-follow = alias Issue
- InterveneDialog: tuile « Suivre » câblée (toggle + état) ; TicketsPage sync

« Trouver un créneau »
- hub: /dispatch/occupancy (techOccupancy) — occupation par tech filtrée skill
- SuggestSlotsDialog: panneau occupation (bandes verticales) + props de
  pré-remplissage (adresse/coords/skill)
- ClientDetailPage: entrée directe « Trouver un créneau » (appel/réparation)
- fix moteur créneaux: suggestSlots + techOccupancy respectent désormais les
  congés (Tech Availability approuvé), « En pause » et « Archivé »

Horaire par technicien (components/planif/TechScheduleDialog)
- écran unique: pause indéfinie + motif · horaire récurrent (réutilise
  WeeklyScheduleEditor) · calendrier du mois (cases carrées, glisser-
  sélectionner + pinceau congé/maladie/indispo/effacer) · archivage réversible
- hub: /roster/technician/:id/archive (status Archivé) + exclusion au fetch
  (_fetchTechniciansRaw) + /roster/technicians?archived=1 (restauration)
- icône « event » de la rangée tech remplace le bouton pause

Journal des changements (icône history)
- chaque changement de quart révertible individuellement (bouton « annuler »),
  à la façon de la liste « Publier »

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 12:40:22 -04:00
louispaulb
77343d0a98 planif : chip Cc (assistant) affiche les initiales en pastille
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>
2026-07-08 10:25:48 -04:00
louispaulb
915a898c04 planif : assignation ticket façon courriel To/Cc + décodage entités à la source du pool
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&#039;é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>
2026-07-08 00:13:21 -04:00
louispaulb
92ed0a0734 planif (feuille job) : décode les entités HTML de l'en-tête (sujet · client · adresse)
L'en-tête de la feuille job affichait les champs bruts → « 424 chemin de l&#039;é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>
2026-07-07 23:50:42 -04:00
louispaulb
23e3c0d2e2 planif (feuille job mobile) : commentaire interne par défaut — plus de courriel client accidentel
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>
2026-07-07 23:43:53 -04:00
louispaulb
d8325f7e55 planif (vue semaine) : retire le faux avertissement 72h + élargit la colonne tech
- 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>
2026-07-07 22:33:17 -04:00
louispaulb
2396c10f14 planif : extraction #4/3 — dialogue Types de shift → components/planif/ShiftTypesDialog.vue
3e extraction. Couplage traité proprement : le dialogue édite les Shift Template et
la liste `templates` est PARTAGÉE (grille/garde/demande) → le composant émet `changed`
et le parent recharge via refreshTemplates.
- components/planif/ShiftTypesDialog.vue (NOUVEAU) : editTpls + newTpl + newTplRange +
  save/add/del déplacés ; semé depuis la prop `templates` à l'ouverture (watch) ;
  après add → refetch local ; helpers copiés (calcHours/fmtH/hToNum/numToTime/chip) ;
  CSS .demand-tbl + .code-chip recopiés
- PlanificationPage : dialogue + état + 4 fonctions retirés ; `chip()` supprimé (seul
  consommateur déplacé) ; helpers partagés (calcHours/fmtH/hToNum/numToTime) CONSERVÉS ;
  bouton « Types de shift » → `showShiftEditor = true` ; `<ShiftTypesDialog @changed="refreshTemplates">`
- Vérifié en aperçu : 20 modèles rendus (Jour/Soir/Matinal…), pastille couleur stylée, 0 erreur

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 22:20:45 -04:00
louispaulb
4bed6bb9fd planif : extraction #4/2 — dialogue Synchroniser les techniciens → components/planif/TechSyncDialog.vue
2e extraction de la décomposition (couplage nul : ne lit AUCUN état partagé).
- components/planif/TechSyncDialog.vue (NOUVEAU) : reactive techSync + techSyncSelCount +
  applyTechSync déplacés ; rapport self-fetch à l'ouverture (watch modelValue) ; v-model
  pour l'ouverture ; émet `applied` après création → le parent appelle loadBase
- PlanificationPage : dialogue + reactive + 2 fonctions retirés ; menu Outils → `showTechSync = true` ;
  `<TechSyncDialog v-model="showTechSync" @applied="loadBase" />`
- Vérifié en aperçu : ouvre via le menu, rapport chargé (staff 53 · fiches 56 · tech 41), 0 erreur

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 22:02:10 -04:00
louispaulb
659cfae12c planif : extraction #4/1 — dialogue Congés & disponibilités → components/planif/LeaveDialog.vue
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>
2026-07-07 21:45:22 -04:00
louispaulb
987ae98bfc planif : éditeurs de compétences unifiés sur TagEditor (fin de SkillSelect)
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>
2026-07-07 21:06:15 -04:00
louispaulb
7ca3ceef66 ops : priorités 3 niveaux (source unique) + compétences multi + création unifiée + planif
- config/dispatch-priority.js (NOUVEAU) : DISPATCH_PRIORITIES = SOURCE UNIQUE des 3 niveaux
  (Urgent/Moyenne/Basse, couleurs 🔴🟠) — élimine 5 copies locales (UnifiedCreateModal,
  TaskNode, CreateOfferModal, TechJobDetailPage, PlanificationPage POOL_PRIOS) et corrige
  la dérive « Moyenne » bleue de CreateOfferModal
- PlanificationPage/TechJobDetailPage : éditeur de compétences SkillSelect → /roster/job-skills ;
  FIX : SkillSelect émet une CSV (pas un tableau) → les handlers n'effaçaient plus les compétences ;
  panneau job (statut/reporter/réserver un tech) ; sommaire avant publication + auto-save + journal + annuler
- UnifiedCreateModal/ProjectWizard/QuoteWizard/TaskNode : formulaire de création unifié,
  divulgation progressive, dédup client (courriel/téléphone) + message d'erreur convivial ;
  suppression de NewTicketDialog (fusionné)
- IssueDetail : fil de ticket (nom d'expéditeur) ; app terrain lecture/commentaire
- useConversationDisplay/useHelpers/wizard-constants : priorités alignées sur 3 niveaux

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 20:46:57 -04:00
louispaulb
0ba789a5c0 planif Notifier : toggles SMS/courriel PAR TECH (SMS préféré) + édition inline du contact
- 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>
2026-07-07 14:04:11 -04:00
louispaulb
d6db1a5fa4 planif: contrôle Statut/Reporter aussi sur le panneau job (3 vues couvertes)
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>
2026-07-07 13:57:41 -04:00
louispaulb
8a655f9933 planif: assistant renfort — nom robuste (jamais « undefined »)
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>
2026-07-07 13:36:43 -04:00
louispaulb
969d5af9f9 planif: horaires (lot) — recherche/skill/tout-aucun ; grille 2 sem. par défaut (sélecteur retiré)
- 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>
2026-07-07 12:56:17 -04:00
louispaulb
712f113e29 planif barre d'outils : sélecteur de vue à gauche, Publier unifié, picker single-job filtré
- 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>
2026-07-07 12:41:37 -04:00
louispaulb
9f2fb0eaf9 planif: liens Street View + Google Maps/satellite dans le volet détail job (géoloc)
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>
2026-07-07 12:21:15 -04:00
louispaulb
4ae2907172 solveur : le RANG (compétence principale) domine la polyvalence — répare la mauvaise répartition
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>
2026-07-07 12:10:37 -04:00
louispaulb
88a5e84f90 planif Suggérer : UNE seule méthode (VRP) + retrait des réglages de coûts
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>
2026-07-07 11:47:17 -04:00
louispaulb
61a2f1000f planif: efficacité GLOBALE en % intuitif (haut = plus rapide), cohérent avec le per-skill
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>
2026-07-07 11:19:29 -04:00
louispaulb
3a0b65942c planif Suggérer : ordre AM→PM des arrêts + picker « déplacer vers » filtré
- 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>
2026-07-07 11:14:07 -04:00
louispaulb
746a8de6df planif: fix figement carte des tournées suggérées (boucle de rendu) + tracés routiers réels
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>
2026-07-07 10:57:39 -04:00
louispaulb
8586b9310a 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>
2026-07-07 09:56:12 -04:00
louispaulb
042c5545f7 ops: MapLibre/OSM basemap (drop Mapbox) + satellite toggle, unified Créer flow, wizard pricing, infra-first reverse client
- Maps: MapLibre GL + self-hosted Protomaps tiles + ESRI satellite toggle
- FAB 'Créer' chooser (incl. Soumission wizard) via useCreateSignal
- Quote wizard: new default tiers (80/500/1500 @ 39.95/49.95/49.95), competitor
  price-match, mini-map pin w/ green check, carry qualified address
- api/address.reverse() + reverseGeocodeOSM tries RQA-backed hub, Nominatim fallback

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 21:52:14 -04:00
louispaulb
7da4b1289c feat(ops+hub): notify techs of their tournée (SMS/email + token link + flags)
À 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>
2026-07-06 09:24:21 -04:00