cpro-client 0.2.2 → 0.2.4
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 +34 -0
- data/Gemfile.lock +1 -1
- data/README.md +28 -0
- data/lib/cpro/adressage.rb +70 -0
- data/lib/cpro/entities/ligne_annuaire.rb +5 -0
- data/lib/cpro/enveloppe.rb +21 -8
- data/lib/cpro/resources/annuaire.rb +27 -0
- data/lib/cpro/version.rb +1 -1
- data/lib/cpro.rb +1 -0
- 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: eed2218b6135210909bb231938a2adf3d8f9b432a19468576b232ffd2f352d7a
|
|
4
|
+
data.tar.gz: 95739510c05be2fdc248b2f001e4b53e08b3487162dbb29f50bc26606d929e06
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7208819c95be0220d40498c3d09c5df5479619c652c6103aac41903ce1cac6054f47b53a7c87638fd7b474e99581a61b6924ab1d0f17b633bb18a72d431e8a94
|
|
7
|
+
data.tar.gz: 4c292e8c13863bdff0fe54ab567e42880c4d25e5d928ca70fb6c1a83284198912cc14b5dd787f0733763e181a7923f08ab7768a0ee5007b5b4e52b3719c4b7f0
|
data/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,40 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
## [0.2.3] - 2026-08-31
|
|
5
|
+
|
|
6
|
+
### Ajouté
|
|
7
|
+
- `Cpro::Resources::Annuaire#find_identifiant_actif` résout une valeur d'adressage — SIREN, SIRET,
|
|
8
|
+
SIRET_SERVICE ou SIREN_SIRET_SERVICE — vers la ligne d'annuaire active la plus proche : la
|
|
9
|
+
valeur elle-même si sa plateforme est active, sinon son parent le plus proche, en remontant
|
|
10
|
+
jusqu'au SIREN. Rend `nil` lorsqu'aucun niveau n'est adressable. Un seul appel réseau, la
|
|
11
|
+
consultation par SIREN retournant aussi les lignes SIRET et service.
|
|
12
|
+
- `Cpro::Resources::Annuaire#find_lignes_adressage` rend les mêmes lignes sans filtre de
|
|
13
|
+
plateforme, pour distinguer « unité légale inconnue » de « connue mais sans plateforme active ».
|
|
14
|
+
- `Cpro::Adressage` décompose une valeur d'adressage en SIREN, SIRET et code service. Le code
|
|
15
|
+
service se lit comme tout ce qui suit le SIRET : il contient lui-même des underscores
|
|
16
|
+
(`SERVICE_PUBLIQUE_1_71915767420316`) et peut être numérique (`2026`), un découpage sur « _ »
|
|
17
|
+
serait donc faux. Un SIREN incohérent avec le SIRET qui le suit lève une `ArgumentError`.
|
|
18
|
+
- `Cpro::Entities::LigneAnnuaire#to_s` rend l'identifiant d'adressage, la valeur à porter en BT-49.
|
|
19
|
+
|
|
20
|
+
### Corrigé
|
|
21
|
+
- `Cpro::Enveloppe.from` prenait un `Pathname` pour un flux, celui-ci répondant à `read` sans
|
|
22
|
+
porter de `path` : tout dépôt fait à partir d'un `Pathname` — la forme naturelle sous Rails, où
|
|
23
|
+
les chemins viennent de `Rails.root.join` — échouait sur `ArgumentError (un nom de fichier est
|
|
24
|
+
requis)` alors même qu'un chemin était fourni. Un `Pathname` est désormais traité comme un
|
|
25
|
+
chemin, donc relu en binaire plutôt que dans l'encodage par défaut du processus.
|
|
26
|
+
- Les sources qui sont réellement des flux passent en `binmode` avant lecture, pour qu'un IO
|
|
27
|
+
ouvert en mode texte ne dénature pas le PDF.
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
## [0.2.2] - 2026-08-30
|
|
31
|
+
|
|
32
|
+
### Corrigé
|
|
33
|
+
- `lib/cpro/middleware/json.rb` et son test étaient absents de la 0.2.1 : n'ayant pas été suivis
|
|
34
|
+
par git, ils étaient exclus de `spec.files`, que le gemspec construit depuis `git ls-files`. La
|
|
35
|
+
gem publiée échouait donc au chargement sur `require_relative`.
|
|
36
|
+
|
|
37
|
+
|
|
4
38
|
## [0.2.1] - 2026-08-28
|
|
5
39
|
|
|
6
40
|
### Modifié
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -136,6 +136,34 @@ codes.first.actif?
|
|
|
136
136
|
|
|
137
137
|
Filtrez sur `siret`, faute de quoi la réponse balaie tout l'annuaire.
|
|
138
138
|
|
|
139
|
+
## Adresser une facture
|
|
140
|
+
|
|
141
|
+
BT-49, l'adresse électronique de l'acheteur, ne se transmet pas à l'API : elle voyage dans le XML
|
|
142
|
+
Factur-X, et c'est d'elle que le PPF déduit l'acheminement. Une facture qui la porte mal est
|
|
143
|
+
rejetée `REJ_ADR` — « le destinataire n'est pas trouvable » — plusieurs minutes après le dépôt.
|
|
144
|
+
|
|
145
|
+
`find_identifiant_actif` fait la résolution en un appel. Il accepte les quatre formes d'une valeur
|
|
146
|
+
d'adressage — SIREN, SIRET, SIRET_SERVICE, SIREN_SIRET_SERVICE — et remonte la hiérarchie
|
|
147
|
+
SIREN → SIRET → service jusqu'à trouver une ligne dont la plateforme est active :
|
|
148
|
+
|
|
149
|
+
```ruby
|
|
150
|
+
ligne = client.annuaire.find_identifiant_actif("71915767420316")
|
|
151
|
+
ligne.to_s # => "719157674_71915767420316", à porter en BT-49
|
|
152
|
+
ligne.identifiant_routage # => "" — l'adresse de base de l'établissement
|
|
153
|
+
|
|
154
|
+
client.annuaire.find_identifiant_actif("71915767420316_SERVICE_INEXISTANT").to_s
|
|
155
|
+
# => "719157674_71915767420316" — service inconnu, on retombe sur son parent
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
`nil` signifie qu'aucun niveau n'est adressable, donc qu'on ne peut pas facturer. Pour savoir
|
|
159
|
+
*pourquoi*, `find_lignes_adressage` rend les mêmes lignes sans filtrer : une unité légale inconnue
|
|
160
|
+
lève une `Cpro::NotFoundError`, tandis qu'une unité connue mais non raccordée rend ses lignes avec
|
|
161
|
+
`plateforme_active?` à faux.
|
|
162
|
+
|
|
163
|
+
Deux limites à connaître. La résolution ne redescend jamais vers les enfants : un SIREN nu ne
|
|
164
|
+
remonte rien si la structure n'a d'adresses qu'au niveau SIRET. Et une adresse active n'est pas une
|
|
165
|
+
promesse d'acheminement — elle garantit que le destinataire est identifiable, pas qu'il recevra.
|
|
166
|
+
|
|
139
167
|
## Transmission d'une facture Factur-X
|
|
140
168
|
|
|
141
169
|
```ruby
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cpro
|
|
4
|
+
# Situe une valeur d'adressage dans la hiérarchie de l'annuaire : SIREN → SIRET → service.
|
|
5
|
+
# Quatre formes sont acceptées — SIREN, SIRET, SIRET_SERVICE et SIREN_SIRET_SERVICE.
|
|
6
|
+
#
|
|
7
|
+
# Le service ne se lit jamais par découpage sur « _ » : il en contient lui-même
|
|
8
|
+
# (SERVICE_PUBLIQUE_1_71915767420316) et peut être purement numérique ("2026"). Il se lit comme
|
|
9
|
+
# tout ce qui suit le SIRET.
|
|
10
|
+
class Adressage
|
|
11
|
+
PREFIXE_SIREN = /\A(\d{9})_(?=\d{14})/.freeze
|
|
12
|
+
SIRET = /\A(\d{14})/.freeze
|
|
13
|
+
SIREN_SEUL = /\A(\d{9})\z/.freeze
|
|
14
|
+
|
|
15
|
+
attr_reader :siren, :siret, :service
|
|
16
|
+
|
|
17
|
+
def self.parse(valeur)
|
|
18
|
+
texte = valeur.to_s.strip
|
|
19
|
+
raise ArgumentError, "valeur d'adressage vide" if texte.empty?
|
|
20
|
+
|
|
21
|
+
prefixe = texte[PREFIXE_SIREN, 1]
|
|
22
|
+
reste = prefixe ? texte[10..] : texte
|
|
23
|
+
depuis_siret(prefixe, reste) || depuis_siren(reste) ||
|
|
24
|
+
raise(ArgumentError, "valeur d'adressage illisible : #{valeur.inspect} " \
|
|
25
|
+
"(attendus : SIREN, SIRET, SIRET_SERVICE ou SIREN_SIRET_SERVICE)")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.depuis_siret(prefixe, reste)
|
|
29
|
+
siret = reste[SIRET, 1]
|
|
30
|
+
return nil if siret.nil?
|
|
31
|
+
|
|
32
|
+
raise ArgumentError, "SIREN #{prefixe} incohérent avec le SIRET #{siret}" if prefixe && prefixe != siret[0, 9]
|
|
33
|
+
|
|
34
|
+
new(siren: siret[0, 9], siret: siret, service: reste[14..].to_s.sub(/\A_/, ""))
|
|
35
|
+
end
|
|
36
|
+
private_class_method :depuis_siret
|
|
37
|
+
|
|
38
|
+
def self.depuis_siren(reste)
|
|
39
|
+
siren = reste[SIREN_SEUL, 1]
|
|
40
|
+
siren && new(siren: siren, siret: nil, service: nil)
|
|
41
|
+
end
|
|
42
|
+
private_class_method :depuis_siren
|
|
43
|
+
|
|
44
|
+
def initialize(siren:, siret: nil, service: nil)
|
|
45
|
+
@siren = siren
|
|
46
|
+
@siret = siret
|
|
47
|
+
@service = service.nil? || service.empty? ? nil : service
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Du plus précis au plus général : la valeur elle-même, puis ses parents successifs. C'est
|
|
51
|
+
# l'ordre dans lequel une adresse active est cherchée.
|
|
52
|
+
def paliers
|
|
53
|
+
[
|
|
54
|
+
(self.class.new(siren: siren, siret: siret, service: service) if service),
|
|
55
|
+
(self.class.new(siren: siren, siret: siret) if siret),
|
|
56
|
+
self.class.new(siren: siren)
|
|
57
|
+
].compact
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Une ligne d'annuaire correspond à ce palier si elle décrit le même niveau de précision.
|
|
61
|
+
def correspond?(ligne)
|
|
62
|
+
ligne.siret == siret && ligne.identifiant_routage.to_s.empty? == service.nil? &&
|
|
63
|
+
(service.nil? || ligne.identifiant_routage == service)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def to_s
|
|
67
|
+
[siret ? "#{siren}_#{siret}" : siren, service].compact.join("_")
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -37,6 +37,11 @@ module Cpro
|
|
|
37
37
|
def plateforme_en_attente?
|
|
38
38
|
statut_plateforme == PLATEFORME_EN_ATTENTE
|
|
39
39
|
end
|
|
40
|
+
|
|
41
|
+
# L'identifiant d'adressage est ce qu'on porte en BT-49 : c'est la valeur utile de la ligne.
|
|
42
|
+
def to_s
|
|
43
|
+
identifiant_adressage.to_s
|
|
44
|
+
end
|
|
40
45
|
end
|
|
41
46
|
end
|
|
42
47
|
end
|
data/lib/cpro/enveloppe.rb
CHANGED
|
@@ -17,15 +17,28 @@ module Cpro
|
|
|
17
17
|
attr_reader :content, :name, :code_interface
|
|
18
18
|
|
|
19
19
|
def self.from(source, code_interface:, name: nil)
|
|
20
|
-
if
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
20
|
+
return depuis_flux(source, code_interface, name) if flux?(source)
|
|
21
|
+
|
|
22
|
+
chemin = source.respond_to?(:to_path) ? source.to_path : source.to_s
|
|
23
|
+
new(content: File.binread(chemin), name: name || File.basename(chemin),
|
|
24
|
+
code_interface: code_interface)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Répondre à `read` ne suffit pas à faire un flux : un Pathname en répond aussi, sans être
|
|
28
|
+
# ouvrable ni porter de `path`. Le distinguer par `binmode`, que tout IO possède et qu'aucun
|
|
29
|
+
# chemin n'a, évite de le traiter en flux anonyme — et le fait relire en binaire, là où
|
|
30
|
+
# Pathname#read l'aurait rendu dans l'encodage par défaut.
|
|
31
|
+
def self.flux?(source)
|
|
32
|
+
source.respond_to?(:read) && source.respond_to?(:binmode)
|
|
33
|
+
end
|
|
34
|
+
private_class_method :flux?
|
|
35
|
+
|
|
36
|
+
def self.depuis_flux(source, code_interface, name)
|
|
37
|
+
source.binmode
|
|
38
|
+
new(content: source.read, code_interface: code_interface,
|
|
39
|
+
name: name || (File.basename(source.path) if source.respond_to?(:path)))
|
|
28
40
|
end
|
|
41
|
+
private_class_method :depuis_flux
|
|
29
42
|
|
|
30
43
|
def initialize(content:, name:, code_interface:)
|
|
31
44
|
raise ArgumentError, "le contenu du flux est vide" if content.nil? || content.empty?
|
|
@@ -77,6 +77,33 @@ module Cpro
|
|
|
77
77
|
Entities::ResultatRecherche.new(payload, item_class: Entities::CodeRoutage, total_key: TOTAL_KEY)
|
|
78
78
|
end
|
|
79
79
|
|
|
80
|
+
# Adresse active la plus proche d'une valeur d'adressage : la valeur elle-même si elle porte
|
|
81
|
+
# une plateforme active, sinon son parent le plus proche, et ainsi de suite jusqu'au SIREN.
|
|
82
|
+
# Rend la ligne d'annuaire, dont `to_s` donne l'identifiant à porter en BT-49.
|
|
83
|
+
#
|
|
84
|
+
# `nil` a un sens unique — aucun niveau n'est adressable, donc on ne peut pas facturer. Pour
|
|
85
|
+
# distinguer « SIREN inconnu » de « connu mais sans plateforme active », voir
|
|
86
|
+
# `#find_lignes_adressage`, qui ne filtre pas et laisse remonter le 404.
|
|
87
|
+
#
|
|
88
|
+
# Un seul appel réseau : `GET /siren/code-insee:` retourne aussi les lignes SIRET et service.
|
|
89
|
+
def find_identifiant_actif(valeur)
|
|
90
|
+
adressage = Adressage.parse(valeur)
|
|
91
|
+
actives = find_lignes_adressage(adressage).select(&:plateforme_active?)
|
|
92
|
+
adressage.paliers.each do |palier|
|
|
93
|
+
ligne = actives.find { |active| palier.correspond?(active) }
|
|
94
|
+
return ligne if ligne
|
|
95
|
+
end
|
|
96
|
+
nil
|
|
97
|
+
rescue NotFoundError
|
|
98
|
+
nil
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Toutes les lignes d'annuaire de l'unité légale, sans filtre de plateforme.
|
|
102
|
+
def find_lignes_adressage(valeur, **options)
|
|
103
|
+
adressage = valeur.is_a?(Adressage) ? valeur : Adressage.parse(valeur)
|
|
104
|
+
find_by_siren(adressage.siren, **options).lignes_annuaire
|
|
105
|
+
end
|
|
106
|
+
|
|
80
107
|
def healthcheck
|
|
81
108
|
get("healthcheck")
|
|
82
109
|
end
|
data/lib/cpro/version.rb
CHANGED
data/lib/cpro.rb
CHANGED
|
@@ -6,6 +6,7 @@ require_relative "cpro/version"
|
|
|
6
6
|
require_relative "cpro/errors"
|
|
7
7
|
require_relative "cpro/configuration"
|
|
8
8
|
require_relative "cpro/account"
|
|
9
|
+
require_relative "cpro/adressage"
|
|
9
10
|
require_relative "cpro/auth/token"
|
|
10
11
|
require_relative "cpro/auth/token_provider"
|
|
11
12
|
require_relative "cpro/middleware/json"
|
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.4
|
|
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-
|
|
11
|
+
date: 2026-09-04 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: faraday
|
|
@@ -53,6 +53,7 @@ files:
|
|
|
53
53
|
- lib/cpro-client.rb
|
|
54
54
|
- lib/cpro.rb
|
|
55
55
|
- lib/cpro/account.rb
|
|
56
|
+
- lib/cpro/adressage.rb
|
|
56
57
|
- lib/cpro/archive.rb
|
|
57
58
|
- lib/cpro/auth/token.rb
|
|
58
59
|
- lib/cpro/auth/token_provider.rb
|