facturacr 0.1.2
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/.DS_Store +0 -0
- data/.gitignore +9 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +7 -0
- data/README.md +181 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/bin/signer/signer.jar +0 -0
- data/config/config.yml +10 -0
- data/exe/facturacr +3 -0
- data/facturacr.gemspec +31 -0
- data/lib/facturacr.rb +37 -0
- data/lib/facturacr/api.rb +68 -0
- data/lib/facturacr/api/document_status.rb +36 -0
- data/lib/facturacr/builder.rb +223 -0
- data/lib/facturacr/cli.rb +116 -0
- data/lib/facturacr/cli/generate.rb +133 -0
- data/lib/facturacr/configuration.rb +51 -0
- data/lib/facturacr/credit_note.rb +33 -0
- data/lib/facturacr/debit_note.rb +33 -0
- data/lib/facturacr/document.rb +171 -0
- data/lib/facturacr/document/code.rb +4 -0
- data/lib/facturacr/document/exoneration.rb +49 -0
- data/lib/facturacr/document/fax.rb +24 -0
- data/lib/facturacr/document/identification_document.rb +41 -0
- data/lib/facturacr/document/issuer.rb +56 -0
- data/lib/facturacr/document/item.rb +67 -0
- data/lib/facturacr/document/location.rb +49 -0
- data/lib/facturacr/document/phone.rb +22 -0
- data/lib/facturacr/document/phone_type.rb +37 -0
- data/lib/facturacr/document/receiver.rb +59 -0
- data/lib/facturacr/document/reference.rb +46 -0
- data/lib/facturacr/document/regulation.rb +26 -0
- data/lib/facturacr/document/summary.rb +64 -0
- data/lib/facturacr/document/tax.rb +44 -0
- data/lib/facturacr/invoice.rb +34 -0
- data/lib/facturacr/signed_document.rb +19 -0
- data/lib/facturacr/signer/signer.rb +245 -0
- data/lib/facturacr/ticket.rb +34 -0
- data/lib/facturacr/version.rb +3 -0
- data/lib/facturacr/xml_document.rb +161 -0
- data/resources/FacturaElectronica_V.4.2.xsd +1571 -0
- data/resources/NotaCreditoElectronica_V4.2.xsd +1657 -0
- data/resources/credit_note.xml +86 -0
- data/resources/data.yml +73 -0
- data/resources/debit_note.xml +86 -0
- data/resources/invoice.xml +94 -0
- data/resources/pruebas.xml +37 -0
- data/resources/test.p12 +0 -0
- metadata +235 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
module FE
|
4
|
+
class SignedDocument
|
5
|
+
attr_accessor :document, :path, :base64, :payload
|
6
|
+
|
7
|
+
def initialize(document, path)
|
8
|
+
@document = document
|
9
|
+
@path = path
|
10
|
+
@base64 = nil
|
11
|
+
if File.exist?(@path)
|
12
|
+
file = File.open(@path,"rb")
|
13
|
+
@base64 = Base64.encode64(file.read).gsub("\n","");
|
14
|
+
end
|
15
|
+
@payload = document.api_payload
|
16
|
+
@payload[:comprobanteXml] = @base64
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,245 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'base64'
|
3
|
+
require "rexml/document"
|
4
|
+
require "rexml/xpath"
|
5
|
+
|
6
|
+
module FE
|
7
|
+
class Signer
|
8
|
+
REXML::Document::entity_expansion_limit = 0
|
9
|
+
C14N = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315" #"http://www.w3.org/2001/10/xml-exc-c14n#"
|
10
|
+
DSIG = "http://www.w3.org/2000/09/xmldsig#"
|
11
|
+
NOKOGIRI_OPTIONS = Nokogiri::XML::ParseOptions::STRICT | Nokogiri::XML::ParseOptions::NONET | Nokogiri::XML::ParseOptions::NOENT
|
12
|
+
RSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
|
13
|
+
RSA_SHA256 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
|
14
|
+
RSA_SHA384 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384"
|
15
|
+
RSA_SHA512 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512"
|
16
|
+
SHA1 = "http://www.w3.org/2000/09/xmldsig#sha1"
|
17
|
+
SHA256 = "http://www.w3.org/2001/04/xmlenc#sha256"
|
18
|
+
SHA384 = "http://www.w3.org/2001/04/xmldsig-more#sha384"
|
19
|
+
SHA512 = "http://www.w3.org/2001/04/xmlenc#sha512"
|
20
|
+
ENVELOPED_SIG = "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
|
21
|
+
INC_PREFIX_LIST = "#default samlp saml ds xs xsi md"
|
22
|
+
NAMESPACES = "#default ds xs xsi xades xsd"
|
23
|
+
|
24
|
+
XADES = "http://uri.etsi.org/01903/v1.3.2#"
|
25
|
+
XADES141 = "http://uri.etsi.org/01903/v1.4.1#"
|
26
|
+
SIGNATURE_POLICY = "https://tribunet.hacienda.go.cr/docs/esquemas/2016/v4/Resolucion%20Comprobantes%20Electronicos%20%20DGT-R-48-2016.pdf"
|
27
|
+
|
28
|
+
|
29
|
+
def initialize(key_path, key_password,input_xml, output_path=nil)
|
30
|
+
@doc = REXML::Document.new(File.read(input_xml))
|
31
|
+
@doc.context[:attribute_quote] = :quote
|
32
|
+
@doc << REXML::XMLDecl.new(REXML::XMLDecl::DEFAULT_VERSION,REXML::XMLDecl::DEFAULT_ENCODING, REXML::XMLDecl::DEFAULT_STANDALONE)
|
33
|
+
@p12 = OpenSSL::PKCS12.new(File.read("tmp/pruebas.p12"),"8753")
|
34
|
+
@x509 = @p12.certificate
|
35
|
+
@output_path = output_path
|
36
|
+
end
|
37
|
+
|
38
|
+
def sign
|
39
|
+
#Build parts for Digest Calculation
|
40
|
+
key_info = build_key_info_element
|
41
|
+
signed_properties = build_signed_properties_element
|
42
|
+
signed_info_element = build_signed_info_element(key_info,signed_properties)
|
43
|
+
# Compute Signature
|
44
|
+
signed_info_canon = canonicalize_document(signed_info_element)
|
45
|
+
signature_value = compute_signature(@p12.key,algorithm(RSA_SHA256).new,signed_info_canon)
|
46
|
+
|
47
|
+
# delete parts namespaces
|
48
|
+
delete_namespaces(signed_info_element)
|
49
|
+
delete_namespaces(key_info)
|
50
|
+
delete_namespaces(signed_properties)
|
51
|
+
|
52
|
+
# Created Signature element and add parts
|
53
|
+
signature_element = REXML::Element.new("ds:Signature").add_namespace('ds', DSIG)
|
54
|
+
signature_element.add_attribute("Id","xmldsig-#{uuid}")
|
55
|
+
|
56
|
+
signature_element.add_element(signed_info_element)
|
57
|
+
signature_element.add_element("ds:SignatureValue","Id"=>"xmldsig-#{uuid}-sigvalue").text = signature_value
|
58
|
+
signature_element.add_element(key_info)
|
59
|
+
|
60
|
+
object = signature_element.add_element("ds:Object")
|
61
|
+
qualifying_properties = object.add_element("xades:QualifyingProperties", {"Target"=>"#xmldsig-#{uuid}"})
|
62
|
+
qualifying_properties.add_namespace("xades", XADES)
|
63
|
+
qualifying_properties.add_namespace("xades141", XADES141)
|
64
|
+
|
65
|
+
qualifying_properties.add_element(signed_properties)
|
66
|
+
|
67
|
+
@doc.root.add_element(signature_element)
|
68
|
+
|
69
|
+
File.open(@output_path,"w"){|f| f.write(@doc.to_s)} if @output_path
|
70
|
+
|
71
|
+
@doc
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def build_signed_properties_element
|
77
|
+
cert_digest = compute_digest(@x509.to_der,algorithm(SHA256))
|
78
|
+
policy_digest = compute_digest(@x509.to_der,algorithm(SHA256))
|
79
|
+
signing_time = DateTime.now
|
80
|
+
|
81
|
+
element = REXML::Element.new("xades:SignedProperties",nil,{:attribute_quote=>:quote})
|
82
|
+
element.add_namespace("https://tribunet.hacienda.go.cr/docs/esquemas/2017/v4.2/facturaElectronica")
|
83
|
+
element.add_namespace("ds","http://www.w3.org/2000/09/xmldsig#")
|
84
|
+
element.add_namespace("xades","http://uri.etsi.org/01903/v1.3.2#")
|
85
|
+
element.add_namespace("xsd","http://www.w3.org/2001/XMLSchema")
|
86
|
+
element.add_namespace("xsi","http://www.w3.org/2001/XMLSchema-instance")
|
87
|
+
element.add_namespace("xades141", XADES141)
|
88
|
+
element.add_attribute("Id","xmldsig-#{uuid}-signedprops")
|
89
|
+
|
90
|
+
element.add_element("xades:SigningTime").text = signing_time.rfc3339
|
91
|
+
signing_certificate_elem = element.add_element("xades:SigningCertificate")
|
92
|
+
cert_elem = signing_certificate_elem.add_element("xades:Cert")
|
93
|
+
cert_digest_elem = cert_elem.add_element("xades:CertDigest")
|
94
|
+
cert_digest_elem.add_element("ds:DigestMethod", {"Algorithm"=>SHA256})
|
95
|
+
cert_digest_elem.add_element("ds:DigestValue").text = cert_digest
|
96
|
+
|
97
|
+
issuer_serial = cert_elem.add_element("xades:IssuerSerial")
|
98
|
+
issuer_serial.add_element("ds:X509IssuerName").text = @x509.issuer.to_a.reverse.map{|c| c[0..1].join("=")}.join(", ")
|
99
|
+
issuer_serial.add_element("ds:X509SerialNumber").text = @x509.serial.to_s
|
100
|
+
|
101
|
+
policy_elem = element.add_element("xades:SignaturePolicyIdentifier")
|
102
|
+
policy_id_elem = policy_elem.add_element("xades:SignaturePolicyId")
|
103
|
+
sig_policy_id = policy_id_elem.add_element("xades:SigPolicyId")
|
104
|
+
sig_policy_id.add_element("xades:Identifier").text = SIGNATURE_POLICY
|
105
|
+
|
106
|
+
policy_hash = sig_policy_id.add_element("xades:SigPolicyHash")
|
107
|
+
policy_hash.add_element("ds:DigestMethod", {"Algorithm"=>"http://www.w3.org/2000/09/xmldsig#sha1"})
|
108
|
+
policy_hash.add_element("ds:DigestValue").text = "V8lVVNGDCPen6VELRD1Ja8HARFk="#policy_digest
|
109
|
+
|
110
|
+
element
|
111
|
+
end
|
112
|
+
|
113
|
+
def build_key_info_element
|
114
|
+
key_info = REXML::Element.new("ds:KeyInfo",nil,{:attribute_quote=>:quote})
|
115
|
+
key_info.add_namespace("https://tribunet.hacienda.go.cr/docs/esquemas/2017/v4.2/facturaElectronica")
|
116
|
+
key_info.add_namespace("ds","http://www.w3.org/2000/09/xmldsig#")
|
117
|
+
key_info.add_namespace("xsd","http://www.w3.org/2001/XMLSchema")
|
118
|
+
key_info.add_namespace("xsi","http://www.w3.org/2001/XMLSchema-instance")
|
119
|
+
|
120
|
+
key_info.add_attribute("Id","xmldsig-#{uuid}-keyinfo")
|
121
|
+
x509_data = key_info.add_element("ds:X509Data")
|
122
|
+
x509_data.add_element("ds:X509Certificate").text = @x509.to_pem.to_s.gsub("-----BEGIN CERTIFICATE-----","").gsub("-----END CERTIFICATE-----","").gsub(/\n|\r/, "")
|
123
|
+
key_value = key_info.add_element("ds:KeyValue")
|
124
|
+
rsa_value = key_value.add_element("ds:RSAKeyValue")
|
125
|
+
rsa_value.add_element("ds:Modulus").text = Base64.encode64(@x509.public_key.params["n"].to_s(2)).gsub("\n","")
|
126
|
+
rsa_value.add_element("ds:Exponent").text = Base64.encode64(@x509.public_key.params["e"].to_s(2)).gsub("\n","")
|
127
|
+
key_info
|
128
|
+
end
|
129
|
+
|
130
|
+
def build_signed_info_element(key_info_element, signed_props_element)
|
131
|
+
|
132
|
+
signed_info_element = REXML::Element.new("ds:SignedInfo",nil,{:attribute_quote=>:quote})
|
133
|
+
signed_info_element.add_namespace("https://tribunet.hacienda.go.cr/docs/esquemas/2017/v4.2/facturaElectronica")
|
134
|
+
signed_info_element.add_namespace("ds","http://www.w3.org/2000/09/xmldsig#")
|
135
|
+
signed_info_element.add_namespace("xsd","http://www.w3.org/2001/XMLSchema")
|
136
|
+
signed_info_element.add_namespace("xsi","http://www.w3.org/2001/XMLSchema-instance")
|
137
|
+
signed_info_element.add_element("ds:CanonicalizationMethod", { "Algorithm"=>C14N })
|
138
|
+
signed_info_element.add_element("ds:SignatureMethod", {"Algorithm"=>RSA_SHA256})
|
139
|
+
|
140
|
+
# Add Ref0
|
141
|
+
ref0 = signed_info_element.add_element("ds:Reference",{"Id"=>"xmldsig-#{uuid}-ref0"})
|
142
|
+
transforms = ref0.add_element("ds:Transforms")
|
143
|
+
transform = transforms.add_element("ds:Transform", {"Algorithm"=>ENVELOPED_SIG})
|
144
|
+
digest_method_element = ref0.add_element("ds:DigestMethod", {"Algorithm"=> SHA256})
|
145
|
+
ref0.add_element("ds:DigestValue").text = digest_document(@doc,SHA256)
|
146
|
+
|
147
|
+
# Add KeyInfo Ref
|
148
|
+
ref_key_info = signed_info_element.add_element("ds:Reference",{"URI"=>"#xmldsig-#{uuid}-keyinfo"})
|
149
|
+
ref_key_info.add_element("ds:DigestMethod", {"Algorithm"=> SHA256})
|
150
|
+
ref_key_info.add_element("ds:DigestValue").text = digest_document(key_info_element, SHA256, true)
|
151
|
+
|
152
|
+
# Add SignedProps Ref
|
153
|
+
ref_props = signed_info_element.add_element("ds:Reference",{"URI"=>"#xmldsig-#{uuid}-signedprops", "Type"=>"http://uri.etsi.org/01903#SignedProperties"})
|
154
|
+
ref_props.add_element("ds:DigestMethod", {"Algorithm"=> SHA256})
|
155
|
+
ref_props.add_element("ds:DigestValue").text = digest_document(signed_props_element, SHA256,true)
|
156
|
+
|
157
|
+
signed_info_element
|
158
|
+
end
|
159
|
+
|
160
|
+
def digest_document(doc, digest_algorithm=SHA256, strip=false)
|
161
|
+
compute_digest(canonicalize_document(doc,strip),algorithm(digest_algorithm))
|
162
|
+
end
|
163
|
+
|
164
|
+
def canonicalize_document(doc,strip=false)
|
165
|
+
doc = doc.to_s if doc.is_a?(REXML::Element)
|
166
|
+
doc.strip! if strip
|
167
|
+
doc.encode("UTF-8")
|
168
|
+
noko = Nokogiri::XML(doc) do |config|
|
169
|
+
config.options = NOKOGIRI_OPTIONS
|
170
|
+
end
|
171
|
+
|
172
|
+
noko.canonicalize(canon_algorithm(C14N),NAMESPACES.split(" "))
|
173
|
+
end
|
174
|
+
|
175
|
+
def delete_namespaces(element)
|
176
|
+
NAMESPACES.split(" ").each do |ns|
|
177
|
+
element.delete_namespace(ns)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def uuid
|
182
|
+
@uuid ||= SecureRandom.uuid
|
183
|
+
end
|
184
|
+
|
185
|
+
def canon_algorithm(element)
|
186
|
+
algorithm = element
|
187
|
+
if algorithm.is_a?(REXML::Element)
|
188
|
+
algorithm = element.attribute('Algorithm').value
|
189
|
+
end
|
190
|
+
|
191
|
+
case algorithm
|
192
|
+
when "http://www.w3.org/TR/2001/REC-xml-c14n-20010315",
|
193
|
+
"http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"
|
194
|
+
Nokogiri::XML::XML_C14N_1_0
|
195
|
+
when "http://www.w3.org/2006/12/xml-c14n11",
|
196
|
+
"http://www.w3.org/2006/12/xml-c14n11#WithComments"
|
197
|
+
Nokogiri::XML::XML_C14N_1_1
|
198
|
+
else
|
199
|
+
Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def algorithm(element)
|
204
|
+
algorithm = element
|
205
|
+
if algorithm.is_a?(REXML::Element)
|
206
|
+
algorithm = element.attribute("Algorithm").value
|
207
|
+
end
|
208
|
+
|
209
|
+
algorithm = algorithm && algorithm =~ /(rsa-)?sha(.*?)$/i && $2.to_i
|
210
|
+
|
211
|
+
case algorithm
|
212
|
+
when 256 then OpenSSL::Digest::SHA256
|
213
|
+
when 384 then OpenSSL::Digest::SHA384
|
214
|
+
when 512 then OpenSSL::Digest::SHA512
|
215
|
+
else
|
216
|
+
OpenSSL::Digest::SHA1
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
def compute_signature(private_key, signature_algorithm, document)
|
221
|
+
Base64.encode64(private_key.sign(signature_algorithm, document)).gsub(/\n/, "")
|
222
|
+
end
|
223
|
+
|
224
|
+
def compute_digest(document, digest_algorithm)
|
225
|
+
digest = digest_algorithm.digest(document)
|
226
|
+
Base64.encode64(digest).strip!
|
227
|
+
end
|
228
|
+
|
229
|
+
end
|
230
|
+
|
231
|
+
class JavaSigner
|
232
|
+
|
233
|
+
def initialize(key_file,password,path,out_path)
|
234
|
+
@key_file = key_file
|
235
|
+
@password = password
|
236
|
+
@path = path
|
237
|
+
@out_path = out_path
|
238
|
+
end
|
239
|
+
|
240
|
+
def sign
|
241
|
+
null_device = Gem.win_platform? ? "/nul" : "/dev/null"
|
242
|
+
system("java -jar #{FE.bin}/signer/signer.jar #{@key_file} #{@password} #{@path} #{@out_path} 1>#{null_device} 2>#{null_device}")
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'facturacr/document'
|
2
|
+
|
3
|
+
module FE
|
4
|
+
|
5
|
+
class Ticket < Document
|
6
|
+
|
7
|
+
def initialize(args={})
|
8
|
+
@date = args[:date]
|
9
|
+
@issuer = args[:issuer]
|
10
|
+
@receiver = args[:receiver]
|
11
|
+
@items = args[:items]
|
12
|
+
@number = args[:number]
|
13
|
+
@condition = args[:condition]
|
14
|
+
@payment_type = args[:payment_type] || "01"
|
15
|
+
@document_type = "04"
|
16
|
+
@credit_term = args[:credit_term]
|
17
|
+
@summary = args[:summary]
|
18
|
+
@regulation = args[:regulation] ||= FE::Document::Regulation.new
|
19
|
+
@security_code = args[:security_code]
|
20
|
+
@document_situation = args[:document_situation]
|
21
|
+
@namespaces = {
|
22
|
+
"xmlns:xsi"=>"http://www.w3.org/2001/XMLSchema-instance",
|
23
|
+
"xmlns:xsd"=>"http://www.w3.org/2001/XMLSchema",
|
24
|
+
"xmlns"=>"https://tribunet.hacienda.go.cr/docs/esquemas/2017/v4.2/tiqueteElectronico",
|
25
|
+
"xsi:schemaLocation"=>"https://tribunet.hacienda.go.cr/docs/esquemas/2017/v4.2/tiqueteElectronico https://tribunet.hacienda.go.cr/docs/esquemas/2017/v4.2/tiqueteElectronico.xsd"
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
def document_tag
|
30
|
+
"TiqueteElectronico"
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
require 'facturacr/document'
|
4
|
+
|
5
|
+
module FE
|
6
|
+
class XmlDocument
|
7
|
+
|
8
|
+
attr_accessor :document, :root_tag
|
9
|
+
|
10
|
+
def initialize(xml_path)
|
11
|
+
if File.exist?(xml_path)
|
12
|
+
@doc = Nokogiri::XML(File.open(xml_path,"rb")) do |config|
|
13
|
+
config.options = Nokogiri::XML::ParseOptions::NOBLANKS | Nokogiri::XML::ParseOptions::NOENT
|
14
|
+
end
|
15
|
+
root_tag = @doc.elements.first.name
|
16
|
+
|
17
|
+
|
18
|
+
if root_tag.eql?('FacturaElectronica')
|
19
|
+
@document = FE::Invoice.new
|
20
|
+
elsif root_tag.eql?("NotaCreditoElectronica")
|
21
|
+
@document = FE::CreditNote.new
|
22
|
+
elsif root_tag.eql?("NotaDebitoElectronica")
|
23
|
+
@document = FE::DebitNote.new
|
24
|
+
end
|
25
|
+
@document.date = DateTime.parse(@doc.css("#{root_tag} FechaEmision").text)
|
26
|
+
@key = @doc.css("#{root_tag} Clave").text
|
27
|
+
@document.number = @key[31..40].to_i
|
28
|
+
@document.document_situation = @key[41]
|
29
|
+
@document.security_code = @key[42..-1]
|
30
|
+
@document.condition = @doc.css("#{root_tag} CondicionVenta").text
|
31
|
+
@document.credit_term = @doc.css("#{root_tag} PlazoCredito").text unless @doc.css("#{root_tag} PlazoCredito").empty?
|
32
|
+
@document.payment_type = @doc.css("#{root_tag} MedioPago").first.text
|
33
|
+
|
34
|
+
@issuer = FE::Document::Issuer.new
|
35
|
+
@issuer.identification_document = FE::Document::IdentificationDocument.new type: @doc.css("#{root_tag} Emisor Identificacion Tipo").text, number: @doc.css("#{root_tag} Emisor Identificacion Numero").text.to_i
|
36
|
+
@issuer.name = @doc.css("#{root_tag} Emisor Nombre").text
|
37
|
+
@issuer.comercial_name = @doc.css("#{root_tag} Emisor NombreComercial").text unless @doc.css("#{root_tag} Emisor NombreComercial").empty?
|
38
|
+
location = FE::Document::Location.new
|
39
|
+
location.province = @doc.css("#{root_tag} Emisor Ubicacion Provincia").text
|
40
|
+
location.county = @doc.css("#{root_tag} Emisor Ubicacion Canton").text
|
41
|
+
location.district = @doc.css("#{root_tag} Emisor Ubicacion Distrito").text
|
42
|
+
location.others = @doc.css("#{root_tag} Emisor Ubicacion OtrasSenas").text
|
43
|
+
@issuer.location = location
|
44
|
+
|
45
|
+
if !@doc.css("#{root_tag} Emisor Telefono").empty?
|
46
|
+
@issuer.phone = FE::Document::Phone.new country_code: @doc.css("#{root_tag} Emisor Telefono CodigoPais").text, number: @doc.css("#{root_tag} Emisor Telefono CodigoPais").text
|
47
|
+
end
|
48
|
+
if !@doc.css("#{root_tag} Emisor Fax").empty?
|
49
|
+
@issuer.fax = FE::Document::Phone.new country_code: @doc.css("#{root_tag} Emisor Telefono CodigoPais").text, number: @doc.css("#{root_tag} Emisor Telefono CodigoPais").text
|
50
|
+
end
|
51
|
+
@issuer.email = @doc.css("#{root_tag} Emisor CorreoElectronico").text
|
52
|
+
|
53
|
+
unless @doc.css("#{root_tag} Receptor").empty?
|
54
|
+
@receiver = FE::Document::Receiver.new
|
55
|
+
@receiver.name = @doc.css("#{root_tag} Receptor Nombre").text
|
56
|
+
unless @doc.css("#{root_tag} Receptor Identificacion").empty?
|
57
|
+
@receiver.identification_document = FE::Document::IdentificationDocument.new type: @doc.css("#{root_tag} Receptor Identificacion Tipo").text, number: @doc.css("#{root_tag} Receptor Identificacion Numero").text.to_i
|
58
|
+
end
|
59
|
+
|
60
|
+
unless @doc.css("#{root_tag} Receptor IdentificacionExtranjero").empty?
|
61
|
+
@receiver.foreign_id_number = @doc.css("#{root_tag} Receptor IdentificacionExtranjero").text
|
62
|
+
end
|
63
|
+
@receiver.comercial_name = @doc.css("#{root_tag} Receptor NombreComercial").text unless @doc.css("#{root_tag} Receptor NombreComercial").empty?
|
64
|
+
|
65
|
+
unless @doc.css("#{root_tag} Receptor Ubicacion").empty?
|
66
|
+
location = FE::Document::Location.new
|
67
|
+
location.province = @doc.css("#{root_tag} Receptor Ubicacion Provincia").text
|
68
|
+
location.county = @doc.css("#{root_tag} Receptor Ubicacion Canton").text
|
69
|
+
location.district = @doc.css("#{root_tag} Receptor Ubicacion Distrito").text
|
70
|
+
location.others = @doc.css("#{root_tag} Receptor Ubicacion OtrasSenas").text
|
71
|
+
@receiver.location = location
|
72
|
+
end
|
73
|
+
|
74
|
+
if !@doc.css("#{root_tag} Receptor Telefono").empty?
|
75
|
+
@issuer.phone = FE::Document::Phone.new country_code: @doc.css("#{root_tag} Receptor Telefono CodigoPais").text, number: @doc.css("#{root_tag} Receptor Telefono CodigoPais").text
|
76
|
+
end
|
77
|
+
if !@doc.css("#{root_tag} Receptor Fax").empty?
|
78
|
+
@receiver.fax = FE::Document::Phone.new country_code: @doc.css("#{root_tag} Receptor Telefono CodigoPais").text, number: @doc.css("#{root_tag} Receptor Telefono CodigoPais").text
|
79
|
+
end
|
80
|
+
@receiver.email = @doc.css("#{root_tag} Receptor CorreoElectronico").text unless @doc.css("#{root_tag} Receptor CorreoElectronico").empty?
|
81
|
+
end
|
82
|
+
@items = []
|
83
|
+
@doc.css("#{root_tag} DetalleServicio LineaDetalle").each do |line|
|
84
|
+
item = FE::Document::Item.new
|
85
|
+
item.line_number = line.css("NumeroLinea").text.to_i
|
86
|
+
item.code = line.css("Codigo Codigo").text
|
87
|
+
item.quantity = line.css("Cantidad").text
|
88
|
+
item.unit = line.css("UnidadMedida").text
|
89
|
+
item.description = line.css("Detalle").text
|
90
|
+
item.unit_price = line.css("PrecioUnitario").text.to_f
|
91
|
+
item.total = line.css("MontoTotal").text.to_f
|
92
|
+
item.discount = line.css("MontoDescuento").text.to_f unless line.css("MontoDescuento").empty?
|
93
|
+
item.discount_reason = line.css("NaturalezaDescuento").text unless line.css("NaturalezaDescuento").empty?
|
94
|
+
item.subtotal = line.css("SubTotal").text.to_f
|
95
|
+
item.net_total = line.css("MontoTotalLinea").text.to_f
|
96
|
+
item.taxes = []
|
97
|
+
line.css("Impuesto").each do |tax|
|
98
|
+
item.taxes << FE::Document::Tax.new(code: tax.css("Codigo").text, rate: tax.css("Tarifa").text.to_f, total: tax.css("Monto").text.to_f)
|
99
|
+
end
|
100
|
+
unless line.css("Exoneracion").empty?
|
101
|
+
exo = FE::Document::Exoneration.new
|
102
|
+
exo.document_type = line.css("Exoneracion TipoDocumento").text
|
103
|
+
exo.document_number = line.css("Exoneracion NumeroDocumento").text
|
104
|
+
exo.institution = line.css("Exoneracion NombreInstitucion").text
|
105
|
+
exo.date = DateTime.parse(line.css("Exoneracion FechaEmision").text)
|
106
|
+
exo.total_tax = line.css("Exoneracion MontoImpuesto").text.to_f
|
107
|
+
exo.percentage = line.css("Exoneracion PorcentajeCompra").text.to_i
|
108
|
+
item.exoneration = exo
|
109
|
+
end
|
110
|
+
@items << item
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
@summary = FE::Document::Summary.new
|
115
|
+
sum = @doc.css("#{root_tag} ResumenFactura")
|
116
|
+
@summary.currency = sum.css("CodigoMoneda").text
|
117
|
+
@summary.exchange_rate = sum.css("TipoCambio").text.to_f
|
118
|
+
@summary.services_taxable_total = sum.css("TotalServGravados").text.to_f
|
119
|
+
@summary.services_exent_total = sum.css("TotalServExentos").text.to_f
|
120
|
+
@summary.goods_taxable_total = sum.css("TotalMercanciasGravadas").text.to_f
|
121
|
+
@summary.goods_exent_total = sum.css("TotalMercanciasExentas").text.to_f
|
122
|
+
@summary.taxable_total = sum.css("TotalGravado").text.to_f
|
123
|
+
@summary.exent_total = sum.css("TotalExento").text.to_f
|
124
|
+
@summary.subtotal = sum.css("TotalVenta").text.to_f
|
125
|
+
@summary.discount_total = sum.css("TotalDescuentos").text.to_f
|
126
|
+
@summary.gross_total = sum.css("TotalVentaNeta").text.to_f
|
127
|
+
@summary.tax_total = sum.css("TotalImpuesto").text.to_f
|
128
|
+
@summary.net_total = sum.css("TotalComprobante").text.to_f
|
129
|
+
|
130
|
+
refs = @doc.css("#{root_tag} InformacionReferencia")
|
131
|
+
@references = []
|
132
|
+
unless refs.empty?
|
133
|
+
refs.each do |ref|
|
134
|
+
reference = FE::Document::Reference.new
|
135
|
+
reference.document_type = ref.css("TipoDoc")
|
136
|
+
reference.number = ref.css("Numero")
|
137
|
+
reference.date = ref.css("FechaEmision")
|
138
|
+
reference.code = ref.css("Codigo")
|
139
|
+
reference.reason = ref.css("Razon")
|
140
|
+
@references << reference
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
reg = @doc.css("#{root_tag} Normativa")
|
145
|
+
@regulation = FE::Document::Regulation.new
|
146
|
+
@regulation.number = reg.css("NumeroResolucion").text
|
147
|
+
@regulation.date = reg.css("FechaResolucion").text
|
148
|
+
|
149
|
+
|
150
|
+
@document.issuer = @issuer
|
151
|
+
@document.receiver = @receiver
|
152
|
+
@document.items = @items
|
153
|
+
@document.summary = @summary
|
154
|
+
@document.references = @references
|
155
|
+
@document.regulation = @regulation
|
156
|
+
else
|
157
|
+
raise "#{xml_path} does not exist"
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|