---
suivi: 1439
date: 2026-09-04
sujet: Avis d'imposition — consultation de l'historique par ligne
chantier: foncier
type: feature
statut: poussé
hash: 7f15251b
fichiers:
  - app/Http/Controllers/Foncier/AvisImpositionController.php
  - resources/js/Pages/Foncier/Patrimoine/AvisImposition/Index.vue
  - routes/web.php
  - tests/Unit/AvisImposition1439SourceTest.php
  - database/data/journal_mises_a_jour_post_2026_06_12.php
  - public/build/
---

# SUIVI #1439 — Historique consultable par ligne (lecture seule)

## PROMPT ENVOYÉ

Les modifications sont déjà journalisées dans `avis_imposition_historique` (#1420)
mais rien ne les affiche. Icône par ligne (pas d’écran global), mêmes groupes que
la suppression #1438 (`GROUPES_SUPPRESSION`, GS_COMPTABILITE exclu), lecture seule,
pas de restauration. Contrôle serveur via `m365GroupNames()` brut. Pas de N+1 au
chargement du tableau. Distinguer / masquer `import_excel`. Impersonation visible.
Ne pas défaire #1435 / #1437 / #1438. Pas de migration, pas de changement de colonnes.

## FICHES LUES

- `SUIVI_1438` LEÇON : `min-w-0`, `overflow-x: scroll`, `max-height: calc(100dvh - 18rem)`,
  jamais `overflow-x-hidden`. Actions `flex-nowrap` (hauteur compacte).
  `GROUPES_SUPPRESSION` + `m365GroupNames()` brut. Réutilisé tel quel pour l’historique.
- `SUIVI_1435` LEÇON : contrat PATCH `{champ, valeur}` intact.
- `SUIVI_1437` : colonne 2026 + case TLVH — ne pas revert.
- `SUIVI_1420` : table historique, actions `creation` / `modifie` / `suppression`,
  `impersonator_id`. Pas d’UI de consultation (ce lot).
- `SUIVI_1422` : actions document `document_ajoute` / `document_supprime`.
- `SUIVI_1423` : import CLI, `action=import_excel`, `user_id` NULL.
- `SUIVI_1271` : jamais masquer le débordement X.

## SYNTHÈSE

### Prérequis

`origin/main` contient #1438 (`db6d1853` / fiche `c12478a2`). Confirmé ancêtre
avant rebase / commit.

### Étape 0

0.1 Base locale `127.0.0.1:3306` inaccessible (`SQLSTATE[HY000] [2002]`). Schéma
non reconstruit depuis les migrations. Requête à exécuter en prod :

`SHOW COLUMNS FROM avis_imposition_historique;`

0.2 Actions dans le code :
- contrôleur `logHistorique` : `creation`, `modifie`, `suppression`,
  `document_ajoute`, `document_supprime`
- import `AvisImpositionCsvImporter::ACTION_HISTORIQUE` : `import_excel`

0.3 Volumétrie — requête à exécuter en prod (base locale indisponible) :

`SELECT action, COUNT(*) AS n FROM avis_imposition_historique GROUP BY action ORDER BY n DESC;`

### Choix import

Masquées par défaut (case « Afficher l’import initial »). Rendu synthétique :
« Ligne créée par l’import du JJ/MM/AAAA » — pas le dump champ par champ
(311 lignes importées, chaque diff contient tous les champs).

### Impersonation

`user_id` = compte agi ; `impersonator_id` = compte réel (#1420). Affichage :
« Sandrine PORTALES, via Robin PORTALES ». Auteur nul : « Import » si
`import_excel`, sinon « Système ».

### Serveur

`GET /patrimoine/avis-imposition/{avisImposition}/historique` — une ligne,
`orderByDesc created_at/id`, limite 100 + `has_more`. Eager-load `user` et
`impersonator` (id, name). 403 JSON si hors `GROUPES_SUPPRESSION`.
`peutConsulterHistorique()` délègue à `peutSupprimer()` (pas de 2e constante).
Index : `with(['documents'])` seulement — pas d’historique N+1.

### Front

Icône `pi-history` dans `.avis-imposition-actions` `flex-nowrap` (`!h-7 !w-7`),
`v-if="peut_historique"` (absente, pas grisée). Dialog lecture seule. Libellés
français côté serveur. Pas de restauration. Pas de `overflow-x-hidden`.

Comptes refus : Sabrina BARTOLI / Rudy BENOR (`GS_COMPTABILITE`).
Sandrine PORTALES (`GS_GEMME_PDG`) : icône + consultation.

## DÉPLOIEMENT-TEST

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

Pas de migration. Requêtes Étape 0 ci-dessus.

## LEÇON

Un historique journalisé n’existe pas pour l’utilisateur tant qu’il n’y a pas
d’icône par ligne + route filtrée. Réutiliser `GROUPES_SUPPRESSION` évite une
2e liste qui dériverait. `import_excel` noie les diffs humains : masquer par
défaut + synthèse, jamais le dump. `m365GroupNames()` brut : `isAdminERP()` +
`Gate::before` court-circuitent `hasAnyM365Group` / Gates. Robin (seul
`GS_ADMIN_ERP`) ne peut pas tester le 403 — Sabrina / Rudy (`GS_COMPTABILITE`).
Limite d’affichage : 100 dernières entrées par ligne.
