facturx 0.1.0 → 1.0.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 (85) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +29 -2
  3. data/NOTICE.md +2 -0
  4. data/README.md +62 -4
  5. data/lib/facturx/builders/association.rb +8 -0
  6. data/lib/facturx/builders/base.rb +129 -0
  7. data/lib/facturx/builders/schema.rb +112 -0
  8. data/lib/facturx/builders.rb +25 -0
  9. data/lib/facturx/coerce.rb +74 -0
  10. data/lib/facturx/conformance.rb +163 -0
  11. data/lib/facturx/diagnostic.rb +20 -0
  12. data/lib/facturx/document.rb +45 -0
  13. data/lib/facturx/error.rb +3 -0
  14. data/lib/facturx/format.rb +85 -0
  15. data/lib/facturx/generate.rb +21 -0
  16. data/lib/facturx/group.rb +5 -0
  17. data/lib/facturx/model/common.rb +8 -0
  18. data/lib/facturx/model/immutable.rb +75 -0
  19. data/lib/facturx/model/line.rb +14 -0
  20. data/lib/facturx/model/party.rb +10 -0
  21. data/lib/facturx/model/payment.rb +9 -0
  22. data/lib/facturx/model/reference.rb +7 -0
  23. data/lib/facturx/model/trade.rb +9 -0
  24. data/lib/facturx/model.rb +9 -0
  25. data/lib/facturx/pdf/extractor.rb +7 -4
  26. data/lib/facturx/profile_resolver.rb +40 -0
  27. data/lib/facturx/reader/semantic_mapper/delivery_mapping.rb +43 -0
  28. data/lib/facturx/reader/semantic_mapper/document_mapping.rb +109 -0
  29. data/lib/facturx/reader/semantic_mapper/helpers.rb +114 -0
  30. data/lib/facturx/reader/semantic_mapper/line_mapping.rb +120 -0
  31. data/lib/facturx/reader/semantic_mapper/line_references.rb +19 -0
  32. data/lib/facturx/reader/semantic_mapper/party_mapping.rb +90 -0
  33. data/lib/facturx/reader/semantic_mapper/payment_mapping.rb +108 -0
  34. data/lib/facturx/reader/semantic_mapper/trade_mapping.rb +83 -0
  35. data/lib/facturx/reader/semantic_mapper.rb +76 -0
  36. data/lib/facturx/reader/term_reader/coercion.rb +69 -0
  37. data/lib/facturx/reader/term_reader/unmapped.rb +37 -0
  38. data/lib/facturx/reader/term_reader.rb +132 -0
  39. data/lib/facturx/reader.rb +97 -0
  40. data/lib/facturx/reading.rb +23 -0
  41. data/lib/facturx/source_reader.rb +30 -0
  42. data/lib/facturx/term.rb +5 -0
  43. data/lib/facturx/terms/declaration.rb +20 -0
  44. data/lib/facturx/terms/declarations/adjustments.rb +230 -0
  45. data/lib/facturx/terms/declarations/delivery.rb +83 -0
  46. data/lib/facturx/terms/declarations/document.rb +223 -0
  47. data/lib/facturx/terms/declarations/groups.rb +356 -0
  48. data/lib/facturx/terms/declarations/lines.rb +166 -0
  49. data/lib/facturx/terms/declarations/parties.rb +609 -0
  50. data/lib/facturx/terms/declarations/payment.rb +106 -0
  51. data/lib/facturx/terms/declarations/products.rb +103 -0
  52. data/lib/facturx/terms/declarations/references.rb +72 -0
  53. data/lib/facturx/terms/declarations/taxes.rb +92 -0
  54. data/lib/facturx/terms/declarations/totals.rb +107 -0
  55. data/lib/facturx/terms/reference.rb +21 -0
  56. data/lib/facturx/terms.rb +109 -0
  57. data/lib/facturx/version.rb +1 -1
  58. data/lib/facturx/writer/context.rb +53 -0
  59. data/lib/facturx/writer/stage.rb +47 -0
  60. data/lib/facturx/writer/stage_support/contact_details.rb +57 -0
  61. data/lib/facturx/writer/stage_support/emission.rb +78 -0
  62. data/lib/facturx/writer/stage_support/identifiers.rb +129 -0
  63. data/lib/facturx/writer/stage_support/structure.rb +88 -0
  64. data/lib/facturx/writer/stages/document_context.rb +31 -0
  65. data/lib/facturx/writer/stages/exchanged_document.rb +32 -0
  66. data/lib/facturx/writer/stages/trade_agreement/parties.rb +105 -0
  67. data/lib/facturx/writer/stages/trade_agreement/references.rb +92 -0
  68. data/lib/facturx/writer/stages/trade_agreement.rb +45 -0
  69. data/lib/facturx/writer/stages/trade_delivery.rb +77 -0
  70. data/lib/facturx/writer/stages/trade_lines/agreement.rb +92 -0
  71. data/lib/facturx/writer/stages/trade_lines/product.rb +78 -0
  72. data/lib/facturx/writer/stages/trade_lines/quantity.rb +17 -0
  73. data/lib/facturx/writer/stages/trade_lines/settlement.rb +89 -0
  74. data/lib/facturx/writer/stages/trade_lines.rb +67 -0
  75. data/lib/facturx/writer/stages/trade_settlement/parties.rb +33 -0
  76. data/lib/facturx/writer/stages/trade_settlement/payment.rb +97 -0
  77. data/lib/facturx/writer/stages/trade_settlement/summary.rb +88 -0
  78. data/lib/facturx/writer/stages/trade_settlement/taxes.rb +121 -0
  79. data/lib/facturx/writer/stages/trade_settlement.rb +70 -0
  80. data/lib/facturx/writer.rb +68 -0
  81. data/lib/facturx/xml/namespaces.rb +19 -0
  82. data/lib/facturx/xml/profile_detector.rb +2 -4
  83. data/lib/facturx.rb +42 -0
  84. data/rbi/facturx.rbi +1090 -0
  85. metadata +79 -4
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../error'
4
+ require_relative '../format'
5
+ require_relative '../terms'
6
+ require_relative 'stage_support/contact_details'
7
+ require_relative 'stage_support/emission'
8
+ require_relative 'stage_support/identifiers'
9
+ require_relative 'stage_support/structure'
10
+
11
+ module Facturx
12
+ class Writer
13
+ class Stage
14
+ include StageSupport::Emission
15
+ include StageSupport::Structure
16
+ include StageSupport::ContactDetails
17
+ include StageSupport::Identifiers
18
+
19
+ class << self
20
+ def term_ids(*ids)
21
+ @term_ids = ids.map(&:to_s).freeze unless ids.empty?
22
+ @term_ids || [].freeze
23
+ end
24
+ end
25
+
26
+ def initialize(context)
27
+ @context = context
28
+ end
29
+
30
+ private
31
+
32
+ attr_reader :context
33
+
34
+ def document
35
+ context.document
36
+ end
37
+
38
+ def profile
39
+ context.profile
40
+ end
41
+
42
+ def tracker
43
+ context.tracker
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Facturx
4
+ class Writer
5
+ module StageSupport
6
+ module ContactDetails
7
+ ADDRESS_ELEMENTS = {
8
+ postcode: 'ram:PostcodeCode',
9
+ line_one: 'ram:LineOne',
10
+ line_two: 'ram:LineTwo',
11
+ line_three: 'ram:LineThree',
12
+ city: 'ram:CityName',
13
+ country_code: 'ram:CountryID',
14
+ country_subdivision: 'ram:CountrySubDivisionName'
15
+ }.freeze
16
+
17
+ private
18
+
19
+ def emit_date(parent, id, value, wrapper:, value_element: 'udt:DateTimeString')
20
+ unless value
21
+ emit(id, nil, element: value_element, parent:)
22
+ return
23
+ end
24
+
25
+ container(parent, wrapper) do |node|
26
+ emit(id, value, element: value_element, parent: node, attributes: { 'format' => '102' })
27
+ end
28
+ end
29
+
30
+ def emit_address(parent, address, group_id, ids)
31
+ within_group(group_id, address, element: 'ram:PostalTradeAddress', parent:) do |node, item|
32
+ ADDRESS_ELEMENTS.each do |attribute, element|
33
+ emit(ids.fetch(attribute), item.public_send(attribute), element:, parent: node)
34
+ end
35
+ end
36
+ end
37
+
38
+ def emit_contact(parent, contact, group_id, ids)
39
+ within_group(group_id, contact, element: 'ram:DefinedTradeContact', parent:) do |node, item|
40
+ emit(ids.fetch(:name), item.name, element: 'ram:PersonName', parent: node)
41
+ communication(node, ids.fetch(:telephone), item.telephone,
42
+ 'ram:TelephoneUniversalCommunication', 'ram:CompleteNumber')
43
+ communication(node, ids.fetch(:email), item.email, 'ram:EmailURIUniversalCommunication', 'ram:URIID')
44
+ end
45
+ end
46
+
47
+ def communication(parent, id, value, wrapper, element)
48
+ if value
49
+ container(parent, wrapper) { |node| emit(id, value, element:, parent: node) }
50
+ else
51
+ emit(id, nil, element:, parent:)
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Facturx
4
+ class Writer
5
+ module StageSupport
6
+ module Emission
7
+ private
8
+
9
+ def emit(id, value, **options)
10
+ term = Terms.fetch(id)
11
+ values = present_values(value)
12
+ return [] unless options.fetch(:group_present, true)
13
+
14
+ accepted = tracker.observe_term?(term, values:, path: term.xpath)
15
+ return [] unless accepted
16
+
17
+ values.filter_map { |item| emit_value(term, item, options) }
18
+ end
19
+
20
+ def emit_attribute(id, value, **options)
21
+ term = Terms.fetch(id)
22
+ values = present_values(value)
23
+ group_present = options.fetch(:group_present, !options.fetch(:node).nil?)
24
+ return unrepresentable(id, 'Qualifier requires its carrier') if !group_present && values.any?
25
+ return unless group_present
26
+
27
+ accepted = tracker.observe_term?(term, values:, path: term.xpath)
28
+ return unless accepted
29
+
30
+ emit_attribute_value(term, values, options)
31
+ end
32
+
33
+ def observe?(id, value, group_present: true)
34
+ return false unless group_present
35
+
36
+ term = Terms.fetch(id)
37
+ tracker.observe_term?(term, values: present_values(value), path: term.xpath)
38
+ end
39
+
40
+ def unrepresentable(id, message)
41
+ term = Terms.fetch(id)
42
+ tracker.invalid_term(term, error: FormattingError.new(message, path: term.xpath), path: term.xpath)
43
+ end
44
+
45
+ def present_values(value)
46
+ (value.is_a?(Array) ? value : [value]).compact
47
+ end
48
+
49
+ def format(value, term)
50
+ Format.call(value, term:)
51
+ rescue FormattingError => e
52
+ tracker.invalid_term(term, error: e, path: term.xpath)
53
+ nil
54
+ end
55
+
56
+ def emit_value(term, value, options)
57
+ formatted = format(value, term)
58
+ return unless formatted
59
+
60
+ context.element(
61
+ options.fetch(:parent),
62
+ options.fetch(:element),
63
+ text: formatted,
64
+ attributes: options.fetch(:attributes, {})
65
+ )
66
+ end
67
+
68
+ def emit_attribute_value(term, values, options)
69
+ node = options.fetch(:node)
70
+ return unless node && values.one?
71
+
72
+ formatted = format(values.first, term)
73
+ node[options.fetch(:attribute)] = formatted if formatted
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Facturx
4
+ class Writer
5
+ module StageSupport
6
+ module Identifiers
7
+ private
8
+
9
+ def emit_compound_identifier(parent, identifier, **options)
10
+ value_id = options.fetch(:value_id)
11
+ value = identifier&.value
12
+ node = emit(value_id, value, element: options.fetch(:element), parent:).first
13
+ emit_identifier_scheme(node, identifier, options)
14
+ node
15
+ end
16
+
17
+ def emit_party_identifiers(parent, identifiers, value_id, scheme_id)
18
+ items = Array(identifiers)
19
+ local, global = items.partition { |item| item.scheme_id.nil? }
20
+ missing, global = global.partition { |item| item.value.to_s.strip.empty? }
21
+ emit_global_identifiers(parent, missing, value_id, scheme_id)
22
+
23
+ return unless observe?(value_id, items.map(&:value))
24
+
25
+ emit_local_identifiers(parent, local, value_id)
26
+ emit_global_identifiers(parent, global, value_id, scheme_id)
27
+ end
28
+
29
+ def format_identifier_value(id, value)
30
+ return if value.nil?
31
+
32
+ format(value, Terms.fetch(id))
33
+ end
34
+
35
+ def emit_tax_registration(parent, id, identifier, scheme)
36
+ report_unrepresentable_identifier_scheme(id, identifier, represented_scheme: scheme)
37
+ value = identifier&.value
38
+ unless value
39
+ observe?(id, nil)
40
+ return
41
+ end
42
+
43
+ container(parent, 'ram:SpecifiedTaxRegistration') do |node|
44
+ emit(id, value, element: 'ram:ID', parent: node, attributes: { 'schemeID' => scheme })
45
+ end
46
+ end
47
+
48
+ def emit_document_reference(parent, id, reference, element, represented_attributes: [])
49
+ unless reference
50
+ observe?(id, nil)
51
+ return
52
+ end
53
+
54
+ represented = [:id, *represented_attributes]
55
+ report_unrepresentable_reference_attributes(id, reference, represented_attributes: represented)
56
+
57
+ container(parent, element) do |node|
58
+ emit(id, reference.id, element: 'ram:IssuerAssignedID', parent: node)
59
+ end
60
+ end
61
+
62
+ def emit_invoiced_object(parent, identifier, value_id, scheme_id)
63
+ unless identifier&.value
64
+ observe?(value_id, nil)
65
+ report_unrepresentable_identifier_scheme(scheme_id, identifier)
66
+ return
67
+ end
68
+
69
+ container(parent, 'ram:AdditionalReferencedDocument') do |node|
70
+ emit(value_id, identifier.value, element: 'ram:IssuerAssignedID', parent: node)
71
+ technical(node, 'ram:TypeCode', '130')
72
+ emit(scheme_id, identifier.scheme_id, element: 'ram:ReferenceTypeCode', parent: node)
73
+ end
74
+ end
75
+
76
+ def report_unrepresentable_reference_attributes(id, reference, represented_attributes: [])
77
+ %i[id line_id name issue_date].each do |attribute|
78
+ next unless reference.public_send(attribute) && !represented_attributes.include?(attribute)
79
+
80
+ unrepresentable(id, "Document reference #{attribute} cannot be represented")
81
+ end
82
+ end
83
+
84
+ def emit_identifier_value(id, identifier, element, parent)
85
+ report_unrepresentable_identifier_scheme(id, identifier)
86
+ emit(id, identifier&.value, element:, parent:)
87
+ end
88
+
89
+ def report_unrepresentable_identifier_scheme(id, identifier, represented_scheme: nil)
90
+ scheme_id = identifier&.scheme_id
91
+ return unless scheme_id && scheme_id != represented_scheme
92
+
93
+ unrepresentable(id, 'Identifier scheme cannot be represented')
94
+ end
95
+
96
+ def emit_identifier_scheme(node, identifier, options)
97
+ scheme_id = options[:scheme_id]
98
+ return unless scheme_id
99
+
100
+ emit_attribute(
101
+ scheme_id,
102
+ identifier&.scheme_id,
103
+ node:,
104
+ attribute: options.fetch(:scheme_attribute, 'schemeID'),
105
+ group_present: !node.nil?
106
+ )
107
+ end
108
+
109
+ def emit_local_identifiers(parent, identifiers, value_id)
110
+ identifiers.each do |item|
111
+ technical(parent, 'ram:ID', format_identifier_value(value_id, item.value))
112
+ end
113
+ end
114
+
115
+ def emit_global_identifiers(parent, identifiers, value_id, scheme_id)
116
+ identifiers.each do |item|
117
+ if item.value.to_s.strip.empty?
118
+ unrepresentable(value_id, 'Global identifier requires a value')
119
+ next
120
+ end
121
+
122
+ node = technical(parent, 'ram:GlobalID', format_identifier_value(value_id, item.value))
123
+ emit_attribute(scheme_id, item.scheme_id, node:, attribute: 'schemeID', group_present: true)
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Facturx
4
+ class Writer
5
+ module StageSupport
6
+ module Structure
7
+ private
8
+
9
+ def within_group(id, value, element:, parent:, represented_attributes: [])
10
+ group = Terms.group(id)
11
+ values = present_values(value)
12
+ accepted = tracker.observe_group?(group, count: values.size, path: group.xpath)
13
+ return unless accepted
14
+
15
+ values.each do |item|
16
+ write_group_item(group, item, element, parent, represented_attributes) { |node| yield(node, item) }
17
+ end
18
+ end
19
+
20
+ def each_group(id, values, element:, parent:, represented_attributes: [], &)
21
+ within_group(id, Array(values), element:, parent:, represented_attributes:, &)
22
+ end
23
+
24
+ def write_group_item(group, item, element, parent, represented_attributes)
25
+ if group.attribute
26
+ report_unrepresentable_attributes(group.id, item, model: group.model,
27
+ represented_attributes:)
28
+ end
29
+ node = context.element(parent, element)
30
+ yield node
31
+ remove_if_empty(node)
32
+ end
33
+
34
+ def technical(parent, element, value, default: nil)
35
+ text = value.to_s
36
+ text = default.to_s if text.strip.empty?
37
+ return if text.strip.empty?
38
+
39
+ context.element(parent, element, text:)
40
+ end
41
+
42
+ def container(parent, element)
43
+ node = context.element(parent, element)
44
+ yield node
45
+ remove_if_empty(node)
46
+ node
47
+ end
48
+
49
+ def remove_if_empty(node)
50
+ node.remove if node.element_children.empty? && node.text.empty? && node.attribute_nodes.empty?
51
+ end
52
+
53
+ def report_unrepresentable_attributes(group_id, item, model: model_for(item), represented_attributes: [])
54
+ return unless item.is_a?(Data) && model_for(item) == model
55
+
56
+ group = Terms.group(group_id)
57
+ item.members.each do |attribute|
58
+ value = item.public_send(attribute)
59
+ next unless unrepresentable_attribute?(group, model, attribute, value, represented_attributes)
60
+
61
+ tracker.unrepresentable_attribute(group, model:, attribute:)
62
+ end
63
+ end
64
+
65
+ def report_unrepresentable_attribute(group_id, model:, attribute:)
66
+ tracker.unrepresentable_attribute(Terms.group(group_id), model:, attribute:)
67
+ end
68
+
69
+ def attribute_supported_in_group?(group, model, attribute)
70
+ Terms.all.any? { |term| term.group_id == group.id && term.model == model && term.attribute == attribute }
71
+ end
72
+
73
+ def unrepresentable_attribute?(group, model, attribute, value, represented_attributes)
74
+ populated?(value) && !represented_attributes.include?(attribute) &&
75
+ !attribute_supported_in_group?(group, model, attribute)
76
+ end
77
+
78
+ def populated?(value)
79
+ !value.nil? && (!value.respond_to?(:empty?) || !value.empty?)
80
+ end
81
+
82
+ def model_for(item)
83
+ item.class.name.split('::').last.gsub(/([a-z\\d])([A-Z])/, '\\1_\\2').downcase.to_sym
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Facturx
4
+ class Writer
5
+ module Stages
6
+ class DocumentContext < Stage
7
+ term_ids 'BT-23', 'BT-24'
8
+
9
+ def call
10
+ within_group('BG-2', document, element: 'rsm:ExchangedDocumentContext', parent: context.root,
11
+ represented_attributes: [:vat_point_date_code]) do |node,|
12
+ emit_context_parameter(node, 'BT-23', document.business_process,
13
+ 'ram:BusinessProcessSpecifiedDocumentContextParameter')
14
+ guideline = document.guideline_urn || profile.guideline_urn
15
+ emit_context_parameter(node, 'BT-24', guideline, 'ram:GuidelineSpecifiedDocumentContextParameter')
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def emit_context_parameter(parent, id, value, element)
22
+ if value
23
+ container(parent, element) { |node| emit(id, value, element: 'ram:ID', parent: node) }
24
+ else
25
+ emit(id, nil, element: 'ram:ID', parent: parent)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Facturx
4
+ class Writer
5
+ module Stages
6
+ class ExchangedDocument < Stage
7
+ term_ids 'BT-1', 'BT-3', 'BT-2', 'BT-22', 'BT-21'
8
+
9
+ def call
10
+ node = context.element(context.root, 'rsm:ExchangedDocument')
11
+ emit('BT-1', document.invoice_number, element: 'ram:ID', parent: node)
12
+ emit('BT-3', document.type_code, element: 'ram:TypeCode', parent: node)
13
+ issue_date(node)
14
+ notes(node)
15
+ end
16
+
17
+ private
18
+
19
+ def notes(parent)
20
+ each_group('BG-1', document.notes, element: 'ram:IncludedNote', parent:) do |note_node, note|
21
+ emit('BT-22', note.content, element: 'ram:Content', parent: note_node)
22
+ emit('BT-21', note.subject_code, element: 'ram:SubjectCode', parent: note_node)
23
+ end
24
+ end
25
+
26
+ def issue_date(parent)
27
+ emit_date(parent, 'BT-2', document.issue_date, wrapper: 'ram:IssueDateTime')
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Facturx
4
+ class Writer
5
+ module Stages
6
+ module TradeAgreementSupport
7
+ module Parties
8
+ private
9
+
10
+ def write_parties(parent)
11
+ seller(parent)
12
+ buyer(parent)
13
+ tax_representative(parent)
14
+ end
15
+
16
+ def seller(parent)
17
+ within_group('BG-4', document.seller, element: 'ram:SellerTradeParty', parent:,
18
+ represented_attributes: %i[address contact]) do |node, party|
19
+ write_seller_identity(node, party)
20
+ write_seller_details(node, party)
21
+ end
22
+ end
23
+
24
+ def write_seller_identity(node, party)
25
+ emit_party_identifiers(node, party.identifiers, 'BT-29', 'BT-29-1')
26
+ emit('BT-27', party.name, element: 'ram:Name', parent: node)
27
+ emit('BT-33', party.description, element: 'ram:Description', parent: node)
28
+ legal_organization(node, party, 'BT-30', 'BT-30-1', trading_name_id: 'BT-28')
29
+ end
30
+
31
+ def write_seller_details(node, party)
32
+ emit_contact(node, party.contact, 'BG-6', self.class::SELLER_CONTACT)
33
+ emit_address(node, party.address, 'BG-5', self.class::SELLER_ADDRESS)
34
+ electronic_address(node, party.electronic_address, 'BT-34', 'BT-34-1')
35
+ emit_tax_registration(node, 'BT-31', party.vat_identifier, 'VA')
36
+ emit_tax_registration(node, 'BT-32', party.tax_identifier, 'FC')
37
+ end
38
+
39
+ def buyer(parent)
40
+ within_group('BG-7', document.buyer, element: 'ram:BuyerTradeParty', parent:,
41
+ represented_attributes: %i[address contact]) do |node, party|
42
+ write_buyer_identity(node, party)
43
+ write_buyer_details(node, party)
44
+ end
45
+ end
46
+
47
+ def write_buyer_identity(node, party)
48
+ emit_party_identifiers(node, party.identifiers, 'BT-46', 'BT-46-1')
49
+ emit('BT-44', party.name, element: 'ram:Name', parent: node)
50
+ legal_organization(node, party, 'BT-47', 'BT-47-1', trading_name_id: 'BT-45')
51
+ end
52
+
53
+ def write_buyer_details(node, party)
54
+ emit_contact(node, party.contact, 'BG-9', self.class::BUYER_CONTACT)
55
+ emit_address(node, party.address, 'BG-8', self.class::BUYER_ADDRESS)
56
+ electronic_address(node, party.electronic_address, 'BT-49', 'BT-49-1')
57
+ emit_tax_registration(node, 'BT-48', party.vat_identifier, 'VA')
58
+ end
59
+
60
+ def tax_representative(parent)
61
+ within_group('BG-11', document.tax_representative, element: 'ram:SellerTaxRepresentativeTradeParty',
62
+ parent:,
63
+ represented_attributes: [:address]) do |node, party|
64
+ emit('BT-62', party.name, element: 'ram:Name', parent: node)
65
+ emit_address(node, party.address, 'BG-12', self.class::REPRESENTATIVE_ADDRESS)
66
+ emit_tax_registration(node, 'BT-63', party.vat_identifier, 'VA')
67
+ end
68
+ end
69
+
70
+ def legal_organization(parent, party, value_id, scheme_id, trading_name_id:)
71
+ legal = party.legal_registration
72
+ trading = party.trading_name
73
+ return observe_missing_legal_organization(value_id, scheme_id, trading_name_id) unless legal || trading
74
+
75
+ container(parent, 'ram:SpecifiedLegalOrganization') do |node|
76
+ emit_compound_identifier(node, legal, value_id:, scheme_id:, element: 'ram:ID')
77
+ emit(trading_name_id, trading, element: 'ram:TradingBusinessName', parent: node)
78
+ end
79
+ end
80
+
81
+ def observe_missing_legal_organization(value_id, scheme_id, trading_name_id)
82
+ observe?(value_id, nil)
83
+ observe?(scheme_id, nil, group_present: false)
84
+ observe?(trading_name_id, nil)
85
+ nil
86
+ end
87
+
88
+ def electronic_address(parent, identifier, value_id, scheme_id)
89
+ return observe_missing_identifier(value_id, scheme_id) unless identifier
90
+
91
+ container(parent, 'ram:URIUniversalCommunication') do |node|
92
+ emit_compound_identifier(node, identifier, value_id:, scheme_id:, element: 'ram:URIID')
93
+ end
94
+ end
95
+
96
+ def observe_missing_identifier(value_id, scheme_id)
97
+ observe?(value_id, nil)
98
+ observe?(scheme_id, nil, group_present: false)
99
+ nil
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Facturx
4
+ class Writer
5
+ module Stages
6
+ module TradeAgreementSupport
7
+ module References
8
+ private
9
+
10
+ def write_references(parent)
11
+ document_references(parent)
12
+ additional_references(parent)
13
+ project(parent)
14
+ end
15
+
16
+ def document_references(parent)
17
+ emit_document_reference(parent, 'BT-14', document.sales_order_reference,
18
+ 'ram:SellerOrderReferencedDocument')
19
+ emit_document_reference(parent, 'BT-13', document.purchase_order_reference,
20
+ 'ram:BuyerOrderReferencedDocument')
21
+ emit_document_reference(parent, 'BT-12', document.contract_reference, 'ram:ContractReferencedDocument')
22
+ end
23
+
24
+ def additional_references(parent)
25
+ typed_reference(parent, 'BT-17', document.tender_or_lot_reference, '50')
26
+ invoiced_object_reference(parent)
27
+ each_group('BG-24', document.supporting_documents, element: 'ram:AdditionalReferencedDocument',
28
+ parent:) do |node, item|
29
+ write_supporting_document(node, item)
30
+ end
31
+ end
32
+
33
+ def write_supporting_document(node, item)
34
+ emit('BT-122', item.reference, element: 'ram:IssuerAssignedID', parent: node)
35
+ emit('BT-124', item.external_location, element: 'ram:URIID', parent: node)
36
+ technical(node, 'ram:TypeCode', '916')
37
+ emit('BT-123', item.description, element: 'ram:Name', parent: node)
38
+ attachment(node, item)
39
+ end
40
+
41
+ def typed_reference(parent, id, value, type_code)
42
+ report_unrepresentable_reference_attributes(id, value, represented_attributes: [:id]) if value
43
+
44
+ unless value&.id
45
+ observe?(id, nil)
46
+ return
47
+ end
48
+
49
+ container(parent, 'ram:AdditionalReferencedDocument') do |node|
50
+ emit(id, value.id, element: 'ram:IssuerAssignedID', parent: node)
51
+ technical(node, 'ram:TypeCode', type_code)
52
+ end
53
+ end
54
+
55
+ def invoiced_object_reference(parent)
56
+ emit_invoiced_object(parent, document.invoiced_object_identifier, 'BT-18', 'BT-18-1')
57
+ end
58
+
59
+ def attachment(parent, item)
60
+ node = emit('BT-125', item.content, element: 'ram:AttachmentBinaryObject', parent:).first
61
+ present = !item.content.nil?
62
+ emit_attribute('BT-125-1', item.mime_code, node:, attribute: 'mimeCode', group_present: present)
63
+ emit_attribute('BT-125-2', item.filename, node:, attribute: 'filename', group_present: present)
64
+ end
65
+
66
+ def project(parent)
67
+ value = document.project_reference
68
+ return observe?('BT-11', nil) unless project_reference?(value)
69
+ return unless representable_project_reference?(value)
70
+
71
+ container(parent, 'ram:SpecifiedProcuringProject') do |node|
72
+ emit('BT-11', value.id, element: 'ram:ID', parent: node)
73
+ technical(node, 'ram:Name', value.name, default: value.id)
74
+ end
75
+ end
76
+
77
+ def project_reference?(value)
78
+ value && (value.id || value.name)
79
+ end
80
+
81
+ def representable_project_reference?(value)
82
+ report_unrepresentable_reference_attributes('BT-11', value, represented_attributes: %i[id name])
83
+ return true if value.id
84
+
85
+ unrepresentable('BT-11', 'Project reference requires an identifier')
86
+ false
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end