---
suivi: 1186
date: 2026-08-04
sujet: Diagnostic dation — statut lot vs dation foncière (PHASE 1)
chantier: commercialisation
type: diagnostic
statut: poussé
hash: 2621d528
fichiers:
  - tools/diag/diag_dation_1186.php
  - docs/suivi/SUIVI_1186_diag_dation.md
---

## PROMPT ENVOYÉ

SUIVI #1186 — PHASE 1 DIAGNOSTIC — NE PAS CODER. Tâche Mélanie BENEDETTI :
statut `dation` sur lots (exclu du comptage En vente comme `annule`). Robin
suspecte le même objet que la dation foncière. Script tools/diag/ lecture seule
+ fiche 7 réponses. Aucune écriture, aucune migration.

## FICHES LUES

- `SUIVI_1089` — promesses brique 1 : **lots de dation hors périmètre**, table
  `promesse_lots_dation` **jamais créée**.
- `SUIVI_1126` — composantes prix offre : seed `dation_lots` + `odf` (CDC D9).
- `SUIVI_1141` — faisabilité promotion : typologie `dation` + `prix_dation` /
  `tva_dation` 20 %.
- `SUIVI_1154` — pipeline statut lot, ENUM, remises ; LEÇON #1171 : annulation
  résa ≠ statut lot `annule`.
- WIP PROJECT (Q13b lots ODF bilan — absent de `docs/` commités).

Approche changée : on ne part **pas** de « créer un 6ᵉ statut » ; on cartographie
les surfaces `dation` déjà présentes (`mode_vente`, composante, typologie,
type promesse) avant toute implémentation.

---

## RÉPONSES AUX 7 QUESTIONS

### 1. La dation foncière — ce qui existe vraiment

| Surface | Emplacement | Lien `lots_commerciaux` | Volume |
|---------|-------------|-------------------------|--------|
| Table `promesse_lots_dation` | **ABSENTE** (planifiée #1089, jamais livrée) | — | 0 |
| `promesses.type = 'dation'` | `FoncierReferentiels::PROMESSE_TYPE_DATION` | aucun | à mesurer (script) |
| `types_composantes_prix.code = 'dation_lots'` | seed migration #1126 | aucun (via offre) | 1 type référentiel |
| `offre_composantes_prix` | FK → offre + type | **aucun** | à mesurer |
| `scenario_lots.type_lot = terrain_a_batir_dation` | referential lotissement | aucun | à mesurer |
| Faisabilité promotion #1141 | typologie `dation` + `prix_dation` + TVA 20 % | aucun (PHP pur) | N/A |
| `lots_commerciaux.mode_vente = 'dation'` | #914, const `MODE_VENTE` | **colonne sur le lot** | à mesurer |

**Grep « dation » (hors faux positifs validation/consolidation)** :
- `app/Support/Foncier/Faisabilite/*` (typologie + prix)
- `app/Support/Foncier/FoncierReferentiels.php` (promesse + type lot scénario)
- `app/Models/LotCommercial.php` (`MODE_VENTE['dation']`)
- `resources/js/Components/Commercialisation/LotsGrillePanel.vue` (option Mode de vente)
- `resources/js/Pages/Admin/TypesComposantesPrix.vue` + Admin Index
- migration `2026_07_31_140100_create_types_composantes_prix_table.php`

**FK vers `lots_commerciaux` depuis une table « dation »** : **aucune**.
La seule présence commerciale est le **tag** `mode_vente='dation'` sur le lot
lui-même — pas un rattachement à une promesse / offre.

### 2. Même objet ? — question centrale (faits + question Robin)

**Faits :**
- Métier Mélanie : « sortie du stock » / « acquisition par dation en paiement »
  → un lot quitte le stock vendable autrement que par vente classique.
- Métier foncier : engagement de céder des lots en paiement du terrain
  (composante d’offre `dation_lots`, type promesse `dation`, typologie
  faisabilité avec prix terrain séparé).
- Ces deux formulations décrivent **le même événement économique** vu de
  deux côtés (engagement foncier ↔ sortie stock commercial).
- **Mais** côté commercial, `dation` existe **déjà** comme `mode_vente`, **pas**
  comme `statut`. Et `mode_vente` n’exclut **pas** du comptage En vente
  aujourd’hui (seuls `statut=annule` / filtres `whereIn` le font).
- Le lien « lot commercial ↔ promesse qui l’engage » **n’existe pas**
  (`promesse_lots_dation` absente).

**Question à Robin (ne pas trancher seul) :**
1. La demande Mélanie = **même** dation que l’offre/promesse foncière, ou un
   libellé stock distinct (ex. sortie pour autre motif) ?
2. Si même objet : faut-il **attendre** la table de rattachement promesse↔lots
   avant d’ajouter un statut, ou suffire d’exclure du stock via
   `mode_vente=dation` déjà en place ?
3. Un lot `mode_vente=dation` encore `en_vente` aujourd’hui — est-ce une
   saisie partielle à corriger, ou un état voulu (« engagé mais pas sorti ») ?

### 3. Enum des statuts — endroits qui testent + ceux qui traitent `annule` à part

**ENUM réel** : `en_stock|en_vente|reserve|acte|annule`, NOT NULL, défaut
`en_stock` (migration `2026_05_12_180000_…`). Aucun cast Eloquent. Aucun scope.

**Transitions contrôlées** (pas d’édition libre du statut) :
`peutMettreEnVente` / `peutRemettreEnStock` / `peutReserver` / `peutActer` /
`peutAnnuler`. Whitelist grille **sans** `statut`. Création limitée à
`en_stock|en_vente` (#1173).

**Écriture de `ANNULE`** : quasi orpheline — **ImportPegao** (`bloque`→`annule`).
L’action métier « annuler réservation » remet le lot en **`en_stock`**, pas
`annule`.

#### Endroits qui excluent / traitent `annule` à part (candidats `dation`)

| # | Fichier | Comportement actuel | Si `dation` « comme annule » |
|---|---------|---------------------|-----------------------------|
| A1 | `ProgrammeDashboardController` | dénominateur taux = hors `annule` | **évident** : exclure aussi |
| A2 | `CommercialisationController::calculerStockSynthese` | `lotsActifs` hors `annule` | **évident** |
| A3 | `ProgrammeResumeCommercialBuilder` CA potentiel | `statut != ANNULE` | **évident** pour stock restant ; **décision** pour CA |
| A4 | `LotsGrillePanel.vue` / `SynthesePanel.vue` | `stockTotal` hors `annule` | **évident** |
| A5 | `BilanFinancierService` | `restants` exclut annule ; `potentiel` = **tous** ; `nb_stock` **inclut** annule ; taux = (R+A)/nbTotal **avec** annules | **décision** (déjà incohérent vs dashboard) |
| A6 | `RemisesAccordeesCalculator` + `useRemisesAccordees.js` | seulement R+A | **implicite** OK si dation ∉ R/A |
| A7 | `valeurCommercialeTtc` | hors R/A/stock/vente → 0 | **évident** si dation hors pipeline |
| A8 | `WidgetDataService` synthèse | compte R/A/stock+vente | **évident** : ne pas compter dation en vente |
| A9 | Filtres UI (`STATUT_*_OPTIONS`) | 5 valeurs | **évident** : ajouter libellé si statut créé |
| A10 | `LotCommercial::statutLabel` + historique | 5 labels | **évident** |
| A11 | Machine `peut*` | pas de transition vers `annule` métier | **décision** : quelles transitions vers/depuis `dation` ? |
| A12 | Import Pegao | seul writer `ANNULE` | **décision** : mapping ? |

#### Endroits qui filtrent par statut sans « comme annule » (impact si nouveau statut)

| # | Fichier | Comportement | Note |
|---|---------|--------------|------|
| B1 | `ReservationController` / `CommercialController` / sync | → `reserve` / `acte` / remise `en_stock` | dation ne doit pas être réservable (**évident**) |
| B2 | `OffreCommercialeController` | offre si `EN_VENTE` | dation hors (**évident**) |
| B3 | `AcquereurController` lots rattachables | stock\|vente | dation hors (**évident**) |
| B4 | `TMAController` | R\|A | implicite |
| B5 | `GenerationAppelFondService` | lot `ACTE` | implicite |
| B6 | `ReservationAcquereurUpdateService` | refuse hors stock/vente | dation hors (**évident**) |

**Exports grille lots commerciaux** : aucun trouvé (AO = autre table).

### 4. Impact sur les calculs financiers (instruire, ne pas décider)

| Calcul | Formule actuelle | Lot en dation — options | Risque |
|--------|------------------|-------------------------|--------|
| Remises accordées | Σ max(0, grille−vente) sur **R\|A** (+ frais acte/résa) ; réf. 107 304,48 € | Si dation ∉ R/A → **hors KPI** (souhaitable : pas de « remise » fictive). Si on force un `prix_vente` << grille en statut dation mal classé R/A → **fuite de marge** | Ne **jamais** laisser un lot dation en `reserve`/`acte` « classique » |
| CA réservé+acté résumé #1177 | Σ pivot TTC résas en_cours/acte | Dation **n’est pas** une réservation VEFA → hors CA (**proposé**). Neutralisation / coût acquisition = **Q13b**, hors scope Mélanie | Séparer affichage stock et CA |
| Taux commercialisation dashboard | (R+A) / hors `annule` ; Tritons ~29 % | Dénominateur : exclure dation comme annule (**proposé Mélanie**). Numérateur : dation ≠ R/A | Sinon taux faussé à la baisse si dation reste en `en_vente` |
| CA potentiel résumé | Σ grille hors `annule` | Exclure dation du potentiel vendable (**proposé**) | Aligné « sortie de stock » |
| Bilan recettes | potentiel = **tous** lots ; restants = stock+vente ; taux / nbTotal | **Décision métier** — déjà divergent du dashboard ; ne pas préempter Q13b | |

### 5. Verrou Q13b — ne pas préempter

**Q13b** (WIP PROJECT, CDC v21) : *lots ODF au bilan — CA prévisionnel,
neutralisés en trésorerie, ou exclus du CA / coût d’acquisition ?*
À arbitrer avec la comptable. **Bloque Phase 3 foncier** + cession interne.

CDC D9 : ODF et `dation_lots` « considérés identiques » côté offre — Robin
tranche à l’usage (commentaire migration + UI Admin).

**Implication d’un statut `dation` pour Q13b** : le statut stock **peut**
exister sans trancher le traitement bilan. Mais dès qu’un lot dation entre
dans `BilanFinancierService` (potentiel / prévisionnel / marge), on **touche**
Q13b. → Lot d’implémentation stock : **neutre bilan** (exclure des KPI stock /
taux dashboard uniquement ; **ne pas** changer les formules bilan tant que
Q13b ouvert).

### 6. Migration ENUM — risque

- ALTER ENUM MySQL sur ~182 lignes : **faible volume**, mais **irréversible
  testable** (pas de CREATE DATABASE OVH ; leçon #1099).
- Dépendances liste exacte : const PHP + miroirs Vue + `Rule::in` création +
  `SHOW COLUMNS` — **pas** de vue SQL / index fonctionnel trouvé sur les
  valeurs.
- Précédent `contacts.type_financement` : ENUM → **VARCHAR(32)** + validation
  applicative (`2026_05_13_160000`).

**Moins risqué ici** : si on ajoute vraiment une 6ᵉ valeur → **ALTER ENUM
additif** (`ADD VALUE` via `MODIFY … ENUM(...,'dation')`) est le chemin le
plus simple *et* cohérent avec l’existant — **à condition** que le down soit
documenté (remettre `dation` → `en_stock` avant shrink). Passer toute la
colonne en varchar = plus large blast radius (tous les lecteurs SQL).

**Mais** : le diagnostic recommande de **ne pas migrer** tant que Robin n’a
pas tranché §2 — `mode_vente=dation` existe déjà **sans** ALTER.

### 7. Réversibilité

- Statut **pas libre** : machine `peut*`. Aucune transition vers `annule`
  métier (sauf import).
- Retour en vente aujourd’hui : `en_stock` ↔ `en_vente` seulement via
  endpoints dédiés.
- Pour un futur `dation` : **il faudra définir** explicitement
  `peutSortirEnDation` / `peutRevenirDeDation` (qui, depuis quels statuts,
  avec/sans verrou AF). Sinon erreur de saisie = lot coincé hors stock.

`mode_vente` est **éditable librement** via la grille à tout statut → un
opérateur peut déjà taguer/détaguer `dation` sans contrôle de transition.

---

## RECOMMANDATION TRANCHÉE (une phrase)

**Même objet métier que la dation foncière (cession de lot en paiement du
terrain), déjà tagué côté commercial via `mode_vente=dation` — ne pas créer
un 6ᵉ statut ENUM parallèle tant que Robin n’a pas tranché le lien
promesse↔lot ; pour la sortie de stock demandée par Mélanie, préférer
s’appuyer sur `mode_vente` (ou un futur rattachement `promesse_lots_dation`)
plutôt qu’une neuvième notion.**

---

## LISTE EXHAUSTIVE — endroits à traiter si implémentation

Légende : **É** = évident si dation = hors stock comme annule ;
**D** = demande décision Robin/comptable ;
**N** = neutre Q13b (ne pas toucher en lot stock).

| ID | Endroit | Action proposée | É/D/N |
|----|---------|-----------------|-------|
| 1 | `LotCommercial` const + `statutLabel` + `peut*` | Si statut : ajouter const + transitions ; si mode_vente : helpers `estHorsStockVente()` | D |
| 2 | Migration ENUM / varchar | Seulement si statut choisi | D |
| 3 | `ProgrammeDashboardController` taux | Exclure dation du dénominateur (et du numérateur « en vente ») | É |
| 4 | `CommercialisationController` stock_synthese | Exclure des `lotsActifs` / compteurs En vente | É |
| 5 | `LotsGrillePanel` / `SynthesePanel` stockTotal | Exclure | É |
| 6 | `ProgrammeResumeCommercialBuilder` CA potentiel | Exclure grille dation | É |
| 7 | Idem CA réservé+acté | Ne pas compter comme vente VEFA | É |
| 8 | `RemisesAccordeesCalculator` | Garantir hors R/A ; pas de remise sur dation | É |
| 9 | `useRemisesAccordees.js` | Miroir | É |
| 10 | `BilanFinancierService` | **Ne pas modifier** dans le lot stock | N / D (Q13b) |
| 11 | `WidgetDataService` | Exclure du stock vente | É |
| 12 | Filtres / badges Vue | Libellé + filtre | É |
| 13 | `CommercialController` / réservation / sync | Interdire réserve/acte sur lot dation | É |
| 14 | `OffreCommercialeController` / TMA / AF | Hors offre / hors AF classiques | É |
| 15 | Import Pegao | Mapping éventuel | D |
| 16 | Lien `promesse_lots_dation` | Créer table + FK si même objet confirmé | D (phase foncier) |
| 17 | Faisabilité / offre composantes | **Ne pas fusionner** dans le lot stock ; surfaces distinctes OK | N |

---

## DÉPLOIEMENT-TEST (diag)

```bash
git pull origin main
php tools/diag/diag_dation_1186.php
```

Coller la sortie volumes (mode_vente=dation, promesses, offre_composantes) dans
le fil avant PHASE 2.

## LEÇON

`promesse_lots_dation` citée dans un prompt ≠ table existante — toujours
`Schema::hasTable` / migration avant de « relier ». Un libellé métier
(`dation`) déjà posé sur **trois** surfaces (`mode_vente`, composante offre,
typologie faisabilité) : ajouter un **statut** sans arbitrage = neuvième
notion au sens #1170 / #1181.
