Panneau « jobs à assigner » v2 : multi-sélection (cases), groupes parent-enfant surlignés, heuristique terrain/à distance (activation/netadmin pré-décochés), pré-total d'heures, aperçu d'occupation PROJETÉE au survol (barre fantôme + badge). Fix barre d'occupation figée après drop : /roster/assign-job pose désormais un start_time (premier-trou-libre dans le shift) + garantit duration_h, sinon le job compte dans les heures mais n'affiche aucun bloc. Nouvel endpoint /roster/backfill-start-times (idempotent) pour rattraper l'historique. Infobulle de cellule : nb de jobs + liste triée par priorité (occupancyByTechDay renvoie jobs[]). Timeline contextuelle par ressource (dialogue, 0 appel réseau). Lisibilité du drag : fantôme compact semi-transparent décalé sous le curseur (ne masque plus l'aperçu) + source estompée. Scoring de priorité : hook proximité (neutre — secteur géré manuellement), réservé à 20% du score quand la géoloc arrivera. Refactor hub : helper partagé firstFitStart (assign-job + backfill). Nettoyage : retrait code mort (onDeleteRosterTag, projUsedH), carte des sections en tête de PlanificationPage. Doc : docs/features/roster.md + index. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
6.8 KiB
6.8 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.
TODO / dette
- Bloquer réellement (≠ 🔒 visuel) le dépôt d'un job « On Hold » avant son prérequis.
- Brancher la proximité (lat/lng Service Location ↔ base/secteur du tech) dans
techProximity. - Deep-link Dispatch filtré tech/date (DispatchPage ne lit pas encore
route.query). - Apprentissage IA compétence ↔ type de cas (historique des jobs).
- Refactor :
PlanificationPage.vue(~1,5k lignes) gagnerait à extraire des composables (useGarde,useOccupancy,useAssignPanel,useSkillEditor) — non fait (risque sur fichier prod).