einvoicing 0.3.0 → 0.4.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 +4 -4
- data/CHANGELOG.md +13 -0
- data/lib/einvoicing/errors.rb +11 -0
- data/lib/einvoicing/fr/siret_lookup.rb +54 -0
- data/lib/einvoicing/fr.rb +14 -0
- data/lib/einvoicing/party.rb +13 -0
- data/lib/einvoicing/siret_lookup.rb +62 -0
- data/lib/einvoicing/validators/peppol.rb +137 -0
- data/lib/einvoicing/version.rb +1 -1
- data/lib/einvoicing.rb +53 -0
- metadata +6 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e57cfd36d8de0852320a7b72d1fe1ced81ff64eb024199b31733be06859ee509
|
|
4
|
+
data.tar.gz: 03a602eb77133a12a8493939b8292749f91fd9c2c834c4777108b8f4d0793d9d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fca0526e61db40a9125f9a5060c75db8f5bcefa69b158b55ce58d2c824ccbc2868594aa77946308919b8aea7922f364f3d0d9890fe63b77f21ecac673619ecd1
|
|
7
|
+
data.tar.gz: c57a78e842ca6beac1f0b98dfa52659e436eb2b03929c7938bdf9e615b009d28a24018c55ed28023853591054657e124099330c4c99d8f078b4b4885fe6cbc1c
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.4.0] - 2026-03-13
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- `Einvoicing.xml(invoice, format: :cii | :ubl)` — top-level XML generation
|
|
12
|
+
- `Einvoicing.embed(pdf, invoice_or_xml)` — top-level Factur-X embedding
|
|
13
|
+
- `Einvoicing.validate(invoice, market: :fr)` — top-level validation
|
|
14
|
+
- `Einvoicing.process(invoice, format:, market:, pdf:)` — full pipeline, never raises
|
|
15
|
+
- `Einvoicing::FR::SiretLookup.find(siren)` — SIRET lookup via French government API (no auth, stdlib only)
|
|
16
|
+
- `Einvoicing::FR::SiretLookup.enrich!(party)` — auto-fills SIRET on a Party from its SIREN
|
|
17
|
+
- `Einvoicing::Validators::Peppol.validate_ubl(xml)` — Peppol BIS 3.0 Schematron validation (requires Java + Saxon-HE 12)
|
|
18
|
+
- `Einvoicing::Errors::JavaNotFound`, `Einvoicing::Errors::ValidationError`
|
|
19
|
+
- `lib/einvoicing/fr.rb` — FR submodule entrypoint
|
|
20
|
+
|
|
8
21
|
## [0.3.0] - 2026-03-13
|
|
9
22
|
|
|
10
23
|
### Added
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Einvoicing
|
|
4
|
+
module Errors
|
|
5
|
+
# Raised when Java is not found in PATH and is required for validation.
|
|
6
|
+
JavaNotFound = Class.new(StandardError)
|
|
7
|
+
|
|
8
|
+
# Raised when an external validator (e.g. Saxon) fails unexpectedly.
|
|
9
|
+
ValidationError = Class.new(StandardError)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -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"
|
|
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,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "fr/siret_lookup"
|
|
4
|
+
|
|
5
|
+
module Einvoicing
|
|
6
|
+
module FR
|
|
7
|
+
# France-specific features for the einvoicing gem.
|
|
8
|
+
# Market: France (FR) — Factur-X, PPF/PDP mandate (Sept 2026)
|
|
9
|
+
#
|
|
10
|
+
# Usage:
|
|
11
|
+
# Einvoicing::FR::SiretLookup.find("898208145")
|
|
12
|
+
# Einvoicing::FR::SiretLookup.enrich!(party)
|
|
13
|
+
end
|
|
14
|
+
end
|
data/lib/einvoicing/party.rb
CHANGED
|
@@ -25,5 +25,18 @@ module Einvoicing
|
|
|
25
25
|
def siren_number
|
|
26
26
|
siren || (siret && siret[0, 9])
|
|
27
27
|
end
|
|
28
|
+
|
|
29
|
+
# Look up SIRET via the Sirene API and return a new Party with siret filled in.
|
|
30
|
+
# No-op (returns self) if siren is blank or siret is already set.
|
|
31
|
+
#
|
|
32
|
+
# @return [Party] self or new Party with siret populated
|
|
33
|
+
def fetch_siret!
|
|
34
|
+
return self unless siren_number && siret.nil?
|
|
35
|
+
|
|
36
|
+
result = SiretLookup.find(siren_number)
|
|
37
|
+
return self unless result
|
|
38
|
+
|
|
39
|
+
with(siret: result[:siret])
|
|
40
|
+
end
|
|
28
41
|
end
|
|
29
42
|
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "uri"
|
|
5
|
+
require "json"
|
|
6
|
+
|
|
7
|
+
module Einvoicing
|
|
8
|
+
# Looks up SIRET and company name from a SIREN number using the free French
|
|
9
|
+
# government API (no authentication required).
|
|
10
|
+
module SiretLookup
|
|
11
|
+
API_URL = "https://recherche-entreprises.api.gouv.fr/search"
|
|
12
|
+
SIREN_RE = /\A\d{9}\z/
|
|
13
|
+
|
|
14
|
+
# Find company info for a given SIREN number.
|
|
15
|
+
#
|
|
16
|
+
# @param siren [String, nil] 9-digit SIREN number
|
|
17
|
+
# @return [Hash, nil] { siret:, name:, address: } or nil on any error
|
|
18
|
+
def self.find(siren)
|
|
19
|
+
return nil unless siren.to_s.match?(SIREN_RE)
|
|
20
|
+
|
|
21
|
+
uri = URI(API_URL)
|
|
22
|
+
uri.query = URI.encode_www_form(q: siren, mtq: "true")
|
|
23
|
+
|
|
24
|
+
response = fetch(uri)
|
|
25
|
+
return nil if response.nil?
|
|
26
|
+
|
|
27
|
+
parse(response)
|
|
28
|
+
rescue StandardError
|
|
29
|
+
nil
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.fetch(uri)
|
|
33
|
+
Net::HTTP.start(uri.host, uri.port,
|
|
34
|
+
use_ssl: uri.scheme == "https",
|
|
35
|
+
open_timeout: 5,
|
|
36
|
+
read_timeout: 10) do |http|
|
|
37
|
+
res = http.get("#{uri.path}?#{uri.query}")
|
|
38
|
+
return nil unless res.is_a?(Net::HTTPSuccess)
|
|
39
|
+
|
|
40
|
+
res.body
|
|
41
|
+
end
|
|
42
|
+
rescue StandardError
|
|
43
|
+
nil
|
|
44
|
+
end
|
|
45
|
+
private_class_method :fetch
|
|
46
|
+
|
|
47
|
+
def self.parse(body)
|
|
48
|
+
data = JSON.parse(body)
|
|
49
|
+
result = Array(data["results"]).first
|
|
50
|
+
return nil unless result
|
|
51
|
+
|
|
52
|
+
siege = result["siege"] || {}
|
|
53
|
+
siret = siege["siret"]
|
|
54
|
+
return nil if siret.nil? || siret.empty?
|
|
55
|
+
|
|
56
|
+
{ siret: siret, name: result["nom_complet"], address: siege["adresse"] }
|
|
57
|
+
rescue JSON::ParserError
|
|
58
|
+
nil
|
|
59
|
+
end
|
|
60
|
+
private_class_method :parse
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "open3"
|
|
4
|
+
require "tempfile"
|
|
5
|
+
require "net/http"
|
|
6
|
+
require "uri"
|
|
7
|
+
|
|
8
|
+
module Einvoicing
|
|
9
|
+
module Validators
|
|
10
|
+
# Validates UBL 2.1 invoices against Peppol BIS Billing 3.0 Schematron rules
|
|
11
|
+
# using Saxon-HE via the system Java runtime.
|
|
12
|
+
#
|
|
13
|
+
# Requirements:
|
|
14
|
+
# - Java in PATH
|
|
15
|
+
# - Saxon-HE 12 CLI jar (auto-downloaded to /tmp/saxon-he.jar on first use)
|
|
16
|
+
# - Peppol XSLT (bundled at lib/einvoicing/data/; auto-downloaded on first use)
|
|
17
|
+
#
|
|
18
|
+
# @example
|
|
19
|
+
# errors = Einvoicing::Validators::Peppol.validate_ubl(xml_string)
|
|
20
|
+
# errors #=> [] when valid, or [{ field:, error:, message: }] when invalid
|
|
21
|
+
module Peppol
|
|
22
|
+
XSLT_URL = "https://github.com/OpenPEPPOL/peppol-bis-invoice-3/releases/download/3.0.21/PEPPOL-EN16931-UBL.xslt"
|
|
23
|
+
SAXON_URL = "https://repo1.maven.org/maven2/net/sf/saxon/Saxon-HE/12.5/Saxon-HE-12.5.jar"
|
|
24
|
+
SAXON_JAR = "/tmp/saxon-he.jar"
|
|
25
|
+
XSLT_PATH = File.expand_path("../data/PEPPOL-EN16931-UBL.xslt", __dir__)
|
|
26
|
+
|
|
27
|
+
# Validate a UBL 2.1 XML string against Peppol BIS 3.0 rules.
|
|
28
|
+
#
|
|
29
|
+
# @param xml_string [String] UBL 2.1 invoice XML
|
|
30
|
+
# @return [Array<Hash>] errors — empty array means valid
|
|
31
|
+
# @raise [Einvoicing::Errors::JavaNotFound] if java is not in PATH
|
|
32
|
+
# @raise [Einvoicing::Errors::ValidationError] if Saxon fails unexpectedly
|
|
33
|
+
def self.validate_ubl(xml_string)
|
|
34
|
+
ensure_java!
|
|
35
|
+
ensure_saxon!
|
|
36
|
+
ensure_xslt!
|
|
37
|
+
|
|
38
|
+
svrl = run_saxon(xml_string)
|
|
39
|
+
parse_svrl(svrl)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.java_available?
|
|
43
|
+
_, _, status = Open3.capture3("java -version")
|
|
44
|
+
status.success?
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def self.ensure_java!
|
|
48
|
+
raise Einvoicing::Errors::JavaNotFound, "java not found in PATH" unless java_available?
|
|
49
|
+
end
|
|
50
|
+
private_class_method :ensure_java!
|
|
51
|
+
|
|
52
|
+
def self.ensure_saxon!
|
|
53
|
+
return if File.exist?(SAXON_JAR)
|
|
54
|
+
|
|
55
|
+
download(SAXON_URL, SAXON_JAR)
|
|
56
|
+
return if File.exist?(SAXON_JAR)
|
|
57
|
+
|
|
58
|
+
raise Einvoicing::Errors::ValidationError,
|
|
59
|
+
"Saxon JAR not available. Download from #{SAXON_URL} and place at #{SAXON_JAR}"
|
|
60
|
+
end
|
|
61
|
+
private_class_method :ensure_saxon!
|
|
62
|
+
|
|
63
|
+
def self.ensure_xslt!
|
|
64
|
+
return if File.exist?(XSLT_PATH) && File.size(XSLT_PATH) > 100
|
|
65
|
+
|
|
66
|
+
download(XSLT_URL, XSLT_PATH)
|
|
67
|
+
return if File.exist?(XSLT_PATH) && File.size(XSLT_PATH) > 100
|
|
68
|
+
|
|
69
|
+
raise Einvoicing::Errors::ValidationError,
|
|
70
|
+
"Peppol XSLT not available at #{XSLT_PATH}. Download from #{XSLT_URL}"
|
|
71
|
+
end
|
|
72
|
+
private_class_method :ensure_xslt!
|
|
73
|
+
|
|
74
|
+
def self.download(url, dest)
|
|
75
|
+
uri = URI(url)
|
|
76
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https",
|
|
77
|
+
open_timeout: 30, read_timeout: 120) do |http|
|
|
78
|
+
res = http.get(uri.request_uri)
|
|
79
|
+
return unless res.is_a?(Net::HTTPSuccess)
|
|
80
|
+
|
|
81
|
+
File.binwrite(dest, res.body)
|
|
82
|
+
end
|
|
83
|
+
rescue StandardError
|
|
84
|
+
nil # caller checks if file exists
|
|
85
|
+
end
|
|
86
|
+
private_class_method :download
|
|
87
|
+
|
|
88
|
+
def self.run_saxon(xml_string)
|
|
89
|
+
input = Tempfile.new(["peppol-input", ".xml"])
|
|
90
|
+
output = Tempfile.new(["peppol-output", ".svrl"])
|
|
91
|
+
|
|
92
|
+
begin
|
|
93
|
+
input.write(xml_string)
|
|
94
|
+
input.close
|
|
95
|
+
output.close
|
|
96
|
+
|
|
97
|
+
cmd = ["java", "-jar", SAXON_JAR,
|
|
98
|
+
"-s:#{input.path}",
|
|
99
|
+
"-xsl:#{XSLT_PATH}",
|
|
100
|
+
"-o:#{output.path}"]
|
|
101
|
+
|
|
102
|
+
_, stderr, status = Open3.capture3(*cmd)
|
|
103
|
+
|
|
104
|
+
unless status.success?
|
|
105
|
+
raise Einvoicing::Errors::ValidationError,
|
|
106
|
+
"Saxon failed (exit #{status.exitstatus}): #{stderr.strip}"
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
File.read(output.path)
|
|
110
|
+
ensure
|
|
111
|
+
input.unlink
|
|
112
|
+
output.unlink
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
private_class_method :run_saxon
|
|
116
|
+
|
|
117
|
+
def self.parse_svrl(svrl_xml)
|
|
118
|
+
require "rexml/document"
|
|
119
|
+
|
|
120
|
+
doc = REXML::Document.new(svrl_xml)
|
|
121
|
+
errors = []
|
|
122
|
+
|
|
123
|
+
REXML::XPath.each(doc, "//svrl:failed-assert",
|
|
124
|
+
"svrl" => "http://purl.oclc.org/dsdl/svrl") do |node|
|
|
125
|
+
errors << {
|
|
126
|
+
field: node.attributes["id"] || node.attributes["location"] || "",
|
|
127
|
+
error: node.attributes["test"] || "",
|
|
128
|
+
message: node.text.to_s.strip
|
|
129
|
+
}
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
errors
|
|
133
|
+
end
|
|
134
|
+
private_class_method :parse_svrl
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
data/lib/einvoicing/version.rb
CHANGED
data/lib/einvoicing.rb
CHANGED
|
@@ -5,8 +5,11 @@ require "bigdecimal"
|
|
|
5
5
|
require "bigdecimal/util"
|
|
6
6
|
|
|
7
7
|
require_relative "einvoicing/version"
|
|
8
|
+
require_relative "einvoicing/errors"
|
|
8
9
|
require_relative "einvoicing/tax"
|
|
9
10
|
require_relative "einvoicing/party"
|
|
11
|
+
require_relative "einvoicing/siret_lookup"
|
|
12
|
+
require_relative "einvoicing/fr"
|
|
10
13
|
require_relative "einvoicing/line_item"
|
|
11
14
|
require_relative "einvoicing/invoice"
|
|
12
15
|
require_relative "einvoicing/xml_builder"
|
|
@@ -16,6 +19,7 @@ require_relative "einvoicing/formats/facturx"
|
|
|
16
19
|
require_relative "einvoicing/i18n"
|
|
17
20
|
require_relative "einvoicing/validators/base"
|
|
18
21
|
require_relative "einvoicing/validators/fr"
|
|
22
|
+
require_relative "einvoicing/validators/peppol"
|
|
19
23
|
require_relative "einvoicing/invoiceable"
|
|
20
24
|
require_relative "einvoicing/rails/concern"
|
|
21
25
|
require_relative "einvoicing/ppf"
|
|
@@ -47,4 +51,53 @@ end
|
|
|
47
51
|
# xml = Einvoicing::Formats::CII.generate(invoice)
|
|
48
52
|
# ubl = Einvoicing::Formats::UBL.generate(invoice)
|
|
49
53
|
module Einvoicing
|
|
54
|
+
# ─── Top-level convenience API ────────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
# Generate XML from an invoice.
|
|
57
|
+
# @param invoice [Einvoicing::Invoice]
|
|
58
|
+
# @param format [Symbol] :cii (default, Factur-X) or :ubl (Peppol BIS 3.0)
|
|
59
|
+
# @return [String] XML document
|
|
60
|
+
def self.xml(invoice, format: :cii)
|
|
61
|
+
case format
|
|
62
|
+
when :cii then Formats::CII.generate(invoice)
|
|
63
|
+
when :ubl then Formats::UBL.generate(invoice)
|
|
64
|
+
else raise ArgumentError, "Unknown format: #{format.inspect}. Use :cii or :ubl"
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Embed a Factur-X CII XML into a PDF, returning a PDF/A-3 binary.
|
|
69
|
+
# @param pdf_data [String] raw PDF binary
|
|
70
|
+
# @param invoice_or_xml [Invoice, String] Invoice (CII generated internally) or raw XML string
|
|
71
|
+
# @return [String] Factur-X PDF/A-3 binary
|
|
72
|
+
def self.embed(pdf_data, invoice_or_xml)
|
|
73
|
+
xml_str = invoice_or_xml.is_a?(String) ? invoice_or_xml : xml(invoice_or_xml, format: :cii)
|
|
74
|
+
Formats::FacturX.embed(pdf_data, xml_str)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Validate an invoice against a market's rules.
|
|
78
|
+
# @param invoice [Einvoicing::Invoice]
|
|
79
|
+
# @param market [Symbol] :fr (default)
|
|
80
|
+
# @return [Array<Hash>] array of { field:, error:, message: } — empty means valid
|
|
81
|
+
def self.validate(invoice, market: :fr)
|
|
82
|
+
case market
|
|
83
|
+
when :fr then Validators::FR.validate(invoice)
|
|
84
|
+
else raise ArgumentError, "Unknown market: #{market.inspect}. Use :fr"
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Full pipeline: validate → generate XML → optionally embed in PDF.
|
|
89
|
+
# Never raises — errors are returned in the result hash.
|
|
90
|
+
# @param invoice [Einvoicing::Invoice]
|
|
91
|
+
# @param format [Symbol] :cii or :ubl
|
|
92
|
+
# @param market [Symbol] :fr
|
|
93
|
+
# @param pdf [String, nil] optional raw PDF binary to embed into
|
|
94
|
+
# @return [Hash] { valid:, errors:, xml:, pdf: }
|
|
95
|
+
def self.process(invoice, format: :cii, market: :fr, pdf: nil)
|
|
96
|
+
errors = validate(invoice, market: market)
|
|
97
|
+
xml_str = xml(invoice, format: format)
|
|
98
|
+
pdf_out = pdf ? embed(pdf, xml_str) : nil
|
|
99
|
+
{ valid: errors.empty?, errors: errors, xml: xml_str, pdf: pdf_out }
|
|
100
|
+
rescue StandardError => e
|
|
101
|
+
{ valid: false, errors: [{ field: :unknown, error: :exception, message: e.message }], xml: nil, pdf: nil }
|
|
102
|
+
end
|
|
50
103
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: einvoicing
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Nathan Le Ray
|
|
@@ -124,9 +124,12 @@ files:
|
|
|
124
124
|
- config/locales/einvoicing.fr.yml
|
|
125
125
|
- lib/einvoicing.rb
|
|
126
126
|
- lib/einvoicing/data/srgb.icc
|
|
127
|
+
- lib/einvoicing/errors.rb
|
|
127
128
|
- lib/einvoicing/formats/cii.rb
|
|
128
129
|
- lib/einvoicing/formats/facturx.rb
|
|
129
130
|
- lib/einvoicing/formats/ubl.rb
|
|
131
|
+
- lib/einvoicing/fr.rb
|
|
132
|
+
- lib/einvoicing/fr/siret_lookup.rb
|
|
130
133
|
- lib/einvoicing/i18n.rb
|
|
131
134
|
- lib/einvoicing/invoice.rb
|
|
132
135
|
- lib/einvoicing/invoiceable.rb
|
|
@@ -139,9 +142,11 @@ files:
|
|
|
139
142
|
- lib/einvoicing/ppf/submitter.rb
|
|
140
143
|
- lib/einvoicing/rails/concern.rb
|
|
141
144
|
- lib/einvoicing/rails/engine.rb
|
|
145
|
+
- lib/einvoicing/siret_lookup.rb
|
|
142
146
|
- lib/einvoicing/tax.rb
|
|
143
147
|
- lib/einvoicing/validators/base.rb
|
|
144
148
|
- lib/einvoicing/validators/fr.rb
|
|
149
|
+
- lib/einvoicing/validators/peppol.rb
|
|
145
150
|
- lib/einvoicing/version.rb
|
|
146
151
|
- lib/einvoicing/xml_builder.rb
|
|
147
152
|
homepage: https://github.com/sxnlabs/einvoicing
|