---
suivi: 1487
date: 2026-09-08
sujet: Fix 1055 only_full_group_by bloc bancaire tableau de bord
chantier: comptabilite / synthese
type: fix
statut: poussé
hash: 8d474c4e
fichiers:
  - app/Services/Compta/ComptaTableauBordService.php
  - tests/Unit/ComptaTableauBord1478SourceTest.php
  - docs/suivi/SUIVI_1487_fix_1055_group_by_tableau_bord.md
---

# SUIVI #1487 — 500 prod `/factures/tableau-de-bord` (SQLSTATE 1055)

## PROMPT ENVOYÉ

500 depuis le #1483 : GROUP BY d'expressions (CASE / JSON_EXTRACT ocr_raw)
incompatible avec only_full_group_by MySQL 8.4. Restructurer en table
dérivée (ligne à ligne) + GROUP BY d'alias de colonnes. Pas de ANY_VALUE,
pas de désactivation du sql_mode. Ne pas toucher FactureGlobale* (#1485)
ni ComptabilisationController / DepotFactureController.

## BASE GIT

Prompt : HEAD attendu `615c603c` (fiche #1486), #1485 annoncé en cours.
Réel : `a6098cca` chore(suivi) hash fiche #1485 — origin/main à jour,
#1485 déjà poussé. Pas de rebase. Fichiers FactureGlobale* / Pages/Factures
non touchés.

MySQL agent : refusé (127.0.0.1:3306). Aucun chiffre inventé.
sqlite phpunit ne reproduit PAS only_full_group_by.

## FICHES LUES

- **#1483** (LEÇON) — une clé de groupement doit produire le libellé ;
  une fonction « à payer », deux consommateurs. Impact : conserver
  exactement le comportement (clé unifiée, TTC, avoirs, eligibleFacturesQuery).
  Le garde-fou « only_full_group_by » y était nommé mais lu comme
  « qualifier les colonnes » — c'est la cause de cette récidive.
- **#1482** (LEÇON) — grouper sur un id et afficher un autre COALESCE
  produit des jumeaux. Impact : ne pas re-grouper sur le label ; le
  label reste MAX() d'une colonne déjà calculée dans la dérivée.
- **#1485** — lot parallèle FactureGlobale* : ne pas toucher ces fichiers.

## SYNTHÈSE

### Cause

`banques()` faisait `GROUP BY` des expressions SQL (`compteKeySql`,
`estAvoirSql` CASE sur `ocr_raw`, nature, origine, bucket). MySQL 8.4
`only_full_group_by` exige une correspondance **littérale** SELECT ↔
GROUP BY. Répéter le CASE dans le GROUP BY ne suffit pas : l'expression
#3 (`est_avoir`) lit `depot_factures.ocr_raw`, colonne hors GROUP BY
→ SQLSTATE 1055 (prod 2026-09-08 16:07:52 et 16:07:53).

### Structure avant / après

AVANT : une requête, SELECT d'expressions + GROUP BY des mêmes
expressions (groupByRaw).

APRÈS :
1. Dérivée `banques_lignes` : une ligne par facture à payer, alias
   `compte_key`, `compte_label_source`, `est_avoir`, `nature`,
   `origine`, `bucket`, `montant_unitaire`. Aucun GROUP BY.
2. Externe : `GROUP BY compte_key, est_avoir, nature, origine, bucket`
   (noms de colonnes uniquement) + `COUNT(*)`, `SUM(montant_unitaire)`,
   `MAX(compte_label_source)`.
3. `LIMIT PHP_INT_MAX` sur la dérivée : empêche MySQL 8 `derived_merge`
   de réinjecter les CASE dans le GROUP BY externe (1055 de retour).

Pas de `ANY_VALUE()`. Pas de changement de `sql_mode`.

### Audit GROUP BY du service

Méthode | GROUP BY | Verdict
`banques()` / `banquesAgregatsQuery()` | expressions → alias de dérivée | **restructurée**
`banquesLignesDeriveeQuery()` | aucun | conforme (détail ligne)
`agregatDepots()` | aucun (COUNT/SUM/MIN globaux) | conforme
`pipelineAPayer()` | aucun | conforme
`countsBlocages()` | aucun (sous-requêtes COUNT scalaires) | conforme
`queryFournisseurInactif` / `queryRibManquant` / `queryDoublonNonControle` / `queryPosteSansCompte` / `queryEchecSage` | aucun | conforme
`echues()` agrégat | aucun | conforme
`echues()` top 5 | ORDER BY + LIMIT, pas GROUP BY | conforme

Hors service, dans `eligibleFacturesQuery` (whereHas `depotFacture`
latestOfMany) : `SELECT MAX(id), facture_id GROUP BY facture_id` —
colonnes du GROUP BY = colonnes du SELECT. Conforme. Non modifié.

### Comportement #1483 conservé

Clé unifiée JSON → fe_programme.intacct_id → E-04/512900 E-13/512901
→ `non_rattache`. Un seul « Non rattaché ». Label CASE + PHP « Compte {id} ».
À payer / échues / banque : TTC `COALESCE(net_a_payer_ttc, montant_ttc, depot.montant_ttc)`.
Pipeline amont : HT. Avoirs inclus + ligne séparée. Source
`eligibleFacturesQuery`, `saisie_historique = 0`, dernier dépôt `MAX(df2.id)`.

## DÉPLOIEMENT-TEST

1. git pull origin main
2. find app/ config/ database/ -name "*.php" -exec touch {} +
3. php artisan optimize:clear && php artisan view:clear
4. Ouvrir `/factures/tableau-de-bord` (GS_COMPTABILITE) : plus de 500.
   Une seule carte « Non rattaché ». Pied = carte À payer. TTC.

Pas de migrate. Pas de journal:sync. JAMAIS npm/composer/optimize/config:cache sur OVH.

Preuve 1055 : coller la requête MySQL de la synthèse (Robin), sql_mode
avec only_full_group_by. sqlite phpunit ne prouve rien ici.

## LEÇON

`only_full_group_by` n'est pas « qualifier les colonnes ». C'est
« grouper sur des noms de colonnes, pas sur des expressions ». Un CASE
recopié dans le GROUP BY n'est pas une colonne. MySQL 8 peut en plus
fusionner la dérivée (`derived_merge`) et ramener le 1055 : limiter
la sous-requête pour forcer la matérialisation.
