---
suivi: 1441
date: 2026-09-04
sujet: Avis d'imposition — correctif récap (montants et agrégation)
chantier: foncier
type: fix
statut: poussé
hash: b00b2191
fichiers:
  - app/Services/Foncier/AvisImpositionRecapService.php
  - resources/js/Pages/Foncier/Patrimoine/AvisImposition/Index.vue
  - tests/Unit/AvisImposition1441SourceTest.php
  - database/data/journal_mises_a_jour_post_2026_06_12.php
  - public/build/
---

# SUIVI #1441 — Récap avis d'imposition : montants et agrégation

## PROMPT ENVOYÉ

Le récap #1440 en prod affiche 0,00 € partout, HECTARE/HOLDING sans départements,
« Sans structure (TLVH) » répété, aucune société. Établir la cause par le code
avant de corriger (leçon juridique #1380–#1384).

## FICHES LUES

- `SUIVI_1440` — RecapService, GROUP BY expressions, assembler PHP. Tests unitaires
  sur des **tableaux**, pas des modèles Eloquent.
- `SUIVI_1423` — 311 lignes, sommes 2025=359845 / 2024=364727.
- `SUIVI_1437` / `1438` / `1439` / `1435` — ne pas défaire.

## ÉTAPE 0 — CAUSE

### 0.2 GROUP BY
La requête ne groupe **pas** sur `id`. Elle groupe sur trois expressions CASE
(bloc / département pour HECTARE|HOLDING / structure sinon). SQL du #1440 :

SELECT
  CASE WHEN UPPER(TRIM(COALESCE(structure,''))) = 'HECTARE HOLDING' THEN 'holding'
       WHEN UPPER(TRIM(COALESCE(structure,''))) = 'HECTARE' THEN 'hectare'
       ELSE 'autres' END AS bloc,
  CASE WHEN UPPER(TRIM(COALESCE(structure,''))) IN ('HECTARE','HECTARE HOLDING')
       THEN TRIM(COALESCE(departement,'')) ELSE '' END AS departement,
  CASE WHEN UPPER(TRIM(COALESCE(structure,''))) IN ('HECTARE','HECTARE HOLDING')
       THEN UPPER(TRIM(structure)) ELSE TRIM(COALESCE(structure,'')) END AS structure_cle,
  SUM(total_avis_2021) AS m2021, … m2026, COUNT(*) AS nb
FROM avis_impositions
WHERE deleted_at IS NULL
GROUP BY <les trois mêmes CASE>

Pas d’identifiant de ligne. Un GROUP BY incomplet `only_full_group_by` aurait
fait un **500**, pas une fenêtre à zéros. La requête a réussi.

### 0.3 / 0.4 / 0.5 — cause unique, ÉTABLIE PAR LE CODE

`lignesAgregees()` fait `AvisImposition::query()->…->get()->all()`.
`get()` hydrate des **modèles** Eloquent.

`assembler()` faisait `$arr = is_array($row) ? $row : (array) $row`.

`(array)` sur un Model PHP n’expose pas les attributs sous `bloc` / `m2025` :
les clés deviennent du type `"\0*\0attributes"`. Donc :
- `$arr['bloc']` absent → `''` → tout va dans AUTRES ;
- `$arr['structure_cle']` absent → `''` → libellé « Sans structure (TLVH) » ;
- `$arr['m2025']` absent → `?? 0` → 0,00 €.

Une ligne d’agrégat SQL (département HECTARE, ENVOL, etc.) devient une ligne
« Sans structure » à 0 €. HECTARE et HOLDING restent vides (aucun `bloc` lu).
Les tests #1440 passaient des **arrays** à `assembler()` : verts, et faux en prod.

Les colonnes sommées (`total_avis_2021` … `2026`) sont les bonnes. Pas de nom
dynamique erroné, pas de COALESCE qui force zéro.

## SYNTHÈSE

- `toBase()->get()` : stdClass, plus de Model.
- `attributs()` : array / `getAttributes()` / `get_object_vars`.
- Écran : masquer les lignes dont le montant de l’année choisie est < 0,005 €
  (sous-totaux et total général inchangés, calculés sur tout).

## DÉPLOIEMENT-TEST

```
git pull origin main
php artisan optimize:clear
php artisan view:clear
```

Pas de migrate. Requête de vérif : voir livrable.

## LEÇON

`get()` Eloquent + `(array) $model` pour lire des alias SELECT = attributs
invisibles, zéros partout, grouping « perdu » à l’affichage alors que SQL
agrégeait. Tester `assembler()` avec un Model (`setRawAttributes`) ou stdClass,
pas seulement un array. Un test vert sur le mauvais type d’entrée ne prouve rien.
