---
suivi: 1054
date: 2026-07-29
sujet: Notification Teams à la clôture d'une tâche de suivi
chantier: taches_suivi
type: feature
statut: poussé
hash: 5eed4227
fichiers:
  - app/Services/TacheSuiviService.php
  - app/Services/TeamsNotificationService.php
  - database/data/journal_mises_a_jour_post_2026_06_12.php
  - docs/suivi/SUIVI_1054_notification_teams_cloture_tache.md
---

## PROMPT ENVOYÉ

SUIVI #1054 — notifier Teams à la clôture (`termine` + `abandonne`) : créateur ∪ associés
hors acteur. Réutiliser TeamsNotificationService + mécanique assignation. Un seul message
par clôture (bouton / statut / DnD). Log::error sur échec, clôture non bloquée. Journal #412.
Pas de Vue / migration. Préserver #1042/#1048/#1051. Fiche #1045 absente du repo — cartographie
reconstituée depuis le code.

## SYNTHÈSE

### Comportement
- `valider()` : si ancien statut ≠ `termine` → `notifierClotureTache(..., termine)` après commit.
- `changerStatut(abandonne)` : si ancien ≠ `abandonne` → notification après commit.
- Autres statuts : aucun envoi.
- Destinataires : créateur ∪ personnes, unique par id, exclude acteur ; email M365 requis.
- Message : `#{id}`, objet, terminée/abandonnée, qui a clôturé, lien `taches-suivi.show`.

### Unicité d’envoi (preuve)
| Chemin | Appelle | Gate |
|---|---|---|
| POST `/valider` | `valider()` | ancien ≠ termine |
| PATCH statut=termine | `changerStatut` → `valider()` | idem (une seule entrée) |
| DnD colonne Terminé | `deplacerKanbanColonne` : `if (statut !== actuel) changerStatut` → `valider()` | pas d’appel si déjà termine |
| abandonne (PATCH / DnD) | `changerStatut` direct | ancien ≠ abandonne |

Pas de double appel valider+changerStatut. Re-clôture / re-drop → 0 notif.

### Réutilisé vs ajouté
- Réutilisé : `TeamsNotificationService::notifierUtilisateur` (timeout 10s), exclusion acteur +
  skip email vide (pattern `notifierAssignationPersonnes`).
- Ajouté : `notifierClotureTache` + `buildTeamsClotureMessage` ; envoi **après** transaction.

### Logs
- Échec HTTP / exception service : `Log::error` + trace (ex-`warning`, leçon #1043/#1045).
- Webhook absent : `Log::info` inchangé.
- Clôture : si `notifierUtilisateur` false et webhook configuré → `Log::error` contextuel ;
  exception wrappée → `Log::error` non fatale.
- Assignation : booléen de retour **toujours ignoré** (à traiter ailleurs). Niveau error du
  service profite toutefois aux échecs d’assignation (bénéfice collatéral).

### CLI / ops
- Aucune commande artisan / job n’appelle `valider`/`changerStatut` sur `taches_suivi`
  (grep Console = 0). Chemins = web PHP-FPM uniquement → HTTPS Teams OK.
- Tâches opérationnelles (`tasks`) : hors périmètre.

### Docs #1045
Fichier `docs/suivi/SUIVI_1045_*` introuvable dans le dépôt au moment du lot ; diagnostic
reconstitué depuis `TacheSuiviService` / routes / prompt.

## DÉPLOIEMENT-TEST

```bash
git pull origin main
php artisan optimize:clear
php artisan view:clear
find app/ -name "*.php" -exec touch {} +
php artisan journal:sync
```

Pas de migrate / npm / composer / optimize.
Test réel : clôturer une tâche en prod ; vérifier réception Teams (ex. Lahouari / Laury).

## LEÇON

Un envoi de notification dont le booléen de retour est ignoré et dont l’échec sortait en
`Log::warning` est un envoi qu’on ne saura jamais avoir raté. Surveiller `production.ERROR`.
