facturx 0.2.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +43 -3
  3. data/DOCUMENTATION.md +271 -0
  4. data/README.md +79 -69
  5. data/lib/facturx/attach.rb +4 -20
  6. data/lib/facturx/builders/association.rb +8 -0
  7. data/lib/facturx/builders/base.rb +129 -0
  8. data/lib/facturx/builders/schema.rb +112 -0
  9. data/lib/facturx/builders.rb +25 -0
  10. data/lib/facturx/error.rb +7 -1
  11. data/lib/facturx/format.rb +85 -0
  12. data/lib/facturx/generate.rb +21 -0
  13. data/lib/facturx/pdf/composer.rb +32 -0
  14. data/lib/facturx/profile.rb +1 -1
  15. data/lib/facturx/profile_resolver.rb +40 -0
  16. data/lib/facturx/profiles.rb +1 -8
  17. data/lib/facturx/reader/semantic_mapper/line_mapping.rb +1 -1
  18. data/lib/facturx/reader/semantic_mapper/line_references.rb +19 -0
  19. data/lib/facturx/reader/semantic_mapper/payment_mapping.rb +48 -11
  20. data/lib/facturx/reader/semantic_mapper.rb +9 -0
  21. data/lib/facturx/reader/term_reader.rb +12 -4
  22. data/lib/facturx/reader.rb +3 -7
  23. data/lib/facturx/source_reader.rb +1 -1
  24. data/lib/facturx/validation.rb +61 -0
  25. data/lib/facturx/version.rb +1 -1
  26. data/lib/facturx/writer/context.rb +53 -0
  27. data/lib/facturx/writer/stage.rb +47 -0
  28. data/lib/facturx/writer/stage_support/contact_details.rb +57 -0
  29. data/lib/facturx/writer/stage_support/emission.rb +78 -0
  30. data/lib/facturx/writer/stage_support/identifiers.rb +129 -0
  31. data/lib/facturx/writer/stage_support/structure.rb +88 -0
  32. data/lib/facturx/writer/stages/document_context.rb +31 -0
  33. data/lib/facturx/writer/stages/exchanged_document.rb +32 -0
  34. data/lib/facturx/writer/stages/trade_agreement/parties.rb +105 -0
  35. data/lib/facturx/writer/stages/trade_agreement/references.rb +92 -0
  36. data/lib/facturx/writer/stages/trade_agreement.rb +45 -0
  37. data/lib/facturx/writer/stages/trade_delivery.rb +77 -0
  38. data/lib/facturx/writer/stages/trade_lines/agreement.rb +92 -0
  39. data/lib/facturx/writer/stages/trade_lines/product.rb +78 -0
  40. data/lib/facturx/writer/stages/trade_lines/quantity.rb +17 -0
  41. data/lib/facturx/writer/stages/trade_lines/settlement.rb +89 -0
  42. data/lib/facturx/writer/stages/trade_lines.rb +67 -0
  43. data/lib/facturx/writer/stages/trade_settlement/parties.rb +33 -0
  44. data/lib/facturx/writer/stages/trade_settlement/payment.rb +97 -0
  45. data/lib/facturx/writer/stages/trade_settlement/summary.rb +88 -0
  46. data/lib/facturx/writer/stages/trade_settlement/taxes.rb +121 -0
  47. data/lib/facturx/writer/stages/trade_settlement.rb +70 -0
  48. data/lib/facturx/writer/tracker.rb +131 -0
  49. data/lib/facturx/writer.rb +76 -0
  50. data/lib/facturx/xml/namespaces.rb +19 -0
  51. data/lib/facturx/xml/parser.rb +1 -1
  52. data/lib/facturx/xml/profile_detector.rb +2 -4
  53. data/lib/facturx/xml/report_builder.rb +44 -0
  54. data/lib/facturx/xml/schema_registry.rb +22 -0
  55. data/lib/facturx/xml/schema_validator.rb +7 -2
  56. data/lib/facturx/xml/validator.rb +32 -0
  57. data/lib/facturx/xml/verifier.rb +16 -11
  58. data/lib/facturx.rb +41 -4
  59. data/rbi/facturx.rbi +1755 -0
  60. metadata +44 -4
@@ -0,0 +1,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'