---
suivi: 1365
date: 2026-08-24
sujet: diag_fournisseur_inactif_validation
chantier: depot_factures
type: diagnostic
statut: poussé
hash: 32a961a2
fichiers:
  - docs/suivi/SUIVI_1365_diag_fournisseur_inactif_validation.md
  - tools/diag/diag_fournisseur_inactif_validation_1365.php
---

## PROMPT ENVOYÉ

PHASE 1 — DIAGNOSTIC SEUL. Blocage prod à l’envoi en validation dépôt :
« Le fournisseur sélectionné n'est pas actif ou n'existe pas. »
Cas CERQUAL / FVQMC2605006 / MCH-2026-098 / ENV34-004. Champ Fournisseur
grisé (déduit du marché). Ne pas coder le fix (#1366).

## FICHES LUES

- `#1358` — fournisseur ERP obligatoire SIWI ; réécrit `resoudreFournisseur` ;
  **ne touche pas** DepotFacture / FournisseurActifValidation (confirmé par diff).
- `#1363` — LEÇON scripts `tools/diag/` : garde `PHP_SAPI !== 'cli'`, require relatif,
  CWD = racine OVH `/home/hectare-envol/envol.hectare.fr`.
- `#1364` — en cours / poussé (volet Comptabilité) : **ne pas toucher**
  `DepotFactureSaisieForm.vue` dans ce lot.

## SYNTHÈSE (6 objets)

### OBJET 1 — LA RÈGLE

| Élément | Valeur |
|---------|--------|
| Message | `Le fournisseur sélectionné n'est pas actif ou n'existe pas.` |
| Production | `app/Rules/FournisseurActifPourSelection.php:47` |
| Critère EXACT | `Fournisseur::whereKey($id)->where('statut', 'actif')->exists()` (l.41-44) |
| Colonne | `fournisseurs.statut` — enum MySQL `['actif','inactif']` (migration `2026_05_06_095400_…`) |
| Existence id | `exists:fournisseurs,id` **avant** la règle ; un id inexistant échoue aussi via `exists` **ou** via la règle (exists actif = false) |
| Intacct | **Non** directement : la règle ne lit pas Sage. `SyncFournisseursDepuisIntacct` mappe `STATUS=active` → `statut=actif`, sinon `inactif` |
| Tolérance | Si `$existingId` (= `depot.fournisseur_id` déjà en colonne) **égal** à l’id posté → **passe** même inactif (l.31-33) |

Branche dépôt validation : `DepotFactureController::depotFactureValiderValidationRules` (~967-973) :
`FournisseurActifValidation::pourSelection($depot?->fournisseur_id)`.

Schéma réel : confirmer en prod via script (`SHOW COLUMNS` + `DISTINCT statut`) — ne jamais
déduire d’un `??` sur l’objet.

### OBJET 2 — ÉTAT RÉEL (décision)

**DB locale Cursor : indisponible** (`127.0.0.1:3306` refusé ; bootstrap local cassé
`Reservation.php` vide hors lot). Sortie brute **obligatoire** via script OVH ci-dessous.

Analyse code (hypothèse dominante à confirmer par la sortie) :

1. À la sélection du marché, `applyFournisseurDepuisMarcheObj` écrit
   `form.fournisseur_id = marche.fournisseur_id` et le **libellé** depuis
   `marche.fournisseur_nom` / relation (`DepotFactureSaisieForm.vue:1802-1834`).
2. Le Select est grisé (`fournisseurVerrouilleParMarche`, `:2632-2634`, `:disabled` ~8291/8401).
3. `props.fournisseurs` = **actifs seuls** (`FournisseurSelectionOptions::forSelect()`),
   mais le libellé marché s’affiche quand même (option synthétique `:1745-1769`).
4. Donc : **libellé ≠ preuve d’activité** ; l’id posté est bien celui du marché.
5. Si `depot.fournisseur_id` est encore `null` (brouillon jamais accepté avec cet id,
   ou jamais persisté), la tolérance `existingId` **ne s’applique pas** → rejet si
   `statut <> 'actif'`.

→ Trancher en prod : `VERDICT_OBJET2` du script
(`fournisseur RÉELLEMENT INACTIF` vs `id transmis VIDE/INTROUVABLE`).

### OBJET 3 — AMPLEUR

Chiffres **par requête** (script OBJET 3) :
- COUNT marchés `statut='en_cours'` + fournisseur null / introuvable / `statut<>'actif'`
  + liste (référence, programme, fournisseur).
- COUNT dépôts boîte / `en_cours_saisie` rattachés à un tel marché
  (+ lecture tolérance `existingId`).

### OBJET 4 — DEPUIS QUAND

| Commit | Date | Rôle |
|--------|------|------|
| `0ce68cf7233ae1fe7233004d3d07b4cacb77fe69` | 2026-06-29 | **Introduit** la règle + branchement `DepotFactureController` (message exact) |
| `f4ebb888e5b6e4fe9ca597d0a014f257ecbcf9d3` (#1358) | 2026-08-14 | SIWI only : `resoudreFournisseur` exige `fournisseur_hectarion_id` ; **aucun** fichier Depot/FournisseurActif* |

Conclusion : le contrôle dépôt **n’a pas été durci par #1358**. Il existe depuis le
29/06/2026 (`0ce68cf7`). Le symptôme apparaît quand un titulaire de marché **déjà
signé** passe `inactif` (souvent via sync Intacct) alors que le dépôt n’a pas encore
persisté `fournisseur_id` → pas de tolérance.

### OBJET 5 — QUESTION MÉTIER (ne pas trancher)

Options pour Robin :

1. **Bloquer toujours** (état actuel) — cohérence référentiel / Intacct ; cas comme
   CERQUAL (dernière facture / marché soldé après) restent bloqués jusqu’à réactivation.
2. **Autoriser si id = `marches.fournisseur_id`**, avec avertissement — débloque les
   factures dues sur marchés déjà signés ; risque : facturer un fournisseur « mort »
   côté Sage (à anticiper à la comptabilisation Intacct).
3. **Autoriser seulement si marché `en_cours`/`signé`** (même id titulaire) — plus
   étroit que (2) ; refuse un inactif choisi librement hors marché.

### OBJET 6 — COHÉRENCE ÉCRAN

Issues **aujourd’hui** :
- Croix « modifier le fournisseur manuellement » (`deverrouillerFournisseurMarche`) :
  ouvre le Select, mais options = **actifs seuls** → ne permet **pas** de resélectionner
  le titulaire inactif du marché.
- Changer de marché : seulement si un autre marché du même fournisseur actif existe
  (souvent faux).
- `DemandeFournisseurDialog` : POST `/demandes-fournisseurs` → **création** d’une
  demande compta (nouveau fournisseur), **pas** une réactivation. Toast :
  « apparaîtra après validation et synchro Intacct ».
- Réactivation réelle : côté admin / audit fournisseurs + sync Intacct
  (`STATUS=active`), hors parcours déposeur.

Manque : message explicite « titulaire du marché inactif », lien demande de
réactivation, ou déverrouillage métier (options 2/3).

## DÉPLOIEMENT-TEST (diag)

```
cd /home/hectare-envol/envol.hectare.fr
php -l tools/diag/diag_fournisseur_inactif_validation_1365.php
php -d display_errors=1 tools/diag/diag_fournisseur_inactif_validation_1365.php
php -d display_errors=1 tools/diag/diag_fournisseur_inactif_validation_1365.php FVQMC2605006
```

Coller la sortie OBJET 2 (`VERDICT_OBJET2`) et les COUNT OBJET 3 dans le ticket #1366.

## LEÇON

Un Select grisé prérempli depuis le marché peut afficher un libellé hors liste
`actifs` (option synthétique) alors que la validation exige `statut=actif` sans
tolérance si `depot.fournisseur_id` n’est pas encore cet id. Ne pas confondre
affichage marché et éligibilité sélection. #1358 n’est pas la cause du durcissement
dépôt — chercher `FournisseurActifPourSelection` / `0ce68cf7`.

**Plantage run 1 OVH** : `m.entite_id` **deviné** (n’existe pas sur `marches` —
entité via `programmes.entite_id` / `depot_factures.entite_id`). Idem risque
`programmes.code` (réel : `code_envol`). Script corrigé : SHOW COLUMNS sur les
4 tables **avant** les SELECT ; try/catch par section ; colonnes filtrées.
Chiffre conservé : `fournisseurs.statut` enum actif/inactif — 3405 / 589.
