zatca-sdk 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 (59) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +2 -0
  3. data/Gemfile +8 -0
  4. data/LICENSE +21 -0
  5. data/README.md +60 -0
  6. data/Rakefile +4 -0
  7. data/bin/console +15 -0
  8. data/bin/setup +8 -0
  9. data/lib/zatca/client.rb +211 -0
  10. data/lib/zatca/hacks.rb +45 -0
  11. data/lib/zatca/hashing.rb +18 -0
  12. data/lib/zatca/qr_code_extractor.rb +31 -0
  13. data/lib/zatca/qr_code_generator.rb +28 -0
  14. data/lib/zatca/signing/certificate.rb +78 -0
  15. data/lib/zatca/signing/csr.rb +220 -0
  16. data/lib/zatca/signing/ecdsa.rb +59 -0
  17. data/lib/zatca/tag.rb +44 -0
  18. data/lib/zatca/tags.rb +46 -0
  19. data/lib/zatca/tags_schema.rb +22 -0
  20. data/lib/zatca/types.rb +7 -0
  21. data/lib/zatca/ubl/base_component.rb +142 -0
  22. data/lib/zatca/ubl/builder.rb +166 -0
  23. data/lib/zatca/ubl/common_aggregate_components/allowance_charge.rb +64 -0
  24. data/lib/zatca/ubl/common_aggregate_components/classified_tax_category.rb +25 -0
  25. data/lib/zatca/ubl/common_aggregate_components/delivery.rb +27 -0
  26. data/lib/zatca/ubl/common_aggregate_components/invoice_line.rb +63 -0
  27. data/lib/zatca/ubl/common_aggregate_components/item.rb +21 -0
  28. data/lib/zatca/ubl/common_aggregate_components/legal_monetary_total.rb +59 -0
  29. data/lib/zatca/ubl/common_aggregate_components/party.rb +28 -0
  30. data/lib/zatca/ubl/common_aggregate_components/party_identification.rb +25 -0
  31. data/lib/zatca/ubl/common_aggregate_components/party_legal_entity.rb +19 -0
  32. data/lib/zatca/ubl/common_aggregate_components/party_tax_scheme.rb +30 -0
  33. data/lib/zatca/ubl/common_aggregate_components/postal_address.rb +59 -0
  34. data/lib/zatca/ubl/common_aggregate_components/price.rb +20 -0
  35. data/lib/zatca/ubl/common_aggregate_components/tax_category.rb +56 -0
  36. data/lib/zatca/ubl/common_aggregate_components/tax_total.rb +58 -0
  37. data/lib/zatca/ubl/common_aggregate_components.rb +2 -0
  38. data/lib/zatca/ubl/invoice.rb +481 -0
  39. data/lib/zatca/ubl/invoice_subtype_builder.rb +50 -0
  40. data/lib/zatca/ubl/signing/cert.rb +48 -0
  41. data/lib/zatca/ubl/signing/invoice_signed_data_reference.rb +44 -0
  42. data/lib/zatca/ubl/signing/key_info.rb +25 -0
  43. data/lib/zatca/ubl/signing/object.rb +20 -0
  44. data/lib/zatca/ubl/signing/qualifying_properties.rb +27 -0
  45. data/lib/zatca/ubl/signing/signature.rb +50 -0
  46. data/lib/zatca/ubl/signing/signature_information.rb +19 -0
  47. data/lib/zatca/ubl/signing/signature_properties_reference.rb +26 -0
  48. data/lib/zatca/ubl/signing/signed_info.rb +21 -0
  49. data/lib/zatca/ubl/signing/signed_properties.rb +81 -0
  50. data/lib/zatca/ubl/signing/signed_signature_properties.rb +23 -0
  51. data/lib/zatca/ubl/signing/ubl_document_signatures.rb +25 -0
  52. data/lib/zatca/ubl/signing/ubl_extension.rb +22 -0
  53. data/lib/zatca/ubl/signing/ubl_extensions.rb +17 -0
  54. data/lib/zatca/ubl/signing.rb +2 -0
  55. data/lib/zatca/ubl.rb +2 -0
  56. data/lib/zatca/version.rb +5 -0
  57. data/lib/zatca.rb +48 -0
  58. data/zatca_sdk.gemspec +52 -0
  59. metadata +301 -0
@@ -0,0 +1,166 @@
1
+ class ZATCA::UBL::Builder
2
+ extend Dry::Initializer
3
+
4
+ option :element, type: ZATCA::Types.Instance(ZATCA::UBL::BaseComponent)
5
+
6
+ def build(
7
+ canonicalized: false,
8
+ spaces: 4,
9
+ apply_invoice_hacks: false,
10
+ remove_root_xml_tag: false
11
+ )
12
+ @remove_root_xml_tag = remove_root_xml_tag
13
+
14
+ builder = Nokogiri::XML::Builder.new(encoding: "UTF-8") do |xml|
15
+ element.build_xml(xml)
16
+ end
17
+
18
+ xml = if canonicalized
19
+ canonicalized_xml(builder: builder)
20
+ else
21
+ uncanonicalized_xml(builder: builder, spaces: spaces)
22
+ end
23
+
24
+ xml = apply_hacks_to_invoice(element, xml) if apply_invoice_hacks
25
+
26
+ xml
27
+ end
28
+
29
+ private
30
+
31
+ # ZATCA sadly requires very specific and unconventional indentation in the XML
32
+ # when it is pretty (uncanonicalized), the only way we can accomplish this is
33
+ # to find and replace blocks manually.
34
+ def apply_hacks_to_invoice(element, xml)
35
+ return xml unless element.is_a?(ZATCA::UBL::Invoice)
36
+
37
+ apply_qualifying_properties_hacks(element, xml)
38
+ end
39
+
40
+ def apply_qualifying_properties_hacks(invoice, xml)
41
+ return xml if invoice.qualifying_properties.blank?
42
+
43
+ regex = ZATCA::Hacks.qualifying_properties_regex
44
+
45
+ xml.gsub(regex, invoice.qualifying_properties)
46
+ end
47
+
48
+ # This function does not produce canonicalization matching C14N 1.1, it applies
49
+ # C14N 1.1 then manually adds back the whitespace in the format that ZATCA
50
+ # expects.
51
+ def canonicalized_xml(builder:)
52
+ builder.doc.canonicalize(Nokogiri::XML::XML_C14N_1_1)
53
+
54
+ # TODO: In case ZATCA ever asks us to use their whitespace format again.
55
+ # In some meetings they say we have to use it, in some meetings they say
56
+ # we don't. The simpler approach is that we don't use it.
57
+ #
58
+ # ZATCA's docs specifically state we must use C14N 1.1 canonicalization.
59
+ # xml = uncanonicalized_xml(builder: builder, spaces: 4)
60
+ # xml_doc = Nokogiri::XML(xml)
61
+
62
+ # canonical_xml = xml_doc.canonicalize(Nokogiri::XML::XML_C14N_1_1)
63
+
64
+ # canonical_xml
65
+ end
66
+
67
+ def uncanonicalized_xml(builder:, spaces: 4)
68
+ builder.to_xml(indent: spaces.to_i)
69
+
70
+ # xml = builder.to_xml(indent: spaces.to_i)
71
+ # xml = match_xml_string_to_zatca_whitespaces(xml)
72
+ # xml
73
+ end
74
+
75
+ def match_xml_string_to_zatca_whitespaces(xml)
76
+ # ZATCA has elements that are not spaced by multiples of 4, and random new
77
+ # lines with trailing whitespaces, so we need to manually adjust our
78
+ # indentation to match ZATCA's.
79
+ zatca_weird_whitespaces.each do |whitespace_hash|
80
+ xml.gsub!(whitespace_hash[:our_version], whitespace_hash[:zatca_version])
81
+ end
82
+
83
+ # Canonicalization already removes the root XML tag for us, but since we had
84
+ # to create a new uncanonicalized document for ZATCA's invoice hacks, we
85
+ # have to remove it manually.
86
+ if @remove_root_xml_tag
87
+ xml.gsub!("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n", "")
88
+ end
89
+
90
+ # ZATCA Removes the final newline character, so we do the same
91
+ xml.chomp
92
+
93
+ # This part is not clear, ZATCA shared documents with us that use CRLF
94
+ # but the samples in the SDK use LF, so we're not sure which one is correct.
95
+ # ZATCA wants CRLF (\r\n) in their canonicalized form instead of just LF (\n)
96
+ xml.gsub!("\n", "\r\n")
97
+
98
+ xml
99
+ end
100
+
101
+ # Not sure if this is needed, in some meetings ZATCA says you have to match
102
+ # their whitspace exactly and in some meetings they say you don't.
103
+ # HACK: This is really hacky, using regexes or XPaths would be better, but
104
+ # that wasn't easy to build and maintain, so we're using this if/until we run
105
+ # into issues.
106
+ #
107
+ # We may eventually go to an approach where we just have hardcoded XML in each
108
+ # element's file and we just add in the values instead of generating our own
109
+ # XML if this gets too hard to maintain.
110
+ def zatca_weird_whitespaces
111
+ @_zatca_weird_whitespaces ||= [
112
+ {
113
+ our_version: "<cbc:ProfileID>",
114
+ zatca_version: "\n <cbc:ProfileID>"
115
+ },
116
+ {
117
+ our_version: "<cac:AccountingSupplierParty>",
118
+ zatca_version: "\n \n <cac:AccountingSupplierParty>"
119
+ },
120
+ {
121
+ our_version: " <cbc:CompanyID>",
122
+ zatca_version: " <cbc:CompanyID>"
123
+ },
124
+ {
125
+ our_version: " <cac:TaxCategory>",
126
+ zatca_version: " <cac:TaxCategory>"
127
+ },
128
+ {
129
+ our_version: " </cac:TaxCategory>",
130
+ zatca_version: " </cac:TaxCategory>"
131
+ },
132
+ {
133
+ our_version: ' <cbc:ID schemeAgencyID="6" schemeID="UN/ECE 5305">S</cbc:ID>',
134
+ zatca_version: ' <cbc:ID schemeAgencyID="6" schemeID="UN/ECE 5305">S</cbc:ID>'
135
+ },
136
+ {
137
+ our_version: "<cac:TaxScheme>\n <cbc:ID schemeAgencyID=\"6\" schemeID=\"UN/ECE 5153\">VAT</cbc:ID>",
138
+ zatca_version: "<cac:TaxScheme>\n <cbc:ID schemeAgencyID=\"6\" schemeID=\"UN/ECE 5153\">VAT</cbc:ID>"
139
+ },
140
+ {
141
+ our_version: "<cac:TaxTotal>\n <cbc:TaxAmount currencyID=\"SAR\">",
142
+ zatca_version: "<cac:TaxTotal>\n <cbc:TaxAmount currencyID=\"SAR\">"
143
+ },
144
+ {
145
+ our_version: " <cbc:RoundingAmount currencyID=\"SAR\">",
146
+ zatca_version: " <cbc:RoundingAmount currencyID=\"SAR\">"
147
+ },
148
+ {
149
+ our_version: " <cbc:ChargeIndicator>",
150
+ zatca_version: " <cbc:ChargeIndicator>"
151
+ },
152
+ {
153
+ our_version: " <cbc:AllowanceChargeReason>",
154
+ zatca_version: " <cbc:AllowanceChargeReason>"
155
+ },
156
+ {
157
+ our_version: " <cbc:Amount currencyID=\"SAR\">",
158
+ zatca_version: " <cbc:Amount currencyID=\"SAR\">"
159
+ },
160
+ {
161
+ our_version: "<cbc:ID schemeAgencyID=\"6\" schemeID=\"UN/ECE 5305\">S</cbc:ID>\n <cbc:Percent>",
162
+ zatca_version: "<cbc:ID schemeAgencyID=\"6\" schemeID=\"UN/ECE 5305\">S</cbc:ID>\n <cbc:Percent>"
163
+ }
164
+ ]
165
+ end
166
+ end
@@ -0,0 +1,64 @@
1
+ class ZATCA::UBL::CommonAggregateComponents::AllowanceCharge < ZATCA::UBL::BaseComponent
2
+ attr_accessor :id, :charge_indicator, :allowance_charge_reason, :amount
3
+
4
+ # <cac:AllowanceCharge>
5
+ # <cbc:ID>1</cbc:ID>
6
+ # <cbc:ChargeIndicator>false</cbc:ChargeIndicator>
7
+ # <cbc:AllowanceChargeReason>discount</cbc:AllowanceChargeReason>
8
+ # <cbc:Amount currencyID="SAR">2</cbc:Amount>
9
+ # <cac:TaxCategory>
10
+ # <cbc:ID schemeAgencyID="6" schemeID="UN/ECE 5305">S</cbc:ID>
11
+ # <cbc:Percent>15</cbc:Percent>
12
+ # <cac:TaxScheme>
13
+ # <cbc:ID schemeAgencyID="6" schemeID="UN/ECE 5153">VAT</cbc:ID>
14
+ # </cac:TaxScheme>
15
+ # </cac:TaxCategory>
16
+ # </cac:AllowanceCharge>
17
+
18
+ def initialize(
19
+ charge_indicator:, allowance_charge_reason:, amount:, currency_id: "SAR",
20
+ tax_category: nil, add_tax_category: true, add_id: true, tax_categories: []
21
+ )
22
+ super()
23
+
24
+ @charge_indicator = charge_indicator.to_s
25
+
26
+ @allowance_charge_reason = allowance_charge_reason
27
+ @amount = amount
28
+ @currency_id = currency_id
29
+
30
+ @add_tax_category = add_tax_category
31
+ @tax_category = tax_category
32
+ @add_id = add_id
33
+
34
+ if add_tax_category && @tax_category.blank?
35
+ @tax_category = ZATCA::UBL::CommonAggregateComponents::TaxCategory.new
36
+ end
37
+
38
+ @tax_categories = if @tax_category.present? && tax_categories.blank?
39
+ [@tax_category]
40
+ else
41
+ tax_categories
42
+ end
43
+ end
44
+
45
+ def name
46
+ "cac:AllowanceCharge"
47
+ end
48
+
49
+ def id_element
50
+ if @add_id && index.present?
51
+ ZATCA::UBL::BaseComponent.new(name: "cbc:ID", value: index)
52
+ end
53
+ end
54
+
55
+ def elements
56
+ [
57
+ id_element,
58
+ ZATCA::UBL::BaseComponent.new(name: "cbc:ChargeIndicator", value: @charge_indicator),
59
+ ZATCA::UBL::BaseComponent.new(name: "cbc:AllowanceChargeReason", value: @allowance_charge_reason),
60
+ ZATCA::UBL::BaseComponent.new(name: "cbc:Amount", value: @amount, attributes: {"currencyID" => @currency_id}),
61
+ *@tax_categories
62
+ ]
63
+ end
64
+ end
@@ -0,0 +1,25 @@
1
+ class ZATCA::UBL::CommonAggregateComponents::ClassifiedTaxCategory < ZATCA::UBL::BaseComponent
2
+ attr_reader :scheme_id, :id
3
+
4
+ def initialize(id: "S", percent: "15.00", tax_scheme_id: "VAT")
5
+ super()
6
+
7
+ @id = id
8
+ @percent = percent
9
+ @tax_scheme_id = tax_scheme_id
10
+ end
11
+
12
+ def name
13
+ "cac:ClassifiedTaxCategory"
14
+ end
15
+
16
+ def elements
17
+ [
18
+ ZATCA::UBL::BaseComponent.new(name: "cbc:ID", value: @id),
19
+ ZATCA::UBL::BaseComponent.new(name: "cbc:Percent", value: @percent),
20
+ ZATCA::UBL::BaseComponent.new(name: "cac:TaxScheme", elements: [
21
+ ZATCA::UBL::BaseComponent.new(name: "cbc:ID", value: @tax_scheme_id)
22
+ ])
23
+ ]
24
+ end
25
+ end
@@ -0,0 +1,27 @@
1
+ class ZATCA::UBL::CommonAggregateComponents::Delivery < ZATCA::UBL::BaseComponent
2
+ attr_reader :scheme_id, :id
3
+
4
+ def initialize(actual_delivery_date:, latest_delivery_date: nil)
5
+ super()
6
+
7
+ @latest_delivery_date = latest_delivery_date
8
+ @actual_delivery_date = actual_delivery_date
9
+ end
10
+
11
+ def name
12
+ "cac:Delivery"
13
+ end
14
+
15
+ def latest_delivery_date_element
16
+ return nil if @latest_delivery_date.nil?
17
+
18
+ ZATCA::UBL::BaseComponent.new(name: "cbc:LatestDeliveryDate", value: @latest_delivery_date)
19
+ end
20
+
21
+ def elements
22
+ [
23
+ ZATCA::UBL::BaseComponent.new(name: "cbc:ActualDeliveryDate", value: @actual_delivery_date),
24
+ latest_delivery_date_element
25
+ ]
26
+ end
27
+ end
@@ -0,0 +1,63 @@
1
+ class ZATCA::UBL::CommonAggregateComponents::InvoiceLine < ZATCA::UBL::BaseComponent
2
+ # <cac:InvoiceLine>
3
+ # <cbc:ID>1</cbc:ID>
4
+ # <cbc:InvoicedQuantity unitCode="PCE">44.000000</cbc:InvoicedQuantity>
5
+ # <cbc:LineExtensionAmount currencyID="SAR">966.00</cbc:LineExtensionAmount>
6
+ # <cac:TaxTotal>
7
+ # <cbc:TaxAmount currencyID="SAR">144.90</cbc:TaxAmount>
8
+ # <cbc:RoundingAmount currencyID="SAR">1110.90</cbc:RoundingAmount>
9
+
10
+ # </cac:TaxTotal>
11
+ # <cac:Item>
12
+ # <cbc:Name>dsd</cbc:Name>
13
+ # <cac:ClassifiedTaxCategory>
14
+ # <cbc:ID>S</cbc:ID>
15
+ # <cbc:Percent>15.00</cbc:Percent>
16
+ # <cac:TaxScheme>
17
+ # <cbc:ID>VAT</cbc:ID>
18
+ # </cac:TaxScheme>
19
+ # </cac:ClassifiedTaxCategory>
20
+ # </cac:Item>
21
+ # <cac:Price>
22
+ # <cbc:PriceAmount currencyID="SAR">22.00</cbc:PriceAmount>
23
+ # <cac:AllowanceCharge>
24
+ # <cbc:ChargeIndicator>false</cbc:ChargeIndicator>
25
+ # <cbc:AllowanceChargeReason>discount</cbc:AllowanceChargeReason>
26
+ # <cbc:Amount currencyID="SAR">2.00</cbc:Amount>
27
+ # </cac:AllowanceCharge>
28
+ # </cac:Price>
29
+ # </cac:InvoiceLine>
30
+
31
+ def initialize(
32
+ invoiced_quantity:, invoiced_quantity_unit_code:,
33
+ line_extension_amount:, tax_total:, item:,
34
+ price:, allowance_charge: nil, currency_id: "SAR"
35
+ )
36
+ super()
37
+
38
+ @invoiced_quantity = invoiced_quantity
39
+ @invoiced_quantity_unit_code = invoiced_quantity_unit_code
40
+ @line_extension_amount = line_extension_amount
41
+ @tax_total = tax_total
42
+ @item = item
43
+ @price = price
44
+ @allowance_charge = allowance_charge
45
+ @currency_id = currency_id
46
+ end
47
+
48
+ def name
49
+ "cac:InvoiceLine"
50
+ end
51
+
52
+ def elements
53
+ [
54
+ ZATCA::UBL::BaseComponent.new(name: "cbc:ID", value: index),
55
+ ZATCA::UBL::BaseComponent.new(name: "cbc:InvoicedQuantity", value: @invoiced_quantity, attributes: {unitCode: @invoiced_quantity_unit_code}),
56
+ ZATCA::UBL::BaseComponent.new(name: "cbc:LineExtensionAmount", value: @line_extension_amount, attributes: {currencyID: @currency_id}),
57
+ @tax_total,
58
+ @item,
59
+ @price,
60
+ @allowance_charge
61
+ ]
62
+ end
63
+ end
@@ -0,0 +1,21 @@
1
+ class ZATCA::UBL::CommonAggregateComponents::Item < ZATCA::UBL::BaseComponent
2
+ attr_reader :scheme_id, :id
3
+
4
+ def initialize(name:, classified_tax_category: nil)
5
+ super()
6
+
7
+ @name = name
8
+ @classified_tax_category = classified_tax_category || ZATCA::UBL::CommonAggregateComponents::ClassifiedTaxCategory.new
9
+ end
10
+
11
+ def name
12
+ "cac:Item"
13
+ end
14
+
15
+ def elements
16
+ [
17
+ ZATCA::UBL::BaseComponent.new(name: "cbc:Name", value: @name),
18
+ @classified_tax_category
19
+ ]
20
+ end
21
+ end
@@ -0,0 +1,59 @@
1
+ class ZATCA::UBL::CommonAggregateComponents::LegalMonetaryTotal < ZATCA::UBL::BaseComponent
2
+ attr_reader :line_extension_amount, :tax_exclusive_amount,
3
+ :tax_inclusive_amount, :allowance_total_amount, :prepaid_amount,
4
+ :payable_amount, :currency_id
5
+
6
+ # <cac:LegalMonetaryTotal>
7
+ # <cbc:LineExtensionAmount currencyID="SAR">966.00</cbc:LineExtensionAmount>
8
+ # <cbc:TaxExclusiveAmount currencyID="SAR">964.00</cbc:TaxExclusiveAmount>
9
+ # <cbc:TaxInclusiveAmount currencyID="SAR">1108.90</cbc:TaxInclusiveAmount>
10
+ # <cbc:AllowanceTotalAmount currencyID="SAR">2.00</cbc:AllowanceTotalAmount>
11
+ # <cbc:PrepaidAmount currencyID="SAR">0.00</cbc:PrepaidAmount>
12
+ # <cbc:PayableAmount currencyID="SAR">1108.90</cbc:PayableAmount>
13
+ # </cac:LegalMonetaryTotal>
14
+
15
+ def initialize(
16
+ line_extension_amount:,
17
+ tax_exclusive_amount:,
18
+ tax_inclusive_amount:,
19
+ allowance_total_amount:,
20
+ prepaid_amount: nil,
21
+ payable_amount: nil,
22
+ currency_id: "SAR"
23
+ )
24
+ @line_extension_amount = line_extension_amount
25
+ @tax_exclusive_amount = tax_exclusive_amount
26
+ @tax_inclusive_amount = tax_inclusive_amount
27
+ @allowance_total_amount = allowance_total_amount
28
+ @prepaid_amount = prepaid_amount
29
+ @payable_amount = payable_amount
30
+ @currency_id = currency_id
31
+ end
32
+
33
+ def name
34
+ "cac:LegalMonetaryTotal"
35
+ end
36
+
37
+ def prepaid_amount_element
38
+ return nil if @prepaid_amount.blank?
39
+
40
+ ZATCA::UBL::BaseComponent.new(name: "cbc:PrepaidAmount", value: @prepaid_amount, attributes: {"currencyID" => @currency_id})
41
+ end
42
+
43
+ def payable_amount_element
44
+ return nil if @payable_amount.blank?
45
+
46
+ ZATCA::UBL::BaseComponent.new(name: "cbc:PayableAmount", value: @payable_amount, attributes: {"currencyID" => @currency_id})
47
+ end
48
+
49
+ def elements
50
+ [
51
+ ZATCA::UBL::BaseComponent.new(name: "cbc:LineExtensionAmount", value: @line_extension_amount, attributes: {"currencyID" => @currency_id}),
52
+ ZATCA::UBL::BaseComponent.new(name: "cbc:TaxExclusiveAmount", value: @tax_exclusive_amount, attributes: {"currencyID" => @currency_id}),
53
+ ZATCA::UBL::BaseComponent.new(name: "cbc:TaxInclusiveAmount", value: @tax_inclusive_amount, attributes: {"currencyID" => @currency_id}),
54
+ ZATCA::UBL::BaseComponent.new(name: "cbc:AllowanceTotalAmount", value: @allowance_total_amount, attributes: {"currencyID" => @currency_id}),
55
+ prepaid_amount_element,
56
+ payable_amount_element
57
+ ]
58
+ end
59
+ end
@@ -0,0 +1,28 @@
1
+ class ZATCA::UBL::CommonAggregateComponents::Party < ZATCA::UBL::BaseComponent
2
+ # Has:
3
+ # PartyIdentification
4
+ # PostalAddress
5
+ # PartyTaxScheme
6
+ # PartyLegalEntity
7
+ def initialize(party_identification:, postal_address:, party_tax_scheme:, party_legal_entity:)
8
+ super()
9
+
10
+ @party_identification = party_identification
11
+ @postal_address = postal_address
12
+ @party_tax_scheme = party_tax_scheme
13
+ @party_legal_entity = party_legal_entity
14
+ end
15
+
16
+ def name
17
+ "cac:Party"
18
+ end
19
+
20
+ def elements
21
+ [
22
+ @party_identification,
23
+ @postal_address,
24
+ @party_tax_scheme,
25
+ @party_legal_entity
26
+ ]
27
+ end
28
+ end
@@ -0,0 +1,25 @@
1
+ class ZATCA::UBL::CommonAggregateComponents::PartyIdentification < ZATCA::UBL::BaseComponent
2
+ attr_reader :scheme_id, :id
3
+
4
+ SCHEMA = Dry::Schema.Params do
5
+ required(:id).filled(:string)
6
+ required(:scheme_id).filled(:string)
7
+ end
8
+
9
+ def initialize(id:, scheme_id: "CRN")
10
+ super()
11
+
12
+ @id = id
13
+ @scheme_id = scheme_id
14
+ end
15
+
16
+ def name
17
+ "cac:PartyIdentification"
18
+ end
19
+
20
+ def elements
21
+ [
22
+ ZATCA::UBL::BaseComponent.new(name: "cbc:ID", value: @id, attributes: {"schemeID" => @scheme_id})
23
+ ]
24
+ end
25
+ end
@@ -0,0 +1,19 @@
1
+ class ZATCA::UBL::CommonAggregateComponents::PartyLegalEntity < ZATCA::UBL::BaseComponent
2
+ attr_reader :scheme_id, :id
3
+
4
+ def initialize(registration_name:)
5
+ super()
6
+
7
+ @registration_name = registration_name
8
+ end
9
+
10
+ def name
11
+ "cac:PartyLegalEntity"
12
+ end
13
+
14
+ def elements
15
+ [
16
+ ZATCA::UBL::BaseComponent.build(name: "cbc:RegistrationName", value: @registration_name)
17
+ ]
18
+ end
19
+ end
@@ -0,0 +1,30 @@
1
+ class ZATCA::UBL::CommonAggregateComponents::PartyTaxScheme < ZATCA::UBL::BaseComponent
2
+ def initialize(
3
+ company_id: nil,
4
+ tax_scheme_id: "VAT"
5
+ )
6
+ super()
7
+
8
+ @company_id = company_id
9
+ @tax_scheme_id = tax_scheme_id
10
+ end
11
+
12
+ def name
13
+ "cac:PartyTaxScheme"
14
+ end
15
+
16
+ def company_id_element
17
+ return nil if @company_id.blank?
18
+
19
+ ZATCA::UBL::BaseComponent.new(name: "cbc:CompanyID", value: @company_id)
20
+ end
21
+
22
+ def elements
23
+ [
24
+ company_id_element,
25
+ ZATCA::UBL::BaseComponent.new(name: "cac:TaxScheme", elements: [
26
+ ZATCA::UBL::BaseComponent.new(name: "cbc:ID", value: @tax_scheme_id)
27
+ ])
28
+ ]
29
+ end
30
+ end
@@ -0,0 +1,59 @@
1
+ class ZATCA::UBL::CommonAggregateComponents::PostalAddress < ZATCA::UBL::BaseComponent
2
+ attr_accessor :street_name,
3
+ :additional_street_name,
4
+ :building_number,
5
+ :plot_identification,
6
+ :city_subdivision_name,
7
+ :city_name,
8
+ :postal_zone,
9
+ :country_subentity,
10
+ :country
11
+
12
+ SCHEMA = Dry::Schema.Params do
13
+ required(:street_name).filled(:string)
14
+ required(:additional_street_name).filled(:string)
15
+ required(:building_number).filled(:string)
16
+ required(:plot_identification).filled(:string)
17
+ required(:city_subdivision_name).filled(:string)
18
+ required(:city_name).filled(:string)
19
+ required(:postal_zone).filled(:string)
20
+ required(:country_subentity).filled(:string)
21
+ required(:country_identification_code).filled(:string)
22
+ end
23
+
24
+ def initialize(
25
+ street_name:, building_number:, plot_identification:,
26
+ city_subdivision_name:, city_name:, postal_zone:, country_subentity:,
27
+ additional_street_name: nil, country_identification_code: "SA"
28
+ )
29
+ @street_name = street_name
30
+ @additional_street_name = additional_street_name
31
+ @building_number = building_number
32
+ @plot_identification = plot_identification
33
+ @city_subdivision_name = city_subdivision_name
34
+ @city_name = city_name
35
+ @postal_zone = postal_zone
36
+ @country_subentity = country_subentity
37
+ @country_identification_code = country_identification_code
38
+ end
39
+
40
+ def name
41
+ "cac:PostalAddress"
42
+ end
43
+
44
+ def elements
45
+ [
46
+ ZATCA::UBL::BaseComponent.build(name: "cbc:StreetName", value: @street_name),
47
+ ZATCA::UBL::BaseComponent.build(name: "cbc:AdditionalStreetName", value: @additional_street_name),
48
+ ZATCA::UBL::BaseComponent.build(name: "cbc:BuildingNumber", value: @building_number),
49
+ ZATCA::UBL::BaseComponent.build(name: "cbc:PlotIdentification", value: @plot_identification),
50
+ ZATCA::UBL::BaseComponent.build(name: "cbc:CitySubdivisionName", value: @city_subdivision_name),
51
+ ZATCA::UBL::BaseComponent.build(name: "cbc:CityName", value: @city_name),
52
+ ZATCA::UBL::BaseComponent.build(name: "cbc:PostalZone", value: @postal_zone),
53
+ ZATCA::UBL::BaseComponent.build(name: "cbc:CountrySubentity", value: @country_subentity),
54
+ ZATCA::UBL::BaseComponent.build(name: "cac:Country", elements: [
55
+ ZATCA::UBL::BaseComponent.build(name: "cbc:IdentificationCode", value: @country_identification_code)
56
+ ])
57
+ ]
58
+ end
59
+ end
@@ -0,0 +1,20 @@
1
+ class ZATCA::UBL::CommonAggregateComponents::Price < ZATCA::UBL::BaseComponent
2
+ def initialize(price_amount:, allowance_charge: nil, currency_id: "SAR")
3
+ super()
4
+
5
+ @price_amount = price_amount
6
+ @allowance_charge = allowance_charge
7
+ @currency_id = currency_id
8
+ end
9
+
10
+ def name
11
+ "cac:Price"
12
+ end
13
+
14
+ def elements
15
+ [
16
+ ZATCA::UBL::BaseComponent.new(name: "cbc:PriceAmount", value: @price_amount, attributes: {currencyID: @currency_id}),
17
+ @allowance_charge
18
+ ]
19
+ end
20
+ end
@@ -0,0 +1,56 @@
1
+ class ZATCA::UBL::CommonAggregateComponents::TaxCategory < ZATCA::UBL::BaseComponent
2
+ attr_reader :id, :percent
3
+
4
+ # <cac:TaxCategory>
5
+ # <cbc:ID schemeAgencyID="6" schemeID="UN/ECE 5305">S</cbc:ID>
6
+ # <cbc:Percent>15.00</cbc:Percent>
7
+ # <cac:TaxScheme>
8
+ # <cbc:ID schemeAgencyID="6" schemeID="UN/ECE 5153">VAT</cbc:ID>
9
+ # </cac:TaxScheme>
10
+ # </cac:TaxCategory>
11
+ # <cac:TaxCategory>
12
+ # <cbc:ID>O</cbc:ID>
13
+ # <cbc:TaxExemptionReasonCode>VATEX-EU-O</cbc:TaxExemptionReasonCode>
14
+ # <cac:TaxScheme>
15
+ # <cbc:ID>VAT</cbc:ID>
16
+ # </cac:TaxScheme>
17
+ # </cac:TaxCategory>
18
+ def initialize(
19
+ tax_percent: "15.00", id: "S", scheme_agency_id: "6",
20
+ scheme_id: "UN/ECE 5305", tax_scheme_id: "VAT",
21
+ tax_scheme_scheme_id: "UN/ECE 5153", tax_exemption_reason_code: nil
22
+ )
23
+ super()
24
+
25
+ @tax_percent = tax_percent
26
+ @id = id
27
+ @scheme_agency_id = scheme_agency_id
28
+ @scheme_id = scheme_id
29
+ @tax_scheme_id = tax_scheme_id
30
+ @tax_scheme_scheme_id = tax_scheme_scheme_id
31
+ @tax_exemption_reason_code = tax_exemption_reason_code
32
+ end
33
+
34
+ def name
35
+ "cac:TaxCategory"
36
+ end
37
+
38
+ def elements
39
+ [
40
+ ZATCA::UBL::BaseComponent.new(name: "cbc:ID", value: @id, attributes: {"schemeAgencyID" => @scheme_agency_id, "schemeID" => @scheme_id}.compact),
41
+ tax_exemption_reason_code_element,
42
+ ZATCA::UBL::BaseComponent.new(name: "cbc:Percent", value: @tax_percent),
43
+ ZATCA::UBL::BaseComponent.new(name: "cac:TaxScheme", elements: [
44
+ ZATCA::UBL::BaseComponent.new(name: "cbc:ID", value: @tax_scheme_id, attributes: {"schemeAgencyID" => @scheme_agency_id, "schemeID" => @tax_scheme_scheme_id}.compact)
45
+ ])
46
+ ]
47
+ end
48
+
49
+ private
50
+
51
+ def tax_exemption_reason_code_element
52
+ if @tax_exemption_reason_code.present?
53
+ ZATCA::UBL::BaseComponent.new(name: "cbc:TaxExemptionReasonCode", value: @tax_exemption_reason_code)
54
+ end
55
+ end
56
+ end