gigafibre-fsm/docs/features/roster.md
louispaulb bc5bb06794 roster(planif/dispatch): On-Hold bloqué, alerte hors-quart, deep-link Dispatch, aviser client (#58)
- On Hold : onCellDrop REFUSE d'assigner un job en attente d'un prérequis (notify), reste au panneau (≠ 🔒 visuel)
- Hors quart publié : marqueur ⚠ dans la cellule libre (offShiftJobs/rawCellJobs lit occByTechDay brut) +
  badge « hors quart » dans la timeline ressource — surface les jobs assignés un jour sans quart
- Deep-link : Planif gotoDispatch(tech) → /dispatch?tech=&date= ; DispatchPage lit route.query
  (goToDay(date+T12:00:00) anti-décalage tz + selectTechOnBoard)
- #58 : bouton « Désaffecter + aviser le client » dans le dialogue d'unassign Dispatch →
  roster.notifyReschedule (désassigne serveur + SMS lien /book au mobile du Customer)
- Doc docs/features/roster.md mise à jour (Fait récemment / TODO)

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

7.7 KiB
Raw Permalink Blame History

Planification (Roster AI) & assignation dispatch — Handoff dev

Grille hebdomadaire ressources × jours pour planifier les quarts, la garde, et prendre en charge les jobs dispatch (glisser-déposer, timeline, occupation). Aucune paie : on planifie, on approuve, on assigne.

Surfaces

Couche Fichier Rôle
UI Ops apps/ops/src/pages/PlanificationPage.vue Grille, garde, panneau « jobs à assigner », timeline ressource, dialogues d'impact
Client API apps/ops/src/api/roster.js Wrappers fetch vers targo-hub /roster/*
Backend services/targo-hub/lib/roster.js Endpoints, accès ERPNext, moteur de créneaux, orchestration solveur
Copilote services/targo-hub/lib/roster-assistant.js Actions d'écriture en langage naturel (réassign / SMS / escalade)
Solveur services/roster-solver/ (conteneur OR-Tools, réseau erpnext_erpnext:8090) Propose un horaire (ne publie jamais)
RDV client apps/ops/src/pages/RendezVousPage.vue, portail Lovable Prise de rendez-vous (booking) roster-aware

Doctypes ERPNext (site facturation erp.gigafibre.ca)

  • Dispatch Technician — ressources. Custom fields clés (cf. dispatch-app/frappe-setup/setup_dispatch_custom_fields.py) : efficiency (cadence globale), skills (CSV), skill_levels (JSON {compétence:1-5} = maîtrise), skill_eff (JSON {compétence:facteur} = vitesse par compétence), cost_salary_h/cost_charges_pct/cost_other_h.
  • Shift Template — quarts. on_call (garde : capacité de réserve, jamais offerte au booking).
  • Shift Requirement — besoins de couverture (« dispo vs requis »).
  • Shift Assignment — assignations (statut Proposé/Publié). Le solveur propose, /publish écrit.
  • Tech Availability — congés/absences (Approuvé). long_term = à remplacer (≠ vacances ponctuelles).
  • Dispatch Job — les tickets. Champs utilisés ici : assigned_tech, scheduled_date, start_time, duration_h, priority, parent_job/depends_on/step_order (groupes), booking_*, required_skill.

Concepts UI (PlanificationPage.vue — voir la carte des sections en tête de <script setup>)

  • Garde LIVE : jamais matérialisée. gardeOverlay (règles de rotation) ⊕ manualGarde (touche G, localStorage) → gardeEffective. Recalculée à chaque rendu ⇒ pas de désync. La garde est exclue des heures travaillées et du coût.
  • Filtre compétences (ET) : skillFilter — un tech n'apparaît que s'il a toutes les compétences cochées.
  • Score de priorité : priorityScores = maîtrise (techCompetence 1-5) ⊕ vitesse (techSpeed, efficacité par compétence) ⊕ coût. Hook proximité (techProximity) câblé mais neutre (renvoie null) — secteur géré manuellement via les zones ; réservé à 20 % du score quand la géoloc arrivera.
  • Occupation : occCells combine la fenêtre de shift (bookableH) et les jobs (occByTechDay du hub) → usedH, pct, blocks (barres colorées), jobs (liste triée priorité→heure). ⚠️ un bloc n'apparaît que si le job a un start_time (sinon il compte dans usedH mais reste invisible — d'où le premier-trou-libre, voir hub).
  • Timeline ressource : openTimeline(t) → dialogue listant les jobs de la semaine visible (barre + liste priorisée). 0 appel réseau (réutilise les helpers de cellule). Lien « Dispatch » vers le tableau (/dispatch).
  • Panneau « jobs à assigner » (Outils) : flottant, déplaçable. Multi-sélection (cases), groupes parent-enfant surlignés, heuristique terrain vs à distance (jobIsOnsite : activation/config/netadmin = à distance → pré-décoché), pré-total d'heures, glisser la sélection sur une case → assignation séquentielle, aperçu d'occupation projetée (barre fantôme + badge +Xh → Y%) au survol.
  • Dialogues d'impact : retirer une compétence ou poser une absence sur un tech avec jobs assignés → liste des jobs affectés + candidats classés (qualifiés + libres au créneau) ou « À recontacter ».

Endpoints hub /roster/* (extrait)

Méthode Chemin Usage
GET /technicians /templates /requirements /assignments /coverage /stats /occupancy /absences Lecture grille
POST /generate Solveur OR-Tools (propose)
POST /publish-week Écrit les Shift Assignment (diff : crée/retire seulement le delta)
POST /technician/:id/skills /pause /efficiency /cost Édition ressource
GET /unassigned-jobs Jobs ouverts/On Hold sans tech (+ groupe/dépendances) pour le panneau
POST /assign-job Assigne un job (pose start_time premier-trou-libre + garantit duration_h)
POST /backfill-start-times Pose un start_time sur les jobs déjà assignés sans heure (idempotent)
GET /skill-impact /absence-impact /job-candidates Données des dialogues d'impact
POST /skill-impact/redistribute plan (explicite) / auto / requeue
GET/POST /book/slots /book/fit /book/confirm /book/hold /book/meta /policy Prise de RDV (booking)

Moteur de créneaux partagé : loadBookingDatatechGaps (trous libres) → firstFitStart (premier trou pour une durée, réutilisé par assign-job et backfill-start-times) → bookingSlots / fitBooking.

Pièges (à NE PAS réintroduire)

  • frappe_pg ne supporte PAS la concurrence : erp.list/écritures en séquentiel, jamais Promise.all (sous charge concurrente la connexion PG se réinitialise → « load fail »). Les sauvegardes d'édition sont debouncées.
  • Occupation invisible sans start_time : un job assigné sans heure ne dessine aucun bloc. assign-job pose donc toujours une heure (premier-trou-libre). Le backfill rattrape l'historique. Si le tech n'a aucun quart ce jour-là, on ne peut pas placer de bloc (écart de planification, pas un bug).
  • Scheduler ERPNext en pause : la facturation legacy reste autoritaire jusqu'au cutover.
  • Déploiement : front via apps/ops/deploy.sh (DEPLOY_BASE=/ops/ quasar build + tar/scp → /opt/ops-app/) ; hub via scp lib/roster.js → /opt/targo-hub/lib/ + docker restart targo-hub. Custom fields via le script frappe-setup.

Fait récemment

  • On Hold : le dépôt d'un job en attente d'un prérequis est REFUSÉ (notify), plus seulement 🔒 visuel (onCellDrop).
  • Alerte hors quart : un job assigné un jour où le tech n'a aucun quart publié → marqueur ⚠ dans la cellule libre (offShiftJobs) + badge « hors quart publié » dans la timeline. (Le backfill ignore ces jobs : pas de bande à remplir.)
  • Deep-link Dispatch : Planification → gotoDispatch(tech) ouvre /dispatch?tech=&date= ; DispatchPage lit route.query (goToDay(date+'T12:00:00') anti-décalage tz + selectTechOnBoard).
  • #58 Aviser le client : bouton « Désaffecter + aviser le client » dans le dialogue d'unassign Dispatch → roster.notifyReschedule (désassigne serveur + SMS lien /book au mobile du Customer).

TODO / dette

  • Brancher la proximité (lat/lng Service Location ↔ base/secteur du tech) dans techProximity (hook neutre en place).
  • Apprentissage IA compétence ↔ type de cas (historique des jobs).
  • #57 Hold côté client sur /book (Lovable) : bloquer la plage à la sélection.
  • Compteur global « N jobs hors quart cette semaine » dans la barre d'outils (complément du marqueur ⚠).
  • Refactor : PlanificationPage.vue (~1,5k lignes) gagnerait à extraire des composables (useGarde, useOccupancy, useAssignPanel, useSkillEditor) — non fait (risque sur fichier prod).