---
suivi: 1773
date: 2026-10-02
sujet: Synthèse CIE — ventilation facultative et saisie au clic
chantier: programmes / CIE
type: évolution
statut: poussé
hash:
fichiers:
  - app/Http/Controllers/CieController.php
  - app/Http/Controllers/Concerns/AuthorizesCieAccess.php
  - app/Services/CieService.php
  - routes/web.php
  - resources/js/Pages/Cie/Index.vue
  - resources/js/Pages/Cie/CieSyntheseGrille.vue
  - resources/js/Pages/Cie/CieOperationForm.vue
  - resources/js/Pages/DepotFactures/DepotFactureSaisieForm.vue
  - database/data/journal_mises_a_jour_post_2026_06_12.php
  - tests/Unit/CieSyntheseVentilation1773SourceTest.php
  - public/build/
---

# SUIVI #1773 — synthèse CIE, ventilation au clic

Fiche #1772 relue avant le code. Pas de migration.

## PROMPT ENVOYÉ

Trois évolutions sur Programme > Chantier > CIE : ventilation facultative dans la modale, nouvelle disposition de la Synthèse, saisie du ventilé au clic. Colonne Facturé uniquement via le lien existant retenue ↔ opération. Ne pas toucher au calcul des retenues, à Sage, ni à l’onglet Retenues d’un marché. Onglet Opérations inchangé.

## SYNTHÈSE

### Lien facture ↔ opération

Le lien existe. `cie_prelevements.cie_repartition_id` → `cie_repartitions` (`cie_operation_id` + `fournisseur_id`). La colonne Facturé somme les prélèvements actifs (statut différent de `annule`, hors soft-delete) dont le dépôt a passé le NV1 (`FactureStatutService::estAuDelaDeNv1Inclus`). Une case sans retenue NV1 affiche « — ». Le total d’opération à 0 s’affiche `0,00`. Aucune règle d’imputation inventée.

### Fournisseurs à plusieurs marchés travaux

Non compté. MySQL local `127.0.0.1:3306` / `erp_immo` : `SQLSTATE[HY000] [2002]` connexion refusée, comme au #1772. Le nombre n’est pas inventé. À l’écran, un fournisseur dans ce cas voit un choix de marché dans la cellule.

### Mapping du choix de marché

| Libellé affiché | Valeur postée | Colonne | Libellé relu côté back |
|---|---|---|---|
| `référence — intitulé` (ex. MCH-2026-111 — SNP) | `marche_id` (entier) | `cie_repartitions.marche_id` | même construction depuis `marches.reference` et `marches.intitule` |

Un seul marché travaux sur le programme : rattachement automatique, pas de liste.

### Ce qui a été fait

- Modale : montant HT obligatoire (> 0). Répartition facultative (0, partielle ou totale). Le bouton Enregistrer n’est plus bloqué par l’écart. Indicateur « Total réparti : X / Y » vert si égal au centime, orange sinon. Le montant HT de l’opération ne se change que dans la modale.
- Synthèse : une ligne par fournisseur ayant au moins un marché vivant dont le poste est rattaché à la rubrique de libellé « 4 — TRAVAUX » (variantes de tiret, ou libellé commençant par « 4. »). Marchés soft-supprimés exclus (Eloquent, pas `DB::table()`). Colonne 1 : nom + `intacct_id`. Colonne 2 : somme des montants ventilés. Puis une colonne par opération, date croissante, en-tête avanceur + libellé + date, sous-colonnes Ventilé (avec le % entier) et Facturé. Ligne « Total HT opération » juste sous l’en-tête : montant HT de l’opération sous Ventilé, somme facturée sous Facturé, somme des montants HT d’opérations en colonne 2. Colonnes Retenu HT et Reste HT retirées de cette vue.
- Couleurs de police seulement. % et montants orange si la ventilation n’est pas 100 %. Tirets gris. Case fournisseur facturée (facturé = ventilé, > 0, au centime) en vert, y compris si la colonne est orange. Ligne 2 de l’opération en vert si la somme facturée égale le montant HT.
- Clic sur Ventilé d’une ligne fournisseur : Entrée ou sortie du champ enregistre, Échap annule, vide ou 0 supprime la ventilation. Recalcul sans rechargement. Négatif refusé. Montant supérieur au HT de l’opération refusé. Somme des cellules supérieure au HT autorisée (orange). Une case déjà facturée ne peut pas descendre sous le facturé. Une ligne qui porte déjà une retenue active (même avant NV1) n’est pas supprimée : message explicite, pour ne pas détacher le prélèvement.
- Aucun statut clôturé / figé sur `cie_operations` (#1772) : toute opération reste éditable si le droit d’écriture est là.
- Aucun historique de modification des opérations : rien n’a été créé.
- Droit d’écriture : `m365GroupNames()` intersecté avec GS_ENVOL, GS_DSI, GS_ADMIN_ERP. Plus de passage par `hasAnyM365Group` (qui laissait passer `isAdminERP` avant l’intersection). La lecture est inchangée. La modale et la cellule partagent ce contrôle.
- La mise à jour d’une opération ne soft-delete plus toutes les répartitions avant de les recréer : mise à jour en place, pour que les prélèvements restent sur la ligne.
- Textes retirés : bandeau Lot / poste ; état vide « Aucune répartition vivante… soft-supprimées » remplacé par « Aucune opération CIE sur ce programme. » ; ligne de debug « CIE dues : ok · HTTP 200 · … » sur le bloc Retenues de la facture. Le motif « aucun CIE dû pour ce fournisseur sur ce marché » est conservé.
- Onglet Opérations, calcul des retenues en validation / comptabilisation, envoi Sage, récapitulatif Retenues d’un marché : non modifiés.

### Bundles

- `public/build/assets/Index-DzX16Uml.js` (page CIE)
- `public/build/assets/CieSyntheseGrille-hG0Te2zJ.js`
- `public/build/assets/CieOperationForm-BcR9nmhT.js`
- `public/build/assets/Index-CySwnueK.js` (chantier, embarque la page CIE)
- `public/build/assets/DepotFactureSaisieForm-gtr4tnlf.js`
- `public/build/assets/app-BhkrPq_h.js`

## DÉPLOIEMENT-TEST

```
git pull origin main
php artisan optimize:clear
php artisan view:clear
php artisan route:clear
php artisan journal:sync
```

Pas de migration. Pas de `npm` ni de `composer` sur le serveur.

Tests manuels, Louis Pasteur ENV13-001. Non exécutés ici : pas de MySQL local, pas de serveur web joignable pour l’écran.

1. Nouvelle opération, montant 900,00 HT, aucune ligne de répartition, Enregistrer. La colonne apparaît, % 0, montants en orange, total ventilé 0.
2. Clic sur Ventilé de deux entreprises de travaux, 450 puis 450. Le % passe à 100, police normale (plus orange). La colonne Total HT de chaque ligne et la ligne Total HT opération se mettent à jour sans recharger.
3. Saisir 0 (ou vider) dans une de ces cases, Entrée. La ventilation de cette entreprise disparaît (tiret), le % retombe (50 si l’autre 450 est encore là).
4. Saisir -10 : message « Le montant ne peut pas être négatif. » Saisir 1000 : message indiquant que le ventilé ne peut pas dépasser le montant HT de l’opération (900,00 € HT). La case n’est pas enregistrée.
5. Une entreprise qui a un marché travaux sur le programme et aucune opération CIE : sa ligne est visible, Total HT à 0,00, cases en tirets. S’il n’y a aucune opération, le texte « Aucune opération CIE sur ce programme. » est affiché au-dessus.
6. Usurpation Manon Koelh (hors GS_ENVOL, GS_DSI, GS_ADMIN_ERP) : les cases ne sont pas des champs. Un PATCH forgé vers `/programmes/{id}/cie/{operation}/ventilation` répond 403.
7. Onglet Retenues du marché SNP : les totaux sont ceux d’avant, le service de cet onglet n’a pas été modifié. Comparaison chiffrée non faite (base injoignable).

## LEÇON

Ne pas recréer les répartitions par soft-delete général : les prélèvements restent accrochés à l’ancienne ligne et le Facturé disparaît. Le Facturé se lit sur `cie_prelevements`, pas en devinant un marché. L’écriture CIE se vérifie avec `m365GroupNames()`, pas avec `hasAnyM365Group`. Il n’y a pas d’historique CIE à réutiliser : ne pas en créer un au passage.
