---
suivi: 1171
date: 2026-08-04
sujet: Diagnostic — incohérences structurelles module commercial
chantier: commercialisation
type: diagnostic
statut: poussé
hash: b213e7d1
fichiers:
  - tools/diag/diag_commercial_incoherences_1171.php
  - docs/suivi/SUIVI_1171_diag_commercial_incoherences.md
---

## PROMPT ENVOYÉ

SUIVI #1171 PHASE 1 DIAG — recherche systématique d’incohérences structurelles
(classes A–K) dans le module commercial. Aucun code métier. Script lecture
seule + fiche. HEAD annoncé ae854c5a ; HEAD réel au push = voir hash ci-dessous
(après #1170).

## SYNTHÈSE

### Fiches lues
- `SUIVI_1154` — cartographie / cardinalités ; LEÇON : ne pas ouvrir des champs
  tant que multi-lot / multi-acquéreur non tranchés.
- `SUIVI_1161` — 20 lecteurs prix / AF ; LEÇON : une vérité prix avant DDL.
- `SUIVI_1167` / `SUIVI_1168` — pivot + prix dérivé (dettes assumées exclues).

### Script
`php tools/diag/diag_commercial_incoherences_1171.php` (CWD racine, require
relatif `vendor/autoload.php`, lecture seule).

**Coller ici la sortie Q0–Q10 produite en prod** (volumes + chiffres I/B/A).

### Constats CODE (indépendants de la BDD)

#### Classe A — orphelins UI
- `date_option` : **exposé** grille + fiche → anomalie A si remplissage 0 % (déjà
  signalé #1154 à 0/59).
- Famille hors UI mais validée store : `date_figurant_ssp`, `date_ar_client`,
  `date_rdv_signature` (lu WidgetDataService), EDD, memos, revente, acompte
  legacy, `frais_inclus`, `date_limite_depot_demande`…
- `tma_montant` : UI retirée #1170 ; reste colonne + sync → plus classe A écran,
  dette #1170.

#### Classe B — notions dupliquées
| Notion | Emplacements | Sync |
|--------|--------------|------|
| TMA | `tmas` / `reservations.tma_montant` / `lots.tma_montant_ttc` | résa→lot only ; tmas autonome |
| Remises | `remises_commerciales_ttc` / `remises_ttc` | one-way résa→lot |
| Agence | `agence_fournisseur_id` / `agence_nom` | FK→nom ; legacy Index écrit nom |
| Acompte | `montant_acompte`+`acompte_encaisse` vs `acompte_demande`+`acompte_paye`+dates | aucune |
| Dépôt prêt | `date_limite_depot_pret` (UI) vs `date_limite_depot_demande` | aucune |
| Frais acte | `frais_acte_estimes_ttc`+#1166 vs `frais_inclus` JSON | aucune |
| Matrimonial | `reservation_acquereurs.*` vs `contacts.situation/regime` | fiche n’écrit plus le contact |

#### Classe C/D
- `date_reception_ssp` : en schéma, **hors fillable** Reservation → ni lecture ni écriture app.
- ~15 colonnes fillable/validées store sans aucun formulaire Vue (écrites seulement via API JSON / défauts création).

#### Classe E — miroirs
| Miroir | Verdict |
|--------|---------|
| Motifs annulation 17 codes PHP ↔ Show ↔ Grille | **aligné** (#1157 corrigé) |
| RemisesAccordees PHP ↔ JS | **aligné** (#1168) |
| ReferentielMatrimonial via props | **aligné** |
| SRU 4 statuts | **aligné** |
| `Contact::TYPES_FINANCEMENT` (8) vs selects grille/fiche (7, **sans `pret`**) | **écart** |
| `FINANCEMENT_OPTIONS` dans LotsGrillePanel | **mort** (aucune saisie type_financement à la réservation) |

#### Classe F
- `contacts.type_financement` : migré ENUM→VARCHAR(32) ; const PHP 8 valeurs ; clé longue 26 OK.
- `source_decouverte` : options Vue fermées, **pas de Rule::in** serveur.
- situation/régime : VARCHAR(64), normalisation soft, pas Rule::in strict.

#### Classe G
- Double surface : `UpdateAcquereurFicheRequest` (anti-silence) vs `validateReservationUpdate` (plus large, **pas** de rejet clés inconnues).
- Création grille : contact **sans** `type_financement` alors que store le valide sur branche new contact.
- Inline lotissement create : accepte `statut` + `prix_vente_ttc` (bypass transitions métier).

#### Classe H
- Prix : store, update JSON (**AF lock**), fiche (**AF lock**), pivot prix, CommercialController legacy (**sans AF lock**), ImportPegao.
- Statut : acter/annuler, LotCommercialController, sync, **inline create**.
- `date_acte` : whitelisted fiche PUT mais UI = modal acter seulement.

#### Classe J (familles)
- SRU : 5 champs dont `date_sru_fin_delai` auto Observer — UI partielle.
- Financement : UI = limites prêt/accord + fiche réelles ; legacy `date_limite_depot_demande` / `date_limite_accord` / `date_reelle_edition_offre` hors UI.
- Acte : `date_rdv_signature` hors UI mais consommée widgets.
- Revente / EDD / SSP / acompte legacy : **hors UI**.

#### Classe K
- Table `lots` : 3 cols, 0 ref `App\Models\Lot` dans app/ — vestige.
- `appels_fonds` (legacy Observer, UI fiche) vs `appels_de_fonds` (Intacct, 0 ligne attendu) — double stack ; **ne pas fusionner** (Sabrina/Rudy).

### Dettes connues (hors anomalies)
Dual-write FK #1167 · copie prix #1168 · AF principal #1168 · co_acquereur #1160 ·
notaire libre #1163 · SharePoint non rename #1169 · tma UI #1170.

## TABLEAU DE SYNTHÈSE PAR GRAVITÉ

| Grav. | Cl. | Objet | Constat | Risque | Correctif proposé | Effort |
|-------|-----|-------|---------|--------|-------------------|--------|
| 1 | H | Prix via `CommercialController` legacy | Chemin reserve/acte sans `assertPrixVenteModifiable` | Chiffre AF faux crédible (#1043) | Brancher le même verrou AF #1162 ou déprécier l’écran | M |
| 1 | H | Inline create lotissement `statut`/`prix_vente` | Bypass transitions / réservation | Lot « reserve » sans résa | Retirer statut/prix_vente de l’inline create | S |
| 1 | I | Couples statut résa↔lot | À chiffrer script Q7 | Stock / CA faux | Lot correctif données + garde-fou | M |
| 1 | I | Σ `appels_fonds` ≠ prix résa | À chiffrer Q7 I7 | Snapshots AF obsolètes | Décision métier AF (ne pas fusionner tables) | L |
| 2 | G | `validateReservationUpdate` sans anti-silence | Clés hors liste ignorées | Perte silencieuse | Aligner sur withValidator #1156 | S |
| 2 | F | `source_decouverte` sans Rule::in | API peut écrire hors liste | Données sales | Rule::in = options Vue | S |
| 2 | B | Acompte UI vs legacy 4 champs | Aucune sync | Divergence silencieuse | Décision : unifier ou déprécier legacy | M |
| 2 | B | `date_limite_depot_pret` vs `_demande` | Deux colonnes | Confusion / perte | Décision Robin | S |
| 3 | A | `date_option` UI + 0 % probable | Champ trompeur | Utilisateur croit enregistrer | Retirer UI **ou** câbler usage | S |
| 3 | A/J | Revente / EDD / SSP / memos hors UI | Validés store, jamais saisis | Illusion API | Retirer des whitelists store **ou** écrans dédiés | M |
| 3 | E | `pret` absent selects grille/fiche | Const PHP + Annuaire OK, commercial non | Impossible de choisir `pret` | Ajouter option **ou** retirer de const | S |
| 3 | E | `FINANCEMENT_OPTIONS` mort dans grille | Pas de saisie financement à la réservation | Financement manquant à la création | Ajouter champ **ou** supprimer const | S |
| 3 | B | `frais_inclus` JSON vs frais_acte | KPI lit seulement #1166 | Confusion | Documenter / retirer JSON UI future | S |
| 3 | B | TMA triple | #1170 UI retirée ; sync miroir reste | Divergence si saisie API | Mesurer lots ; lot ultérieur miroir | S |
| 3 | K | Fiche AF n’affiche que `appels_fonds` | Intacct invisible sur fiche | Utilisateur croit 0 AF | Afficher les deux **ou** décision stack | M |
| 4 | C | `date_reception_ssp` hors modèle | Colonne morte | Dette cosmétique | DROP ultérieur (OVH prudent) | S |
| 4 | K | Table `lots` 0 ligne | Vestige | Bruit schéma | DROP ultérieur | S |
| 4 | D | `Commercial/Index.vue` legacy | Affiche tma/remises lot | Confusion 2 écrans | Déprécier Index | M |

## DÉCOUPAGE EN LOTS PROPOSÉ (par fichier)

### Série obligatoire — `LotsGrillePanel.vue` + `Acquereur/Show.vue` + `manifest`
1. **#1172?** Retirer `date_option` (et autres A à 0 %) des deux écrans — **1 commit**.
2. **#1173?** Ajouter `type_financement` à la modale réserver + option `pret` (E) — **1 commit**, même zone.
3. **#1174?** Affichage dual AF sur fiche Show (si décision K) — Show seul possible.

Ne **jamais** paralléliser 1–3 (manifest commun).

### Hors zone commune (parallélisables entre eux, pas avec 1–3)
4. Verrou AF sur `CommercialController` (H) — PHP seul.
5. Anti-silence `validateReservationUpdate` (G) — PHP seul.
6. Rule::in `source_decouverte` (F) — PHP + éventuel miroir.
7. Inline create : retirer statut/prix_vente (H/G) — `ManagesCommercialLotPayload` + petit front grille.
8. Script/mesure + correctifs données I (statuts) — back/data.
9. Décision acompte / dépôt prêt / revente — après Robin, lots dédiés.

## DÉCISIONS ROBIN (Cursor ne tranche pas)

1. `date_option` (et dates A à 0 %) : **oubli de process** ou **champ mort** à retirer ?
2. Famille revente / EDD / SSP / memos : conserver pour un futur écran, ou purger whitelists ?
3. `pret` vs `pret_bancaire` : synonyme à fusionner, ou deux notions ?
4. Acompte : garder UI actuelle et abandonner `acompte_demande`/`acompte_paye`, ou l’inverse ?
5. `date_limite_depot_pret` vs `date_limite_depot_demande` : laquelle fait foi ?
6. `frais_inclus` JSON : encore utile quelque part métier ?
7. Double stack AF : laquelle afficher sur la fiche ? (pas de fusion — Sabrina/Rudy)
8. `Commercial/Index.vue` : déprécier au profit de LotsGrillePanel ?
9. `lots_commerciaux.tma_montant_ttc` : après mesure script — retirer affichage Index si 0 ?

## DÉPLOIEMENT-TEST

1. `git pull`
2. `php tools/diag/diag_commercial_incoherences_1171.php` → coller sortie dans cette fiche
3. Aucun migrate / aucun build front (pas de .vue dans ce lot)

## LEÇON

Un champ orphelin exposé (`tma_montant`) n’est pas un cas isolé : croiser
**remplissage prod × présence formulaire × whitelist × miroir PHP/Vue** sur
tout le module avant d’ajouter des champs. Deux chemins d’écriture dont un
seul protégé (#1162) et deux whitelists (#1156 vs update JSON) sont le même
pattern systémique.
