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
2566976191
feat(dispatch): review job-move menu ranks techs by proximity + occupancy
...
Each job's move menu in the optimize review now reuses techsForJob()
(capable â with-shift â nearest â least-loaded) instead of alphabetical,
showing per tech: distance (homeâjob km) and load (h/cap). Lets you drop
a "non casĂ©" job onto the best nearby, least-busy capable tech â the
manual version of "move a job to free a tech's capacity". Works on
placeholder/unassigned entries too (same row).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 15:05:05 -04:00
louispaulb
f5a7547c1f
feat(dispatch): urgency â early-in-day + AM/PM windows per job
...
Time-aware dispatch in the VRP optimizer:
- solver: per-job urgent_weight â SetCumulVarSoftUpperBound(idx, 0, w),
a soft cost on arrival time that pulls urgent jobs to the START of the
route (conditions the tech's day start). Verified: a far urgent job is
served first instead of last. AM/PM handled via existing tw_start/end.
- SPA: per-entry AM / PM / ⥠chips (session map suggestDlg.jobTime,
survives re-solves) + a "RĂ©-optimiser" button. optimizeSuggestion maps
AMâ[480,720], PMâ[720,960] (hard window) and urgency (job priority OR
manual ⥠) â urgent_weight. Verified: chips render, toggle persists
across re-optimize, OSRM still engaged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 14:42:36 -04:00
louispaulb
3513f7e15f
feat(dispatch): solver settings (distanceâspecialty, speed, time) + urgent priority
...
- Config UI (Optimiser mode): live settings â a distanceâspecialty
slider (rank_weight 0-10), fallback speed km/h, and solve-time budget.
Persisted to localStorage, passed to the solver per run â tune the
curve without redeploying.
- Urgent jobs prioritized: each job's priority maps to priority_boost
(drop penalty) â urgent/high never dropped, low priority dropped first
when capacity is tight.
SPA-only (solver already accepts rank_weight/speed_kmh/max_seconds/
priority_boost). Verified: settings panel renders, optimize runs
end-to-end (16 techs, OSRM).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 14:03:11 -04:00
louispaulb
bc157588d1
tune(dispatch): VRP prioritizes distance over specialty (rank_weight 6â2)
...
Skill capability stays a hard filter, so a multi-skill tech (e.g. an
installer who also has the repair skill) can take those jobs. But the
specialist bias (skill-order rank) was too strong (6 virtual min/rank),
so a far repair-specialist beat a nearby installer-who-repairs. Lower
rank_weight to 2 â distance dominates; specialty only breaks near-ties.
Verified: repair job near a rank-3 multi-skill installer (4 min) vs a
far rank-0 specialist (14 min) â old weight picked the far specialist
(bug), new weight picks the nearby installer.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 13:53:46 -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
5803fb3d0e
feat(dispatch): optimizer assumes 8h shift for every selected tech
...
The VRP « Optimiser » now treats each SELECTED tech as having a 8-16
shift (created at Publish via makeShifts) â not just those already
shifted. Weekday placeholders are folded back into their day so the
solver assigns them across all selected techs; weekend placeholders
stay (no auto weekend shift). Result: full multi-tech consolidation
instead of everything falling to placeholders â verified 34 jobs â 16
techs, tight routes (0.5â14.7 km each). Techs without a real shift show
« Créer quart » (their assumed 8h, created at Publish).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 13:34:37 -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
4591ef6169
feat(dispatch): skill PRIORITY ordering per tech (specialists first)
...
Skill order (not just level) now drives auto-dispatch: index 0 in a
tech's skills = their primary function. A job goes to the specialists
of that skill first, sparing less-specialized techs for other work
(e.g. Louis-Paul does repairs but his primary is sales â repair jobs
prefer the repair/install specialists).
- buildSuggestion: skillRank = t.skills.indexOf(reqSkill) (0 = primary);
score += skillRank * W.rank, tuned per strategy (enough/smart weight
specialty highest). Skill capability stays a hard filter.
- TagEditor (shared component): chips are now drag-reorderable
(vuedraggable, touch-friendly) via `sortable` prop; the 1st chip is
marked â
primary. Reorder emits the reordered array â persists as the
ordered skills CSV (order round-trips; stored in custom `skills`
field, not Frappe _user_tags, so no alphabetical re-sort).
- Skill editor: enable sortable chips + explicit priority numbers
(1,2,3âŠ) on the per-skill list, #1 highlighted, plus a hint.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 10:50:51 -04:00
louispaulb
f6e5128859
feat(dispatch): skill-driven auto-dispatch â route grouping, placeholder queues, shift check
...
Overhaul of the Planification « Suggérer » (auto-dispatch) flow:
- fix: read latitude/longitude (hub pool field), not lat/lon â the
distance scoring was inert (NaN) so jobs scattered; they now cluster
by real route (haversine) with a same-city fallback
- skill = HARD filter: incapable techs are no longer candidates
(Josée-Anne no longer gets réparation/installation). Assign/move menus
list capable techs first and block the rest, unless no capable
alternative exists (avoids trapping an unassignable queue)
- placeholder queues for any no-shift day (not just weekend), grouped by
skill then geography into ~8h tournées; the owner is swappable to a
real tech, and two queues can be merged
- per-tech occupation bar in the review (existing load + suggested /
capacity, red on overload); "Créer quart 8-16" button when the
assigned tech has no shift that day (local, saved on Publier)
- fix: "N jobs sans coordonnĂ©es" badge vs locate mismatch â honest
message + open the manual map picker for address-less jobs; also sync
pool jobs (latitude/longitude) on manual save so the badge refreshes
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 10:37:40 -04:00
louispaulb
72e84d6b8d
refactor(dispatch): la JOB impose sa compĂ©tence â retrait du niveau global par compĂ©tence
...
Logique métier : c'est le job précis qui impose la compétence/niveau, pas une
caractéristique globale. Retrait du bouton + éditeur « Niveau requis par compétence »
(level_by_skill/level_by_type globaux) et du repli global dans le moteur â le niveau
vient UNIQUEMENT du job (required_level persistant). La gestion compétences+niveaux se
fait PAR TECHNICIEN dans le SuggĂ©rer via l'icĂŽne âïž = TagEditor (chips + â
), le MĂME
composant que le tableau timeline (Dispatch). Sous-compétences (ex. émondage) = simples
compétences/tags créables et assignables au tech.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 09:38:54 -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
83338ca57b
feat(acte): barĂšme sous-traitant â transport/jour + FTTH 250m+ (base + $/m)
...
- Ajout acte 'transport' (Tarif transport, 100$/jour, unité qte) ; retrait de
l'ancien 'tarif_horaire + transport' combiné (remplacé par transport + perte_temps).
- amountOf : palier « FTTH 250 m + » = base 150-250 m (120$) + 1,20$/m au-delà de 250 m
(l'input mĂštres = distance TOTALE).
- Prix du barĂšme global saisis via /acte/rates (35/26/26/14/29/19/21/60/80/120/âŠ).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 09:04:11 -04:00
louispaulb
453bef006c
fix(ops): sécurité + robustesse dispatch/comms
...
- SĂ©curitĂ© : sanitize DOMPurify sur tout HTML entrant courriel/osTicket (v-html) â
ferme le vecteur XSS (ConversationPanel + IssueDetail). Nouveau src/utils/sanitize.js,
dompurify ajouté en dépendance directe.
- Fix : piÚge TDZ (watches de la carte des tournées placés avant les const suggestDlg/
suggestMapDay/suggestGroups) qui cassait le montage de PlanificationPage â dĂ©placĂ©s
aprÚs les définitions.
- Finition : Ă©tiquette de fenĂȘtre du dispatch auto « Aujourd'hui + Demain » (au lieu de l'ISO).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 08:56:35 -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
6628eaaa5b
feat(ops+hub): sender picker, per-user notifs, editable rating emails, planif & tickets UX
...
- Comms: sélecteur d'expéditeur « De: » (défaut groupe Support TARGO) via resolveSendFrom + alias vérifiés
- Notifs: prefs de feeds PAR utilisateur (/conversations/notif-prefs) + cloche Ă bascules ; boot tooltip-ux (clic prioritaire + anti-empilement)
- Courriel: invitation à évaluer = modÚle Unlayer éditable (transactional-rating-invite-*) ; test-send via Gmail + expansion {{rating}} ; logo TARGO auto-hébergé sur le magasin d'actifs du hub
- Planif: bloc « sans dĂ©placement » (damier, dĂ©but de quart, alerte si pas de quart), quart Ă©ditable dans l'Ă©diteur de jour, icĂŽnes de compĂ©tence en vue jour (TV pour tĂ©lĂ©), clic cellule â Ă©diteur, clic gauche lane â liste / clic droit â menu quart, lien â ticket par job
- Tickets: défaut « Non fermés » + correction du filtre « Mes tickets » (owner)
- Inbox: poll Gmail 1 min + rafraĂźchir Ă la demande (poll-now)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 22:41:51 -04:00
louispaulb
1b7bad1686
security: harden payments/hub auth + remove leaked ERPNext token from source
...
Auth hardening:
- payments: per-customer JWT authorization on dual-use /payments routes
(balance/methods/invoice/checkout/setup/portal/toggle-ppa) via authorizeCustomer
+ PAYMENTS_AUTH=enforce; portal retains+sends magic-link JWT (sessionStorage)
- hub: fail-closed Stripe webhook, /accept/doc-pdf IDOR gate, telephony field-name
guard (SQLi), modem-bridge private-IP guard (SSRF), ALWAYS_ENFORCE expansion
Leaked-credential cleanup (token already rotated in ERPNext):
- de-hardcode ERPNext API token -> env in bulk_submit.py, import_items.py,
apps/client/deploy.sh; placeholder in apps/ops/infra/nginx.conf (nginx injects)
- ops prod build no longer bakes VITE_ERP_TOKEN (.env.production empty)
- de-hardcode legacy DB password -> env in import_items.py
- gitignore legacy migration PII exports (tsv/json)
Conversations/UI:
- floating (un-docked) conversation panel; full-width mailbox
- per-message real sender from email headers; unified scroll; header spacing
- campaign pre-send review (subject/from/channel), Gmail send channel, clone-as-draft
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 06:17:17 -04:00
louispaulb
54f0d271dd
feat(inbox): global thread scroll + close (X) on another agent's draft mirror
...
- Email posts now render at FULL content height (fitFrame cap raised 1800â20000 guard)
so there is no per-message internal scroll â the whole thread scrolls as one.
- The live draft mirror ("X rĂ©dige une rĂ©ponseâŠ") gets a close X (closeOtherDraft):
dismiss another agent's in-progress draft preview; reset on conversation switch so
a genuinely new draft re-appears.
Verified: a 60-paragraph email renders at 2681px (no inner scrollbar); the mirror
shows a close button that hides it on click.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 08:09:07 -04:00
louispaulb
f519bb3dd6
feat(inbox): email height fits content + per-message reply button
...
- Email messages now size to their own content (fitEmailFrame): iframe sandbox gains
allow-same-origin (NO allow-scripts â scripts stay blocked, parent just measures
body.scrollHeight) and the height is set on @load + re-measured on expand. Removes
the fixed 300px frame and the "fill empty space" stretch (no more big whitespace).
msg-body is now v-if (renders on expand) so the measure runs while visible + lazier.
- Reply arrow in each CUSTOMER message header â opens + focuses the composer without
scrolling to the bottom button.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 07:29:34 -04:00
louispaulb
08069ec0dc
feat(inbox): show the agent's real name as sender + cleaner Gmail-style thread
...
Sender identity on outbound:
- Replies/new emails now go out as e.g. "Gilles Drolet" <support@targo.ca> instead
of the generic alias name "Service TARGO". The name is derived from the agent's SSO
email (gilles.drolet@ -> "Gilles Drolet"); the ADDRESS stays the monitored alias so
replies still land in the shared inbox. Verified: Gmail honors the custom display
name on the send-as alias (sent From header = "Gilles Drolet <support@targo.ca>").
- gmail.sendFrom() exported; conversation.js agentSendFrom() builds the From from the
stamped msg.agent (replies) / x-authentik-email (new sends via email-new).
UI cleanup (space + clarity, Gmail-style):
- One thin separator line between messages; removed the rounded gray card box around
emails (.email-card flat), the email-card-head bar, and the blue expanded-head
highlight.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 07:15:23 -04:00
louispaulb
9466645e17
fix(inbox): never resolve our own domains to a customer (wrong sender names)
...
Root cause: some F customer records carry our own/staff addresses in email_billing,
so matchCustomer's `email_billing LIKE '%addr%'` resolved support@targo.ca -> "Guylaine
Gagnon" and gilles@targointernet.com -> "Sylvie Juteau". Every thread on one of our
addresses then inherited that wrong customer + name.
- OWN_DOMAINS promoted to a single source in lib/helpers.js (was duplicated in
conversation.js); inbox-triage.matchCustomer() now returns null for any own-domain
address â a customer is never reachable at targo.ca / targointernet.com / gigafibre.ca.
- conversation.js consumes the shared OWN_DOMAINS (removes the local copy).
Also ran a one-shot repair (temporary endpoint, since removed) that unlinked the 6
already-contaminated threads and reset their display name to the real address.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 06:51:04 -04:00
louispaulb
e604cbf237
fix(inbox/mobile): reply composer no longer hides the "Envoyer" button
...
On mobile the conversation full-page used a fixed height + overflow:hidden, and
the reply q-editor had no max-height â a long body/signature grew it unbounded
and pushed the send button past the clipped bottom (user had to shorten the email).
- q-editor: max-height=40vh â content scrolls internally, toolbar + send row stay put.
- ConversationFullPage @media (max-width:700px): height auto + min-height 100dvh +
overflow visible â the page scrolls vertically instead of clipping.
Verified at 390x844 with a 27-line signature: editor caps at 40vh (overflow auto),
"Envoyer le courriel" stays in view.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 06:24:30 -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
19d31025a6
feat(inbox+historique): sender-identity fix, single-source taxonomy, dispatch history & leaderboards
...
Inbox identity/grouping (fixes mislabeled threads):
- customerName = real From display name, then a customer matched BY EMAIL,
then the raw address â never the AI's content-guessed name (a gilles@ relay
showed as "Sylvie Juteau").
- never group a thread by one of our own domains (support@targo.ca ,
*@targointernet.com ): re-ingested outbound was collapsing unrelated threads
into one mislabeled "Guylaine Gagnon" thread.
Refactor: queues/TYPES/QUEUE_OF/CAT centralized in lib/categories.js (were
drifting across conversation.js + inbox-triage.js; telephonie/television had
no matching triage type). Removed dead export findConversationByEmail.
Feat: addMessage stamps msg.agent on outbound replies -> per-agent stats.
Historique (/historique, HistoriquePage.vue): tournées par technicien (ALL
statuses incl. Completed/Cancelled â the board hid them), tech leaderboard,
inbox leaderboard. Hub: /dispatch/history, /dispatch/leaderboard,
/conversations/leaderboard.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 06:11:47 -04:00
louispaulb
aa108ab13f
feat(field-tech): app Capacitor native (geofence Transistorsoft + scan MLKit) + CI
...
App technicien : appairage 1x (QR), géorepérage natif en arriÚre-plan (app fermée)
-> checkpoints au hub /field/ts, scan série/MAC on-device MLKit. UI réutilisée depuis
le hub (/field). APK Android buildé (debug, 19 Mo, arm64-v8a + armeabi-v7a).
- apps/field-tech : Capacitor 6 + Vite ; src/main.js (appairage @capacitor/preferences
+ BackgroundGeolocation.addGeofences + redirection /field) ; projet android/ avec les
4 correctifs Gradle commités (force work-runtime 2.9.1, minSdk 24, repos maven xms.g
+ Huawei, googlePlayServicesLocationVersion 21.0.1) ; abiFilters arm (APK 32->19 Mo).
- README : recette de build complĂšte (toolchain M4, les 4 fixes, install/appairage) ;
BUILD-ANDROID.md (Docker + Android Studio + release signée) ; CI-SETUP.md (runner Gitea).
- CI Gitea Actions (.gitea/workflows) : android (runner Linux HORS prod, cache Gradle/npm
-> artefact APK) + ios (runner macOS, workflow_dispatch, en attente compte Apple).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 19:47:27 -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
ebad7066d6
feat(dispatch/assign): vue date DUE, confirm Quasar, carteâliste
...
- Panneau « Jobs Ă assigner » : tri/groupes par DATE DUE (â crĂ©ation) avec Ă©tiquettes
relatives (« Aujourd'hui », « MM-DD Ⱐen retard », future) + date due affichée sur chaque ligne
(overdue en orange). ASC/DESC place aujourd'hui dans l'ordre chronologique.
- Fermeture (unitaire + lot) : confirm via $q.dialog (au lieu de window.confirm) â propre + fiable.
- Carte â liste : clic sur un pin sĂ©lectionne + scrolle la ligne dans la liste + dĂ©plie son fil + flash visuel.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 22:46:33 -04:00
louispaulb
3d65b994e5
feat(dispatch): fermer ticket (unitaire + lot), tri ASC/DESC, batch PHP
...
- PHP ops_reassign.php : action `close` (single) + `batch_close` (N tickets en 1 connexion â efficace)
; close = status=closed + date_closed + closed_by=acteur Authentik + réouverture enfants waiting_for + log.
- Hub : closeTicketLegacy + batchCloseLegacy + routes POST close-ticket / batch-close (marque Dispatch Job Completed).
- Ops : bouton « Fermer ce ticket » (fil du panneau) ; « Fermer (N) » sur la sélection (fermeture en lot) ;
tri ASC/DESC du panneau (toggle, surtout pour la date).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 22:40:26 -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
4a3dc01938
Planification grille : retrait de l'axe de temps + la bande n'intercepte plus le clic/drag de la cellule
...
- Axe de temps (hdr-ruler 0..24) retirĂ© des en-tĂȘtes de jour (inutile dans la grille ; encombrant).
- La bande d'occupation `.tl` ne stoppe plus la propagation (plus de @mousedown.stop/@click.stop) â le
clic/drag sur la cellule remonte Ă `<td>` â onCellClick (menu d'horaire) + onDown/onEnter (sĂ©lection) refonctionnent.
- L'Ă©diteur de journĂ©e s'ouvre dĂ©sormais en cliquant un BLOC de job (.tl-blk, @click.stop) â pas toute la
bande. Donc : clic sur un bloc coloré = éditer la tournée ; clic ailleurs dans la cellule = modifier l'horaire.
Le tooltip d'occupation (survol) reste actif. Curseur/hover sur les blocs cliquables.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 18:02:39 -04:00
louispaulb
29054c49c2
Planification éditeur journée : déplacements en pointillés + clic pin = détails + clic ligne = recentre carte
...
- Barre timeline : l'espace entre 2 jobs (le dĂ©placement) est rendu en POINTILLĂS (au lieu du gris vide),
tooltip « đ dĂ©placement ». dayTravelSegs() = gaps entre packedDay[i].end et [i+1].start.
- Carte : clic sur un pin â POPUP avec dĂ©tails du job (n°, sujet, heure, client) ; curseur main au survol.
- Liste : clic sur une ligne â recentre la carte (easeTo zoom 14) sur ce job, en plus d'ouvrir le dĂ©tail.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 17:50:52 -04:00
louispaulb
34b10e0a1e
Planification : carte de journĂ©e INTERACTIVE (Mapbox GL) â itinĂ©raire routier rĂ©el + zoom/dĂ©placement
...
Remplace la mini-image statique (segments Ă vol d'oiseau) par une carte Mapbox GL :
- Itinéraire ROUTIER réel via l'API Directions (geometries=geojson) tracé sur la carte (halo + ligne).
- Pins numérotés dans l'ordre de tournée (cercle coloré par compétence + numéro).
- Navigable : zoom molette + boutons NavigationControl (+/-), déplacement (pan), ajustée au territoire (fitBounds).
- Lifecycle : init à l'ouverture du dialogue (aprÚs anim + resize), refresh débouncé au réordonnancement
(re-trace l'itinéraire), destruction à la fermeture (pas de fuite). mapbox-gl chargé en CDN (comme le Dispatch).
- Avertissement « N sans coords » conservé. Validé : Directions OK (géométrie 392 pts).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 17:44:22 -04:00
louispaulb
1673f7cd9a
Pont : import basé sur l'ADRESSE DE SERVICE (delivery), pas la facturation (compte)
...
ProblĂšme de fond : quand ticket.delivery_id ne se joignait pas, le pont retombait sur l'adresse de
FACTURATION du compte (rĂ©sidence) â sujet + gĂ©ocodage faux. Or les 124 tickets ouverts-3301 ont tous un
delivery cÎté compte (98 via ticket.delivery_id, 26 via le compte).
- fetchTargoTickets : rĂ©solution delivery robuste â COALESCE(ticket.delivery_id, delivery du compte AVEC
coords [plus rĂ©cent], delivery du compte [plus rĂ©cent]) â l'adresse de SERVICE est toujours disponible.
- buildJob prĂ©fĂšre dĂ©jĂ svcAddr (delivery) au billAddr â sujet + gĂ©ocodage utilisent le service.
- Rafraßchit le `subject` des jobs encore au pool (open + non assigné) pour refléter l'adresse de service
(corrige les anciens sujets basés sur la facturation) ; ne touche pas un job déjà dispatché.
RĂ©sultat (re-sync) : delivery 26â37, no_coords 6â1, 0 erreur. Jobs ouverts affichent l'adresse de service
(« Camping Sandysun · 32 Bellevue », « Lac des pins · 25 Ărable », « Franklin · 35 rue Hilltop »âŠ).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 17:36:17 -04:00
louispaulb
de496f8405
Pont : gĂ©oloc camping (fixe) sur les Dispatch Jobs â l'adresse de service â rĂ©sidence du client
...
SymptĂŽme : un job de camping (« Lac des pins | Anton Rimerov ») pointait sur la RĂSIDENCE du client
(428 Rue George, Lasalle = 45.58,-73.73) au lieu du camping. Le pont géocodait l'adresse de compte.
- buildJob : dĂ©tection camping en PRIORITĂ MAX via le registre camping_registry â signal = sujet (label
explicite, prioritaire) puis ville/adresse de delivery. Garde-fou : le texte doit contenir « camping » OU
un mot-clé de LIEU spécifique (évite les faux positifs de patronyme, ex. « Daniel Dauphinais »). coord_src='camping'.
La branche update fait écraser les coords existantes par le camping (comme delivery). 20 jobs ouverts re-coordonnés.
- camping_dispatch_backfill.sql : corrige les jobs DĂJĂ dispatchĂ©s (que le sync ne re-traite plus car le ticket
legacy a quittĂ© le pool ouvert-3301) â 4 Lac des Pins + 2 SandySun. Anton Rimerov/Germaine Thibert â 45.0624,-73.9113 â.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 17:30:11 -04:00
louispaulb
ab5eceb961
Conformité : repli « centre du code postal / ville » pour les unmatched restants (statut 'area')
...
Dernier recours quand l'adresse exacte est introuvable : placer le Service Location au CENTROĂDE
(rqa_addresses) de son code postal (prĂ©fĂ©rĂ©) sinon de sa ville â le job apparaĂźt dans le bon secteur.
- Hub : applyAreaFallback() (CTE centroĂŻdes CP/ville, index-friendly) + POST /address/conformity/apply-area.
Statut 'area', linked_address 'â centre <CP/ville>'. Hors-QC/junk (absents de rqa_addresses) restent unmatched.
- Ops : carte stat « â Secteur (CP/ville) » + bouton « â Centre CP/ville (reste) » dans la page ConformitĂ©.
ExĂ©cutĂ© : 317 placĂ©s (303 par code postal, 14 par ville) â unmatched 365 â 48 (Toronto/boĂźtes postales/junk).
Ătat final : validated 16 257 · review 489 · area 317 · unmatched 48 â 99,7 % des services ont une position.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 17:14:41 -04:00
louispaulb
ed651d3ce9
Campings : gestion du registre + réapplication self-service depuis Ops
...
Rend le mĂ©canisme rĂ©utilisable (« faire de mĂȘme pour tous les campings ») :
- Hub (address-conformity.js) : GET /address/conformity/campings (registre + nb lots par camping),
POST /campings (upsert {keyword,name,address,lat,lon} â applique direct), POST /campings/apply (rĂ©appliquer).
applyCampings() = UPDATE des lots (match ville normalisĂ©e) â gĂ©oloc fixe du camping.
- Ops (page ConformitĂ© adresses) : section « Campings â gĂ©oloc de remplacement fixe » : table du registre
(nom, adresse principale, GPSâGoogle Maps, nb lots) + formulaire d'ajout (nom/mot-clĂ©/adresse/lat/lon)
qui ajoute ET applique, + bouton « réappliquer ». api/address.js : campingsList/Upsert/Apply.
â Pour un nouveau camping : on saisit son adresse principale + GPS, tous ses lots pointent dessus (le tech
navigue au camping). Registre seedé : Lac des Pins, Dauphinais, SandySun, FrontiÚre, Ensoleillé.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 17:07:15 -04:00
louispaulb
e278d0d7b3
Campings : registre + géoloc de remplacement FIXE sur tous les lots (1405 corrigés)
...
ProblĂšme : pour un lot de camping, l'adresse du Service Location = souvent la RĂSIDENCE du client, et la
rue interne (ex. « 2 rue Canard, Lac des Pins ») n'est pas dans le RQA â gĂ©oloc fausse. Solution data-driven :
table `camping_registry` (mot-clĂ© ville â nom + adresse principale + GPS fixe), coords relevĂ©es dans le
legacy delivery. Application : force lat/long du camping sur tous les lots (match ville normalisée), garde
address_line (n° de terrain visible), linked_address = le camping, statut validated.
Appliqué : Lac des Pins 1242 · Dauphinais 134 · SandySun 28 · FrontiÚre 1 (+ Ensoleillé en registre).
Idempotent + ré-applicable. scripts/migration/camping_registry.sql.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 17:04:03 -04:00
louispaulb
5d4f66108d
Planification : minimap du territoire (pins+tracé) dans l'éditeur de journée + retrait du sélecteur priorité
...
- Minimap Mapbox Static ajoutée sous la timeline : pins numérotés dans l'ordre de tournée + tracé reliant
les arrĂȘts, ajustĂ©e auto au territoire des jobs â on VĂRIFIE d'un coup d'Ćil que chaque arrĂȘt tombe Ă la
bonne adresse (un pin mal placĂ© = coord Ă corriger via ConformitĂ© adresses). Clic â carte interactive (Dispatch).
Indique « N sans coords (absent de la carte) » le cas échéant. Helper encodePolyline (precision 5) pour le tracé.
- SĂ©lecteur de prioritĂ© retirĂ© de chaque ligne (dĂ©faut « Moyenne » conservĂ© en donnĂ©e, gĂ©rĂ© au Dispatch) â gain de place.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 16:54:53 -04:00
louispaulb
fbf584f02f
Phase 1 (hygiÚne) : utils partagés + logique pure testable + observabilité erp + 1ers tests
...
Modularisation / dé-duplication :
- lib/util/text.js : `norm` canonique partagé (remplace 2 ré-implémentations : address-db, legacy-dispatch-sync).
- lib/util/legacy-parse.js : parseurs/mapping PURS du pont (DEPT_JOBTYPE, DUR, jobType, prio, tzDate,
startTime, coord) extraits hors I/O â testables en isolation, sans pg/mysql/erp.
- legacy-dispatch-sync + address-db importent ces utils (pont vérifié en prod : preview OK, 0 erreur).
Observabilité (sûr, additif, 1 seul point) :
- erp.js create/update/remove : log de l'Ă©chec Ă la SOURCE quand HTTPâ„400 â toutes les Ă©critures ERPNext
silencieuses des 50+ appelants sont désormais tracées, SANS changer aucun flux de contrÎle.
Tests (fondation) :
- vitest + npm test ; test/util.test.js : 19 tests verts sur norm + coord(bornes QC)/prio/startTime/jobType/tzDate.
Tournent sans installer les deps lourdes du hub (modules purs).
Aligné docs/architecture/VISION.md (P0 hygiÚne). Suite : audit r.ok des appelants financiers (payments/contracts)
en revue supervisée ; CI/CD minimal (Gitea Actions lint+test) ; décomposition des god-files (Phase 2).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 10:36:41 -04:00
louispaulb
fc95a6cd4f
Optimisation (consolidation helpers address) + doc Vision/modularisation
...
Optimisation sûre (vérifiée, 0 régression) :
- helpers.js : `cors()` partagĂ© (en-tĂȘtes CORS gĂ©nĂ©riques) au lieu de 2 copies locales.
- address-conformity.js : réutilise `pool` (address-db) + `cors` (helpers) au lieu de redéfinir un Pool +
cors â 1 seul client pg local partagĂ© pour rqa_addresses/fiber.
- address-validate.js : utilise helpers.cors.
docs/architecture/VISION.md (NOUVEAU) â vision + plan de modularisation + roadmap d'optimisation, fondĂ© sur
un audit chiffré (hub 58 modules/23k lignes, Ops 45k lignes, god-files identifiés). Découpe en 9 domaines
(bounded contexts), principe « source de vérité + validation à la saisie + lien stable » (modÚle Adresses
généralisé à Client/Device/Service), optimisations P0/P1/P2, métriques de succÚs. ComplÚte les docs
architecture existants + ENGINEERING_PRACTICES.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 01:28:54 -04:00
louispaulb
72d23665b2
Page Ops « ConformitĂ© des adresses » â source de vĂ©ritĂ© unique pour rĂ©soudre le backlog
...
Plus besoin de re-chercher avec un processus complexe : une page liste les adresses de service non
conformes (review/unmatched) avec leur proposition AQ canonique, et permet de RĂSOUDRE une fois (persistĂ©) :
- Approuver : la proposition AQ devient officielle (validated, coords RQA).
- Corriger : recherche AQ locale (rqa_addresses + fibre) â lier la bonne adresse.
- GPS : saisir/coller lat,long (relevé sur map.targointernet.com qui a la géoloc des unités de camping)
+ lien direct « voir sur la carte » par ligne.
- Rejeter : pas d'adresse civique (boĂźte postale/hors-QC) â 'no_address'.
Tri par type (camping / civique Ă corriger / Ă confirmer / non-adresse) + stats + recherche + pagination.
Backend : lib/address-conformity.js (GET stats|list|candidates, POST resolve) sur le Postgres LOCAL,
routé /address/conformity/* (server.js). Front : api/address.js + pages/AddressConformityPage.vue + route
/conformite-adresses + entrée nav « Conformité adresses » (icÎne MapPinned, requires view_settings).
Ătat courant : validated 15 195 · review 1 366 · unmatched 550 (camping 540 / civique 333 / non-adresse 93).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 01:06:55 -04:00
louispaulb
d17da8c40c
SL normalisation passe 2 : récupération GPS par numéro+ville (sans contrainte postale)
...
scripts/migration/normalize_service_locations_pass2.sql : pour les SL restées review/unmatched aprÚs la
passe 1 (code postal+numĂ©ro), rematch par NUMĂRO + VILLE (trigram, normalisation StâSaint) + meilleure rue
par similaritĂ© â„0.30 (rue seule, pas ville-incluse pour Ă©viter le gonflement) â upgrade en validated avec
coords RQA réelles. RécupÚre les SL au code postal erroné/manquant mais ville valide (ex. Athelstan/Ch Ridge).
RĂ©sultat : +264 validated (15 195), unmatched 766â550. GPS sur 17 036/17 111 services (99,6%),
dont 15 195 (89%) rooftop AQ. Les 75 sans GPS = boĂźtes postales/hors-QC/placeholders/sobriquets camping
(pas de position de service réelle). Idempotent.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 16:19:37 -04:00
louispaulb
ef6a993e6b
Normalisation AQ des 17k Service Locations (lien RQA + statut, adresse originale préservée)
...
scripts/migration/normalize_service_locations.sql : rattache chaque Service Location Ă l'adresse
canonique Adresses QuĂ©bec (table locale rqa_addresses) par CODE POSTAL + NUMĂRO (clĂ© sĂ©lective indexĂ©e)
+ meilleure rue par similarité désaccentuée (unaccent + pg_trgm). Les 2 tables étant colocalisées dans
la db ERPNext, tout se fait en SQL (~26 s pour 16 345 lignes, pas d'aller-retour applicatif).
Remplit (sans toucher address_line/city/postal_code = origine prĂ©servĂ©e â table de translation) :
- aq_address_id = rqa_addresses.id (clĂ© de translation locale ; â id LOCAL, pas l'uuid AQ officiel absent
de la table locale)
- linked_address = adresse canonique conforme (address_full)
- address_validation_status = validated (simâ„0.20) | review (sim<0.20, ex. surnoms de camping) | unmatched
- latitude/longitude raffinées seulement si validated
RĂ©sultat : validated 14 931 (87%) · review 1 414 (8%) · unmatched 766 (4,5%) â 16 345 liĂ©s (95,5%).
Idempotent (ne traite que les lignes 'pending'). Couvre la tùche « backfill normalisation 17k SL ».
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 15:59:21 -04:00
louispaulb
ccad864165
Recherche d'adresses : base LOCALE (Postgres ERPNext) au lieu du Supabase cloud externe
...
Le hub n'appelle plus rddrjzptzhypltuzmere.supabase.co. La base RQA + dispo fibre est DĂJĂ locale
dans le Postgres ERPNext (rqa_addresses 5,24M + fiber_availability 21,6k jointes par address_id),
le hub y accÚde (réseau erpnext_erpnext + module pg).
- NOUVEAU lib/address-db.js : recherche locale. Phase 1 (civique présent) = filtre numero btree +
mots de rue â ~20-150 ms ; Phase 2 (sans civique) = word_similarity (`<%` indexable GIN) au lieu de
similarity() plein (24-76 s sur 5,24M !) â ~700 ms, dans une txn SET LOCAL (seuil 0.6 + statement_timeout 4s).
Renvoie 2 formes : searchLocal (mappée, compat historique) + searchRaw (colonnes brutes de la fonction).
- address-search.js : searchAddresses + searchAddressesRpc délÚguent à address-db (plus aucun appel Supabase).
â onboarding (/address/validate), checkout (/api/address-search) ET le pont (gĂ©ocodage) passent en LOCAL.
- address-validate.js : endpoints PUBLICS pour le site web (CORS) â POST /rpc/search_addresses (compat
Supabase RPC, tableau direct) + GET /address/search â servis depuis le PG local (fiber_available inclus).
- server.js : route /rpc/ â address-validate.
RĂ©sultat pont (vĂ©rifiĂ©) : couverture 112/125 (vs 109 via Supabase), rqa_geocode 8â25 (table locale plus
complĂšte + search_text dĂ©saccentuĂ©), Mapbox 37â23, no_coords 16â13, 0 erreur. Le local est meilleur.
Env hub : ADDR_DB_* dans /opt/targo-hub/.env (défauts erpnext-db-1/_eb65bdc0c4b1b2d6).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 15:46:47 -04:00
louispaulb
60dc095f56
Pont legacy : géocodage RQA via la recherche TRIGRAM (RPC search_addresses) + garde-fou anti-faux-positif
...
- address-search.js : expose searchAddressesRpc() â RPC Postgres `search_addresses` (pg_trgm), la MĂME
recherche que l'autocomplete de disponibilité fibre. Trouve les rues que l'ilike manquait (générique géré
par la colonne odonyme_recompose_long + phase 2 trigram), priorise les CP J0L/J0S (territoire).
- geocodeRQA() (bridge) bascule de l'ilike vers la RPC. Garde-fou : la phase 2 trigram dérive quand le
civique est absent du RQA (« 2245 RenĂ©-Vinet » â « Rue Grenet, MontrĂ©al »). On n'accepte un rĂ©sultat que si
le civique concorde + un token de nom de rue correspond + (territoire J0L/J0S OU CP/ville legacy concordants).
Vérifié sur les données réelles : accepte 494 Av Curry / 3055 Routhier / 228 Principale / 61 Jean-François ;
rejette RenĂ©-VinetâGrenet/Panet, chemin RidgeâFerme, rue WestâPerras (bons faux positifs Ă©cartĂ©s).
- Le faible compte RQA (8) = haute précision (l'ilike comptait 17 dont des faux positifs). Mapbox couvre le
reste (rues neuves/civiques absents) ; ~109/125 (87 %) coordonnés ; les « aucune » = campings/villes mal écrites.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 15:05:14 -04:00
louispaulb
e14026ae23
Pont legacy : coords GPS fiables (deliveryâSLâRQAâMapbox) + routage routier rĂ©el (Mapbox Matrix)
...
Pont (legacy-dispatch-sync.js) :
- Import des coordonnées par job via cascade : table legacy `delivery` (point de service exact,
JOIN ticket.delivery_id) > Service Location ERPNext > géocodage RQA > géocodage Mapbox.
Validation bornes Québec (coord()). Couverture 153/172 (89%).
- Géocodage RQA corrigé : retrait du générique de voie (Rue/Rang/Chemin absent de
odonyme_recompose_normal) + code postal non accolé au terme (sinon ilike ne matche jamais).
- Repli Mapbox geocoding pour rues trop récentes pour le RQA (MAPBOX_TOKEN).
- Backfill + UPGRADE : coords delivery écrasent des coords SL moins précises (jamais l'inverse).
- ComptabilitĂ© honnĂȘte : vĂ©rifie r.ok sur create/update (erp ne throw pas) â errors + error_samples.
- Verrou de sérialisation sync() : tick + runs manuels ne se chevauchent plus (frappe_pg).
- Subject tronquĂ© Ă 140 (champ Data) â corrige CharacterLengthExceededError sur jobs sans SL.
- Observabilité : coord_src tally + error_samples dans le résumé.
Ops Planification (éditeur de journée) :
- travelBetween() consulte une matrice Mapbox Matrix chargĂ©e Ă l'ouverture (loadDayRoute) â
temps de trajet ROUTIERS RĂELS ; rĂ©ordonnancement instantanĂ© sans nouvelle requĂȘte.
Repli haversine si Mapbox indispo. Indicateur đ rĂ©el vs đ vol d'oiseau.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 14:43:34 -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