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>
87 lines
6.8 KiB
Markdown
87 lines
6.8 KiB
Markdown
# 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).
|