facturacr 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|