---
suivi: 1256
date: 2026-08-07
sujet: Sélection de lots par cases à cocher pour les trois exports AO
chantier: appels-offres / exports
type: feature
statut: poussé
hash: 9a1f44ce
fichiers:
  - app/Http/Controllers/AppelOffreController.php
  - app/Services/AoLotsTableExportService.php
  - app/Services/AoDevisZipExportService.php
  - routes/web.php
  - resources/js/Pages/AppelsOffres/Show.vue
  - resources/views/pdfs/ao-lots-table.blade.php
  - tools/diag/mesure_export_zip_devis_ao_1255.php
  - database/data/journal_mises_a_jour_post_2026_06_12.php
  - public/build/
  - docs/suivi/SUIVI_1256_export_selection_lots_ao.md
---

# SUIVI #1256 — Sélection lots exports AO

## PROMPT ENVOYÉ

Cases à cocher sur les lots d'un appel d'offres ; les trois exports
(Excel, PDF, ZIP devis) ne portent que sur la sélection. Compteur visible,
tout coché par défaut, sélection transient, validation serveur des
identifiants (refus 422 hors périmètre), totaux sur la sélection,
transmission POST, pas de migration.

## FICHES LUES

- `#1255` — export ZIP devis ; `_rapport.txt` ; logique conformes /
  dernier devis ; page `AppelsOffres/Show.vue` ; leçon échec muet (#1108).
  → réutiliser le même filtrage, étendre `prepare($ao, $lotIds)` et
  enrichir le rapport en sélection partielle.
- `#1226` — mention Show.vue AO (uploads) ; hors périmètre fonctionnel.

Anti-collision : aucun autre lot en cours sur `AppelsOffres/Show.vue`
après `#1255` sur `origin/main`. Rebasé avant build.

## SYNTHÈSE

### (a) Sélection par défaut

**Tout coché.** Préserve le comportement pré-#1256 si l'utilisateur ne
touche à rien ; un export vide serait une régression perçue.

### (b) Totaux Excel / PDF

**Sur la sélection.** Label `Totaux (N lots sur M)` + mention
`N lots sur M` en en-tête PDF / ligne Excel **uniquement** si sélection
partielle. Sélection complète → document identique à #1255 (pas de
mention). Coût d'implémentation faible : `buildExportData` filtre déjà
les lignes avant agrégation.

### (c) Transmission

**POST** `lot_ids[]` + CSRF via formulaire dynamique. Justification :
liste potentiellement longue ; query string risquée. Le téléchargement
reste déclenché côté navigateur (`Content-Disposition: attachment`) —
même effet qu'un lien GET.

### (d) Validation serveur

`validatedLotIdsForExport` : `lot_ids` required min:1 ; chaque id doit
appartenir à l'AO courant ; sinon **422** global (pas d'export partiel
silencieux). Gardes existantes conservées (`assertAoScope`,
`ao.lecture` sur ZIP). Pas de `GS_*` en dur.

## LEÇON

Un export déclenché par lien GET ne passe pas naturellement une liste
longue d'ids : POST + form + CSRF conserve le téléchargement fichier
tout en évitant les URL trop longues. Refus 422 hors périmètre > filtre
silencieux (même esprit que #1108).

## DÉPLOIEMENT

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