---
suivi: 1247
date: 2026-08-07
sujet: Hotfix migrations #1242/#1246 constructible non quoté + idempotence
chantier: foncier / parcelles / migrations
type: hotfix
statut: poussé
hash: 84d6b56d
fichiers:
  - database/migrations/2026_08_07_151000_add_type_foncier_constructible_bati_parcelles.php
  - database/migrations/2026_08_07_161000_add_societe_detentrice_id_to_parcelles_table.php
  - database/data/journal_mises_a_jour_post_2026_06_12.php
  - docs/suivi/SUIVI_1247_hotfix_migrations_parcelles.md
---

# SUIVI #1247 — HOTFIX migrations parcelles (#1242 / #1246)

## PROMPT ENVOYÉ

Corriger conversion `constructible` (valeurs quotées / bindings) et rendre les deux
migrations idempotentes pour rejouer sur schéma partiel prod. Pas de nouvelle migration,
pas de toucher à `migrations`, pas de code applicatif.

## FICHES LUES

- `#1242` — migration fautive (ALTER puis UPDATE à mi-parcours)
- `#1246` — societe_detentrice_id (Pending derrière #1242)
- `#1099` — leçon migrations rejouables / DDL commit implicite MySQL

## CAUSE RACINE

Prod (HEAD annoncé e4f81a15 / repo `e2d9c153`) :

| Objet | État |
|---|---|
| `2026_08_07_151000_…` | **Pending** mais `type_foncier` + `constructible` varchar déjà là |
| `2026_08_07_161000_…` | **Pending**, `societe_detentrice_id` absente |
| `parcelle_batis` | absente |
| `constructible` | `'oui'=>4`, `'0'=>3` (conversion interrompue) |

Log : `update parcelles set constructible = oui where constructible in (1, 1)`  
→ valeur / prédicat numérique sur VARCHAR → `1292 Truncated incorrect DOUBLE value: 'oui'`.  
DDL MySQL = commit implicite → schéma partiel + Pending (motif #1099).

Conséquences 500 : prospection / fiche (pas de `parcelle_batis`) / création (pas de `societe_detentrice_id`).

## SYNTHÈSE

### Objet 1 — Conversion
- `DB::update('… = ? …', ['oui', '1', '1'])` et idem `'jamais'` / `'0'`
- SQL substitué : `SET constructible = 'oui'` (jamais `= oui`)
- État actuel géré : laisse `'oui'|'a_devenir'|'jamais'`, convertit `'1'`→`'oui'`, `'0'`→`'jamais'`, reste → NULL

### Objet 2 — Idempotence
- DDL **avant** données
- `hasColumn` / `hasTable` / `hasIndexNamed` (information_schema)
- `constructibleIsVarchar32()` : pas de re-ALTER si déjà varchar(32)
- Index `parcelles_type_foncier_idx` / `parcelles_constructible_idx` : créés seulement s’absents
  (peuvent déjà exister après la passe partielle)

### Objet 3 — Migration #1246
- Relue : **aucun UPDATE**, FK `entites.id` (table `entites`, PK `id` bigint — migration 2026_05_07_142000)
- Renforcée : `hasIndexNamed` information_schema + FK nommée courte

### Objet 4 — Pourquoi l’échec a passé inaperçu — **À REPORTER AU PROJECT.md**

`migrate --force` qui **échoue** renvoie un code ≠ 0 : une chaîne `cmd1 && cmd2` **s’arrête**.
Si la suite (`optimize:clear`, etc.) a quand même tourné, alors soit les commandes étaient
séparées / en `;`, soit le contrôle s’est fié au **nombre total** de migrations dans
`migrate:status` (les Ran: N augmentent avec le temps) sans regarder les **Pending**.

**Règle à ajouter au PROJECT.md / séquence OVH :**

Après tout `php artisan migrate --force`, exiger :

```
php artisan migrate:status | grep -c Pending
```

→ doit afficher **0**. Sinon le déploiement est **en échec**, même si d’autres commandes
semblent avoir « passé ». Un `migrate --force` qui « semble » OK n’est pas une preuve.

## DÉPLOIEMENT-TEST

```bash
git pull origin main
php artisan migrate --force
php artisan migrate:status | grep -c Pending   # doit = 0
php artisan optimize:clear
php artisan view:clear
php artisan journal:sync
```

## LEÇON

DDL MySQL = commit implicite → migration données après ALTER **doit** être rejouable.
Bindings string obligatoires sur UPDATE de valeurs métier.
Preuve de migrate = **Pending = 0**, pas le feeling de la chaîne de déploiement.
