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.
- checksums.yaml +7 -0
- data/.rspec +2 -0
- data/Gemfile +8 -0
- data/LICENSE +21 -0
- data/README.md +60 -0
- data/Rakefile +4 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/lib/zatca/client.rb +211 -0
- data/lib/zatca/hacks.rb +45 -0
- data/lib/zatca/hashing.rb +18 -0
- data/lib/zatca/qr_code_extractor.rb +31 -0
- data/lib/zatca/qr_code_generator.rb +28 -0
- data/lib/zatca/signing/certificate.rb +78 -0
- data/lib/zatca/signing/csr.rb +220 -0
- data/lib/zatca/signing/ecdsa.rb +59 -0
- data/lib/zatca/tag.rb +44 -0
- data/lib/zatca/tags.rb +46 -0
- data/lib/zatca/tags_schema.rb +22 -0
- data/lib/zatca/types.rb +7 -0
- data/lib/zatca/ubl/base_component.rb +142 -0
- data/lib/zatca/ubl/builder.rb +166 -0
- data/lib/zatca/ubl/common_aggregate_components/allowance_charge.rb +64 -0
- data/lib/zatca/ubl/common_aggregate_components/classified_tax_category.rb +25 -0
- data/lib/zatca/ubl/common_aggregate_components/delivery.rb +27 -0
- data/lib/zatca/ubl/common_aggregate_components/invoice_line.rb +63 -0
- data/lib/zatca/ubl/common_aggregate_components/item.rb +21 -0
- data/lib/zatca/ubl/common_aggregate_components/legal_monetary_total.rb +59 -0
- data/lib/zatca/ubl/common_aggregate_components/party.rb +28 -0
- data/lib/zatca/ubl/common_aggregate_components/party_identification.rb +25 -0
- data/lib/zatca/ubl/common_aggregate_components/party_legal_entity.rb +19 -0
- data/lib/zatca/ubl/common_aggregate_components/party_tax_scheme.rb +30 -0
- data/lib/zatca/ubl/common_aggregate_components/postal_address.rb +59 -0
- data/lib/zatca/ubl/common_aggregate_components/price.rb +20 -0
- data/lib/zatca/ubl/common_aggregate_components/tax_category.rb +56 -0
- data/lib/zatca/ubl/common_aggregate_components/tax_total.rb +58 -0
- data/lib/zatca/ubl/common_aggregate_components.rb +2 -0
- data/lib/zatca/ubl/invoice.rb +481 -0
- data/lib/zatca/ubl/invoice_subtype_builder.rb +50 -0
- data/lib/zatca/ubl/signing/cert.rb +48 -0
- data/lib/zatca/ubl/signing/invoice_signed_data_reference.rb +44 -0
- data/lib/zatca/ubl/signing/key_info.rb +25 -0
- data/lib/zatca/ubl/signing/object.rb +20 -0
- data/lib/zatca/ubl/signing/qualifying_properties.rb +27 -0
- data/lib/zatca/ubl/signing/signature.rb +50 -0
- data/lib/zatca/ubl/signing/signature_information.rb +19 -0
- data/lib/zatca/ubl/signing/signature_properties_reference.rb +26 -0
- data/lib/zatca/ubl/signing/signed_info.rb +21 -0
- data/lib/zatca/ubl/signing/signed_properties.rb +81 -0
- data/lib/zatca/ubl/signing/signed_signature_properties.rb +23 -0
- data/lib/zatca/ubl/signing/ubl_document_signatures.rb +25 -0
- data/lib/zatca/ubl/signing/ubl_extension.rb +22 -0
- data/lib/zatca/ubl/signing/ubl_extensions.rb +17 -0
- data/lib/zatca/ubl/signing.rb +2 -0
- data/lib/zatca/ubl.rb +2 -0
- data/lib/zatca/version.rb +5 -0
- data/lib/zatca.rb +48 -0
- data/zatca_sdk.gemspec +52 -0
- 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
|