---
suivi: 1468
date: 2026-09-07
sujet: Bandeau KPI liste demandes juridiques
chantier: juridique / demandes
type: feature
statut: poussé
hash: afe4597b
fichiers:
  - app/Support/Juridique/DemandeJuridiqueKpiService.php
  - app/Http/Controllers/DemandeJuridiqueController.php
  - resources/js/Pages/Juridique/MesDemandes.vue
  - public/build/
  - tests/Unit/DemandeJuridiqueKpi1468Test.php
  - tests/Unit/DemandeJuridiqueKpi1468SourceTest.php
  - tests/Feature/DemandeJuridiqueKpi1468FeatureTest.php
  - database/data/journal_mises_a_jour_post_2026_06_12.php
  - docs/suivi/SUIVI_1468_kpi_demandes_juridiques.md
---

# SUIVI #1468 — bandeau KPI demandes juridiques

## PROMPT ENVOYÉ

Bandeau d’indicateurs sous l’en-tête de `/juridique/mes-demandes` (page
« Demandes juridiques ») : ouvertes par juriste (+ Non affecté + total),
délai moyen terminées + ancienneté max ouvertes, compteur en cours,
répartition par type. Visible uniquement GS_JURIDIQUE / GS_DIRECTION_GENERALE /
GS_ADMIN_ERP via `m365GroupNames()` brut ; props KPI absentes hors droit.
Pas d’élargissement des autres droits module (lot dédié).

## BASE GIT

```
git log --oneline -1 origin/main
b34e79b9 chore(suivi): hash fiche #1467
```

(Attendu prompt : `51eb6e30` #1465 — main a avancé via #1466/#1467.)

## FICHES LUES

- **#1442** (LEÇON) — ne jamais réassigner une variable payload Inertia avec
  `m365GroupNames()` ; extraire `$groupesM365`. Appliqué dans `mesDemandes`.
- **#1451** (LEÇON) — droits suiveurs via `m365GroupNames()` brut.
- **#1427** — widget demande juridique (piste motif, non réutilisé pour le bandeau).
- Motif UI proche : `compteurs_direction` dossiers fonciers
  (`Foncier/Dossiers/Index.vue`) — bandeau conditionnel `v-if`.

## MESURES ÉTAPE 1

1. **Contrôleur** : `DemandeJuridiqueController::mesDemandes`
   (`app/Http/Controllers/DemandeJuridiqueController.php` ~L114).
   Route `GET /juridique/mes-demandes` (`juridique.mes-demandes`).
   Props avant : `demandes`, `est_juriste`, `filtre`. Après : + `kpi` si droit.

2. **Vue** : `resources/js/Pages/Juridique/MesDemandes.vue`.
   Insertion du bandeau **après** l’en-tête (titre / sous-titre / bouton
   Déposer), **avant** la barre d’outils (bascule Afficher / recherche).

3. **Statuts** (sources) :
   - `DemandeJuridique` constantes + `DemandeJuridiqueTypeRegistry::STATUT_LABELS`
   - `DemandeJuridiqueStatutResolver::STATUTS_VALIDES` :
     `a_qualifier`, `en_cours`, `en_attente`, `termine`, `annule`
   - Anciens : `cloturee`, `basculee` (dépréciés, lecture legacy)
   - **OUVERTS** (confirmé code KPI) = `a_qualifier` + `en_cours` + `en_attente`
   - Fermés = `termine` + `annule` (+ legacy hors ouverts)

4. **Date de fin** : colonne `demandes_juridiques.terminee_le`
   (migration `2026_07_07_160000_add_terminee_le_to_demandes_juridiques.php`).
   Remplie quand statut → `termine` (`resolveTermineeLe`) ; NULL sinon /
   si réouverture. Terminées sans date **exclues** du délai moyen.

5. **Juriste** : `demandes_juridiques.juriste_id` ; `NULL` = « Non affecté ».

6. **Type** : colonne `type_demande` ; 14 libellés dans
   `DemandeJuridiqueTypeRegistry::LABELS`.

7. **Motif réutilisé** : bandeau conditionnel type `compteurs_direction`
   (#1442), pas de nouveau widget dashboard.

## TABLEAU DE MAPPING

| Indicateur | Source | Règle | Statuts | Zéro | Groupes |
|---|---|---|---|---|---|
| Ouvertes par juriste | `demandes_juridiques.juriste_id` + `users.name` | GROUP BY juriste_id ; label Non affecté si NULL ; total = somme | ouverts | « Aucune demande ouverte » | JURIDIQUE, DG, ADMIN_ERP |
| Délai moyen terminées | `created_at`, `terminee_le` | AVG jours ; exclut terminee_le NULL | termine | « Aucune terminée » | idem |
| Ancienneté max ouvertes | `created_at` vs now (bound PHP) | MAX jours | ouverts | « Aucune ouverte » | idem |
| Demandes en cours | somme ventilation | = total point 1 | ouverts | 0 | idem |
| Répartition type | `type_demande` | types présents uniquement, tri desc | tous visibles | « Aucun type renseigné » | idem |

## RÈGLE TYPE — JUSTIFICATION

**N’afficher que les types présents (count > 0).** Volume ~13 demandes ;
afficher 14 lignes dont la majorité à zéro serait illisible. Pas de top-N
arbitraire sans seuil métier : la liste courte des types réellement utilisés
reste lisible et exacte.

## REQUÊTES AJOUTÉES

**3** requêtes agrégées au chargement (si bandeau autorisé) : par juriste
ouverts ; délais AVG/MAX ; par type. Aucun chargement PHP de toutes les
demandes pour compter.

## DROITS BANDEAU

`m365GroupNames()` brut ∩ `{GS_JURIDIQUE, GS_DIRECTION_GENERALE, GS_ADMIN_ERP}`.
Clé `kpi` **omise** du payload hors droit (pas `null`). Périmètre données =
`DemandeJuridiqueListQuery` (pas de contournement visibilité).
`est_juriste` / bascule inchangés (`hasM365Group` historique).

Peuplement connu (prompt) : GS_JURIDIQUE 2 · GS_DIRECTION_GENERALE 3 ·
GS_ADMIN_ERP 1 — mécanisme peuplé.

## RELEVÉ GS_JURIDIQUE (lecture seule — lot droits suivant)

| Fichier | Ligne(s) | Rôle |
|---|---|---|
| `DemandeJuridiqueListQuery.php` | 28, 46 | Liste complète / filtre mes dossiers |
| `DemandeJuridiquePresenter.php` | 52, 135, 339 | Options juriste / flags fiche |
| `DemandeJuridiqueParticipantResolver.php` | 34, 46, 55, 71 | Accès / participants / demandeur |
| `DemandeJuridiqueStatutResolver.php` | 14 | Changement statut |
| `DemandeJuridiqueSuiveurResolver.php` | 13 | Gestion suiveurs |
| `DemandeJuridiqueDemandeurResolver.php` | 14 | Choix demandeur |
| `JuristeResolver.php` | 16 | Annuaire juristes |
| `JuridiqueRegistreParticipantResolver.php` | 15 | Accès registre |
| `DemandeJuridiqueProfilDemandeur.php` | 30 | Libellé profil |
| `AffecterJuristeDemandeJuridiqueRequest.php` | 15 | Autorisation affectation |
| `DemandeJuridiqueController.php` | 123 | `est_juriste` liste |
| `HandleInertiaRequests.php` | 225 | Flag Inertia |
| `NotifierDepotDemandeJuridique.php` | 71 | Notif dépôt (m365 brut) |
| `AlerteService.php` | 115 | groupe_cible alerte |
| `ValidationController.php` | 141 | est_juriste (hors module fiche) |

Aucune de ces portes modifiée dans #1468.

## SYNTHÈSE

Service `DemandeJuridiqueKpiService` + wiring `mesDemandes` + bandeau Vue
responsive (grid 1/2/4, pas `overflow-x-hidden`). Tests unit Eloquent schéma
minimal (#1440) + source + Feature Inertia (skipped agent sur sqlite pbt).

## DÉPLOIEMENT / TEST

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

Pas de migrate. Pas de npm/composer/optimize sur OVH.

Comptes Robin : Christelle/Manon (JURIDIQUE) → bandeau ; un DG → bandeau ;
un user hors 3 groupes → page inchangée, pas de prop kpi ; vérifier Non affecté
sur demande sans juriste.

## LEÇON

Bandeau KPI = même piège #1442 (groupes M365 + props liste) : `$groupesM365`
dédié, clé absente hors droit. Délai moyen exclut `termine` sans `terminee_le`.
Ancienneté max : binder `now()` PHP (respecte Carbon::setTestNow en tests).
