---
suivi: 1775
date: 2026-10-02
sujet: Diagnostic — CIE coché, Facturé à 0 sur Louis Pasteur
chantier: factures / CIE
type: diagnostic
statut: poussé
hash:
fichiers:
  - docs/suivi/SUIVI_1775_diag_cie_non_retenu.md
---

# SUIVI #1775 — CIE coché, colonne Facturé à 0

Phase 1. Aucun code modifié. Aucune écriture SQL. Fiches #1772 et #1773 relues.

Écran constaté par Robin, non relu en base ici (MySQL local toujours injoignable) : programme 3 ENV13-001, facture 1269 EMC 83, n° E83-202607500 SIT 10, marché MCH-2026-107, facture du 27/07/2026, statut Payée. Décompte sans ligne CIE (RG et CP seulement). Avancement du marché : CIE 0,00 / 592,50 HT avant et après cette facture. Ventilation actuelle EMC 83 : 252,00 HT + 340,50 HT = 592,50 HT.

## 1. Chemin de la case CIE

### 1.a Où la case apparaît

Un seul composant, à toutes les étapes : `resources/js/Pages/DepotFactures/DepotFactureSaisieForm.vue`.

La case d’en-tête n’est pas un champ posté. C’est le booléen local `showCIE` (l. 2171), checkbox `input-id="depot-show-cie"` (l. 10292-10298). Elle est désactivée si `readonly`, `isCieHistorique` ou `cieCaseActivatable` faux (l. 10296).

Ce qui part au serveur, dans `buildDraftPayload` (l. 7157-7159) et `buildValiderPayload` (l. 7755-7757) :

| Champ posté | Sens | Si la case d’en-tête est décochée |
|---|---|---|
| `montant_cie` | TTC (commentaire l. 3556 et contrôleur l. 1199) | 0 à la validation ; le brouillon reprend `data.montant_cie`, remis à 0 par le watch l. 5626 |
| `cie_montant_ht` | HT, somme des lignes cochées | 0 |
| `cie_lignes[]` | `{ repartition_id, montant_ht }` HT | tableau vide |

`buildCieLignesPayload` (l. 4491-4498) ne garde une ligne que si elle est cochée et que son `montant_ht` est supérieur à 0,009.

Hôtes du même formulaire :

- Dépôt et saisie : `DepotFactures/Index.vue` l. 1685, `readonly` si `can_edit === false` (l. 1715).
- Fiche dépôt (NV1, NV2, suite du workflow) : `DepotFactures/Show.vue` l. 531, `readonly` si `can_edit` est faux (l. 535). Pas d’autre formulaire.
- Consultation seule : `DepotFactures/SaisieConsultation.vue` l. 125, `readonly` et `lecture-seule-integrale` vrais (l. 128-129). `refreshCieDues` ne part pas (l. 4562-4564). Aucune écriture.
- Depuis un marché : `Marches/Show.vue` l. 6270, même formulaire quand la saisie est éditable.
- Comptabilisation : `Factures/Comptabilisation/Index.vue` l. 709, prop `comptabilisationMode`. L’enregistrement passe par `ComptabilisationController` puis le même `applyDepotSaisieFromValidated`.

Il n’y a pas de case CIE distincte à la validation NV1, NV2 ou à la comptabilisation.

### 1.b Qui écrit `cie_prelevements`

`DepotFactureController::applyDepotSaisieFromValidated` (l. 1690) appelle `assertCieRetenueServeur` (l. 1713) puis, dans la transaction, `synchroniserPrelevementsCieDepuisValidated` (l. 1986).

Cette méthode (l. 5935) lit `cie_lignes` via `cieLignesDepuisValidated` (l. 5651) et appelle `CieService::syncPrelevementsPourDepot` (l. 1083).

Une ligne n’est créée que si `repartition_id` > 0 et `montant_retenu_ht` > 0,009 (l. 1097-1100). Le montant stocké est le HT de la ligne (`montant_retenu_ht`, l. 1183). Le statut écrit est `retenu`, jamais `encaisse` (l. 1164, commentaire l. 1253). La répartition est celle dont l’id est posté : pas d’allocation automatique depuis le #1268 (l. 1078-1079 et refus l. 5893-5897).

Les dues proposées à l’écran viennent de `repartitionsDuePourFournisseurMarche` (l. 1003) : répartition vivante, même `fournisseur_id` et même `marche_id` que la facture, opération parente vivante, reste dû HT > 0 (l. 1009-1038). Endpoint `cieRetenueDues` (l. 5970).

### 1.c Case cochée, rien écrit, pas d’erreur

1. Case d’en-tête cochée, deux opérations dues ou plus, aucune ligne d’opération cochée. `onShowCieToggle` (l. 4541-4558) ne pré-coche la ligne que si `cieLignes.length === 1`. Avec deux lignes (252,00 HT et 340,50 HT), cocher « CIE » laisse les lignes décochées. Le payload part avec `cie_lignes: []` et `montant_cie: 0`. `assertCieRetenueServeur` sort sans erreur (l. 5844-5846). `syncPrelevementsPourDepot` reçoit un tableau vide et n’insère rien (l. 1110-1126). Aucun toast.

2. Même situation au chargement, avant NV1, s’il y a exactement une due : la pré-coche automatique existe (`exactlyOne && mayPrecheck`, l. 4664-4682). Dès que le dépôt a franchi NV1 (`depotFranchiNv1`, l. 4265, statuts l. 4199-4206), cette pré-coche ne se refait plus (#1301, l. 4665). Rouvrir la facture en validation ne recrée pas les lignes.

3. Ligne cochée mais montant 0 ou vide : filtrée au payload (l. 4496) et au serveur (l. 5665). Si tout est à 0, sortie silencieuse l. 5844.

4. Aucune répartition vivante pour ce fournisseur et ce `marche_id` (marché null, autre marché, répartition soft-supprimée). `dues` est vide, motif « aucun CIE dû pour ce fournisseur sur ce marché » (l. 4651-4652). `cieCaseActivatable` est faux (l. 4418-4432) : la case est grisée. Un clic ne change rien.

5. Chargement des dues en erreur ou timeout 20 s (l. 4731-4750) : case non activable (`phase !== 'ok'`, l. 4426).

6. Marché ou fournisseur encore vides : l’appel des dues ne part pas (l. 4600-4615), motif « marché et fournisseur requis ».

7. Enregistrement qui envoie `cie_lignes` vide et `montant_cie` à 0 alors qu’un prélèvement actif existait : `syncPrelevementsPourDepot` passe le statut à `annule` puis soft-delete (l. 1115-1119), sans message si l’assert est déjà sorti à 0. La ligne existe encore en base, `deleted_at` rempli, invisible pour Eloquent et pour la colonne Facturé.

8. Clé `cie_lignes` absente du payload (règle `nullable`, l. 1202 : Laravel ne la met pas dans `$validated`). `cieLignesDepuisValidated` renvoie `[]` (l. 5653-5655). Si `montant_cie` stocké est déjà > 0, l’assert refuse avec un message visible (l. 5894-5897). Si le TTC stocké est aussi à 0, c’est le cas 7, silencieux.

9. Dépôt payé, comptabilisé, rejeté, archivé, ou déjà envoyé à Sage (`intacct_bill_id` non vide), ou `canEditDepot` faux : l’assert jette une erreur visible (l. 5867-5882), ce n’est pas un échec silencieux. Une facture Payée ne peut plus recevoir la retenue après coup.

Le `try/catch` autour du sync (l. 5947-5963) ne transforme que `InvalidArgumentException` en erreur de validation. Il n’avale pas le reste.

### 1.d Décompte et Sage

Décompte de la fiche : `FactureDecompteSaisiePresenter::depuisSaisie` lit seulement `ocr_raw.saisie`. La ligne « CIE » est ajoutée si la somme HT de `cie_lignes`, ou à défaut le HT déduit de `montant_cie` TTC, dépasse 0,009 (l. 156-162 et l. 384-406). Pas de lecture de `cie_prelevements`. Une fiche sans ligne CIE a donc un TTC `montant_cie` et des lignes `cie_lignes` à ~0. RG et CP viennent des pourcentages stockés, indépendamment.

Le HT à régler / le net à payer du décompte sont les totaux saisis (`totauxAReglerCommeSaisie`, l. 40-43). Le CIE TTC entre dans la reconstitution du brut (`computeRetenuesGlobales`, numérateur, `EcrituresComptablesCalculator` l. 268-313) seulement si `montant_cie` TTC > 0.

Avancement du marché : le théorique CIE est la somme HT des répartitions vivantes de ce `marche_id` (`MarcheRetenuesEtatsService::cieTheoriqueHt`, l. 544-554). Le retenu vient de `montant_cie` TTC dans la saisie, converti en HT (`montantsSaisiePourDepot`, l. 498-517). 0,00 / 592,50 HT veut dire : 592,50 HT ventilés sur ce marché, et aucune saisie de facture de ce marché avec un `montant_cie` TTC non nul. Ce n’est pas la colonne Facturé du #1773.

Sage : `EcrituresComptablesCalculator` ajoute une ligne `role = cie`, crédit = le montant CIE TTC (`$glob['cie']`, l. 616-617). Compte par défaut `401600` (`config/erp.php` l. 57). `ComptabilisationController::sommesRetenuesApbillDepuisLignesComptables` (l. 2879) additionne ce crédit et le range dans une variable nommée `cie_ht` (l. 2932) : le montant envoyé est celui de la ligne comptable, donc le TTC, pas un HT recalculé depuis `cie_prelevements`. Sans `montant_cie` TTC à la génération des écritures, aucune ligne CIE ne part vers Sage.

### 1.e Historique git depuis le 01/07/2026

Commande : `git log --oneline --since=2026-07-01 --` les sept fichiers du chemin. Sortie brute :

```
4d8a0c23 feat(cie): ventilation facultative et saisie dans la synthese (#1773)
ba04a633 feat(factures): photographier les montants a chaque jalon (#1768)
5057c6af feat(factures): remettre un cheque disparu dans la file et partir en virement par defaut (#1765)
cc4d59ce feat(depot): confirmer la suppression et actualiser la liste (#1752)
bdf65c5e feat(compta): afficher le SIRET et un IBAN masque dans la liste des fournisseurs (#1749)
e1caf630 feat(compta): reconnaitre le fournisseur d'une facture par SIRET, SIREN ou TVA (#1746)
f1c98c54 feat(compta): envoyer le projet Sage et l'exiger en nouveau système (#1714)
9b7485e7 feat(compta): valideur designe peut marquer payee une facture historique (#1620)
89c88ec2 feat(compta): acompte debit 409100 aligne ecran et Sage (#1619)
ce44ce7c feat(depot): liens applicatifs vers ecran complet ?depot= (#1617)
5c21152a feat(compta): creation APADVANCE Sage a la comptabilisation acompte (#1616)
3cd1295e feat(depot): type ACOMPTE saisie et workflow sans Sage (#1612)
8ecec4f3 feat(compta): valideur peut corbeiller un dépôt, compta marque une facture historique payée (#1604)
a231728c feat(compta): abandonner la destination Projet des factures (#1599)
df36dda1 feat(foncier): corbeille des dossiers fonciers (#1589)
c7954466 feat(depot): enregistrer sans contrainte quelle que soit la destination (#1559)
36e19cd7 feat(depot): accélérer le chargement d'une facture (#1552)
71371409 fix(factures): TTC brut fiche depuis les lignes TVA (#1548)
f77fe442 feat(factures): afficher le decompte sur la fiche (#1541)
d14e2d5d fix(marches): afficher la liberation RG a l avancement (#1539)
a8779e89 fix(depot): retrancher la liberation RG au numerateur (#1537)
dad4ea01 fix(depot): injecter la liberation RG dans le net a payer (#1536)
e83a83e9 feat(depot): liberer la RG depuis une facture (#1535)
a836e26f feat(depot): autoriser la modification d'une saisie historique (#1527)
5e58eaee feat(depot): consultation lecture seule de la saisie d'un depot (#1525)
eeed3e46 feat(depot): autoriser les retenues hors marché sur programme hors ERP uniquement (#1521)
4ffcecc0 fix(factures): Enregistrer depot Projet sans classe Sage (#1515)
c2bd7c0f fix(factures): persistance dossier/poste Projet a Valider (#1513)
580370a9 feat(depot): UI saisie destination Projet + journalisation RGPD (#1505)
98f545ff feat(compta): harmoniser le filtre societe CSV f_societe (#1496)
c4ec2514 feat(depot): ancrer la destination PROJET sur le type de nomenclature (#1492)
39fc6bea fix(factures): cloturer le workflow NV si deja reglee en cours de validation (#1489)
38583986 feat(depot): tâche Compléter et valider une facture (#1458)
f33abb92 feat(depot): enregistrement permissif et filtre valideur (#1450)
6c81449d fix(factures): retablir l'acces direct Voir le doublon (#1444)
67454fc5 feat(factures): controle humain doublon detecte (#1413)
4f4b4045 fix(factures): GS_ADMIN_ERP peut retirer le verrou (#1411)
39799926 feat(factures): verrou consultation nominative sensible (#1410)
e4fdb523 feat(depot): anciennete jours + filtre entite + recherche liste (#1407)
30840365 feat(depot-factures): rattachement programme hors ERP (#1392)
cce8b397 fix(factures): NV2 apres NV1 uniquement pour programmes ENVOL (#1386)
f163bab4 fix(compta): verrou retenues/montants dès comptabilisation, plus NV1 (#1367)
3d468ed8 fix(compta): volet Comptabilité sibling du wrapper Informations (#1364)
3e42c9f8 fix(depot): OCR non bloquant en lot + cout maitrise (#1361)
8f5a4a12 fix(depot): ne plus pré-cocher CIE à la réouverture NV1+ (#1301)
2b601165 fix(depot): présentation Décompte HT + type avancé (#1299)
6b017a95 fix(factures): plafond hors retenues + confirmation dépassement (#1295)
94c06eb9 fix(marches): deux etats retenues + Synthese alignee (#1287)
e15f7459 feat(cie): synthese en matrice entreprises x operations (#1282)
b2ea8f58 feat(cie): onglet Synthèse par entreprise (programme) (#1280)
e12b373b fix(depot): whitelist cie_lignes sur PATCH brouillon/compta (#1279)
7a79edf8 fix(depot): Show — Teleport conditionnel + filet rendu CIE (#1278)
d22b3bfc fix(depot): CIE dues — sortir du loading infini et croire modifiable serveur (#1277)
22125c37 fix(depot): aligner canEditDepot sur droit de valider NV1/NV2 + CIE visible (#1276)
5543bb10 fix(depot): cartes gap uniforme, totaux deplaces, docs sans cadre (#1272)
842c6103 fix(depot): largeur saisie — truncate selects, gap cartes, sans overflow-x-hidden (#1271)
161f4e89 feat(depot): actions sous fil d'Ariane, cartes et resserrage saisie (#1270)
79febc7b feat(depot): refonte presentation saisie — bulles et sous-menu (#1264)
b66be7fa feat(cie): affectation retenue par operation — lignes cochees, suppression FIFO (#1268)
16430160 feat(cie): retenue sur situation — case RETENUES, numerateur, prelevements (#1267)
8c8e5eaf feat(cie): modele structurel cout/prelevements + soft-delete + masque pair-a-pair (#1266)
```

Dates des commits qui changent l’enregistrement : #1267 et #1268 le 11/08/2026, #1279 le 11/08/2026, #1301 le 12/08/2026, #1773 le 02/10/2026. Le log ci-dessus continue en amont (dépôt, compta, CIE marché) ; aucun de ces commits ne rétablit une allocation automatique quand plusieurs opérations sont dues.

#1268 supprime l’allocation automatique : cocher l’en-tête ne suffit plus dès qu’il y a plusieurs opérations. #1301 retire la pré-coche à la réouverture dès NV1. #1773 ne modifie pas `syncPrelevementsPourDepot` ni le payload de la case. Il ne peut pas, à lui seul, empêcher l’écriture d’un prélèvement. La facture porte la date du 27/07/2026, avant ces commits : la date de saisie ERP (à lire dans l’historique, pas la date fournisseur) dit si la case existait déjà.

## 2. Colonne Facturé (#1773)

### 2.a Ce qui est sommé

`CieService::factureHtNv1` (l. 1931-1946), appelé depuis `grilleVentilation` sur les répartitions vivantes de l’opération (relation Eloquent, soft-delete exclu).

Pour chaque prélèvement de la relation `prelevements` (soft-delete exclu) :

- statut `annule` : ignoré (l. 1937-1938) ;
- sinon `FactureStatutService::estAuDelaDeNv1Inclus` sur le dépôt lié (l. 1940) ;
- le montant ajouté est `montant_retenu_ht` (HT).

Pas de jointure SQL. Un prélèvement dont la répartition est soft-supprimée n’est pas chargé : la grille ne parcourt que les répartitions vivantes. Un prélèvement annulé puis soft-supprimé (cas 1.c.7) ne compte pas. Une case sans montant NV1 est envoyée `null` et affichée « — » ; le total d’opération à 0 s’affiche 0,00.

### 2.b Statut Payée

`estAuDelaDeNv1Inclus` (l. 117-127) est vrai si le statut normalisé est dans `nv1_en_cours`, `nv2_en_cours`, `a_comptabiliser`, `comptabilisee`, `mise_en_paiement`, `en_paiement`, `payee` (l. 92-102), et que ce n’est ni `rejetee` ni `archivee`.

`mapRawStatut` (l. 243) range `paye`, `payee` et `paiement_confirme` dans `payee` (`RAW_GROUPS` l. 24). Le dépôt prend le statut de la facture liée s’il est renseigné (l. 256-262).

Une facture Payée n’est donc pas exclue par ce filtre. Si la colonne est à 0,00 sur toutes les opérations de Louis Pasteur, ce n’est pas parce que le statut est Payée. C’est qu’il n’y a pas de prélèvement vivant, non annulé, porté par une répartition vivante, dont le dépôt passe ce filtre.

## 3. Requêtes lecture seule

À coller sur OVH, une commande après l’autre. `withTrashed` pour voir les soft-delete. Pas de `DB::table` sur ces modèles. Les montants `montant_retenu_ht`, `montant_calcule_ht` et `cie_montant_ht` sont HT. `montant_cie` est TTC. Le crédit d’une ligne comptable `role=cie` est le TTC envoyé à Sage.

Filtre de la commande (d) : `depot_factures.date_facture` à partir du 07/07/2026, programme 3, fournisseurs qui ont au moins une répartition CIE (y compris soft-supprimée) de montant HT > 0 sur ce programme. `prelevements` compte les lignes `cie_prelevements` du dépôt, soft-supprimées comprises.

```
php artisan tinker --execute="\$ops = App\Models\CieOperation::withTrashed()->where('programme_id', 3)->pluck('id'); \$reps = App\Models\CieRepartition::withTrashed()->whereIn('cie_operation_id', \$ops)->pluck('id'); \$rows = App\Models\CiePrelevement::withTrashed()->whereIn('cie_repartition_id', \$reps)->orderBy('id')->get(); echo 'count='.\$rows->count().PHP_EOL; foreach (\$rows as \$p) { \$dep = \$p->depotFacture()->withTrashed()->first(); echo 'id='.\$p->id.' facture_id='.(\$dep->facture_id ?? '-').' depot='.\$p->depot_facture_id.' repartition='.\$p->cie_repartition_id.' montant_ht='.\$p->montant_retenu_ht.' statut='.\$p->statut.' deleted='.(\$p->deleted_at ?: '-').' created='.\$p->created_at.PHP_EOL; } echo 'FIN_A'.PHP_EOL;"

php artisan tinker --execute="\$f = App\Models\Facture::withTrashed()->find(1269); echo 'facture id='.(\$f->id ?? '-').' numero='.(\$f->numero_facture_fournisseur ?? '-').' statut='.(\$f->statut ?? '-').' programme='.(\$f->programme_id ?? '-').' rg_ht='.(\$f->retenue_garantie_ht ?? '-').' cp_ht='.(\$f->compte_prorata_ht ?? '-').PHP_EOL; \$depots = \$f ? \$f->depotFactures()->withTrashed()->orderBy('id')->get() : collect(); if (\$depots->isEmpty()) { echo 'AUCUN_DEPOT'.PHP_EOL; } foreach (\$depots as \$d) { \$s = (array) data_get(\$d->ocr_raw, 'saisie', []); echo 'depot='.\$d->id.' statut='.\$d->statut.' facture_id='.\$d->facture_id.' numero='.\$d->numero_facture.' marche='.\$d->marche_id.' fournisseur='.\$d->fournisseur_id.' intacct_bill='.(\$d->intacct_bill_id ?: '-').' date_facture='.\$d->date_facture.' created='.\$d->created_at.PHP_EOL; echo 'montant_cie_ttc='.(array_key_exists('montant_cie', \$s) ? \$s['montant_cie'] : 'ABSENT').' cie_montant_ht='.(array_key_exists('cie_montant_ht', \$s) ? \$s['cie_montant_ht'] : 'ABSENT').PHP_EOL; \$lignes = \$s['cie_lignes'] ?? []; echo 'cie_lignes='.(is_array(\$lignes) ? json_encode(\$lignes) : 'NON_TABLEAU').PHP_EOL; foreach ((array) data_get(\$s, 'lignes_comptables', []) as \$lg) { if (is_array(\$lg) === false) { continue; } \$role = \$lg['role'] ?? ''; \$cpt = (string) (\$lg['compte_numero'] ?? ''); if (\$role === 'cie' || \$cpt === '401600') { echo 'ecriture role='.\$role.' compte='.\$cpt.' debit='.(\$lg['debit'] ?? 0).' credit_ttc='.(\$lg['credit'] ?? 0).PHP_EOL; } } foreach (App\Models\FactureHistorique::query()->where(function (\$q) use (\$d) { \$q->where('facture_id', 1269)->orWhere('depot_facture_id', \$d->id); })->orderBy('id')->get() as \$h) { \$cm = (array) \$h->champs_modifies; \$avant = (array) (\$cm['avant'] ?? []); \$apres = (array) (\$cm['apres'] ?? []); echo 'hist id='.\$h->id.' action='.\$h->action.' created='.\$h->created_at.' avant_cie_ttc='.json_encode(\$avant['montant_cie'] ?? null).' apres_cie_ttc='.json_encode(\$apres['montant_cie'] ?? null).' avant_lignes='.json_encode(\$avant['cie_lignes'] ?? null).' apres_lignes='.json_encode(\$apres['cie_lignes'] ?? null).PHP_EOL; } } foreach (App\Models\FactureJalonValeur::query()->where('facture_id', 1269)->orderBy('id')->get() as \$j) { \$v = (array) \$j->valeurs; echo 'jalon='.\$j->jalon.' created='.\$j->created_at.' cie_montant_ht='.(array_key_exists('cie_montant_ht', \$v) ? \$v['cie_montant_ht'] : 'ABSENT').' cie_lignes='.json_encode(\$v['cie_lignes'] ?? []).PHP_EOL; } echo 'FIN_B'.PHP_EOL;"

php artisan tinker --execute="\$fou = App\Models\Fournisseur::query()->where('intacct_id', 'F1135')->first(); echo 'fournisseur id='.(\$fou->id ?? '-').' nom='.(\$fou->nom ?? '-').PHP_EOL; if (\$fou) { \$ops = App\Models\CieOperation::withTrashed()->where('programme_id', 3)->get()->keyBy('id'); \$reps = App\Models\CieRepartition::withTrashed()->where('fournisseur_id', \$fou->id)->whereIn('cie_operation_id', \$ops->keys())->orderBy('id')->get(); foreach (\$reps as \$r) { \$op = \$ops->get(\$r->cie_operation_id); \$m = \$r->marche_id ? App\Models\Marche::withTrashed()->find(\$r->marche_id) : null; echo 'rep='.\$r->id.' operation='.\$r->cie_operation_id.' libelle='.(\$op->libelle ?? '-').' marche_id='.(\$r->marche_id ?? '-').' reference='.(\$m->reference ?? '-').' montant_ht='.\$r->montant_calcule_ht.' deleted='.(\$r->deleted_at ?: '-').' created='.\$r->created_at.PHP_EOL; } } echo 'FIN_C'.PHP_EOL;"

php artisan tinker --execute="\$ops = App\Models\CieOperation::withTrashed()->where('programme_id', 3)->pluck('id'); \$fouIds = App\Models\CieRepartition::withTrashed()->whereIn('cie_operation_id', \$ops)->where('montant_calcule_ht', '>', 0)->pluck('fournisseur_id')->unique()->values(); foreach (App\Models\DepotFacture::withTrashed()->where('programme_id', 3)->whereIn('fournisseur_id', \$fouIds)->whereDate('date_facture', '>=', '2026-07-07')->orderBy('id')->get() as \$d) { \$fou = App\Models\Fournisseur::query()->find(\$d->fournisseur_id); \$m = \$d->marche_id ? App\Models\Marche::withTrashed()->find(\$d->marche_id) : null; \$fac = \$d->facture_id ? App\Models\Facture::withTrashed()->find(\$d->facture_id) : null; \$n = App\Models\CiePrelevement::withTrashed()->where('depot_facture_id', \$d->id)->count(); echo 'facture='.(\$d->facture_id ?? '-').' depot='.\$d->id.' numero='.\$d->numero_facture.' fournisseur='.(\$fou->intacct_id ?? '-').' '.(\$fou->nom ?? '-').' marche='.(\$m->reference ?? '-').' statut_facture='.(\$fac->statut ?? '-').' statut_depot='.\$d->statut.' date='.\$d->date_facture.' prelevements='.\$n.PHP_EOL; } echo 'FIN_D'.PHP_EOL;"
```

## 4. Conclusion

Cause la plus solide, avant le retour des requêtes : la case d’en-tête cochée n’écrit une retenue que si au moins une ligne d’opération part avec un montant HT > 0. Avec deux ventilations sur le même marché (252,00 HT et 340,50 HT), cocher seulement « CIE » poste un tableau vide et un TTC à 0, sans message. Le décompte et l’avancement du marché lisent cette saisie, pas `cie_prelevements` : l’absence de ligne CIE et le 0,00 / 592,50 HT collent à une saisie sans montant CIE. La colonne Facturé du #1773, elle, lirait un prélèvement HT vivant même sur une facture Payée ; elle affiche 0,00 parce que cette ligne n’a pas été créée, ou qu’elle a été annulée et soft-supprimée, ou qu’elle est restée sur une répartition soft-supprimée.

Ce n’est pas tranché tant que les requêtes ne sont pas revenues. Elles diront, pour la facture 1269 : `montant_cie` TTC, `cie_lignes` HT, présence ou non d’un prélèvement (y compris supprimé), et si les répartitions d’EMC 83 pointent bien vers MCH-2026-107 et depuis quand.

Liste des factures concernées : pas établie ici. Elle sortira de la requête (d) une fois la sortie collée.

Pas de correction proposée pour les factures déjà payées dans Sage.

## LEÇON

Cocher « CIE » n’est pas cocher une opération. Depuis le #1268, sans ligne cochée et sans montant HT, le serveur enregistre le vide et ne le dit pas. Le décompte lit la saisie TTC/HT ; la colonne Facturé lit les prélèvements HT vivants. Les deux peuvent être à zéro pour des raisons différentes : ici l’écran (décompte + avancement) désigne d’abord la saisie vide.
