facturx 0.2.0 → 1.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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +43 -3
  3. data/DOCUMENTATION.md +271 -0
  4. data/README.md +79 -69
  5. data/lib/facturx/attach.rb +4 -20
  6. data/lib/facturx/builders/association.rb +8 -0
  7. data/lib/facturx/builders/base.rb +129 -0
  8. data/lib/facturx/builders/schema.rb +112 -0
  9. data/lib/facturx/builders.rb +25 -0
  10. data/lib/facturx/error.rb +7 -1
  11. data/lib/facturx/format.rb +85 -0
  12. data/lib/facturx/generate.rb +21 -0
  13. data/lib/facturx/pdf/composer.rb +32 -0
  14. data/lib/facturx/profile.rb +1 -1
  15. data/lib/facturx/profile_resolver.rb +40 -0
  16. data/lib/facturx/profiles.rb +1 -8
  17. data/lib/facturx/reader/semantic_mapper/line_mapping.rb +1 -1
  18. data/lib/facturx/reader/semantic_mapper/line_references.rb +19 -0
  19. data/lib/facturx/reader/semantic_mapper/payment_mapping.rb +48 -11
  20. data/lib/facturx/reader/semantic_mapper.rb +9 -0
  21. data/lib/facturx/reader/term_reader.rb +12 -4
  22. data/lib/facturx/reader.rb +3 -7
  23. data/lib/facturx/source_reader.rb +1 -1
  24. data/lib/facturx/validation.rb +61 -0
  25. data/lib/facturx/version.rb +1 -1
  26. data/lib/facturx/writer/context.rb +53 -0
  27. data/lib/facturx/writer/stage.rb +47 -0
  28. data/lib/facturx/writer/stage_support/contact_details.rb +57 -0
  29. data/lib/facturx/writer/stage_support/emission.rb +78 -0
  30. data/lib/facturx/writer/stage_support/identifiers.rb +129 -0
  31. data/lib/facturx/writer/stage_support/structure.rb +88 -0
  32. data/lib/facturx/writer/stages/document_context.rb +31 -0
  33. data/lib/facturx/writer/stages/exchanged_document.rb +32 -0
  34. data/lib/facturx/writer/stages/trade_agreement/parties.rb +105 -0
  35. data/lib/facturx/writer/stages/trade_agreement/references.rb +92 -0
  36. data/lib/facturx/writer/stages/trade_agreement.rb +45 -0
  37. data/lib/facturx/writer/stages/trade_delivery.rb +77 -0
  38. data/lib/facturx/writer/stages/trade_lines/agreement.rb +92 -0
  39. data/lib/facturx/writer/stages/trade_lines/product.rb +78 -0
  40. data/lib/facturx/writer/stages/trade_lines/quantity.rb +17 -0
  41. data/lib/facturx/writer/stages/trade_lines/settlement.rb +89 -0
  42. data/lib/facturx/writer/stages/trade_lines.rb +67 -0
  43. data/lib/facturx/writer/stages/trade_settlement/parties.rb +33 -0
  44. data/lib/facturx/writer/stages/trade_settlement/payment.rb +97 -0
  45. data/lib/facturx/writer/stages/trade_settlement/summary.rb +88 -0
  46. data/lib/facturx/writer/stages/trade_settlement/taxes.rb +121 -0
  47. data/lib/facturx/writer/stages/trade_settlement.rb +70 -0
  48. data/lib/facturx/writer/tracker.rb +131 -0
  49. data/lib/facturx/writer.rb +76 -0
  50. data/lib/facturx/xml/namespaces.rb +19 -0
  51. data/lib/facturx/xml/parser.rb +1 -1
  52. data/lib/facturx/xml/profile_detector.rb +2 -4
  53. data/lib/facturx/xml/report_builder.rb +44 -0
  54. data/lib/facturx/xml/schema_registry.rb +22 -0
  55. data/lib/facturx/xml/schema_validator.rb +7 -2
  56. data/lib/facturx/xml/validator.rb +32 -0
  57. data/lib/facturx/xml/verifier.rb +16 -11
  58. data/lib/facturx.rb +41 -4
  59. data/rbi/facturx.rbi +1755 -0
  60. metadata +44 -4
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../validation'
4
+
5
+ module Facturx
6
+ module Xml
7
+ class ReportBuilder
8
+ class << self
9
+ def syntax(error)
10
+ from_error(error, layer: :syntax, code: :invalid_xml)
11
+ end
12
+
13
+ def profile(error)
14
+ guideline_urn = error.details[:guideline_urn]
15
+ code = guideline_urn.nil? || guideline_urn.empty? ? :missing_profile : :unknown_profile
16
+ from_error(error, layer: :profile, code:)
17
+ end
18
+
19
+ def xsd(profile, error)
20
+ from_error(error, profile:, layer: :xsd, code: :xsd_violation)
21
+ end
22
+
23
+ private
24
+
25
+ def from_error(error, layer:, code:, profile: nil)
26
+ diagnostics = error.details[:errors] || [{ message: error.message }]
27
+ issues = diagnostics.map { |diagnostic| issue(error, diagnostic, layer:, code:) }
28
+ Validation::Report.new(profile:, issues:)
29
+ end
30
+
31
+ def issue(error, diagnostic, layer:, code:)
32
+ Validation::Issue.new(
33
+ code:,
34
+ message: diagnostic.fetch(:message),
35
+ layer:,
36
+ line: diagnostic[:line],
37
+ column: diagnostic[:column],
38
+ details: error.details.except(:errors).merge(diagnostic.except(:message, :line, :column))
39
+ )
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Facturx
4
+ module Xml
5
+ class SchemaRegistry
6
+ SPECIFICATION_VERSION = '1.09.2'
7
+ ROOT = File.expand_path("../schema/#{SPECIFICATION_VERSION}", __dir__).freeze
8
+ PATHS = {
9
+ minimum: 'minimum/Factur-X_1.09.2_MINIMUM.xsd',
10
+ basic_wl: 'basic-wl/Factur-X_1.09.2_BASICWL.xsd',
11
+ basic: 'basic/Factur-X_1.09.2_BASIC.xsd',
12
+ en16931: 'en16931/Factur-X_1.09.2_EN16931.xsd',
13
+ extended: 'extended/Factur-X_1.09.2_EXTENDED.xsd'
14
+ }.transform_values { |path| File.join(ROOT, path).freeze }.freeze
15
+ private_constant :SPECIFICATION_VERSION, :ROOT, :PATHS
16
+
17
+ def fetch(profile)
18
+ PATHS.fetch(profile.id)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'nokogiri'
4
4
  require_relative '../error'
5
+ require_relative 'schema_registry'
5
6
 
6
7
  module Facturx
7
8
  module Xml
@@ -27,6 +28,10 @@ module Facturx
27
28
  end
28
29
  end
29
30
 
31
+ def initialize(registry: SchemaRegistry.new)
32
+ @registry = registry
33
+ end
34
+
30
35
  def call(document:, profile:)
31
36
  validation_errors = load_schema(profile).validate(document)
32
37
  raise_validation_error(profile, validation_errors) unless validation_errors.empty?
@@ -35,7 +40,7 @@ module Facturx
35
40
  private
36
41
 
37
42
  def load_schema(profile)
38
- self.class.send(:load_schema, File.expand_path(profile.xsd_path))
43
+ self.class.send(:load_schema, @registry.fetch(profile))
39
44
  rescue SystemCallError, Nokogiri::XML::SyntaxError => e
40
45
  raise_schema_load_error(profile, e)
41
46
  end
@@ -53,7 +58,7 @@ module Facturx
53
58
  end
54
59
 
55
60
  def raise_schema_load_error(profile, error)
56
- raise XsdValidationError.new(
61
+ raise SchemaLoadError.new(
57
62
  "Unable to load the Factur-X #{profile.id} schema",
58
63
  profile: profile.id,
59
64
  errors: [{ message: error.message }.freeze].freeze
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../validation'
4
+ require_relative 'parser'
5
+ require_relative 'profile_detector'
6
+ require_relative 'report_builder'
7
+ require_relative 'schema_validator'
8
+
9
+ module Facturx
10
+ module Xml
11
+ class Validator
12
+ def initialize(parser: Parser.new, profile_detector: ProfileDetector.new, schema_validator: SchemaValidator.new)
13
+ @parser = parser
14
+ @profile_detector = profile_detector
15
+ @schema_validator = schema_validator
16
+ end
17
+
18
+ def call(xml:)
19
+ document = @parser.call(xml:)
20
+ profile = @profile_detector.call(document:)
21
+ @schema_validator.call(document:, profile:)
22
+ Validation::Report.new(profile:)
23
+ rescue UnknownProfileError => e
24
+ ReportBuilder.profile(e)
25
+ rescue XsdValidationError => e
26
+ ReportBuilder.xsd(profile, e)
27
+ rescue InvalidXmlError => e
28
+ ReportBuilder.syntax(e)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -1,23 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'parser'
4
- require_relative 'profile_detector'
5
- require_relative 'schema_validator'
3
+ require_relative 'validator'
6
4
 
7
5
  module Facturx
8
6
  module Xml
9
7
  class Verifier
10
- def initialize(parser: Parser.new, profile_detector: ProfileDetector.new, schema_validator: SchemaValidator.new)
11
- @parser = parser
12
- @profile_detector = profile_detector
13
- @schema_validator = schema_validator
8
+ ERROR_CLASSES = {
9
+ syntax: InvalidXmlError,
10
+ profile: UnknownProfileError,
11
+ xsd: XsdValidationError
12
+ }.freeze
13
+ private_constant :ERROR_CLASSES
14
+
15
+ def initialize(validator: Validator.new)
16
+ @validator = validator
14
17
  end
15
18
 
16
19
  def call(xml:)
17
- document = @parser.call(xml: xml)
18
- profile = @profile_detector.call(document: document)
19
- @schema_validator.call(document: document, profile: profile)
20
- profile
20
+ report = @validator.call(xml:)
21
+ return report.profile if report.valid?
22
+
23
+ issue = report.issues.first
24
+ error_class = ERROR_CLASSES.fetch(issue.layer)
25
+ raise error_class.new(issue.message, report:, issues: report.issues)
21
26
  end
22
27
  end
23
28
  end
data/lib/facturx.rb CHANGED
@@ -5,32 +5,51 @@ require 'facturx/error'
5
5
  require 'facturx/embedding'
6
6
  require 'facturx/profile'
7
7
  require 'facturx/profiles'
8
+ require 'facturx/profile_resolver'
8
9
  require 'facturx/term'
9
10
  require 'facturx/group'
10
11
  require 'facturx/terms'
11
12
  require 'facturx/model'
13
+ require 'facturx/document'
14
+ require 'facturx/builders'
12
15
  require 'facturx/diagnostic'
13
16
  require 'facturx/reading'
14
17
  require 'facturx/coerce'
18
+ require 'facturx/format'
19
+ require 'facturx/validation'
20
+ require 'facturx/xml/validator'
15
21
  require 'facturx/xml/verifier'
16
22
  require 'facturx/pdf/document'
17
23
  require 'facturx/pdf/inspector'
18
24
  require 'facturx/pdf/extractor'
19
25
  require 'facturx/pdf/verifier'
26
+ require 'facturx/pdf/composer'
20
27
  require 'facturx/composers/ghostscript'
21
28
  require 'facturx/attach'
22
29
  require 'facturx/reader'
30
+ require 'facturx/writer'
31
+ require 'facturx/generate'
23
32
 
24
33
  module Facturx
25
- DEFAULT_ATTACHER = Attach.new
34
+ DEFAULT_XML_VALIDATOR = Xml::Validator.new
35
+ DEFAULT_PDF_COMPOSER = Pdf::Composer.new
36
+ DEFAULT_ATTACHER = Attach.new(pdf_composer: DEFAULT_PDF_COMPOSER)
26
37
  DEFAULT_READER = Reader.new
38
+ DEFAULT_PROFILE_RESOLVER = ProfileResolver.new
39
+ DEFAULT_WRITER = Writer.new
40
+ DEFAULT_GENERATOR = Generate.new(writer: DEFAULT_WRITER, pdf_composer: DEFAULT_PDF_COMPOSER,
41
+ profile_resolver: DEFAULT_PROFILE_RESOLVER)
42
+ private_constant :DEFAULT_XML_VALIDATOR
43
+ private_constant :DEFAULT_PDF_COMPOSER
27
44
  private_constant :DEFAULT_ATTACHER
28
45
  private_constant :DEFAULT_READER
46
+ private_constant :DEFAULT_PROFILE_RESOLVER
47
+ private_constant :DEFAULT_WRITER
48
+ private_constant :DEFAULT_GENERATOR
29
49
 
30
50
  class << self
31
- def verify_xml(xml:)
32
- Xml::Verifier.new.call(xml:)
33
- true
51
+ def validate_xml(xml:)
52
+ DEFAULT_XML_VALIDATOR.call(xml:)
34
53
  end
35
54
 
36
55
  def attach(pdf:, xml:)
@@ -44,5 +63,23 @@ module Facturx
44
63
  def read(source, on_unknown_profile: :fallback)
45
64
  DEFAULT_READER.call(source, on_unknown_profile:)
46
65
  end
66
+
67
+ def validate_document(document:, profile:)
68
+ canonical_profile = DEFAULT_PROFILE_RESOLVER.call(profile)
69
+ DEFAULT_WRITER.validate(document:, profile: canonical_profile)
70
+ end
71
+
72
+ def build_xml(document:, profile:)
73
+ canonical_profile = DEFAULT_PROFILE_RESOLVER.call(profile)
74
+ DEFAULT_WRITER.call(document:, profile: canonical_profile)
75
+ end
76
+
77
+ def generate(pdf:, document:, profile:)
78
+ DEFAULT_GENERATOR.call(pdf:, document:, profile:)
79
+ end
47
80
  end
81
+
82
+ private_constant :Attach, :Builders, :Coerce, :CoercionError, :Composers, :Format, :FormattingError,
83
+ :Generate, :Group, :Model, :Pdf, :ProfileResolver, :Reader, :SourceReader, :Term,
84
+ :TermDeclarations, :Terms, :Writer, :Xml
48
85
  end