---
suivi: 1037
date: 2026-07-29
sujet: Diagnostic regression filtre Facturé NV1 après #1036
chantier: budgets-programme
type: diagnostic
statut: poussé
hash: 65d71a61
fichiers:
  - tools/diag/diag_bilan_1037.php
  - docs/suivi/SUIVI_1036_bilan_filtre_nv1_source_unique.md
  - docs/suivi/SUIVI_1035_bilan_engage_hors_marche.md
  - tools/diag/diag_bilan_1035.php
  - app/Models/DepotFacture.php
---

## PROMPT ENVOYÉ

SUIVI #1037 — Phase 1 diagnostic uniquement. INTERDICTION ABSOLUE de modifier du code applicatif.

Objectif : analyser pourquoi le #1036 (source unique NV1) sous-évalue la colonne « Facturé », malgré une règle métier censée inclure toute facture dont NV1 est validée.

Livrable demandé :
- `tools/diag/diag_bilan_1037.php` (lecture seule) : schéma + populations exactes OLD (#1035) vs NEW (#1036), regroupement par cause, recherche focus facture test 100€, échantillon PHP vs SQL pour divergences.
- `docs/suivi/SUIVI_1037_diag_regression_filtre_nv1.md` : synthèse A→F (A/B = code, C/D/E chiffrés issus prod via script).

## SYNTHÈSE

### A. Schéma et valeurs réelles de `workflow_validations` (dépend prod)

Le script `tools/diag/diag_bilan_1037.php` exécute en prod :
1. `SHOW COLUMNS FROM workflow_validations` (A1).
2. `SELECT niveau, statut, COUNT(*) ... GROUP BY niveau, statut` (A2).
3. Comptage du marqueur "automatique" via la constante code : `WorkflowValidationStepPresenter::COMMENTAIRE_VALIDATION_AUTOMATIQUE_DEPOSANT` (A3) si la colonne `commentaire` existe.
4. Comptage des factures ayant au moins une ligne `workflow_validations` avec `niveau='nv1'` ET `statut='valide'` (A4).

Les valeurs chiffrées exactes seront celles affichées dans la sortie runtime prod (script).

### B. Mécanisme de validation NV1 automatique (dépend code, réponse ici)

**B1 — où se trouve l’affichage « (automatique) » sur la frise :**
- Déterminé par `app/Support/WorkflowValidationStepPresenter.php` :
  - `WorkflowValidationStepPresenter::estValidationAutomatique()` :
    - renvoie `true` si `trim((string) ($workflow->commentaire ?? ''))` vaut
      `WorkflowValidationStepPresenter::COMMENTAIRE_VALIDATION_AUTOMATIQUE_DEPOSANT`.
- Consommé par :
  - `WorkflowValidationStepPresenter::metaPourEtape()` qui expose `actor_automatique`.
- Injecté dans le payload frise par `app/Http/Controllers/FactureController.php` :
  - la meta pour l’étape `nv1` est construite via `WorkflowValidationStepPresenter::metaPourEtape($facture, 'nv1', ...)`.

**B2 — ce chemin écrit-il une ligne `workflow_validations` NV1/valide ?**
- Pour la validation NV1 automatique (cas déposant = validateur NV1), le code qui crée explicitement la preuve base est :
  - `app/Services/FactureWorkflowApresNv1Service.php`
    - `creerWorkflowsApresDepotAutoNv1(...)`
      - crée une ligne `WorkflowValidation::query()->create([...])` avec :
        - `niveau` = `'nv1'`
        - `statut` = `'valide'`
        - `commentaire` = `WorkflowValidationStepPresenter::COMMENTAIRE_VALIDATION_AUTOMATIQUE_DEPOSANT`
        - et `date_validation`/`date_assignation` renseignés.

Conclusion code-à-code (sans prod) :
- Le marqueur « automatique » ne vient pas d’une simple donnée dérivée : il est codé sur `workflow_validations.commentaire`.
- Si une facture affiche NV1 validée comme automatique, la base est censée contenir une ligne `workflow_validations` nv1/valide dont le `commentaire` correspond.
- La régression #1036 mesurée peut donc venir de :
  - des factures déposées avant le fix de stockage (ancien historique sans ligne nv1/valide),
  - ou d’un cas où la frise est “verte” via un calcul de frise qui ne dépend pas strictement d’une ligne nv1/valide au format attendu (à prouver via script C + inspection des `nv1_statuts_distinct`).

**B3 — autres chemins NV1 :**
Les endroits où le code crée / modifie `workflow_validations` pour du `niveau='nv1'` vers `statut='valide'` (sans exhaustivité “tous”, mais chemins qui touchent explicitement la transition) :
1. **Validation manuelle NV1** : `app/Http/Controllers/ValidationController.php`
   - `workflow->update([ 'statut' => 'valide', 'niveau' => 'nv1', ... ])` pour le `workflow` en attente.
2. **Auto NV1** (déposant = validateur NV1) : `app/Services/FactureWorkflowApresNv1Service.php`
   - `creerWorkflowsApresDepotAutoNv1()` crée directement `nv1/valide` + commentaire automatique.
3. **NV1 "déjà validée au dépôt"** :
   - `app/Services/DepotFactureValiderDejaValideService.php`
     - `validerNv1EnAttente(...)` :
       - met le `pending` `nv1/en_attente` à `statut='valide'` et renseigne `commentaire` (constante “déjà validée”).

Les endroits où on crée seulement `nv1/en_attente` (donc pas une preuve de NV1 validée) :
- `app/Http/Controllers/FactureController.php` (création d’une facture -> `nv1` en attente)
- `app/Services/DepotFactureEngagerWorkflowService.php` (mise en place de workflow)
- `app/Http/Controllers/DepotFactureController.php` (cas d’envoi/engagement selon branches)

### C. Population exacte des régressions OLD (#1035) vs NEW (#1036) (dépend prod via script)

Le script `tools/diag/diag_bilan_1037.php` produit :
1. Les ensembles OLD/New et leurs tailles :
   - OLD (#1035) = `depot_factures.deleted_at IS NULL` ET `depot_factures.statut NOT IN (rejete, archive)` (définition “AVANT” en §2 dans `tools/diag/diag_bilan_1035.php`)
   - NEW = `DepotFacture::sqlConditionCompteDansFactureApresNv1Valide('d')`
2. La liste détaillée “dépôts perdus” (OLD inclus / NEW exclus) avec :
   - `depot#id`, `programme_id`, `programmes.libelle`,
   - `facture_id`, `depot.statut`, `factures.statut`, `factures.source`,
   - `montant_ht`, `saisie_historique`,
   - `marche_id` effectif et `programme_budget_id` effectif,
   - présence + statuts distincts des lignes `workflow_validations` niveau nv1.
3. Le regroupement par causes (C2) avec `nb` et ΣHT.
4. Le symétrique “amélioration attendue” (OLD exclus / NEW inclus) + causes.
5. Un focus sur `montant_ht=100.00` sur `programme_id=1` si présent, et indique `old_included/new_included`.

Les chiffres exacts (-71 523,29 € ; -222 157,26 € ; etc.) doivent être recopiés de la sortie du script en prod (elles ne sont pas vérifiées localement).

### D. Défaut du script de bouclage #1035 (méthode, code)

Réponses attendues :
1. §1 n’a pas changé car il ne mesure pas l’inclusion NV1 validée. Il s’appuie sur des agrégats de dépôts liés à l’axe marché/enveloppe via statuts `paye/valide` (donc effet NV1 sur `en_validation_nv2` exclu sous #1036 ne peut pas modifier §1).
2. Donc `Aucune anomalie détectée` sur §1 ne prouve rien sur la règle NV1 de la colonne “Facturé”.
3. §1 n’est pas un test de la même règle que celle affichée dans la colonne Facturé.

Le script #1037 rappelle explicitement cette limite en sortie (section D).

### E. Divergence PHP ↔ SQL (diagnostic de désaccords, dépend prod)

Le #1036 vise la source unique :
- SQL canonique : `DepotFacture::sqlConditionCompteDansFactureApresNv1Valide()`
- PHP objet : `DepotFacture::compteDansFactureApresNv1Valide()` (même logique : garde-fous + branche historique + preuve NV1 via workflow existe).

Le script #1037 exécute E2 :
- échantillon >= 50 dépôts (LIMIT 80) variés,
- calcule un booléen SQL (via expression) et un booléen PHP (via méthode),
- liste les désaccords (si désaccord : objectif source unique non atteint).

Les résultats (nb désaccords) dépendent de la prod.

### F. Recommandations de fix (sans coder)

Recommandations dépendant des causes réellement observées en C2 :
1. Si une grosse part des “OLD inclus / NEW exclus” provient de `facture_id=NULL` :
   - option : élargir la preuve NV1 en SQL pour un cas où la workflow existe ailleurs que via `facture_id` (si le schéma le permet), ou exiger un backfill minimal “proof NV1 manquante”.
2. Si la part dominante est “aucun workflow nv1/valide” alors que la frise/écran indique NV1 validée :
   - option privilégiée : un backfill ciblé de `workflow_validations` manquants (preuve manquante), plutôt qu’un fallback sur statuts dépôt (qui réintroduirait un filtre parallèle).
3. Si la part dominante est “workflow nv1 existe mais statut != valide” :
   - option : identifier le statut réel de la validation NV1 automatique/particulière dans `workflow_validations` (A2/A3) et adapter l’EXISTS canonique en conservant une seule preuve.

Dans tous les cas : décider après lecture de la sortie script #1037 (sections A, C et E).

## DÉPLOIEMENT-TEST

1. `git pull origin main` sur OVH (racine projet)
2. (Aucune migration, aucune écriture)
3. Exécuter :
   - `php tools/diag/diag_bilan_1037.php`
4. Le diagnostic s’appuie sur les sorties :
   - A1-A4 (workflow_validations + factures)
   - C1-C3 (populations exactes OLD vs NEW)
   - D (limites bouclage)
   - E2 (désaccords PHP vs SQL)

## LEÇON

Une régression de “Facturé” après un changement de preuve (NV1 via EXISTS workflow_validations) doit être expliquée par une preuve base concrète :
- soit la ligne `workflow_validations` attendue n’existe pas (historique/auto non stocké),
- soit elle existe mais avec un contenu/clé différent de la condition SQL,
- soit la frise “verte” se calcule à partir d’un autre indicateur que la présence de `nv1/valide`.

Le script #1037 est conçu pour trancher ces hypothèses sans modifier de code.

