---
suivi: 1571
date: 2026-09-16
sujet: Fix — ne plus marquer payée une fiche Sage extournée
chantier: factures / paiement / sage
type: fix
statut: poussé
hash: 37bf9bc8
fichiers:
  - app/Services/IntacctPaiementStatusService.php
  - app/Services/FacturePaiementStatutSyncService.php
  - tests/Unit/IntacctPaiementStatusArraySafetyTest.php
  - tests/Unit/IntacctPaiementStatutMappingTest.php
  - database/data/journal_mises_a_jour_post_2026_06_12.php
  - docs/suivi/SUIVI_1571_fix_paiement_fiche_extournee.md
---

# SUIVI #1571 — fiche Sage extournée : ne plus conclure au paiement

## PROMPT ENVOYÉ

PHASE 2. `estApBillPaye` traitait WHENPAID+TOTALDUE≈0 comme payé.
Probe prod bill 691359 : STATE=Reversed, déjà dans ignore_states du
pré-check APBILL. Réutiliser CETTE liste. Pas d’alerte, pas de
rattrapage 1404, pas d’anti-régression. Log une ligne. Push main.

## BASE GIT

HEAD attendu : `origin/main` après #1570 (`1c521ebf`). Working tree
sale (WIP foncier hors lot) : non committé. Pas de .vue → pas de Vite.

## FICHES LUES

- **#1570 LEÇON** — WHENPAID n’est pas un paiement ; Reversed conserve
  la date et TOTALDUE=0 ; `paiement_effectif` NULL = sync auto ;
  anti-régression empêche de reculer un faux payé déjà posé.
  Impact : ce lot coupe **l’entrée** du faux payé, pas la sortie.
  FAC-2026-1404 non touchée.
- **#1570 §4.3** — statut brut `V` NON DÉTERMINÉ dans l’ERP. La probe
  prod du 691359 a tranché l’API : `STATE=Reversed` (pas `V`). `V`
  n’est toujours pas dans `ignore_states` et n’est pas ajouté ici.
- **#1570 §5** — `isApBillStateIgnoredForPrecheck` lit
  `config('erp.intacct.apbill_precheck_ignore_states')` :
  Reversed, Reversal, Extourné, Extourne. Réutiliser, ne pas
  dupliquer.

## LISTE RÉUTILISÉE

Source unique : `config/erp.php:81-83` (clé
`erp.intacct.apbill_precheck_ignore_states`), défaut identique
`IntacctService::APBILL_PRECHECK_IGNORE_STATES_DEFAULT` (`:53-58`).
Lecture : `IntacctService::getApBillPrecheckIgnoreStates()` +
`isApBillStateIgnoredForPrecheck()` (`:2691-2721`, comparaison
`strcasecmp`).

La sync paiement n’ouvre **pas** une seconde config. Appel :
`IntacctPaiementStatusService::estApBillExtourne()` →
`intacctService->isApBillStateIgnoredForPrecheck($state)`.

Probe 691359 : `STATE=Reversed` ∈ cette liste
(`ignored_by_precheck = true`). Reversed = bill original extourné ;
Reversal = écriture d’extourne. Les deux écartés.

`V` : toujours non mappé. La probe du cas réel n’a pas renvoyé `V`
comme `STATE` API. Rien à ajouter tant qu’un bill prod n’a pas
`STATE=V`.

## SYNTHÈSE

`estApBillPaye` refuse d’abord toute fiche dont `STATE` est dans
la liste d’ignore, **puis** seulement `paid` ou WHENPAID+TOTALDUE≈0.

`mapperVersEtatErp` retourne `null` dès l’extourne, avant
DISPLAYSTATE Confirmed / STATE C — sinon une APPYMT collée
re-conclurait au paiement.

Sync : une ligne `Log::channel('intacct')->info` avec référence
facture, bill, STATE. Pas d’alerte. Anti-régression
`SyncService.php` appliquerEtat inchangée. 1404 inchangé.

## DÉPLOIEMENT-TEST

1. `git pull origin main`
2. `php artisan optimize:clear` + `view:clear` + `journal:sync`
3. Manuel compta : facture `en_paiement` dont le bill Sage est
   Reversed (WHENPAID encore visible) → ouvrir la fiche ou sync
   liste → **reste non payée**. Log `intacct.log` : « fiche APBILL
   extournée ».
4. Facture Paid réelle → toujours payée après sync.
5. FAC-2026-1404 : Robin l’annule au geste existant ; ce lot ne
   la recule pas.

Pas de migration. JAMAIS npm/composer/optimize/config:cache sur OVH.

## LEÇON

L’état API d’extourne est `Reversed` / `Reversal` (probe 691359),
déjà dans le pré-check APBILL. Couper le paiement = appeler
`isApBillStateIgnoredForPrecheck` **avant** WHENPAID. Couper aussi
`mapperVersEtatErp` : sinon Confirmed/C contourne `estApBillPaye`.
Ne pas reculer les déjà `payee` dans le même lot.
