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

97 lines
7.7 KiB
Markdown
Raw Permalink 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.
## 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).