---
suivi: 1461
date: 2026-09-04
sujet: Correction validateur_id double-encodé (JSON_QUOTE) + normalisation
chantier: depot_factures
type: fix
statut: poussé
hash: 6bd137140078778470368af7e413da8349a379ac
fichiers:
  - app/Console/Commands/BackfillDepotFactureValidateurCommand.php
  - app/Console/Commands/NormalizeDepotFactureValidateurIdCommand.php
  - app/Services/FactureValidateurNv1AssignmentService.php
  - app/Support/FactureHistoriquePresenter.php
  - tests/Unit/FixValidateurDoubleEncode1461SourceTest.php
  - tests/Unit/NormalizeDepotFactureValidateurId1461Test.php
  - database/data/journal_mises_a_jour_post_2026_06_12.php
---

# SUIVI #1461 — Fix validateur double-encodé (PHASE 2)

## PROMPT ENVOYÉ

PHASE 2 correction suite #1459 : retirer `JSON_QUOTE` du backfill #1453
(branche MySQL), commande `depot-factures:normalize-validateur-id`, trim
défensif `resolveUser`. 14 dépôts mesurés en prod (Robin).

## FICHES LUES

- `#1459` LEÇON : `JSON_SET` + `JSON_QUOTE` = double cite ; sqlite tests aveugles ;
  cast `ocr_raw => array` à ne pas toucher ; ne jamais réécrire tout `ocr_raw`.
- `#1453` : signature backfill inchangée ; historique `system: true`.
- `#1458` : 18 tâches existantes ; backfill idempotent via `idempotency_key`.
- `#1450` : filtre a_completer peuplé mais échoue sur valeurs quotées.
- `#1460` : déjà sur main (`fe79ca8a`) — WidgetDataService, pas de conflit fichiers.

Base : `origin/main` = `fe79ca8a` (chore #1460), après #1459 `66addfa2`.
Rebase signalé : base attendue était `66addfa2`, main a avancé avec #1460.

## SYNTHÈSE

### SQL MySQL backfill — AVANT / APRÈS

AVANT (#1453 fautif) :
`JSON_QUOTE(IF(u.azure_id …, u.azure_id, CAST(u.id AS CHAR)))`

APRÈS (#1461) :
`IF(u.azure_id IS NOT NULL AND TRIM(u.azure_id) <> '', u.azure_id, CAST(u.id AS CHAR))`

Branche sqlite inchangée. Signature `depot-factures:backfill-validateur {--apply}` inchangée.

### Commande normalisation

Signature littérale :
`depot-factures:normalize-validateur-id {--apply : …}`

Dry-run attendu : 14 lignes (ids 964…1633), valeur avant avec guillemets,
après sans, user résolu (Magali / Sabrina / Isabelle / Sandrine / Manon).

Écriture : `JSON_SET(ocr_raw, '$.saisie.validateur_id', TRIM(BOTH CHAR(34) FROM JSON_UNQUOTE(...)))`
avec garde LEFT/RIGHT = CHAR(34). Historique `normalize_validateur_id_quotes`.

### Trim défensif

`FactureValidateurNv1AssignmentService::resolveUser` — strip une paire `"` après
trim. Signature 3 params (`$allowAnnuaire = true`) préservée.
`whereSaisieValidateurEstUser` (#1450) non retouché (nettoyage données suffit).

## DÉPLOIEMENT-TEST

Voir séquence OVH dans le livrable (touch OPcache + dry-run + apply +
`taches:backfill-completer-saisie --apply`). Pas de migration, pas de rebuild front.

## LEÇON

`JSON_SET` MySQL cite déjà une string SQL : `JSON_QUOTE` autour = valeur
stockée avec guillemets littéraux. Les tests sqlite sans `json_quote` ne
détectent pas le défaut MySQL — exiger un test source anti-`JSON_QUOTE`.
Namespace artisan = token avant le premier `:` (`depot-factures`, pas `depot`).
