facturx 0.2.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 +21 -3
- data/README.md +40 -8
- 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/conformance.rb +163 -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/profile_resolver.rb +40 -0
- data/lib/facturx/reader/semantic_mapper/line_mapping.rb +1 -1
- data/lib/facturx/reader/semantic_mapper/line_references.rb +19 -0
- data/lib/facturx/reader/semantic_mapper/payment_mapping.rb +48 -11
- data/lib/facturx/reader/semantic_mapper.rb +9 -0
- data/lib/facturx/reader/term_reader.rb +12 -4
- data/lib/facturx/reader.rb +3 -7
- 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 +28 -0
- data/rbi/facturx.rbi +1090 -0
- metadata +38 -4
|
@@ -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
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'trade_agreement/parties'
|
|
4
|
+
require_relative 'trade_agreement/references'
|
|
5
|
+
|
|
6
|
+
module Facturx
|
|
7
|
+
class Writer
|
|
8
|
+
module Stages
|
|
9
|
+
class TradeAgreement < Stage
|
|
10
|
+
include TradeAgreementSupport::Parties
|
|
11
|
+
include TradeAgreementSupport::References
|
|
12
|
+
|
|
13
|
+
SELLER_ADDRESS = {
|
|
14
|
+
postcode: 'BT-38', line_one: 'BT-35', line_two: 'BT-36', line_three: 'BT-162', city: 'BT-37',
|
|
15
|
+
country_code: 'BT-40', country_subdivision: 'BT-39'
|
|
16
|
+
}.freeze
|
|
17
|
+
BUYER_ADDRESS = {
|
|
18
|
+
postcode: 'BT-53', line_one: 'BT-50', line_two: 'BT-51', line_three: 'BT-163', city: 'BT-52',
|
|
19
|
+
country_code: 'BT-55', country_subdivision: 'BT-54'
|
|
20
|
+
}.freeze
|
|
21
|
+
REPRESENTATIVE_ADDRESS = {
|
|
22
|
+
postcode: 'BT-67', line_one: 'BT-64', line_two: 'BT-65', line_three: 'BT-164', city: 'BT-66',
|
|
23
|
+
country_code: 'BT-69', country_subdivision: 'BT-68'
|
|
24
|
+
}.freeze
|
|
25
|
+
SELLER_CONTACT = { name: 'BT-41', telephone: 'BT-42', email: 'BT-43' }.freeze
|
|
26
|
+
BUYER_CONTACT = { name: 'BT-56', telephone: 'BT-57', email: 'BT-58' }.freeze
|
|
27
|
+
|
|
28
|
+
term_ids(*%w[
|
|
29
|
+
BT-10 BT-29 BT-29-1 BT-27 BT-33 BT-30 BT-30-1 BT-28 BT-41 BT-42 BT-43 BT-38 BT-35 BT-36
|
|
30
|
+
BT-162 BT-37 BT-40 BT-39 BT-34 BT-34-1 BT-31 BT-32 BT-46 BT-46-1 BT-44 BT-47 BT-47-1 BT-45
|
|
31
|
+
BT-56 BT-57 BT-58 BT-53 BT-50 BT-51 BT-163 BT-52 BT-55 BT-54 BT-49 BT-49-1 BT-48 BT-62 BT-67
|
|
32
|
+
BT-64 BT-65 BT-164 BT-66 BT-69 BT-68 BT-63 BT-14 BT-13 BT-12 BT-17 BT-18 BT-18-1 BT-122 BT-124
|
|
33
|
+
BT-123 BT-125 BT-125-1 BT-125-2 BT-11
|
|
34
|
+
])
|
|
35
|
+
|
|
36
|
+
def call
|
|
37
|
+
agreement = context.element(context.transaction, 'ram:ApplicableHeaderTradeAgreement')
|
|
38
|
+
emit('BT-10', document.buyer_reference, element: 'ram:BuyerReference', parent: agreement)
|
|
39
|
+
write_parties(agreement)
|
|
40
|
+
write_references(agreement)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Facturx
|
|
4
|
+
class Writer
|
|
5
|
+
module Stages
|
|
6
|
+
class TradeDelivery < Stage
|
|
7
|
+
ADDRESS_IDS = {
|
|
8
|
+
postcode: 'BT-78', line_one: 'BT-75', line_two: 'BT-76', line_three: 'BT-165', city: 'BT-77',
|
|
9
|
+
country_code: 'BT-80', country_subdivision: 'BT-79'
|
|
10
|
+
}.freeze
|
|
11
|
+
|
|
12
|
+
term_ids(*%w[BT-71 BT-71-1 BT-70 BT-78 BT-75 BT-76 BT-165 BT-77 BT-80 BT-79 BT-72 BT-16 BT-15])
|
|
13
|
+
|
|
14
|
+
def call
|
|
15
|
+
value = document.delivery
|
|
16
|
+
node = context.element(context.transaction, 'ram:ApplicableHeaderTradeDelivery')
|
|
17
|
+
delivery(node, value) if delivery_accepted?(value)
|
|
18
|
+
references(node)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def delivery_accepted?(value)
|
|
24
|
+
group = Terms.group('BG-13-00')
|
|
25
|
+
tracker.observe_group?(group, count: value ? 1 : 0, path: group.xpath)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def references(parent)
|
|
29
|
+
emit_document_reference(parent, 'BT-16', document.despatch_advice_reference,
|
|
30
|
+
'ram:DespatchAdviceReferencedDocument')
|
|
31
|
+
emit_document_reference(parent, 'BT-15', document.receiving_advice_reference,
|
|
32
|
+
'ram:ReceivingAdviceReferencedDocument')
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def delivery(parent, value)
|
|
36
|
+
ship_to(parent, value)
|
|
37
|
+
delivery_date(parent, value.date)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def delivery_date(parent, value)
|
|
41
|
+
unless value
|
|
42
|
+
observe?('BT-72', nil)
|
|
43
|
+
return
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
container(parent, 'ram:ActualDeliverySupplyChainEvent') do |event|
|
|
47
|
+
emit_date(event, 'BT-72', value, wrapper: 'ram:OccurrenceDateTime')
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def ship_to(parent, delivery)
|
|
52
|
+
party = delivery.party
|
|
53
|
+
location = delivery.location_identifier
|
|
54
|
+
present = party || location
|
|
55
|
+
within_group('BG-13', present, element: 'ram:ShipToTradeParty', parent:,
|
|
56
|
+
represented_attributes: %i[name address]) do |node,|
|
|
57
|
+
delivery_identifier(node, location)
|
|
58
|
+
emit('BT-70', party&.name, element: 'ram:Name', parent: node)
|
|
59
|
+
emit_address(node, party&.address, 'BG-15', ADDRESS_IDS)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def delivery_identifier(parent, value)
|
|
64
|
+
if value&.scheme_id
|
|
65
|
+
node = if observe?('BT-71', value.value)
|
|
66
|
+
technical(parent, 'ram:GlobalID', format_identifier_value('BT-71', value.value))
|
|
67
|
+
end
|
|
68
|
+
emit_attribute('BT-71-1', value.scheme_id, node:, attribute: 'schemeID', group_present: !node.nil?)
|
|
69
|
+
else
|
|
70
|
+
emit('BT-71', value&.value, element: 'ram:ID', parent:)
|
|
71
|
+
observe?('BT-71-1', nil, group_present: false)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Facturx
|
|
4
|
+
class Writer
|
|
5
|
+
module Stages
|
|
6
|
+
class TradeLines
|
|
7
|
+
class Agreement < Stage
|
|
8
|
+
PRICES = {
|
|
9
|
+
gross: { ids: %w[BT-148 BT-149-1 BT-150-1], element: 'ram:GrossPriceProductTradePrice' },
|
|
10
|
+
net: { ids: %w[BT-146 BT-149 BT-150], element: 'ram:NetPriceProductTradePrice' }
|
|
11
|
+
}.freeze
|
|
12
|
+
|
|
13
|
+
def initialize(context, quantity:)
|
|
14
|
+
super(context)
|
|
15
|
+
@quantity = quantity
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def call(parent:, line:)
|
|
19
|
+
within_group('BG-29', line, element: 'ram:SpecifiedLineTradeAgreement', parent:) do |node, item|
|
|
20
|
+
buyer_order_reference(node, item.buyer_order_reference)
|
|
21
|
+
price(node, item.gross_price, kind: :gross)
|
|
22
|
+
price(node, item.net_price, kind: :net)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def buyer_order_reference(parent, value)
|
|
29
|
+
return observe?('BT-132', nil) unless value
|
|
30
|
+
|
|
31
|
+
report_unrepresentable_reference_attributes('BT-132', value, represented_attributes: [:line_id])
|
|
32
|
+
|
|
33
|
+
container(parent, 'ram:BuyerOrderReferencedDocument') do |node|
|
|
34
|
+
emit('BT-132', value.line_id, element: 'ram:LineID', parent: node)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def price(parent, value, kind:)
|
|
39
|
+
config = PRICES.fetch(kind)
|
|
40
|
+
return observe_missing_price(config, kind:) unless value
|
|
41
|
+
return unrepresentable(config.fetch(:ids).first, 'Price requires an amount') unless value.amount
|
|
42
|
+
|
|
43
|
+
represented_attributes = %i[amount basis_quantity]
|
|
44
|
+
represented_attributes << :discount if kind == :gross
|
|
45
|
+
report_unrepresentable_attributes('BG-29', value, represented_attributes:)
|
|
46
|
+
|
|
47
|
+
container(parent, config.fetch(:element)) do |node|
|
|
48
|
+
emit_price(node, value, config.fetch(:ids))
|
|
49
|
+
gross_discount(node, value.discount) if kind == :gross
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def emit_price(parent, value, ids)
|
|
54
|
+
emit(ids[0], value.amount, element: 'ram:ChargeAmount', parent:)
|
|
55
|
+
@quantity.call(parent:, value: value.basis_quantity, amount_id: ids[1], unit_id: ids[2],
|
|
56
|
+
element: 'ram:BasisQuantity')
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def observe_missing_price(config, kind:)
|
|
60
|
+
config.fetch(:ids).each { |id| observe?(id, nil) }
|
|
61
|
+
return unless kind == :gross
|
|
62
|
+
|
|
63
|
+
observe?('BT-147-01', nil)
|
|
64
|
+
observe?('BT-147', nil)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def gross_discount(parent, value)
|
|
68
|
+
return observe_missing_discount? unless value
|
|
69
|
+
return unless observe?('BT-147-01', false) && observe?('BT-147-02', false)
|
|
70
|
+
|
|
71
|
+
emit_discount(parent, value)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def observe_missing_discount?
|
|
75
|
+
observe?('BT-147-01', nil)
|
|
76
|
+
observe?('BT-147-02', nil, group_present: false)
|
|
77
|
+
observe?('BT-147', nil)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def emit_discount(parent, value)
|
|
81
|
+
container(parent, 'ram:AppliedTradeAllowanceCharge') do |node|
|
|
82
|
+
container(node, 'ram:ChargeIndicator') do |indicator|
|
|
83
|
+
technical(indicator, 'udt:Indicator', format_identifier_value('BT-147-02', false))
|
|
84
|
+
end
|
|
85
|
+
emit('BT-147', value, element: 'ram:ActualAmount', parent: node)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Facturx
|
|
4
|
+
class Writer
|
|
5
|
+
module Stages
|
|
6
|
+
class TradeLines
|
|
7
|
+
class Product < Stage
|
|
8
|
+
def call(parent:, value:)
|
|
9
|
+
within_group('BG-31', value, element: 'ram:SpecifiedTradeProduct', parent:,
|
|
10
|
+
represented_attributes: %i[attributes classifications]) do |node, item|
|
|
11
|
+
identifiers(node, item)
|
|
12
|
+
emit('BT-153', item.name, element: 'ram:Name', parent: node)
|
|
13
|
+
emit('BT-154', item.description, element: 'ram:Description', parent: node)
|
|
14
|
+
attributes(node, item.attributes)
|
|
15
|
+
classifications(node, item.classifications)
|
|
16
|
+
origin_country(node, item.origin_country_code)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def identifiers(parent, item)
|
|
23
|
+
emit_compound_identifier(parent, item.global_identifier, value_id: 'BT-157', scheme_id: 'BT-157-1',
|
|
24
|
+
element: 'ram:GlobalID')
|
|
25
|
+
emit_identifier_value('BT-155', item.seller_identifier, 'ram:SellerAssignedID', parent)
|
|
26
|
+
emit_identifier_value('BT-156', item.buyer_identifier, 'ram:BuyerAssignedID', parent)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def attributes(parent, values)
|
|
30
|
+
each_group('BG-32', values, element: 'ram:ApplicableProductCharacteristic', parent:) do |node, item|
|
|
31
|
+
emit('BT-160', item.name, element: 'ram:Description', parent: node)
|
|
32
|
+
emit('BT-161', item.value, element: 'ram:Value', parent: node)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def classifications(parent, values)
|
|
37
|
+
emit_unrepresentable_classifications(parent, values)
|
|
38
|
+
emit_representable_classifications(parent, values)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def emit_unrepresentable_classifications(parent, values)
|
|
42
|
+
Array(values).reject { |item| classification_code?(item) }.each do |item|
|
|
43
|
+
classification(parent, item)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def emit_representable_classifications(parent, values)
|
|
48
|
+
items = Array(values).select { |item| classification_code?(item) }
|
|
49
|
+
return unless observe?('BT-158', items.map(&:code))
|
|
50
|
+
|
|
51
|
+
items.each { |item| classification(parent, item) }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def classification_code?(item)
|
|
55
|
+
!item.code.to_s.strip.empty?
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def classification(parent, item)
|
|
59
|
+
container(parent, 'ram:DesignatedProductClassification') do |node|
|
|
60
|
+
code = technical(node, 'ram:ClassCode', format_identifier_value('BT-158', item.code))
|
|
61
|
+
emit_attribute('BT-158-1', item.list_id, node: code, attribute: 'listID', group_present: !code.nil?)
|
|
62
|
+
emit_attribute('BT-158-2', item.list_version_id, node: code, attribute: 'listVersionID',
|
|
63
|
+
group_present: !code.nil?)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def origin_country(parent, value)
|
|
68
|
+
return observe?('BT-159', nil) unless value
|
|
69
|
+
|
|
70
|
+
container(parent, 'ram:OriginTradeCountry') do |node|
|
|
71
|
+
emit('BT-159', value, element: 'ram:ID', parent: node)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Facturx
|
|
4
|
+
class Writer
|
|
5
|
+
module Stages
|
|
6
|
+
class TradeLines
|
|
7
|
+
class Quantity < Stage
|
|
8
|
+
def call(parent:, value:, amount_id:, unit_id:, element:)
|
|
9
|
+
node = emit(amount_id, value&.value, element:, parent:).first
|
|
10
|
+
emit_attribute(unit_id, value&.unit_code, node:, attribute: 'unitCode',
|
|
11
|
+
group_present: !node.nil?)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|