cpro-client 0.2.7 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -0
- data/CHORUS_PRO.md +302 -0
- data/Gemfile.lock +1 -1
- data/lib/cpro/resources/factures.rb +17 -0
- data/lib/cpro/suivi.rb +19 -4
- data/lib/cpro/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 629795027a48606736ffd0273cd3bf2b1e457237508f428978a4bdbce3245f56
|
|
4
|
+
data.tar.gz: 5a3a218220129860479c710a68aaf4eb612ab74f2f6c8a812a7aa65e2f90f7a0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 23cc35215eb0530f66b71677d35948c1da2896c626a400018901de87c1e2ed8d517ffcb3eefe5f02d5978a7e1f966c513fd8cb1c114aa3950294fb0805915f21
|
|
7
|
+
data.tar.gz: 89a63f46184b02fb02ce615a964148f3ee3997899855312bacd77f3c41d5a7da6cd1b0ee255040fcf2304d3fa05541b13fbb57915ebca9b0c5187b7c07a20b80
|
data/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,28 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
## [0.2.8] - 2026-09-25
|
|
5
|
+
|
|
6
|
+
### Ajouté
|
|
7
|
+
- `Cpro::Resources::Factures#search` refuse une pagination hors bornes avant tout appel réseau :
|
|
8
|
+
`limite` au-delà de 50 — le maximum déclaré par le swagger — ou `ignorer` négatif lèvent une
|
|
9
|
+
`ArgumentError`. Relevé en production : au-delà de la borne, la plateforme répond `500 Erreur
|
|
10
|
+
interne` et non un 400, ce qui laisse croire à une panne alors que la requête est en cause.
|
|
11
|
+
|
|
12
|
+
- `Cpro::Suivi#orientation` cesse de traiter `:en_cours` comme un fourre-tout, qui absorbait neuf
|
|
13
|
+
statuts sur vingt-deux — dont `ENCAISSEE`, une facture payée ressortant « en cours ». Deux
|
|
14
|
+
valeurs s'ajoutent :
|
|
15
|
+
- **`:deposee`** — `DEPOSEE` sans aucun motif. Le dépôt a réussi, la plateforme n'a rien à
|
|
16
|
+
signaler, l'acheminement n'a pas encore eu lieu ;
|
|
17
|
+
- **`:cycle_de_vie`** — `ENCAISSEE`, `COMPLETEE`, `ANNULEE`, `CHANGEMENT_DE_COMPTE_A_PAYER`, les
|
|
18
|
+
statuts que seul le vendeur pose. Ils ne prouvent pas l'acheminement : on encaisse une facture
|
|
19
|
+
jamais transmise après remise d'un duplicata.
|
|
20
|
+
|
|
21
|
+
`:en_cours` ne garde que `DEMANDE_DE_PAIEMENT_DIRECT` et les trois statuts d'affacturage, sur
|
|
22
|
+
lesquels nous n'avons ni mesure ni lecture. Un statut posé par le vendeur l'emporte désormais sur
|
|
23
|
+
un `NON_TRANSMISE` devenu caduc, dont l'appelant a déjà fait ce qu'il fallait.
|
|
24
|
+
|
|
25
|
+
|
|
4
26
|
## [0.2.7] - 2026-09-07
|
|
5
27
|
|
|
6
28
|
### Ajouté
|
data/CHORUS_PRO.md
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
# Comportements observés de Chorus Pro G2B
|
|
2
|
+
|
|
3
|
+
Ce que la plateforme répond vraiment, scénario par scénario. Les swaggers décrivent les champs, pas
|
|
4
|
+
les enchaînements ; ce fichier comble l'écart, et sert à reconnaître une situation sans avoir à la
|
|
5
|
+
reproduire.
|
|
6
|
+
|
|
7
|
+
Chaque ligne porte sa provenance :
|
|
8
|
+
|
|
9
|
+
- **mesuré** — relevé sur la sandbox PISTE, avec sa date. C'est la source la plus fiable ;
|
|
10
|
+
- **documenté** — tiré du *Dossier de spécifications externes FE — Chorus Pro v1.1* ou de l'annexe
|
|
11
|
+
*Correspondance codes interfaces / CDV / statuts v1.2*, sans avoir été observé.
|
|
12
|
+
|
|
13
|
+
Un comportement documenté mais non mesuré n'est pas une certitude : le swagger de l'Annuaire
|
|
14
|
+
déclarait `contient` là où le serveur exige `strict`, et la consultation d'un code routage ne sert
|
|
15
|
+
pas les champs que sa recherche renvoie.
|
|
16
|
+
|
|
17
|
+
## Deux niveaux qu'il ne faut pas confondre
|
|
18
|
+
|
|
19
|
+
Un dépôt est jugé **deux fois**, et les deux verdicts vivent dans des API différentes.
|
|
20
|
+
|
|
21
|
+
| | Le flux | La facture |
|
|
22
|
+
| --- | --- | --- |
|
|
23
|
+
| Ce qui est jugé | l'enveloppe : archive, checksum, syntaxe des fichiers | le contenu métier : adressage, unicité, conformité |
|
|
24
|
+
| Où le lire | `GET /flux/{uid}` → `Cpro::Entities::StatutFlux` | Recherche Factures → `Cpro::Entities::Facture` |
|
|
25
|
+
| Statuts | `RECEVABLE` (500), `IRRECEVABLE` (501) | les 22 statuts du cycle de vie |
|
|
26
|
+
| Motifs | `StatutFlux#motifs_rejet` | `Facture#historique` → `ChangementStatut#motifs_rejet` |
|
|
27
|
+
|
|
28
|
+
**`RECEVABLE` ne dit rien du sort de la facture.** Un flux dont la facture référence un vendeur
|
|
29
|
+
inconnu ressort quand même recevable *(mesuré le 2026-08-14)*. `Cpro::Client#suivi` enchaîne les
|
|
30
|
+
deux niveaux et rassemble leurs motifs.
|
|
31
|
+
|
|
32
|
+
## Tout verdict est asynchrone
|
|
33
|
+
|
|
34
|
+
`POST /flux` ne valide que la requête : un 400 ou un 403 lève immédiatement, un 200 rend un
|
|
35
|
+
`uidFlux` et ne dit rien d'autre. **Aucun statut n'accompagne le dépôt**, pas même l'irrecevabilité —
|
|
36
|
+
contrôler la syntaxe des fichiers suppose d'ouvrir l'archive, ce que le PPF ne fait pas dans le
|
|
37
|
+
temps de la requête.
|
|
38
|
+
|
|
39
|
+
Le verdict se lit ensuite par `GET /flux/{uid}`, documenté `404 Flux non trouvé **ou non traité**` :
|
|
40
|
+
la même réponse pour un flux inexistant et pour un flux en attente de jugement.
|
|
41
|
+
|
|
42
|
+
Écarts relevés entre le moment où la plateforme pose le statut (`dateMajStatut`) et celui où il est
|
|
43
|
+
lisible, d'après les cassettes sandbox :
|
|
44
|
+
|
|
45
|
+
| Cassette | Statut posé | Lu à | Écart |
|
|
46
|
+
| --- | --- | --- | --- |
|
|
47
|
+
| `flux_statut` | 14/08 20:26:02 | 14/08 20:36:58 | ~11 min |
|
|
48
|
+
| `flux_statut_cdv` | 15/08 15:40:11 | 15/08 15:43:31 | ~3 min |
|
|
49
|
+
|
|
50
|
+
Le test sandbox `test_a_processed_flux_is_recevable` interroge d'ailleurs un flux déposé lors d'une
|
|
51
|
+
session antérieure, et non celui que `test_depositing_a_facturx_invoice_returns_a_flux_uid` vient de
|
|
52
|
+
déposer : un flux tout juste déposé n'est pas consultable.
|
|
53
|
+
|
|
54
|
+
Compter en minutes pour le verdict d'enveloppe, davantage pour que la facture apparaisse dans
|
|
55
|
+
Recherche Factures. `Cpro::Client#suivi` rend `:flux_en_attente` pendant toute cette période, au
|
|
56
|
+
coût d'un seul appel réseau.
|
|
57
|
+
|
|
58
|
+
## Les scénarios
|
|
59
|
+
|
|
60
|
+
Le `→` sépare les étapes successives. Un motif entre crochets est porté par le statut qui le
|
|
61
|
+
précède.
|
|
62
|
+
|
|
63
|
+
| Scénario | Flux | Facture | Issue |
|
|
64
|
+
| --- | --- | --- | --- |
|
|
65
|
+
| **Enveloppe invalide** *(mesuré 15/08)* | `IRRECEVABLE` (501) [`IRR_SYNTAX`] | jamais créée, Recherche Factures répond 204 | terminal — corriger le fichier et redéposer |
|
|
66
|
+
| **Adressage introuvable** *(mesuré 30/08)* | `RECEVABLE` (500) | `REJETEE` [`REJ_ADR`] — seule entrée de l'historique | terminal — la facture existe, son numéro est consommé |
|
|
67
|
+
| **Destinataire sans plateforme** *(mesuré 04/09)* | `RECEVABLE` (500) | `DEPOSEE` [`NON_TRANSMISE`] | **conforme** — jamais transmise, duplicata à la charge de l'émetteur |
|
|
68
|
+
| **Plateforme non raccordée** *(mesuré 04/09)* | `RECEVABLE` (500) | `DEPOSEE` [`NON_TRANSMISE`] | **conforme** — sera transmise au raccordement de la plateforme |
|
|
69
|
+
| **Doublon** *(mesuré 04/09)* | `RECEVABLE` (500) | `DEPOSEE` [`REJ_UNI`] | le triptyque est déjà pris |
|
|
70
|
+
| **Transmise** *(documenté)* | `RECEVABLE` (500) | `DEPOSEE`, puis les statuts que la plateforme et le destinataire posent | nominal — **jamais observé en sandbox**, et l'ordre exact n'est pas documenté |
|
|
71
|
+
| **Non-conformité** *(documenté)* | `RECEVABLE` (500) | `REJETEE` | la facture n'est pas intégrée ; retransmission après correction possible |
|
|
72
|
+
| **Routage impossible** *(documenté)* | `RECEVABLE` (500) | `ERREUR_ROUTAGE` (221) | terminal |
|
|
73
|
+
|
|
74
|
+
Les deux cas `NON_TRANSMISE` portent le **même statut et le même motif** : seule la note les
|
|
75
|
+
distingue, et seul l'annuaire permet de les anticiper.
|
|
76
|
+
|
|
77
|
+
| `statutPlateforme` de la ligne d'annuaire | Note du motif | Ce que fait Chorus Pro |
|
|
78
|
+
| --- | --- | --- |
|
|
79
|
+
| `Plateforme active` | — | transmet |
|
|
80
|
+
| `Plateforme en attente d'activation` | « La plateforme agréée de votre client n'est pas raccordée à CHORUS PRO » | transmettra au raccordement |
|
|
81
|
+
| `Pas de plateforme` | « Le destinataire n'a pas choisi de plateforme agréée » | ne transmettra jamais |
|
|
82
|
+
|
|
83
|
+
C'est pourquoi `Annuaire#find_identifiant_adressage` rend l'adresse **même sans plateforme active**,
|
|
84
|
+
en signalant le cas par `plateforme_active?` : la facture reste déposable, et l'obligation
|
|
85
|
+
réglementaire remplie.
|
|
86
|
+
|
|
87
|
+
## Le piège du statut porteur
|
|
88
|
+
|
|
89
|
+
Les motifs sont accrochés au statut sous lequel l'incident survient, qui n'a pas à être alarmant.
|
|
90
|
+
Une facture non transmise est `DEPOSEE` :
|
|
91
|
+
|
|
92
|
+
```ruby
|
|
93
|
+
facture.statut # => "DEPOSEE"
|
|
94
|
+
facture.rejetee? # => false ← et pourtant elle n'arrivera jamais
|
|
95
|
+
facture.en_echec? # => true
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Se fier au statut seul fait manquer le cas. `Facture#en_echec?` couvre les statuts d'échec **et**
|
|
99
|
+
tout motif présent dans l'historique ; `#dernier_incident` isole le plus récent, avec ses motifs.
|
|
100
|
+
|
|
101
|
+
Un même changement de statut porte plusieurs motifs — `NON_TRANSMISE` et `REJ_UNI` ont été relevés
|
|
102
|
+
ensemble le 04/09.
|
|
103
|
+
|
|
104
|
+
## Orienter une facture vers son traitement
|
|
105
|
+
|
|
106
|
+
Le statut seul ne suffit pas : deux couples de cas partagent le même. L'orientation se fait sur le
|
|
107
|
+
couple **statut × motif**.
|
|
108
|
+
|
|
109
|
+
| Cas | Statut | Motif | Comment le distinguer |
|
|
110
|
+
| --- | --- | --- | --- |
|
|
111
|
+
| **En cours** | `DEPOSEE` | aucun | rien à faire, réinterroger |
|
|
112
|
+
| **Rejet corrigeable** | `REJETEE` | conformité *(documenté)* | la facture n'est pas intégrée : corriger et redéposer sous le même numéro |
|
|
113
|
+
| **Rejet définitif** | `REJETEE` | `REJ_ADR` *(mesuré)* | la facture est intégrée, son numéro est consommé : avoir + nouvelle facture |
|
|
114
|
+
| | `ERREUR_ROUTAGE` | — | acheminée mais non routable chez le destinataire |
|
|
115
|
+
| **Transmission différée** | `DEPOSEE` | `NON_TRANSMISE` | note « La plateforme agréée de votre client n'est pas raccordée » |
|
|
116
|
+
| **Jamais transmise** | `DEPOSEE` | `NON_TRANSMISE` | note « Le destinataire n'a pas choisi de plateforme agréée » |
|
|
117
|
+
| **Transmise** | voir liste ci-dessous | — | un statut que seul l'aval peut poser |
|
|
118
|
+
|
|
119
|
+
**La note distingue les deux `NON_TRANSMISE`**, faute de mieux. Plus sûr : interroger l'annuaire
|
|
120
|
+
*avant* le dépôt — `find_identifiant_adressage(siret).statut_plateforme` vaut
|
|
121
|
+
`Plateforme en attente d'activation` dans le premier cas, `Pas de plateforme` dans le second.
|
|
122
|
+
|
|
123
|
+
### Statuts qui prouvent l'acheminement
|
|
124
|
+
|
|
125
|
+
D'après la colonne « Sens flux » de l'annexe, ces statuts proviennent de la plateforme du
|
|
126
|
+
destinataire ou constatent l'émission par Chorus Pro. Les recevoir prouve que la facture est partie.
|
|
127
|
+
|
|
128
|
+
```ruby
|
|
129
|
+
TRANSMISE = %w[
|
|
130
|
+
EMISE_PAR_LA_PLATEFORME RECUE_DE_LA_PLATEFORME MISE_A_DISPOSITION PRISE_EN_CHARGE
|
|
131
|
+
APPROUVEE APPROUVEE_PARTIELLEMENT EN_LITIGE SUSPENDUE PAIEMENT_TRANSMIS REFUSEE VISEE
|
|
132
|
+
].freeze
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
`APPROUVEE`, `REFUSEE` et `VISEE` circulent **dans les deux sens** : reçus, ils prouvent
|
|
136
|
+
l'acheminement ; émis par vous via `Flux#emettre_statut`, ils ne prouvent rien. Ils figurent quand
|
|
137
|
+
même dans la liste, parce que les recevoir est le cas courant et que les omettre ferait retomber
|
|
138
|
+
une facture refusée par l'acheteur sur « en cours » — ce qui serait faux. Si votre application émet
|
|
139
|
+
l'un d'eux, elle le sait et peut l'écarter.
|
|
140
|
+
|
|
141
|
+
`COMPLETEE`, `ENCAISSEE`, `ANNULEE` et `CHANGEMENT_DE_COMPTE_A_PAYER`, que seul le vendeur pose, ne
|
|
142
|
+
sont en revanche jamais des preuves d'acheminement.
|
|
143
|
+
|
|
144
|
+
### Mise en œuvre
|
|
145
|
+
|
|
146
|
+
`Cpro::Suivi#orientation` aplatit l'étape du suivi et l'état de la facture en une valeur :
|
|
147
|
+
|
|
148
|
+
| Orientation | Origine | Traitement |
|
|
149
|
+
| --- | --- | --- |
|
|
150
|
+
| `:en_attente` | flux non encore jugé | réinterroger |
|
|
151
|
+
| `:enveloppe_irrecevable` | flux `IRRECEVABLE` | corriger le fichier, redéposer |
|
|
152
|
+
| `:facture_introuvable` | recherche vide | réinterroger, puis vérifier le rattachement du compte |
|
|
153
|
+
| `:deposee` | `DEPOSEE` sans motif | réinterroger — dépôt réussi, acheminement en attente |
|
|
154
|
+
| `:cycle_de_vie` | statut posé par le vendeur | la facture vit sa vie commerciale |
|
|
155
|
+
| `:en_cours` | statut non classé | ne rien en déduire |
|
|
156
|
+
| `:rejetee` | statut `REJETEE` | lire les motifs |
|
|
157
|
+
| `:non_transmise` | motif `NON_TRANSMISE` sur l'incident courant | préparer un duplicata |
|
|
158
|
+
| `:erreur_routage` | statut `ERREUR_ROUTAGE` | terminal |
|
|
159
|
+
| `:transmise` | statut de `Suivi::STATUTS_TRANSMISE` | rien |
|
|
160
|
+
|
|
161
|
+
```ruby
|
|
162
|
+
case suivi.orientation
|
|
163
|
+
when :en_attente, :en_cours, :facture_introuvable then reprogrammer_le_suivi
|
|
164
|
+
when :enveloppe_irrecevable, :rejetee then signaler(suivi.motifs)
|
|
165
|
+
when :non_transmise then preparer_un_duplicata
|
|
166
|
+
when :erreur_routage then alerter
|
|
167
|
+
when :transmise then classer
|
|
168
|
+
end
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
L'ordre d'évaluation compte : les statuts passent avant les motifs, si bien qu'un acheminement
|
|
172
|
+
avéré ou un encaissement l'emportent sur un `NON_TRANSMISE` devenu caduc, dont l'appelant a déjà
|
|
173
|
+
fait ce qu'il fallait. Et c'est l'**incident courant** qui décide, non le cumul de l'historique :
|
|
174
|
+
une facture déposée sans être transmise, puis mise à disposition, puis refusée, ressort
|
|
175
|
+
`:transmise`.
|
|
176
|
+
|
|
177
|
+
`:cycle_de_vie` couvre `ENCAISSEE`, `COMPLETEE`, `ANNULEE` et `CHANGEMENT_DE_COMPTE_A_PAYER` — les
|
|
178
|
+
statuts que seul le vendeur pose. Ils ne prouvent pas l'acheminement : on encaisse très bien une
|
|
179
|
+
facture jamais transmise, après remise d'un duplicata.
|
|
180
|
+
|
|
181
|
+
`:en_cours` ne garde que les statuts sur lesquels nous n'avons ni mesure ni lecture —
|
|
182
|
+
`DEMANDE_DE_PAIEMENT_DIRECT` et les trois d'affacturage. Ce n'est pas un fourre-tout : tout autre
|
|
183
|
+
statut a sa place ailleurs.
|
|
184
|
+
|
|
185
|
+
### Ce que l'orientation ne tranche pas
|
|
186
|
+
|
|
187
|
+
Deux distinctions restent à l'appelant, parce que les porter dans la gem serait fragile.
|
|
188
|
+
|
|
189
|
+
**Les deux `NON_TRANSMISE`.** Seul le texte de la note les sépare, et il est rédigé par l'AIFE :
|
|
190
|
+
une reformulation casserait la classification sans que rien ne le signale. Le moyen sûr est en
|
|
191
|
+
amont — `Annuaire#find_identifiant_adressage(siret).statut_plateforme` vaut
|
|
192
|
+
`Plateforme en attente d'activation` pour le différé, `Pas de plateforme` pour le définitif.
|
|
193
|
+
|
|
194
|
+
**Rejet corrigeable ou définitif.** La distinction repose sur l'idée qu'un rejet de conformité
|
|
195
|
+
n'intègre pas la facture et ne consomme donc pas son numéro. La spécification le laisse entendre,
|
|
196
|
+
nous ne l'avons pas mesuré. `suivi.motifs` permet de trancher chez soi :
|
|
197
|
+
|
|
198
|
+
```ruby
|
|
199
|
+
definitif = suivi.motifs.any? { |motif| motif.code == "REJ_ADR" }
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
`REJ_UNI` se superpose aux autres motifs sans les remplacer : il signale que le triptyque était déjà
|
|
203
|
+
pris, ce qui se traite indépendamment de l'orientation.
|
|
204
|
+
|
|
205
|
+
**Ce qui est mesuré et ce qui ne l'est pas.** Seuls `REJETEE`/`REJ_ADR`, `DEPOSEE`/`NON_TRANSMISE`
|
|
206
|
+
dans ses deux variantes et `DEPOSEE`/`REJ_UNI` ont été observés. Le rejet de conformité,
|
|
207
|
+
`ERREUR_ROUTAGE` et l'ensemble des statuts d'acheminement sont déduits de l'annexe et de la
|
|
208
|
+
spécification, jamais vus en sandbox.
|
|
209
|
+
|
|
210
|
+
## Motifs rencontrés
|
|
211
|
+
|
|
212
|
+
| Code | Libellé | Où | Signification |
|
|
213
|
+
| --- | --- | --- | --- |
|
|
214
|
+
| `IRR_SYNTAX` | Contrôle syntaxique des fichiers du flux | flux | un fichier de l'archive est mal formé ; la note nomme le fichier fautif |
|
|
215
|
+
| `REJ_ADR` | Rejet sur Contrôle d'adressage | facture | BT-49 ne correspond à aucune adresse de l'annuaire |
|
|
216
|
+
| `NON_TRANSMISE` | Destinataire non connecté | facture | adresse valide, destinataire non raccordé |
|
|
217
|
+
| `REJ_UNI` | Contrôle de l'unicité du fichier de facture | facture | le triptyque est déjà utilisé |
|
|
218
|
+
| `ROUTAGE_ERR` | Erreur de routage | facture | destinataire **public** adressé par le code interface G2B |
|
|
219
|
+
| `ADR_ERR` | *(documenté)* | facture | l'adresse électronique est absente de la facture |
|
|
220
|
+
|
|
221
|
+
## L'unicité, et ce qu'elle implique
|
|
222
|
+
|
|
223
|
+
> le triptyque permettant d'assurer l'unicité de la facture est le suivant : **année d'émission
|
|
224
|
+
> (BT-2)**, **SIREN de l'émetteur (BT-30)**, **numéro de facture (BT-1)**.
|
|
225
|
+
|
|
226
|
+
Le libellé du motif parle du « fichier », mais le contrôle porte sur ces trois valeurs : deux dépôts
|
|
227
|
+
dont seuls la date d'émission et BT-49 différaient ont bien été jugés doublons *(mesuré 04/09)*.
|
|
228
|
+
|
|
229
|
+
**Un numéro est consommé au premier dépôt, quel que soit le sort de celui-ci.** Une facture rejetée
|
|
230
|
+
sur l'adressage occupe son numéro : la corriger et la redéposer sous le même numéro échoue.
|
|
231
|
+
|
|
232
|
+
La spécification prévoit pourtant qu'une facture rejetée pour **non-conformité** peut être
|
|
233
|
+
retransmise après correction — elle précise que dans ce cas « la facture n'est pas intégrée dans la
|
|
234
|
+
solution Chorus Pro ». La lecture cohérente avec nos mesures est donc qu'un rejet de conformité
|
|
235
|
+
n'intègre pas la facture et ne consomme pas son numéro, alors qu'un rejet d'adressage l'intègre et
|
|
236
|
+
le consomme. Nous n'avons mesuré que le second cas.
|
|
237
|
+
|
|
238
|
+
## Qui émet quoi
|
|
239
|
+
|
|
240
|
+
D'après l'annexe *Correspondance codes interfaces / CDV / statuts*, feuillet G2B. En mode API il n'y
|
|
241
|
+
a **aucune notification** : tout ce qui vient de la plateforme se lit par interrogation.
|
|
242
|
+
|
|
243
|
+
**Émis par nous** (interface `FSO3300A`, via `Flux#emettre_statut`) — les sept statuts du catalogue
|
|
244
|
+
`Cpro::Cdv::Statut`, dont deux obligatoires : `Refusée` (210) et `Encaissée` (212).
|
|
245
|
+
|
|
246
|
+
**Reçus** (interface `FEN4000A` ou `CSO311xA`) — `Déposée` (200), `Émise par la plateforme` (201),
|
|
247
|
+
`Reçue de la plateforme` (202), `Mise à disposition` (203), `Prise en charge` (204), `Approuvée
|
|
248
|
+
partiellement` (206), `En litige` (207), `Suspendue` (208), `Paiement transmis` (211), `Rejetée`
|
|
249
|
+
(213), `Erreur routage` (221). `Approuvée` (205), `Refusée` (210) et `Visée` (214) circulent dans
|
|
250
|
+
les deux sens.
|
|
251
|
+
|
|
252
|
+
Un CDV déposé peut lui-même être rejeté — statut `601`, interface `FEN4002A`. Par quel appel API ce
|
|
253
|
+
rejet se lit reste à déterminer.
|
|
254
|
+
|
|
255
|
+
## Points pratiques
|
|
256
|
+
|
|
257
|
+
**Lier la facture à ses statuts.** BT-18 (identifiant d'objet facturé) qualifié `AJW` est repris par
|
|
258
|
+
Chorus Pro dans chaque cycle de vie en MDT-92, avec le même qualifiant. C'est un lien plus stable
|
|
259
|
+
que le `nomFlux`, dont le format doit changer.
|
|
260
|
+
|
|
261
|
+
**Allotir.** La spécification recommande un maximum de 500 factures par flux.
|
|
262
|
+
|
|
263
|
+
**Un destinataire public ne se facture pas en G2B.** Relevé en production le 25/09 :
|
|
264
|
+
|
|
265
|
+
> `ROUTAGE_ERR` — « Il n'est pas possible de transmettre des factures intra sphère publique par ce
|
|
266
|
+
> code interface »
|
|
267
|
+
|
|
268
|
+
Le G2B (`FSO3117A`) va du public vers le privé. Une facture destinée à une autre entité publique
|
|
269
|
+
relève du circuit **G2G**, que cette gem ne couvre pas. L'annuaire ne le signale pas à l'adressage :
|
|
270
|
+
`find_identifiant_adressage` rend l'adresse d'une structure publique comme de n'importe quelle
|
|
271
|
+
autre. La nature du destinataire se lit par `search_siren(filtres: { siren: … }).first.type_entite`,
|
|
272
|
+
qui vaut `Publique` ou `Privée assujettie` — et seule la **recherche** la sert, la consultation
|
|
273
|
+
laissant le champ nul.
|
|
274
|
+
|
|
275
|
+
**Un rejet n'est pas une étape plus avancée qu'un dépôt.** `REJETEE` et `ERREUR_ROUTAGE` prouvent
|
|
276
|
+
que la plateforme a traité la facture, jamais qu'elle l'a transmise. Pour mesurer si un circuit
|
|
277
|
+
fonctionne, ne comptez que les statuts de `Suivi::STATUTS_TRANSMISE` — tout le reste, y compris les
|
|
278
|
+
échecs, laisse la question ouverte.
|
|
279
|
+
|
|
280
|
+
**Une facture peut rester `DEPOSEE` indéfiniment, sans aucun motif.** Relevé en production : une
|
|
281
|
+
facture déposée le 18/09 était encore `DEPOSEE` avec un `detailStatut` vide une semaine plus tard.
|
|
282
|
+
Chorus Pro n'a alors rien à dire, et aucune API ne donne de raison. La seule piste est l'annuaire :
|
|
283
|
+
si le destinataire n'a pas de plateforme active, la facture n'ira nulle part — même sans motif
|
|
284
|
+
`NON_TRANSMISE` pour le signaler. `local/sonde_bloquees.rb` automatise ce recoupement.
|
|
285
|
+
|
|
286
|
+
**Une requête hors bornes donne un 500, pas un 400.** `limite` est plafonné à 50 sur
|
|
287
|
+
`POST /facture/recherche` ; au-delà, la plateforme répond `500 Erreur interne` sans rien expliquer.
|
|
288
|
+
Le même comportement a été relevé sur l'Annuaire, où un filtre `siret` privé de son opérateur
|
|
289
|
+
donne également un 500. **Ne lisez pas un 500 comme une panne avant d'avoir vérifié votre requête
|
|
290
|
+
contre le swagger.** `Factures#search` refuse désormais localement une pagination hors bornes.
|
|
291
|
+
|
|
292
|
+
**Le 204 de Recherche Factures est ambigu** : aucune facture correspondante, ou compte technique
|
|
293
|
+
rattaché ni à la structure émettrice ni à la destinataire. Rien ne permet de les distinguer.
|
|
294
|
+
|
|
295
|
+
**Les dates.** L'API Flux date en UTC **sans marqueur de fuseau** (`2026-09-04T16:38:00.731094`),
|
|
296
|
+
Recherche Factures avec un `Z` depuis fin août. `Entities::Base#parse_time` absorbe les deux formes.
|
|
297
|
+
|
|
298
|
+
## Sources
|
|
299
|
+
|
|
300
|
+
- *Dossier de spécifications externes FE — Chorus Pro v1.1*
|
|
301
|
+
- *Annexe Chorus Pro — Correspondance codes interfaces / CDV / statuts v1.2*, feuillet G2B
|
|
302
|
+
- Relevés sandbox PISTE, du 2026-08-14 au 2026-09-04
|
data/Gemfile.lock
CHANGED
|
@@ -14,6 +14,10 @@ module Cpro
|
|
|
14
14
|
|
|
15
15
|
TOTAL_KEY = "nombreTotalResultats"
|
|
16
16
|
|
|
17
|
+
# Le swagger plafonne `limite` à 50. Au-delà, la plateforme répond 500 et non 400 : mieux
|
|
18
|
+
# vaut refuser localement que d'aller chercher une erreur serveur qui n'explique rien.
|
|
19
|
+
LIMITE_MAX = 50
|
|
20
|
+
|
|
17
21
|
def search(filtres:, **options)
|
|
18
22
|
payload = post("facture/recherche", search_body(filtres, options))
|
|
19
23
|
Entities::ResultatRecherche.new(payload, item_class: Entities::Facture, total_key: TOTAL_KEY)
|
|
@@ -39,6 +43,7 @@ module Cpro
|
|
|
39
43
|
private
|
|
40
44
|
|
|
41
45
|
def search_body(filtres, options)
|
|
46
|
+
verifier_pagination!(options)
|
|
42
47
|
compact_body(
|
|
43
48
|
"filtres" => build_filtres(filtres),
|
|
44
49
|
"champs" => Array(options[:champs]).map(&:to_s),
|
|
@@ -48,6 +53,18 @@ module Cpro
|
|
|
48
53
|
)
|
|
49
54
|
end
|
|
50
55
|
|
|
56
|
+
def verifier_pagination!(options)
|
|
57
|
+
limite = options[:limite]
|
|
58
|
+
if !limite.nil? && (limite.to_i.negative? || limite.to_i > LIMITE_MAX)
|
|
59
|
+
raise ArgumentError, "limite hors bornes : #{limite.inspect} (0 à #{LIMITE_MAX})"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
ignorer = options[:ignorer]
|
|
63
|
+
return if ignorer.nil? || !ignorer.to_i.negative?
|
|
64
|
+
|
|
65
|
+
raise ArgumentError, "ignorer ne peut pas être négatif : #{ignorer.inspect}"
|
|
66
|
+
end
|
|
67
|
+
|
|
51
68
|
def build_filtres(filtres)
|
|
52
69
|
raise ArgumentError, "au moins un filtre est requis" if filtres.nil? || filtres.empty?
|
|
53
70
|
|
data/lib/cpro/suivi.rb
CHANGED
|
@@ -10,8 +10,8 @@ module Cpro
|
|
|
10
10
|
class Suivi
|
|
11
11
|
ETAPES = %i[flux_en_attente flux_irrecevable facture_introuvable facture_connue].freeze
|
|
12
12
|
|
|
13
|
-
ORIENTATIONS = %i[en_attente enveloppe_irrecevable facture_introuvable en_cours
|
|
14
|
-
rejetee non_transmise erreur_routage transmise].freeze
|
|
13
|
+
ORIENTATIONS = %i[en_attente enveloppe_irrecevable facture_introuvable deposee en_cours
|
|
14
|
+
rejetee non_transmise erreur_routage cycle_de_vie transmise].freeze
|
|
15
15
|
|
|
16
16
|
# Statuts qui prouvent que la facture a quitté Chorus Pro : d'après la colonne « Sens flux »
|
|
17
17
|
# de l'annexe des statuts, ils proviennent de la plateforme du destinataire ou constatent
|
|
@@ -25,8 +25,14 @@ module Cpro
|
|
|
25
25
|
APPROUVEE APPROUVEE_PARTIELLEMENT EN_LITIGE SUSPENDUE PAIEMENT_TRANSMIS REFUSEE VISEE
|
|
26
26
|
].freeze
|
|
27
27
|
|
|
28
|
+
# Statuts que seul le vendeur pose, via `Flux#emettre_statut`. La facture vit sa vie
|
|
29
|
+
# commerciale — mais ils ne prouvent pas l'acheminement : on peut encaisser une facture jamais
|
|
30
|
+
# transmise, après remise d'un duplicata. C'est même le parcours que décrit la spécification.
|
|
31
|
+
STATUTS_CYCLE_DE_VIE = %w[ENCAISSEE COMPLETEE ANNULEE CHANGEMENT_DE_COMPTE_A_PAYER].freeze
|
|
32
|
+
|
|
28
33
|
MOTIF_NON_TRANSMISE = "NON_TRANSMISE"
|
|
29
34
|
STATUT_ERREUR_ROUTAGE = "ERREUR_ROUTAGE"
|
|
35
|
+
STATUT_DEPOSEE = "DEPOSEE"
|
|
30
36
|
|
|
31
37
|
attr_reader :etape, :statut_flux, :facture
|
|
32
38
|
|
|
@@ -99,7 +105,10 @@ module Cpro
|
|
|
99
105
|
end
|
|
100
106
|
|
|
101
107
|
# Nomme la situation en une valeur sur laquelle brancher, l'étape du suivi et l'état de la
|
|
102
|
-
# facture étant aplatis ensemble.
|
|
108
|
+
# facture étant aplatis ensemble. `:deposee` dit que la plateforme a pris la facture sans rien
|
|
109
|
+
# signaler — le dépôt a réussi, l'acheminement n'a pas encore eu lieu.
|
|
110
|
+
#
|
|
111
|
+
# Ne repose que sur des codes : statut, code motif, étape.
|
|
103
112
|
#
|
|
104
113
|
# Deux distinctions sont délibérément laissées à l'appelant, parce que les porter ici serait
|
|
105
114
|
# fragile :
|
|
@@ -133,13 +142,19 @@ module Cpro
|
|
|
133
142
|
|
|
134
143
|
private
|
|
135
144
|
|
|
136
|
-
# L'ordre compte
|
|
145
|
+
# L'ordre compte. Les statuts passent avant les motifs : un acheminement avéré ou un encaissement
|
|
146
|
+
# l'emportent sur un NON_TRANSMISE devenu caduc, dont l'hôte a déjà fait ce qu'il fallait. Et
|
|
137
147
|
# c'est l'incident courant — non le cumul de l'historique — qui décrit la situation présente.
|
|
148
|
+
#
|
|
149
|
+
# `:en_cours` ne reste que pour les statuts sur lesquels nous n'avons ni mesure ni lecture :
|
|
150
|
+
# les trois d'affacturage et DEMANDE_DE_PAIEMENT_DIRECT. Ce n'est pas un fourre-tout.
|
|
138
151
|
def orientation_facture
|
|
139
152
|
return :erreur_routage if facture.statut == STATUT_ERREUR_ROUTAGE
|
|
140
153
|
return :rejetee if facture.rejetee?
|
|
141
154
|
return :transmise if STATUTS_TRANSMISE.include?(facture.statut)
|
|
155
|
+
return :cycle_de_vie if STATUTS_CYCLE_DE_VIE.include?(facture.statut)
|
|
142
156
|
return :non_transmise if non_transmise?
|
|
157
|
+
return :deposee if facture.statut == STATUT_DEPOSEE
|
|
143
158
|
|
|
144
159
|
:en_cours
|
|
145
160
|
end
|
data/lib/cpro/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: cpro-client
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.8
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Hôtentic
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-09-
|
|
11
|
+
date: 2026-09-25 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: faraday
|
|
@@ -41,6 +41,7 @@ files:
|
|
|
41
41
|
- ".gitignore"
|
|
42
42
|
- ".rubocop.yml"
|
|
43
43
|
- CHANGELOG.md
|
|
44
|
+
- CHORUS_PRO.md
|
|
44
45
|
- CODE_OF_CONDUCT.md
|
|
45
46
|
- Gemfile
|
|
46
47
|
- Gemfile.lock
|