---
suivi: 1809
date: 2026-10-07
sujet: Réception des fiches monprojet
chantier: monprojet
type: feature
statut: poussé
hash: 0fe78f93
fichiers:
  - docs/contrats/monprojet_email_v1.md
  - docs/suivi/SUIVI_1809_monprojet_reception_fiches.md
  - config/services.php
  - .env.example
  - app/Support/Monprojet/MonprojetVocabulaire.php
  - app/Support/Monprojet/MonprojetDroits.php
  - app/Support/Monprojet/MonprojetJsonCanonique.php
  - app/Support/Monprojet/MonprojetSignature.php
  - app/Support/Monprojet/MonprojetTexte.php
  - app/Services/Monprojet/MonprojetEnveloppeControle.php
  - app/Services/Monprojet/MonprojetIngestionService.php
  - app/Services/Monprojet/MonprojetTexteExpediteur.php
  - app/Services/Monprojet/MonprojetReleveService.php
  - app/Services/Monprojet/MonprojetSimulation.php
  - app/Console/Commands/MonprojetReleverCommand.php
  - app/Console/Commands/MonprojetSimulerCommand.php
  - app/Models/MonprojetFiche.php
  - app/Models/MonprojetTranscription.php
  - app/Models/MonprojetQuarantaine.php
  - app/Models/MonprojetFicheHistorique.php
  - app/Http/Controllers/MonprojetFicheController.php
  - database/migrations/2026_10_07_141500_create_monprojet_tables.php
  - database/data/journal_mises_a_jour_post_2026_06_12.php
  - app/Support/JournalMiseAJourVisibility.php
  - routes/web.php
  - routes/console.php
  - resources/js/Layouts/AppShell.vue
  - resources/js/Pages/Monprojet/Index.vue
  - resources/js/Pages/Monprojet/Show.vue
  - resources/js/Pages/Monprojet/Quarantaine.vue
  - public/build/
  - tests/Unit/MonprojetSignatureTest.php
---

# SUIVI #1809 — Réception des fiches monprojet

Côté Hectarion seul. La page publique monprojet.hectare.fr n’est pas dans ce dépôt.

## Fiches lues

`SUIVI_1806_diag_monprojet_cote_hectarion.md`. Elle écarte le glisser-déposer Outlook (`/me`), les leads Meta, les contacts et la prospection foncière. Elle signale que `getAppAccessToken` n’a pas d’interdiction `PHP_SAPI` : le message « contexte web requis » est dans `SiwiDocumentDepotService` quand le jeton est une chaîne vide. Ce lot n’appelle pas ce service et ne le modifie pas. La commande `monprojet:relever` ne joint plus Microsoft : en ligne de commande elle s’arrête. La relève part du site (#1858).

Pas de menu Commercialisation autonome (c’est un onglet de programme). L’entrée est sous Groupe, à côté de META.

Les droits passent par `m365GroupNames()` brut. `hasM365Group()` et le middleware `m365.groups` traitent l’admin comme membre de tout groupe. Une permission `*.lecture` ouvrirait l’écran à ENVOL via le joker de lecture.

## PROMPT ENVOYÉ

Contrat d’e-mail signé, relève Graph lecture seule, tables neuves, liste, fiche, quarantaine, simulation CLI. Hors lot : tableau de bord, purge automatique.

## SYNTHÈSE

E-mail : sujet `[monprojet] Projet de {prénom}`, corps lisible, pièce `monprojet.json`. Signature HMAC-SHA256 de `version|id|emis_le|sha256(canonique(fiche)+canonique(transcription))`. Contrat : `docs/contrats/monprojet_email_v1.md`.

Config `services.monprojet` : `MONPROJET_BOITE`, `MONPROJET_SIGNATURE_CLE`. Aucune valeur dans le dépôt. Clé ou boîte absente : la relève s’arrête avec un message, sans tourner.

Relève : `GET /users/{boîte}/messages`, 50 messages, jeton applicatif, rôle `Mail.Read` (un jeton qui a déjà `Mail.ReadWrite` est accepté en lecture ; aucun appel d’écriture, aucun marquage lu, aucun déplacement). Déjà traités : identifiant Graph unique en base. Verrou anti-chevauchement, partagé avec « Synchroniser maintenant » (une fois par minute). Pas de tâche planifiée : le premier GET authentifié de chaque heure lance la relève depuis le site (#1858).

Échec : quarantaine (identifiant Graph, reçu le, expéditeur, sujet, motif). Pas le corps. `Log::warning` sans clé, sans jeton, sans contenu.

Conservation calculée à la création : 3 ans si e-mail ou téléphone, sinon 12 mois.

Quatrième table `monprojet_fiche_historiques` : le suivi et la note interne sont historisés. Le lot en demandait trois ; l’historique ne tient pas dans la fiche sans écraser la valeur précédente.

Menu Groupe → Projets prospects. Quarantaine visible seulement pour `GS_DSI` et `GS_ADMIN_ERP`. Fiches : `GS_COMMERCIAL`, `GS_DIRECTION_GENERALE`, `GS_COMITE_HECTARE`, `GS_COMMUNICATION`, `GS_ADMIN_ERP`, `GS_DSI`. Refus 403 sinon. Le contrôleur n’appelle pas `authorize()` (pas le trait) : le refus est `abort(403)` après intersection de la liste brute.

`monprojet:simuler` et `monprojet:simuler --invalide` passent dans le même pipeline. `--rejeu` renvoie la dernière enveloppe avec un nouvel identifiant de message, pour le test anti-rejeu. Réservé au CLI. La fiche porte le marqueur simulation.

Bundles : `Index-BmMxLto92.js`, `Show-DtW2IqRg.js`, `Quarantaine-b8U_nR_k.js`, coque `app-BX-vSRuG.js`.

### Mapping

Libellé affiché = libellé relu. Détail des tranches dans le contrat.

| Famille | Colonne | Valeur postée ou JSON | Libellé |
|---|---|---|---|
| Suivi | statut_traitement | nouveau, en_cours, traite, sans_suite | Nouveau, En cours, Traité, Sans suite |
| Budget | budget | moins_250, de_250_a_350, de_350_a_450, de_450_a_600, plus_600, ne_sait_pas, autre | Moins de 250 000 €, 250 000 à 350 000 €, 350 000 à 450 000 €, 450 000 à 600 000 €, Plus de 600 000 €, Ne sait pas, Autre |
| Type | type_projet | maison_terrain, maison_seule, terrain_seul, renovation, autre | Maison et terrain, Maison seule, Terrain seul, Rénovation, Autre |
| Foyer | foyer | une_personne, couple, famille, autre | Une personne, Couple, Famille, Autre |
| Chambres | chambres | 1, 2, 3, 4, 5_ou_plus, ne_sait_pas, autre | 1, 2, 3, 4, 5 ou plus, Ne sait pas, Autre |
| Horizon | horizon | des_que_possible, moins_6_mois, six_a_douze_mois, un_a_deux_ans, plus_de_deux_ans, ne_sait_pas, autre | Dès que possible, Moins de 6 mois, 6 à 12 mois, 1 à 2 ans, Plus de 2 ans, Ne sait pas, Autre |
| Terrain | terrain_possede | oui, non, en_recherche, ne_sait_pas, autre | Oui, Non, En recherche, Ne sait pas, Autre |
| Contact | mode_contact | email, telephone, les_deux, ne_sait_pas, autre | E-mail, Téléphone, E-mail et téléphone, Ne sait pas, Autre |
| Fin | fin_conversation | validee, cloturee, abandonnee, autre | Validée, Clôturée, Abandonnée, Autre |
| Niveau | niveau | 1, 2, 3, autre | Niveau 1, Niveau 2, Niveau 3, Autre |
| Maturité | analyse_maturite | decouverte, reflexion, pret_a_avancer, autre | Découverte, Réflexion, Prêt à avancer, Autre |
| Format | suivi_format | banniere, email, reseaux, bouche_a_oreille, autre | Bannière, E-mail, Réseaux, Bouche à oreille, Autre |

Une valeur fermée inconnue est stockée `autre`. Le texte reçu, HTML retiré, est dans `valeurs_brutes` et affiché « Valeur reçue ».

## Migration

`2026_10_07_141500_create_monprojet_tables.php`

- `monprojet_fiches` : champs du contrat, `message_graph_id` unique, `enveloppe_id` unique, `recu_le`, `emis_le`, `statut_traitement`, `note_interne`, `conserver_jusqu_au`, `simulation`, timestamps, soft delete.
- `monprojet_transcriptions` : `fiche_id`, `messages` JSON. Clé `mp_tr_fiche_fk`.
- `monprojet_quarantaine` : identifiant Graph unique, enveloppe nullable unique, reçu le, expéditeur, sujet, motif.
- `monprojet_fiche_historiques` : fiche, utilisateur, champ, ancienne, nouvelle. Clés `mp_hist_fiche_fk`, `mp_hist_user_fk`.

`down()` supprime les quatre tables.

## Configuration à poser (Robin)

Noms seulement. Ne pas committer les valeurs.

- `MONPROJET_BOITE` — boîte lue (la cible prévue est projet@hectare.fr).
- `MONPROJET_SIGNATURE_CLE` — secret partagé avec la page publique, le même des deux côtés.
- Application Entra déjà utilisée par `AZURE_CLIENT_ID` : droit d’application **Mail.Read** (lecture seule), consentement administrateur. Pas de Mail.ReadWrite à ajouter pour ce lot.
- Pas de tâche OVH pour la relève. Microsoft n’est pas joignable en ligne de commande sur cet hébergement. Le lanceur `monprojet_relever.php`, s’il est encore sur le serveur, est obsolète : ne pas le planifier. La relève part du site (#1858).

## DÉPLOIEMENT

```
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.

## TESTS

1. `php artisan monprojet:simuler` → fiche simulation, liste, fiche, conversation en bulles.
2. `php artisan monprojet:simuler --invalide` → quarantaine, aucune fiche.
3. `php artisan monprojet:simuler --rejeu` → « Identifiant déjà vu », aucune fiche.
4. E-mail à la main vers la boîte, sans pièce `monprojet.json` → quarantaine au passage suivant.
5. Synchroniser maintenant affiche le résultat. Un second clic dans la minute est refusé.
6. Compte hors des six groupes → 403.

Unitaire, sans base : `tests/Unit/MonprojetSignatureTest.php` — 5 tests, OK. MySQL local arrêté pendant le lot : la migration n’a pas été rejouée ici.

## LEÇON

Le garde-fou SIWI n’est pas un `PHP_SAPI` dans `getAppAccessToken`. Le lever pour cette commande, c’est de ne pas passer par `SiwiDocumentDepotService`. Ne pas y ajouter d’exception « monprojet ».

L’identifiant d’enveloppe et l’identifiant Graph sont deux anti-doublons. Le rejeu de test doit changer le second et garder le premier.

Ne pas brancher cet écran sur une permission de lecture : le joker ENVOL le rendrait visible.
