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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +29 -2
- data/NOTICE.md +2 -0
- data/README.md +62 -4
- data/lib/facturx/builders/association.rb +8 -0
- data/lib/facturx/builders/base.rb +129 -0
- data/lib/facturx/builders/schema.rb +112 -0
- data/lib/facturx/builders.rb +25 -0
- data/lib/facturx/coerce.rb +74 -0
- data/lib/facturx/conformance.rb +163 -0
- data/lib/facturx/diagnostic.rb +20 -0
- data/lib/facturx/document.rb +45 -0
- data/lib/facturx/error.rb +3 -0
- data/lib/facturx/format.rb +85 -0
- data/lib/facturx/generate.rb +21 -0
- data/lib/facturx/group.rb +5 -0
- data/lib/facturx/model/common.rb +8 -0
- data/lib/facturx/model/immutable.rb +75 -0
- data/lib/facturx/model/line.rb +14 -0
- data/lib/facturx/model/party.rb +10 -0
- data/lib/facturx/model/payment.rb +9 -0
- data/lib/facturx/model/reference.rb +7 -0
- data/lib/facturx/model/trade.rb +9 -0
- data/lib/facturx/model.rb +9 -0
- data/lib/facturx/pdf/extractor.rb +7 -4
- data/lib/facturx/profile_resolver.rb +40 -0
- data/lib/facturx/reader/semantic_mapper/delivery_mapping.rb +43 -0
- data/lib/facturx/reader/semantic_mapper/document_mapping.rb +109 -0
- data/lib/facturx/reader/semantic_mapper/helpers.rb +114 -0
- data/lib/facturx/reader/semantic_mapper/line_mapping.rb +120 -0
- data/lib/facturx/reader/semantic_mapper/line_references.rb +19 -0
- data/lib/facturx/reader/semantic_mapper/party_mapping.rb +90 -0
- data/lib/facturx/reader/semantic_mapper/payment_mapping.rb +108 -0
- data/lib/facturx/reader/semantic_mapper/trade_mapping.rb +83 -0
- data/lib/facturx/reader/semantic_mapper.rb +76 -0
- data/lib/facturx/reader/term_reader/coercion.rb +69 -0
- data/lib/facturx/reader/term_reader/unmapped.rb +37 -0
- data/lib/facturx/reader/term_reader.rb +132 -0
- data/lib/facturx/reader.rb +97 -0
- data/lib/facturx/reading.rb +23 -0
- data/lib/facturx/source_reader.rb +30 -0
- data/lib/facturx/term.rb +5 -0
- data/lib/facturx/terms/declaration.rb +20 -0
- data/lib/facturx/terms/declarations/adjustments.rb +230 -0
- data/lib/facturx/terms/declarations/delivery.rb +83 -0
- data/lib/facturx/terms/declarations/document.rb +223 -0
- data/lib/facturx/terms/declarations/groups.rb +356 -0
- data/lib/facturx/terms/declarations/lines.rb +166 -0
- data/lib/facturx/terms/declarations/parties.rb +609 -0
- data/lib/facturx/terms/declarations/payment.rb +106 -0
- data/lib/facturx/terms/declarations/products.rb +103 -0
- data/lib/facturx/terms/declarations/references.rb +72 -0
- data/lib/facturx/terms/declarations/taxes.rb +92 -0
- data/lib/facturx/terms/declarations/totals.rb +107 -0
- data/lib/facturx/terms/reference.rb +21 -0
- data/lib/facturx/terms.rb +109 -0
- data/lib/facturx/version.rb +1 -1
- data/lib/facturx/writer/context.rb +53 -0
- data/lib/facturx/writer/stage.rb +47 -0
- data/lib/facturx/writer/stage_support/contact_details.rb +57 -0
- data/lib/facturx/writer/stage_support/emission.rb +78 -0
- data/lib/facturx/writer/stage_support/identifiers.rb +129 -0
- data/lib/facturx/writer/stage_support/structure.rb +88 -0
- data/lib/facturx/writer/stages/document_context.rb +31 -0
- data/lib/facturx/writer/stages/exchanged_document.rb +32 -0
- data/lib/facturx/writer/stages/trade_agreement/parties.rb +105 -0
- data/lib/facturx/writer/stages/trade_agreement/references.rb +92 -0
- data/lib/facturx/writer/stages/trade_agreement.rb +45 -0
- data/lib/facturx/writer/stages/trade_delivery.rb +77 -0
- data/lib/facturx/writer/stages/trade_lines/agreement.rb +92 -0
- data/lib/facturx/writer/stages/trade_lines/product.rb +78 -0
- data/lib/facturx/writer/stages/trade_lines/quantity.rb +17 -0
- data/lib/facturx/writer/stages/trade_lines/settlement.rb +89 -0
- data/lib/facturx/writer/stages/trade_lines.rb +67 -0
- data/lib/facturx/writer/stages/trade_settlement/parties.rb +33 -0
- data/lib/facturx/writer/stages/trade_settlement/payment.rb +97 -0
- data/lib/facturx/writer/stages/trade_settlement/summary.rb +88 -0
- data/lib/facturx/writer/stages/trade_settlement/taxes.rb +121 -0
- data/lib/facturx/writer/stages/trade_settlement.rb +70 -0
- data/lib/facturx/writer.rb +68 -0
- data/lib/facturx/xml/namespaces.rb +19 -0
- data/lib/facturx/xml/profile_detector.rb +2 -4
- data/lib/facturx.rb +42 -0
- data/rbi/facturx.rbi +1090 -0
- 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
|