---
suivi: 1162
date: 2026-08-03
sujet: Commercial — auth lots + verrou AF prix + sync acte (#1161 dettes)
chantier: commercialisation
type: fix
statut: poussé
hash: 2017b663
fichiers:
  - app/Http/Controllers/Concerns/AssertsFeaturePermission.php
  - app/Http/Controllers/LotCommercialController.php
  - app/Http/Controllers/CommercialController.php
  - app/Http/Controllers/ReservationController.php
  - app/Http/Controllers/AcquereurController.php
  - app/Http/Controllers/OffreCommercialeController.php
  - app/Services/ReservationAcquereurUpdateService.php
  - routes/web.php
  - docs/suivi/SUIVI_1162_commercial_auth_verrou_af_sync_acte.md
---

## PROMPT ENVOYÉ

SUIVI #1162 — trois correctifs code (pas d’arbitrage comptable) issus de
#1161 / #1159 : auth LotCommercial, verrou AF sur ReservationController::update,
sync CommercialController action=acte. Hors périmètre : TVA 20 %, ARINVOICE HT,
compte 706000, cohabitation appels_fonds / appels_de_fonds.

## SYNTHÈSE

### Fiches lues

- `SUIVI_1161` — fuite acte (lot seul) ; trou ReservationController::update ;
  chemins prix ; AdF snapshots.
- `SUIVI_1159` — destroy sans authorize ; Controller base vide.
- `SUIVI_1160` — ne pas casser pivot acquéreurs (inchangé ici).

### Objet 1 — Autorisation écriture / suppression

**Pattern** : `feature.permission:{clé}` (middleware) + `AssertsFeaturePermission`
(`abort_unless` PermissionService) — **liste blanche**, pas early-return permissif.
Pas de `$this->authorize()` / Policy : AuthorizesRequests **volontairement absent**
(éviter fatal si appelé sans trait) ; le système commercial = features_permissions.

| Contrôleur | Avant | Après (écriture/suppression) |
|------------|-------|------------------------------|
| LotCommercialController | auth seul | `lots.ecriture` route + assert |
| CommercialController | auth seul (purge = M365) | store/update/destroy `lots.ecriture` + assert ; purge inchangé |
| ReservationController | auth seul | store/update/destroy/acter/annuler/docs `acquereurs.ecriture` + assert |
| AcquereurController | auth seul | update + observations `acquereurs.ecriture` + assert ; **show lecture libre** |
| OffreCommercialeController | OffresCommercialesDroits (M365 settings) | + `offres_commerciales.ecriture` (AND) |
| CommercialisationController | auth seul | **lecture seule** — non verrouillée ce lot |
| TMAController | déjà `tma.*` | inchangé |
| AppelsDeFondsController | déjà `programmes.appels_de_fonds.*` | inchangé |

Lecture (index, show, plan PDF) : **non verrouillée** dans ce lot.

### Objet 2 — Verrou AF factorisé

`ReservationAcquereurUpdateService::assertPrixVenteModifiable()` (public) +
compteurs legacy/Intacct. Appelé par :
- fiche acquéreur (`update`)
- `ReservationController::update`
- `CommercialController` action=acte (si prix posté)

Message explicite : nombre d’AF legacy + Intacct + total.

### Objet 3 — Sync acte

`CommercialController` action=`acte` : écrit d’abord la **réservation**
(prix/remises/commission/agence/commentaire), puis copie lot depuis la résa
(+ statut ACTE). Plus d’écriture `lot.prix_vente_ttc` sans résa.

### Chemins d’écriture `prix_vente_ttc` (exhaustif)

| Chemin | Protégé AF ? | Note |
|--------|--------------|------|
| ReservationAcquereurUpdateService (fiche) | OUI | déjà |
| ReservationController::update | OUI (#1162) | |
| CommercialController action=reserve | N/A création | sync résa→lot |
| CommercialController action=acte | OUI si Δ prix (#1162) | sync résa→lot |
| CommercialController action=annule | N/A | lot → null (voulu) |
| ReservationController::store | N/A création | |
| ReservationController::acter | n’écrit pas le prix | dates/statut |
| ReservationController::annuler | lot → null | résa conserve prix |
| createLotCommercial / store lot | création / import grille | whitelist grille unset vente (#856) |
| ImportPegaoController | import initial | m365 GS_DSI/ADMIN |
| LOT_GRILLE_WHITELIST update | **interdit** prix_vente | |

### Données

**Aucune écriture base** dans ce lot (pas de migration, pas de backfill).
Les 3 divergences annulées restent ; 0 attendu sur en_cours|acte.

## DÉPLOIEMENT-TEST

1. `git pull origin main`
2. `php artisan route:clear` (routes middleware)
3. `php artisan optimize:clear`
4. Tests manuels : destroy sans `lots.ecriture` → 403 ; avec permission + lot
   propre → OK ; PUT résa avec AF → 422 message chiffré ; fiche idem ; sans AF
   → OK ; acte → lot/resa sync ; relancer `tools/diag/diag_prix_lots_reservation_1161.php`

## LEÇON

Auth commercial = middleware `feature.permission` + assert contrôleur, pas
Policy/`authorize` (base Controller vide). Deux chemins d’écriture du même
champ exigent le **même** service de garde — sinon bypass.
