---
suivi: 1610
date: 2026-09-18
sujet: Diagnostic — acompte sur devis (hors APBILL)
chantier: comptabilite / depot-factures / sage
type: diagnostic
statut: poussé
hash: 822a18a8
fichiers:
  - docs/suivi/SUIVI_1610_diagnostic_acompte_sur_devis.md
---

# SUIVI #1610 — diagnostic acompte sur devis (aucun code applicatif)

## PROMPT ENVOYÉ

Hectare paie parfois un fournisseur sur DEVIS, sans facture. L’ERP ne
sait que FACTURE et AVOIR (APBILL). Un paiement sur devis doit passer
par le module Acompte Sage (409100, écriture unique, sans TVA).
Critère = la pièce reçue. Workflow ERP identique. Arbitrage A : l’acompte
payé compte dans le facturé. Diagnostic lecture seule, pas de code.

## FICHES LUES

- **#1599 LEÇON** — destination `PROJET` abandonnée ; CLASSID = programme.
  Impact : l’acompte n’est pas un « projet ». Ne pas rouvrir `destination=PROJET`.
- **#1604 LEÇON** — valideur = JSON `ocr_raw.saisie`, pas une colonne ;
  `saisie_historique` ≠ type de pièce. Impact : ne pas confondre acompte
  et saisie historique ; `Gate::before` / compte Robin ne prouve aucun 403.
- **#1601** — objets Sage déjà sync (`CLASS`, `VENDOR`, `LOCATION`…) ;
  `PROJECT` n’est pas un acompte. Impact : objet à chercher = AP, pas PROJECT.
- **#1605** — BILAN lit les dépôts via `sqlConditionCompteDansFactureApresNv1Valide`,
  sans filtre de type de pièce. Impact : un 3e type NV1-validé gonfle le facturé
  tout seul (double comptage).
- **#1264 / #1299** (code) — toggle tête FACTURE/AVOIR ; `DEVIS` déjà au
  catalogue mais **exclu** du dépôt. Impact : ne pas réutiliser le libellé
  `DEVIS` (pièce jointe ≠ type).

MySQL local `127.0.0.1:3306` refusé (`SQLSTATE[HY000] [2002]`).
`Schema::getColumnListing()` tenté, échec. Colonnes ci-dessous = migrations
+ modèles (`protected $table` / `$fillable`), pas d’invention.

---

## A. LE TYPE DE PIÈCE AUJOURD’HUI

### A.1 Où vit FACTURE / AVOIR

Pas un ENUM MySQL sur `depot_factures`. Deux couches redondantes :

| Couche | Table.colonne | Forme | Preuve |
|---|---|---|---|
| Référentiel | `types_factures.libelle` | `string` (pas un enum SQL) | `database/migrations/2026_04_22_100000_create_types_factures_table.php:12-13` + `TypeFacture::$table = 'types_factures'` |
| Fiche facture | `factures.type_facture_id` | FK nullable vers `types_factures.id` | `2026_04_22_100100_create_factures_table.php:16` + `Facture` fillable `type_facture_id` |
| Dépôt | **aucune colonne** `type_facture` | JSON `ocr_raw.saisie.type_facture` (chaîne) | `2026_05_07_143000_create_depot_factures_table.php` : `ocr_raw` json ; pas de `type_facture` |
| Constante PHP | `TypeFacture::LIBELLES_SAISIE` | `['FACTURE','DGD','RG','CIE','PRORATA','AVOIR','DEVIS']` | `app/Models/TypeFacture.php:11` |
| Catalogue rôles | `TypeFacture::catalogueReference()` | FACTURE → `conducteur_travaux` ; AVOIR et **DEVIS** → `responsable_programme` | `TypeFacture.php:29-57` |

Redondance : `FactureEstAvoir::check()` lit d’abord le JSON dépôt, puis
`factures.type_facture_id` → `types_factures.libelle` (`app/Support/FactureEstAvoir.php:25-36`).
Les listes SQL font de même (`depotEstAvoirSqlExpression`, L50-63).

`DEVIS` existe déjà au catalogue et au seeder, mais le front l’exclut du
dépôt (`TYPES_FACTURE_EXCLUS_DEPOT = ['RG','DEVIS','CIE','PRORATA']`,
`DepotFactureSaisieForm.vue:1486`). Robin : le devis est la **pièce jointe**,
pas le type. **Ne pas réutiliser `DEVIS`.** Nouveau libellé dédié
(ex. `ACOMPTE`) à ajouter au catalogue.

### A.2 Points d’usage (mesure du coût d’une 3e valeur)

**Branche binaire AVOIR vs le reste (à étendre, pas seulement whitelist) — 22 points**

| # | Fichier | Ligne | Quoi |
|---|---|---|---|
| 1 | `app/Support/FactureEstAvoir.php` | 28, 36, 57, 61-62 | Cœur SQL + PHP « est un avoir » |
| 2 | `app/Http/Controllers/ComptabilisationController.php` | 458 | `depotEstTypeAvoir` |
| 3 | idem | 778 | Branche envoi : avoir → `AvoirIntacctApbillService` au lieu d’APBILL |
| 4 | idem | 1053-1059 | Signe du HT (négatif avoir / positif facture) |
| 5 | idem | 1206 | Spec APBILL avoir réservé |
| 6 | `app/Http/Controllers/DepotFactureController.php` | 971-978 | HT négatif réservé aux avoirs |
| 7 | `app/Services/AvoirIntacctApbillService.php` | 34 | Garde type avoir |
| 8 | `app/Services/ApplicationAvoirDepotService.php` | 72, 254 | Application avoir → facture |
| 9 | `app/Support/StatutAvoir.php` | 32 | Badge application |
| 10 | `app/Support/FactureFriseEtapePresenter.php` | 92, 99-107 | Frise : avoir saute la chaîne paiement Sage, étape « en attente d’application » |
| 11 | `app/Services/FactureGlobaleListeQuery.php` | 244-255 | Onglet « Avoirs » |
| 12 | `app/Support/FactureGlobaleEtapeStatutFilter.php` | 186 | Filtre avoir |
| 13 | `app/Services/Compta/ComptaTableauBordService.php` | 832-850 | Compteurs / filtre AVOIR |
| 14 | `app/Http/Controllers/FactureController.php` | 894, 913 | Prop `est_avoir` fiche |
| 15 | `resources/js/Pages/Factures/Show.vue` | 36, 840 | Entête « Avoir » / « Facture » (binaire) |
| 16 | `resources/js/Pages/Factures/TableauBord.vue` | 374 | Bordure or si avoir |
| 17 | `resources/js/Pages/Factures/Global.vue` | 1474 | Onglet « Avoirs (n) » |
| 18 | `resources/js/Pages/DepotFactures/DepotFactureSaisieForm.vue` | 1488-1514, 1491, 4861, 9199-9228 | Toggle 2 boutons + `isTypeFactureAvoir` (signes compta) |
| 19 | `DepotFactureSaisieForm.vue` | 1535-1556 | Équilibre débit/crédit vs TTC (avoir = magnitudes) |
| 20 | `app/Http/Controllers/FactureController.php` | 257, 376 | `marche_id` required si `validateur_nv1_role === conducteur_travaux` (création hors dépôt) |
| 21 | `app/Http/Middleware/HandleInertiaRequests.php` | 259-275 | Badges menu NV1 par **rôle du type**, pas par libellé |
| 22 | `app/Http/Controllers/ValidationController.php` | 185-201 | File NV1 filtrée par rôle du type |

**Whitelist / persistance (doivent accepter le nouveau libellé) — 12 points**

| # | Fichier | Ligne |
|---|---|---|
| 23 | `TypeFacture.php` | 11 `LIBELLES_SAISIE`, 27-63 catalogue, 111-134 `libellesPourSaisie` |
| 24 | `DepotFactureController.php` | 1060, 1173, 2288, 2373, 2447, 2856, 4663 |
| 25 | `DepotFactureEngagerWorkflowService.php` | 53-57, 98, 154 |
| 26 | `DepotFactureValiderDejaValideService.php` | 146-150, 198, 254 |
| 27 | `database/seeders/TypesFacturesSeeder.php` | 15 |
| 28 | `database/migrations/2026_05_19_120000_ensure_types_factures_reference.php` | 18 |
| 29 | `Facture.php` | 56, 174 relation `typeFacture` |
| 30 | `Validations/Index.vue` | 264, 354 affichage `typeFacture.libelle` |
| 31 | `DepotFactureSaisieForm.vue` | 509 défaut `'FACTURE'`, 6882 / 7100 / 7477 persist |
| 32 | `MarcheController.php` | 3406-3425 type « REVERSEMENT CP » (autre sujet, ne pas toucher) |
| 33 | `FactureController.php` store/update `type_facture_id` | 261, 302, 380, 420 |
| 34 | Tests (hors coût prod, à étendre) : `ApplicationAvoirDepotTest`, `ComptabilisationAvoir*`, `StatutAvoirTest`, `FactureEstAvoirSqlTest`, `DepotFactureAvoirMontantValidationTest` |

**Total à modifier pour un 3e type : ~34 points applicatifs + tests.**
Dont **22 branches binaires** (le vrai coût) et **12 whitelist/affichage**.
Les agrégats marché/bilan (section D) **ne testent pas** le type : ils
avaleront l’acompte sans changement — c’est le double comptage.

NV1/NV2 **ne lisent pas** le libellé pour autoriser une étape. Ils lisent
`types_factures.validateur_nv1_role` et le JSON `ocr_raw.saisie.validateur_id`
(#1604). Si le 3e type reprend `conducteur_travaux` comme FACTURE, la file
NV1 est la même. Si on copie DEVIS (`responsable_programme`), **autre file**.

---

## B. LE CHEMIN SAGE ACTUEL

### B.1 Chaîne FACTURE → APBILL

1. `ComptabilisationController` (user GS_COMPTABILITE / GS_ADMIN_ERP / GS_DSI,
   `ensureComptabilisationUser` L92-104 — `hasAnyM365Group`, donc
   **Gate::before / compte Robin = faux positif**).
2. Si `depotEstTypeAvoir` → `AvoirIntacctApbillService` (L778-784) :
   aujourd’hui avoir = ERP + spec, envoi Sage avoir à part (#643e).
3. Sinon `envoyerDepotApbillEtPaiementIntacct` (L1459+) :
   - CLASSID : saisie `analytics_id` puis, destination PROGRAMMES,
     `resolveProgrammeClassIdForApbill` (L76-89) =
     `programmes.intacct_class_analytics_id` → legacy `intacct_class_id` → saisie.
   - LOCATION : `etablissement_id` → `etablissements.location_id` (L1511-1514).
   - Entité session : `entites.intacct_id` = LOCATIONENTITY (L1464-1468).
   - Lignes : `saisie.lignes_comptables` (HT, TVA 445, 401, retenues 401700/401800…).
   - `when_due` : JSON `ocr_raw.date_echeance` ou `saisie.date_echeance`, sinon date facture (L1671-1676, L1777).
   - `doc_number` : `depot_factures.numero_facture` ou `DEPOT-{id}` (L1378-1379).
4. `IntacctService::createApbillWithOptionalAppymt` (L2248) →
   `buildExtBillCreateFromApbillSpec` (L1882) →
   `App\Intacct\Functions\AccountsPayable\ExtBillCreate` (SDK local, car le
   SDK vendor n’expose pas ExtBillCreate).
5. APPYMT **ensuite**, écran mise en paiement :
   `PaiementAppymtExecutionService` + `createAppymtForBill` /
   `buildAppymtCreateXml` (`FINANCIALENTITY` + `PAYMENTMETHOD`).
   Commentaire L1690 : APPYMT désactivé dans le flux « déjà réglée » ;
   marquage payé ERP sans paiement Sage.

### B.2 Objets Sage déjà implémentés dans le connecteur

**Création (`<create>` dans `IntacctService.php`)**

| Objet XML | Usage | Ligne |
|---|---|---|
| `VENDOR` | création fournisseur | 1205 |
| `APBILL` | facture AP (via ExtBillCreate SDK) | 2287, 2676 |
| `APPYMT` | paiement AP | 2424 |
| `CUSTOMER` | client appel de fonds | 4253 |
| `ARINVOICE` | facture AR appel de fonds | 4310 |
| `SUPDOCUMENT` | pièce jointe | 1526, 4137 |

**Lecture `readByQuery`** : `VENDOR`, `CLASS`, `PROJECT` (#1607), `EMPLOYEE`,
`GLACCOUNT`, `DEPARTMENT`, `LOCATIONENTITY`, `LOCATION`, `CHECKINGACCOUNT`,
`CREDITCARD`, `APBILL`.

**Aucune occurrence** de `APADVANCE`, `APADVANCEITEM`, `create_advance`,
`ADVANCEKEY` dans `app/` (grep 2026-09-18).

### B.3 Nom exact de l’objet Acompte

**Objet XML Intacct : `APADVANCE`** (lignes `APADVANCEITEM`).
Module UI Sage : Comptes fournisseurs > Paiements > Acomptes.
Preuves hors code ERP (le connecteur ne l’implémente pas) :

- Glossaire objets AP Sage : XML `APADVANCE` / `APADVANCEITEM`
  (https://www.intacct.com/ia/docs/en_US/help_action/Reporting/Object_glossary_for_custom_reports/ap_payment_objects.htm)
- Application sur une facture AP : `APPYMTDETAIL` avec `ADVANCEKEY`,
  `ADVANCEENTRYKEY`, `TRX_POSTEDADVANCEAMOUNT`
  (https://developer.intacct.com/api/accounts-payable/ap-payments/)
- Création UI : défaut GL = compte « Supplier advances » de Configure AP
  (https://www.intacct.com/ia/docs/en_GB/help_action/Accounts_Payable/Advances/create-an-advance.htm)
- Mapping DataBlend (champs create) : `advancedate`, `vendorid`,
  `paymentmethod` (printedCheck / creditCard / eft / cash / ach),
  `financialentityid` **requis**, ligne `entry_classid` et `entry_locationid`
  **requis** (https://lp.datablend.com/knowledge/ap-advance-sage-intacct-data-target-requirements)

Le portail developer Intacct **n’a pas** de page dédiée « AP Advances »
dans le menu AP (Bills, Payments, Adjustments… — pas Advances). Création
attendue via fonction générique `<create><APADVANCE>…` comme `APPYMT`,
pas via ExtBillCreate.

**Le connecteur ne sait pas créer un Acompte.** Ampleur d’un ajout :
nouveau builder XML (même famille que `buildAppymtCreateXml`, ~80–150
lignes) + branche dans `ComptabilisationController` à côté de L778 +
persistance RECORDNO (colonne nouvelle ou JSON, **Phase 2 schéma**) +
pas de ventilation TVA. **Écart métier important** : un APADVANCE **est
un paiement à la création** (`FINANCIALENTITY` + `PAYMENTMETHOD`
obligatoires). Un APBILL est une dette, payée plus tard en APPYMT.
Le workflow ERP « identique jusqu’à comptabilisation » ne dit pas si
la mise en paiement ERP est sautée.

### B.4 Résolutions réutilisables ?

| Dimension Sage acompte | Source ERP actuelle | Réutilisable ? |
|---|---|---|
| CLASSID | `resolveProgrammeClassIdForApbill` / `intacct_class_analytics_id` | Oui, même fonction |
| LOCATION / établissement | `etablissements.location_id` | Oui |
| Entité (session) | `entites.intacct_id` | Oui |
| Compte bancaire `FINANCIALENTITY` | `programmes.financial_entity_id` → `intacct_financial_entities` ; override `ocr_raw.mise_en_paiement.financial_entity_intacct_id` ; sync `CHECKINGACCOUNT` | **Oui en données, non en timing** : aujourd’hui exigé à l’APP YMT, pas à l’APBILL. Pour APADVANCE : exigé **à la comptabilisation** |
| Mode de règlement | `PaiementModeReglementMapper` → PAYMENTMETHOD APPYMT | Oui, mêmes libellés. Doc Sage : ACH standard **pas** dispo pour advances (UK/AU help). À confirmer sur le tenant Hectare |
| Compte 409100 | — | Voir B.5 |

### B.5 Compte 409100

Grep `app/` + `database/` + `resources/` : **zéro** occurrence de `409100`
ni `4091`. Pas de rattachement code à un `postes_budgetaires_types`.

Le plan de comptes ERP est `comptes_comptables.accountno` (sync
`GLACCOUNT`). Sans MySQL, impossible de dire s’il est en base locale.
À vérifier en prod : `SELECT id, accountno, titre, statut FROM comptes_comptables WHERE accountno LIKE '409%';`
et le paramétrage Sage « Supplier advances » (Configure AP).

Les retenues actuelles utilisent `401700` (RG) et `401800` (CP)
(`DepotFactureSaisieForm.vue:100, 6490-6516`) — autre famille, ne pas confondre.

---

## C. LE WORKFLOW

### C.1 Droits / étapes vs type de pièce

Les étapes NV1 / NV2 / comptabilisation **ne testent pas** `libelle`
FACTURE vs AVOIR pour autoriser. Elles s’appuient sur :

- statut dépôt / `facture_id` / workflows ;
- `ocr_raw.saisie.validateur_id` (JSON, #1604) ;
- `types_factures.validateur_nv1_role` pour **router** la file NV1
  (`ValidationController.php:185-201`, `HandleInertiaRequests.php:259-275`).

Exception **après** comptabilisation : la frise avoir
(`FactureFriseEtapePresenter.php:99-107`) remplace
préparation_paiement → payée par « en attente d’application ».
Un acompte n’est ni un avoir ni une facture AP : la frise paiement
Sage actuelle est **fausse** pour un APADVANCE déjà payé à l’émission.

`FactureController::store` (création hors dépôt, L257) exige `marche_id`
si le rôle du type est `conducteur_travaux` (FACTURE). Le dépôt
(`depotFactureValiderValidationRules` L1175) laisse `marche_id` **nullable**
pour tous les types. Robin « rattachement marché oui » : le dépôt le
permet déjà ; il n’est pas obligatoire.

### C.2 Retenues — déclenchement et neutralisation

Déclenchées **si les % / montants du formulaire sont > 0**, pas par le type :

1. Prefill à la sélection marché (après mount L7364
   `marcheRetenuesPrefillEnabled = true`) :
   `applyRetenuesPrefillDepuisMarche` (`DepotFactureSaisieForm.vue:2228-2256`)
   copie `marches.retenue_garantie_pct`, `compte_prorata_pct`, `taux_opc_pct`.
2. `genererEcrituresDepuisInformations` → `buildLignesComptablesDepuisStructure`
   (L6490+) : crédit `401700` / `401800` / CIE / finitions / OPC si glob > 0.
3. Poste budgétaire : flags `applicable_retenue_garantie` /
   `applicable_compte_prorata` / `applicable_cie` sur
   `postes_budgetaires_types` (masquent les panneaux marché, pas le type de pièce).
4. KPI marché : `MarcheCouvertureKpiService` (retenues **en plus** du facturé).
5. Synthèse : `EcrituresComptablesCalculator::computeRetenuesGlobales` sur
   **chaque** dépôt du marché (`MarcheSyntheseFinanciereService.php:592-600`)
   — si l’acompte a des % marché, ils sont repris.

**Neutralisation propre possible** : gater (1) et (2) sur le nouveau type
(forcer % à 0, ne pas préremplir, ne pas émettre les lignes 401700/401800).
Ne pas toucher aux flags poste. Ne pas passer par `saisie_historique`.

Confirmé : les retenues **s’appliquent aujourd’hui** dès qu’un marché a des
taux, y compris si on collait un devis sur une « FACTURE ». Sans gate type,
un acompte rattaché à un marché **prendrait** RG/CP.

---

## D. 🔴 DOUBLE COMPTAGE

Avec l’arbitrage A, un acompte NV1+/payé est un `depot_factures` comme une
facture. **Aucun agrégat ne filtre le type.** La facture de solde (100 % du
marché) s’ajoute à l’acompte → facturé / avancement / reste faux.

### D.1 Calculs concernés (fichier + ligne)

| Indicateur | Fichier | Ligne | Formule / source |
|---|---|---|---|
| Cache `marches.total_facture_ht` | `Marche.php` `recalculer` | 317-329 | `totauxCachePourMarche()['paye_ht']` (nom trompeur : écrit le **payé**) |
| Cache `marches.pct_avancement` | `Marche.php` | 321, 330 | `facture_ht / montant_total_ht * 100` **sans clamp** |
| Cache `marches.reste_a_regler_ht` | `Marche.php` | 325, 331 | `montant_total_ht − facture_ht − pénalités` |
| `facture_ht` live | `MarcheMontantsAgregesService.php` | 27-34, 196-211 | dépôts `montantsHtAgregesPourMarche` + reprise |
| Agrégat dépôts | `DepotFacture.php` `montantsHtAgregesPourMarche` | 707-763 | tous dépôts `pourMarche`, hors rejet/archive, **sans type** |
| Bilan facturé NV1 | `BilanFinancierService.php` `buildFactureNv1ByMarche` | 1236-1260 | `sqlConditionCompteDansFactureApresNv1Valide` + SUM `d.montant_ht` |
| Bilan hors marché | idem `buildDepotsHorsMarcheKpiParPoste` | 1280-1316 | même condition NV1 |
| Condition « facturé » | `DepotFacture.php` | 454-488 | NV1 validée / preuves aval / historique — **pas de type** |
| Synthèse marché `total_facture_ht` | `MarcheSyntheseFinanciereService.php` | 177-180, 553-574 | SUM HT historique dépôts hors rejet |
| `reste_a_facturer_ht` | idem | 207 | plafond − total_facture_ht |
| `reste_a_payer_ht` | idem | 205 | plafond − payé |
| Frise / KPI couverture | `MarcheCouvertureKpiService.php` | 208+ | facture_ht dépôts |
| Liste marchés KPI | `MarcheController.php` | 2252-2257, 2443-2448, 3003-3027 | totaux dépôts + reprise |
| Recalc déclenché à la compta | `ComptabilisationController.php` | 513 `mettreAJourCalculsMarche` | après avoir ; même helper côté facture |

### D.2 `pct_avancement` — définition et dépassement

Migration `2026_04_20_100100_create_marches_table.php:33` :

`decimal('pct_avancement', 5, 2)->default(0)`

- Type : DECIMAL(5,2) signé (pas UNSIGNED dans la migration).
- Précision : 3 chiffres avant virgule, 2 après.
- Plage MySQL : **-999.99 à 999.99**.
- Cast Eloquent : `'pct_avancement' => 'decimal:2'` (`Marche.php:356`).
- Écriture : `round($pctAvancement, 2)` **sans min/max** (`Marche.php:321-330`).

`SQLSTATE 22003` (Out of range) si la valeur sortie de `recalculer`
n’entre pas dans ±999.99, et `saveQuietly()` **fait échouer l’enregistrement
du marché** (le dépôt peut déjà être comptabilisé).

Chiffres réalistes acompte+solde :

- Acompte 30 % + solde 100 % = **130,00** → **tient** dans (5,2).
- Acompte 100 % + solde 100 % = **200,00** → tient.
- Overflow seulement si facturé / marché > **9,9999×** (ex. marché au
  montant quasi nul, ou empilement d’acomptes + soldes sans application).

Donc à ~100 acomptes/an le **22003 n’est pas le risque fréquent** ;
le risque fréquent est un **facturé / avancement / reste faux** (130 %
affichés, reste négatif) **sans** erreur SQL. Le 22003 redevient réel
si `montant_marche_ht` est aberrant (très petit) — cas déjà possible
aujourd’hui dès qu’une facture dépasse ~10× le marché.

Aucune occurrence `22003` dans `docs/suivi/` ; le rappel du prompt
n’a pas de fiche chiffrée. La plage DECIMAL(5,2) suffit à l’expliquer.

### D.3 Phase 2 — application de l’acompte sur la facture de solde
**(proposé, non implémenté)**

Précédent interne : table `application_avoir` (`avoir_facture_id`,
`facture_id`, `montant_applique`) + `ApplicationAvoirDepotService`.
Côté Sage : `APPYMTDETAIL.ADVANCEKEY` + `TRX_POSTEDADVANCEAMOUNT`.

À stocker plus tard (ne pas créer maintenant) :

1. Identifiant Sage acompte : `RECORDNO` APADVANCE (+ `ADVANCEENTRYKEY` de ligne).
2. Montant TTC (HT=TTC) encore disponible.
3. Lien optionnel `facture_solde_id` / table d’application (miroir avoir).
4. Statut : ouvert / partiel / épuisé (comme `StatutAvoir`).

Mécanisme proposé :

- À la saisie de la facture de solde, proposer « imputer l’acompte »
  (montant, pièce). Le HT facturé marché de la facture de solde est
  **net d’acompte déjà compté**, **ou** l’acompte est retiré des agrégats
  une fois appliqué (un seul des deux, jamais les deux).
- À la comptabilisation / paiement Sage de la facture : APPYMT avec
  `ADVANCEKEY` (doc officielle AP Payments).
- Tant que Phase 2 n’existe pas : **interdire** l’arbitrage A en prod,
  ou accepter un facturé gonflé, ou ne **pas** compter l’acompte dans
  `montantsHtAgregesPourMarche` (contredit A).

---

## E. ÉCRANS IMPACTÉS

| Écran | Fichier | Type affiché ? | Impact 3e valeur |
|---|---|---|---|
| Saisie dépôt | `DepotFactureSaisieForm.vue` | Toggle 2 boutons L9199-9228 | **Oui** — voir E.1 |
| Liste dépôts | `DepotFactures/Index.vue` | libellé FACTURES nav, pas le type pièce | Filtre / colonne à décider |
| Liste globale | `Factures/Global.vue` | onglet Avoirs L1474 ; hors-avoirs ailleurs | 3e onglet ou badge |
| Fiche facture | `Factures/Show.vue` | `est_avoir` → « Avoir » sinon « Facture » L840 | Binaire, cassé pour acompte |
| Tableau de bord | `Factures/TableauBord.vue` | bordure si `est_avoir` | Badge |
| Comptabilisation | `Factures/Comptabilisation/Index.vue` | réutilise le formulaire saisie | Branche Sage |
| Validations NV1/NV2 | `Validations/Index.vue` | `typeFacture.libelle` | Affiche le nouveau libellé tel quel |
| Fiche marché / frise | `Marches/Show.vue` onglet FACTURES ; `MarcheSyntheseFinanciere*` | pas le type ; SUM HT | Chiffres faux (D) |
| Bilan programme | `BilanFinancierService` + UI bilan | pas le type | Facturé NV1 gonflé |
| Budget / postes | mêmes dépôts NV1 | pas le type | idem |
| Export Excel factures | **aucun** export liste factures trouvé (PDF zip pièces : `FacturePdfZipService`, pas le type) | — | — |
| Export Excel marché | non listé comme export du type | — | KPI faux si on exporte les totaux |
| Menu | `AppShell.vue` | « FACTURES » | Cosmétique |

### E.1 Toggle FACTURE / AVOIR — regarder le composant, pas supposer

Pas un `SelectButton` PrimeVue. Deux `<button class="flex-1">` dans un
`div.flex` largeur unique (`DepotFactureSaisieForm.vue:9202-9228`).
Chaque segment prend 50 %. Un 3e segment « ACOMPTE SUR DEVIS » (18
caractères) **ne tient pas** : overflow ou texte cassé sur sm.

Options UI (à arbitrer, pas coder) :

- Libellé court **ACOMPTE** + ligne d’aide sous le toggle (« Paiement
  sur devis, sans facture — compte 409100, sans TVA »).
- Select 3 options au lieu du toggle 2.
- Garder le toggle 2 et un 3e choix ailleurs : **contre** l’objectif
  « même endroit que FACTURE/AVOIR ».

Les types hors toggle (DGD, et aujourd’hui DEVIS s’il était en base)
tombent sur un badge ambre lecture seule (L9231-9238) : inacceptable
pour un flux ~100/an.

### E.2 « Num Facture »

| | |
|---|---|
| Colonne dépôt | `depot_factures.numero_facture` nullable string 100 (`create_depot_factures:21`) |
| Colonne facture | `factures.numero_facture_fournisseur` |
| JSON | `ocr_raw.saisie` recopié |
| Validation Valider | **nullable** (`DepotFactureController.php:1168`) — **pas obligatoire** |
| Doublon | `DepotFactureDuplicateDetector` compare ce numéro |
| APBILL `DOCNUMBER` | ce champ, sinon `DEPOT-{id}` (`ComptabilisationController.php:1378-1379`) |
| Label UI | dur « Num Facture » L9160 et L9243 |

On **peut** changer le libellé selon le type **sans** changer la
validation (toujours nullable). Pour Sage, `DOCNUMBER` / référence
acompte peut rester ce champ (numéro de devis). Anti-doublon : un
même n° de devis redéposé serait signalé — souhaitable.

### E.3 « Échéance »

Pas de colonne `depot_factures.date_echeance` (aucune migration).
Stockage : JSON `ocr_raw.date_echeance` ou `saisie.date_echeance`
(`DepotFacture::resolveDateEcheance` L282-298).

`factures.date_echeance` existe mais le Valider dépôt l’écrit à **`null`**
(`DepotFactureController.php:2385`).

Aval :

- APBILL `when_due` (L1370-1376, 1671-1676) ;
- `date_paiement_previsionnel` au saving si échéance présente (L262-265) ;
- sinon date_facture + 30 jours (L269-270) ;
- UI DatePicker « Échéance » L9283.

Pour un acompte (paiement immédiat Sage), l’échéance **n’a pas le même
sens** (APADVANCE = `ADVANCEDATE`, pas DATEDUE de facture). On peut
garder le champ comme date de paiement / date d’acompte sans toucher
à la validation (nullable). À arbitrer le libellé (« Date de l’acompte »).

---

## DÉCOUPAGE EN LOTS (risque)

| Lot | Contenu | Risque | Dépend de |
|---|---|---|---|
| 1610 | ce diagnostic | — | — |
| A | Type `ACOMPTE` (pas `DEVIS`) : catalogue, toggle 3e choix court + aide, HT=TTC / TVA 0, num devis, date, **retenues forcées à 0**, persistance JSON + `type_facture_id`. **Zéro Sage.** | Moyen (formulaire monstre `DepotFactureSaisieForm.vue`) | Arbitrage libellé + rôle NV1 |
| B | Workflow : même file NV1 que FACTURE (`conducteur_travaux` + valideur JSON). Frise : ne pas copier l’avoir. Tests **sans** compte Robin. | Faible si rôle = FACTURE ; élevé si on copie DEVIS | A |
| C | Connecteur `APADVANCE` + branche comptabilisation (pas APBILL). Banque + mode **exigés à la compta**. Compte 409100. | **Élevé** (API mal documentée, tenant, ACH) | A, B, sonde lecture APADVANCE (patron #1570, web GS_DSI, pas CLI) |
| D | Double comptage : **ne pas livrer A en prod comptable marché** sans règle. Soit exclure l’acompte des agrégats jusqu’à E, soit livrer E en même temps. Clamp `pct_avancement` 0–999.99 pour le 22003. | **Élevé métier** | Arbitrage A vs exclusion temporaire |
| E | Phase 2 application acompte → facture de solde (table type `application_avoir` + APPYMT `ADVANCEKEY`) | Élevé, plus tard | C en prod, RECORDNO stocké |

Ne pas fusionner C et le formulaire dans un seul commit : un APBILL
envoyé par erreur sur un devis est irréversible côté Sage.

---

## POINTS RESTANT À ARBITRER PAR ROBIN

1. **Libellé** stocké : `ACOMPTE` vs `ACOMPTE_SUR_DEVIS` — et libellé court du 3e bouton. Ne pas réutiliser `DEVIS`.
2. **`validateur_nv1_role`** : `conducteur_travaux` (comme FACTURE, même file) ou `responsable_programme` (comme AVOIR/DEVIS) ? « Workflow identique » impose le premier.
3. **Mise en paiement ERP** : sautée (l’APADVANCE paie à la création) ou conservée (alors double paiement Sage) ?
4. **Déjà réglée** : un acompte est-il forcément `deja_reglee`, ou suit-il le toggle actuel ?
5. **Double comptage** : livrer le type **sans** le compter dans le marché jusqu’à la Phase 2, ou livrer l’application en même temps que le type ?
6. **409100** : confirmer en Sage (Configure AP + GL) et en `comptes_comptables` prod. Qui choisit le compte si le défaut Sage ≠ 409100 ?
7. **ACH / virement** : doc Sage dit ACH indisponible pour advances — le tenant Hectare le permet-il ?
8. **Échéance** : date d’acompte = date de paiement, ou on masque le champ ?
9. **Sonde prod** `inspect` / `readByQuery` objet `APADVANCE` (web GS_DSI, pas CLI OVH) avant le lot C ?

## GARDE-FOUS RAPPELÉS

- FACTURE / AVOIR : ne rien changer dans ce diagnostic (rien changé).
- Destination PROJET #1599 : hors sujet.
- `saisie_historique` #1604 : hors sujet.
- `validateur_id` n’est pas une colonne `depot_factures`.
- `marches.montant_marche_ht` (pas `montant_ht`).
- Droits : ne pas valider NV1/NV2 avec le compte Robin (`Gate::before`).

## DÉPLOIEMENT

Documentation seule. OVH : `git pull origin main`. Pas de migrate, pas de
`journal:sync`, pas de npm.

## LEÇON

Un 3e type n’est pas un ENUM SQL à étendre : c’est une **chaîne JSON +
FK** et surtout **22 branches « est un avoir »**. Le connecteur Sage
ne connaît que APBILL/APPYMT/ARINVOICE : l’objet acompte s’appelle
**`APADVANCE`** et **n’existe pas** dans `IntacctService`. Compter
l’acompte dans le facturé (arbitrage A) ne demande **aucun** changement
d’agrégat — c’est pour ça que le double comptage est automatique.
`pct_avancement` DECIMAL(5,2) casse à 1000 %, pas à 130 %. Ne jamais
réutiliser le libellé catalogue `DEVIS` : déjà là, déjà exclu du toggle,
et Robin l’a défini comme pièce jointe.
