---
suivi: 1646
date: 2026-09-22
sujet: Bilan — détail des dépenses hors facture sous le poste + retrait pastilles Taxe/Concess.
chantier: Financier / Bilan
type: feature
statut: poussé
hash: aa5c539a
fichiers:
  - app/Services/BilanFinancierService.php
  - app/Services/ProgrammeDepenseManuelleService.php
  - resources/js/Pages/Bilan/Index.vue
  - tests/Unit/BilanDetailDepensesHorsFacture1646SourceTest.php
  - database/data/journal_mises_a_jour_post_2026_06_12.php
  - public/build/
  - docs/suivi/SUIVI_1646_bilan_detail_depenses_hors_facture.md
---

# SUIVI #1646 — détail des dépenses hors facture sous le poste

## PROMPT ENVOYÉ

Rendre dépliable tout poste portant au moins une dépense hors facture (réutiliser le
mécanisme [+]/[−] existant), une ligne enfant par dépense (date — fournisseur — HT dans
Facturé Payé + pastille HF, commentaire en title, pas de navigation), marchés puis dépenses
dans la même liste, SoftDeletes via `whereNull('d.deleted_at')` explicite, total = somme
du détail (même source). Retirer les pastilles vertes Taxe / Concess. de l’affichage du
bilan uniquement. Aucun calcul modifié, aucune migration, aucune route.

## FICHES LUES

- **#1638** — arbitrages Facturé Payé (pas « Facturé »), pastille HF, `whereNull('d.deleted_at')`
  déjà posé dans le Query Builder. Conservés tels quels.
- **#1644** — SoftDeletes + piège `DB::table` ; écran de saisie intouché. LEÇON : toute
  nouvelle requête détail doit porter le même filtre explicite.
- **#1139 / bilan historique** — mécanisme `expandedPostes` / `rowsForRubrique` / marchés →
  avenants : réutilisé, pas de second state.

## SYNTHÈSE

### Mécanisme de dépliage réutilisé (pas de second)

| Élément | Fichier:ligne |
|---|---|
| État `expandedPostes = ref({})` | `Bilan/Index.vue` ~50 |
| `hasDetails` / `isExpanded` / `toggleExpanded` | ~101–114 |
| Bouton [+]/[−] rendu | ~1099–1107 (`@click="toggleExpanded(data.id)"`) |
| Production enfants côté front | `rowsForRubrique` ~116–178 |
| Production marchés côté serveur | `BilanFinancierService::buildMarchesKpiProgramme` → `details_by_poste` injectés dans `buildRubriqueSection` |

`hasDetails` étendu : `hasMarches \|\| hasBudgets \|\| hasDepensesManuelles`. Un poste
sans marché mais avec HF a donc un [+].

### Structure des lignes de détail HF

Payload par poste : `depenses_manuelles_detail` (liste) + `montant_depenses_manuelles`
(= somme des `montant_ht` de cette liste, **même source**).

Ligne front : `__is_depense_manuelle`, libellé `↳ JJ/MM/AAAA — FOURNISSEUR — X €`, montant
dans Facturé Payé (+ Bilan réel), pastille HF ambre, `title` = commentaire, autres colonnes
« — ». Pas de lien / clic.

Ordre dans `rowsForRubrique` : marchés (et avenants) → enveloppes budgets → dépenses HF.

### SoftDeletes

`ProgrammeDepenseManuelleService::detailsHtParPosteBudgetaire()` :
`->whereNull('d.deleted_at')` (Query Builder). `totauxHtParPosteBudgetaire()` dérive de
cette méthode (plus de second SQL SUM).

### Pastilles de catégorie recensées

| Valeur affichée | Source | Fichier:ligne (avant) | Action |
|---|---|---|---|
| `Taxe` | `poste_budgetaire_types.est_taxe` via payload `est_taxe` | `Bilan/Index.vue` Tag `v-if="data.est_taxe"` | **Retiré affichage bilan** |
| `Concess.` | `poste_budgetaire_types.est_concessionnaire` via `est_concessionnaire` | idem Tag | **Retiré affichage bilan** |

Aucune autre pastille de ce type sur le bilan. Payload et colonnes DB inchangés.
Conservées ailleurs : `Admin/Nomenclature.vue` (édition + pastilles liste),
`Marches/Create.vue` (badge Concessionnaire métier distinct).

Aucun filtre / tri / regroupement du bilan ne dépendait de ces pastilles (regroupement
par `rubrique_bilan_id` uniquement).

### Closures relues

`detailsHtParPosteBudgetaire` : closure `join` sans capture externe (OK).
`totauxHtParPosteBudgetaire` : foreach PHP, pas de closure.
`buildRubriqueSection` : pas de nouvelle closure.

## DÉPLOIEMENT / TEST

1. OVH : `git pull origin main` puis `php artisan optimize:clear` + `php artisan journal:sync`.
2. LOUIS PASTEUR / PUBLICITE : [+] → 2 lignes HF (500 + 1 000), somme = pastille HF du poste.
3. Poste marchés + HF : marchés d’abord, HF ensuite.
4. Poste marchés seuls : inchangé.
5. Soft-delete une dépense → disparaît détail + total.
6. TAXES / CONCESSIONNAIRES : plus de pastilles vertes.
7. 8 colonnes de totaux identiques au centime.
8. Compte tiers (hors GS_ADMIN_ERP) : mêmes droits qu’avant.
9. 1366 / 1920 : pas d’ascenseur horizontal ajouté.

## LEÇON

- Réutiliser `expandedPostes` / `rowsForRubrique` : un second state aurait divergé.
- Une seule source pour total et détail (somme PHP des lignes) évite l’écart centime.
- `DB::table` + SoftDeletes : toujours `whereNull('d.deleted_at')` explicite.
- Pastilles Taxe/Concess. = bruit d’affichage quand la rubrique les porte déjà.
