---
suivi: 1175
date: 2026-08-04
sujet: Diagnostic — frais_inclus vs frais_acte_estimes_ttc (acte en main)
chantier: commercialisation
type: diagnostic
statut: poussé
hash: 7181c5d3
fichiers:
  - tools/diag/diag_frais_acte_1175.php
  - docs/suivi/SUIVI_1175_diag_frais_acte.md
---

## PROMPT ENVOYÉ

SUIVI #1175 PHASE 1 DIAG — instruire si `frais_inclus` (61/61) et
`frais_acte_estimes_ttc` (#1166, 0/61) sont un doublon monétaire silencieux
(motif TMA #1170) avant saisie Mélanie. Script lecture seule + fiche.
Aucun code métier. HEAD annoncé aa618bd2.

## SYNTHÈSE

### Fiches lues (consultation-suivi)

- `SUIVI_1154` — KPI remises = écart grille−vente ; `frais_inclus` HORS formule ;
  alerte « trois concepts homonymes » (remise KPI / remises_commerciales /
  frais_inclus).
- `SUIVI_1166` — ajoute `acte_en_main` + `frais_acte_estimes_ttc` ; LEÇON :
  « Ne pas réutiliser `frais_inclus` pour un KPI monétaire structuré ».
- `SUIVI_1171` — classe B : `frais_acte_estimes_ttc` vs `frais_inclus` JSON,
  aucune sync ; question Robin #6 « frais_inclus encore utile ? ».
- `SUIVI_1170` — motif TMA (3 emplacements divergents) : référence pour
  juger le risque de doublon ici.
- Dernier numéro avant ce lot : **#1174** (en cours, hors ce commit).

### Script

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

**Coller ici la sortie Q0–Q3 produite en prod** (JSON bruts + agrégats).

---

## Q1 — Contenu réel de `frais_inclus`

### Structure code (source de vérité du défaut)

`Contact::defaultFraisInclus()` retourne **7 lignes** :

| libelle | provision | remboursement |
|---------|-----------|---------------|
| Notaire | 0 | 0 |
| Procuration | 0 | 0 |
| Int Intercalaires | 0 | 0 |
| Caution | 0 | 0 |
| Financement | 0 | 0 |
| Garanties | 0 | 0 |
| Autres Frais | 0 | 0 |

Chaque entrée = `{ "libelle": "…", "provision": 0, "remboursement": 0 }`.

**Oui, une clé / libelle « Notaire » existe.** Elle porte deux **montants**
(`provision`, `remboursement`), pas un booléen. Sur le défaut (et donc sur
toute résa créée sans saisie), ces montants valent **0**.

Le script affiche en prod :
- le JSON **intégral brut** de 10 résas représentatives ;
- la liste **exhaustive** des libellés + distribution provision/remboursement ;
- le count `egal_defaut` vs `diff_defaut` ;
- Notaire avec montants ≠ 0.

Attente code (à confirmer par sortie prod) : **100 % = défaut**, Notaire
toujours à 0/0 → aucune saisie métier derrière le « 61/61 ».

---

## Q2 — Qui écrit / qui lit `frais_inclus`

### Écriture

| Chemin | Comportement |
|--------|--------------|
| `ReservationController::store` | Si absent/null/`[]` → `Contact::defaultFraisInclus()` |
| `ReservationController::update` | Si clé présente et vide → même défaut |
| `CommercialController` (legacy reserve) | Toujours `defaultFraisInclus()` |
| `ImportPegaoController` | Toujours `defaultFraisInclus()` |
| Migration `2026_05_13_140500` | Copie historique depuis `clients.frais_inclus` |
| `UpdateAcquereurFicheRequest` | **Absent** de la whitelist → fiche n’écrit pas |
| Observers | **Aucun** |

### Lecture

| Consommateur | Utilise `frais_inclus` ? |
|--------------|--------------------------|
| UI Vue (`resources/js`) | **Non** (0 hit) |
| `RemisesAccordeesCalculator` / `useRemisesAccordees.js` | **Non** |
| PDF / Intacct / BilanFinancier / SharePoint | **Non** |
| Cast Eloquent `Reservation` | Oui (stockage `array` seulement) |

### KPI remises — confirmation #1154

`RemisesAccordeesCalculator::fraisActeReservation` lit **uniquement**
`acte_en_main` + `frais_acte_estimes_ttc`. `frais_inclus` **n’entre dans
aucun calcul**. Confirmé dans le code actuel (post-#1166/#1168).

---

## Q3 — Saisie ou dérivée ?

**Dérivée / défaut technique**, pas une saisie métier.

Toute création (modale, legacy, Pegao) pose le template à zéros si le
client n’envoie rien. Aucun formulaire Vue n’expose le champ. Donc
**61/61 renseigné ≠ 61/61 utilisés** : c’est le même motif que
`tma_montant = 0` forcé, pas une donnée commerciale vivante.

Si la sortie prod montre `egal_defaut == alive` → verdict fermé.
Si `diff_defaut > 0` → inspecter les JSON bruts (import legacy clients
possible) ; même alors, hors UI/KPI.

---

## Q4 — Le #1166 répond-il au même besoin ?

Demande Mélanie (#102) : case **Acte en main** + **montant estimé** qui
**s’ajoute** à la remise commerciale dans le KPI (ex. 200k/195k/3k → 8k).

| Critère | `frais_inclus` | `#1166` |
|---------|----------------|---------|
| Flag « acte en main » | Non | `acte_en_main` |
| Montant unique estimé | Non (N lignes provision/remboursement) | `frais_acte_estimes_ttc` |
| Branché KPI remises | Non | Oui |
| UI saisie | Non | Oui (grille + fiche) |
| Permet l’exemple 8 000 € | **Non** | **Oui** |

`frais_inclus` **ne permet pas** ce calcul et n’est **pas** lié à la
notion d’acte en main.

**Verdict Q4 : deux notions distinctes**, pas un doublon monétaire
silencieux type TMA.

- `frais_inclus` = checklist legacy « frais (notaire, caution…) —
  provision vs remboursement » (morte / défaut).
- `acte_en_main` + `frais_acte_estimes_ttc` = concession commerciale
  « le promoteur prend les frais d’acte » pour le KPI remises.

Homonymie de vocabulaire (« Notaire » / « frais d’acte »), **pas** deux
emplacements pour la même valeur KPI.

---

## Q5 — « Acte en main » préexistait-il ?

**Non.** Avant #1166 :
- aucune colonne `acte_en_main` / `frais_acte*` / `frais_notaire` sur
  `reservations` (constat fiche #1166) ;
- grep `app/` + `resources/js/` : `acte_en_main` apparaît seulement
  depuis ce lot ;
- `frais_inclus` existait (héritage `clients`, mai 2025) sous une autre
  sémantique (lignes provision/remboursement).

Le #1166 **n’a pas recréé** un mécanisme existant sous un autre nom : il
a créé la notion manquante pour le KPI.

---

## Q6 — Autres emplacements de frais

| Emplacement | Remplissage (#1171) | Rôle | Doublon #1166 ? |
|-------------|---------------------|------|-----------------|
| `reservations.frais_inclus` | 61/61 (défaut) | Checklist legacy | Non (autre sémantique) |
| `reservations.frais_acte_estimes_ttc` + `acte_en_main` | 0/61 | KPI remises | — |
| `reservations.memo_vente_frais` | 0/61 | Texte libre, hors UI | Non |
| `promesses.frais_acte_verses` | (foncier) | Promesse foncière | **Hors** commercial |
| FK / textes `notaire*` | identité du notaire | Pas un montant | Non |
| `remises_commerciales_ttc` | remises | Autre notion | Non (déjà distinct du KPI) |

**Pas de troisième emplacement monétaire « frais d’acte »** dans le
périmètre commercial (contrairement au TMA en 3 lieux).

---

## RECOMMANDATION TRANCHÉE

**Deux notions distinctes qui cohabitent :** garder `#1166`
(`acte_en_main` + `frais_acte_estimes_ttc`) pour la concession KPI ;
`frais_inclus` est un **template technique mort** (zéros) — ne pas s’en
servir pour le besoin Mélanie, et ne pas supprimer `#1166` sous prétexte
du 61/61.

Mélanie peut saisir dans le nouveau champ **sans data-fix**. Un lot
ultérieur (hors urgence) pourra retirer `frais_inclus` des whitelists
API / store ; DROP COLUMN seulement après confirmation prod
`egal_defaut == alive` et décision Robin (#1171 Q6).

**Ne rien corriger dans ce lot** (constat uniquement).

---

## DÉPLOIEMENT-TEST

```text
git pull origin main
php tools/diag/diag_frais_acte_1175.php
```

Coller la sortie complète (surtout Q1 JSON bruts + `egal_defaut` /
Notaire ≠ 0) dans cette fiche. Aucun migrate. Aucun npm.

## LEÇON

Un taux de remplissage 100 % sur une colonne JSON peut être un **défaut
de création**, pas une adoption métier — croiser avec UI + lecteurs +
égalité au template code avant de conclure à un doublon. Le motif TMA
(#1170) exige **même sémantique monétaire + chemins d’écriture
concurrentiels** ; ici la sémantique diverge (checklist vs montant KPI)
et un seul chemin est branché au calcul.
