gigafibre-fsm/docs/features/roster.md
louispaulb f1204ed459 roster(planif): assignation drag-drop + timeline ressource + occupation + nettoyage
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>
2026-06-05 15:50:17 -04:00

87 lines
6.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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é : `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**, 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.
## 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).