---
suivi: 1624
date: 2026-09-21
sujet: Collision référence dossier foncier (corbeille)
chantier: foncier / dossiers
type: fix
statut: poussé
hash: b6edbe76
fichiers:
  - app/Http/Controllers/Foncier/DossierFoncierController.php
  - tests/Unit/DossierFoncier1624ReferenceSourceTest.php
  - tests/Feature/Foncier/FoncierDossier1624ReferenceCollisionTest.php
  - database/data/journal_mises_a_jour_post_2026_06_12.php
  - docs/suivi/SUIVI_1624_reference_dossier_foncier_collision.md
---

# SUIVI #1624 — référence dossier foncier, corbeille comprise

## PROMPT ENVOYÉ

Bug bloquant prod : Éric PORTALES ne peut plus créer de dossier
foncier depuis le 17/09. 500 Duplicate entry `DF-2026-0011` sur
`dossiers_fonciers_reference_unique` (store L233). Cause : le
générateur ignore les lignes soft-deleted ; DF-2026-0011 est en
corbeille (id 99, « boisseron test », deleted_at 2026-09-17).
MAX des vivants → 0011 déjà pris par l’index unique. Fix : MAX
toutes lignes de l’année (withTrashed) + retry borné, jamais un
500 brut. Inventaire des autres générateurs, sans les corriger.
Ne pas toucher unique, données, corbeille, référentiel.

## FICHES LUES

- **#1589 LEÇON** — SoftDeletes ajouté sur `dossiers_fonciers` ;
  unique `reference` volontairement inchangé. Impact : le
  générateur n’a pas été aligné ; c’est exactement l’incident.
- **#1519 LEÇON** — SoftDeletes filtre Eloquent, pas l’UNIQUE
  MySQL. À l’époque dossiers = pas de SoftDeletes. Impact : même
  motif que `code_envol` ; ici le SoftDeletes est arrivé après
  (#1589).
- **#1606 / #1605** — Query Builder / agrégats ignorent
  SoftDeletes (famille inverse : trop de lignes vs trop peu).
  Impact : ne pas compter les vivants pour une séquence UNIQUE.
- **#1137** — rang d’offre « jamais réattribué » + unique
  `(dossier, rang)`. Impact : signalé, pas corrigé dans ce lot.
- **#1249 LEÇON** — unique MySQL + soft delete. Impact : on ne
  drop pas l’unique ; on avance le compteur.

Aucune fiche #1621–#1623 sur `origin/main`. Lot PHP uniquement
(pas de `.vue`, pas de `public/build/`). Pas de collision de
zones communes.

## SYNTHÈSE

### Générateur fautif

`DossierFoncierController::generateReference` (appelé par
`store`). Eloquent sans `withTrashed()` → SoftDeletes exclut
DF-2026-0011 → MAX vivant 0010 → propose 0011 → 1062.

### Correctif

- `withTrashed()` + `withoutGlobalScope(RecordHabilitationScope)`
  + MAX numérique du suffixe de l’année (`DF-AAAA-`).
  1er janvier : préfixe nouvelle année, compteur 0001.
- `store` : jusqu’à 8 essais ; collision **uniquement** sur
  `dossiers_fonciers_reference_unique` → numéro suivant.
  Autre unique (programme) : rethrow. Épuisement : 422
  `ValidationException`, `Log::error`, jamais un 500.
- Unique `reference` inchangé. Aucune renumérotation. Aucune
  purge. Référentiel foncier non touché.

### Restauration

Pas d’UI de restauration (#1589). `restore()` Eloquent conserve
la référence ; le numéro reste brûlé.

## AUTRES GÉNÉRATEURS (non corrigés)

Voir synthèse livrable. Offres : `prochainRang` + unique
`(dossier, rang)` = même famille, latent (offres corbeillées
avec le dossier, création d’offre sur dossier vivant peu
exposée).

## DÉPLOIEMENT

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

Pas de migrate, pas de `route:clear`, pas de npm.

## LEÇON

Ajouter SoftDeletes sans relire les générateurs UNIQUE = 500
définitive dès qu’une valeur est en corbeille. `withTrashed()`
(ou `DB::table`) pour toute séquence dont l’index unique porte
sur toutes les lignes. Un retry borné + 422 empêche le 500
résiduel (course). Ne pas compter les vivants. Ne pas drop
l’unique pour « débloquer ».
