---
suivi: 1636
date: 2026-09-22
sujet: Faisabilité — saisie tableur étendue et règles de calcul rendues visibles
chantier: module-foncier
type: feature
statut: poussé
hash: 482fb1a8
fichiers:
  - resources/js/Pages/Foncier/Scenarios/Show.vue
  - tests/Unit/FaisabiliteSaisieTableur1636SourceTest.php
  - database/data/journal_mises_a_jour_post_2026_06_12.php
  - public/build/
  - docs/suivi/SUIVI_1636_faisabilite_saisie_tableur.md
---

# SUIVI #1636 — saisie tableur étendue + unités des cases à règle de calcul

## PROMPT ENVOYÉ

Étendre la saisie type tableur au reste de la page de faisabilité, et surtout :
**inventorier toutes les cases dont la valeur saisie n'est pas la valeur
utilisée** (exemple des VRD saisis par lot puis multipliés), puis écrire l'unité
attendue et afficher le résultat calculé à côté de la saisie. Aucune formule ne
doit changer.

## FICHES LUES

- **#1635** — saisie en ligne de la grille de prix : mécanisme
  (`registerCell` / `focusCellule` / `onCelluleEnter` / `memoriserValeur` /
  `annulerEdition`) à **réutiliser**, pas à réécrire.
- **#1634** — barre KPI collante : non touchée.
- **#1139** — postes saisis de l'étude Fi (`EtudeFiSaisies`), 24 codes.
- **#1143** — `FaisabiliteLotissementMoteur` : toutes les formules, et surtout
  la notion de **`nature`** de poste, qui est la clé de l'inventaire.
- **#1144 / #1145** — fiche foncière et grille de prix.

## 🔴 INVENTAIRE DES CASES À RÈGLE DE CALCUL (livrable principal)

Source : `FaisabiliteLotissementMoteur::construireEtudeFi()` et ses fabriques
`posteParLot()` / `posteUnitaireNombre()` / `posteSaisi()` / `posteForfait()` /
`posteCalcule()`. Le moteur **étiquette déjà** chaque poste par sa `nature` et
fournit `unitaire`, `quantite` et `montant` : l'inventaire est donc exhaustif par
construction, pas par relecture visuelle.

### A. Saisie MULTIPLIÉE par le nombre de lots — `nature = saisi_par_lot`
Formule : `montant = valeur saisie × quantité`. Quantité = **nombre de lots
TOTAL** (Hectare + ODF) pour tous les postes ci-dessous.
Fichier : `app/Support/Foncier/Faisabilite/FaisabiliteLotissementMoteur.php`.

| Libellé affiché | Unité réellement attendue | Code de saisie | Ligne (appel) |
|---|---|---|---|
| LOT 1 Terrassement | Prix **par lot** | `vrd_terrassement_par_lot` | L390 |
| LOT 2 Réseaux Humides | Prix **par lot** | `vrd_reseaux_humides_par_lot` | L391 |
| LOT 3 Réseaux Secs | Prix **par lot** | `vrd_reseaux_secs_par_lot` | L392 |
| LOT 4 Espaces Verts | Prix **par lot** | `vrd_espaces_verts_par_lot` | L393 |
| Géomètre | Prix **par lot** | `ext_geometre_par_lot` | L416 |
| Architecte | Prix **par lot** | `ext_architecte_par_lot` | L417 |
| Urbaniste | Prix **par lot** | `ext_urbaniste_par_lot` | L418 |
| BE VRD | Prix **par lot** | `ext_be_vrd_par_lot` | L419 |
| BE hydraulique | Prix **par lot** | `ext_be_hydraulique_par_lot` | L420 |
| BE environnemental | Prix **par lot** | `ext_be_environnemental_par_lot` | L421 |
| Géotechnicien et études de sol | Prix **par lot** | `ext_geotechnicien_par_lot` | L422 |
| Paysagiste | Prix **par lot** | `ext_paysagiste_par_lot` | L423 |

**12 cases.** C'est l'exemple de Robin : le libellé affiché par le moteur ne
disait **pas** « par lot » (seul `EtudeFiSaisies::codesLibelles()` le dit, et il
n'est pas utilisé à l'écran). Le `× N` était affiché, mais sans nommer l'unité
ni montrer le résultat à côté.

### B. Saisie MULTIPLIÉE par un nombre saisi — `nature = saisi_unitaire_x_nombre`
| Libellé affiché | Unité attendue | Codes de saisie | Ligne |
|---|---|---|---|
| Compteurs supplémentaires | Montant **unitaire** × **nombre** | `vrd_compteurs_montant_unitaire` + `vrd_compteurs_nombre` | L394-400 (fabrique L594-611) |

**2 cases** (le montant unitaire et le nombre).

### C. Postes « par lot » dont l'unitaire est CALCULÉ, non saisi — lecture seule
`posteParLot()` renvoie `code_saisie = null` pour eux (`default => null` du
`match`, L577) : ils affichent `unitaire × quantité` sans être éditables.
Quantité = nombre de lots **HECTARE**.

| Libellé | Règle | Ligne |
|---|---|---|
| Honoraires de commercialisation | unitaire calculé (taux ou forçage) × nb lots Hectare | L436-442 |
| Communication groupe | paramètre `communicationGroupeParLot` × nb lots Hectare | L443-449 |

### D. Postes calculés par un POURCENTAGE ou une dérivation — `nature = calcule`
Non éditables ; listés parce que leur libellé porte la règle et qu'ils ne doivent
pas être confondus avec une saisie.

| Libellé | Règle | Ligne |
|---|---|---|
| Imprévus travaux (3 % des 4 LOTS) | % des 4 lots VRD | L401 |
| Honoraires de gestion HT | taux × assiette | L435 |
| Frais bancaires (4 % recettes Hectare TTC) | % des recettes | L458 |
| Impôts fonciers | paramètre par lot × nb lots | L459 |
| TOTAL PRIX DE REVIENT, Contrôle somme postes, Écart contrôle, Marge foncière, TVA sur marge, TVA totale, Bénéfice brut, % bénéfice / CA HT, % bénéfice / prix de revient, Bénéfice par lot, Honoraires internes (indicateur), Résultat consolidé, % consolidé / CA HT, % consolidé / prix de revient, Consolidé par lot | dérivations | L468-482 |

### E. Saisies utilisées TELLES QUELLES — `nature = saisi` / `forfait`
Aucune règle : la valeur saisie est le montant. Listées pour fermer
l'inventaire.
`commission_apporteur_ht`, `taxes_zac_pup_pae_ht`, `taxes_diverses_ht`,
`vrd_travaux_divers`, `vrd_fouilles_archeologiques`, `vrd_demolition`,
`vrd_erdf`, `vrd_divers_1`, `vrd_divers_2` (nature `saisi`) ;
`ext_avocat_forfait`, `ext_sps_forfait`, `ext_huissier_forfait` (nature
`forfait`).

### F. Hors étude Fi — grille de prix et fiche foncière
- **Grille de prix** : les cellules `€/m²`, `TVA`, `Hon. mont.`, `Hon. gest.` sont
  calculées (déjà grisées au #1635). La TVA dépend du **mode et du taux** du lot
  (#1635) : la saisie « Prix TTC » n'est donc pas la base imposable.
- **Fiche foncière** : `Mode`, `Prix total`, `€/m² ach.`, `€/m² glob.` sont
  calculés par le moteur à partir de `prix_achat_numeraire`,
  `prix_achat_odf_ttc` et des surfaces — ces quatre colonnes sont en fond gris et
  sans champ de saisie.

**Total des cases de saisie portant une règle de multiplication : 14**
(12 « par lot » + 2 « unitaire × nombre »).

## SYNTHÈSE

### Unité écrite + résultat affiché
Dans l'étude financière, chaque case saisie annonce désormais son unité
au-dessus du champ, et les cases à règle affichent le **résultat calculé** juste
à côté :
- `saisi_par_lot` → « **Prix par lot × N lot(s)** », puis
  `[ 1 200 ] × 24 = **28 800 €**` ;
- `saisi_unitaire_x_nombre` → « **Montant unitaire × nombre** », puis
  `[ 250 ] × [ 8 ] = **2 000 €**` ;
- `forfait` → « Forfait (montant total) » ; `saisi` → « Montant total HT » ;
- postes « par lot » non éditables (C) → « Unitaire calculé × nombre de lots ».

Le résultat affiché est **`poste.montant`, tel que renvoyé par le moteur** :
aucune multiplication n'est faite dans la page (test dédié). Les libellés du
moteur n'ont pas été touchés — l'unité est une mention d'interface, pas un
renommage de poste.

### Saisie tableur étendue — mécanisme du #1635 réutilisé
Le registre de cellules a été **généralisé**, pas dupliqué : `cellKey` passe de
`idx:colonne` à **`table:idx:colonne`**, et `onCelluleEnterTable(table, idx, colonne, nbLignes)`
s'ajoute à côté du `onCelluleEnter` de la grille (qui garde sa gestion de
pagination). Il n'existe toujours qu'**un seul** `registerCell`, `focusCellule`
et `annulerEdition` dans la page (vérifié par test).

| Section | Tableur ? | Cellules branchées |
|---|---|---|
| Grille de prix (#1635) | **Oui** | Lot, Type, Mode TVA, Taux, Surf. cessible, Prix TTC |
| **Fiche foncière** | **Oui (ce lot)** | Surf. tot., Surf. ach., Surf. ODF, Lots ODF, Prix num., Prix ODF TTC, Régime TVA |
| **Étude financière** | **Oui (ce lot)** | toutes les cases saisies (par lot, unitaire × nombre, saisi, forfait) |
| Identité | Non — formulaire simple (libellé, mode, statut, commentaire) |
| Assiette | Non — lignes issues d'une recherche de parcelle, cases à cocher et listes ; le prompt la classe explicitement en saisie classique |
| Documents (#1629), Historique, dialogues d'import | Non — pas des grilles de saisie |

### Cohérence clavier
Identique partout : **Entrée** descend d'une ligne **dans la même table** ;
**Tabulation** reste **native** (l'ordre du DOM est ligne par ligne, donc droite
puis ligne suivante) ; **Échap** rétablit la valeur mémorisée au focus.

**Dernière cellule d'un tableau** : `if (suivante >= nbLignes) return;` — Entrée
valide et **reste** sur la cellule. Aucun saut vers une autre section, ce qui
était le risque signalé. La Tabulation, elle, sort naturellement du tableau
vers le contrôle suivant du DOM (bouton « Enregistrer … » de la section), ce qui
reste prévisible.

### Non-régression des calculs
Aucun fichier PHP de calcul n'est modifié par ce lot (le seul PHP touché depuis
hier l'a été au #1635, pour exposer les taux). Les formules, natures, quantités
et montants sont ceux du moteur ; la page ne fait que les afficher. Un test
vérifie la présence des formules clés et l'absence de toute multiplication
ajoutée côté page.

## VÉRIFICATIONS

**Aucun PHP modifié par ce lot** : `php -l` limité au journal et au test.
`ReflectionClass` / DI exécutés sur le moteur (voir synthèse) pour garantir que
les fabriques de postes sont inchangées.

Tests : `FaisabiliteSaisieTableur1636SourceTest` (8 tests) → **8 passed (98
assertions)**, dont le verrouillage du **compte de 14 appels `posteParLot`** et
des 5 natures. Non-régression faisabilité / scénario (#1635, #1634, #1145,
#1144, #1139, moteur) → **74 passed, 26 skipped**, avec **1 échec préexistant
hors périmètre** (`FoncierScenarioFaisabilite1142SourceTest > vues onglet et
fiche coquille`, attend `'faisabilite'` dans `Foncier/Dossiers/Show.vue`, fichier
non modifié par ce lot, déjà en échec au #1629).

⚠️ **Aucun test avec base** (`pdo_sqlite` absent) : la saisie réelle et le
recalcul n'ont pas été exécutés contre une base. Le test 9 (scénario existant
qui se calcule à l'identique) est indispensable en recette.

## DÉPLOIEMENT-TEST

```bash
git pull origin main
php artisan optimize:clear
php artisan view:clear
php artisan journal:sync
```

Aucune migration, aucune route. Pas de `npm` / `composer` sur OVH.

1. L'inventaire ci-dessus est le livrable : 12 cases « par lot » + 2 cases
   « unitaire × nombre », plus les postes calculés listés.
2. Étude financière : chaque case saisie affiche son unité au-dessus.
3. Saisir 1 200 sur « LOT 1 Terrassement » avec 24 lots → « × 24 = 28 800 € »
   affiché à côté, et le sous-total suit.
4. Comparer tous les montants de l'étude Fi avec une capture d'avant le lot :
   **identiques**.
5. Fiche foncière : clic sur une cellule → édition immédiate.
6. Entrée descend d'une ligne, Tabulation va à droite, Échap annule — même
   comportement que sur la grille de prix.
7. Mode / Prix total / €/m² (fiche) et €/m² / TVA / honoraires (grille) : non
   éditables, en fond gris.
8. Identité, commentaire et assiette : saisie classique inchangée.
9. **Ouvrir un scénario existant : tous les KPI et sous-totaux identiques.**

## LEÇON

L'inventaire des « cases à règle de calcul » n'a pas demandé de relire l'écran :
le moteur **étiquetait déjà** chaque poste (`nature`, `unitaire`, `quantite`).
Quand une information manque à l'utilisateur, vérifier d'abord si le back la
possède déjà — ici il fallait seulement l'afficher. Corollaire : le libellé
métier « (par lot) » existait dans `EtudeFiSaisies::codesLibelles()` mais le
moteur renvoyait un libellé plus court à l'écran ; deux sources de libellé pour
le même poste, c'est exactement comment une unité se perd.

Étendre un mécanisme de navigation à plusieurs tableaux se fait en **ajoutant une
dimension à la clé** (`table:ligne:colonne`), pas en dupliquant le registre.
Et une navigation clavier doit être **bornée à sa table** : sauter de section au
milieu d'une saisie est plus déroutant que de ne pas bouger.
