einvoicing-connect 0.1.0
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 +7 -0
- data/config/locales/en.yml +13 -0
- data/config/locales/fr.yml +13 -0
- data/lib/einvoicing/connect/i18n.rb +16 -0
- data/lib/einvoicing/connect/version.rb +7 -0
- data/lib/einvoicing/connect.rb +10 -0
- data/lib/einvoicing/fr/siret_lookup.rb +54 -0
- data/lib/einvoicing/ppf/client.rb +117 -0
- data/lib/einvoicing/ppf/errors.rb +12 -0
- data/lib/einvoicing/ppf/invoice_adapter.rb +61 -0
- data/lib/einvoicing/ppf/submitter.rb +32 -0
- data/lib/einvoicing-connect.rb +17 -0
- metadata +141 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 54ce574178b7dcbed5b437a34f88672ae3d0d5923f7262d679cbc927d582265e
|
|
4
|
+
data.tar.gz: 96f661a9749c2b1c41b79bc5f52c9c48823bef8d314a7432e783137c560be7c8
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 4d32efd286fb9e98e0b3e9038e409e40f4f44f9d3b641e4dccb94584e4866daa076146799d8d2985dfbe5799966d8f3c8af43332d9f70a8daafeeface89c9909
|
|
7
|
+
data.tar.gz: 03df43147ea54a77683c0775bfda4f94c0e492cfca4427c8e7a1ded0215d266f79fe845984fb7864ef70f783cf13fb93d80f848ed8656860ebb5787a4ed47c90
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
en:
|
|
2
|
+
einvoicing:
|
|
3
|
+
connect:
|
|
4
|
+
ppf:
|
|
5
|
+
auth_failed: "OAuth token request failed: %{code} %{body}"
|
|
6
|
+
unauthorized: "Unauthorized: %{body}"
|
|
7
|
+
forbidden: "Forbidden: %{body}"
|
|
8
|
+
not_found: "Not found: %{body}"
|
|
9
|
+
api_error: "API error %{code}: %{body}"
|
|
10
|
+
structure_not_found: "Buyer SIRET %{siret} not found in Chorus Pro"
|
|
11
|
+
fr:
|
|
12
|
+
siret_api_error: "SIRET lookup failed: %{message}"
|
|
13
|
+
siret_not_found: "No company found for SIREN: %{siren}"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
fr:
|
|
2
|
+
einvoicing:
|
|
3
|
+
connect:
|
|
4
|
+
ppf:
|
|
5
|
+
auth_failed: "Échec de la requête de jeton OAuth : %{code} %{body}"
|
|
6
|
+
unauthorized: "Non autorisé : %{body}"
|
|
7
|
+
forbidden: "Accès interdit : %{body}"
|
|
8
|
+
not_found: "Non trouvé : %{body}"
|
|
9
|
+
api_error: "Erreur API %{code} : %{body}"
|
|
10
|
+
structure_not_found: "SIRET acheteur %{siret} non trouvé dans Chorus Pro"
|
|
11
|
+
fr:
|
|
12
|
+
siret_api_error: "Échec de la recherche SIRET : %{message}"
|
|
13
|
+
siret_not_found: "Aucune entreprise trouvée pour le SIREN : %{siren}"
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "i18n"
|
|
4
|
+
|
|
5
|
+
module Einvoicing
|
|
6
|
+
module Connect
|
|
7
|
+
module I18nSetup
|
|
8
|
+
def self.setup
|
|
9
|
+
::I18n.load_path += Dir[File.join(__dir__, "../../../config/locales/*.yml")]
|
|
10
|
+
::I18n.backend.load_translations
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
Einvoicing::Connect::I18nSetup.setup
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "uri"
|
|
5
|
+
require "json"
|
|
6
|
+
|
|
7
|
+
module Einvoicing
|
|
8
|
+
module FR
|
|
9
|
+
module SiretLookup
|
|
10
|
+
API_URL = "https://recherche-entreprises.api.gouv.fr/search" unless defined?(API_URL)
|
|
11
|
+
|
|
12
|
+
# Find SIRET for a given SIREN using the French government Sirene API.
|
|
13
|
+
# Returns { siret:, name:, address: } or nil on any error.
|
|
14
|
+
def self.find(siren)
|
|
15
|
+
return nil unless siren.to_s.match?(/\A\d{9}\z/)
|
|
16
|
+
|
|
17
|
+
uri = URI(API_URL)
|
|
18
|
+
uri.query = URI.encode_www_form(q: siren.to_s, mtq: "true")
|
|
19
|
+
|
|
20
|
+
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true,
|
|
21
|
+
open_timeout: 5, read_timeout: 10) do |http|
|
|
22
|
+
http.get(uri.request_uri)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
return nil unless response.code == "200"
|
|
26
|
+
|
|
27
|
+
data = JSON.parse(response.body)
|
|
28
|
+
result = data["results"]&.first
|
|
29
|
+
return nil unless result
|
|
30
|
+
|
|
31
|
+
siege = result["siege"] || {}
|
|
32
|
+
siret = siege["siret"]
|
|
33
|
+
return nil if siret.nil? || siret.empty?
|
|
34
|
+
|
|
35
|
+
{ siret: siret, name: result["nom_complet"], address: siege["adresse"] }
|
|
36
|
+
rescue StandardError
|
|
37
|
+
nil
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Enrich a Party object by fetching and setting its SIRET from the API.
|
|
41
|
+
# Only calls the API if party.siren is present and party.siret is blank.
|
|
42
|
+
# Returns the party.
|
|
43
|
+
def self.enrich!(party)
|
|
44
|
+
return party if party.siren.to_s.strip.empty?
|
|
45
|
+
return party if party.respond_to?(:siret) && !party.siret.to_s.strip.empty?
|
|
46
|
+
|
|
47
|
+
result = find(party.siren.to_s.gsub(/\s/, ""))
|
|
48
|
+
return party unless result&.dig(:siret)
|
|
49
|
+
|
|
50
|
+
party.with(siret: result[:siret])
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "json"
|
|
5
|
+
|
|
6
|
+
module Einvoicing
|
|
7
|
+
module PPF
|
|
8
|
+
class Client
|
|
9
|
+
SANDBOX_OAUTH_URL = "https://sandbox-oauth.piste.gouv.fr" unless defined?(SANDBOX_OAUTH_URL)
|
|
10
|
+
SANDBOX_API_URL = "https://sandbox-api.piste.gouv.fr/cpro/factures" unless defined?(SANDBOX_API_URL)
|
|
11
|
+
PROD_OAUTH_URL = "https://oauth.piste.gouv.fr" unless defined?(PROD_OAUTH_URL)
|
|
12
|
+
PROD_API_URL = "https://api.piste.gouv.fr/cpro/factures" unless defined?(PROD_API_URL)
|
|
13
|
+
|
|
14
|
+
attr_reader :sandbox
|
|
15
|
+
|
|
16
|
+
def initialize(client_id:, client_secret:, sandbox: true)
|
|
17
|
+
@client_id = client_id
|
|
18
|
+
@client_secret = client_secret
|
|
19
|
+
@sandbox = sandbox
|
|
20
|
+
@token = nil
|
|
21
|
+
@token_expires_at = nil
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Returns current access token, refreshing if expired.
|
|
25
|
+
def access_token
|
|
26
|
+
refresh_token! if token_expired?
|
|
27
|
+
@token
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# POST /v1/rechercher/structure — find structure by SIRET, returns idStructureCPP.
|
|
31
|
+
def find_structure(siret:)
|
|
32
|
+
post("/v1/rechercher/structure", { siret: siret })
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# POST /v1/consulter/structure — get mandatory params (engagement, service codes).
|
|
36
|
+
def get_structure(id_structure_cpp:)
|
|
37
|
+
post("/v1/consulter/structure", { idStructureCPP: id_structure_cpp })
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# POST /v1/rechercher/service/structure — list active services for a structure.
|
|
41
|
+
def list_services(id_structure_cpp:, page: 1)
|
|
42
|
+
post("/v1/rechercher/service/structure", {
|
|
43
|
+
idStructureCPP: id_structure_cpp,
|
|
44
|
+
pageResultat: page
|
|
45
|
+
})
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# POST /v1/soumettre/factures — submit an invoice.
|
|
49
|
+
# facture_hash: Hash matching the Chorus Pro invoice schema.
|
|
50
|
+
def submit_invoice(facture_hash)
|
|
51
|
+
post("/v1/soumettre/factures", facture_hash)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def base_url
|
|
57
|
+
sandbox ? SANDBOX_API_URL : PROD_API_URL
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def oauth_url
|
|
61
|
+
sandbox ? SANDBOX_OAUTH_URL : PROD_OAUTH_URL
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def token_expired?
|
|
65
|
+
@token.nil? || @token_expires_at.nil? || Time.now >= @token_expires_at
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def refresh_token!
|
|
69
|
+
uri = URI("#{oauth_url}/api/oauth/token")
|
|
70
|
+
req = Net::HTTP::Post.new(uri)
|
|
71
|
+
req.set_form_data(
|
|
72
|
+
grant_type: "client_credentials",
|
|
73
|
+
client_id: @client_id,
|
|
74
|
+
client_secret: @client_secret,
|
|
75
|
+
scope: "openid"
|
|
76
|
+
)
|
|
77
|
+
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
|
|
78
|
+
raise AuthenticationError, ::I18n.t("einvoicing.connect.ppf.auth_failed", code: res.code, body: res.body) unless res.is_a?(Net::HTTPSuccess)
|
|
79
|
+
|
|
80
|
+
data = JSON.parse(res.body)
|
|
81
|
+
@token = data["access_token"]
|
|
82
|
+
@token_expires_at = Time.now + data.fetch("expires_in", 3600).to_i - 60
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def post(path, body)
|
|
86
|
+
uri = URI("#{base_url}#{path}")
|
|
87
|
+
req = Net::HTTP::Post.new(uri)
|
|
88
|
+
req["Authorization"] = "Bearer #{access_token}"
|
|
89
|
+
req["Content-Type"] = "application/json;charset=UTF-8"
|
|
90
|
+
req["Accept"] = "application/json"
|
|
91
|
+
req.body = body.to_json
|
|
92
|
+
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
|
|
93
|
+
handle_response(res)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def handle_response(res)
|
|
97
|
+
body = begin
|
|
98
|
+
JSON.parse(res.body)
|
|
99
|
+
rescue StandardError
|
|
100
|
+
res.body
|
|
101
|
+
end
|
|
102
|
+
case res
|
|
103
|
+
when Net::HTTPSuccess
|
|
104
|
+
body
|
|
105
|
+
when Net::HTTPUnauthorized
|
|
106
|
+
raise AuthenticationError, ::I18n.t("einvoicing.connect.ppf.unauthorized", body: body)
|
|
107
|
+
when Net::HTTPForbidden
|
|
108
|
+
raise AuthorizationError, ::I18n.t("einvoicing.connect.ppf.forbidden", body: body)
|
|
109
|
+
when Net::HTTPNotFound
|
|
110
|
+
raise NotFoundError, ::I18n.t("einvoicing.connect.ppf.not_found", body: body)
|
|
111
|
+
else
|
|
112
|
+
raise APIError, ::I18n.t("einvoicing.connect.ppf.api_error", code: res.code, body: body)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Einvoicing
|
|
4
|
+
module PPF
|
|
5
|
+
Error = Class.new(StandardError) unless defined?(Error)
|
|
6
|
+
AuthenticationError = Class.new(Error) unless defined?(AuthenticationError)
|
|
7
|
+
AuthorizationError = Class.new(Error) unless defined?(AuthorizationError)
|
|
8
|
+
NotFoundError = Class.new(Error) unless defined?(NotFoundError)
|
|
9
|
+
APIError = Class.new(Error) unless defined?(APIError)
|
|
10
|
+
ValidationError = Class.new(Error) unless defined?(ValidationError)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Einvoicing
|
|
4
|
+
module PPF
|
|
5
|
+
class InvoiceAdapter
|
|
6
|
+
# Converts an Einvoicing::Invoice to a Chorus Pro SoumettreFacture payload.
|
|
7
|
+
#
|
|
8
|
+
# @param invoice [Einvoicing::Invoice]
|
|
9
|
+
# @param id_structure_cpp [Integer] from find_structure()
|
|
10
|
+
# @param code_service [String, nil] from list_services() — optional
|
|
11
|
+
# @param numero_engagement [String, nil] buyer PO/engagement number — optional for B2B
|
|
12
|
+
def self.to_chorus_payload(invoice, id_structure_cpp:, code_service: nil, numero_engagement: nil)
|
|
13
|
+
{
|
|
14
|
+
idStructureCPP: id_structure_cpp,
|
|
15
|
+
codeService: code_service,
|
|
16
|
+
numeroEngagement: numero_engagement,
|
|
17
|
+
cadreFacturation: {
|
|
18
|
+
codeCadreFacturation: "FACTURE_FOURNISSEUR",
|
|
19
|
+
codeServiceValideur: nil
|
|
20
|
+
},
|
|
21
|
+
identifiantFactureFournisseur: invoice.invoice_number,
|
|
22
|
+
dateFacture: invoice.issue_date.strftime("%Y-%m-%dT00:00:00.000+01:00"),
|
|
23
|
+
dateEcheancePaiement: invoice.due_date&.strftime("%Y-%m-%dT00:00:00.000+01:00"),
|
|
24
|
+
montantHT: invoice.net_total.to_f,
|
|
25
|
+
montantTVA: invoice.tax_total.to_f,
|
|
26
|
+
montantTTC: invoice.gross_total.to_f,
|
|
27
|
+
devise: invoice.currency || "EUR",
|
|
28
|
+
siretFournisseur: invoice.seller.siret,
|
|
29
|
+
siretDestinataire: invoice.buyer.siret,
|
|
30
|
+
typeFacture: "FACTURE",
|
|
31
|
+
lignesPoste: invoice.lines.map.with_index(1) { |line, i| line_to_chorus(line, i) },
|
|
32
|
+
modePaiement: chorus_payment_mode(invoice)
|
|
33
|
+
}.compact
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private_class_method def self.line_to_chorus(line, index)
|
|
37
|
+
{
|
|
38
|
+
numeroLigne: index,
|
|
39
|
+
designation: line.description,
|
|
40
|
+
quantite: line.quantity.to_f,
|
|
41
|
+
unite: "U",
|
|
42
|
+
prixUnitaireHT: line.unit_price.to_f,
|
|
43
|
+
montantHT: line.net_amount.to_f,
|
|
44
|
+
tauxTVA: (line.vat_rate * 100).to_f,
|
|
45
|
+
montantTVA: (line.net_amount * line.vat_rate).to_f.round(2)
|
|
46
|
+
}
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private_class_method def self.chorus_payment_mode(invoice)
|
|
50
|
+
return nil unless invoice.respond_to?(:payment_means_code) && invoice.payment_means_code
|
|
51
|
+
|
|
52
|
+
code_map = { 30 => "VIREMENT", 42 => "VIREMENT", 58 => "VIREMENT" }
|
|
53
|
+
mode = code_map[invoice.payment_means_code] || "VIREMENT"
|
|
54
|
+
result = { modePaiement: mode }
|
|
55
|
+
result[:iban] = invoice.iban if invoice.respond_to?(:iban) && invoice.iban
|
|
56
|
+
result[:bic] = invoice.bic if invoice.respond_to?(:bic) && invoice.bic
|
|
57
|
+
result
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Einvoicing
|
|
4
|
+
module PPF
|
|
5
|
+
class Submitter
|
|
6
|
+
def initialize(client)
|
|
7
|
+
@client = client
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Submit an invoice to Chorus Pro / PPF.
|
|
11
|
+
# Returns the submission result hash from the API.
|
|
12
|
+
#
|
|
13
|
+
# @param invoice [Einvoicing::Invoice]
|
|
14
|
+
# @param code_service [String, nil] optional service code from list_services
|
|
15
|
+
# @param numero_engagement [String, nil] optional engagement number
|
|
16
|
+
def submit(invoice, code_service: nil, numero_engagement: nil)
|
|
17
|
+
structure = @client.find_structure(siret: invoice.buyer.siret)
|
|
18
|
+
id_structure = structure["idStructureCPP"] || structure.dig("parametres", "idStructureCPP")
|
|
19
|
+
raise ValidationError, ::I18n.t("einvoicing.connect.ppf.structure_not_found", siret: invoice.buyer.siret) unless id_structure
|
|
20
|
+
|
|
21
|
+
payload = InvoiceAdapter.to_chorus_payload(
|
|
22
|
+
invoice,
|
|
23
|
+
id_structure_cpp: id_structure,
|
|
24
|
+
code_service: code_service,
|
|
25
|
+
numero_engagement: numero_engagement
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
@client.submit_invoice(payload)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "einvoicing"
|
|
4
|
+
require_relative "einvoicing/connect/version"
|
|
5
|
+
require_relative "einvoicing/connect/i18n"
|
|
6
|
+
require_relative "einvoicing/fr/siret_lookup"
|
|
7
|
+
require_relative "einvoicing/ppf/errors"
|
|
8
|
+
require_relative "einvoicing/ppf/client"
|
|
9
|
+
require_relative "einvoicing/ppf/invoice_adapter"
|
|
10
|
+
require_relative "einvoicing/ppf/submitter"
|
|
11
|
+
|
|
12
|
+
module Einvoicing
|
|
13
|
+
module Connect
|
|
14
|
+
# einvoicing-connect adds platform connectivity to the einvoicing gem.
|
|
15
|
+
# Requires: gem "einvoicing-connect" in your Gemfile.
|
|
16
|
+
end
|
|
17
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: einvoicing-connect
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Nathan Le Ray
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-03-14 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: einvoicing
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0.5'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0.5'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rspec
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '3.13'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '3.13'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: webmock
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '3.0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '3.0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: rubocop
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '1.70'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '1.70'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: rubocop-rails-omakase
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - ">="
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '0'
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">="
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '0'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: rubocop-rspec
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - "~>"
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '3.0'
|
|
90
|
+
type: :development
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - "~>"
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '3.0'
|
|
97
|
+
description: Adds French PPF/Chorus Pro invoice submission and SIRET lookup to the
|
|
98
|
+
einvoicing gem.
|
|
99
|
+
email:
|
|
100
|
+
- nathan@sxnlabs.com
|
|
101
|
+
executables: []
|
|
102
|
+
extensions: []
|
|
103
|
+
extra_rdoc_files: []
|
|
104
|
+
files:
|
|
105
|
+
- config/locales/en.yml
|
|
106
|
+
- config/locales/fr.yml
|
|
107
|
+
- lib/einvoicing-connect.rb
|
|
108
|
+
- lib/einvoicing/connect.rb
|
|
109
|
+
- lib/einvoicing/connect/i18n.rb
|
|
110
|
+
- lib/einvoicing/connect/version.rb
|
|
111
|
+
- lib/einvoicing/fr/siret_lookup.rb
|
|
112
|
+
- lib/einvoicing/ppf/client.rb
|
|
113
|
+
- lib/einvoicing/ppf/errors.rb
|
|
114
|
+
- lib/einvoicing/ppf/invoice_adapter.rb
|
|
115
|
+
- lib/einvoicing/ppf/submitter.rb
|
|
116
|
+
homepage: https://www.sxnlabs.com/en/gems/einvoicing/
|
|
117
|
+
licenses:
|
|
118
|
+
- MIT
|
|
119
|
+
metadata:
|
|
120
|
+
homepage_uri: https://www.sxnlabs.com/en/gems/einvoicing/
|
|
121
|
+
source_code_uri: https://github.com/sxnlabs/einvoicing-connect
|
|
122
|
+
post_install_message:
|
|
123
|
+
rdoc_options: []
|
|
124
|
+
require_paths:
|
|
125
|
+
- lib
|
|
126
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
127
|
+
requirements:
|
|
128
|
+
- - ">="
|
|
129
|
+
- !ruby/object:Gem::Version
|
|
130
|
+
version: '3.2'
|
|
131
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
132
|
+
requirements:
|
|
133
|
+
- - ">="
|
|
134
|
+
- !ruby/object:Gem::Version
|
|
135
|
+
version: '0'
|
|
136
|
+
requirements: []
|
|
137
|
+
rubygems_version: 3.5.22
|
|
138
|
+
signing_key:
|
|
139
|
+
specification_version: 4
|
|
140
|
+
summary: Platform connectors for the einvoicing gem — PPF/Chorus Pro, SIRET lookup
|
|
141
|
+
test_files: []
|