Completes the jobs-to-assign pool unification whose PlanificationPage wiring
landed in 9c49054 (which referenced these files before they were committed):
- JobPool.vue: one shared component (floating/docked/mobile) replacing the 3
divergent pool implementations
- useJobPool.js: shared filter/sort/group/badge composable
- RouteMap.vue: live tech icons join the explode/fan; single-tech real Traccar
day-route layer
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>
Three fixes from live feedback:
1. techOccupancy counted only jobs WITH a start_time → legacy osTicket jobs
(assigned, no appointment hour) showed as free (e.g. Houssam "40h libre"
with 6 jobs). Now untimed assigned jobs count toward busy (rendered as
hatched "unscheduled" blocks) and jobs match by technician_id OR docname.
techOccupancy also accepts CSV skills (require ALL) for multi-skill needs.
2. Support now maps to a 'support' skill (was []), so it lists only
support-skilled agents — techs with no skills set are excluded. Added
'support' to SKILL_VOCAB + DEPARTMENT_SKILLS.
3. AvailabilityByReason skills list is editable: auto-added skills (e.g.
'sans-fil' from wireless service address) can be removed per-chip when
not relevant. Bands + summary now reflect ALL selected skills.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The recipient row's customer link was href="/#/clients/<id>" — with the app on
hash routing served under /ops/, a leading slash before # resolves against the
site root, dropping /ops/ and landing on ERPNext desk. Drop the leading slash
so it stays scoped under the current /ops/ path.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- rsvpLink() now appends &lang=<en|fr> when generating a recipient's personal
link, so the English invitation's link opens the English RSVP form instead
of defaulting to French. Threaded through sendTest and the mass-send worker.
- New optional per-language "address" field (editor input, SEED default for
fete-20-ans, Unlayer template var), rendered under the date with a pin icon
in both the festive email and the RSVP page.
- Logo: +20px bottom margin in both the email template and the RSVP page.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Frontend half of the campaign-integrated mass send: an "Envoyer à N
destinataire(s)" button in the send dialog's mass tab, gated behind a
confirmation dialog that shows the exact recipient count and channel before
firing. On success, links to the created campaign in /campaigns for tracking.
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".
- Email template: an event can use an editable campaign-style template (Unlayer) for
its invitation instead of the built-in festive builder. Config field email_template
('' = festive default/fallback). inviteEmail() renders the named template via
campaigns.renderNamedTemplate with {{firstname}}/{{rsvp_url}} (+ event vars), else
festive. campaigns.js: add 'event-' template prefix + export renderNamedTemplate.
New GET /events/:id/invite-preview (festive | configured | override) for live preview.
Editor gains a template selector, "design in editor" link, and an inbox preview dialog.
- Audience "Tous (incl. résiliés)": filters.include_resiliated skips the résilié guard
for win-back; "Clients actifs" still excludes them.
- Manual picker: realtime fuzzy search into a results list (add per row), separate pool,
then add-batch — replaces the combobox.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ops event page overhaul (hub lib/events.js + email.js, ops EventsPage.vue + api):
- Create/edit/delete events (persisted config shadowing the seeded SEED_CONTENT);
labels factored into shared LABELS, content editable bilingual (EN falls back to FR).
- Capacity cap by headcount: public RSVP form closes with a "booking is full" message
when reached (server-enforced); existing registrants can still update.
- Email attachments (image/PDF, <=4MB, <=6), tagged per language (fr/en/both) so each
customer gets files in their account language; optional Gemini auto-detect on images.
email.sendEmail gains a generic attachments[] param.
- Additive main audience list (persisted per event): build one deduped list by appending
batches from filters, manual picks, or CSV; per-source breakdown, remove/clear.
- Filters: name-contains (customer_name like) and city (Service Location.city, resolved
accent/case-insensitively via PG with REST fallback), intersected with other criteria.
- Manual picker + fuzzy search reuse app-wide /collab/customer-search (pg_trgm), so
"repa" finds "Réparation …".
- nginx /hub/ proxy: client_max_body_size 8m for base64 attachment uploads.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
- apps/ops/infra/nginx.conf brought back in sync with the live working conf
(was missing the /hub proxy + /auth/whoami → deploying the old template
would have broken OPS). Tokens are placeholders (__ERP_API_TOKEN__ /
__HUB_TOKEN__), never real values.
- deploy.sh now renders nginx.conf on every deploy: substitutes the tokens
from the server's /opt/targo-hub/.env (ERP_TOKEN = service account, not
Administrator; HUB_SERVICE_TOKEN), validates via throwaway nginx -t, then
restarts ops-frontend. Fixes the "nginx.conf deleted from host → OPS down
on restart" landmine and makes token rotation a one-command redeploy.
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>
Converge every ticket-detail surface onto the canonical IssueDetail (in
DetailModal) + TicketCard, per reference_ticket_detail_disparities.
STATUS — one model:
- IssueDetail no longer double-renders status. The green button (shared
TicketStatusControl) is relabelled "Reporter" with a schedule icon — it is
a postpone/snooze + quick-close control, not a 2nd status setter. The single
5-value status select (Open/Replied/On Hold/Resolved/Closed) is canonical.
TAGS — one source, via the shared TagEditor:
- New useTagCatalog composable unifies the tag catalogue: ERPNext "Dispatch Tag"
∪ roster technician skills (hub /roster/technicians). Roster skills like
"sans-fil"/"épissure" now appear on ticket tags (were missing before).
- IssueDetail replaces its bespoke q-select + chip row with TagEditor; the tag
manager (rename/recolor/delete) is delegated to the composable's CRUD.
ONE ticket-row component (TicketCard):
- LocationCard's ad-hoc per-address ticket list now renders TicketCard.
- TicketsPage mobile card mode (DataTable #item slot) renders TicketCard.
- assignee handling (assigned_staff/opened_by_staff avatars) is now consistent
across fiche, location, and /tickets.
Verified in local preview against live backend: Reporter button + single
status select, TagEditor dropdown surfacing roster skills ("sans-fil" under
"Compétence"), TicketCard rows on fiche + LocationCard, no component errors.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le panneau (IssueDetail dans DetailModal, ouvert depuis /tickets ou la fiche client) ne
montrait pas le tech assigné — seulement `_assign` (agents) + `assigned_tech` enfoui dans
l'arbre des tâches. Ajout d'une bannière « Technicien » en tête : chips (initiales + nom)
des techs des Dispatch Jobs liés (`assigned_tech`, déjà fetchés) + staff legacy
(`doc.assigned_staff`, ce que montrent les lignes de liste), dédupliqués ; « Non assigné »
si des jobs existent sans tech. Réutilise le `initials()` local. (1/4 de la standardisation
des détails ticket — reste en session dédiée, cf reference_ticket_detail_disparities.)
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>
Suite de la consolidation des énumérations partagées (après priorités).
- config/job-types.js (NOUVEAU) : JOB_TYPE_OPTIONS + JOB_TYPES (Installation/Réparation/
Maintenance/Retrait/Dépannage/Autre) = SOURCE UNIQUE
- élimine 3 copies : wizard-constants (ré-exporte désormais), TaskNode, UnifiedCreateModal,
flow-editor/kind-catalogs (JOB_TYPES)
- LAISSÉS volontairement (vocabulaires DISTINCTS, pas des doublons) : statuts de job
(cycle de vie ≠ actions du pool ≠ ticket ≠ campagne ≠ appareil) ; issueTypeOptions ;
jobTypes de l'app terrain (valeurs anglaises Repair/Delivery/Other — à réconcilier
séparément, risque de données)
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>
Nouveau TicketStatusControl (doctype-conscient Issue/Dispatch Job) : Reporter +1j/+7j/date · En attente
d'un autre ticket #X · Fermer/Rouvrir. Reporter = On Hold + note datée (job = replanifie scheduled_date).
Posé sur IssueDetail (page ticket + fiche client) en remplacement des boutons Fermer/Rouvrir épars.
Prochaine étape (séquencée) : câbler sur le panneau job (Planification) + convergence des 3 vues (#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>
Le @navigate du panneau ticket routait tout vers loadModalTicket ; désormais dt==='Customer' → fiche.
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>
Ajoute assigned_staff à la requête + colonne Assigné (avatar initiales, couleur par tech) ;
issue_type renommé Type→Département. Distingue les tickets quasi identiques (ex. 3 Cancellation).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- TicketCard : badge Département (issue_type) + technicien (assigned_staff) → distingue les
tickets quasi identiques (ex. 3 « Cancellation » = Support/Facturation/Rétention).
- migrate_tickets.py : importe assigned_staff (ticket.assign_to → nom du staff) — le rattrapage
ne le posait pas. Backfill des ~7,5k tickets assignés en F mais sans technicien côté ERPNext.
- logToLinkedTicket : direction Received si l'expéditeur = adresse destinataire du ticket
(une réponse depuis un domaine interne @targo n'est plus mal classée « Sent »).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Hub (conversation.js) : getOrCreateConvForIssue (lie une Issue à une conv email) + emailTicket
(envoie dans un fil Gmail, sujet [Ticket #ISS-…] + lien direct, journalise Sent Communication).
Route POST /conversations/ticket-email (gaté Authentik). La réponse du client re-thread par le tag
sujet (TICKET_REF_RX) OU le threadId → logToLinkedTicket (déjà en place) → Communication Received
sur l'Issue → visible au panneau. notifyCustomer mémorise désormais conv.threadId au 1er envoi.
UI (IssueDetail) : bouton « Envoyer par courriel » + dialogue (À/Cc/message), « Répondre » scindé
en « Note interne » (Communication) vs courriel. Lien direct = URL du ticket.
Testé : envoi réel à louis@targo.ca sur ISS-0000250013 (channel:email, Sent Communication OK).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- richHtml() (sanitize.js) : décode entités osTicket (é, ', double-échappées &#039;)
puis DOMPurify → plus de « codes html » dans les détails.
- IssueDetail : fusionne Échanges (Communication) + Fil (Comment) en UN fil chronologique
en bulles (notes internes en ambre) ; aère la mise en page.
- Bandeau Client cherchable par nom, association/dissociation 1 clic (comme une conversation)
— le ticket portait déjà .customer mais aucun champ pour le voir/lier. Résout le cas voicemail.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
JSX collapses the newline between {price} and <sup>.cents</sup>; a Vue template
renders it as a visible space. Made the dollar amount and the <sup> cents
adjacent in Services/Telephone/Television price cards. Verified: $39.95/$25.00/$10.00.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>