# Chantier — Modal d'alerte machines sous contrat silencieuses

> ## ✅ CHANTIER LIVRÉ EN PRODUCTION
> **Vérifié le 2026-05-29** — Ne plus relire pour valider la livraison.

**Statut :** plan validé (suite de la suppression du fallback +3 ans), à implémenter
**Date plan :** 2026-05-01

## Contexte / Pourquoi

Audit complet sur les **111 machines avec badge CONTRAT** (`sous_contrat_site=1`) de la BDD locale (script `/tmp/audit_planning_machines.php`) — **7 machines** déclenchent un cas problématique :

| Site | Machine | Freq | Ancre | Fin contrat | Généré |
|---|---|---|---|---|---|
| DASSAULT AVIATION | POINTE HAUTE | 1 an | 20/04/2026 | 31/12/2026 | 0 |
| DASSAULT AVIATION | POINTE HAUTE EXTENSION | 1 an | 21/04/2026 | 31/12/2026 | 0 |
| DASSAULT AVIATION | VINCI - GF1 | 1 an | 16/04/2026 | 31/12/2026 | 0 |
| DASSAULT AVIATION | VINCI - GF2 | 1 an | 16/04/2026 | 31/12/2026 | 0 |
| GARAGE MANNES | ENSEMBLE AEROTHERME | 6 mois | 20/04/2026 | 03/07/2026 | 0 |
| GARAGE MANNES | GROUPE EAU GLACEE | 1 an | 23/04/2026 | 03/07/2026 | 0 |
| TOTAL B | GF CLIMATISATION | 1 an | 17/04/2026 | 20/02/2027 | 0 |

Profil commun : **ancre `date_premiere_intervention_annuelle` dans le passé** + **fin de contrat trop proche pour que la prochaine occurrence (today + freq) tombe avant l'horizon**.

Le moteur fait son boulot (filtre les occurrences passées, respecte l'horizon contrat), mais le résultat = aucune intervention auto pour ces machines. Côté terrain : une machine sous contrat « silencieuse » qui n'apparaît plus sur le calendrier admin/tech, c'est de l'oubli garanti.

Décision (user, 2026-05-01) : **on n'auto-cap PAS l'ancre à today** (ça déplacerait le cycle de toutes les autres machines, cf chantier précédent). À la place, on **alerte l'admin via une modal** pour qu'il :
- corrige la date de première intervention annuelle (saisie erronée),
- ou contacte le développeur pour discuter si c'est un bug.

## Décisions métier

### Quand déclencher l'alerte

- **À la sauvegarde d'une machine** (`POST /api/machines.php?action=update` ou `=create`) : si la machine est sous contrat avec freq + ancre + horizon valides mais que `MaintenancePlanningService::scheduleMachineById` retourne `created = 0`, retourner un flag `silent_machine: true` dans la réponse JSON. Le frontend ouvre la modal.
- **À la réactivation** (`action=reactivate`) : même check après replanification.
- **À l'ouverture de la page admin machines** : check global « combien de machines sous contrat sont silencieuses ? » → bandeau d'alerte cliquable qui ouvre une modal listant les machines concernées avec lien vers chacune.

### Critères de détection (machine silencieuse)

Une machine est « silencieuse » si **toutes** les conditions sont vraies :

- `sous_contrat_site = 1` (badge UI CONTRAT)
- `statut = 'actif'` (les inactives sont attendues silencieuses, pas une alerte)
- `date_premiere_intervention_annuelle IS NOT NULL` (sinon c'est le cas « ancre NULL », à traiter séparément)
- Au moins UNE des deux sources d'occurrences disponible :
  - `machine_sous_contrat = 1` AVEC `frequence_controle_contrat` non vide
  - OU `frequence_controle_cerfa` non vide ET non « Non requis »
- `site.date_renouvellement_contrat IS NOT NULL` OU `machine.date_fin_contrat IS NOT NULL` (sinon c'est le cas « pas de contrat », attendu silencieux)
- Le moteur retourne `created = 0` après planification (cause = ancre passée + horizon contrat plus court que la prochaine occurrence)

### Contenu de la modal

```
⚠️ Machine sous contrat sans intervention planifiée

La machine "POINTE HAUTE" (DASSAULT AVIATION) est sous contrat de
maintenance mais aucune intervention automatique n'a été générée.

Détail :
- Fréquence contrat : 1 an
- Date de première intervention annuelle : 20/04/2026
- Fin de contrat du site : 31/12/2026

Cause probable : la date de première intervention annuelle est passée
et la prochaine occurrence prévue (20/04/2027) dépasse la fin de
contrat du site.

Que faire :
[ Modifier la machine ]    Mettez à jour la date de première
                            intervention annuelle pour qu'elle tombe
                            avant la fin de contrat.
[ Modifier le site ]        Si le contrat a été renouvelé, mettez à
                            jour la date de fin de contrat du site.
[ Contacter le support ]    Si vous pensez qu'il s'agit d'un bug,
                            contactez le développeur.
[ Ignorer pour l'instant ]
```

## Fichiers à toucher

### Backend

- `src/services/MaintenancePlanningService.php` :
  - Le retour de `scheduleMachineById` contient déjà `created`, `warnings[]`. Ajouter le warning `'silent_under_contract'` quand `created === 0` et que les critères de détection sont remplis (ancre + freq + horizon présents).
- `src/controllers/MachineApiController.php` :
  - `handleUpdate()`, `handleCreate()`, `handleReactivate()` : extraire le warning de la réponse de `scheduleMachineById` et le faire remonter dans le JSON sortant (clé `silent_machine: true` + payload détaillé).
- Nouvel endpoint `GET /api/machines.php?action=silent_machines` :
  - Liste toutes les machines actives sous contrat qui satisfont les critères de détection.
  - Pour chacune : id, nom, site, freq_contrat, freq_cerfa, ancre, horizon, raison.

### Frontend

- `public/admin/machines.php` :
  - Au chargement : fetch `?action=silent_machines`.
  - Si > 0 : bandeau d'alerte rouge en haut « ⚠️ X machine(s) sous contrat sans intervention planifiée. [Voir le détail] ».
- `public/js/admin/machines.js` :
  - `openSilentMachinesModal()` : modal listant chaque machine avec son détail + boutons « Modifier la machine » (ouvre le form d'édition) et « Modifier le site ».
  - Hook sur les réponses de `update`/`create`/`reactivate` : si `silent_machine: true`, ouvrir la modal mono-machine.

### Tests

- `tests/Unit/MaintenancePlanningServiceTest.php` : ajouter `testScheduleMachineByIdReturnsSilentWarningWhenZeroCreated`.
- `tests/Unit/MachineApiControllerTest.php` : tester que `handleUpdate` propage `silent_machine: true`.

## Points d'attention

- **Cas « pas de fréquence »** : si l'admin a coché `machine_sous_contrat=1` mais oublié de saisir une fréquence, la machine est silencieuse aussi. À traiter dans le même chantier ou faire un check distinct au form ? Décision : message différent (« fréquence non renseignée » vs « ancre passée + horizon court »).
- **Cas « ancre NULL »** : déjà couvert ailleurs (la règle « pas de date = pas d'intervention » est explicite dans le code). On peut quand même remonter dans `silent_machines` avec un message dédié.
- **Cas « pas de contrat » (LARIBOISIERE)** : 14 machines avec `site.date_renouvellement_contrat=NULL` sont silencieuses depuis la suppression du fallback +3 ans. Elles **ne doivent PAS** déclencher l'alerte (c'est le résultat voulu : sans contrat connu, pas de planif). Mais elles devraient remonter dans un autre check « machines sous contrat sur sites sans date de fin de contrat » → encore un autre chantier (cohérence métier).

## Port MissioFlow

À porter à l'identique. Multi-tenant : filtrer `tenant_id` partout. Branding modal.

## Estimation

- Backend : 2h (warning + endpoint silent_machines + tests).
- Frontend : 3-4h (bandeau + modal + glue).
- Tests : 1h.
- Port missioflow : 1h.
- **Total : ~1 journée** sur coolcare + demi-journée missioflow.
