- 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>
7.7 KiB
7.7 KiB
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 (techCompetence1-5) ⊕ vitesse (techSpeed, efficacité par compétence) ⊕ coût. Hook proximité (techProximity) câblé mais neutre (renvoienull) — secteur géré manuellement via les zones ; réservé à 20 % du score quand la géoloc arrivera. - Occupation :
occCellscombine la fenêtre de shift (bookableH) et les jobs (occByTechDaydu hub) →usedH,pct,blocks(barres colorées),jobs(liste triée priorité→heure). ⚠️ un bloc n'apparaît que si le job a unstart_time(sinon il compte dansusedHmais 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é : loadBookingData → techGaps (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, jamaisPromise.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-jobpose 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 viascp 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 litroute.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).
#57Hold 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).