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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a797bddcc8e42f168e7c6ac7e18f6a02de3f45b6dbe6bc03f4eb7f11a3854800
4
- data.tar.gz: 25effc0a7fd95d27528d48c20d1289e236717ad7efbe3318d9a3284033ecf720
3
+ metadata.gz: 40f1e1557770b6ee03af92e419c95e19071d2b4a53848db46cdd76e409802eb6
4
+ data.tar.gz: 2f75110a47a4954f8151fe5d5cd85daa17e4d3e8b924512a96eab899865faf41
5
5
  SHA512:
6
- metadata.gz: '095e98ac8c3752f4a117aab59f74bb77a15cd506bd5621637185e857261c8471d53850cbf5d42e2fcf72f054435ced5d060a26c659942224d80d3561813ea189'
7
- data.tar.gz: e74b20cba8005d0d7b14b178d8e8e80e48b8f96a84f6c5cd859f7465abdaa1d1ed58d787c659a378b0896a592de9a61ee645266128d1241922a3665211992df2
6
+ metadata.gz: 7e479ee8731fcd352635ade84a00ffc1a22901ce9d986d4ee47b26a65bc4313a746fbc40b6e7917191aab6002d2b95e0409b230cebae81e9ff952253479ff0e4
7
+ data.tar.gz: 9a75779dfefdd3a2b1e1ff5f156cc051969554cb7ac557bdc24c2c677ac0697d5f754f57274b38374ac27969b2af7011385d62f3a672f2cac518844c55fdf416
data/.gitignore CHANGED
@@ -8,3 +8,4 @@
8
8
  /tmp/
9
9
  /.idea/
10
10
  /local/
11
+ *.gem
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
- et rejeu unique d'un appel refusé en 401.
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.0)
5
- faraday (~> 2.0)
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.denomination
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
- Recherche multi-critères l'opérateur de comparaison (`contient` ou `strict`) est déduit du champ :
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.map(&:siret)
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
- ### Limite actuelle de l'Annuaire
294
-
295
- En sandbox, `find_by_siret` et `find_by_siren` ne renvoient que le bloc `lignesAnnuaire` : `siret`,
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`. Le mapping suit le
298
- swagger et se remplira si l'implémentation se complète ; en attendant, l'information exploitable est
299
- `lignes_annuaire`, qui porte les `identifiantAdressage` nécessaires à l'adressage de la facture.
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
@@ -28,5 +28,5 @@ Gem::Specification.new do |spec|
28
28
  spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
29
29
  spec.require_paths = ["lib"]
30
30
 
31
- spec.add_dependency "faraday", "~> 2.0"
31
+ spec.add_dependency "faraday", ">= 0.9", "< 3.0"
32
32
  end
data/lib/cpro/account.rb CHANGED
@@ -4,6 +4,8 @@ require "base64"
4
4
 
5
5
  module Cpro
6
6
  class Account
7
+ HEADER = "cpro-account"
8
+
7
9
  attr_reader :login
8
10
 
9
11
  def initialize(login:, password:)
@@ -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.response :json, content_type: /\bjson$/
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
@@ -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.request :json
8
+ faraday.use Middleware::EncodeJson
9
9
  faraday.use Middleware::Authentication, token_provider: token_provider
10
10
  faraday.use Middleware::RaiseError
11
- faraday.response :json, content_type: /\bjson$/
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
- # Injecte les deux niveaux d'authentification Chorus Pro : le jeton OAuth2 PISTE, commun à
6
- # l'application, et le compte technique `cpro-account`, porté par requête via le contexte
7
- # Faraday puisqu'il peut changer d'un appel à l'autre.
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
- def on_complete(env)
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 dans le swagger : l'appelant fournit
12
- # `siret: "702042755"` et l'opérateur attendu est déduit d'ici.
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" => "contient",
15
- "siren" => "contient",
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" => "contient",
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
@@ -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.options.context = { account: required_account }
30
+ request.headers[Account::HEADER] = account.header_value
30
31
  end
31
32
  response.body
32
33
  end
data/lib/cpro/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Cpro
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.1"
5
5
  end
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.0
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-27 00:00:00.000000000 Z
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: '2.0'
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: '2.0'
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