Commit Graph

182 Commits

Author SHA1 Message Date
louispaulb
2e6eb6dd01 feat(dispatch): review shows tech's existing jobs + sector, and ordered skills inline
- Each tech group day now exposes the jobs ALREADY assigned (OPS
  occupancy + legacy F field tickets) behind a "🔒 N dĂ©jĂ  assignĂ©(s) ·
  cities" toggle — locked greyed rows (time, skill, subject, duration).
  Header shows 📍 sector(s) where the tech already works, so the
  dispatcher can adjust accordingly.
- Header shows the tech's skills IN PRIORITY ORDER (★ first + "+N"),
  clickable → same drag-reorder skill editor (visible answer to
  "ordonner les compétences depuis le résultat").
- cityOfOcc: heterogeneous legacy subjects ("Type | Ville | Client" vs
  "Ville | Client") → picks the first city-looking pipe segment (no
  digits, ≀4 words, no job stop-words), else address tail, else
  " - Ville" suffix. Avoids garbage like "Bris de fibre" as a sector.

Verified: Simon Clot-Gagnon → 📍 Sherrington, 2 locked repair rows;
Anthony Dion → ★ installation · rĂ©paration · +4.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 18:22:57 -04:00
louispaulb
28f544a777 ui(dispatch): lighter solver settings — a cost per criterion, no slider
Replaced the distance↔specialty slider (heavy) with 4 compact number
inputs: SpĂ©cialitĂ© (rank_weight), Heures sup (overtime_coef — now
tunable, was hardcoded), km/h (speed), Calcul s (max_seconds). Persisted
in solverOpts (localStorage), passed to the solver. Tooltips replace the
verbose inline hints.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 17:50:23 -04:00
louispaulb
d9215a20fa fix(dispatch): optimize by default; per-day occupation (not 16h); balance >100% capped 120%; medium=amber
- default strategy = optimize → the FIRST GĂ©nĂ©rer runs the VRP (real
  techs, no more greedy placeholders on first attempt).
- occupation bar is now PER-DAY (worst day), so a 2-day window shows /8h
  not /16h; overload shows in red.
- solver: each vehicle may go up to +20% overtime (≈120% cap, hard) but
  time past the nominal shift is soft-penalized → overflow spreads to
  techs still under 100% before anyone does overtime. Verified: 20 jobs /
  2 techs → 9h+9h (balanced, ≀9.6h), overflow dropped; under capacity it
  still concentrates (no forced equalization).
- priority medium flag = amber (yellow/orange) per request.

Verified live: default optimize on, 16 real techs / 0 placeholders,
per-day /8h occupation, none over, medium flag amber.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 17:39:45 -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
86bd8080d2 fix(dispatch): optimizer respects existing load; drop pseudo-tech placeholders; edit skills from review
- Overload fix: each vehicle's window is reduced by the tech's already-
  assigned load that day (shift_start += existing) — no more 20.8/16h
  (verified: William 20.8→5.3/8h). Capacity is now real.
- No more "Tournée N / non casé" pseudo-techs: the optimizer uses ONLY
  the selected techs; everything it can't place goes to ONE plain
  "⚠ Non assignĂ©s" bucket (weekend + capacity/skill overflow). Assign
  them via the proximity+load ⇄ menu, or add techs.
- Not forced to fill every tech: unneeded techs are simply left empty
  (day off) — the VRP no longer fake-distributes.
- Edit/reorder skills from the optimizer: ✏ on each tech header opens
  the same skill editor (drag-reorder chips + ★ + rank list).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 15:35:25 -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
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
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
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
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
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
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
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
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
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
louispaulb
482cc948b3 refactor(planif): clic progressbar → timeline Ă©ditable Dispatch (tech+jour) au lieu du popup maison
- réutilisation max + cohérence : le clic sur le progressbar ouvre le tableau Dispatch focalisé sur
  le tech + le jour cliqué (gotoDispatch(t, d.iso)) = LE timeline éditable (drag-drop réordonner, supprimer/désaffecter)
- retire le popup cellJobsMenu (rĂ©ordonner/prioritĂ©) → rĂšgle aussi le chevauchement avec l'infobulle mouseover
- (endpoint /roster/reorder-jobs conservé, réutilisable ; le réordonnancement se fait désormais cÎté Dispatch)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 13:19:59 -04:00
louispaulb
5eb1e587da feat(planif): tri du panneau flottant « Jobs à assigner » (groupe/compétence/date/ville/priorité)
- assignSort + assignGroups regroupe/trie selon le mode (défaut = groupe parent-enfant) ;
  ajout du tri par COMPÉTENCE (demandĂ©) + date/ville/prioritĂ© (jobCity = dernier segment adresse ou « Ville | » du sujet)
- barre de tri dans le panneau (hors zone de drag) + en-tĂȘte de groupe par label ; indentation enfant seulement en mode groupe

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 12:48:44 -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
7f22647ce4 feat(dispatch): bouton « Activer STB (Ministra) » — lien d'activation TV extrait du ticket legacy (Phase 1)
- pont : extrait le lien connect_ministra.php du ticket_msg (déjà posté par le wizard legacy à la vente)
  → champ legacy_activation_url sur le Dispatch Job (backfill des 10 tickets TV). Read-only legacy, aucun 2e
  chemin d'Ă©criture → zĂ©ro risque de double-facturation (cf. analyse processus).
- store _mapJob : expose legacyActivationUrl ; RightPanel : bouton violet « đŸ“ș Activer STB (Ministra) »
  (MÊME lien que le tech reçoit, ouvre connect_ministra.php avec tid/did/sid/cr/m intacts).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 11:20:09 -04:00
louispaulb
dab1281953 feat(dispatch): bouton « Répondre dans legacy » (lien reply_ticket.php du tech)
- useHelpers.legacyReplyUrl(job, staffId?) → https://store.targo.ca/targo/reply_ticket.php?ticket=<legacy_ticket_id>&staff=<3301 par dĂ©faut = Tech Targo>
- RightPanel : n° ticket legacy cliquable + bouton d'action « 📝 RĂ©pondre dans legacy »
- permet au tech d'écrire dans le ticket legacy depuis ERPNext (lecture seule de notre cÎté)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 10:30:32 -04:00
louispaulb
5e32f5933a feat(dispatch): pastille couleur par type + badge « en retard » dans le pool & le détail
- BottomPanel : pastille couleur (jobColor → type legacy) par ligne + badge ⏰ EN RETARD
  sur les groupes de date passée (le pool est déjà groupé/trié par date)
- RightPanel : badge « en retard » prÚs de la date planifiée (hors Completed)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 10:19:03 -04:00