---
suivi: 1589
date: 2026-09-17
sujet: Corbeille dossier foncier (consultation, pas de restauration)
chantier: foncier / dossiers
type: feature
statut: poussé
hash: df36dda1
fichiers:
  - database/migrations/2026_09_17_140000_add_soft_deletes_to_dossiers_fonciers_and_satellites.php
  - app/Support/Foncier/DossierFoncierCorbeilleDroits.php
  - app/Services/Foncier/DossierFoncierCorbeilleService.php
  - app/Http/Controllers/Foncier/DossierFoncierCorbeilleController.php
  - app/Http/Controllers/Foncier/DossierFoncierController.php
  - app/Models/DossierFoncier.php
  - app/Models/OffreFonciere.php
  - app/Models/PromesseFonciere.php
  - app/Models/DossierFoncierEchange.php
  - app/Models/EntiteCommentaire.php
  - app/Models/ActeFoncier.php
  - app/Models/Parcelle.php
  - app/Services/Foncier/ParcelleStatutService.php
  - app/Services/Foncier/DossierFoncierSuiviAggregator.php
  - app/Services/Foncier/FoncierEntityDocumentAccess.php
  - app/Services/Foncier/FoncierParcelleVisibiliteService.php
  - app/Services/Foncier/ParcelleSupervisionQuery.php
  - app/Models/Scopes/FoncierParcelleVisibiliteScope.php
  - app/Models/TacheSuivi.php
  - resources/js/Pages/Foncier/Dossiers/Show.vue
  - resources/js/Pages/Foncier/Dossiers/Corbeille.vue
  - resources/js/Components/Foncier/DossierSyntheseTab.vue
  - resources/js/Layouts/AppShell.vue
  - app/Http/Controllers/ProgrammeController.php
  - app/Http/Controllers/DepotFactureController.php
  - app/Http/Controllers/Foncier/ActeFoncierController.php
  - app/Services/Foncier/ParcelleCreationUnitaireService.php
  - routes/web.php
  - tests/Unit/DossierFoncier1589CorbeilleSourceTest.php
  - database/data/journal_mises_a_jour_post_2026_06_12.php
  - public/build/
  - docs/suivi/SUIVI_1589_corbeille_dossier_foncier.md
---

# SUIVI #1589 — Corbeille dossier foncier

## PROMPT ENVOYÉ

Mise en corbeille d’un dossier foncier (GS_ADMIN_ERP, soft delete,
pas d’effacement, pas de restauration). Parcelles détachées +
recalcul statut (#1569). Actes intacts. Page corbeille lecture seule
sur le patron parcelles. Disparition de la circulation.

## FICHES LUES

- **#1249 LEÇON** — unique MySQL + soft delete = colonne STORED vivante.
  Impact : `programme_id_vivant` pour libérer le chaînage programme
  (#1556) sans perdre `programme_id` en consultation.
- **#1569** — statut déduit, on APPELLE `recalculerIds`, on ne change
  pas l’ordre de `deduire()`. Impact : filtre `whereNull(deleted_at)`
  dans `chargerFaits` (même motif que `parcelles.deleted_at`), sinon
  une offre/promesse en corbeille continuerait à produire En
  négociation / Sous compromis.
- **#1579 / #1583** — journal agrégé en lecture. Impact : événement
  `mise_en_corbeille` (qui / quand) ; snapshot des parcelles d’alors
  pour les rattachements après détachement du pivot.
- **#1581** — supervision via `dossier_foncier_parcelle`. Impact :
  détachement + `deleted_at IS NULL` dans le GREATEST SQL.
- **#1588 LEÇON** — avertissement « déjà dans un autre dossier » via
  `$parcelle->dossiersFonciers` (Eloquent, hors ACL pour citer DF-xxxx).
  Impact : SoftDeletes + détachement du pivot excluent le dossier
  corbeillé de `dossiers_existants` à la création.

## OBJETS MIS EN CORBEILLE (avant code)

| Objet | Table | `deleted_at` avant | Traitement |
|---|---|---|---|
| Dossier | `dossiers_fonciers` | **absent** | SoftDeletes + `deleted_by` + snapshot JSON. Migration. |
| Offres | `offres_foncieres` | **absent** | SoftDeletes. Migration. Pivot `offre_parcelle` conservé (consultation). |
| Promesses | `promesses` | **absent** | SoftDeletes. Migration. Pivot `promesse_parcelle` conservé. |
| Échanges | `dossier_foncier_echanges` | **absent** | SoftDeletes. Migration. |
| Commentaires | `entite_commentaires` | **absent** | SoftDeletes. Migration. |
| Tâches | `taches_suivi` | **déjà là** | `$tache->delete()` (SoftDeletes existant). Pas de migration. |
| Documents | `entity_documents` | **absent, volontaire** | **Pas** de SoftDeletes. `FoncierEntityDocumentService::delete()` retire le fichier disque ; y brancher un soft-delete casserait le téléchargement. Les lignes restent ancrées sur le parent mis en corbeille. Téléchargement via route corbeille. |
| Actes | `actes_fonciers` | absent | **Non touchés.** Voir ci-dessous. |
| Scénarios | `scenarios` | absent | Hors liste imposée. Restent attachés, visibles en consultation corbeille, exclus de la circulation via le dossier soft-deleted. |
| Parcelles | `dossier_foncier_parcelle` | n/a | **Détachées** (DELETE pivot). Snapshot `corbeille_snapshot` pour « parcelles d’alors ». Recalcul `ParcelleStatutService::recalculerIds`. |

Unique `dossiers_fonciers.reference` inchangé (référence auto, pas de
recréation attendue). Unique `programme_id` (#1556) remplacé par
`programme_id_vivant` STORED (index FK `df_programme_id_idx` avant
drop unique, motif #1253).

## ACTES — TRAITEMENT RETENU

Les actes portent `dossier_foncier_id` **et** le pivot `acte_parcelle`.
Signalé : rattachement au dossier lui-même, pas seulement aux parcelles.

Traitement : **on ne met pas l’acte en corbeille, on ne nullifie pas
`dossier_foncier_id`.** L’acte reste un fait juridique actif. La
relation `ActeFoncier::dossierFoncier()` charge `withTrashed()` pour
ne pas perdre le lien à l’affichage. Une parcelle Détenue le reste
parce que `societe_detentrice_id` et l’acte subsistent (#1569).

## AUTORISATION

`DossierFoncierCorbeilleDroits::userPeut()` lit `m365GroupNames()` brut,
groupe `GS_ADMIN_ERP` uniquement. `abort_unless` serveur. Jamais
`hasAnyM365Group` / `isAdminERP` / `Gate`. Le compte Robin est légitime
pour l’autorisation, **nul** pour le refus.

Bouton front : `peut_mettre_en_corbeille` serveur. Menu « Corbeille
dossiers » : `m365GroupNames.includes('GS_ADMIN_ERP')`. La corbeille
parcelles (`foncier.corbeille`) n’est pas touchée.

## POINTS DE LECTURE VÉRIFIÉS (exclusion corbeille)

| Point | Mécanisme |
|---|---|
| Liste dossiers `DossierFoncierController::index` | Eloquent SoftDeletes |
| Fiche live `show` / binding `{dossierFoncier}` | 404 si trashed |
| Hub programme `DossierProgrammeLink` | Eloquent SoftDeletes + unique vivant |
| Création programme `exists:dossiers_fonciers` | `Rule::exists()->whereNull('deleted_at')` |
| Dépôt facture destination projet | idem |
| Acte / parcelle unitaire `dossier_foncier_id` | idem |
| Recherche dépôt `DepotProjetDossierService` | Eloquent |
| Avertissement création #1588 `dossiers_existants` | Eloquent `dossiersFonciers` + pivot vidé |
| Fiche parcelle `dossiersFonciers()` / `suivi_autre_dossier` | Eloquent + pivot vidé |
| Visibilité service `FoncierParcelleVisibiliteService` | `whereNull(d.deleted_at)` (propriétaires restent attachés) |
| Doublon #1076 `FoncierDoublonDetectionService` | Eloquent SoftDeletes (hors ACL, dossiers actifs) |
| Supervision #1581 GREATEST | pivot vidé + `deleted_at IS NULL` |
| Visibilité parcelle `FoncierParcelleVisibiliteScope` | `whereNull(d.deleted_at)` |
| Mes tâches `TacheSuivi` scope dossier | tâches soft-deleted + `whereNull(d.deleted_at)` |
| Offres / promesses listes Eloquent | SoftDeletes |
| `ParcelleStatutService::chargerFaits` | `whereNull` offres / promesses / dossiers |

## DÉPLOIEMENT

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

## LEÇON

Corbeille dossier ≠ corbeille parcelle : pas de restauration, actes
hors cascade, documents hors SoftDeletes (le delete métier efface le
fichier). Unique programme : STORED vivant, pas NULL du FK. Filtrer
`deleted_at` dans `chargerFaits` n’est pas un changement de `deduire()`,
c’est le même cran que `parcelles.deleted_at` déjà présent. Le test
d’autorisation avec le compte Robin ne prouve pas le refus.
