---
suivi: 1711
date: 2026-09-25
sujet: Diagnostic — projet Sage dans la comptabilisation
chantier: compta
type: diagnostic
statut: poussé
hash:
fichiers:
  - docs/suivi/SUIVI_1711_diag_projet_dans_comptabilisation.md
---

# SUIVI #1711 — diagnostic projet dans la comptabilisation

Aucun code applicatif. Décision A/B non tranchée. Départ `0a76e87e`.

## 1. Points qui envoient une dimension analytique

| Écriture | Niveau | Dimension | Source de la classe | XML |
|---|---|---|---|---|
| APBILL facture | ligne `APBILLITEM` | CLASSID obligatoire ; PROJECTID seulement si `line.project_id` non vide — aucun appelant ne le remplit | `resolveProgrammeClassIdForApbill` : `intacctClassAnalytics.classid`, puis `intacctClass.classid` (legacy), puis `saisie.analytics_id` | `ExtBillCreate.php:90` PROJECTID, `:95` CLASSID. Spec : `IntacctService.php:2011` et `:2023-2025` |
| APBILL avoir | même ligne, montants négatifs | même CLASSID | même méthode, `ComptabilisationController.php:1249-1276` | même builder |
| APBILL retenues | mêmes lignes, montants négatifs | même `classIdForBill` recopié | `ComptabilisationController.php:3052` et suivantes | même builder |
| APBILL multi-fournisseurs | un CLASSID pour tous les bills | pas de classe par ventilation | `envoyerApbillsMultiFournisseurVentile` reçoit `$classIdForBill` (`:2272`) | même builder |
| APADVANCE acompte | ligne `APADVANCEITEM` | CLASSID obligatoire | `AcompteIntacctApadvanceService.php:303-342`, même `resolveProgrammeClassIdForApbill` | `ApadvanceCreateXmlBuilder.php:86`. PROJECTID interdit (`:96-97`) |
| APPYMT paiement | en-tête + détail `RECORDKEY` | aucune | — | `IntacctService.php:2543-2558` : FINANCIALENTITY, PAYMENTMETHOD, VENDORID, PAYMENTDATE, RECORDKEY, TRX_PAYMENTAMOUNT |
| ARINVOICE appel de fonds | ligne | CLASSID si non vide, sinon balise absente | `code_analytique`, puis `code_analytique_compta`, puis `intacctClassAnalytics` (`AppelDeFondsIntacctSyncService.php:83-106`) | `IntacctService.php:4613` et `:4635`. Pas de PROJECTID |

Session d’écriture facture / paiement / avoir / acompte : `sendComptabilisationRequest` avec `entites.intacct_id` (`IntacctService.php:186-197`). Création de projet : `sendRequest`, session générale (`:153-156`, `creerProjet` `:814`).

`analytique_par_projet` n’est lu par aucun de ces envois (`AnalytiqueParProjetNonLuParSageSourceTest`).

## 2. PROJECTID côté Sage — ce que le code sait

- Objet PROJECT : création et lecture au niveau général. Champs envoyés : PROJECTID, NAME, PROJECTCATEGORY `Contract`, PROJECTTYPE selon enseigne, STATUS `active`. Pas de LOCATIONID à la création (`IntacctService.php:783-793`).
- APBILL : le SDK écrit PROJECTID sur la ligne si `project_id` est fourni. Aucune facture ne le fournit.
- APADVANCE : le builder refuse explicitement la balise PROJECTID.
- APPYMT : pas de dimension.
- ARINVOICE : pas de PROJECTID dans le XML écrit.
- Contrainte « projet actif », « dimension projet activée sur le module fournisseurs », « projet du niveau général utilisable dans une session entité » : inconnue dans le code. Ne pas l’inventer.

## 3. Multi-entité

Le code ne le dit pas. Un projet est créé sans LOCATIONID, en session générale. Une facture part dans la session de l’entité (`E-04`, `E-13`…). La lecture unitaire `lireProjetParProjectId` (`IntacctService.php:749`) est aussi en session générale et ramène LOCATIONID s’il existe.

Le panneau « Lecture Sage brute » (#1692) lit l’APBILL et l’APPYMT d’une facture déjà envoyée. Il ne lit pas un PROJECT, ni dans la session entité. Contrôle web à ajouter (lecture seule, pas dans ce lot) : même PROJECTID, une fois en session générale, une fois dans `getSessionIdForComptabilisation` de l’entité de la facture test. La CLI ne joint pas Sage.

## 4. Cas particuliers

- Frais généraux (`destination` ≠ `PROGRAMMES`) : la classe vient de `saisie.analytics_id`, pas du programme. Pas de projet programme à envoyer. Le blocage classe reste.
- Multi-fournisseurs : un seul `programme_id` de saisie, un seul CLASSID recopié sur chaque bill. Pas de ventilation par programme (`DepotFactureVentilation` n’a pas `programme_id`).
- Nouveau système sans projet : aujourd’hui bloqué comme sans classe. À trancher au lot correctif : bloquer avec un message « projet Sage manquant », ne pas envoyer sans dimension.
- Sans classe et sans projet : reste bloqué.
- Sans classe mais avec projet (`intacct_projects.projectid = code_envol`) : aujourd’hui bloqué à l’envoi. Règle Robin : ne plus bloquer ; la facture part avec le projet. Ce qu’on envoie si classe ET projet dépend de A/B, non tranché.
- Hors ERP : le drapeau n’est pas lu à l’envoi. Si la facture a une destination PROGRAMMES, la même exigence de classe s’applique.

## 5. Contrôles qui exigent la classe

| Lieu | Message | Nouvelle condition proposée |
|---|---|---|
| `ComptabilisationController.php:1252` avoir | `Classe analytique (CLASSID) introuvable.` | classe de saisie, ou programme avec classe, ou projet miroir |
| `:1272` avoir programme | `n’a pas de classe Sage Intacct (CLASSID).` | classe OU projet présent |
| `:1504` facture | `Classe analytique (CLASSID) introuvable.` | idem avoir |
| `:1527` facture programme | `n’a pas de classe Sage Intacct (CLASSID). Renseignez-la sur la fiche programme.` | classe OU projet ; si projet seul, ne pas exiger la classe |
| `:3115` chaque ligne | `Ligne APBILL sans classe analytique (CLASSID).` | ligne avec CLASSID ou PROJECTID |
| `AcompteIntacctApadvanceService.php:311` frais généraux | `CLASSID obligatoire pour un acompte en frais généraux…` | inchangé (pas de programme) |
| `:315` | `Classe analytique (CLASSID) introuvable.` | classe OU projet si destination programme |
| `:338` | `n’a pas de classe Sage Intacct (CLASSID). Renseignez-la sur la fiche programme.` | classe OU projet |
| `ProgrammeParametresBancairesController.php:129` | `La classe Intacct est obligatoire.` (effacement) | autoriser l’effacement si un projet Sage existe |
| `ProgrammeCompleteness.php:34` | manquant `Classe (Intacct)` | manquant seulement si ni classe ni projet |
| Pastille incomplets `sqlIncomplets` | compte `intacct_class_analytics_id IS NULL` | hors de ce correctif d’écriture ; la pastille « sans classe » a déjà le filtre projet (#1710) |
| File paiement | pas de rejet | `PaiementLigneReferentielResolver.php:16` affiche la classe ou vide. L’APPYMT n’envoie pas de dimension. Le blocage réel est en amont : pas d’APBILL sans classe, donc pas de mise en paiement |
| `DepotFactureController.php:1566` | `analytics_id` required hors destination PROJET | jeu non appelé par Enregistrer ni par l’envoi (seulement des tests). Ne pas s’en servir comme garde |

Appel de fonds : absence de classe journalisée, envoi quand même sans balise (`AppelDeFondsIntacctSyncService.php:101`). Hors périmètre facture fournisseur.

## 6. Variantes non implémentées

### A — projet seul

Pour `analytique_par_projet` et un projet miroir : PROJECTID de ligne = `code_envol`, CLASSID omis. Ancien système : CLASSID inchangé, pas de PROJECTID.

Fichiers : `ComptabilisationController` (résolution + spec de ligne facture, avoir, retenues, multi-fournisseurs), `AcompteIntacctApadvanceService` et `ApadvanceCreateXmlBuilder` (lever l’interdit PROJECTID, ou laisser les acomptes en classe — à confirmer), `IntacctService::buildExtBillCreateFromApbillSpec`. Gardes : sans classe ni projet → blocage actuel ; sans projet en nouveau système → blocage explicite ; frais généraux → classe de saisie seule. Non-régression : test source ancien système (`analytique_par_projet` faux) conserve CLASSID et n’écrit pas PROJECTID. Recette : une facture test Envol nouveau système avec projet, sans classe, dans une entité réelle ; relire l’APBILL (ligne PROJECTID, CLASSID vide) via le panneau web.

### B — projet et classe

Même PROJECTID, et CLASSID conservé quand la classe existe. Source de classe inchangée (`resolveProgrammeClassIdForApbill`). Si pas de classe mais projet : PROJECTID seul (règle Robin). Si les deux : les deux balises. Le cas « quelle classe si les deux existent » reste la classe déjà résolue ; ne pas inventer une autre source.

Mêmes fichiers que A, plus le maintien de CLASSID dans le spec. APADVANCE : aujourd’hui PROJECTID est interdit, donc B sur acompte exige de retirer cet interdit. Recette : une facture avec classe et projet (les deux balises), une sans classe avec projet (PROJECTID seul), une ancien système (CLASSID seul).

## DÉPLOIEMENT

git pull origin main. Pas de migration, pas de journal, pas de cache.

## LEÇON

La classe qui part dans Sage est celle de la ligne APBILL, pas le drapeau nouveau système. Le paiement ne porte pas de dimension : s’il « bloque », c’est que la facture n’a pas été comptabilisée. Un projet créé au niveau général n’est pas prouvé utilisable dans la session entité tant qu’une lecture web ne l’a pas montré.
