---
suivi: 1159
date: 2026-08-03
sujet: Commercialisation — suppression lot sans données rattachées (#99)
chantier: commercialisation_grille
type: feature
statut: poussé
hash: e8eb5e8d
fichiers:
  - app/Models/LotCommercial.php
  - app/Http/Controllers/LotCommercialController.php
  - app/Http/Controllers/CommercialController.php
  - resources/js/Components/Commercialisation/LotsGrillePanel.vue
  - database/data/journal_mises_a_jour_post_2026_06_12.php
  - public/build/
  - docs/suivi/SUIVI_1159_commercial_suppression_lot.md
---

## PROMPT ENVOYÉ

SUIVI #1159 — lot 4 commercial. Élargir la suppression soft-delete d’un lot
(#99) : plus seulement `statut === en_stock`, mais dès qu’aucune donnée
**bloquante**. Refus motivé. Soft-delete uniquement (pas de cascade MySQL).
Réf. SUIVI_1154.

## SYNTHÈSE

### Fiches lues

- `SUIVI_1154` — FK CASCADE vs soft-delete ; destroy limité à `en_stock` ;
  2 lots en_stock sans résa/AF.
- `SUIVI_1158` — zone `LotsGrillePanel` ; KPI via `lotsCommerciaux()`
  (exclut trashed).

### Règle métier (#1159)

| Donnée | Bloquant ? |
|--------|------------|
| réservations (tous statuts, **withTrashed**) | OUI |
| appels_de_fonds | OUI |
| offres_commerciales (hors soft-deleted offre) | OUI |
| tmas (hors soft-deleted TMA) | OUI |
| lot_emplacements / lot_annexes / historique / plan | NON |

Suppression = **`$lot->delete()` SoftDeletes**. Jamais `forceDelete` ici
(le hard delete emporterait historique / résas via CASCADE).

Statut **ignoré** pour l’autorisation : un `en_vente` sans rattachement
est OK ; un `en_stock` avec TMA est refusé.

### API / contrôleurs

- `LotCommercial::compteursDonneesBloquantes()`,
  `peutSupprimerSansDonneesRattachees()`, `messageRefusSuppression()`.
- Relations ajoutées : `tmas()`, `appelsDeFonds()`.
- `LotCommercialController::destroy` + `CommercialController::destroy`
  (legacy) alignés → 422 + message « Impossible de supprimer le lot X :
  1 réservation, 2 TMA. »

### Auth / AuthorizesRequests

- Base `Controller` vide (Laravel 11+) : **pas** de trait
  `AuthorizesRequests` sur `LotCommercialController` — **aucun**
  `$this->authorize()` (vérifié Reflection : `authorize` = NO).
- Protection actuelle : middleware `auth` sur le groupe programmes ;
  **pas** de Policy `LotCommercial` ni middleware groupe dédié sur
  `DELETE lots-commerciaux` (même modèle qu’avant #1159).
- Contrôle métier **serveur** sur données bloquantes (pas seulement UI).

### UI

- Bouton « Supprimer » visible dès qu’un lot est en édition (plus seulement
  `en_stock`).
- Confirm nommant le n° + précision soft-delete / restaurable.
- Toast d’erreur = message serveur motivé.

### KPI / trashed

`CommercialisationController` charge `$programme->lotsCommerciaux()` —
relation SoftDeletes → **exclut** `deleted_at`. Synthèse stock / remises
ne comptent pas le lot trashed préexistant. Soft-delete ne purge pas
`lot_commercial_historique`.

## DÉPLOIEMENT / TEST

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

Pas de migrate.

### Tests

1. Lot sans bloquant → archivé, hors grille.
2. 1 réservation → 422 « 1 réservation ».
3. TMA seul → refusé.
4. Emplacements/annexes seuls → OK.
5. Historique encore en base après soft-delete.
6. Lot trashed absent des KPI / stock.
7. DELETE API direct sur lot bloqué → 422.

## LEÇON

Soft-delete + CASCADE MySQL = combinaison trompeuse : le soft-delete est
sain précisément parce qu’il **ne** cascade pas. Ne jamais élargir vers
`forceDelete` sans checklist enfants. Le critère `statut === en_stock`
était un proxy insuffisant de « sans données ».
