gigafibre-fsm/docs/shifts-module-spec.md
louispaulb f4138cdd75 Roster AI (planification) + prise de rendez-vous client
Solveur OR-Tools (services/roster-solver) : couverture, compétences,
équité, coût chargé, cadence/efficacité, capacité-par-job ; contraintes
dures/souples façon Timefold.

Hub (lib/roster.js) : génération via solveur, publication par réécriture
de semaine (anti-doublons), demande (effectif ou nb de jobs), cadence/coût/
compétences par tech, pause, congés (Tech Availability + approbation),
booking (slots roster-aware / fit 3-dispos / confirm) + portail public /book.
Réessai sur serialization failures frappe_pg ; appels ERP séquentiels.

Ops : page Planification (grille compacte « J8 », multi-shift, drag-select
+ undo/redo, modèles de semaine, éditeur cadence&coût, congés, SMS opt-in),
page Rendez-vous (répartiteur), jobColor tech en pause → tickets rouges.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 16:42:44 -04:00

108 lines
6.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.

# Module Shifts — spécification (intégré au Dispatch, sans paie)
> **Statut : spec v2 (2026-06-03) — architecture résolue.**
> Parcours : on a d'abord exploré **Frappe HR sur PostgreSQL** (déployé+validé à
> hr.gigafibre.ca, cf. `reference_frappe_hr_postgres.md`) — mais (1) son UI Desk
> n'est pas assez conviviale (l'usager la compare à Odoo Planning) et (2) elle est
> **déconnectée des vrais techs/jobs** qui vivent dans l'ERPNext de FACTURATION
> (`Dispatch Technician` / `Dispatch Job`). → On revient donc à la conclusion d'origine :
> **build-our-own, intégré au domaine dispatch.**
>
> **Décisions arrêtées :**
> - **Moteur (« Roster AI ») = OR-Tools CP-SAT** (Python), BÂTI + validé →
> `services/roster-solver/`. Contraintes dures/souples « façon Timefold ».
> - **Données du roster = dans l'ERPNext de facturation** (custom doctypes, à côté
> de Dispatch Technician/Job) → intégration triviale des features dispatch.
> - **UI = page « Planification » dans Ops** (Vue/Quasar, style Odoo : grille Gantt,
> drag-drop, bouton « Générer »), à côté du Dispatch.
> - L'instance Frappe HR (hr.gigafibre.ca) n'est PAS le foyer du roster (techs
> ailleurs). Gardée seulement si on veut de la vraie RH (soldes de congés) plus tard.
>
> **Features demandées par l'usager (2026-06-03) :** modèles de shifts → **dispo vs
> requis** par jour ; **mettre un tech en pause → ses tickets dispatch passent en
> ROUGE** ; demande de **vacances** ; demande de **modification de shift**.
## 1. Périmètre
Gestion de shifts + **approbations**. **Pas de paie.** On pousse les heures
approuvées vers ERPNext par API (la paie/compta reste côté ERPNext/legacy).
Validé contre la page Frappe HR « Shifts & Attendance » — toutes les fonctions
shift/approbation sont couvertes (voir mapping §7).
## 2. Contraintes transverses
- **Multitenant** : `org_id` sur chaque doctype (shared-DB), pour usage interne
Targo/Gigafibre **et** revente future à des tiers.
- **SSO** : Authentik OIDC (comme toutes les apps — cf. `reference_authentik`).
- **Stockage** : Postgres (hub ou base dédiée). Pas de dépendance ERPNext pour le
fonctionnement; sync sortante par API.
## 3. Modèle de données (doctypes / tables)
| Entité | Champs clés |
|---|---|
| **ShiftType** | `org_id`, nom, heure_début, heure_fin, durée_pause, seuil_demi_journée (h), seuil_absent (h), **grâce_retard** (min), **grâce_sortie_hâtive** (min), marge_pointage_avant/après (min), couleur |
| **ShiftAssignment** | `org_id`, employee_id, shift_type_id, date_début, date_fin, statut (Active/Inactive), source (manual/schedule/swap) |
| **ShiftSchedule** | `org_id`, nom, patron récurrent (RRULE hebdo), shift_type, équipe/employés, fenêtre de génération |
| **ShiftRequest** | `org_id`, employee_id, shift_type_id, date(s), motif, **approver_id**, statut (Draft→Submitted→Approved/Rejected), historique |
| **ShiftSwapRequest** | `org_id`, requester_id, counterparty_id, assignment_a, assignment_b, statut (Proposé→Accepté_par_pair→Approuvé_gestionnaire/Rejeté) |
| **Punch** (EmployeeCheckin) | `org_id`, employee_id, type (IN/OUT), timestamp, lat, lon, **job_id** (réf dispatch), device, source (mobile/web), within_geofence (bool) |
| **AttendanceDay** | `org_id`, employee_id, date, shift_assignment_id, statut (Présent/Retard/Demi/Absent), heures_calculées, première_IN, dernière_OUT |
| **AttendanceRequest** (régularisation) | `org_id`, employee_id, date, type, valeur_demandée, motif, **approver_id**, statut |
## 4. Workflows d'approbation
- **ShiftRequest** : employé soumet → `approver` (gestionnaire/département) approuve/rejette
→ si approuvé, crée/maj la `ShiftAssignment`. Multi-niveaux possible (chaîne d'approbateurs).
- **ShiftSwapRequest** : demandeur propose → **le pair accepte****gestionnaire approuve**
→ échange des deux `ShiftAssignment`. Tout refus annule.
- **AttendanceRequest** : employé demande correction → `approver` approuve → maj `AttendanceDay`.
- Approbateurs : configurables par employé / équipe / org. Notifs in-app + (option) SMS via le hub (Twilio).
## 5. Différenciateur — géofence vs **adresse du job dispatch**
À chaque punch terrain, on calcule la distance entre `(lat,lon)` du punch et
l'**adresse géocodée du job dispatch assigné** (RQA address DB), pas un lieu fixe.
`within_geofence = distance ≤ rayon_org` (configurable, ex. 150 m). Le punch hors
rayon est accepté mais **flaggé** (preuve de présence pour le dispatch).
## 6. Auto-attendance & auto-rostering
- **Auto-attendance** : à la clôture du shift, `AttendanceDay` est calculé depuis les
punchs vs le `ShiftType` (retard si IN > début+grâce ; sortie hâtive si OUT < fingrâce ;
demi-journée/absent selon seuils d'heures).
- **Auto-rostering** : **Timefold Solver** (Apache-2.0, successeur d'OptaPlanner)
même moteur que le dispatch par tags/compétences (`feedback_dispatch_tags`).
Contraintes : couverture des shifts, compétences requises, disponibilités,
équité, coûts. Génère des `ShiftAssignment` proposées (révisables).
## 7. Mapping Frappe HR → ce build (validé)
| Frappe HR | Ici |
|---|---|
| Shift Type (seuils, grâce) | ShiftType |
| Shift Assignment (+ outil lot) | ShiftAssignment (+ assignation en lot) |
| Repeating schedules / rotating | ShiftSchedule + Timefold |
| Shift Request + approbation multi-niveaux | ShiftRequest (workflow) |
| Roster drag-and-drop + échange | Vue roster + ShiftSwapRequest |
| Check-in/out mobile + géoloc | Punch (app field-tech) |
| Auto-attendance, retard/sortie hâtive | AttendanceDay |
| Attendance regularization | AttendanceRequest |
| Rapports/calendrier | Vues calendrier + exports |
| Paie | hors périmètre push heures ERPNext API |
## 8. Écrans
- **Ops (desktop)** : calendrier roster (drag-and-drop), définition ShiftType,
assignation en lot, file d'approbations (ShiftRequest/Swap/AttendanceRequest),
tableau de présence, déclencheur Timefold.
- **App field-tech** (déjà existante) : mon shift du jour, **punch IN/OUT** (géoloc
vs job), demander un shift / un échange, demander une régularisation.
## 9. Intégrations
- **Authentik OIDC** : auth + rôles (employé / gestionnaire / admin org).
- **Dispatch** : `Punch.job_id` jobs ; le solver partage les contraintes de skills/tags.
- **ERPNext** : push des heures approuvées (par employé/période) via l'API REST
(le hub a déjà `erpFetch` + token). Aucune dépendance hrms.
## 10. Phasage
- **MVP** : ShiftType, ShiftAssignment (+ lot), ShiftRequest + approbation, Punch
géoloc-vs-job, AttendanceDay auto, calendrier roster. Mono-org (Targo).
- **v2** : ShiftSchedule récurrent + **Timefold** auto-roster, ShiftSwap,
AttendanceRequest, multitenant `org_id` + revente, push ERPNext.