Commit Graph

14 Commits

Author SHA1 Message Date
louispaulb
04fb52426f fix(ops): slot finder respects untimed load (humanly possible)
suggestSlots built gaps only from timed jobs, so a tech loaded with untimed
(no start_time) legacy jobs still looked free and got offered slots. Now the
day is skipped when timed + untimed committed hours + the requested duration
exceed the shift. Also matches assigned_tech by technician_id OR docname.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 14:43:21 -04:00
louispaulb
7df6ad30e3 fix(ops): accurate occupancy denominator + editable skills + support filter
Three fixes from live feedback:
1. techOccupancy counted only jobs WITH a start_time → legacy osTicket jobs
   (assigned, no appointment hour) showed as free (e.g. Houssam "40h libre"
   with 6 jobs). Now untimed assigned jobs count toward busy (rendered as
   hatched "unscheduled" blocks) and jobs match by technician_id OR docname.
   techOccupancy also accepts CSV skills (require ALL) for multi-skill needs.
2. Support now maps to a 'support' skill (was []), so it lists only
   support-skilled agents — techs with no skills set are excluded. Added
   'support' to SKILL_VOCAB + DEPARTMENT_SKILLS.
3. AvailabilityByReason skills list is editable: auto-added skills (e.g.
   'sans-fil' from wireless service address) can be removed per-chip when
   not relevant. Bands + summary now reflect ALL selected skills.

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

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

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

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 18:33:30 -04:00
louispaulb
844eff9a49 fix: capacité réelle (hors congé/pause/archivé), archive via store hub, calendrier horaire tech
- capacité/occupation par jour : retranche les (tech,jour) en congé approuvé /
  pause / archivé du dénominateur (dashboard cap_h + bande Planif visStat) —
  fini le « 24/150 » gonflé par des techs indisponibles
- archive technicien : store hub durable (data/archived_techs.json) au lieu
  d'écrire status=Archivé (ERPNext v16 rejette la valeur hors liste → 500) ;
  exclusion partout (roster/solveur/créneaux/occupation) + restauration
- TechScheduleDialog calendrier : jours SANS quart grisés (comme week-end),
  jours AVEC quart en blanc + mince barre d'occupation → vraie dispo du tech
- techOccupancy : horizon jusqu'à 42 j (calendrier mois)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 14:46:21 -04:00
louispaulb
13039be301 feat: suivi généralisé, occupation ressources, horaire tech, revert journal
Follow / subscribe (au-delà des tickets)
- hub: store généralisé /conversations/follow {doctype,name} (follows.json,
  migre ticket_follows.json sous Issue) ; ticket-follow = alias Issue
- InterveneDialog: tuile « Suivre » câblée (toggle + état) ; TicketsPage sync

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

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

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

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 12:40:22 -04:00
louispaulb
4870cec09b hub+solveur : compétences multi par job + réservation + matérialisation quarts + app terrain natif
- roster.js : store /roster/job-skills (LISTE = source unique des compétences requises),
  enrichissement required_skills[] + required_skill (1re, rétro-compat), solveur techCovers
  (le tech doit les avoir TOUTES) ; /roster/reserve (bloc placeholder priorité moyenne, soft) ;
  publishWeek modes draft/submit/approve/publish ; matérialiseur de quarts récurrents
  (source:pattern) ; décodage des entités HTML aux points de service
- route_solver.py : skill_ok list-aware (toutes les compétences requises)
- dispatch.js : suggestSlots ignoreReserved (une réservation ne bloque pas la recherche de créneau)
- conversation.js : agentName sur le fil (commentaire au nom du tech, pas « Targo Ops »)
- legacy-dispatch-sync.js : postTicketLegacy(actorEmail, actorName) — commentaire attribué
- field-app.html + server.js : commentaire + fil de ticket NATIFS (restent sur OPS)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 20:46:42 -04:00
louispaulb
8b9d5376a7 feat(booking): slot search respects skill + real shifts + weekend-for-repairs
suggestSlots was skill-blind and assumed a default 8-17 shift for every
tech. Now:
- SKILL FILTER: only techs whose skills (or _user_tags) include the
  requested skill are considered. Dialog passes the picked chip's skill.
- REAL SHIFTS: slot windows come from published Shift Assignment ×
  Shift Template (garde/on_call excluded). A tech with no regular shift
  that day yields no slot — so it reflects reality (needs shifts created).
- WEEKENDS reserved for repairs: Sat/Sun skipped unless the skill is a
  repair/dépannage. Removed SLOT_DEFAULT_SHIFT.
- Empty-state message hints to publish a shift / weekend rule.

Verified: with only garde shifts in the horizon, correctly returns 0
(regular_count=0). Skill/weekend/garde-exclusion logic confirmed via
data diagnostic. Produces slots once a regular day-shift is published for
a qualified tech.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 10:21:01 -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
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
5d253380ab fix(chain+subs): safe job-delete, plan_name from Quotation, bi-dir sub link
- contracts.js: _inferPlanName now reads the Quotation's first positive-rate
  item ("Internet Megafibre 80 Mbps") instead of generic fallback.
- contracts.js: subPayload writes service_contract back-ref so an active/
  pending sub blocks its parent contract's deletion (LinkExistsError).
- contracts.js: GET /contract/audit-orphans[?fix=1] scans for orphaned subs
  (dangling contract link or no link at all) and contracts without a sub;
  filters out 2026-03-29 legacy-migration batch via LEGACY_CUTOFF.
- dispatch.js: deleteJobSafely() rewires children's depends_on to the
  victim's parent, re-parents descendants if victim was chain root, then
  deletes. POST /dispatch/job-delete exposes it. Fixes LinkExistsError
  when users delete a middle step in the UI.
- TaskNode.vue: confirmDelete calls /dispatch/job-delete and surfaces a
  warning when dependents will be rewired.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-23 10:19:56 -04:00
louispaulb
69fc971c04 feat: contract → chain → subscription → prorated invoice lifecycle + tech group claim
- contracts.js: built-in install chain fallback when no Flow Template matches
  on_contract_signed — every accepted contract now creates a master Issue +
  chained Dispatch Jobs (fiber_install template) so we never lose a signed
  contract to a missing flow config.
- acceptance.js: export createDeferredJobs + propagate assigned_group into
  Dispatch Job payload (was only in notes, not queryable).
- dispatch.js: chain-walk helpers (unblockDependents, _isChainTerminal,
  setJobStatusWithChain) + terminal-node detection that activates pending
  Service Subscriptions (En attente → Actif, start_date=tomorrow) and emits
  a prorated Sales Invoice covering tomorrow → EOM. Courtesy-day billing
  convention: activation day is free, first period starts next day.
- dispatch.js: fix Sales Invoice 417 by resolving company default income
  account (Ventes - T) and passing company + income_account on each item.
- dispatch.js: GET /dispatch/group-jobs + POST /dispatch/claim-job for tech
  self-assignment from the group queue; enriches with customer_name /
  service_location via per-job fetches since those fetch_from fields aren't
  queryable in list API.
- TechTasksPage.vue: redesigned mobile-first UI with progress arc, status
  chips, and new "Tâches du groupe" section showing claimable unassigned
  jobs with a "Prendre" CTA. Live updates via SSE job-claimed / job-unblocked.
- NetworkPage.vue + poller-control.js: poller toggle semantics flipped —
  green when enabled, red/gray when paused; explicit status chips for clarity.

E2E verified end-to-end: CTR-00007 → 4 chained jobs → claim → In Progress →
Completed walks chain → SUB-0000100002 activated (start=2026-04-24) →
SINV-2026-700012 prorata $9.32 (= 39.95 × 7/30).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 20:40:54 -04:00
louispaulb
cec5252944 feat: flow editor, Gemini QR scanner with offline queue, dispatch planning v2
Major additions accumulated over 9 days — single commit per request.

Flow editor (new):
- Generic visual editor for step trees, usable by project wizard + agent flows
- PROJECT_KINDS / AGENT_KINDS catalogs decouple UI from domain
- Drag-and-drop reorder via vuedraggable with scope isolation per peer group
- Chain-aware depends_on rewrite on reorder (sequential only — DAGs preserved)
- Variable picker with per-applies_to catalog (Customer / Quotation /
  Service Contract / Issue / Subscription), insert + copy-clipboard modes
- trigger_condition helper with domain-specific JSONLogic examples
- Global FlowEditorDialog mounted once in MainLayout, Odoo inline pattern
- Server: targo-hub flow-runtime.js, flow-api.js, flow-templates.js
- ERPNext: Flow Template/Run doctypes, scheduler, 5 seeded system templates
- depends_on chips resolve to step labels instead of opaque "s4" ids

QR/OCR scanner (field app):
- Camera capture → Gemini Vision via targo-hub with 8s timeout
- IndexedDB offline queue retries photos when signal returns
- Watcher merges late-arriving scan results into the live UI

Dispatch:
- Planning mode (draft → publish) with offer pool for unassigned jobs
- Shared presets, recurrence selector, suggested-slots dialog
- PublishScheduleModal, unassign confirmation

Ops app:
- ClientDetailPage composables extraction (useClientData, useDeviceStatus,
  useWifiDiagnostic, useModemDiagnostic)
- Project wizard: shared detail sections, wizard catalog/publish composables
- Address pricing composable + pricing-mock data
- Settings redesign hosting flow templates

Targo-hub:
- Contract acceptance (JWT residential + DocuSeal commercial tracks)
- Referral system
- Modem-bridge diagnostic normalizer
- Device extractors consolidated

Migration scripts:
- Invoice/quote print format setup, Jinja rendering
- Additional import + fix scripts (reversals, dates, customers, payments)

Docs:
- Consolidated: old scattered MDs → HANDOFF, ARCHITECTURE, DATA_AND_FLOWS,
  FLOW_EDITOR_ARCHITECTURE, BILLING_AND_PAYMENTS, CPE_MANAGEMENT,
  APP_DESIGN_GUIDELINES
- Archived legacy wizard PHP for reference
- STATUS snapshots for 2026-04-18/19

Cleanup:
- Removed ~40 generated PDFs/HTMLs (invoice_preview*, rendered_jinja*)
- .gitignore now covers invoice preview output + nested .DS_Store

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 10:44:17 -04:00
louispaulb
10d0854e51 refactor: reduce token count, DRY code, consolidate docs
Backend services:
- targo-hub: extract deepGetValue to helpers.js, DRY disconnect reasons
  lookup map, compact CAPABILITIES, consolidate vision.js prompts/schemas,
  extract dispatch scoring weights, trim section dividers across 9 files
- modem-bridge: extract getSession() helper (6 occurrences), resetIdleTimer(),
  consolidate DM query factory, fix duplicate username fill bug, trim headers
  (server.js -36%, tplink-session.js -47%, docker-compose.yml -57%)

Frontend:
- useWifiDiagnostic: extract THRESHOLDS const, split processDiagnostic into
  6 focused helpers (processOnlineStatus, processWanIPs, processRadios,
  processMeshNodes, processClients, checkRadioIssues)
- EquipmentDetail: merge duplicate ROLE_LABELS, remove verbose comments

Documentation (17 → 13 files, -1,400 lines):
- New consolidated README.md (architecture, services, dependencies, auth)
- Merge ECOSYSTEM-OVERVIEW into ARCHITECTURE.md
- Merge MIGRATION-PLAN + ARCHITECTURE-COMPARE + FIELD-GAP + CHANGELOG → MIGRATION.md
- Merge COMPETITIVE-ANALYSIS into PLATFORM-STRATEGY.md
- Update ROADMAP.md with current phase status
- Delete CONTEXT.md (absorbed into README)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 08:39:58 -04:00
louispaulb
a8eeb1b77d refactor: major cleanup — remove dead dispatch app, commit all backend code, extract client composables
- Remove apps/dispatch/ (100% replaced by ops dispatch module, unmaintained)
- Commit services/targo-hub/lib/ (24 modules, 6290 lines — was never tracked)
- Commit services/docuseal + services/legacy-db docker-compose configs
- Extract client app composables: useOTP, useAddressSearch, catalog data, format utils
- Refactor CartPage.vue 630→175 lines, CatalogPage.vue 375→95 lines
- Clean hardcoded credentials from config.js fallback values
- Add client portal: catalog, cart, checkout, OTP verification, address search
- Add ops: NetworkPage, AgentFlowsPage, ConversationPanel, UnifiedCreateModal
- Add ops composables: useBestTech, useConversations, usePermissions, useScanner
- Add field app: scanner composable, docker/nginx configs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-08 17:38:38 -04:00