cpro-client 0.1.0 → 0.2.1
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/.gitignore +1 -0
- data/.rubocop.yml +5 -0
- data/CHANGELOG.md +47 -5
- data/Gemfile.lock +5 -2
- data/README.md +94 -13
- data/cpro-client.gemspec +1 -1
- data/lib/cpro/account.rb +2 -0
- data/lib/cpro/auth/token_provider.rb +1 -1
- data/lib/cpro/connection.rb +2 -2
- data/lib/cpro/entities/code_routage.rb +67 -0
- data/lib/cpro/middleware/authentication.rb +3 -7
- data/lib/cpro/middleware/raise_error.rb +12 -4
- data/lib/cpro/resources/annuaire.rb +30 -5
- data/lib/cpro/resources/base.rb +2 -1
- data/lib/cpro/version.rb +1 -1
- data/lib/cpro.rb +2 -0
- metadata +13 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 40f1e1557770b6ee03af92e419c95e19071d2b4a53848db46cdd76e409802eb6
|
|
4
|
+
data.tar.gz: 2f75110a47a4954f8151fe5d5cd85daa17e4d3e8b924512a96eab899865faf41
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7e479ee8731fcd352635ade84a00ffc1a22901ce9d986d4ee47b26a65bc4313a746fbc40b6e7917191aab6002d2b95e0409b230cebae81e9ff952253479ff0e4
|
|
7
|
+
data.tar.gz: 9a75779dfefdd3a2b1e1ff5f156cc051969554cb7ac557bdc24c2c677ac0697d5f754f57274b38374ac27969b2af7011385d62f3a672f2cac518844c55fdf416
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
|
@@ -17,6 +17,11 @@ Style/Documentation:
|
|
|
17
17
|
Layout/LineLength:
|
|
18
18
|
Max: 120
|
|
19
19
|
|
|
20
|
+
# Poser rubygems_mfa_required exigerait d'activer la MFA sur le compte RubyGems qui publie ;
|
|
21
|
+
# ce n'est pas le mode de publication retenu ici.
|
|
22
|
+
Gemspec/RequireMFA:
|
|
23
|
+
Enabled: false
|
|
24
|
+
|
|
20
25
|
# Le fichier doit porter le nom de la gem pour que `require "cpro-client"` fonctionne.
|
|
21
26
|
Naming/FileName:
|
|
22
27
|
Exclude:
|
data/CHANGELOG.md
CHANGED
|
@@ -1,7 +1,53 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
|
|
4
|
+
## [0.2.1] - 2026-08-28
|
|
5
|
+
|
|
6
|
+
### Modifié
|
|
7
|
+
- La contrainte sur Faraday passe de `~> 2.0` à `>= 0.9, < 3.0`. La gem s'installe désormais dans
|
|
8
|
+
une application verrouillée sur Faraday 0.9 par une autre dépendance, sans cesser de fonctionner
|
|
9
|
+
avec Faraday 2. Suite vérifiée sur 0.9.2, 1.10.4 et 2.8.1.
|
|
10
|
+
- Les middlewares JSON sont portés par la gem (`Cpro::Middleware::EncodeJson` et `ParseJson`)
|
|
11
|
+
plutôt qu'empruntés à Faraday, qui ne les embarque qu'à partir de la 1.0 — avant, ils venaient
|
|
12
|
+
de la gem `faraday_middleware`. La dépendance d'exécution reste Faraday seul.
|
|
13
|
+
- `Cpro::Middleware::RaiseError` reçoit `on_complete` de l'objet réponse au lieu de l'hériter de sa
|
|
14
|
+
classe de base : c'est la seule forme commune à Faraday 0.9 et à Faraday 2, dont les classes de
|
|
15
|
+
base diffèrent. `reason_phrase`, absent avant la 1.0, n'est lu que s'il existe.
|
|
16
|
+
|
|
17
|
+
### Retiré
|
|
18
|
+
- `Cpro::Middleware::Authentication::ACCOUNT_HEADER`, remplacé par `Cpro::Account::HEADER`. Le
|
|
19
|
+
compte technique n'est plus transmis par le contexte de requête Faraday — introduit en 1.0 —
|
|
20
|
+
mais posé en en-tête par `Cpro::Resources::Base`. Aucun changement pour l'appelant :
|
|
21
|
+
`with_account` reste le point d'entrée.
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
## [0.2.0] - 2026-08-27
|
|
25
|
+
|
|
26
|
+
### Corrigé
|
|
27
|
+
- Les filtres `siret` et `siren` des recherches d'annuaire partaient avec l'opérateur `contient`,
|
|
28
|
+
déclaré par le swagger mais refusé par la plateforme : tout appel les portant échouait en
|
|
29
|
+
`400 Failed to read HTTP message`. Ils utilisent désormais `strict`. `search_siret` et
|
|
30
|
+
`search_siren` étaient inutilisables sur ces critères en 0.1.0.
|
|
31
|
+
|
|
32
|
+
### Ajouté
|
|
33
|
+
- Recherche de codes routage : `Cpro::Resources::Annuaire#search_code_routage` et l'entité
|
|
34
|
+
`Cpro::Entities::CodeRoutage`. Seul accès au libellé lisible d'un code routage
|
|
35
|
+
(`libelleCodeRoutage`) et à son `gestionEngagementJuridique` — ni la consultation d'un
|
|
36
|
+
établissement ni celle d'un code routage ne les servent.
|
|
37
|
+
- Tests sandbox couvrant les chemins de recherche de l'Annuaire, et garde-fou sautant un test
|
|
38
|
+
plutôt que de le faire échouer lorsque sa cassette est absente.
|
|
39
|
+
|
|
40
|
+
### Documentation
|
|
41
|
+
- README : la consultation et la recherche de l'Annuaire retournent des champs disjoints, ce qui
|
|
42
|
+
n'est pas une limite du jeu de données sandbox mais de la consultation. Adressage des structures
|
|
43
|
+
privées à la maille SIREN, et divergence d'opérateur avec le swagger.
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
## [0.1.0] - 2026-08-27
|
|
47
|
+
|
|
48
|
+
- Initial release
|
|
3
49
|
- Authentification OAuth2 PISTE (`client_credentials`) avec cache du jeton, renouvellement anticipé
|
|
4
|
-
|
|
50
|
+
et rejeu unique d'un appel refusé en 401.
|
|
5
51
|
- Compte technique `cpro-account` scopé par appel via `Cpro::Client#with_account`.
|
|
6
52
|
- API Annuaire G2B : consultation par SIRET et par SIREN, recherche multi-critères, lignes
|
|
7
53
|
d'annuaire paginées.
|
|
@@ -23,7 +69,3 @@
|
|
|
23
69
|
enregistrement contre la vraie sandbox PISTE (`CPRO_RECORD=1`).
|
|
24
70
|
- Hiérarchie d'erreurs `Cpro::Error` exposant statut HTTP, corps de réponse et identifiant de
|
|
25
71
|
corrélation PISTE.
|
|
26
|
-
|
|
27
|
-
## [0.1.0] - 2026-08-14
|
|
28
|
-
|
|
29
|
-
- Initial release
|
data/Gemfile.lock
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
cpro-client (0.1
|
|
5
|
-
faraday (
|
|
4
|
+
cpro-client (0.2.1)
|
|
5
|
+
faraday (>= 0.9, < 3.0)
|
|
6
6
|
|
|
7
7
|
GEM
|
|
8
8
|
remote: https://rubygems.org/
|
|
@@ -25,6 +25,8 @@ GEM
|
|
|
25
25
|
language_server-protocol (3.17.0.6)
|
|
26
26
|
lint_roller (1.1.0)
|
|
27
27
|
minitest (5.26.1)
|
|
28
|
+
nokogiri (1.15.7-arm64-darwin)
|
|
29
|
+
racc (~> 1.4)
|
|
28
30
|
nokogiri (1.15.7-x86_64-darwin)
|
|
29
31
|
racc (~> 1.4)
|
|
30
32
|
parallel (1.28.0)
|
|
@@ -64,6 +66,7 @@ GEM
|
|
|
64
66
|
hashdiff (>= 0.4.0, < 2.0.0)
|
|
65
67
|
|
|
66
68
|
PLATFORMS
|
|
69
|
+
arm64-darwin-25
|
|
67
70
|
x86_64-darwin-21
|
|
68
71
|
|
|
69
72
|
DEPENDENCIES
|
data/README.md
CHANGED
|
@@ -16,6 +16,11 @@ Scénario couvert :
|
|
|
16
16
|
gem "cpro-client"
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
+
Faraday est la seule dépendance d'exécution, de la version 0.9 à la version 2 incluse — la gem
|
|
20
|
+
porte elle-même les middlewares JSON, que Faraday n'embarque qu'à partir de la 1.0. La suite est
|
|
21
|
+
vérifiée sur 0.9.2, 1.10.4 et 2.8.1 ; le développement se fait sur la plus récente, et l'on teste
|
|
22
|
+
un plancher avec `gem "faraday", "0.9.2"` ajouté au `Gemfile` avant `bundle install`.
|
|
23
|
+
|
|
19
24
|
## Configuration
|
|
20
25
|
|
|
21
26
|
Les identifiants PISTE identifient l'application et sont stables :
|
|
@@ -52,11 +57,26 @@ Le compte technique doit être rattaché à une structure publique, faute de quo
|
|
|
52
57
|
|
|
53
58
|
## Annuaire
|
|
54
59
|
|
|
60
|
+
**Consultation et recherche ne retournent pas les mêmes champs, et ne se remplacent pas.** C'est
|
|
61
|
+
le point le moins intuitif de cette API :
|
|
62
|
+
|
|
63
|
+
| | consultation (`find_by_…`) | recherche (`search_…`) |
|
|
64
|
+
| --- | :-: | :-: |
|
|
65
|
+
| identité — dénomination, adresse, état administratif | ✗ | ✓ |
|
|
66
|
+
| `donneesB2gComplementaires` — engagement juridique, gestion du code service… | ✗ | ✓ |
|
|
67
|
+
| `lignesAnnuaire` — les identifiants d'adressage | ✓ | ✗ |
|
|
68
|
+
|
|
69
|
+
Il faut donc les deux appels pour avoir toute l'information d'une structure. La gem ne les
|
|
70
|
+
enchaîne pas à votre place : c'est un état de la plateforme, pas une règle métier, et il changera.
|
|
71
|
+
|
|
72
|
+
### Consultation
|
|
73
|
+
|
|
74
|
+
Elle sert les lignes d'annuaire, c'est-à-dire les identifiants nécessaires à l'adressage d'une
|
|
75
|
+
facture :
|
|
76
|
+
|
|
55
77
|
```ruby
|
|
56
78
|
etablissement = client.annuaire.find_by_siret("70204275500240")
|
|
57
|
-
etablissement.
|
|
58
|
-
etablissement.publique?
|
|
59
|
-
etablissement.gestion_engagement_juridique? # numéro d'engagement obligatoire ?
|
|
79
|
+
etablissement.lignes_annuaire.map(&:identifiant_adressage)
|
|
60
80
|
etablissement.lignes_annuaire.map(&:identifiant_routage)
|
|
61
81
|
|
|
62
82
|
unite = client.annuaire.find_by_siren("702042755")
|
|
@@ -70,7 +90,13 @@ lignes.nombre_total
|
|
|
70
90
|
lignes.suite? # reste-t-il des lignes au-delà de cette page ?
|
|
71
91
|
```
|
|
72
92
|
|
|
73
|
-
|
|
93
|
+
Une structure sans ligne d'annuaire à la maille SIRET répond **404** en consultation par SIRET,
|
|
94
|
+
tout en étant présente dans l'annuaire : c'est le cas des structures privées, dont l'adressage se
|
|
95
|
+
fait à la maille SIREN. Cherchez-les alors par `search_siret`, ou consultez leur SIREN.
|
|
96
|
+
|
|
97
|
+
### Recherche
|
|
98
|
+
|
|
99
|
+
Elle sert l'identité et les données B2G :
|
|
74
100
|
|
|
75
101
|
```ruby
|
|
76
102
|
resultat = client.annuaire.search_siret(
|
|
@@ -79,10 +105,37 @@ resultat = client.annuaire.search_siret(
|
|
|
79
105
|
limite: 20
|
|
80
106
|
)
|
|
81
107
|
|
|
82
|
-
resultat.nombre_total
|
|
83
|
-
resultat.
|
|
108
|
+
resultat.nombre_total # nil si l'API ne le renseigne pas — ce n'est pas zéro
|
|
109
|
+
resultat.first.denomination
|
|
110
|
+
resultat.first.gestion_engagement_juridique?
|
|
84
111
|
```
|
|
85
112
|
|
|
113
|
+
L'opérateur de comparaison est déduit du champ. Les libellés se cherchent en `contient`, **les
|
|
114
|
+
identifiants en `strict`** : le swagger déclare `contient` sur `siret` et `siren`, mais le serveur
|
|
115
|
+
refuse cette forme par un `400 Failed to read HTTP message`. Un filtre inconnu lève une
|
|
116
|
+
`ArgumentError` avant tout appel réseau.
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
client.annuaire.search_siret(filtres: { siret: "70204275500240" }) # strict, implicitement
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Codes routage
|
|
123
|
+
|
|
124
|
+
Un code routage est le troisième niveau de l'annuaire — le service, sous l'établissement. Sa
|
|
125
|
+
recherche est le **seul** accès à son libellé lisible et à son engagement juridique : ni la
|
|
126
|
+
consultation de l'établissement ni celle du code routage lui-même ne les servent.
|
|
127
|
+
|
|
128
|
+
```ruby
|
|
129
|
+
codes = client.annuaire.search_code_routage(filtres: { siret: "71915767420316" })
|
|
130
|
+
|
|
131
|
+
codes.map(&:to_s) # => ["FACTURES_PUBLIQUES — Service des factures publiques", …]
|
|
132
|
+
codes.first.libelle # "Service des factures publiques"
|
|
133
|
+
codes.first.gestion_engagement_juridique? # numéro d'engagement obligatoire pour ce service ?
|
|
134
|
+
codes.first.actif?
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Filtrez sur `siret`, faute de quoi la réponse balaie tout l'annuaire.
|
|
138
|
+
|
|
86
139
|
## Transmission d'une facture Factur-X
|
|
87
140
|
|
|
88
141
|
```ruby
|
|
@@ -289,22 +342,50 @@ Chaîne complète validée le 14/08/2026 : dépôt d'un Factur-X accepté (`uidF
|
|
|
289
342
|
- La **recevabilité porte sur l'enveloppe**, pas sur le contenu métier : un flux dont le vendeur est
|
|
290
343
|
inconnu du destinataire ressort quand même `RECEVABLE`. Le sort de la facture elle-même se lit via
|
|
291
344
|
l'API Recherche Factures G2B, à partir du `nomFlux`.
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
345
|
+
- Les filtres `siret` et `siren` des recherches d'annuaire n'acceptent que l'opérateur `strict`,
|
|
346
|
+
alors que le swagger déclare `contient` : cette forme est refusée par un
|
|
347
|
+
`400 Failed to read HTTP message`, une erreur de désérialisation levée avant tout traitement.
|
|
348
|
+
- La **recherche d'annuaire est plus riche que la consultation**, et réciproquement : identité et
|
|
349
|
+
données B2G d'un côté, lignes d'annuaire de l'autre, sans recouvrement.
|
|
350
|
+
- Le libellé d'un code routage et son `gestionEngagementJuridique` ne sont servis que par
|
|
351
|
+
`search_code_routage`. La consultation unitaire d'un code routage retourne moins que
|
|
352
|
+
`find_by_siret`, ce qui est la raison pour laquelle la gem ne l'expose pas.
|
|
353
|
+
|
|
354
|
+
### Ce que la consultation ne dit pas
|
|
355
|
+
|
|
356
|
+
`find_by_siret` et `find_by_siren` ne renvoient que le bloc `lignesAnnuaire` : `siret`,
|
|
296
357
|
`denomination`, `adresse`, `uniteLegale` et `donneesB2gComplementaires` ressortent à `nil`, y
|
|
297
|
-
compris pour la structure du compte appelant et même en précisant `champs`.
|
|
298
|
-
|
|
299
|
-
|
|
358
|
+
compris pour la structure du compte appelant et même en précisant `champs`.
|
|
359
|
+
|
|
360
|
+
Ce n'est **pas** une limite du jeu de données : la recherche, elle, sert ces champs — mesuré sur la
|
|
361
|
+
même structure, au même instant. C'est la consultation qui est incomplète. Passez donc par
|
|
362
|
+
`search_siret` pour l'identité, et par la consultation pour les identifiants d'adressage :
|
|
300
363
|
|
|
301
364
|
```ruby
|
|
302
365
|
client.annuaire.find_by_siret("71915767420316").lignes_annuaire.map(&:identifiant_adressage)
|
|
303
366
|
# => ["719157674_71915767420316",
|
|
304
367
|
# "719157674_71915767420316_FACTURES_PUBLIQUES",
|
|
305
368
|
# "719157674_71915767420316_SERVICE_PUBLIQUE_1_71915767420316"]
|
|
369
|
+
|
|
370
|
+
client.annuaire.search_siret(filtres: { siret: "71915767420316" }).first.denomination
|
|
371
|
+
# => "Destinataire 71915767420316"
|
|
306
372
|
```
|
|
307
373
|
|
|
374
|
+
### Adressage des structures privées
|
|
375
|
+
|
|
376
|
+
En G2B, le destinataire est une entreprise privée. Sur le matelas de qualification, ces structures
|
|
377
|
+
n'ont **aucune ligne d'annuaire à la maille SIRET** : leur seul identifiant d'adressage est le SIREN
|
|
378
|
+
nu, et leur `statutPlateforme` vaut « Pas de plateforme ». Leur SIRET reste consultable par
|
|
379
|
+
recherche, mais pas par `find_by_siret`, qui répond 404.
|
|
380
|
+
|
|
381
|
+
```ruby
|
|
382
|
+
client.annuaire.find_by_siren("474775418").lignes_annuaire.map(&:identifiant_adressage)
|
|
383
|
+
# => ["474775418"]
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
C'est cet identifiant qui doit alimenter BT-49 sur la facture et MDT-73 sur le cycle de vie.
|
|
387
|
+
Vérifiez `plateforme_active?` avant de compter sur un acheminement.
|
|
388
|
+
|
|
308
389
|
## Licence
|
|
309
390
|
|
|
310
391
|
Disponible en open source sous les termes de la [licence MIT](https://opensource.org/licenses/MIT).
|
data/cpro-client.gemspec
CHANGED
data/lib/cpro/account.rb
CHANGED
|
@@ -48,7 +48,7 @@ module Cpro
|
|
|
48
48
|
@connection ||= Faraday.new do |faraday|
|
|
49
49
|
faraday.request :url_encoded
|
|
50
50
|
faraday.use Middleware::RaiseError
|
|
51
|
-
faraday.
|
|
51
|
+
faraday.use Middleware::ParseJson
|
|
52
52
|
faraday.options.timeout = configuration.timeout
|
|
53
53
|
faraday.options.open_timeout = configuration.open_timeout
|
|
54
54
|
faraday.adapter configuration.adapter
|
data/lib/cpro/connection.rb
CHANGED
|
@@ -5,10 +5,10 @@ module Cpro
|
|
|
5
5
|
# Middlewares ordering matters here
|
|
6
6
|
def self.build(configuration, token_provider, base_path)
|
|
7
7
|
Faraday.new(url: "#{configuration.api_host}#{base_path}/") do |faraday|
|
|
8
|
-
faraday.
|
|
8
|
+
faraday.use Middleware::EncodeJson
|
|
9
9
|
faraday.use Middleware::Authentication, token_provider: token_provider
|
|
10
10
|
faraday.use Middleware::RaiseError
|
|
11
|
-
faraday.
|
|
11
|
+
faraday.use Middleware::ParseJson
|
|
12
12
|
faraday.response :logger, configuration.logger if configuration.logger
|
|
13
13
|
faraday.options.timeout = configuration.timeout
|
|
14
14
|
faraday.options.open_timeout = configuration.open_timeout
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cpro
|
|
4
|
+
module Entities
|
|
5
|
+
class CodeRoutage < Base
|
|
6
|
+
ETAT_ACTIF = "A"
|
|
7
|
+
|
|
8
|
+
def identifiant_routage
|
|
9
|
+
payload["identifiantRoutage"]
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def libelle
|
|
13
|
+
payload["libelleCodeRoutage"]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def siret
|
|
17
|
+
payload["siret"]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def type_identifiant_routage
|
|
21
|
+
payload["typeIdentifiantRoutage"]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def etat_administratif
|
|
25
|
+
payload["etatAdministratif"]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def actif?
|
|
29
|
+
etat_administratif == ETAT_ACTIF
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# La recherche de codes routage est le seul accès à cette information : ni la consultation
|
|
33
|
+
# d'un établissement ni celle d'un code routage ne la servent.
|
|
34
|
+
def gestion_engagement_juridique?
|
|
35
|
+
payload["gestionEngagementJuridique"]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def adresse
|
|
39
|
+
payload["adresse"] || {}
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def code_postal
|
|
43
|
+
adresse["codePostal"]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def localite
|
|
47
|
+
adresse["localite"]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def etablissement
|
|
51
|
+
return nil unless payload["etablissement"]
|
|
52
|
+
|
|
53
|
+
@etablissement ||= Etablissement.new(payload["etablissement"])
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def unite_legale
|
|
57
|
+
return nil unless payload["uniteLegale"]
|
|
58
|
+
|
|
59
|
+
@unite_legale ||= UniteLegale.new(payload["uniteLegale"])
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def to_s
|
|
63
|
+
[identifiant_routage, libelle].compact.join(" — ")
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -2,12 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
module Cpro
|
|
4
4
|
module Middleware
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
# Faraday
|
|
5
|
+
# Porte le jeton OAuth2 PISTE, commun à l'application, et rejoue une fois l'appel refusé en
|
|
6
|
+
# 401. Le compte technique, lui, est posé par Resources::Base : il change d'un appel à l'autre
|
|
7
|
+
# et Faraday n'offre pas de contexte de requête avant la 1.0.
|
|
8
8
|
class Authentication < Faraday::Middleware
|
|
9
|
-
ACCOUNT_HEADER = "cpro-account"
|
|
10
|
-
|
|
11
9
|
def initialize(app, token_provider:)
|
|
12
10
|
super(app)
|
|
13
11
|
@token_provider = token_provider
|
|
@@ -43,8 +41,6 @@ module Cpro
|
|
|
43
41
|
|
|
44
42
|
def apply(env)
|
|
45
43
|
env.request_headers["Authorization"] = "Bearer #{@token_provider.token.value}"
|
|
46
|
-
account = env.request.context && env.request.context[:account]
|
|
47
|
-
env.request_headers[ACCOUNT_HEADER] = account.header_value if account
|
|
48
44
|
end
|
|
49
45
|
end
|
|
50
46
|
end
|
|
@@ -16,7 +16,16 @@ module Cpro
|
|
|
16
16
|
|
|
17
17
|
CORRELATION_HEADER = "x-correlationid"
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
# Appelé sur l'objet réponse : Faraday 0.9 tient `on_complete` de
|
|
20
|
+
# Faraday::Response::Middleware, que Faraday 2 a supprimé, et Faraday 2 le tient de
|
|
21
|
+
# Faraday::Middleware, que Faraday 0.9 n'en dote pas. La réponse, elle, l'expose partout.
|
|
22
|
+
def call(env)
|
|
23
|
+
@app.call(env).on_complete { |response| verifier(response) }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def verifier(env)
|
|
20
29
|
return if env.status < 400
|
|
21
30
|
|
|
22
31
|
raise error_class(env.status).new(
|
|
@@ -27,14 +36,13 @@ module Cpro
|
|
|
27
36
|
)
|
|
28
37
|
end
|
|
29
38
|
|
|
30
|
-
private
|
|
31
|
-
|
|
32
39
|
def error_class(status)
|
|
33
40
|
ERRORS[status] || (status >= 500 ? ServerError : ApiError)
|
|
34
41
|
end
|
|
35
42
|
|
|
43
|
+
# `reason_phrase` n'existe pas avant Faraday 1.0.
|
|
36
44
|
def message_for(env)
|
|
37
|
-
detail = detail_for(env.body) || env.reason_phrase
|
|
45
|
+
detail = detail_for(env.body) || (env.reason_phrase if env.respond_to?(:reason_phrase))
|
|
38
46
|
[env.status, detail].compact.join(" ")
|
|
39
47
|
end
|
|
40
48
|
|
|
@@ -8,11 +8,17 @@ module Cpro
|
|
|
8
8
|
SIRET_LENGTH = 14
|
|
9
9
|
SIREN_LENGTH = 9
|
|
10
10
|
|
|
11
|
-
# Chaque champ filtrable n'admet qu'un seul opérateur
|
|
12
|
-
# `siret: "
|
|
11
|
+
# Chaque champ filtrable n'admet qu'un seul opérateur : l'appelant fournit
|
|
12
|
+
# `siret: "70204275500240"` et l'opérateur attendu est déduit d'ici.
|
|
13
|
+
#
|
|
14
|
+
# `siret` et `siren` divergent du swagger, qui les déclare en `contient`. Le serveur refuse
|
|
15
|
+
# cette forme par un 400 « Failed to read HTTP message » — une erreur de désérialisation,
|
|
16
|
+
# levée avant tout traitement — et n'accepte que `strict`, sur les trois recherches de
|
|
17
|
+
# l'Annuaire. Vérifié en sandbox le 27/08/2026 ; ce sont des identifiants, une recherche
|
|
18
|
+
# partielle n'y a de toute façon pas de sens.
|
|
13
19
|
OPERATEURS_SIRET = {
|
|
14
|
-
"siret" => "
|
|
15
|
-
"siren" => "
|
|
20
|
+
"siret" => "strict",
|
|
21
|
+
"siren" => "strict",
|
|
16
22
|
"denomination" => "contient",
|
|
17
23
|
"localite" => "contient",
|
|
18
24
|
"codePostal" => "contient",
|
|
@@ -23,12 +29,22 @@ module Cpro
|
|
|
23
29
|
}.freeze
|
|
24
30
|
|
|
25
31
|
OPERATEURS_SIREN = {
|
|
26
|
-
"siren" => "
|
|
32
|
+
"siren" => "strict",
|
|
27
33
|
"raisonSociale" => "contient",
|
|
28
34
|
"typeEntite" => "strict",
|
|
29
35
|
"etatAdministratif" => "strict"
|
|
30
36
|
}.freeze
|
|
31
37
|
|
|
38
|
+
OPERATEURS_CODE_ROUTAGE = {
|
|
39
|
+
"siret" => "strict",
|
|
40
|
+
"identifiantRoutage" => "contient",
|
|
41
|
+
"libelleCodeRoutage" => "contient",
|
|
42
|
+
"localite" => "contient",
|
|
43
|
+
"codePostal" => "contient",
|
|
44
|
+
"lignesAdresse" => "contient",
|
|
45
|
+
"etatAdministratif" => "strict"
|
|
46
|
+
}.freeze
|
|
47
|
+
|
|
32
48
|
def find_by_siret(siret, **options)
|
|
33
49
|
path = "siret/code-insee:#{normalize(siret, SIRET_LENGTH, "SIRET")}"
|
|
34
50
|
Entities::Etablissement.new(get(path, query(options)))
|
|
@@ -52,6 +68,15 @@ module Cpro
|
|
|
52
68
|
Entities::ResultatRecherche.new(payload, item_class: Entities::UniteLegale, total_key: TOTAL_KEY)
|
|
53
69
|
end
|
|
54
70
|
|
|
71
|
+
# Seul accès au libellé d'un code routage et à son `gestionEngagementJuridique` : la
|
|
72
|
+
# consultation unitaire `GET /code-routage/siret:…/code:…` ne retourne que ses lignes
|
|
73
|
+
# d'annuaire, soit moins que `find_by_siret`. Filtrer sur `siret` pour se limiter à une
|
|
74
|
+
# structure, faute de quoi la réponse balaie tout l'annuaire.
|
|
75
|
+
def search_code_routage(filtres: {}, **options)
|
|
76
|
+
payload = post("code-routage/recherche", search_body(OPERATEURS_CODE_ROUTAGE, filtres, options))
|
|
77
|
+
Entities::ResultatRecherche.new(payload, item_class: Entities::CodeRoutage, total_key: TOTAL_KEY)
|
|
78
|
+
end
|
|
79
|
+
|
|
55
80
|
def healthcheck
|
|
56
81
|
get("healthcheck")
|
|
57
82
|
end
|
data/lib/cpro/resources/base.rb
CHANGED
|
@@ -23,10 +23,11 @@ module Cpro
|
|
|
23
23
|
# Le swagger Annuaire ne déclare pas cpro-account, mais l'API le rejette en 400 sans lui,
|
|
24
24
|
# healthcheck compris : toutes les ressources G2B l'exigent (vérifié en sandbox).
|
|
25
25
|
def request(method, path, params: nil, body: nil)
|
|
26
|
+
account = required_account
|
|
26
27
|
response = connection.public_send(method) do |request|
|
|
27
28
|
request.url(path, params || {})
|
|
28
29
|
request.body = body unless body.nil?
|
|
29
|
-
request.
|
|
30
|
+
request.headers[Account::HEADER] = account.header_value
|
|
30
31
|
end
|
|
31
32
|
response.body
|
|
32
33
|
end
|
data/lib/cpro/version.rb
CHANGED
data/lib/cpro.rb
CHANGED
|
@@ -8,6 +8,7 @@ require_relative "cpro/configuration"
|
|
|
8
8
|
require_relative "cpro/account"
|
|
9
9
|
require_relative "cpro/auth/token"
|
|
10
10
|
require_relative "cpro/auth/token_provider"
|
|
11
|
+
require_relative "cpro/middleware/json"
|
|
11
12
|
require_relative "cpro/middleware/authentication"
|
|
12
13
|
require_relative "cpro/middleware/raise_error"
|
|
13
14
|
require_relative "cpro/connection"
|
|
@@ -16,6 +17,7 @@ require_relative "cpro/entities/ligne_annuaire"
|
|
|
16
17
|
require_relative "cpro/entities/lignes_annuaire"
|
|
17
18
|
require_relative "cpro/entities/unite_legale"
|
|
18
19
|
require_relative "cpro/entities/etablissement"
|
|
20
|
+
require_relative "cpro/entities/code_routage"
|
|
19
21
|
require_relative "cpro/entities/note_statut"
|
|
20
22
|
require_relative "cpro/entities/motif_rejet"
|
|
21
23
|
require_relative "cpro/entities/changement_statut"
|
metadata
CHANGED
|
@@ -1,29 +1,35 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: cpro-client
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1
|
|
4
|
+
version: 0.2.1
|
|
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-08-
|
|
11
|
+
date: 2026-08-28 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: faraday
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
|
-
- - "
|
|
17
|
+
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '
|
|
19
|
+
version: '0.9'
|
|
20
|
+
- - "<"
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: '3.0'
|
|
20
23
|
type: :runtime
|
|
21
24
|
prerelease: false
|
|
22
25
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
26
|
requirements:
|
|
24
|
-
- - "
|
|
27
|
+
- - ">="
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
version: '0.9'
|
|
30
|
+
- - "<"
|
|
25
31
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: '
|
|
32
|
+
version: '3.0'
|
|
27
33
|
description: 'Client Ruby des API G2B de Chorus Pro (PPF) : authentification OAuth2
|
|
28
34
|
PISTE, consultation de l''annuaire par SIRET ou SIREN, et dépôt de factures Factur-X.'
|
|
29
35
|
email:
|
|
@@ -57,6 +63,7 @@ files:
|
|
|
57
63
|
- lib/cpro/connection.rb
|
|
58
64
|
- lib/cpro/entities/base.rb
|
|
59
65
|
- lib/cpro/entities/changement_statut.rb
|
|
66
|
+
- lib/cpro/entities/code_routage.rb
|
|
60
67
|
- lib/cpro/entities/depot_flux.rb
|
|
61
68
|
- lib/cpro/entities/etablissement.rb
|
|
62
69
|
- lib/cpro/entities/facture.rb
|