---
suivi: 1249
date: 2026-08-07
sujet: Soft delete parcelle + corbeille administrateur
chantier: foncier / parcelles
type: feature
statut: poussé
hash: fd197bd5
fichiers:
  - database/migrations/2026_08_07_170000_add_soft_deletes_to_parcelles_table.php
  - database/migrations/2026_08_07_170100_add_foncier_corbeille_permission.php
  - app/Models/Parcelle.php
  - app/Services/Foncier/ParcelleSuppressionService.php
  - app/Http/Controllers/Foncier/ParcelleCorbeilleController.php
  - app/Http/Controllers/Foncier/ParcelleController.php
  - resources/js/Pages/Foncier/Parcelles/Show.vue
  - resources/js/Pages/Foncier/Parcelles/Corbeille.vue
  - resources/js/Layouts/AppShell.vue
  - database/data/journal_mises_a_jour_post_2026_06_12.php
  - docs/suivi/SUIVI_1249_parcelle_suppression_corbeille.md
---

# SUIVI #1249 — Soft delete parcelle + corbeille

## PROMPT ENVOYÉ

Soft delete parcelle, unicité cadastrale via colonne générée + proposition de
restauration à la création, corbeille permission `foncier.corbeille`, pas de
cascade, audit, exclusion des soft-deleted partout (dont `DB::table`).

## FICHES LUES

- `#1247` — DDL avant données, bindings, Pending=0 après migrate
- `#1096` — pattern `detention_courante` STORED pour UNIQUE + NULL
- `#1134` — snapshot audit à la suppression
- `#1217` — dépliage commune 4 requêtes constantes
- `#1235` — ne pas alourdir GS_* en dur applicatif
- `#1204` — `preference()` jamais `getPreference()`

## SOLUTION UNICITÉ (objet 1)

**(b) + (c)** :

- **(b)** colonne générée STORED `parcelle_vivante` =
  `CASE WHEN deleted_at IS NULL THEN 1 ELSE NULL END`,
  UNIQUE `(code_insee_commune, section_cadastrale, numero, parcelle_vivante)`.
  Même motif que `detention_courante` (#1096). (a) avec `deleted_at` seul
  échoue car `NULL ≠ NULL` en MySQL.
- **(c)** à la création, `findTrashedDuplicate` → flash `parcelle_en_corbeille`
  proposant restauration (si `foncier.corbeille`) plutôt qu’erreur UNIQUE.

## AUTORISATION

- Suppression : `assertGestion` / `foncier.gestion` (mécanisme parcelle existant).
- Corbeille : permission catalogue `foncier.corbeille` (pas de GS_* en contrôleur).

## TABLEAU CONSOMMATEURS (objet 6)

| Consommateur | Exclut soft-deleted | Corrigé |
|---|---|---|
| Eloquent `Parcelle::query` / SoftDeletes | oui (auto) | SoftDeletes ajouté |
| Prospection agg / compteurs état (Eloquent) | oui | n/a |
| Prospection dépliage joins `DB::table` nb proprio/dossiers | non → oui | `whereNull(parcelles.deleted_at)` |
| `ParcelleDetentionPropagationService` | non → oui | `whereNull` sur p/fille |
| `exists:parcelles,id` (7 contrôleurs) | non → oui | `Rule::exists…whereNull('deleted_at')` |
| Relations BelongsToMany actes/promesses/dossiers | oui (Eloquent) | n/a |
| Filiation source/issue | avec withTrashed + badge | volontaire |
| Aktor / exports / tools/diag | hors runtime UI | non touchés |
| `findDuplicate` | oui (soft) | + `findTrashedDuplicate` |

## PERFORMANCE

- Dépliage commune : toujours **4 requêtes** (agg Eloquent + etats + 2 joins) — filtre `deleted_at` ajouté aux joins, pas de requête supplémentaire.
- Compteurs Prospection : SoftDeletes sur `Parcelle::query()` — inchangé en nombre.

## LEÇON

Unicité MySQL + soft delete = colonne générée STORED (pas `deleted_at` dans UNIQUE).
Livrer le tableau consommateurs **avant** implémentation ; corriger tout `DB::table('parcelles')`.
Après `migrate --force`, exiger `migrate:status | grep -c Pending` → 0 (#1247).

## DÉPLOIEMENT

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

Attribuer `foncier.corbeille` via /admin/droits si besoin hors DSI/ADMIN_ERP.
