---
suivi: 1244
date: 2026-08-07
sujet: Diag séparation propriétaires externes / sociétés du groupe
chantier: foncier / proprietaires / detention
type: diagnostic
statut: poussé
hash: 1bda52c8
fichiers:
  - tools/diag/diag_proprietaires_societes_1244.php
  - docs/suivi/SUIVI_1244_diag_proprietaires_societes.md
---

# SUIVI #1244 — PHASE 1 DIAGNOSTIC — propriétaires vs sociétés du groupe

## PROMPT ENVOYÉ

Cartographie lecture seule avant décision de séparation (options A / B / C).
Aucun code métier, migration, Vue, rebuild, Graph. Script diag + fiche uniquement.

## FICHES LUES

- `#1092` / `#1099` — socle actes, détention, R5/R7, `entite_id` UNIQUE
- `#1111` — dossier porte les propriétaires (`dossier_foncier_proprietaire`)
- `#1120` — Patrimoine (détention `societe_groupe` + `entite_id`)
- `#1129` / `#1134` — historisation + modèle `ParcelleDetention` (2 chemins)
- `#1155` / `#1182` — division/réunion + propagation détention
- `#1194` — retrait `BelongsToMany::attach` métier sur le pivot
- `#1213` — `dossier_foncier_proprietaire` **existe** (D51 non « supprimée »)
- `#1079` — consentement retiré du formulaire (Q34 CDC)
- `#1242` — saisie multi-lignes parcelles (contexte demande immédiate)

## OBJET 1 — État réel de `proprietaires`

### Schéma (migrations + modèle)

Table créée `#1074` (`2026_07_30_140000`) puis étendue :

| Colonne | Origine | Notes |
|---|---|---|
| `id` | 140000 | PK |
| `type` string(32) | 140000 | **existe** ; index `proprietaires_type_idx` |
| `nom`, `prenom`, `raison_sociale`, `siret` | 140000 | |
| `adresse`, `cp`, `commune`, `tel`, `email` | 140000 | PII |
| `responsable_id` FK users | 140000 | chargé de développement |
| `statut`, `commentaire` | 140000 | |
| `rgpd_consent`, `rgpd_consent_at`, `rgpd_canal`, `anonymise`, `rgpd_anonymise_at` | 140000 | RGPD |
| `created_by` | 140000 | |
| `entite_id` FK entites **UNIQUE** | 170100 | R7 — nullable, `proprietaires_entite_id_uq` |
| `createur_id` | 100200 | cloisonnement #1116 |

### Champ de type — OUI, déjà en place

- Colonne : `proprietaires.type`
- Valeurs référentiel (`FoncierReferentiels::TYPES_PROPRIETAIRE`) :
  `physique`, `morale`, `indivision`, `sci`, `collectivite`, **`societe_groupe`**
- **Qui écrit `societe_groupe`** : `ActeAcquisitionService::findOrCreateProprietairePourEntite` (R7),
  à la création d’un acte d’acquisition (automatique, pas manuel utilisateur).
- Fiabilité théorique : bonne si la règle R7 est respectée ; anomalies à mesurer en prod
  (`type=societe_groupe` sans `entite_id`, `entite_id` sans `type=societe_groupe`).

→ **Option B est déjà largement amorcée** au niveau données. Ce qui manque : dériver
  attributs UI, confidentialité, RGPD et règles de saisie **depuis** ce type.

### Tableau pertinence colonnes (divergence d’attributs)

| Colonne | Ext. physique | Ext. morale | Société groupe | Remarque |
|---|---|---|---|---|
| `type` | oui | oui | oui | discriminant |
| `nom` / `prenom` | oui | non (ou secondaire) | miroir `entites.nom` | |
| `raison_sociale` / `siret` | non | oui | oui (doublon entité) | |
| `adresse` / `cp` / `commune` | oui | oui | rarement utile | |
| `tel` / `email` | oui | contact | **non pertinent** | PII |
| `responsable_id` | oui | oui | non (pas une négo) | |
| `rgpd_*` / `anonymise` | oui | personne morale ≠ RGPD personne | **absurde** | |
| `entite_id` | non | non | **obligatoire R7** | |
| `commentaire` / `statut` | oui | oui | possible | |
| `createur_id` / `created_by` | oui | oui | auto acte | |

Taux de remplissage **ventilés par nature** : sortie section 3 du script OVH
(ne pas inventer depuis local).

## OBJET 2 — Pivot `parcelle_proprietaire` (le chiffre qui décide)

### Schéma

| Colonne | Notes |
|---|---|
| `id` | PK |
| `parcelle_id`, `proprietaire_id` | FK CASCADE |
| `quote_part` | decimal |
| `date_debut_detention`, `date_fin_detention` | historisation #1092 |
| `acte_foncier_id` | SET NULL |
| `detention_courante` | générée STORED `CASE WHEN date_fin IS NULL THEN 1 ELSE NULL END` |
| UNIQUE `pp_courante_uq (parcelle_id, proprietaire_id, detention_courante)` | #1096 |

### Points de code app/ recensés (lecture ou écriture du pivot)

**Total : ~62 sites app/ touchant réellement `parcelle_proprietaire`**
(+ 1 site R7 hors pivot couplé à R5).

Dont **~22 écritures** (insert/update/delete/clôture/propagation).

Principaux clusters :

| Cluster | Fichiers | Rôle |
|---|---|---|
| R5 / R7 | `ActeAcquisitionService` | `DB::table` brut (bypass Eloquent) |
| Création / prospection | `ParcelleCreationService`, `ParcelleController`, `ProprietaireController`, `DossierFoncierController` | `ParcelleDetention::create` |
| Édition fiche | `ParcelleDetentionService` | modifier / clore / supprimer + audit |
| #1155 / #1182 | `ParcelleOperationCadastraleService`, `ParcelleDetentionPropagationService`, CLI | propager / clôturer |
| Patrimoine | `PatrimoineFoncierQuery` | filtre `societe_groupe` + `entite_id` |
| Prospection | `ParcelleProspectionQuery`, `ProspectionController` | listes / filtres |
| Lecture UI | `ParcelleController`, `DossierFoncierController`, `ActeFoncierController`, `OffreFonciereController` | sérialisation |
| Valorisation | `ParcelleValorisationService` | prix via `acte_foncier_id` pivot |
| Audit | `FieldAuditService` + trait sur `ParcelleDetention` | |

**Hors pivot (ne pas confondre)** : `dossier_foncier_proprietaire`, pivot offres,
`promesses.proprietaire_id`, `charges_portage.parcelle_id`.
**Aucun** accès Aktor / export dédié / scénario faisabilité **direct** sur ce pivot
(faisabilité lit le propriétaire de fiche, pas le pivot parcelle).

### Deux chemins d’écriture (dette #1134 / #1194)

1. `ParcelleDetention` Eloquent → audit OK
2. `DB::table` dans `ActeAcquisitionService` (R5) → **pas d’audit champ**

### Colonne générée STORED × pivot polymorphe (A/C)

Oui, MySQL 8.x accepte une UNIQUE sur
`(parcelle_id, detenteur_type, detenteur_id, detention_courante)` avec
`detention_courante` STORED identique. **Mais** cela impose de remplacer
`proprietaire_id` partout (~62 sites) + migrer les FK + adapter BelongsToMany.
Coût dominant = reprise code, pas la syntaxe SQL.

## OBJET 3 — R5 et R7

### R5 — implémentation

`ActeAcquisitionService` (~L131–163) :
1. Clôture `date_fin_detention = date_acte` sur détentions courantes des **parcelles de l’acte uniquement**
2. Insert détention société groupe + `acte_foncier_id`
3. `etat_foncier → acquise`

Si détenteur polymorphe : R5 doit écrire `detenteur_type=entite` (ou table dédiée)
au lieu de `proprietaire_id` miroir — **réécriture obligatoire** du cœur R5.

### R7 — implémentation

`findOrCreateProprietairePourEntite` : match **uniquement** `entite_id` UNIQUE ;
crée `type=societe_groupe` automatiquement à l’acte.

### Option C — que devient R7 ?

**Gain :**
- Fin du miroir `proprietaires` ↔ `entites` (contournement R7 n’a plus lieu d’être)
- Une seule source de vérité Intacct (`entites` : SIRET, enseigne, sync)
- Patrimoine peut joindre `entites` directement
- RGPD / cloisonnement ne s’appliquent plus par erreur aux sociétés du groupe

**Perte / coût :**
- Pivot polymorphe (même ordre de grandeur que A) : ~62 points à reprendre
- R5 plus complexe (deux formes de détenteur)
- Relations Eloquent `BelongsToMany` actuelles cassées → morph ou double relation
- Historique d’audit déjà écrit sur `proprietaire_id` à migrer / dual-lire

**Verdict instruire (non tranché)** : C est conceptuellement le plus juste *si* on
accepte le coût pivot ; sinon C n’apporte rien sans ce coût.

## OBJET 4 — Conséquences autres objets

| Objet | A (2 tables + morph) | B (type formalisé) | C (entites + morph) |
|---|---|---|---|
| **D51 dossier** | Filtrer : dossiers n’acceptent que externes | Idem via `type != societe_groupe` | Idem ; table `dossier_foncier_proprietaire` **existe encore** (#1213) — ne pas la « supprimer » sans lot dédié |
| **Offres R2** | N externes ; jamais société groupe | Règle UI/API sur `type` | Idem |
| **Promesses** | `proprietaire_id` reste externe | Idem + validation type | Idem |
| **Patrimoine** | Lit table/sociétés dédiée | Reste filtre `type`+`entite_id` (déjà) | Lit détention→`entites` — **plus naturel** |
| **Charges portage** | Sur `parcelle_id` — peu d’impact | Peu d’impact | Peu d’impact |
| **Prospection #1208** | Exclure sociétés groupe du mode proprio | Filtrer `type` (aujourd’hui elles **peuvent** apparaître si détention courante) | Exclure entités |
| **Cession future** | Facilite : céder = détention groupe→externe | Possible via type | Naturel (entite cède) |

## OBJET 5 — Confidentialité et RGPD

- **Patrimoine** : visibilité large `foncier.lecture` (hors cloisonnement #1116) ;
  montants filtrés `foncier.prix`. Justification code : actes publiés.
- **Propriétaires externes** : cloisonnement D62 / `HasFoncierReferentielVisibilite` +
  logs d’accès ; négociations confidentielles.
- **Aujourd’hui** : même table, **deux régimes déjà partiellement divergents**
  (Patrimoine bypass ACL ; liste propriétaires cloisonnée). Risque : une fiche
  `societe_groupe` dans l’UI propriétaires / prospection mélange les régimes ;
  l’inverse (fuite négo via patrimoine) est limité car patrimoine ne liste que
  `societe_groupe`+`entite_id`.
- **RGPD** : colonnes PII + anonymisation `FieldAuditService` ; consentement retiré
  du formulaire (#1079) — fondement intérêt légitime (Q34). Séparation (A/C) ou
  dérivation stricte depuis `type` (B) clarifie : **jamais d’anonymisation /
  consentement sur société du groupe**.

## OBJET 6 — Demande immédiate (#1242) — sans trancher

Trois traitements possibles :

1. **Grille = externes seulement** ; société groupe **uniquement via acte (R5)**.
   Protège le patrimoine. Indivision + création nom/prénom OK.
2. **Grille accepte les deux** (reprise historique sans acte). Utile si prod a déjà
   des `acquise` / détentions `societe_groupe` sans `acte_foncier_id` (chiffre script §6).
3. **Grille accepte les deux mais exige un acte** (existant ou créé dans le flux).
   Plus lourd UX ; respecte R5.

⚠️ Phrase Robin « date d’acquisition d’une de nos sociétés » = **court-circuit R5**
si on pose `societe_groupe` + `acquise` sans acte.

Mesure script : `acquise` sans `acte_parcelle` ; détentions `societe_groupe` sans
`acte_foncier_id`.

## OBJET 7 — Défaut d’affichage grille #1242 (signalé, non corrigé)

- Fichier : `resources/js/Components/Foncier/Prospection/ProspectionSaisieDialog.vue`
- Modale `max-w-6xl`, **8 colonnes** (Section, Numéro, Commune, Surface, Zonage PLU,
  Constructible, Type foncier, Lieu-dit).
- Zonage `InputText w-20` à côté de Constructible `Select w-32` → chevauchement
  d’en-têtes / champs dans une largeur contrainte.
- Proposition (hors lot) : élargir (`max-w-7xl` plein écran), ou regrouper
  zonage+constructible+type en « Urbanisme » sur 2e ligne, ou 5 colonnes visibles
  + complément fiche. L’objet 6 (propriétaires + date) **aggraverait** le problème.

## OBJET 8 — Stratégie et découpage

### Recommandation : **B d’abord**, porte ouverte vers **C**

**Raisonnement chiffré :**
- ~**62** points de code sur le pivot → coût A ou C = **élevé** (semaines, risque R5/Patrimoine)
- Le champ `type` + `entite_id` UNIQUE **existent déjà** → B = formaliser comportements
  (UI, validation, RGPD, exclusion dossiers/offres/promesses, libellé date_detention)
  à **faible risque**, sans migration de schéma pivot
- Si volumes prod = jeux d’essai (hypothèse Robin : 6 parcelles) → reprise données
  **n’est pas l’obstacle** ; l’obstacle reste le **nombre de points pivot**

### Seuil de bascule

| Vers | Seuil |
|---|---|
| **Rester B** | Moins de ~15 points métier réellement bloqués par le mélange ; Patrimoine OK ; R7 stable |
| **Basculer C** | Décision produit « une seule vérité = `entites` » **et** budget pour réécrire le pivot (~62 sites) + R5 ; ou douleurs opérationnelles du miroir R7 (doublons, sync SIRET, confusion UI) |
| **Basculer A** | Exigence juridique/RGPD de **tables physiquement séparées** que le `type` ne suffit pas à garantir (ex. droit à l’oubli automatisé impossible à cloisonner autrement) — rare si B est strict |

### Découpage progressif proposé

1. **Lot comportemental B** (pas de morph) : scopes/helpers `estSocieteGroupe()`,
   exclusion saisie dossier/offre/promesse/prospection, UI attributs selon type,
   libellés date (acquisition vs détention externe), garde « pas de société groupe
   sans acte » dans création parcelle
2. **Mesure prod** (ce script) → confirmer volumes / anomalies
3. **Si bascule C** : migration dual-read (pivot accepte encore `proprietaire_id`
   miroir **et** `entite_id` détenteur) → bascule écritures R5 → drop miroir
4. **Jamais big bang** A sans phase dual-read

### Cohabitation / réversibilité

- B : réversible (retirer les gardes) — pas de migration
- C/A : dual-write puis dual-read ; rollback = rejouer miroir R7 + restaurer
  `proprietaire_id` depuis backup pivot

### Reprise données

Script sim / `?apply=1` (leçon #1191) **après** mesure volumes OVH.
Si proprietaires/actes = aussi jeux d’essai → reprise **non bloquante** ;
sinon chiffrer lignes `societe_groupe` + détentions liées.

## Commande OVH

```
php tools/diag/diag_proprietaires_societes_1244.php
```

Coller la sortie complète dans le lot de décision PHASE 2.

## LEÇON

- Un champ `type` déjà présent ne signifie pas que les comportements divergent :
  c’est le **coût d’usage** (UI, ACL, RGPD, saisie) qui manque, pas la colonne.
- Le chiffre qui décide A/C vs B = **nombre de points d’accès au pivot**, pas le
  volume de lignes si le patrimoine est encore un jeu d’essai.
- Deux chemins d’écriture pivot (Eloquent vs `DB::table` R5) restent une dette :
  tout redesign doit les unifier.
