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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +43 -3
- data/DOCUMENTATION.md +271 -0
- data/README.md +79 -69
- data/lib/facturx/attach.rb +4 -20
- 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/error.rb +7 -1
- data/lib/facturx/format.rb +85 -0
- data/lib/facturx/generate.rb +21 -0
- data/lib/facturx/pdf/composer.rb +32 -0
- data/lib/facturx/profile.rb +1 -1
- data/lib/facturx/profile_resolver.rb +40 -0
- data/lib/facturx/profiles.rb +1 -8
- 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/source_reader.rb +1 -1
- data/lib/facturx/validation.rb +61 -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/tracker.rb +131 -0
- data/lib/facturx/writer.rb +76 -0
- data/lib/facturx/xml/namespaces.rb +19 -0
- data/lib/facturx/xml/parser.rb +1 -1
- data/lib/facturx/xml/profile_detector.rb +2 -4
- data/lib/facturx/xml/report_builder.rb +44 -0
- data/lib/facturx/xml/schema_registry.rb +22 -0
- data/lib/facturx/xml/schema_validator.rb +7 -2
- data/lib/facturx/xml/validator.rb +32 -0
- data/lib/facturx/xml/verifier.rb +16 -11
- data/lib/facturx.rb +41 -4
- data/rbi/facturx.rbi +1755 -0
- metadata +44 -4
|
@@ -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
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Facturx
|
|
4
|
+
class Writer
|
|
5
|
+
module Stages
|
|
6
|
+
class TradeLines
|
|
7
|
+
class Settlement < Stage
|
|
8
|
+
ADJUSTMENT_IDS = {
|
|
9
|
+
'BG-27' => %w[BT-138 BT-137 BT-136 BT-140 BT-139],
|
|
10
|
+
'BG-28' => %w[BT-143 BT-142 BT-141 BT-145 BT-144]
|
|
11
|
+
}.freeze
|
|
12
|
+
|
|
13
|
+
def call(parent:, line:)
|
|
14
|
+
node = context.element(parent, 'ram:SpecifiedLineTradeSettlement')
|
|
15
|
+
line_tax(node, line.tax)
|
|
16
|
+
period(node, line.period)
|
|
17
|
+
adjustment_groups(node, line.allowances, 'BG-27', false)
|
|
18
|
+
adjustment_groups(node, line.charges, 'BG-28', true)
|
|
19
|
+
monetary_sum(node, line.net_amount)
|
|
20
|
+
line_invoiced_object(node, line.invoiced_object_identifier)
|
|
21
|
+
accounting_reference(node, 'BT-133', line.buyer_accounting_reference)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def line_tax(parent, value)
|
|
27
|
+
within_group('BG-30', value, element: 'ram:ApplicableTradeTax', parent:,
|
|
28
|
+
represented_attributes: [:type_code]) do |node, item|
|
|
29
|
+
technical(node, 'ram:TypeCode', item.type_code, default: 'VAT')
|
|
30
|
+
emit('BT-151', item.category_code, element: 'ram:CategoryCode', parent: node)
|
|
31
|
+
emit('BT-152', item.rate, element: 'ram:RateApplicablePercent', parent: node)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def period(parent, value)
|
|
36
|
+
within_group('BG-26', value, element: 'ram:BillingSpecifiedPeriod', parent:) do |node, item|
|
|
37
|
+
emit_date(node, 'BT-134', item.start_date, wrapper: 'ram:StartDateTime')
|
|
38
|
+
emit_date(node, 'BT-135', item.end_date, wrapper: 'ram:EndDateTime')
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def adjustment_groups(parent, values, group_id, indicator)
|
|
43
|
+
ids = ADJUSTMENT_IDS.fetch(group_id)
|
|
44
|
+
each_group(group_id, values, element: 'ram:SpecifiedTradeAllowanceCharge', parent:,
|
|
45
|
+
represented_attributes: [:indicator]) do |node, item|
|
|
46
|
+
report_unrepresentable_attributes(group_id, item.tax) if item.tax
|
|
47
|
+
adjustment_indicator(node, group_id, indicator)
|
|
48
|
+
emit_adjustment_fields(node, item, ids)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def emit_adjustment_fields(parent, item, ids)
|
|
53
|
+
emit(ids[0], item.percentage, element: 'ram:CalculationPercent', parent:)
|
|
54
|
+
emit(ids[1], item.base_amount, element: 'ram:BasisAmount', parent:)
|
|
55
|
+
emit(ids[2], item.amount, element: 'ram:ActualAmount', parent:)
|
|
56
|
+
emit(ids[3], item.reason_code, element: 'ram:ReasonCode', parent:)
|
|
57
|
+
emit(ids[4], item.reason, element: 'ram:Reason', parent:)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def adjustment_indicator(parent, group_id, value)
|
|
61
|
+
within_group("#{group_id}-0", value, element: 'ram:ChargeIndicator', parent:) do |node, indicator|
|
|
62
|
+
within_group("#{group_id}-1", indicator, element: 'udt:Indicator', parent: node) do |leaf, raw|
|
|
63
|
+
leaf.content = raw ? 'true' : 'false'
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def monetary_sum(parent, value)
|
|
69
|
+
container(parent, 'ram:SpecifiedTradeSettlementLineMonetarySummation') do |node|
|
|
70
|
+
emit('BT-131', value, element: 'ram:LineTotalAmount', parent: node)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def line_invoiced_object(parent, value)
|
|
75
|
+
emit_invoiced_object(parent, value, 'BT-128', 'BT-128-1')
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def accounting_reference(parent, id, value)
|
|
79
|
+
return observe?(id, nil) unless value
|
|
80
|
+
|
|
81
|
+
container(parent, 'ram:ReceivableSpecifiedTradeAccountingAccount') do |node|
|
|
82
|
+
emit(id, value, element: 'ram:ID', parent: node)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Facturx
|
|
4
|
+
class Writer
|
|
5
|
+
module Stages
|
|
6
|
+
class TradeLines < Stage
|
|
7
|
+
REPRESENTED_ATTRIBUTES = %i[
|
|
8
|
+
product gross_price net_price tax period allowances charges buyer_order_reference
|
|
9
|
+
].freeze
|
|
10
|
+
|
|
11
|
+
term_ids(*%w[
|
|
12
|
+
BT-126 BT-127 BT-157 BT-157-1 BT-155 BT-156 BT-153 BT-154 BT-160 BT-161 BT-158 BT-158-1
|
|
13
|
+
BT-158-2 BT-159 BT-132 BT-148 BT-149-1 BT-150-1 BT-147-01 BT-147-02 BT-147 BT-146 BT-149
|
|
14
|
+
BT-150 BT-129 BT-130 BT-151 BT-152 BT-134 BT-135 BT-138 BT-137 BT-136 BT-140 BT-139 BT-143
|
|
15
|
+
BT-142 BT-141 BT-145 BT-144 BT-131 BT-128 BT-128-1 BT-133
|
|
16
|
+
])
|
|
17
|
+
|
|
18
|
+
def initialize(context)
|
|
19
|
+
super
|
|
20
|
+
quantity = Quantity.new(context)
|
|
21
|
+
@product = Product.new(context)
|
|
22
|
+
@agreement = Agreement.new(context, quantity:)
|
|
23
|
+
@settlement = Settlement.new(context)
|
|
24
|
+
@quantity = quantity
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def call
|
|
28
|
+
each_group('BG-25', document.lines, element: 'ram:IncludedSupplyChainTradeLineItem',
|
|
29
|
+
parent: context.transaction,
|
|
30
|
+
represented_attributes: REPRESENTED_ATTRIBUTES) do |node, line|
|
|
31
|
+
line_document(node, line)
|
|
32
|
+
@product.call(parent: node, value: line.product)
|
|
33
|
+
@agreement.call(parent: node, line:)
|
|
34
|
+
delivery(node, line)
|
|
35
|
+
@settlement.call(parent: node, line:)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def line_document(parent, line)
|
|
42
|
+
container(parent, 'ram:AssociatedDocumentLineDocument') do |node|
|
|
43
|
+
emit('BT-126', line.id, element: 'ram:LineID', parent: node)
|
|
44
|
+
if line.note
|
|
45
|
+
container(node, 'ram:IncludedNote') do |note|
|
|
46
|
+
emit('BT-127', line.note, element: 'ram:Content', parent: note)
|
|
47
|
+
end
|
|
48
|
+
else
|
|
49
|
+
observe?('BT-127', nil)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def delivery(parent, line)
|
|
55
|
+
node = context.element(parent, 'ram:SpecifiedLineTradeDelivery')
|
|
56
|
+
@quantity.call(parent: node, value: line.quantity, amount_id: 'BT-129', unit_id: 'BT-130',
|
|
57
|
+
element: 'ram:BilledQuantity')
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
require_relative 'trade_lines/quantity'
|
|
65
|
+
require_relative 'trade_lines/product'
|
|
66
|
+
require_relative 'trade_lines/agreement'
|
|
67
|
+
require_relative 'trade_lines/settlement'
|