---
suivi: 1873
date: 2026-10-13
sujet: Alignement des fiches monprojet et retraitement
chantier: monprojet
type: feature
statut: poussé
hash: 77c8d215f
fichiers:
  - app/Services/Monprojet/MonprojetEnveloppeControle.php
  - app/Services/Monprojet/MonprojetRetraitementService.php
  - app/Services/Monprojet/MonprojetTableauBordService.php
  - app/Services/Monprojet/MonprojetPurgeService.php
  - app/Http/Controllers/MonprojetFicheController.php
  - app/Support/Monprojet/MonprojetAffichage.php
  - app/Support/Monprojet/MonprojetVocabulaire.php
  - app/Support/Monprojet/MonprojetDroits.php
  - app/Models/MonprojetQuarantaine.php
  - database/migrations/2026_10_13_171500_monprojet_quarantaine_retraitement.php
  - database/data/journal_mises_a_jour_post_2026_06_12.php
  - resources/js/Pages/Monprojet/Index.vue
  - resources/js/Pages/Monprojet/Show.vue
  - resources/js/Pages/Monprojet/TableauBord.vue
  - resources/js/Pages/Monprojet/Quarantaine.vue
  - routes/web.php
  - tests/Unit/MonprojetContratV2Test.php
  - tests/Unit/MonprojetAlignement1873Test.php
  - public/build/
  - docs/suivi/SUIVI_1873_alignement_monprojet.md
---

# SUIVI #1873 — Alignement ERP sur les fiches monprojet réelles

## Fiches lues

- #1864 — lecture v1 et v2. La v1 reste lue. `pieces_en_plus` n’avait pas encore de liste fermée : ce lot reprend les codes du site (annexe #1863) et lit la précision dans `valeurs_recues.pieces_en_plus`.
- #1809 — signature, quarantaine, droits sur `m365GroupNames()` brut, sans `authorize()`.
- #1814 — durées de purge inchangées. Les textes libres ajoutés (`projet_libre` et les précisions) sont effacés avec les autres textes d’identité.
- #1858 — la relève part du site. Le retraitement retire la marque « déjà vu » pour que la prochaine relève relise le message. Aucun appel Microsoft en CLI.

Contrats annexes lus : #1863 (pièces en plus), #1865 (consignes), #1866 (parcours). Source : `C:\annexes\docs\contrats\monprojet_email_v2.md` et `C:\annexes\monprojetimmo`.

## PROMPT ENVOYÉ

Le premier envoi réel (13/10/2026 14:29, sujet « [monprojet] Projet de Robin ») est en quarantaine : « Suivi illisible. ». Aligner la lecture sur le contrat v2 complété, tolérer le champ suivi et tout champ facultatif illisible, afficher les nouveaux champs par parcours, ajouter les filtres et les répartitions, et permettre à GS_ADMIN_ERP de retraiter une ligne de quarantaine.

## SYNTHÈSE

### Constat

« Suivi illisible. » est produit dans `MonprojetEnveloppeControle` (avant ce lot) : `suivi()` ligne 629 renvoie faux dès que la valeur n’est pas un objet, puis `normaliserVersion1` ligne 291 et `normaliserVersion2` ligne 942 mettent la fiche en quarantaine.

L’ERP attendait un objet `{campagne, accroche, format}`, ou l’absence / null (déjà acceptés). Le site construit `suivi` avec `array_filter` dans `FicheInitiale` (ligne 31). Sans campagne ni accroche, le résultat est une liste PHP vide. `JsonCanonique::preparer` laisse une liste telle quelle : `[]` reste `[]`. Seul un tableau associatif vide devient `{}`. `json_decode` côté ERP fait de `[]` un tableau, pas un objet. Le sujet « [monprojet] Projet de Robin » est celui de `EnveloppeMonprojet::sujet`. Hypothèse confirmée : liste vide, pas null (déjà accepté) et pas `{}`.

### Lecture

`suivi` absent, null, `[]` ou `{}` : aucune origine technique, fiche ingérée. Un objet est lu sur les clés connues ; une clé inconnue est ignorée. Une forme inattendue est ignorée avec un avertissement `monprojet.champ.ignore` qui ne contient que le nom du champ.

Le même principe vaut pour les champs facultatifs : une valeur illisible est ignorée, la fiche continue. Seuls la signature, la version et les champs obligatoires (fiche, transcription, commune, budget, consentement, et un e-mail présent mais invalide) peuvent encore rejeter.

Les nouveaux champs sont stockés dans `details` et affichés par parcours. Une rubrique qui ne concerne pas le parcours n’apparaît pas. Les trajets sont masqués si « En activité » vaut non. L’état de la vente n’apparaît que si la situation est « propriétaire et je dois vendre ». `consignes_version` est affichée en petit sous le titre, « Consignes {version} ».

### Retraitement

Bouton « Retraiter » sur une ligne de quarantaine, réservé à `GS_ADMIN_ERP` via `m365GroupNames()` brut. `GS_DSI` voit la quarantaine et ne peut pas retraiter. Si le message est encore dans la boîte (GET Graph `messages/{id}?$select=id`, sans conserver le corps), les identifiants Graph et d’enveloppe sont retirés, la ligne garde « Retraité le … par … ». La relève suivante peut recréer la fiche. Si le message n’est plus dans la boîte, la ligne le dit et les identifiants restent. Une panne de consultation ne marque pas le message absent.

Signature, durées de purge et groupes de lecture sont inchangés.

## Mapping

| Champ du contrat | Colonne / JSON | Libellé affiché |
|---|---|---|
| pieces_en_plus | details.pieces_en_plus | Pièces en plus |
| valeurs_recues.pieces_en_plus | details.pieces_en_plus_precision | Pièces en plus (italique) |
| projet_libre | details.projet_libre | Projet en quelques mots |
| parcours_retenu | details.parcours_retenu | Parcours retenu |
| motif_changement | details.motif_changement | Motif du changement |
| valeurs_recues.motif_changement | details.motif_changement_precision | Motif du changement (italique) |
| etat_vente | details.etat_vente | État de la vente |
| contrainte_date | details.contrainte_date | Contrainte de date |
| valeurs_recues.contrainte_date | details.contrainte_date_precision | Contrainte de date (italique si Autre) |
| en_activite | details.en_activite | En activité |
| services_a_pied | details.services_a_pied | Services à pied |
| objectif_investissement | details.objectif_investissement | Objectif |
| nombre_logements | details.nombre_logements | Nombre de logements |
| mode_location | details.mode_location | Mode de location |
| locataires_vises | details.locataires_vises | Locataires visés |
| taille_maison | details.taille_maison | Taille de maison |
| secteur_preference | details.secteur_preference | Préférence de secteur |
| budget_portee | details.budget_portee | Portée du budget |
| premier_investissement | details.premier_investissement | Premier investissement |
| gestion | details.gestion | Gestion |
| duree_detention | details.duree_detention | Durée de détention |
| mode_detention | details.mode_detention | Mode de détention |
| attentes | details.attentes | Vos attentes |
| valeurs_recues.attentes | details.attentes_precision | Vos attentes (italique) |
| connu_par | details.connu_par | Vous nous avez connus par |
| valeurs_recues.connu_par | details.connu_par_precision | Vous nous avez connus par (italique si Autre) |
| consignes_version | details.consignes_version | Consignes {version} |
| avancement expert_comptable | details.avancement | J'ai consulté un expert-comptable ou un conseiller |
| priorites sante | details.priorites | Avoir des services de santé à proximité |
| priorites demande_locative | details.priorites | Une forte demande locative |
| priorites faible_entretien | details.priorites | Un faible entretien |
| priorites valorisation | details.priorites | Une valorisation dans le temps |

Parcours de la liste : `premiere_rp`, `changer_rp`, `nouvelle_etape`, `investissement`, `autre`. Si le projet est `autre` et que `parcours_retenu` est l’un des quatre premiers, la liste et l’affichage suivent ce parcours retenu.

Filtre liste « Connu par » : `details.connu_par`. Répartitions du tableau de bord : attentes, connu par.

## Migration

`2026_10_13_171500_monprojet_quarantaine_retraitement.php`

Ajoute `retraite_le`, `retraite_par_user_id` (clé `mp_quar_retraite_user_fk`, null si l’utilisateur disparaît), `message_absent`. `message_graph_id` devient nullable.

`down()` supprime les lignes dont l’identifiant Graph est vide, retire la clé et les colonnes, remet `message_graph_id` obligatoire.

## DÉPLOIEMENT-TEST

```
git pull origin main
php artisan optimize:clear
php artisan view:clear
php artisan route:clear
php artisan migrate --force
php artisan journal:sync
```

Pas de npm, pas de composer, pas de config:cache sur OVH.

1. Une fiche dont `suivi` est `[]`, null, `{}` ou absent est créée.
2. Une fiche avec une clé inconnue dans `suivi`, ou un champ facultatif d’un mauvais type, est créée. Un champ obligatoire manquant reste en quarantaine.
3. Fiche première résidence : pas de rubrique investissement. Fiche investissement : objectif, logements, location, pas le foyer ni les chambres.
4. La précision des pièces en plus vient de `valeurs_recues.pieces_en_plus`.
5. La liste filtre par parcours et par « connu par ». Le tableau de bord compte les attentes et cette origine.
6. GS_ADMIN_ERP : Retraiter retire la marque, la relève suivante crée la fiche si le message est encore là. Message absent : la ligne le dit. GS_DSI : bouton refusé.

## LEÇON

`array_filter` sur un tableau associatif vide en PHP produit une liste `[]`, que le JSON canonique n’enveloppe pas en objet. Un champ facultatif du contrat ne doit jamais mettre la fiche en quarantaine : seul un obligatoire, la signature ou la version le peuvent.

Retirer seulement l’identifiant Graph ne suffit pas. L’enveloppe a déjà été vue (la signature était valide). Il faut aussi retirer l’identifiant d’enveloppe, sinon la relève suivante répond « déjà vu » au lieu de créer la fiche.
