---
suivi: 1801
date: 2026-10-07
sujet: Reste à payer, retenues, marchés sans plafond
chantier: marches
type: correctif
statut: poussé
hash: af03b665
fichiers:
  - app/Support/MarcheVentilationReglement.php
  - app/Models/Marche.php
  - app/Models/SiwiMarche.php
  - app/Services/MarcheSyntheseFinanciereService.php
  - app/Services/BilanFinancierService.php
  - app/Services/MontantFactureSousTraitantService.php
  - app/Services/SiwiRepriseActionService.php
  - app/Http/Controllers/MarcheController.php
  - app/Http/Controllers/DepotFactureController.php
  - app/Http/Controllers/SiwiImportController.php
  - database/migrations/2026_10_07_120000_add_sans_plafond_to_marches_table.php
  - resources/js/Pages/Marches/Index.vue
  - resources/js/Pages/Marches/Show.vue
  - resources/js/Pages/Bilan/Index.vue
  - resources/js/Pages/SiwiImport/Index.vue
  - resources/js/Pages/Factures/Global.vue
  - resources/js/Pages/DepotFactures/DepotFactureSaisieForm.vue
  - resources/js/Components/Programmes/ProgrammeSyntheseFinanciere.vue
  - database/data/journal_mises_a_jour_post_2026_06_12.php
  - tests/Unit/MarcheVentilationReglement1801Test.php
---

# SUIVI #1801 — reste à payer, retenues, marchés sans plafond

## PROMPT ENVOYÉ

Liste des marchés et bilan : reste à facturer négatif sur les dépenses sans marché, reste à payer absent, retenues invisibles. Import SIWI : les lignes à montant initial 0 créent des marchés à 0 €. Règle : Facturé = Payé + retenues détenues + reste à payer. Marché sans plafond : reste à facturer 0. Vrai dépassement : négatif conservé. Aucune écriture Sage, aucune facture modifiée, calcul HT du #1800 non repris.

## Fiches lues

- #1799 : le plafond lit la colonne montant HT du dépôt, la reprise est comptée une fois, la fenêtre détail lit le cache.
- #1800 : arrêt. `depot_factures.montant_ht` sert au ratio Sage multi-fournisseur. Ce lot ne recalcule pas ce HT.
- #1295 : le dépassement de plafond est une confirmation, pas un blocage. Un marché sans plafond saute cette confirmation.
- #1344 : la reprise SIWI est ajoutée une fois au facturé et au payé.
- #1638 / #1646 : les dépenses hors facture existent déjà et remontent au bilan.

## SYNTHÈSE

Le facturé affiché reste l'agrégat déjà en place (dépôts hors rejet + reprise une fois). La ventilation (retenues, reste à facturer, reste à payer) est calculée à l'affichage. Le HT envoyé à Sage n'est pas touché.

Retenues détenues = RG encore détenue + CP + OPC + CIE + finitions + pénalités. L'OPC est dans la somme pour que l'égalité au centime tienne.

Reste à payer = facturé − payé − retenues, plancher 0. Si le brut est négatif, la ligne est signalée.

### Mapping

| Libellé affiché | Valeur postée | Relu |
|---|---|---|
| Dépense hors facture | creation_mode=depense_hors_facture | état « Dépense hors facture », ligne de dépense manuelle, pas de marché |
| Marché sans plafond | creation_mode=marche_sans_plafond | case sans plafond cochée, badge Sans plafond |
| Case Sans plafond cochée | sans_plafond=1 | badge Sans plafond, reste à facturer 0 |
| Case décochée | sans_plafond=0 | pas de badge, reste = marché + avenants − facturé |

### Migration

`2026_10_07_120000_add_sans_plafond_to_marches_table.php`

Colonne `marches.sans_plafond` booléen, défaut faux, après `montant_marche_ht`. Puis les marchés non supprimés dont le montant est 0 et qui n'ont aucune ligne d'avenant passent à vrai. `down` retire la colonne. Pas de transaction autour du DDL.

Les marchés à 0 € déjà créés ne sont pas convertis en dépense. La liste des candidats (sans facture ERP) n'a pas été lue : MySQL local `127.0.0.1:3306` refusé. Commande de lecture seule, après migration :

php artisan tinker --execute="echo App\Models\Marche::query()->where('sans_plafond', true)->whereDoesntHave('factures')->get(['id','reference','intitule','montant_marche_ht'])->toJson();"

### Factures 260797 et 2608108

La case « Afficher aussi les factures en dépôt » ajoute des dépôts dont le numéro de facture est encore vide. Le tableau les identifiait par ce numéro : plusieurs lignes vides partageaient la même clé et une ligne pouvait s'afficher deux fois. La clé d'affichage est maintenant l'identifiant du dépôt. Aucune facture ni aucun dépôt n'a été supprimé. Les deux numéros n'ont pas été relus en base : s'il existe vraiment deux dépôts, ils restent tous les deux.

### Bundles

- Marches/Index.vue : Index-CShyBmvK.js
- Marches/Show.vue : Show-DaaFYfWY2.js
- Bilan/Index.vue : Index-06ySwJzh.js
- SuiviChantier/Index.vue (synthèse) : Index-D8vHIG-j.js
- SiwiImport/Index.vue : Index-r72PYhuv.js
- Factures/Global.vue : Global-CBN9KsRg.js
- DepotFactureSaisieForm.vue : DepotFactureSaisieForm-BqoOZVGL.js, DepotFactureSaisieForm-BUGfpGik.css
- app.js : app-CI2BpiKT.js

## DÉPLOIEMENT-TEST

git pull origin main
php artisan optimize:clear
php artisan view:clear
php artisan migrate --force
php artisan journal:sync

Tests prod PONTEILLA, non joués ici (pas de session écran) :

1. MCH-2026-253 DDFIP TARN et MCH-2026-255 avocat : reste à facturer 0, badge Sans plafond.
2. MCH-2026-252 PUP : reste à payer 117 944 € si aucune retenue.
3. Un marché avec RG et CP : facturé = payé + retenues + reste à payer, au centime.
4. Total programme : le reste à facturer n'additionne pas les négatifs des marchés sans plafond.
5. Import SIWI d'une ligne sans montant : choix proposé, dépense hors facture avec son poste.

## LEÇON

Le HT qui part en comptabilité et la ventilation affichée (facturé, payé, retenues, reste) ne vivent pas dans la même colonne. `sans_plafond` ne change pas le montant facturé : il met le reste à facturer à 0 et saute la confirmation de plafond.
