---
suivi: 1214
date: 2026-08-05
sujet: Mes tâches — coût morph attachable (+11 diagnosés)
chantier: foncier / taches
type: fix
statut: poussé
hash: bfbc8321
fichiers:
    - app/Models/TacheSuivi.php
    - app/Http/Controllers/MesTachesController.php
    - app/Services/MesTachesResumeService.php
    - app/Services/TacheSuiviService.php
    - app/Services/Assistant/AssistantTacheSuiviRetrievalService.php
    - app/Http/Controllers/Foncier/DossierFoncierController.php
    - tools/diag/diag_mes_taches_queries_1214.php
    - tools/diag/diag_mes_taches_queries_1211.php
    - tests/Unit/TacheSuiviAttachableEager1214Test.php
    - docs/suivi/SUIVI_1214_mes_taches_queries_morph.md
---

# SUIVI #1214 — Expliquer / corriger le surcoût morph Mes tâches

## PROMPT ENVOYÉ

Mesurer le delta attachable à 3 volumes ; lister les requêtes ajoutées ; corriger
si N+1 ; objectif delta ≤ +3 constant ; inventaire des ~15 requêtes de base ;
visibilité D62 intangible ; pas de migration ; commit + push.

## Fiches lues

- #1211 — eager constrain id/reference ; livrable annonçait « ~+1 morph » ; diag
  `diag_mes_taches_queries_1211.php` a produit 15/26/Δ11 en prod (artefact).
- #1209 — morph + scope D62 ; tag référence seule.
- #1208 — modèle seed + ROLLBACK pour preuve constante.
- #1184 — eager partiel / colonnes nommées (rappel anti-rattrapage).

## OBJET 1 — Tableau multi-volumes

Script : `php tools/diag/diag_mes_taches_queries_1214.php`  
(seed + ROLLBACK ; `Auth::setUser` ; `enableQueryLog` — **pas** d’empilement `DB::listen`).

Mesure locale Cursor (`erp_immo_migrate_1113`) — **à rejouer sur OVH** pour le
tableau prod (ne pas confondre avec le stub) :

| Tâches visibles | SANS eager | AVEC eager | Delta | Morph SELECT |
|-----------------|------------|------------|-------|--------------|
| 10              | 4          | 5          | **+1** | 1 |
| 50              | 4          | 5          | **+1** | 1 |
| 200             | 4          | 5          | **+1** | 1 |

**Conclusion : delta CONSTANT (= +1).** Pas de N+1 morph.

Le Δ11 du diag #1211 en prod est un **artefact de mesure** : chaque appel à
`$listen()` enregistre un *nouveau* `DB::listen` sans retirer le précédent → le
2ᵉ run (AVEC) compte chaque requête ×2, plus asymétrie de cache
`ProgrammeAccessService`. Le coût morph réel reste ~+1.

## OBJET 2 — Hypothèses

| # | Hypothèse | Verdict | Preuve |
|---|-----------|---------|--------|
| 1 | `userCanViewRecord` N+1 non mémoïsé | **Écartée** (delta morph) | Serialize liste ne l’appelle pas ; ACL = EXISTS SQL dans `visiblePourUtilisateur` |
| 2 | `$appends` DossierFoncier | **Écartée** | Aucun `$appends` sur le modèle |
| 3 | Eager partiel #1184 | **Écartée** | Tag lit seulement `id`/`reference` déjà select |
| 4 | morphTo sans morphWith | **Faible / durci** | Minimal déjà +1 ; helper ajoute `morphWith([])` + `withoutGlobalScopes` |
| 5 | Presenter re-query | **Écartée** | `serializeOriginesPourListe` lit relationLoaded |
| — | Empilement `DB::listen` diag #1211 | **Confirmée** | Cause du Δ11 affiché |

SQL ajoutée @50 (mesure propre) :

1. `select id, reference from dossiers_fonciers where id in (?, ?)`

## OBJET 3 — Correction

Helper `TacheSuivi::eagerAttachableDossierTag()` :

- `morphWith([DossierFoncier::class => []])`
- `constrain` + `withoutGlobalScopes()` + `select(['id','reference'])`
  (ACL déjà sur `taches_suivi` ; évite EXISTS redondant sur le SELECT morph)

Branché : MesTachesController, MesTachesResumeService (digest),
AssistantTacheSuiviRetrievalService, TacheSuiviService (fresh create),
DossierFoncierController (panneau).

Pas de mémo habilitation (pas de N+1 réel). Pas de dénormalisation référence.
Eager conservé (tag affiché). Objectif ≤ +3 : **atteint (+1 constant)**.

## OBJET 4 — Inventaire base (hors morph, non corrigé)

Sur page complète Mes tâches, le diag #1211 regroupait liste + resume ≈ 15 sans
eager. Décomposition propre (liste seule + resume séparés) :

**Liste suivi (≈ 4–9 selon volume / relations présentes)**  
1. SELECT `taches_suivi` + sous-selects `withCount` checklist (+ EXISTS ACL dossier
   dans le WHERE via `visiblePourUtilisateur`)  
2. Eager `users` (créateur)  
3. Eager pivot / `users` (personnes)  
4. Eager étiquettes (+ programmes si présentes)  
5. Eager projets si `projet_id`  

**Resume badge (`resumePourUtilisateur`, ≈ 6 COUNT)**  
1–2. COUNT `tasks` ouvertes / en retard  
3–6. COUNT `taches_suivi` manage / assigné / union / retard (chacun avec scope D62)

Le total « 15 » prod = liste + resume (+ évent. tâches op / factures hors ce diag
liste). Chantier ultérieur éventuel — **non touché** ici.

## Visibilité

Seed 20 dossiers attachés : habilité (créateur) = 20 ; non-habilité = 0.  
Aucun changement de `scopeVisiblePourUtilisateur` — périmètre badge/digest inchangé.

## DÉPLOIEMENT-TEST

```
git pull origin main
php artisan view:clear
# pas de migrate ; pas de journal:sync
php tools/diag/diag_mes_taches_queries_1214.php
```

Vérifier le tableau 10/50/200 : delta CONSTANT ≤ 3 ; SQL ajoutée = 1 SELECT dossiers.

## LEÇON

1. **Ne jamais empiler `DB::listen`** dans un diag multi-runs : reset via
   `enableQueryLog` / `flushQueryLog`, ou un seul listener + compteur remis à zéro
   *sans* ré-enregistrer.
2. `Auth::login` / `logout` polluent (sessions) ; pour un diag CLI : `Auth::setUser`
   + `Auth::forgetUser`.
3. Un delta affiché élevé n’est pas forcément un N+1 — mesurer à plusieurs volumes
   *avec* un compteur fiable avant de « corriger ».
