---
suivi: 1188
date: 2026-08-04
sujet: Diagnostic — saisie facture (blocage, auto-NV1, enseigne, retenues)
chantier: depot-factures
type: diagnostic
statut: poussé
hash: 0012a944
fichiers:
  - tools/diag/diag_saisie_facture_1188.php
  - docs/suivi/SUIVI_1188_diag_saisie_facture.md
---

## PROMPT ENVOYÉ

SUIVI #1188 PHASE 1 DIAG — 4 sujets saisie facture (Bloquer, flux envoi NV1,
filtre enseigne, dette #1026 retenues). Script lecture seule + fiche.
Aucun code métier. HEAD annoncé b01949cc ; HEAD réel au push = voir hash.

## FICHES LUES

- `SUIVI_1026_diag_rattachement_logique_a.md` — cartographie
  `DepotFactures/DepotFactureSaisieForm.vue` (~9100 L) ; retenues hors marché
  **non masquées** ; cases `sans_marche` / `rattachement_poste_direct` +
  watchers ; poste non exigé sur sans_marche. Approche : mesurer si dette
  toujours vivante avant de rouvrir ce fichier.
- `SUIVI_1184` — eager load enseigne partiel (`modules_actifs`) → cartes
  invisibles ; piège à ne pas reproduire.
- `SUIVI_1059` (via code) — filtre enseigne Programmes/Index (préférence).

Fichier réel : `resources/js/Pages/DepotFactures/DepotFactureSaisieForm.vue`
(pas `Components/Commercialisation/…`).

---

## Script

`php tools/diag/diag_saisie_facture_1188.php` (CWD racine, require relatif).

**Coller ici la sortie A–D produite en prod.**

---

## SUJET A — Bouton « Bloquer » absent de la saisie

### A1 — Le mécanisme existe

Oui. Colonnes `workflow_validations` : `bloque`, `bloque_at`, `bloque_motif`,
`bloque_par_user_id` (migration 2026_06_04).

| Couche | Emplacement |
|--------|-------------|
| Backend | `FactureController::processBloquer` / `processDebloquer` |
| Routes | `POST …/bloquer` et `…/debloquer` (fiche + programme) |
| Permission | `can_block === can_validate` de l’étape en attente |
| UI Bloquer | **`DepotFactures/Show.vue`** (barre haut) + **`Factures/Show.vue`** |
| UI absente | **`DepotFactureSaisieForm.vue`** (0 hit) ; **`DepotFactures/Index.vue`** monte le formulaire **sans** la barre Show |

### A2 — Journal « #249 » / #879 dit-il vrai ?

L’entrée journal du **21/07 (#879)** promet :
« valider/rejeter/**bloquer**/réattribuer … depuis l’écran de saisie ».

Git : `824898cc` a posé Bloquer sur **Show.vue** (shell), **jamais** dans le
formulaire. Le journal est **partiellement vrai** pour le chemin Show, **faux**
pour le chemin Index (modal saisie = Envoyer / Enregistrer seulement).

Ce n’est pas une disparition type #1156 : Bloquer n’a jamais vécu dans
SaisieForm. Lahouari voit probablement Index → saisie, d’où l’absence.

### A3 — Volumes (script prod)

À coller : nb WV `bloque=1`, par niveau, par bloqueur, motif vide/plein,
min/max `bloque_at`. Tritons « 1 bloquée » = au moins un cas vivant.

### A4 — Effet / déblocage

Blocage = étape WV en attente marquée bloquée (badge). Sort du circuit
« à traiter » côté validation (badge Bloqué). Déblocage : **NV1 seulement**
(`processDebloquer` no-op si niveau ≠ nv1). Qui : même périmètre que
`can_validate` de l’étape. Motif : **required** à l’entrée (`motif` max 5000) ;
colonne DB nullable mais l’appli exige. Trace historique `bloque_nv1` écrite
surtout **au déblocage** (asymétrie audit).

### Reco A

Deux chemins utiles et non redondants :
1. **Flux d’envoi (sujet B)** — bloquer au moment de l’envoi sans auto-NV1.
2. **Bouton permanent** — déjà sur Show ; pour Lahouari : soit documenter
   « ouvrir via fiche Show », soit **répliquer Bloquer dans la barre d’actions
   du formulaire / Index quand `can_block`** (aligné journal #879).

Ne pas fusionner les deux : un report d’envoi ≠ un blocage métier.

---

## SUJET B — Auto-validation NV1

### B1 — Où / condition

`DepotFactureController::valider` (~1872) :
`$estValidateurDesigne = utilisateurEstAttributaire(user, validateur_id, validateur_email)`.

Puis `FactureWorkflowApresNv1Service::creerWorkflowsApresDepotAutoNv1` —
WV NV1 créée **déjà `valide`** + commentaire `validation_automatique_deposant`.

**Champ Valideur** (pas permission générique). Prefill = déposant courant.

### B2 — Cascade NV2 / SoD

- Auto-NV2 : **non** (NV2 `en_attente`, validateur null).
- FG : skip NV2 → mise_paiement.
- SoD dépôt+NV1+NV2 : **aucun contrôle** (mesure script : nb factures même
  user NV1+NV2).

### B3 — Files d’attente

Envoi **sans** auto-NV1 → WV NV1 `en_attente` + statut facture en validation
NV1 — doit alimenter les compteurs. Script : répartition `factures.statut` +
WV `en_attente` par niveau (tous programmes).

### B4 — UX / audit

Pas de composant « 3 issues » réutilisable identifié. Confirmations actuelles
= Dialog PrimeVue simples.

Audit aujourd’hui :
| Issue | Trace |
|-------|-------|
| Envoi normal | `envoye_nv1` + WV en_attente |
| Auto-NV1 | `envoye_nv2` (skip) + WV NV1 valide + commentaire auto |
| Blocage | colonnes WV seulement (historique motif au déblocage) |

Manque : distinguer clairement « envoi sans valider » vs « auto-NV1 » vs
« bloqué à l’envoi » (actions historiques dédiées).

### Reco B

**Une seule boîte à trois issues** quand l’émetteur = Valideur :
1. Valider le niveau 1 maintenant
2. Envoyer sans valider *(défaut / Escape / fermeture)*
3. Bloquer la facture *(motif requis)*

Si l’émetteur ≠ Valideur : aucune question (comportement actuel).

Ne pas enchaîner deux modales. Ne jamais default sur Bloquer.

---

## SUJET C — Filtre programmes par enseigne

### Constats

| Élément | État |
|---------|------|
| `programmes.enseigne_id` | NOT NULL → 3 enseignes |
| Sélecteur saisie | **tous** les actifs (`DepotFactureController`, pas AccessService) |
| `ProgrammeAccessService` | ACL (compta/DSI/DG = tout ; sinon enseigne GS_ENVOL/HECTARE **sans GEMME**) — **ne pas réutiliser comme filtre UI** |
| Filtre existant | Programmes/Index #1059 via `users.preferences` |
| `FactureVisibility` | ACL factures (distinct) |

Règle Robin (confort, pas sécurité) : GS_HECTARE→HECTARE, GS_GEMME→GEMME,
GS_ENVOL→ENVOL ; sinon **TOUT** ; multi-groupes = union ; bouton « tout
afficher » pour tous ; **session / saisie**, pas `preferences` / localStorage ;
bandeau visible + compteur « N affichés · M masqués ».

### Reco C

**Implémentable.** Nouveau helper d’affichage (ou extension douce du pattern
#1059) **côté props Inertia du dépôt** :
- déduire enseigne(s) des groupes (ajouter **GS_GEMME**) ;
- transversaux / sans groupe → liste complète ;
- filtre **uniquement** sur le select saisie (autres sélecteurs = lots
  ultérieurs si besoin) ;
- état débloqué = `ref` Vue (durée de la saisie ouverte) ;
- charger `enseigne:id,code,nom` (et `modules_actifs` si `aModule` — leçon #1184).

Ne pas brancher `ProgrammeAccessService` ici : ce serait une restriction
d’accès, pas un confort.

---

## SUJET D — Dette #1026

### Encore présent (code)

- Cases `sans_marche` / `rattachement_poste_direct` + watchers : **oui**
- RG/CP/Finitions/OPC visibles/cliquables hors marché : **oui** (seul
  pénalités a la garde `horsMarcheProgrammes`)
- Poste exigé sur `sans_marche` : **non** (#1031 l’a explicitement retiré)

Script : volumes hors marché + retenues ≠ 0 (colonnes / JSON `saisie`).

### Reco D

Chantier **séparé**, mais **série obligatoire** sur le même fichier 9100 L
après ou avec A/B (un seul rebuild). Ne pas ouvrir 3 fois de suite.

---

## RECOMMANDATIONS TRANCHÉES

| Sujet | Décision |
|-------|----------|
| **A+B** | **Les deux chemins** : (1) dialog 3 issues à l’envoi ; (2) Bloquer visible dès que `can_block` — idéalement aussi depuis Index/saisie, pas seulement Show. |
| **B UX** | **Une boîte, trois issues** ; défaut = envoyer sans valider. |
| **C** | **Oui, implémentable** ; filtre affichage type #1059 + GEMME ; **pas** ProgrammeAccessService ; session Vue. |
| **D** | Toujours vivant ; lot dédié en série sur SaisieForm. |

---

## DÉCOUPAGE EN LOTS

| Lot | Contenu | Fichiers | Série ? |
|-----|---------|----------|---------|
| **#1189?** | Flux envoi 3 issues + audit distinct + (option) Bloquer dans barre Index/saisie si can_block | DepotFactureController, FactureWorkflow*, SaisieForm, Show/Index, presenter historique | **série** SaisieForm |
| **#1190?** | Filtre enseigne select programme (confort) | DepotFactureController props + SaisieForm (petit) ; helper enseigne | série si touche SaisieForm ; sinon parallélisable PHP+petit front |
| **#1191?** | Logique A / retenues hors marché (#1026) | SaisieForm massif + éventuellement back coerce | **série** après 1189 |

Ne **jamais** paralléliser deux lots sur `DepotFactureSaisieForm.vue` +
`manifest.json`.

Ordre suggéré : **B+A (envoi + découvrabilité Bloquer)** → **C** → **D**.

---

## QUESTIONS RESTANT À ROBIN / COMPTA

1. Lahouari passe-t-il par **Index** (modal) ou **Show** (fiche + PDF) ?
   Si Show + can_block déjà OK → bug de découverte ; si Index → manque UI.
2. Un blocage à l’**envoi** doit-il poser NV1 bloquée sans créer de validation,
   ou créer WV en_attente déjà bloquée ?
3. Déblocage NV2 : aujourd’hui impossible — à ouvrir ou assumer ?
4. Filtre enseigne : uniquement sélecteur dépôt, ou aussi Global factures /
   Comptabilisation ?
5. Q13b / retenues hors marché : ordre vs logique A complète (#1026) —
   masquer retenues seul d’abord, ou refonte poste→marché|enveloppe d’un coup ?

---

## DÉPLOIEMENT-TEST

```text
git pull origin main
php tools/diag/diag_saisie_facture_1188.php
```

Coller sortie A–D. Aucun migrate / npm.

## LEÇON

Un journal qui dit « depuis la saisie » peut viser le **shell Show** alors
que 90 % des clics partent de **Index** sans cette barre — même motif que
« bouton disparu » (#1156) mais ici le bouton n’a jamais été dans le form.
Filtre enseigne ≠ ACL programmes : mélanger les deux enferme la compta.

`users` **n’a pas** SoftDeletes / `deleted_at` en prod : un `whereNull('deleted_at')`
plante le diag avant la section D (retenues hors marché) — relivré avec garde
`Schema::hasColumn`.
