louispaulb
eb8376c9c3
app terrain : commentaire NATIF (reste sur OPS) au lieu du renvoi vers store.targo.ca
...
- field-app : « đŹ RĂ©pondre » (lien legacy store.targo.ca) â « đŹ Commenter » = boĂźte native (textarea + case
« envoyer au client (public) ») qui POST /field/comment. Retrait du lien reply legacy.
- Hub /field/comment (token tech) : ajoute au FIL du ticket. Par défaut = NOTE INTERNE (jamais au client) ;
case public cochĂ©e â rĂ©ponse publique (courriel client) via postTicketLegacy(public). Job natif â Comment ERPNext.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 14:16:24 -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
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
8ee4c834f8
planif: retirer les jobs faits/annulés datés dans le FUTUR (artefacts legacy) de l'occupation
...
occupancyByTechDay incluait Completed/Cancelled (trace du tech). Un ticket F fermé mais à due_date
FUTURE â le pont crĂ©e un Dispatch Job 'Completed' datĂ© au futur (ex. LEG-250851, Completed, 2026-07-08)
â il apparaissait comme travail Ă venir (surtout visible sur mobile, oĂč le desktop l'estompe).
Fix : on écarte les jobs faits/annulés dont scheduled_date > aujourd'hui (Est) ; la trace passée/du jour reste.
Vérifié : 250851 absent de l'occupation aprÚs fix.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 12:04:15 -04:00
louispaulb
8b0241673d
solveur VRP : efficacité par compétence + réserve des experts (polyvalence pénalisée)
...
route_solver.py :
- dimension Temps PAR VĂHICULE (AddDimensionWithVehicleTransits) : service Ă efficacitĂ©(tech,skill)
â un tech rapide (facteur bas) tient plus de jobs.
- coĂ»t d'arc : + efficacitĂ© (rapide rĂ©compensĂ©) + pĂ©nalitĂ© de POLYVALENCE (nb de compĂ©tences) â les cas
simples vont d'abord aux techs les moins polyvalents, réservant les experts. eff_bias=8, versatility_weight=6.
roster.js : passe skill_eff + efficiency par véhicule au solveur.
Testé en conteneur prod : tech rapide+mono-compétence rafle les jobs, polyvalent réservé.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 11:51:18 -04:00
louispaulb
e950bd3ac6
hub: reverse geocoding infra-first (fibre field-pins -> RQA fallback); address resolution + geotagged field photos
...
- /address/reverse: reverseNearestFibre() (fibre field-pins, source of truth) then RQA
- searchCustomers: civic address resolves to the resident (not fuzzy homonyms)
- field-app + /field/photo: geotagged photos confirm the correct job
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
louispaulb
0e5c890b74
perf(hub): cache liste techs (30s) + garde anti-chevauchement sync + jeton ingest mémoïsé
...
Batch 2 de l'audit (perf backend), items sûrs :
- roster.js : fetchTechnicians caché 30 s (appelé par endpoint + generate + materialize +
tech-positions polling 25 s pour des données quasi statiques). invalidateTechCache() sur
les 6 Ă©critures Dispatch Technician (coĂ»t/effic/skills/pause/traccar/weekly) â pas de stale
aprĂšs Ă©dition. VĂ©rifiĂ© : call1 71 ms â call2 5 ms (cachĂ©).
- legacy-dispatch-sync.js : garde _inFlight sur le tick d'import ET la veille â un passage
long ne se relance plus par-dessus (évitait doublons Dispatch Job + contention ERPNext).
- conversation.js : jeton d'ingestion webhook mémoïsé (mailIngestToken) au lieu d'un
fs.readFileSync SYNCHRONE à chaque courriel/canal entrant (bloquait la boucle d'événements).
NON fait (par prudence) : paralléliser les erp.list séquentiels de generate() (séquencement
DĂLIBĂRĂ anti-ECONNRESET Frappe, cf. feedback_hub_crash_502) ; batch N+1 Customer du triage
(refactor risquĂ© d'une chaĂźne de fallback stateful â reportĂ©) ; jitter des pollers (502 boot
déjà mitigé).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 20:42:18 -04:00
louispaulb
1af8c62142
feat(planif): gĂ©ofencing des jobs â timeline En route/ArrivĂ©/Reparti (suivi façon colis)
...
Le hub compare la position GPS live de chaque tech (Traccar) aux jobs du jour â
détecte les transitions et les journalise par job. Le statut « live » du job est
DĂRIVĂ du journal (compute-on-read, aucune Ă©criture ERPNext â pas de valeur « en
cours » cĂŽtĂ© doctype). Fiche job : timeline horizontale AssignĂ© â En route â ArrivĂ©
(HH:MM) â Reparti (HH:MM).
- lib/geofence.js : runScan() (rayon 150 m, sortie 230 m hystérésis, séjour 3 min),
store data/geofence.json par job, machine à états ; startScan() scheduler 90 s
(GEOFENCE_SCAN=off pour couper). Vérifié prod : 12 jobs à 14 positions, transitions OK.
- server.js : démarre le scan au boot.
- roster.js : GET /roster/job/:name/geofence (timeline) + /geofence-states + /geofence-scan (test).
- api/roster.js : jobGeofence(), geofenceStates().
- PlanificationPage : openJobDetail charge la timeline ; stepper .jd-geo (scoped).
Suite possible : écrire le vrai statut ERPNext à l'arrivée (nécessite d'ajouter une
valeur « En cours » au doctype Dispatch Job) â non fait pour Ă©viter le risque schĂ©ma.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 18:34:55 -04:00
louispaulb
d966e1b749
feat(planif): positions GPS live des techs (Traccar) sur la carte des tournées
...
Onglet Tournées : marqueurs « live » de chaque tech (appareil Traccar associé),
rafraĂźchis ~25 s tant que l'onglet est ouvert. Marqueur = pastille ronde Ă initiales,
couleur = celle de la tournée du tech, halo pulsé si position fraßche (grisé sans halo
si > 10 min). Tooltip : nom · « il y a X min » · vitesse (km/h) ou « arrĂȘtĂ© ».
Bouton « GPS live (N) » pour afficher/masquer. Respecte les chips techs masqués.
- hub roster.js : GET /roster/tech-positions â {positions:[{techId,techName,lat,lon,time,speed}]}
(batch getPositions par appareil). Vérifié prod : 14 positions.
- api/roster.js : techPositions().
- RouteMap.vue : prop `live` + marqueurs .rm-live (halo pulsé, staleness), watch dédié.
- PlanificationPage : polling 25 s (dĂ©marrĂ© sur l'onglet TournĂ©es, arrĂȘtĂ© sinon +
onUnmounted), couleur mappée depuis dayRoutes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 18:06:16 -04:00
louispaulb
6c5ed0060f
feat(planif): « Suggérer » ne répartit que les jobs des jours cochés
...
Le dispatch auto utilisait tout le pool avec son propre sélecteur de jour,
en ignorant les chips de date du pool (et mĂȘme la sĂ©lection lasso, sur le
chemin solveur hub). Désormais les chips pilotent le dispatch :
- FENĂTRE = les chips de date cochĂ©s (â° en retard / Sans date â placĂ©s
aujourd'hui) ; aucun chip â repli sur le sĂ©lecteur Aujourd'hui/Demain/date.
- JEU DE JOBS restreint aux jours cochés (ou à la sélection lasso) sur les
3 chemins : heuristique locale, solveur local, ET solveur hub (nouveau
param `job_names` dans optimize-plan, additif/rétrocompatible).
- UI : bouton « SuggĂ©rer (N) » + dialogue « N job(s) des jours cochĂ©s â jours ».
- Helper partagé jobMatchesDateChips (dé-dup avec le filtre d'affichage).
VĂ©rifiĂ© en preview : chip 07-06 â 19 arrĂȘts tous le 07-06 (local) ;
chips 07-06+07-07 â 34 arrĂȘts, uniquement ces 2 jours (solveur hub dĂ©ployĂ©).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 11:58:38 -04:00
louispaulb
69f00840c8
feat(planif): hybrid recurring shift model â weekly_schedule pattern + materialization
...
The weekly_schedule pattern per tech is now the SOURCE of shifts. New
materializeShifts() (hub roster.js) generates source='pattern' Shift
Assignment rows over an N-week horizon, auto-skipping QC holidays
(holidays-qc) and vacations (Tech Availability), and PRESERVING manual
overrides (sourceâ 'pattern') â e.g. a one-off night week. Idempotent
(re-runnable / cron-ready).
- hub: fetchTechnicians returns parsed weekly_schedule; ensureShiftTemplate
(find/create Shift Template by exact times); endpoints
POST /roster/technician/:id/weekly-schedule and /roster/materialize-shifts
(GET=dry-run, POST=apply, weeks/tech params).
- api/roster.js: setWeeklySchedule + materializeShifts.
- WeeklyScheduleEditor: mode toggle đ RĂ©current (base, materialized) â
đ Exception (concrete override weeks); adaptive labels/horizon.
- PlanificationPage onScheduleApply branches: recurring â setWeeklySchedule
per tech + materializeShifts; exception â direct-write source='manuel'.
No ERPNext schema change (weekly_schedule + source already exist).
Dry-run against prod: 6 techs patterned â 48 shifts/2wk, 0 collisions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 11:35:57 -04:00
louispaulb
fc1f61eecf
fix(dispatch): OSRM for ALL routing + fix waypoint overshoot + detail dialog polish
...
Routing: the last Mapbox routing calls now go through our OSRM â
useMap.computeDayRoute (Dispatch board route lines) â /roster/osrm-route,
useAutoDispatch optimizeRoute â new /roster/osrm-trip (OSRM /trip TSP,
same response shape as Mapbox optimized-trips). 0 Mapbox directions/
matrix/optimized-trips fetches left in the SPA. (Remaining Mapbox =
gl-js renderer + base tiles + geocoding, which OSRM can't provide.)
Anthony route bug: /roster/osrm-route now sends continue_straight=false.
Without it OSRM forbids U-turns at intermediate waypoints, so on a
homeâstopâhome loop it overshoots the address and turns around in a side
street ("s'éloigne et tourne"). Now it goes straight to the door.
Benefits every map (shared osrm-route).
Job detail dialog:
- Highlighted assigned-tech chip (indigo, avatar+name) in the header â
"who does this job" is now obvious; grey "Non assigné" otherwise.
- Mini-map shows nearby jobs (pool + occupancy within ~3km) as pale
grey dots for context, under the job's own pin; centered on the job.
- Normalize displayed strings: decode HTML entities (subject
"d'Ă©quipement" â "d'Ă©quipement") via deEnt().
Verified: osrm-trip returns Ok order; detail shows Anthony Dion chip +
decoded subject; routes render (43 pins), Anthony 31.7km/35min round trip.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 20:41:00 -04:00
louispaulb
c3f1c77164
feat(dispatch): tech chips on map, distinct colors, remote (no-travel) jobs, group-as-project
...
- TournĂ©es tab: removable/addable TECH CHIPS above the map â each chip
in the tech's color with a job-count badge; click toggles the route
on/off (colors stay stable). Route colors switched from a 12-color
fixed palette to golden-angle HSL (137.5°) â consecutive techs always
get highly distinguishable hues, unlimited count.
- đ„ SANS DĂPLACEMENT: per-job persistent flag (hub store data/
job-remote.json + POST /roster/job-remote; buildUnassigned sets
j.remote). optimizePlan EXCLUDES remote jobs from routing and address
pinning (e.g. "Configuration de boĂźtier tel" doable via ACS â nobody
travels) â returned in a remote[] bucket, shown as a "đ„ Sans
déplacement" group in the review, assignable to an agent. Toggle
button (computer icon) on every review entry. Verified: "Boitier
ouvert | Lac des pins" flagged â excluded from routes, then reverted.
- đŠ GROUPER EN PROJET: same-address entries (đ ) now open a menu â
"Grouper en projet" sets ERPNext parent_job on the shorter tasks
(parent = longest), POST /roster/job-group. Plugs into the existing
parent-child pool sort ("Groupe (parent-enfant)").
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 19:25:39 -04:00
louispaulb
cd12dbe601
feat(dispatch): P4 hub-side optimize orchestration + same-address rules
...
P4 â the FULL optimize orchestration now lives in the hub
(POST /roster/optimize-plan): pool (buildUnassigned), day bucketing
(dated-in-window â its day; overdue/undated â day 1 with spillover to
next day if unplaced), vehicles from real shift windows (assignments Ă
templates) or assumed 8-16 weekdays (assumed_shift map returned; never
weekends; absences excluded), existing load (occupancyByTechDay) reduces
capacity, origins = home else TARGO depot, priorities from j.priority,
per-job AM/PM windows + duration overrides accepted, OSRM matrix +
OR-Tools solve per day (shared solveVrp, also used by /optimize-routes).
Reusable by the morning auto-plan cron and the field app.
Address rules (user cases baked in, hub-side):
- SAME ADDRESS = ONE STOP: pool jobs sharing an address key (~10m coords
or normalized address) are merged into one solver node (service times
summed) â same tech, single stop. Verified: install + TV boĂźtier of
the same customer (Cazaville) grouped on StĂ©phane, đ badges.
- COORDINATION: a pool job at the address of an ALREADY-ASSIGNED job
that day is pinned onto THAT tech (they're going anyway) â verified:
"Réinstallation #253028 " pinned to Frédérique who already has the
install there; capable flag kept honest (â if skill missing).
SPA: optimizeSuggestion â hub-first (dates/techs/opts/dur_overrides/
job_windows), entries enriched client-side (lvl/eff/noShift from
assumed_shift); local orchestration kept as emergency fallback. Review
shows đ teal (coordonnĂ© avec dĂ©jĂ -assignĂ©) / đ indigo (N jobs mĂȘme
adresse = 1 arrĂȘt); map merges same-coord stops into one numbered pin.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 19:14:38 -04:00
louispaulb
462fcef9d5
feat(dispatch): P1 OSRM everywhere + review ticket detail, editable time, per-leg travel
...
P1 â self-hosted OSRM replaces ALL paid Mapbox routing in the SPA:
- hub: POST /roster/osrm-route (geometry + total + per-leg km/min) and
/roster/osrm-table (durations/distances matrix, Mapbox-shaped) proxying
the local OSRM; osrmGet helper.
- SPA: loadRealRoutes (review map), kanban travel matrix, day-editor
matrix, day-map route geometry â all via hub OSRM. 0 paid
directions/matrix calls left (tiles + geocoding only).
Review usability (evaluate whether times fit):
- Click a job â the SAME job-detail dialog as the grid (address, ticket
thread, duration, team). openEntryDetail maps the pool job.
- Per-task time is editable (q-popup-edit on the "X.Xh"): updates the
plan live, survives RĂ©-optimiser (durOverride), persists duration_h
via patchJob (hub whitelist extended, 0-24h).
- Real travel time BETWEEN stops: one OSRM route per techĂday (cached)
â "đ X min" rows between entries + "domicile â 1er arrĂȘt";
haversine â fallback when coords missing.
Verified: OSRM proxies live (route 7.8km/12min with legs 7+6min, table
3x3); review shows 10 real leg rows; detail dialog shows address;
duration popup opens/cancels cleanly.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 18:41:44 -04:00
louispaulb
7fa8c6dc74
refactor(priority): unify on 3 levels (ERPNext standard), high = red, jobs + conversations
...
- shared priorityMeta/PRIORITY_LEVELS â 3 levels: Haute (RED) · Moyenne
(neutral grey) · Basse. Default/medium = grey outlined_flag. Aliases
urgentâhigh, normalâmedium for old conversation data.
- conversations: priority endpoint accepts 'medium' (keeps old values for
back-compat); the flag menu now shows the 3 levels.
- jobs: dropped the parallel job-flags override store â the job flag now
writes the REAL ERPNext priority (low/medium/high) via patchJob/
updateJob, same source as the pool sheet (no more two mechanisms).
setEntryPriority reuses patchJob. Solver: high never dropped + served
early, low dropped first.
Verified: job flag + conversation flag both grey-by-default, menu Haute/
Moyenne/Basse, high=red.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 16:01:25 -04:00
louispaulb
4e5ad011ee
fix(dispatch): reuse the Communications priority flag (grey outline â list), not a red emoji
...
The urgent flag was a red emoji shown red before being set â wrong.
Now reuses the exact Communications component: priorityMeta() + a
priority list (Urgent/Haute/Normale/Basse/Aucune) â grey outlined_flag
by default, colored only when set.
- SPA: per-job flag = q-btn(icon=priorityMeta(...).icon) + q-menu list,
same as ConversationPanel. setEntryPriority(name,lvl) (renamed to avoid
clashing with the existing pool setJobPriority(j,p)). medium (ERPNext
default) shows as grey/none.
- hub: job-flags store now holds a dispatch PRIORITY (urgent|high|normal|
low) overriding j.priority in buildUnassigned â needed because the
ERPNext job-priority field is whitelisted to low/medium/high (can't do
urgent), just like conversations keep their own priority.
Lesson applied: grep for an existing symbol before declaring (2nd
collision this session â techsForJob, setJobPriority).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 15:50:45 -04:00
louispaulb
7d1a5b067f
feat(dispatch): persistent URGENT flag per job (SLA/commercial)
...
Like the omnichannel inbox urgent flag, but for jobs â a đ© that
persists and drives the optimizer.
- hub: durable job-flags store (data/job-flags.json) mirroring
job-levels â getJobFlags/setJobFlag + POST /roster/job-flag {name,
urgent}; buildUnassigned sets j.urgent; invalidatePool on write.
- SPA: đ© chip on each review entry (replaces the session ⥠). Optimistic
local toggle + persists via roster.setJobFlag; reflects j.urgent from
the pool. Flagged jobs â priority_boost (never dropped) + urgent_weight
(served early). Bumped urgent early-weight 6â10.
Verified: flag round-trips (setâurgent, revertâcleared) through the pool;
chips render (đ© /AM/PM).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 15:16:13 -04:00
louispaulb
8b2d7b54e9
feat(dispatch): pre-wired sites â install time Ă·4 (Lac des pins/Camping/Domaine Dauphinais)
...
estimateForJob: an INSTALLATION whose location matches a pre-wired site
(Lac des pins, Camping, Domaine Dauphinais) takes Œ of the normal time
(infra already in place). Applied at the estimator source â flows into
est_min everywhere (greedy duration, VRP service_min, occupancy, travel).
Floored at 15 min; adds a "site prĂ©-cĂąblĂ© ĂÂŒ" label. Verified: Lac des
pins install 120â30 min; normal install unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 15:08:33 -04:00
louispaulb
96929bed6f
feat(dispatch): OSRM road-time matrix for the VRP optimizer (Phase 2)
...
Replace the haversine straight-line estimate with real road travel times
from a self-hosted OSRM (free/offline â Mapbox Matrix is billable).
- hub: osrmMatrix() calls OSRM /table for the job+home coords (node order
matches route_solver: jobs then homes), returns a minutes matrix.
Handles PARTIAL coords â OSRM for geolocated nodes, 0/neutral for the
rest (matches the solver's own fallback), so a few tech homes without
coords don't disable OSRM for the geolocated majority. /roster/
optimize-routes injects body.matrix; graceful fallback to haversine if
OSRM is unreachable or too few points.
- config: OSRM_URL (default http://osrm:5000 ).
- services/osrm/README.md: reproducible setup (Québec extract, MLD
pipeline, osrm-routed on erpnext_erpnext network).
Deployed + verified: OSRM /table hit with the full coord set, no
fallback; optimizer routes on real road times.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 13:48:43 -04:00
louispaulb
a6508845a7
feat(dispatch): VRP route optimizer (OR-Tools) â "Optimiser" mode
...
Real vehicle-routing optimization behind the Suggérer UI, reusing our
existing OR-Tools solver service (no new stack). Fixes the greedy's
structural limits (sector-splitting, no global optimization).
- roster-solver: new route_solver.py (OR-Tools Routing / VRPTW).
Minimizes real travel; skills = hard filter (VehicleVar â allowedâȘ{-1};
SetAllowedVehiclesForIndex has a broken Span typemap in ortools 9.15);
on-site service time within each tech's shift window; optional per-job
time windows; unfittable jobs left unassigned (drop penalty) instead of
infeasible; specialist bias via skill order (per-vehicle arc cost).
New POST /route endpoint. Dockerfile now COPYs route_solver.py.
Unit-tested: skills respected, sectors consolidated, edge cases safe.
- hub: POST /roster/optimize-routes â proxies to solver /route.
- ops SPA: "⥠Optimiser" strategy. Greedy buckets jobs into days +
placeholders, then the solver re-optimizes each day (assignment +
routes) among shifted techs; result maps into the same review dialog
(occupation bars, route map, swap/merge reused). Graceful fallback to
greedy if the solver is unreachable (never drops jobs). shiftWindowMin
derives each vehicle's shift from templates.
Phase 2 (later): OSRM/Mapbox road-time matrix for exact travel times.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 11:24:08 -04:00
louispaulb
aa6cc3e732
feat(dispatch): niveau requis persistant + lasso freeform + routes réelles Mapbox
...
- Niveau requis PAR JOB persistant : store hub durable (job-levels.json) + endpoint
POST /roster/job-level + buildUnassigned enrichit required_level ; pastille « niv »
écrit via l'API (survit reload/session). NB : l'API Custom Field ERPNext v16 échoue
(IndexError) sur ce doctype custom â store hub (migratable en champ ERPNext plus tard).
- Lasso FREEFORM : tracĂ© libre (souris + tactile) â polygone SVG + point-in-polygon sur
les pins/amas projetés (remplace le rectangle) + expansion des amas (getClusterLeaves).
- D v2 : carte des tournĂ©es = routes ROUTIĂRES rĂ©elles (Mapbox Directions par tech,
domicileâarrĂȘts) + distance/temps RĂELS dans la lĂ©gende (cache par signature ;
fallback segments droits + estimation haversine).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 09:18:33 -04:00
louispaulb
512c4a5f1b
feat(ops): dispatch auto complet + perf BoĂźte/rapports + fix session
...
Planificateur « Suggérer » : 4 stratégies (smart / meilleurs d'abord / équilibré /
juste ce qu'il faut), compétences+niveaux par tech (édition inline), niveau requis
par compĂ©tence + par job, carte des tournĂ©es (1 couleur/tech, domicileâarrĂȘts,
sĂ©lecteur de jour), fenĂȘtre de dispatch auj.+demain (dates sĂ©lectionnĂ©es), rĂšgle
week-end + placeholder « en attente du quart », clustering + lasso + filtre-date
sur la carte, accÚs rapide « à assigner » (badge).
BoĂźte : liste /conversations allĂ©gĂ©e (45 Mo â ~1 Mo, 17Ă) + messages chargĂ©s Ă
l'ouverture. Rapports : cache SWR sur revenue-explorer (22Ă). Session : keep-alive
+ timeout fetch global + authFetch durci â fin des rechargements manuels.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 08:49:35 -04:00
louispaulb
0f65c02d83
feat(fsm): platform build â comms UI, FâERPNext sync/billing, roster, campaigns, network, reports
...
Accumulated work on the dispatch/legacy-writeback branch:
- Communications UI: CommunicationsPage, ConversationFullPage, DepartmentBoard,
PipelineBoard, ReaderStack, Orchestrator/NewTicket/ServiceStatus/Outbox dialogs;
hub gmail.js, ticket-collab.js, outbox.js, coupon-triage.js, client-diag.js.
- Billing/sync mirror (FâERPNext): legacy-payments.js, legacy-sync.js,
sync-orchestrator.js, supplier-invoices.js, municipality.js + incremental
migration scripts; LegacySyncPage, SupplierInvoices + negative-billing /
terminated-active reports.
- Roster/campaigns/network/voice: roster + roster-assistant, campaigns, giftbit,
olt-snmp, traccar, twilio, vision, tech-absence-sms, ai/agent/config/helpers,
legacy-dispatch-sync; ops PlanificationPage, RapportsPage, Settings, Tickets,
ClientDetail updates.
- docs/ PLATFORM_GUIDE + UI_AND_OPTIMIZATION; .gitignore __pycache__.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 06:12:12 -04:00
louispaulb
98458861c3
feat(dispatch): capacité AM/PM, durées additives, capture terrain (/field), sync techs
...
Hub (lib/roster.js, vision.js, legacy-dispatch-sync.js, server.js) + Ops + pont legacy.
- Capacité par jour AM/PM (Soir = réserve garde/urgence, jamais offerte) calculée
client-side sur les techs visibles -> suit le filtre de compétence.
- ModÚle de durée ADDITIF (caractéristiques, tableur inline) + auto-détection
DĂTERMINISTE par mots-clĂ©s (sans IA permanente) ; est_min branchĂ© sur capacitĂ© + pool.
- Capture terrain passive : endpoints publics /field (job/tech/checkpoint/ts/photo/
device/vision), tokens HMAC signés sans PII ; dérive actual_start/end. UI hébergée
public/field-app.html (liste/carte Mapbox/Street View/photo/scan MLKit->Gemini).
- Chrono job (start/finish), repositionnement carte (set-location), vue satellite,
Street View clic-droit, année devant les dates dues groupées.
- Sync techniciens : rapport de réconciliation 3 systÚmes (staff legacy / Dispatch
Technician / groupe Authentik), application MANUELLE, zéro écriture Authentik (+11 fiches).
- vision.js : extractEquipment() réutilisable (marque/modÚle/série/MAC/codes-barres).
- Pont legacy (ops_reassign.php) : désassignation reflétée, fermeture ticket, retour
au pool ; notification courriel Ă l'assignation.
Déployé sur le hub ; ce commit aligne le repo sur l'état en production.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 19:47:21 -04:00
louispaulb
4abce6fd66
feat(dispatch): pont d'écriture legacy + adresses/coords/carte/UX Planification
...
Pont d'Ă©criture Ops â legacy osTicket via endpoint PHP token-gated (hĂ©rite des
droits write de l'app facturation.targo.ca â AUCUN grant DB). FidĂšle Ă ticket_view.php :
réassignation (assign_to + participant[assistants] + followed_by + ticket_msg + lock
respecté) + fermeture (status=closed + date_closed + closed_by + réouverture des enfants).
Auteur du log = utilisateur Authentik rĂ©el (email â staff legacy par email/nom).
Hub (legacy-dispatch-sync.js):
- adresse de SERVICE importée (champ address) + reimportAddresses + fillMissingCoords
- garde TERRITOIRE (rejette les homonymes hors-zone Gaspésie/Outaouais/Estrie) + centroïde CP/ville
- purgeStaleOrphans (anciens imports TT- périmés), # ticket legacy dans titre + description
- legacyWrite (POST form + X-Ops-Token lu d'un fichier), pushAssignments (reassign), closeTicketLegacy
roster.js: occupancy + unassigned-jobs exposent address/latitude/longitude.
Ops (PlanificationPage.vue):
- carte des jobs Ă assigner (pins couleur=compĂ©tence + lettre repĂšre listeâcarte), chips
filtre par type, panneau ouvert par défaut
- clics cellule: bande quart/garde + blocs jobs â tournĂ©e ; fond â menu horaire (fini le clic droit)
- éditeur de tournée: itinéraire routier réel + adresse + fil ticket (expand), refresh occupation à l'assignation
- bouton « Publier au legacy » (aperçu + â dĂ©sassigner) + « Fermer le ticket »
- fix dates (lundi calculĂ© en local â plus de dĂ©calage UTC) + nav ±1 jour
services/legacy-bridge/ops_reassign.php: endpoint (placeholders; secrets injectés au déploiement, hors repo).
scripts/migration: backfill_dispatch_address.sql + diagnostics.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 22:32:00 -04:00
louispaulb
22fbb81d3e
feat(planif): éditeur journée = planificateur de tournée (heures recalculées, pas d'overlap, RDV verrouillables, détails)
...
- FIX overlap/ordre : packedDay recalcule les heures depuis la SĂQUENCE (ordre liste) + durĂ©es + transport ;
le timeline et les heures affichĂ©es en dĂ©coulent â rĂ©ordonner/allonger repousse les suivants, plus d'overlap
- RDV Ă heure FIXE : verrou par job (đ , dĂ©faut = booking_status 'ConfirmĂ©') â garde son heure ; flexibles = enchaĂźnĂ©s
- clic sur un job â dĂ©tails (legacy_detail : dates + description) pour juger urgence/durĂ©e ; + tooltip au survol
- save persiste start_time recalculé (+ route_order/priority/duration_h) via reorder-jobs
- hub occupancy renvoie booking_status/legacy_detail/legacy_id ; reorder-jobs accepte start_time
- fix collision fmtH â fmtHM (HH:MM padded)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 13:56:26 -04:00
louispaulb
faea3b0c1a
feat(planif): Ă©diteur journĂ©e â rĂ©ordonnancement fiable (flĂšches+drop) + durĂ©e Ă©ditable (min) + temps de transport
...
- FIX rĂ©ordonnancement : flĂšches ââ (fiable) + drag-drop basĂ© sur le DROP (au lieu du splice live jittery)
- durĂ©e Ă©ditable par job en MINUTES (pas de 5, best practice prĂ©cision) â persistĂ©e via reorder-jobs (duration_h)
- temps de transport estimé entre 2 jobs (haversine sur coords Service Location, 40km/h + 5min) affiché entre les lignes
â en attendant la gĂ©oloc live (Capacitor background-geolocation, notĂ© pour plus tard)
- hub : occupancyByTechDay renvoie lat/lon par job ; reorder-jobs accepte duration_h ; total h en pied
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 13:41:28 -04:00
louispaulb
d96e7be672
feat(planif): Ă©diteur de JOURNĂE contextuel au clic sur le progressbar (drag-drop rĂ©ordonner + retirer)
...
- clic sur le progressbar â q-dialog ciblĂ© sur le techĂjour (garde le contexte de la grille derriĂšre) :
timeline visuelle (blocs colorés par compétence) + liste éditable des jobs
- rĂ©ordonnancement par DRAG-DROP (dragstart/dragover/dragend â route_order) + sĂ©lecteur de prioritĂ© + Enregistrer
- retrait d'un job (â â hub POST /roster/unassign-job : assigned_tech null, status open â retour au pool)
- bouton « Dispatch » comme échappatoire vers le tableau complet (gotoDispatch)
- réutilise occupancy/cellBands/cellBlocks/blockStyle + reorderJobs ; best-practice détail-drawer (pas de navigation pleine page)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 13:29:55 -04:00
louispaulb
510f8c10e0
feat(planif): blocs cellule colorés par compétence + menu réordonner/re-prioriser au clic sur le progressbar
...
- Blocs d'occupation = 1 job, colorĂ© par la COULEUR DE SA COMPĂTENCE (palette skills Ă©ditable),
au lieu du dĂ©gradĂ© vertârouge par taux. Hub occupancyByTechDay attache skill+job Ă chaque bloc
(skillForJob||deptToSkill) ; blockStyle â getTagColor(blk.skill).
- Clic sur le progressbar (.tl, @click.stop+@mousedown.stop) â menu : liste des jobs du techĂjour avec
rĂ©ordonnancement (flĂšches â route_order) + re-priorisation (select) + Enregistrer.
Hub POST /roster/reorder-jobs (route_order/priority, séquentiel) ; tri occupancy par route_order.
- Clic HORS du progressbar dans la cellule â menu shift inchangĂ© (crĂ©er/modifier).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 12:44:22 -04:00
louispaulb
2631ed9199
feat(dispatch): couleur ticket = couleur skill + fil complet du ticket + tri pool (date/ville/priorité)
...
- Couleurs liées aux skills (éditable/cohérent) : hub deptToSkill() déduit une compétence du type legacy
â /roster/unassigned-jobs renvoie required_skill ; PlanificationPage colore la carte par getTagColor(required_skill)
(mĂȘme couleur que le chip skill) ; bordure 5px
- Fil complet du ticket : hub /dispatch/legacy-sync/ticket-thread (ticket_msg + auteur staff, HTML nettoyé) ;
api legacyTicketThread ; RightPanel bouton « đŹ Voir le fil / commentaires » (chargĂ© au clic, messages+auteurs+dates)
- Order-by du pool dispatch : useBottomPanel.bottomSort (date|city|priority) + dropdown â
dans BottomPanel
(ville = 2e segment adresse / token sujet avant |)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 12:37:45 -04:00
louispaulb
b492e30971
feat(dispatch): réconciliation + heartbeat + détails ticket dans Ops + couleurs panneau roster
...
#1 « ne rien échapper » :
- /dispatch/legacy-sync/reconcile : compare legacy(3301,open) â Dispatch Jobs â missing/orphan (70â70, 0/0)
- /dispatch/legacy-sync/status : heartbeat (last_sync + stale) pour Uptime-Kuma
Détails ticket dans Ops (bug signalé) :
- pont extrait le 1er message du fil legacy (stripHtml) â champ legacy_detail (backfill 70)
- store legacyDetail ; RightPanel : section « Détails du ticket » ; tooltip dans le panneau roster
Couleurs panneau roster (bug signalé) :
- /roster/unassigned-jobs renvoie job_type/legacy_dept/priority/legacy_* ; PlanificationPage colore
chaque carte par type (legacyDeptColor partagĂ©) â bordure gauche
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 11:50:21 -04:00
louispaulb
e33dd8e83a
roster(planif): assignation drag-drop + timeline ressource + occupation + nettoyage
...
Panneau « jobs à assigner » v2 : multi-sélection (cases), groupes parent-enfant
surlignés, heuristique terrain/à distance (activation/netadmin pré-décochés),
prĂ©-total d'heures, aperçu d'occupation PROJETĂE au survol (barre fantĂŽme + badge).
Fix barre d'occupation figée aprÚs drop : /roster/assign-job pose désormais un
start_time (premier-trou-libre dans le shift) + garantit duration_h, sinon le job
compte dans les heures mais n'affiche aucun bloc. Nouvel endpoint
/roster/backfill-start-times (idempotent) pour rattraper l'historique.
Infobulle de cellule : nb de jobs + liste triée par priorité (occupancyByTechDay
renvoie jobs[]). Timeline contextuelle par ressource (dialogue, 0 appel réseau).
Lisibilité du drag : fantÎme compact semi-transparent décalé sous le curseur
(ne masque plus l'aperçu) + source estompée.
Scoring de prioritĂ© : hook proximitĂ© (neutre â secteur gĂ©rĂ© manuellement),
réservé à 20% du score quand la géoloc arrivera.
Refactor hub : helper partagé firstFitStart (assign-job + backfill).
Nettoyage : retrait code mort (onDeleteRosterTag, projUsedH), carte des sections
en tĂȘte de PlanificationPage. Doc : docs/features/roster.md + index.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 15:50:17 -04:00
louispaulb
9ec829fc6f
Planification: marquer une absence d'1 jour depuis la grille (touche A + menu)
...
Clic sur une case â touche « A » (bascule) ou menu « Marquer absent ce jour / Retirer l'absence » â
crĂ©e/supprime une Tech Availability d'1 jour APPROUVĂE (hachurĂ©e tout de suite). Multi-sĂ©lection
supportée (A marque toutes, re-A retire). Endpoint POST /roster/absence/set {tech,date,type,remove}
(remove ne touche que les absences d'un jour, pas les vacances multi-jours). Type défaut Congé.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 22:02:39 -04:00
louispaulb
a60a9bee91
Garde: génération sur un HORIZON multi-semaines (évÚnement récurrent) au lieu d'une seule semaine
...
Avant: « Appliquer Ă la semaine » n'Ă©crivait qu'une semaine â 1 seul tech (rotation hebdo). Maintenant
« Générer la garde » matérialise la rotation sur N semaines (4/8/12/26) d'un coup, écrit direct (publié),
navigable semaine par semaine â comme un Ă©vĂšnement rĂ©current. Endpoint /roster/garde/apply : wipe ciblĂ©
des shifts de garde dans l'horizon + recréation (idempotent, reflÚte l'édition de la séquence). Saut
d'absent conservé. (source='manuel' car le Select n'autorise que solveur/manuel.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 21:58:15 -04:00
louispaulb
2e5ef6d001
Roster publish: diff au lieu de wipe+recreate â publication quasi instantanĂ©e
...
Avant: /roster/publish-week supprimait TOUTES les assignations de la semaine puis les recréait toutes
(~2N Ă©critures ERPNext sĂ©quentielles â plusieurs secondes/dizaines de s). Maintenant: diff par clĂ©
tech|date|shift â supprime seulement les retirĂ©es, crĂ©e seulement les nouvelles, ignore les inchangĂ©es.
Republier sans changement: 87 assignations â 0 Ă©criture, 37 ms (vs ~174 Ă©critures avant). Une modif
de quelques cellules = quelques écritures seulement.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 21:20:03 -04:00
louispaulb
fedaf53a3b
Planification: drapeau « longue durée » sur absence (maternité/invalidité = à remplacer)
...
Tech Availability gagne long_term (Check). Case à cocher « Longue durée » dans le dialogue Congés.
absencesByTechDay encode « (longue durĂ©e) » â applyTemplate force « Ă remplacer » (pas juste sauter
comme des vacances), mĂȘme si l'absence ne couvre pas toute la semaine. ComplĂšte l'intelligence
permanent vs vacances (avant: déduit de « absent toute la semaine »).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 20:31:39 -04:00
louispaulb
bb337db14f
Planification: hachuré = ABSENT (congé/pause), garde = pointillé ambre (sur appel hors heures)
...
- Hachuré gris = ABSENT (Tech Availability approuvée + En pause). Nouvel endpoint /roster/absences
(En pause global + congĂ©s approuvĂ©s par jour) â la cellule d'un tech absent est hachurĂ©e (tooltip = type).
Remplace l'ancien 'P' pause.
- GARDE = nouveau visuel: bande à CONTOUR POINTILLà AMBRE + fond ambre léger (sur appel, hors heures
d'ouverture) â distinct du travail planifiĂ© et de l'absent.
- LĂ©gende: dispo (matinâsoir) · occupation · absent (hachurĂ©) · garde (pointillĂ©). Retrait 'P pause'.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 19:43:47 -04:00
louispaulb
054deb7b97
Roster: la garde ne compte PAS comme heures travaillées (mise en dispo)
...
Garde (on_call) exclue partout des heures travaillées + du coût:
- hub statsByDay: heures = somme des shifts NON-garde ; nouveau compteur on_call/jour (techs en dispo).
- Ops: hoursOf (heures/tech + alerte heures supp) et costByDate/weekCost excluent la garde.
- nouvelle ligne de pied 'đĄïž Garde' = nb de techs en disponibilitĂ©/jour (si applicable).
Cohérent avec l'occupation (déjà hors-garde) : la garde = réserve d'urgence, ni offerte ni facturée.
Vérifié 8 juin: 112 h travaillées (garde 6 h exclue), garde=1.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 15:52:01 -04:00
louispaulb
ebb440b5d8
Roster: quart de Garde (on_call) = réserve d'urgence, jamais offert au booking
...
ModĂšle: champ on_call (Check) sur Shift Template. Un quart garde:
- N'est JAMAIS offert au booking client (techGaps retourne null) â vĂ©rifiĂ©: tech Jour+Garde
n'offre que la fenĂȘtre Jour, aucun crĂ©neau dans la plage de garde.
- Est EXCLU du dénominateur d'occupation (heures offrables), affiché à part.
- Timeline: bande HACHURĂE (vs neutre pour l'offrable) + đĄïž dans le label + tag (garde) en infobulle.
- Ăditeur de modĂšles: bascule 'đĄïž Garde' pour crĂ©er/marquer un quart de garde.
hub: fetchTemplates expose on_call; create/update template le gĂšrent. Champ ajoutĂ© Ă
setup_dispatch_custom_fields.py (persistance). Démo: Garde 18h-minuit marquée on_call.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 15:37:34 -04:00
louispaulb
2f868133d5
Planification: mini-timeline positionnĂ©e (fenĂȘtre rĂ©elle du shift + blocs pris)
...
Avant: 'J8' ne distinguait pas 7-15 de 9-17 â mĂȘmes crĂ©neaux apparents, dispo rĂ©elle diffĂ©rente.
Maintenant chaque cellule affiche: chip (lettre) + intervalle '7â15', et une mini-timeline sur un
axe de journĂ©e (06:00â21:00) oĂč la fenĂȘtre du shift est positionnĂ©e (donc 7-15 Ă gauche, 9-17 Ă
droite = visuellement distinctes) avec les blocs de jobs pris (couleur selon charge) â les TROUS
restants = créneaux offrables. Infobulle = intervalle + h occupées/h (%).
- hub occupancyByTechDay renvoie {h, blocks:[{s,e}]} (heures de début réelles des jobs).
- ops: cellWindow/axisPos/shiftStyle/blockStyle, rendu .tl/.tl-shift/.tl-blk + tick midi.
- dĂ©mo 8 juin: modĂšles Matinal 7-15 + DĂ©calĂ© 9-17, techs alignĂ©s (7â13.8, 9â18.6 surbookĂ©).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 15:21:04 -04:00
louispaulb
fd5c83d367
Planification: taux d'occupation par cellule (jobs assignés / heures du shift)
...
Chaque cellule techĂjour avec un shift affiche, sous le chip (J8), une mini-barre + % colorĂ©s
(vert <70, orange 70-99, rouge >=100 surbooké) + infobulle = intervalle du shift + h occupées/h.
Occupation = Σ duration_h des Dispatch Jobs planifiés assignés ce jour ÷ Σ heures du shift.
- hub: occupancyByTechDay(start,days) + GET /roster/occupancy â map 'TECH|date': heures.
- ops api: getOccupancy ; PlanificationPage: occCells (computed), cellOcc/occColor/cellInterval,
rendu barre + q-tooltip, chargé dans loadStats. Données démo semaine 8 juin (45/85/120%).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 15:13:27 -04:00
louispaulb
96a76812d6
Copilote: agit vraiment sur les absences (gerer_absence + IROPS rematch) + gig dispo
...
Bug: le copilote comprenait l'absence mais n'agissait pas â aucun impact. Causes:
1) prompt « par dĂ©faut analyse » â ne dĂ©clenchait pas l'action ;
2) marquer une absence n'excluait PAS des créneaux (techGaps ne testait que le statut
global En pause, pas les Tech Availability approuvées par jour) ;
3) loadBookingData calculait unavail mais ne le retournait pas (oubli) â garde inerte.
Fixes:
- roster.js: loadBookingData inclut unavail (buildUnavailability = En pause + absence_from/until
+ Tech Availability approuvées) ; techGaps exclut le tech absent ce jour-là ; export bookingSlots/fetchTemplates.
- roster-assistant.js: nouvel outil gerer_absence = crée l'absence (par jour) + trouve les RDV
impactĂ©s + RĂASSIGNE auto Ă un autre tech libre du mĂȘme crĂ©neau (IROPS), renvoie les
« à reporter ». Nouvel outil ajouter_disponibilite (tech à l'acte ouvre un créneau). Prompt
orienté ACTION (signaler une absence = instruction d'exécution).
- ValidĂ© prod (lab): copilote crĂ©e l'absence â, booking exclut le tech absent (109â104) â,
rematch DJ-âŠâAntoine mĂȘme crĂ©neau â ; donnĂ©es de test nettoyĂ©es.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 14:43:03 -04:00
louispaulb
d94623e58a
Booking #56 : politique de créneaux offerts + holds temporaires
...
- getBookingPolicy() (sous-objet 'booking' du fichier policy): lead_hours, day_start/end,
days_offered, horizon_days, max_per_day, hold_minutes. AppliquĂ©e dans bookingSlots â
cohérent pour /book + vue agent + fit. ignorePolicy au moment de confirmer (slot encore libre).
- Holds en mĂ©moire (Map TTL): /roster/book/hold {date,start,minutes|release} â fenĂȘtre retirĂ©e
des dispos des autres pendant le hold; libĂ©rĂ©e Ă la confirmation. Ăvite le double-booking
pendant qu'un agent/client choisit.
- roster-assistant: DEFAULT_POLICY.booking + booking_fields/weekdays (descripteurs UI), fusion fine.
- TestĂ©: plage 10-14h filtre bien; hold 10:00 dispo 12â11.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 14:19:42 -04:00
louispaulb
20ba2a327e
Booking /book: grille semaine â jour â Matin/AprĂšs-midi (se situer dans le temps)
...
Avant: liste plate jj/mm peu lisible. Maintenant: bandeau de semaine ('Semaine du 8 â 14 juin'),
en-tĂȘtes de jour complets (lundi 8 juin), sections Matin/AprĂšs-midi. SĂ©lection 1-3 prĂ©fĂ©rences inchangĂ©e.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 14:04:08 -04:00
louispaulb
fa696fb8e9
Fix reschedule: notify-reschedule dĂ©sassigne (vide date/heure/tech, status open) â /book repropose des crĂ©neaux
...
Avant: le job gardait scheduled_date â page /book court-circuitait en 'dĂ©jĂ confirmĂ©' sans options.
Maintenant un report = vrai retour au pool â le client choisit un nouveau crĂ©neau (vĂ©rifiĂ©: 50 crĂ©neaux proposĂ©s, SMS livrĂ©).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 13:56:20 -04:00
louispaulb
b77e251e8e
Reschedule: endpoint aviser-client (lien /book + SMS Twilio + statut 'Ă reporter') + file 'Ă reporter' + outil copilote
...
/roster/job/notify-reschedule (job,phone?) â token /book + SMS + booking_status='Ă reporter'.
/roster/jobs-to-reschedule â file superviseur. Copilote: outil notifier_client_report.
Testé OK (chemin sans téléphone = sûr). Brique P5 de #55 , déclenchable depuis le flux de pause.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 13:28:45 -04:00
louispaulb
1a4c45662a
Roster AI (planification) + prise de rendez-vous client
...
Solveur OR-Tools (services/roster-solver) : couverture, compétences,
équité, coût chargé, cadence/efficacité, capacité-par-job ; contraintes
dures/souples façon Timefold.
Hub (lib/roster.js) : génération via solveur, publication par réécriture
de semaine (anti-doublons), demande (effectif ou nb de jobs), cadence/coût/
compétences par tech, pause, congés (Tech Availability + approbation),
booking (slots roster-aware / fit 3-dispos / confirm) + portail public /book.
Réessai sur serialization failures frappe_pg ; appels ERP séquentiels.
Ops : page Planification (grille compacte « J8 », multi-shift, drag-select
+ undo/redo, modÚles de semaine, éditeur cadence&coût, congés, SMS opt-in),
page Rendez-vous (rĂ©partiteur), jobColor tech en pause â tickets rouges.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 16:42:44 -04:00