---
suivi: 1352
date: 2026-08-14
sujet: widget_depot_facture
chantier: dashboard
type: feat
statut: poussé
hash: 6e5fe9ee
fichiers:
  - config/dashboard_widgets.php
  - app/Services/Dashboard/WidgetDataService.php
  - app/Services/Dashboard/DashboardDefaultLayoutService.php
  - resources/js/Components/Dashboard/DepotFactureWidgetUpload.vue
  - resources/js/Components/Dashboard/WidgetBody.vue
  - database/data/journal_mises_a_jour_post_2026_06_12.php
  - public/build/
  - docs/suivi/SUIVI_1352_widget_depot_facture.md
---

## PROMPT ENVOYÉ

SUIVI #1352 — widget dashboard dépôt de factures (dropzone + options de
masse). Après dépôt → redirect `/depot-factures` avec PDF prêts. Réutiliser
upload existant. Options transmises. Opt-in catalogue, pas défaut. Droits
serveur. Ne pas lancer prune. Ne pas toucher SaisieForm / AppShell /
ObjectSubNavBar / useFormatters / marchés / synthèse / SIWI.

## FICHES LUES

- `#1331` LEÇON : widget lisible ; catalogue ; orphelins ignorés ; prune
  explicite seulement — **prune NON lancé**.
- `#1333` LEÇON : hauteur catalogue ; pas de scroll interne pour listes.
- `#1329` LEÇON : URLs en dur (Ziggy absent) ; HTTP 500 + Log sur panne.
- `#1327` : `position_x = 0` seed — dette connue, **non corrigée** ici.
- `#1351` : ObjectSubNavBar / AppShell — **non touchés**.

## SYNTHÈSE

### Décisions Robin

| # | Choix | Mise en œuvre |
|---|---|---|
| 1 A | Redirect après dépôt | `router.visit('/depot-factures?depot={uuid}')` |
| 2 C | Options masse repliables | UI alignée Index ; valeurs postées en `masse_*` à l’upload (persistées via `mergeMasseSaisieDepuisUpload`) |
| 3 | Opt-in | Catalogue oui ; `resolveDefaultWidgetKeys` = toujours `['factures_a_valider']` seul |

### Upload réutilisé (cité, non réécrit)

- `POST /depot-factures` → `DepotFactureController::store` (~L168)
- `POST /depot-factures/outlook` → `storeFromOutlook` (~L222)
- Limites serveur : `fichiers` max:10, `fichiers.*` max:20480 (Ko) = 20 Mo
- Options masse : `resolveMasseOptionsDepuisUpload` + `mergeMasseSaisieDepuisUpload` (~L3315 / L3352)

`DepotFactureController` **non modifié**. `DepotFactureSaisieForm.vue` **non modifié**.

### Mapping sélecteurs (options de masse)

| Libellé affiché | Valeur postée | Libellé back / usage |
|---|---|---|
| Société (entité) — `{intacct_id} — {nom}` | `masse_entite_id` (int) | `entites.id` → saisie `entite_id` |
| Valideur — displayName Graph | `masse_validateur_id` (+ `masse_validateur_email`) | Graph id / email → saisie `validateur_id` / `validateur_email` |
| Type (destination) — PROGRAMMES / FRAIS GÉNÉRAUX | `masse_destination` | `PROGRAMMES` \| `FRAIS_GENERAUX` → saisie `destination` |

**Source options** : `WidgetDataService::depotFacture` — `Entite::orderBy('nom')` et
`GraphApiService::getUtilisateursGraphOptionsCached()` (même logique que
`DepotFactureController::index` / `resolveUtilisateursGraphOptions`). Destination :
constantes alignées `MASSE_DESTINATION_OPTIONS` d’Index.vue.

### Droits

Page dépôt : tout utilisateur authentifié (`store` L171 `abort_unless User`).
Widget : même endpoint → même garde serveur. Catalogue ouvert
(`required_permissions => []`). UI masque si `can_deposit === false` (réserve).
`hasAnyM365Group` / Gate::before admin **non utilisés** pour ce droit (aligné page).

### Widgets avant / après

| Clé | Avant | Après |
|---|---|---|
| `factures_a_valider` | catalogue + défaut | inchangé |
| `depot_facture` | absent | catalogue seulement (pas défaut) |
| 10 clés obsolete | orphelines conservées | inchangé — **prune non lancé** |

`position_x = 0` (DashboardDefaultLayoutService) : signalé, non réparé. Ajout
manuel via perso place le widget à côté du dernier (`addWidget` / Index.vue).

### Hors périmètre

SaisieForm, AppShell, useFormatters, ObjectSubNavBar, marchés, synthèse #1350,
documentPdf, siwi_import, commune, #1344 : non touchés. Pas de migration.

## DÉPLOIEMENT-TEST

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

Pas de migrate. Pas de route:clear (aucune route nouvelle — réutilise
`depot-factures.store` / `store-outlook` / `api/dashboard/widget/{key}/data`).

Tests :
1. Dashboard édition → ajouter « Dépôt de factures » (absent des dashboards
   existants tant qu’on ne l’ajoute pas).
2. PDF ×1 → redirect `/depot-factures?depot=…` sélectionné, masse appliquée.
3. PDF ×2 → deux dépôts.
4. 11 fichiers / >20 Mo → refus (client + validation Laravel).
5. Compte non-admin authentifié : upload OK (même droit que la page).
6. Retirer le widget → layout sans destruction de données dépôt.
7. Confirmer `dashboard:prune-obsolete-widgets` **non** exécuté.

## LEÇON

Un widget « mise en file » doit appeler l’endpoint métier existant et
rediriger — jamais dupliquer la saisie. Les options de masse se transmettent
en les postant à l’upload (persistées serveur), pas en query UI. Ne jamais
ajouter un widget au seed défaut si l’exigence est opt-in. Ne jamais lancer
le prune des orphelines sans demande explicite.
