# PHASE 0 — Cartographie module Développement Foncier & Montage

**SUIVI #1071** · lecture seule · aucun schéma proposé · date : 2026-07-30

**Règle de fiabilité** : chaque affirmation porte `[CODE]` (repo) ou `[LOCAL]` (base locale).  
La base locale Cursor (`127.0.0.1:3306 / erp_immo`) **refuse la connexion** au moment du diagnostic → **aucun chiffre / colonne / index n’est affirmé en [LOCAL]**. Toute affirmation colonne/index/compte figure dans `tools/diag/foncier_phase0_prod.php` à exécuter en PROD.

Sortie brute connexion locale :

```
DB default: mysql
ERREUR CONNEXION: SQLSTATE[HY000] [2002] Aucune connexion n’a pu être établie car l’ordinateur cible l’a expressément refusée (Connection: mysql, Host: 127.0.0.1, Port: 3306, Database: erp_immo, SQL: SELECT VERSION() as v)
```

---

## BLOC 1 — COLLISION DE NOMS

Commandes utilisées (sorties brutes) :

```
rg -n "class (Parcelle|Proprietaire|Promesse|Scenario|Destination|Jalon|TypeOperation|Tranche|Secteur|Commune|Offre[^C]|Acte|Montage|Financement|Valorisation|ChargePortage|Affectation|Faisabilite|DossierFoncier|Foncier)\b" app --glob "*.php"
(aucune ligne)

rg -n "Schema::create\('(parcelles|proprietaires|promesses|scenarios|destinations|jalons|tranches|secteurs|communes|actes|montages|financements|valorisations|charges_portage|affectations|faisabilites|dossiers_fonciers|fonciers)'" database/migrations --glob "*.php"
(aucune ligne)

rg -n "class (LigneFinancement|ProgrammeEtape|OffreCommerciale|AppelOffre)\b" app/Models --glob "*.php"
→ LigneFinancement, ProgrammeEtape, OffreCommerciale, AppelOffre présents
```

| Terme | Trouvé ? | Fichier:ligne | Nature | Verdict collision |
|-------|----------|---------------|--------|-------------------|
| Parcelle | NON structure | `resources/js/constants/demandeJuridiqueTypes.js:60` (« Parties et parcelles ») | libellé UI | **libre** |
| Proprietaire | NON classe/table | `app/Models/Etiquette.php:123` relation générique `proprietaire()` | méthode | **homonymie sans risque** |
| Promesse | NON entité | `app/Support/EtapesModelesEnvolCatalog.php:44` code jalon ; `LegacyEtapesModelesCatalog.php:28` `PROMESSE` | constante catalogue chronologie | **homonymie sans risque** (déjà un jalon programme) |
| Scenario | OUI colonne | `database/migrations/2026_05_11_160000_create_alertes_table.php:19` + unique L27 ; constantes `SCENARIO_*` dans `app/Services/AlerteService.php:35-85` | colonne `alertes.scenario` + constantes | **bloquante** si modèle/table `Scenario` sans préfixe — **homonymie forte** sur le mot |
| Destination | OUI concept compta | voir sous-section dédiée | sélecteur facture (pas d’enum PHP) | **bloquante** (renommage CDC → tranche justifié) |
| Jalon | NON structure | `resources/js/Pages/Admin/Enseignes/Chronologie/Index.vue:384,491` | libellé admin | **libre** structurellement |
| TypeOperation | NON | — | — | **libre** |
| Tranche | NON | — | — | **libre** (candidat pour remplacer Destination foncière) |
| Secteur | NON entité | `app/Models/LocationCandidature.php:105` `MOTIF_CHANGEMENT_SECTEUR` | constante RH | **homonymie sans risque** |
| Commune | OUI colonne | `database/migrations/2026_04_23_100000_add_fields_to_programmes_table.php:13` ; fillable `Programme.php:32` ; aussi contentieux/précontentieux | colonne scalaire | **homonymie sans risque** (pas de modèle Commune) |
| Offre | OUI familles | `AppelOffre` / `appels_offres` ; `OffreCommerciale` / `offres_commerciales` ; routes `web.php:1090-1095`, `1267-1336` | modèles + routes | **bloquante** pour un modèle nu `Offre` |
| Acte | OUI statuts | `LotCommercial.php:22` ; `Reservation.php:16,67-80` ; route `acter` `web.php:1103-1104` | constantes / dates / route | **homonymie forte** |
| Montage | OUI sémantique | `EtapesModelesEnvolCatalog.php:50-51,85-86` ; dossier SP `SharePointArborescenceStructures.php:86` | jalons + dossier SP | **homonymie sans risque** (pas de modèle) |
| Financement | OUI | `app/Models/LigneFinancement.php:9` ; table `lignes_financement` ; routes `web.php:1038-1047` | modèle + table + UI | **bloquante** pour un modèle nu `Financement` |
| Valorisation | NON | — | — | **libre** |
| ChargePortage | NON | — | — | **libre** |
| Affectation | OUI sémantique | table `facture_reaffectations` ; historique juridique | table / service | **homonymie sans risque** |
| Faisabilite | OUI code jalon | `EtapesModelesEnvolCatalog.php:36-40` `ETUDE_FAISABILITE` | catalogue | **homonymie sans risque** |
| DossierFoncier | NON | — | — | **libre** |
| Foncier | OUI phase | enum `phase=foncier` migration chronologie L15-20 ; catalogue ; dossier SP `FONCIER` | phase / dossier | **homonymie sans risque** comme module ; ambigu comme modèle unique |

### Destination — localisation précise [CODE]

Pas d’enum PHP. Valeurs en dur `PROGRAMMES` | `FRAIS_GENERAUX` :

| Emplacement | Détail |
|-------------|--------|
| Front options | `resources/js/Pages/DepotFactures/DepotFactureSaisieForm.vue:1479-1482` |
| Validation serveur | `app/Http/Controllers/DepotFactureController.php:784-787` |
| Défaut comptabilisation | `app/Http/Controllers/ComptabilisationController.php:242-244` |
| OCR Claude | `app/Services/DepotFactureClaudeAnalysisService.php:153-154` |
| Usage bandeau | même Vue `:6684-6715` (boutons visibles) |

→ Justifie le renommage CDC de la « destination foncière » en **tranche**.

Tables liées trouvées (hors liste CDC pure) [CODE] :

- `offres_commerciales`, `appels_offres`, `lignes_financement`, `facture_reaffectations`

---

## BLOC 2 — TABLE `programmes`

Source colonnes : **migrations + `$fillable` [CODE]**. Confirmation physique → script prod (§4 et §7). Base locale : **indisponible**.

### Liste exhaustive attendue d’après migrations [CODE]

`id`, `libelle`, `created_at`, `updated_at`, `deleted_at`,  
`code_analytique`, `departement`, `commune`, `code_envol`, `code_analytique_compta`, `banque`, `logo_path`,  
`sharepoint_folder_url`, `sharepoint_drive_id`, `sharepoint_item_id`,  
`entite_id`, `enseigne_id`,  
`intacct_class_id`, `intacct_class_analytics_id`, `intacct_sync_par_entite`, `compte_bancaire_intacct_id`, `financial_entity_id`,  
`responsable_suivi_commercialisation_id`, `commercial_nom`, `commercial_resp_suivi`,  
`honoraires_fonctionnement_mensuel`, `honoraires_vente_par_lot`, `provision_imprevus_pct`,  
`montant_etude_fi_recettes_ttc`, `montant_etude_fi_recettes_tva_residuelle`, `montant_etude_fi_recettes_ht`,  
`date_livraison_prevue`,  
`code_societe`, `raison_sociale`, `adresse_societe`, `siret`, `rcs`, `capital_social`, `tva_intracom`, `iban`, `bic`.

Fillable miroir : `app/Models/Programme.php:20-58`.

### Réponses explicites

| Question | Réponse [CODE] |
|----------|----------------|
| Colonne type d’opération / nature / équivalent ? | **NON** |
| Colonnes phase / étape / chronologie sur `programmes` ? | **NON** (hors `date_livraison_prevue`). Chronologie externalisée dans `programme_etapes` / `etapes_modeles`. Migration `add_statut_to_programmes` **vide** (`2026_05_02_095923_…`). |
| Colonnes foncières (surface, parcelle, terrain) ? | **AUCUNE**. Seule localisation générale : `commune`, `departement`. |
| `programmes.libelle` existe ? | **OUI** — migration initiale L17 |
| `programmes.nom` existe ? | **NON** — absent migrations / fillable. Alias payload → libelle : `Programme.php:420-423` |

`[LOCAL]` colonnes réelles + confirmation libelle/nom : **à reconfirmer en prod** (script §4 + §7).

---

## BLOC 3 — MÉCANIQUE DE JALONS EXISTANTE

### Constat [CODE]

**Aucun modèle `ProgrammeFrise`, aucune table `programme_frises`.**  
`ProgrammeFrise` = composant Vue `resources/js/Components/ProgrammeFrise.vue` (orphelin : aucun import/rendu trouvé hors docs).

Structure réelle (#396 chronologie) :

| Couche | Fichier | Rôle |
|--------|---------|------|
| Référentiel | `app/Models/EtapeModele.php` · table `etapes_modeles` | `code`, `libelle`, `phase` enum (`foncier\|conception\|travaux\|livraison`), `ordre`, déclencheurs AF |
| Instance | `app/Models/ProgrammeEtape.php` · table `programme_etapes` | `date_prevue`, `date_reelle`, `statut`, `realise_par`, `realise_at` |
| Init | `app/Services/ProgrammeEtapesInitializer.php` | seed par programme |
| CRUD | `app/Http/Controllers/ProgrammeEtapeController.php` | édition |
| Dashboard | `ProgrammeDashboardController.php` + `Pages/Programmes/Dashboard.vue` | étape courante |
| Admin référentiel | `Pages/Admin/Enseignes/Chronologie/Index.vue` | libellé « jalon » |
| Scope enseigne | migration `2026_06_15_100000_refactor_etapes_modeles_par_enseigne.php` | `enseigne_id` sur modèles |
| Catalogue ENVOL | `app/Support/EtapesModelesEnvolCatalog.php:31-47` | déjà : faisabilité, maîtrise foncière/promesse, achat foncier |

Migration structure dates [CODE] `2026_05_15_100000_create_programme_chronologie_tables.php:30-47` :

- `date_prevue` / `date_reelle`
- unique `(programme_id, etape_modele_id)`
- FK `programme_id` uniquement

### Verdict réutilisabilité

**Réutilisable** pour des jalons de haut niveau **rattachés à un programme** (date prévisionnelle + effective + référentiel scopé enseigne : déjà là).

**Non réutilisable tel quel** pour des jalons par parcelle / propriétaire / promesse / scénario / dossier foncier pré-programme : `programme_etapes` n’a que `programme_id` + `etape_modele_id`. Mélanger chronologie programme globale et chronologie dossier foncier détaillée créerait une collision sémantique (phase `foncier` déjà dans le référentiel programme).

→ Argument code : **doubler** (référentiel + instances scopés dossier foncier) ou étendre le polymorphisme — pas une simple réutilisation de `ProgrammeEtape`.

---

## BLOC 4 — NOMENCLATURE BILAN — ZONE COMPTA SENSIBLE

### `rubriques_bilan` [CODE]

Migration create `2026_05_04_133836_…` + scope #920 `2026_07_23_120000_scope_nomenclature_bilan_by_enseigne.php` :

- Colonnes attendues : `id`, `enseigne_id` **NOT NULL**, `code`, `libelle`, `ordre`, `actif`, timestamps
- Unique composite `(enseigne_id, code)` nommé `rubriques_bilan_ens_code_uq` (L59)
- FK `rubriques_bilan_ens_fk` → enseignes, restrictOnDelete

**Confirmation #920** : **CONFIRMÉE dans le code migration**. État physique prod → script §5.

### `postes_budgetaires_types` [CODE]

Colonnes (cumul migrations) : `id`, `enseigne_id` NOT NULL, `rubrique` (legacy nullable), `rubrique_bilan_id`, `libelle`, `code_interne`, `compte_comptable_id`, `intacct_class_id`, flags `est_*` / `applicable_*`, `ordre_affichage`, `actif`, timestamps.  
Unique `(enseigne_id, code_interne)` = `pbt_ens_code_interne_uq` ; index `pbt_enseigne_idx`.

### Comptes de lignes [LOCAL]

**INACCESSIBLE** (connexion refusée). À reconfirmer prod script §6.

### Fichiers PHP interrogant ces tables [CODE] — sortie brute `rg -l`

```
app\Helpers\MarcheDocumentsHelper.php
app\Http\Controllers\Admin\NomenclatureController.php
app\Http\Controllers\ComptabilisationController.php
app\Http\Controllers\Concerns\ProvidesPosteBudgetaireTypeOptions.php
app\Http\Controllers\DepotFactureController.php
app\Http\Controllers\MarcheController.php
app\Models\DepotFacture.php
app\Models\Marche.php
app\Models\PosteBudgetaire.php
app\Models\PosteBudgetaireType.php
app\Models\ProgrammeBudget.php
app\Models\RubriqueBilan.php
app\Services\BilanFinancierService.php
app\Services\CompteProrataService.php
app\Services\ProgrammeBudgetAncrageHistoriqueBackfillService.php
app\Services\ProgrammeBudgetService.php
app\Services\SharePointService.php
app\Support\EnseignesSetup.php
app\Support\MarchePosteBilanLibelle.php
app\Support\ProgrammeChargeAccountResolver.php
```

**20 fichiers PHP** (+ modèles).

### Fichiers Vue [CODE]

```
resources/js\Pages\DepotFactures\DepotFactureSaisieForm.vue
resources/js\Pages\DepotFactures\Index.vue
resources/js\Pages\DepotFactures\Show.vue
resources/js\Pages\Factures\Comptabilisation\Index.vue
resources/js\Pages\Marches\Show.vue
resources/js\utils\posteBilanSelect.js
```

Aussi (grep élargi) : `Admin/Nomenclature.vue`, `Marches/Create.vue`, `Marches/Index.vue`, `Factures/Show.vue`, widgets retenues.

### Services consommateurs [CODE]

| Service / contrôleur | Usage |
|----------------------|-------|
| `BilanFinancierService` | init/sync postes types ; charge rubriques par enseigne |
| `ComptabilisationController` | résolution poste + compte GL |
| `DepotFactureController` | options postes formulaire |
| `MarcheController` | sélection / validation postes |
| `ProgrammeBudgetService` / ancrage historique | budgets |
| `CompteProrataService` | prorata |
| `NomenclatureController` | admin |
| `SharePointService` | requête DB directe postes (~L2599) |

### Chiffrage risque passage clé `(enseigne_id, type_operation, code)`

**Pas de migration proposée.** Risque :

1. **Unicité #920** : toute requête / `firstOrCreate` / seed qui suppose `(enseigne_id, code)` unique casse ou duplique.
2. **20+ PHP + 6–10 Vue** à revoir pour filtrer/afficher par type d’opération.
3. **Points de rupture** : `BilanFinancierService` sync nomenclature ; `EnseignesSetup` seed rubriques ; admin `NomenclatureController` ; `ProvidesPosteBudgetaireTypeOptions` ; jointures bilan / marchés / factures ; exports SharePoint libellés poste.
4. Volume lignes : **inconnu localement** → prod script §6. Même à faible volume, le blast radius applicatif est **élevé** (cœur compta).

Verdict : **risque compta ÉLEVÉ** — décision CDC Phase 1, pas Phase 0.

---

## BLOC 5 — INFRA ALERTES / TÂCHES / NOTIFICATIONS

### Alerte + AlerteService [CODE]

- Modèle `app/Models/Alerte.php` — morph `entity_type/entity_id`, `module`, `scenario`, `user_id` | `groupe_cible`
- Table : migration `2026_05_11_160000_create_alertes_table.php` — unique `(entity_type, entity_id, scenario)`
- `AlerteService` : `GENERATION_ACTIVE = false` (anciennes familles neutres) ; méthodes métier explicites actives (juridique, signalements, mentions, aktor…)
- **Accueille famille foncier sans modif structurelle** : OUI (`module='foncier'` + morph + nouvelle méthode métier)
- Point d’entrée : nouvelle méthode du type `AlerteService::notifier…Foncier(...)` s’appuyant sur `Alerte::updateOrCreate([...])` (ex. L104-120)
- Canal cloche : **web/PHP-FPM** via contrôleurs → `AlerteController` ; front poll `/alertes/count` (`AppShell.vue:253-261`)

### TaskType [CODE]

- `app/Models/TaskType.php` + `tasks` polymorphes `taskable_*`
- Accueille famille foncier : **OUI** (nouveau `TaskType` + `taskable` foncier)
- Entrée : `TaskType::updateOrCreate(...)` (pattern seed migrations)

### taches_suivi [CODE]

- Modèle `TacheSuivi` ; création `TacheSuiviService::creer(User $createur, array $data): TacheSuivi`
- **Pas polymorphe** — lien métier via URL / étiquette / projet
- Accueille sans modif structurelle : **OUI** si pas de FK stricte ; sinon évolution

### TeamsNotificationService::notifierUtilisateur [CODE]

```
app/Services/TeamsNotificationService.php:12
public function notifierUtilisateur(string $destinataireEmail, string $titre, string $message): bool
```

HTTPS synchrone webhook. Accueille foncier : **OUI**.

| Canal | Déclenchement |
|-------|---------------|
| Cloche ERP (Alerte) | **PHP-FPM** (contrôleurs) |
| Teams mentions / assignations / juridique | **PHP-FPM** |
| Digest tâches | Artisan `taches:digest` **peut** HTTPS en CLI ; en prod déclenché via middleware web `DeclencheDigestQuotidien` (**PHP-FPM**) |
| Synchro Intacct fournisseurs (hors alerte) | Scheduler CLI 02:00 — HTTPS **bloqué OVH CLI** (dette connue) |

⚠️ Alerte foncière déclenchée par **cron CLI** → Teams/Graph **ne partira JAMAIS**. Passer par web/PHP-FPM ou job dispatché depuis requête web.

---

## BLOC 6 — `entite_commentaires` (#564/#567)

### Structure [CODE] — migration `2026_07_06_150000_create_entite_commentaires_tables.php`

`entite_commentaires` : `id`, `commentable_type`, `commentable_id`, `user_id`, `body`, timestamps ; index `ec_commentable_idx`.  
`entite_commentaire_mentions` : `entite_commentaire_id`, `user_id`, `azure_id`, `display_name`.

### Modèles rattachés [CODE]

Trait `HasEntiteCommentaires` sur : `DemandeJuridique`, `ContentieuxJuridique`, `PrecontentieuxJuridique`, `NonConformiteJuridique`.

### @mention [CODE]

`EntiteCommentaireService::ajouter(EntiteCommentaireContext $context, User $auteur, string $body, array $mentionsPayload = []): EntiteCommentaire`  
→ parser, Graph user, **validation accès entité**, alerte cloche, Teams.

### Attacher une nouvelle entité — concret

1. `use HasEntiteCommentaires` sur le modèle (**pas de whitelist DB/morph**)
2. Implémenter `EntiteCommentaireContext` (accès, mentionnables, notification)
3. Contrôleur + routes lecture/écriture
4. Branche registres juridiques : whitelist contrôleur via `JuridiqueRegistreDefinition` — **pattern à ne pas oublier** si on passe par un registre générique

### Visibilité commentaires

**Aucune** colonne/ACL propre au commentaire. Hérite entièrement de `context->peutAcceder(...)`.  
Ex. demande : demandeur / suiveurs / `GS_JURIDIQUE`.

Colonnes physiques prod → script §4.

---

## BLOC 7 — HABILITATION PAR ENREGISTREMENT — POINT CRITIQUE

### Scopes globaux Eloquent [CODE]

Un seul `addGlobalScope` : `CategoriesFraisGeneraux` scope `ordered` (tri) — **pas de sécurité**.

Scopes locaux de visibilité : `Alerte::forUser`, `Task::visiblePourUtilisateur`, `TacheSuivi::visiblePourUtilisateur`, `Projet::visiblePourUtilisateur`, `BugReport::visiblePourUtilisateur`, `DepotFacture::visiblePar`, `Etiquette::visiblesPour`.

### Policies [CODE]

- `app/Policies/TacheSuiviPolicy.php`
- `app/Policies/ProjetPolicy.php`
- `app/Policies/AppelOffrePolicy.php`
- (+ `FactureSuppressionPolicy` support, pas Policy Laravel classique)

`Gate::before` admin ERP → toutes abilities (`AppServiceProvider.php:57-62`).

### Tables partage / habilitation par ligne [CODE]

Pas de table générique ACL. Pivots spécialisés : `tache_suivi_personnes`, `projet_personnes`, `demande_suiveurs`, `aide_article_groupes`.

### Visibilité tâches — 4 chemins [CODE]

Implémentation exacte `TacheSuivi::scopeVisiblePourUtilisateur` L279-322 :

1. Créateur (`createur_id`)
2. Personne associée (`tache_suivi_personnes`)
3. Programme via étiquette active (+ DG voit **toutes** tâches à étiquette programme, pas « tout ERP »)
4. Membre du projet (créateur ∪ invité)

Filtrage **couche données** : `MesTachesController` ; contrôle unitaire : `estVisiblePar` + policy.

**Pattern transposable** : OUI (scope local + pivot + policy + data-layer). **Pas une brique générique plug-and-play.**

### CRM acquéreurs SharePoint « restreint » [CODE]

Création arborescence sous `04_ACQUEREURS` (`SharePointService` ~597-680) + colonnes sur réservation.  
**Aucun** appel Graph `/permissions`, rupture d’héritage, invitation. → **pas de dossier restreint** démontré — seulement arborescence dédiée.

### AideVisibiliteService — couche 1 [CODE]

`app/Services/Aide/AideVisibiliteService.php` :

- DSI voit tout
- non publié → masqué
- sans groupe → visible tous
- sinon intersection groupes M365
- **filtrage SQL** `queryArticlesVisibles()` avant récupération (couche données)

### Verdict ACL

**Brique à construire**, en s’inspirant du pattern `TacheSuivi` (pas d’ACL générique existante répondant au CDC : créateur seul → ouverture manuelle → DG voit tout → filtre données).

---

## BLOC 8 — `fournisseurs` ET SYNCHRO INTACCT

### Colonnes [CODE] (cumul migrations + fillable `Fournisseur.php:13-41`)

`id`, `nom`, `nom_legal`, `siret`, `adresse`, `code_postal`, `ville`, `pays`, `email`, `telephone`, `representant_legal`, `contact_nom`, `contact_email`, `statut`, `ancien_id`, `iban`, `bic`, `intacct_id`, `intacct_synced_at`, `intacct_error`, `dirty_sage`, `champs_modifies_sage`, `audit_reference_valeurs`, `sage_encours`, `sage_encours_synced_at`, `sage_megaentity_id`, `rib_path`, `intacct_supdoc_id`, timestamps.

| Champ type / categorie / famille ? | **NON** [CODE] |

Colonnes physiques prod → script §4.

### Code synchro Intacct [CODE]

| Fichier | Méthodes |
|---------|----------|
| `Jobs/SyncFournisseursDepuisIntacct.php` | `handle(IntacctService): array` — `updateOrCreate` mapping explicite |
| `Services/IntacctService.php` | `getFournisseurs`, `readVendorPayloadByVendorId`, `getVendorBankDetailsIndexed`, `updateFournisseur`, SUPDOC RIB… |
| `Support/FournisseurSyncWriteGuard.php` | colonnes surveillées |
| `Support/FournisseurAuditResetFromSageService.php` | reset mapping |
| `Support/FournisseurAuditSagePushService.php` | push ERP→Sage |
| `Support/FournisseurAuditFieldValidator.php` | `EDITABLE_FIELDS` |
| `Support/FournisseurAuditDirtyTracker.php` | dirty |
| `Support/IntacctVendorUpdateXmlBuilder.php` | XML |
| `Services/FournisseurRibSyncService.php` | RIB |
| `Admin/IntacctController.php` | synchro web |
| `Admin/FournisseurAuditController.php` | audit |
| `routes/console.php:33-36` | schedule CLI 02:00 |

### Verdict colonne type/categorie

**OUI, ajout local possible à risque Intacct faible** : sync descendante construit une **liste explicite** d’attributs (pas de replace global).  
À ne **pas** ajouter dans : mapping sync, dirty tracker, `EDITABLE_FIELDS`, XML builder, reset Sage.  
À ajouter : migration + `$fillable` + UI filtres. Nom préférable `categorie` / `famille_fournisseur` plutôt que `type`.

---

## BLOC 9 — PRESCRIPTEURS / APPORTEURS

Sortie brute :

```
rg -n "prescripteur_id|taux_commission_pct" app database resources/js --glob "*.{php,vue,js}"
(aucune occurrence hors script temporaire local non commité)
```

| Élément | État [CODE] |
|---------|-------------|
| `lots.prescripteur_id` | **ABSENT** |
| `lots.taux_commission_pct` | **ABSENT** |
| Modèle `Prescripteur` | **ABSENT** |
| Table `lots` | socle `id` + `prestation_id` uniquement |
| Embryon | `Contact::PROFIL_PRESCRIPTEUR = 'prescripteur'` (annuaire) ; onglet Commercialisation `prescripteurs` **disabled** (« à venir ») ; `LotCommercial.commission_ttc` sans bénéficiaire |

Comptes NOT NULL [LOCAL] : N/A (colonne absente + DB locale down). Script prod confirmera absence.

### Verdict

**Rien à réutiliser** pour un apporteur foncier structuré. Partir du circuit **`fournisseurs` + dépôt de facture** (fournisseur_id obligatoire à validation). Contact annuaire = identité optionnelle, pas substitut fournisseur.

---

## BLOC 10 — FINANCEMENT / EMPRUNT CÔTÉ PROGRAMME

**OUI — module existant** [CODE] :

- Table / modèle `lignes_financement` / `LigneFinancement`
- Types : fonds propres, refinancement FP, **crédit terrain**, crédit promotion, crédit relais, compte courant
- Champs : banque, montants, taux, garanties (caution / assurance / hypothèque), dates, encours, tirages…
- Routes `programmes/{programme}/financement` ; Vue `Pages/Programmes/Financement/Index.vue`
- Relation `Programme::lignesFinancement()`

**Pas** de modèle `Emprunt` dédié ; **pas** de table GFA métier (seulement jalon legacy catalogue `GFA`).

→ Point CDC « à trancher » : **déjà tranché côté programme** via `LigneFinancement`. Reste à trancher le lien **dossier foncier pré-programme** ↔ lignes (aujourd’hui FK `programme_id` obligatoire).

---

## BLOC 11 — ENSEIGNES / `modules_actifs` (#1061)

### Stockage [CODE]

- Colonne JSON `enseignes.modules_actifs` (cast `array`) — objet clé → booléen
- Lecture : `Enseigne::aModule(string $key): bool` (`Enseigne.php:62-70`)
- Délégation : `Programme::enseigneAModule(string $key): bool`

### Déclaration d’un module [CODE]

1. Constante dans `EnseignesSetup` (ex. `MODULE_GRILLE_LOTISSEMENT`)
2. Ajout dans `MODULES_ENVOL` / `modulesHectare()` / `modulesGemme()`
3. Libellé dans `moduleLabels()`
4. Migration merge via `mergeModulesDefaults()` (n’écrase pas l’existant)
5. Admin toggle : `Admin\EnseignesController::updateModule` + `Pages/Admin/Enseignes/Index.vue`
6. Front métier : booléen passé par le contrôleur (jamais lecture brute JSON côté Vue)

⚠️ **Jamais** `code === 'HECTARE'` en dur pour activer le foncier. Les seeds `defaultModules*PourCode` utilisent encore le code enseigne — **réservé merge migration**, pas runtime métier.

Clé proposée (constat, pas conception schéma) : brancher une clé du type `developpement_foncier` via le même mécanisme.

---

## BLOC 12 — DOCUMENTS SHAREPOINT

### Couplage [CODE]

```
SharePointService::creerArborescenceProgramme(Programme $programme): bool
  → L373 app/Services/SharePointService.php
```

Exige `Programme` + enseigne + `departement`/`commune`/`code_envol`/`libelle` ; persiste `sharepoint_*` **sur programmes**.  
`GedController` : **toutes** signatures typées `Programme $programme` ; journal `ged_actions_log.programme_id`.

### Généralisable ?

Primitives Graph (driveId/itemId) : **oui** (`createFolderInParentItem`, upload, move, delete…).  
Cas métier GED + création auto : **non** — fortement typé Programme.

Pour un dossier foncier **avant** programme : stocker ses propres `sharepoint_*`, extraire création générique, généraliser GED/journal — **sans** créer un faux programme.

### Compte `Log::warning` [CODE]

Sortie brute :

```
rg -c "Log::warning" app/Services/SharePointService.php
76
```

Dette connue #1065 confirmée : **76** warnings (pas ~70). **Non corrigés** dans ce lot.

---

## Synthèse des sources pour le script prod

Toute affirmation colonne / index / compte / spatial → `tools/diag/foncier_phase0_prod.php` (sections 1–7).
