# Chantier : Synchronisation planning -> mail client RDV

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

**Date :** 2026-05-12
**Repo :** coolcare-app (port missioflow-app prevu en fin de chantier)
**Statut :** PLAN - en attente validation Bruno

## Objectif metier

A la creation (ou modification du planning) d'une machine sous contrat, envoyer automatiquement
au client un mail recapitulatif avec la liste des passages d'entretien prevus sur toute la duree
du contrat en cours, accompagne :
- d'un tableau HTML directement lisible dans le mail,
- d'un PDF recapitulatif en piece jointe,
- d'un fichier ICS importable dans le calendrier client (Outlook / Google Calendar).

## Cadrage (valide avec Bruno)

| Sujet | Choix |
|---|---|
| Declencheur | Creation machine + modification du planning (replanification auto) |
| Destinataires | Comme les rappels J-1 : `email_responsable` (To) + `contact_email_2..6` (Cc) + `services@coolcare.fr` (Cc interne) |
| Format contenu | Tableau HTML dans le mail + PDF en PJ + ICS en PJ |
| Perimetre | Une machine par mail (1 ajout = 1 mail centre sur cette machine) |

## Existant a reutiliser

| Composant | Chemin | Role |
|---|---|---|
| Trigger planning | `MachineApiController::triggerPlanningForMachine()` ligne 1164 (create) / 939 (update) | Genere les interventions futures via `scheduleMachineById` |
| Moteur planning | `src/services/MaintenancePlanningService.php` | Genere les `interventions` (`statut=planifiee`, `created_by=NULL`) |
| Modele mail client | `src/services/InterventionClientReminderNotifier.php` | Pattern complet HTML + audit + logo embarque |
| Collect contacts | `src/services/traits/SiteContactsCollectorTrait.php` | `collectSiteContacts()` + `buildAddressing()` |
| Envoi SMTP | `src/utils/EmailSender.php::sendWithAttachments()` | Gere PJ + images embarquees |
| Generation PDF | `src/services/PdfRapportGotenberg.php` | Gotenberg http://gotenberg:3000 (deja en prod) |

## Architecture proposee

### Nouveau service : `MachinePlanningClientNotifier`

Fichier : `src/services/MachinePlanningClientNotifier.php`

```
notify(int $machineId, string $trigger, array $planningResult): array
  trigger ∈ {'machine_created', 'machine_updated'}
  planningResult = retour de scheduleMachineById (created, deleted, items)

  1. Si planning vide (no_contract_end_date OU items = 0) -> skip + log
  2. Si site n'est pas sous contrat -> skip + log
  3. Si trigger = machine_updated mais planning inchange (created=0, deleted=0) -> skip
  4. Charger machine + site + interventions futures (date_prevue >= today, statut=planifiee)
  5. Collecter contacts via SiteContactsCollectorTrait
  6. Si aucun contact valide -> log warning + skip
  7. Construire HTML body, PDF, ICS
  8. Envoyer via EmailSender::sendWithAttachments
  9. Persister audit (nouvelles colonnes machines)
```

### Nouvelle migration BDD

```sql
ALTER TABLE machines
  ADD COLUMN planning_client_notified_at DATETIME NULL
    COMMENT 'Dernier envoi mail recap planning client',
  ADD COLUMN planning_client_notification_log JSON NULL
    COMMENT 'Audit des envois : [{sent_at, trigger, to, cc, status, error, interventions_count}, ...]';
```

Pattern identique a `interventions.client_reminder_sent_at` / `client_reminder_log`.

### Generateur PDF dedie

Fichier : `src/services/MachinePlanningPdfGenerator.php`

- Template HTML avec en-tete (logo, nom client, site, machine)
- Tableau des passages (date FR, type, description)
- Footer (signature CoolCare, contact)
- Genere via Gotenberg (Chrome headless) -> retourne contenu binaire
- Nom fichier : `planning_<site_id>_<machine_id>_<YYYYMMDD>.pdf`

### Generateur ICS dedie

Fichier : `src/services/MachinePlanningIcsGenerator.php`

- Format iCalendar RFC 5545
- 1 `VEVENT` par intervention planifiee :
  - `UID` = `intervention-<id>@coolcare.fr`
  - `DTSTART` / `DTEND` (duree par defaut 2h, ou utiliser duree intervention si dispo)
  - `SUMMARY` = "CoolCare - Maintenance <nom machine>"
  - `DESCRIPTION` = type + machine + site
  - `LOCATION` = adresse complete site
  - `ORGANIZER` = services@coolcare.fr
  - `STATUS:CONFIRMED`
- Encodage UTF-8, CRLF
- Nom fichier : `planning_<site_id>_<machine_id>_<YYYYMMDD>.ics`

### Integration aux hooks

| Endroit | Quand | Trigger |
|---|---|---|
| `MachineApiController::handleCreate()` apres ligne 1164 | Apres `triggerPlanningForMachine` | `'machine_created'` |
| `MachineApiController::handleUpdate()` apres ligne 939 | Apres replanification | `'machine_updated'` (conditionnel: created>0 OR deleted>0) |

Best-effort : si le mail echoue, on log mais on ne rollback PAS la creation/modification machine.
On enveloppe l'appel dans un `try/catch` qui ecrit dans `Logger::error` + `planning_client_notification_log`.

## Phases d'execution

### Phase 1 - Migration + service squelette
- Migration `migration_2026_05_12_machine_planning_notification.sql`
- Classe `MachinePlanningClientNotifier` avec methode `notify()` qui ne fait que charger les donnees et logger (pas encore d'envoi)
- Tests unitaires : chargement donnees + skip cas (pas de contrat, pas de contact, planning vide)

### Phase 2 - Generation HTML + tableau
- Methode `buildHtml()` calquee sur `InterventionClientReminderNotifier::buildHtml()`
- Tableau des passages avec format date FR
- Bloc intro/footer + logo embarque
- Test : snapshot HTML genere

### Phase 3 - Generation PDF + ICS
- `MachinePlanningPdfGenerator` (Gotenberg)
- `MachinePlanningIcsGenerator` (ICS RFC 5545)
- Tests : structure ICS valide (parse via lib si dispo, sinon regex sur les VEVENT)

### Phase 4 - Envoi + audit
- Branchement `EmailSender::sendWithAttachments(to, subject, html, attachments, cc, embeddedImages)`
- UPDATE machines `planning_client_notified_at` + append JSON `planning_client_notification_log`
- Tests : mock EmailSender, verifier appel + persistence audit

### Phase 5 - Integration hooks Controller
- Hook dans `handleCreate()` apres ligne 1164
- Hook dans `handleUpdate()` apres ligne 939 (conditionnel)
- Test d'integration : POST /api/machines -> verifier qu'un mail est envoye (via mock EmailSender)

### Phase 6 - Documentation + port MissioFlow
- Markdown port `missioflow-app/docs/CHANTIER_SYNC_PLANNING_MAIL_CLIENT.md` (local, jamais commit)
- Valider visuellement le mail (envoi reel test sur compte personnel) avant push prod

## Risques / Edge cases

| Cas | Comportement |
|---|---|
| Site pas sous contrat | Pas de mail (skip + log info) |
| Machine sans `date_premiere_intervention_annuelle` | Planning vide -> skip mail |
| Site sans `date_renouvellement_contrat` ET machine sans `date_fin_contrat` | Planning vide -> skip mail |
| Aucun contact mail valide sur le site | Log warning + skip |
| Echec SMTP | Log erreur, audit avec status=error, pas de rollback machine |
| Modif machine sans impact planning (ex: changement nom) | Skip mail (created=0 deleted=0) |
| Creation machine + nombreux passages (ex: 20+) | Tableau scrollable, PDF multi-pages, ICS gere |

## Securite

- HTML : echappement systematique des donnees client via `htmlspecialchars` (pattern existant dans notifier intervention)
- ICS : echappement caracteres speciaux (`,`, `;`, `\`, newline) selon RFC 5545
- PDF : pas de donnees sensibles ; pas d'inclusion de prix/devis
- Aucune SSRF possible (Gotenberg en interne via docker network)

## Tests

- Unitaires : skip conditions, generation HTML/ICS, persistence audit
- Integration : creation machine -> mail envoye (mock SMTP)
- Manuel : creation machine reelle sur env de dev, verification :
  - Reception mail dans Gmail (To responsable, Cc autres)
  - Tableau HTML correct + lisible mobile
  - PDF ouvre + affiche bien
  - ICS importable dans Google Calendar et Outlook
