# Chantier — Modal d'alerte expiration contrat site

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

**Statut :** ⚠️ FUSIONNÉ dans le chantier 4 (cf [CHANTIER_PLANIFICATION_AUDIT_4_CHANTIERS.md](CHANTIER_PLANIFICATION_AUDIT_4_CHANTIERS.md), sous-tâches 4.4 et 4.5).
**Date plan initial :** 2026-05-01
**Date fusion :** 2026-05-04 — décision user : ce flux fait partie du même cycle de vie contrat que la cascade replanif. Les 2 chantiers ne sont plus séparables.
**Lien chantier précédent :** suite immédiate de la refonte désactivation machines (mêmes touches métier)

> Ce document est conservé en archive pour la trace de la décision initiale (modal expiration en chantier autonome). Le périmètre actif est désormais **dans le chantier 4 unifié**. Ne plus modifier ce fichier — se référer au chantier 4.

---

## Contexte / Pourquoi

Aujourd'hui quand un contrat de site arrive à terme (`sites_clients.date_renouvellement_contrat <= today`), rien ne déclenche d'action côté planification. Les machines du site continuent de générer (au prochain trigger de planning) des occurrences au-delà de la fin de contrat — ou pire, l'horizon `site_date_fin_contrat` étant passé, rien ne se génère plus alors que les anciennes interventions planifiées restent dans le calendrier.

Le user demande : **« si un site arrive au terme de son contrat, on doit afficher un modal d'alerte pour demander si on désactive les machines et supprime les interventions programmées mais non commencées »**.

Décision validée :

- L'alerte est interactive (modal admin), pas un cron silencieux.
- L'admin choisit machine par machine (ou « tout désactiver d'un coup ») de désactiver / garder.
- La désactivation appelée depuis ce modal réutilise la même règle métier que `handleDeactivate()` du chantier précédent : DELETE de toutes les interventions sauf `en_cours` et `terminee`.

## Décisions métier

### Quand déclencher l'alerte

- Au moment où l'admin **modifie** la `date_renouvellement_contrat` d'un site (UPDATE site) et que la nouvelle date est dans le passé : afficher le modal après le SUCCESS de l'UPDATE.
- À l'**ouverture** de la page admin sites (`sites_clients.php`) : check « est-ce qu'il existe des sites dont la date_renouvellement_contrat est passée ET qui ont encore des machines actives ? » → bandeau d'alerte cliquable qui ouvre le modal.
- Pas de cron / notification mail dans le scope V1. Si besoin plus tard (notification admin par mail le matin du jour d'expiration), c'est un chantier séparé.

### Ce que fait le modal

- Liste les machines actives du site (`statut = 'actif'`).
- Pour chaque machine : checkbox + nom + nb d'interventions futures (planifiees/reportees/en_attente/annulee, hors en_cours/terminee).
- Boutons : « Désactiver les machines cochées » / « Tout cocher » / « Annuler ».
- Submit → boucle sur les machines cochées et appelle l'endpoint `deactivate` existant (un appel par machine, pas de batch endpoint nouveau pour V1).
- Toast récap : « X machine(s) désactivée(s), Y intervention(s) supprimée(s) ».

### Ce que NE fait PAS le modal

- Pas de modification du contrat site lui-même (l'admin doit éditer la date séparément).
- Pas de réactivation auto si la date est étendue.

## Fichiers à toucher

### Backend

- `src/controllers/SiteApiController.php`
  - `handleUpdate()` : après l'UPDATE réussi, retourner dans la réponse JSON un flag `contract_just_expired: true` si `date_renouvellement_contrat` passée + ID des machines actives + nb interventions à supprimer par machine. Le frontend décide d'ouvrir le modal.
  - Nouveau endpoint GET `?action=expired_contracts` : liste les sites avec contrat expiré + machines actives + compteurs interventions. Utilisé par le bandeau d'alerte de la page sites.

- `src/controllers/MachineApiController.php` : aucune modif (réutilise `handleDeactivate` existant).

### Frontend

- `public/admin/sites_clients.php` ou équivalent :
  - Bandeau d'alerte en haut de page si l'API `?action=expired_contracts` retourne des sites.
  - Bouton « Voir les machines à désactiver » → ouvre le modal.
- `public/js/admin/sites.js` :
  - Fonction `openExpiredContractModal(siteId)` — fetch détail + render modal HTML.
  - Submit : `Promise.all` d'appels à `/api/machines.php?id=X&action=deactivate`.
  - Refresh liste sites + machines après.
- Nouveau partial HTML / template inline pour le modal (réutiliser le composant `CoolCareConfirmDelete` ne convient pas — pas une suppression unique).

### Tests

- `tests/Unit/SiteApiControllerTest.php` :
  - `testHandleUpdateContractExpiredFlagsResponse` : la réponse contient `contract_just_expired` + détail machines.
  - `testHandleExpiredContractsListSitesAvecContratPasse` : nouveau endpoint.
- Tests JS (Jest) : pas de couverture obligatoire pour la modal V1 (UI complexe).

## Points d'attention

- **Idempotence** : si l'admin clique « désactiver tout » et que certaines machines sont déjà inactives, l'API retourne `success: false, message: 'déjà inactive'`. Le code modal doit ignorer ces réponses comme « OK rien à faire » et ne pas afficher d'erreur.
- **Concurrence** : si l'admin a 2 onglets ouverts, l'autre voit le bandeau jusqu'au prochain reload. Acceptable V1.
- **Pas de mass-action endpoint** : V1 = N appels HTTP. Si > 50 machines par site, prévoir un batch côté API plus tard.
- **Tracabilité** : chaque appel `deactivate` log déjà via error_log et `ActivityLogger` (à vérifier).

## Port MissioFlow

À porter à l'identique. Spécificités :

- Multi-tenant : le endpoint `?action=expired_contracts` doit filtrer par `tenant_id`.
- Branding modal : adapter le wording selon MissioFlow.
- Vérifier que `sites.date_renouvellement_contrat` existe bien (devrait être copié de coolcare).

## Estimation

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