---
suivi: 1574
date: 2026-09-16
sujet: Bâti — Pas de bâti vs non vérifié
chantier: foncier / parcelles / bâti
type: feature
statut: poussé
hash: 72e86a7c
fichiers:
  - database/migrations/2026_09_16_150000_add_bati_absent_to_parcelles.php
  - app/Models/Parcelle.php
  - app/Http/Controllers/Foncier/ParcelleBatiController.php
  - app/Http/Controllers/Foncier/ParcelleController.php
  - app/Http/Controllers/Foncier/ProspectionController.php
  - app/Services/Foncier/ParcelleProspectionQuery.php
  - resources/js/Pages/Foncier/Parcelles/Show.vue
  - resources/js/Pages/Foncier/Prospection/Index.vue
  - resources/js/Components/Foncier/Prospection/ProspectionParcellesSublist.vue
  - routes/web.php
  - tests/Unit/Parcelle1574SourceTest.php
  - tests/Feature/Foncier/FoncierAuditInlineSourceTest.php
  - database/data/journal_mises_a_jour_post_2026_06_12.php
  - public/build/
  - docs/suivi/SUIVI_1574_colonne_bati_trois_etats.md
---

# SUIVI #1574 — Colonne Bâti à trois états

## PROMPT ENVOYÉ

Colonne Bâti Oui / Non / — sur le patron Occup. #1563. Marqueur
date+auteur. Filtre SQL. Pas de reprise. `bati_present` non corrigé.

## FICHES LUES

- **#1563 LEÇON** — ne pas créer une fausse ligne ; lever le marqueur
  dans `store()`, pas au front ; booléen nu insuffisant. Impact :
  `bati_absent_at` + auteur, hors fillable, `forceFill`, bouton
  indisponible s’il existe une ligne, ajout de bâti lève le marqueur.
- **#1533 LEÇON** — Occup. dans Index ; eager `withCount` pour éviter
  le N+1. Impact : `withCount('batis as batis_count')` dans
  `appliquerEagerListe`, pas de requête par ligne.
- **#1446** — `bati_present` booléen nullable + bloc détaillé. Impact :
  ne pas s’appuyer dessus pour la colonne (doublon, voir ci-dessous).

## PHASE PRÉALABLE — DEUX SOURCES

### 1. Table détaillée `parcelle_batis`

Écriture : `ParcelleBatiController::store` crée une ligne
(`app/Http/Controllers/Foncier/ParcelleBatiController.php:50`).
Lecture fiche : relation `batis` chargée dans
`ParcelleController::show` (`app/Http/Controllers/Foncier/ParcelleController.php:284`).

### 2. Booléen `parcelles.bati_present` (#1446)

Écriture :
- inline fiche (`ParcelleController::champsInlineAutorises` ligne 76,
  `updateChamp` lignes 749–757) ;
- création unitaire (`ParcelleCreationUnitaireService`) ;
- à l’ajout d’une ligne de bâti, forcé à `true`
  (`ParcelleBatiController.php:69-70`).

Lecture fiche : `serializeParcelle` (`ParcelleController.php:1223-1227`)
— si `bati_present` est NULL, repli sur `batis()->exists()` ; s’il est
`false`/`true`, cette valeur l’emporte même si elle contredit la table.
Affichage : `Show.vue` `v-if="local.bati_present"` autour du détail.

### Divergence possible

Oui. Exemples : `bati_present = true` sans aucune ligne (toggle
inline) ; lignes présentes avec `bati_present = false` (toggle après
coup ; `destroy` de bâti ne remet pas le booléen). Le booléen et la
table ne sont pas synchronisés en retrait.

Mesure en production (parcelles non supprimées) :

SELECT
  SUM(CASE WHEN p.bati_present = 1 AND b.n IS NULL THEN 1 ELSE 0 END) AS bati_present_sans_ligne,
  SUM(CASE WHEN (p.bati_present = 0 OR p.bati_present IS NULL) AND b.n IS NOT NULL THEN 1 ELSE 0 END) AS ligne_sans_bati_present_true,
  COUNT(*) AS nb_parcelles
FROM parcelles p
LEFT JOIN (
  SELECT parcelle_id, COUNT(*) AS n
  FROM parcelle_batis
  GROUP BY parcelle_id
) b ON b.parcelle_id = p.id
WHERE p.deleted_at IS NULL;

### Source retenue pour la colonne / le filtre

Table `parcelle_batis` + marqueur `bati_absent_at` / `bati_absent_par_id`.
`bati_present` n’est **pas** utilisé. Doublon signalé, non corrigé.

### Incompatibilité marqueur / ligne

Aligné #1563 : bouton indisponible s’il existe au moins une ligne
(`batis()->exists()` refuse le POST) ; l’ajout d’un bâti appelle
`leverMarqueurPasBati()` dans `store()`.

## MAPPING FILTRE / COLONNE

| Libellé filtre | Valeur postée | Colonne | Back |
|---|---|---|---|
| Oui | `oui` | Oui | Bâti |
| Non | `non` | Non | Bâti |
| Non vérifié | `indetermine` | — | Bâti |

Mêmes mots que la colonne (pas Occupée vs Non).

## SYNTHÈSE

Marqueur date+auteur. Liste SQL. Filtre SQL. Audit via champs
historisés. `bati_present` inchangé.

## DÉPLOIEMENT-TEST

```
git pull origin main
php artisan migrate --force
php artisan optimize:clear
php artisan view:clear
php artisan journal:sync
```

## LEÇON

`bati_present` et `parcelle_batis` peuvent dire le contraire. Une
colonne à trois états doit s’appuyer sur les lignes + un constat
daté, pas sur un booléen déjà ambigu. `withCount` n’ajoute pas de
round-trip (sous-requête du SELECT liste).
