facturacr 0.1.4 → 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 +4 -4
- data/README.md +11 -10
- data/facturacr.gemspec +8 -8
- data/lib/facturacr.rb +1 -0
- data/lib/facturacr/api.rb +6 -2
- data/lib/facturacr/builder.rb +7 -1
- data/lib/facturacr/cli.rb +16 -29
- data/lib/facturacr/cli/generate.rb +9 -6
- data/lib/facturacr/data_provider.rb +21 -0
- data/lib/facturacr/document.rb +1 -0
- data/lib/facturacr/document/exoneration.rb +7 -5
- data/lib/facturacr/document/item.rb +2 -2
- data/lib/facturacr/document/tax.rb +5 -1
- data/lib/facturacr/reception_message.rb +25 -22
- data/lib/facturacr/signed_document.rb +10 -8
- data/lib/facturacr/signer/signer.rb +149 -119
- data/lib/facturacr/version.rb +1 -1
- data/lib/facturacr/xml_document.rb +155 -150
- data/resources/data.yml +2 -2
- data/resources/test.p12 +0 -0
- data/resources/test.sign.crt +31 -0
- data/resources/test.sign.key +52 -0
- data/resources/test.sign.pem +83 -0
- metadata +36 -32
@@ -2,16 +2,18 @@ require 'base64'
|
|
2
2
|
|
3
3
|
module FE
|
4
4
|
class SignedDocument
|
5
|
-
attr_accessor :document, :
|
5
|
+
attr_accessor :document, :base64, :payload
|
6
6
|
|
7
|
-
def initialize(document,
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
file = File.open(@path,"rb")
|
13
|
-
@base64 = Base64.encode64(file.read).gsub("\n","");
|
7
|
+
def initialize(document, xml_provider)
|
8
|
+
# Backwards compatibility with v0.1.4
|
9
|
+
if xml_provider.is_a?(String)
|
10
|
+
raise ArgumentError, "File: #{xml_provider} does not exist" unless File.exists?(xml_provider)
|
11
|
+
xml_provider = Fe::DataProvider.new(:file, xml_provider)
|
14
12
|
end
|
13
|
+
raise ArgumentError, "Invalid Argument" unless xml_provider.is_a?(FE::DataProvider)
|
14
|
+
|
15
|
+
@document = document
|
16
|
+
@base64 = Base64.encode64(xml_provider.contents).gsub("\n","");
|
15
17
|
@payload = document.api_payload
|
16
18
|
@payload[:comprobanteXml] = @base64
|
17
19
|
end
|
@@ -5,7 +5,6 @@ require "rexml/xpath"
|
|
5
5
|
|
6
6
|
module FE
|
7
7
|
class Signer
|
8
|
-
REXML::Document::entity_expansion_limit = 0
|
9
8
|
C14N = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315" #"http://www.w3.org/2001/10/xml-exc-c14n#"
|
10
9
|
DSIG = "http://www.w3.org/2000/09/xmldsig#"
|
11
10
|
NOKOGIRI_OPTIONS = Nokogiri::XML::ParseOptions::STRICT | Nokogiri::XML::ParseOptions::NONET | Nokogiri::XML::ParseOptions::NOENT
|
@@ -24,137 +23,180 @@ module FE
|
|
24
23
|
XADES = "http://uri.etsi.org/01903/v1.3.2#"
|
25
24
|
XADES141 = "http://uri.etsi.org/01903/v1.4.1#"
|
26
25
|
SIGNATURE_POLICY = "https://tribunet.hacienda.go.cr/docs/esquemas/2016/v4/Resolucion%20Comprobantes%20Electronicos%20%20DGT-R-48-2016.pdf"
|
26
|
+
|
27
27
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
@
|
28
|
+
def initialize(args = {})
|
29
|
+
document_provider = args[:xml_provider]
|
30
|
+
key_provider = args[:key_provider]
|
31
|
+
pin = args[:pin]
|
32
|
+
raise ArgumentError , "Los argumentos no son válidos" if document_provider.nil? || key_provider.nil? || pin.nil?
|
33
|
+
@doc = Nokogiri::XML(document_provider.contents) do |config|
|
34
|
+
config.options = Nokogiri::XML::ParseOptions::NOBLANKS | Nokogiri::XML::ParseOptions::NOENT | Nokogiri::XML::ParseOptions::NOENT
|
35
|
+
end
|
36
|
+
@p12 = OpenSSL::PKCS12.new(key_provider.contents,args[:pin])
|
34
37
|
@x509 = @p12.certificate
|
35
|
-
@output_path = output_path
|
38
|
+
@output_path = args[:output_path]
|
36
39
|
end
|
37
|
-
|
40
|
+
|
38
41
|
def sign
|
39
42
|
#Build parts for Digest Calculation
|
40
43
|
key_info = build_key_info_element
|
41
44
|
signed_properties = build_signed_properties_element
|
42
45
|
signed_info_element = build_signed_info_element(key_info,signed_properties)
|
46
|
+
|
43
47
|
# Compute Signature
|
44
48
|
signed_info_canon = canonicalize_document(signed_info_element)
|
45
49
|
signature_value = compute_signature(@p12.key,algorithm(RSA_SHA256).new,signed_info_canon)
|
50
|
+
|
51
|
+
ds = Nokogiri::XML::Node.new("ds:Signature", @doc)
|
52
|
+
ds["xmlns:ds"] = DSIG
|
53
|
+
#ds["Id"] = SIGNATURE_ID#"xmldsig-#{uuid}"
|
54
|
+
ds["Id"] = "xmldsig-#{uuid}"
|
55
|
+
#ds.add_child(Nokogiri::XML(signed_info_without_ns).root)
|
56
|
+
ds.add_child(signed_info_element.root)
|
46
57
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
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}")
|
58
|
+
sv = Nokogiri::XML::Node.new("ds:SignatureValue", @doc)
|
59
|
+
#sv["Id"] = SIGNATURE_VALUE#"xmldsig-#{uuid}-sigvalue"
|
60
|
+
sv["Id"] = "xmldsig-#{uuid}-sigvalue"
|
61
|
+
sv.content = signature_value
|
62
|
+
ds.add_child(sv)
|
55
63
|
|
56
|
-
|
57
|
-
signature_element.add_element("ds:SignatureValue","Id"=>"xmldsig-#{uuid}-sigvalue").text = signature_value
|
58
|
-
signature_element.add_element(key_info)
|
64
|
+
ds.add_child(key_info.root)
|
59
65
|
|
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
66
|
|
65
|
-
|
67
|
+
dsobj = Nokogiri::XML::Node.new("ds:Object",@doc)
|
68
|
+
dsobj["Id"] = "xades-obj-#{uuid}"#XADES_OBJECT_ID
|
69
|
+
qp = Nokogiri::XML::Node.new("xades:QualifyingProperties",@doc)
|
70
|
+
qp["xmlns:xades"] = XADES
|
71
|
+
#qp["Target"] = "##{SIGNATURE_ID}"#"#xmldsig-#{uuid}"
|
72
|
+
qp["Target"] = "#xmldsig-#{uuid}"
|
73
|
+
qp["Id"] = "QualifyingProperties-#{uuid}"
|
74
|
+
qp.add_child(signed_properties.root)
|
66
75
|
|
67
|
-
|
76
|
+
dsobj.add_child(qp)
|
77
|
+
ds.add_child(dsobj)
|
78
|
+
@doc.root.add_child(ds)
|
68
79
|
|
69
|
-
File.open(@output_path,"w"){|f| f.write(@doc.
|
80
|
+
File.open(@output_path,"w"){|f| f.write(@doc.to_xml(:save_with=>Nokogiri::XML::Node::SaveOptions::AS_XML).gsub(/\r|\n/,""))} if @output_path
|
70
81
|
|
71
|
-
@doc
|
82
|
+
@doc.to_xml(:save_with=>Nokogiri::XML::Node::SaveOptions::AS_XML).gsub(/\r|\n/,"")
|
72
83
|
end
|
73
84
|
|
85
|
+
|
74
86
|
private
|
75
87
|
|
88
|
+
def build_key_info_element
|
89
|
+
builder = Nokogiri::XML::Builder.new
|
90
|
+
attributes = {
|
91
|
+
"xmlns" => "https://tribunet.hacienda.go.cr/docs/esquemas/2017/v4.2/facturaElectronica",
|
92
|
+
"xmlns:ds" => "http://www.w3.org/2000/09/xmldsig#",
|
93
|
+
"xmlns:xsd" => "http://www.w3.org/2001/XMLSchema",
|
94
|
+
"xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance",
|
95
|
+
"Id"=>"xmldsig-#{uuid}-keyinfo"
|
96
|
+
}
|
97
|
+
|
98
|
+
builder.send("ds:KeyInfo", attributes) do |ki|
|
99
|
+
ki.send("ds:X509Data") do |kd|
|
100
|
+
kd.send("ds:X509Certificate", @x509.to_pem.to_s.gsub("-----BEGIN CERTIFICATE-----","").gsub("-----END CERTIFICATE-----","").gsub(/\n|\r/, ""))
|
101
|
+
end
|
102
|
+
ki.send("ds:KeyValue") do |kv|
|
103
|
+
kv.send("ds:RSAKeyValue") do |rv|
|
104
|
+
rv.send("ds:Modulus", Base64.encode64(@x509.public_key.params["n"].to_s(2)).gsub("\n",""))
|
105
|
+
rv.send("ds:Exponent", Base64.encode64(@x509.public_key.params["e"].to_s(2)).gsub("\n",""))
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
builder.doc
|
110
|
+
end
|
111
|
+
|
76
112
|
def build_signed_properties_element
|
77
113
|
cert_digest = compute_digest(@x509.to_der,algorithm(SHA256))
|
78
114
|
policy_digest = compute_digest(@x509.to_der,algorithm(SHA256))
|
79
|
-
signing_time = DateTime.now
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
115
|
+
signing_time = DateTime.now.rfc3339
|
116
|
+
builder = Nokogiri::XML::Builder.new
|
117
|
+
attributes = {
|
118
|
+
"xmlns"=>"https://tribunet.hacienda.go.cr/docs/esquemas/2017/v4.2/facturaElectronica",
|
119
|
+
"xmlns:ds" => "http://www.w3.org/2000/09/xmldsig#",
|
120
|
+
"xmlns:xades" => "http://uri.etsi.org/01903/v1.3.2#",
|
121
|
+
"xmlns:xsd" => "http://www.w3.org/2001/XMLSchema",
|
122
|
+
"xmlns:xsi"=>"http://www.w3.org/2001/XMLSchema-instance",
|
123
|
+
"Id" => "xmldsig-#{uuid}-signedprops"
|
124
|
+
}
|
125
|
+
builder.send("xades:SignedProperties", attributes) do |sp|
|
126
|
+
sp.send("xades:SignedSignatureProperties") do |ssp|
|
127
|
+
ssp.send("xades:SigningTime", signing_time)
|
128
|
+
ssp.send("xades:SigningCertificate") do |sc|
|
129
|
+
sc.send("xades:Cert") do |c|
|
130
|
+
c.send("xades:CertDigest") do |xcd|
|
131
|
+
xcd.send("ds:DigestMethod", {"Algorithm"=>SHA256})
|
132
|
+
xcd.send("ds:DigestValue", cert_digest)
|
133
|
+
end
|
134
|
+
c.send("xades:IssuerSerial") do |is|
|
135
|
+
is.send("ds:X509IssuerName", @x509.issuer.to_a.reverse.map{|c| c[0..1].join("=")}.join(", "))
|
136
|
+
is.send("ds:X509SerialNumber", @x509.serial.to_s)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
ssp.send("xades:SignaturePolicyIdentifier") do |spi|
|
142
|
+
spi.send("xades:SignaturePolicyId") do |spi2|
|
143
|
+
spi2.send("xades:SigPolicyId") do |spi3|
|
144
|
+
spi3.send("xades:Identifier", SIGNATURE_POLICY)
|
145
|
+
spi3.send("xades:Description")
|
146
|
+
end
|
147
|
+
|
148
|
+
spi2.send("xades:SigPolicyHash") do |sph|
|
149
|
+
sph.send("ds:DigestMethod", {"Algorithm"=>"http://www.w3.org/2000/09/xmldsig#sha1"})
|
150
|
+
sph.send("ds:DigestValue", "V8lVVNGDCPen6VELRD1Ja8HARFk=")
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
156
|
+
sp.send("xades:SignedDataObjectProperties") do |sdop|
|
157
|
+
sdop.send("xades:DataObjectFormat", {"ObjectReference"=>"#xmldsig-#{uuid}-ref0"}) do |dof|
|
158
|
+
dof.send("xades:MimeType","text/xml")
|
159
|
+
dof.send("xades:Encoding", "UTF-8")
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
109
163
|
|
110
|
-
|
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
|
164
|
+
builder.doc
|
128
165
|
end
|
129
166
|
|
130
167
|
def build_signed_info_element(key_info_element, signed_props_element)
|
131
168
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
169
|
+
builder = builder = Nokogiri::XML::Builder.new
|
170
|
+
attributes = {
|
171
|
+
"xmlns"=>"https://tribunet.hacienda.go.cr/docs/esquemas/2017/v4.2/facturaElectronica",
|
172
|
+
"xmlns:ds" => "http://www.w3.org/2000/09/xmldsig#",
|
173
|
+
"xmlns:xsd" => "http://www.w3.org/2001/XMLSchema",
|
174
|
+
"xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance"
|
175
|
+
}
|
176
|
+
builder.send("ds:SignedInfo", attributes) do |si|
|
177
|
+
si.send("ds:CanonicalizationMethod", { "Algorithm"=>C14N })
|
178
|
+
si.send("ds:SignatureMethod", {"Algorithm"=>RSA_SHA256})
|
179
|
+
|
180
|
+
si.send("ds:Reference",{"Id"=>"xmldsig-#{uuid}-ref0", "URI"=>""}) do |r|
|
181
|
+
r.send("ds:Transforms") do |t|
|
182
|
+
t.send("ds:Transform", {"Algorithm"=>ENVELOPED_SIG})
|
183
|
+
end
|
184
|
+
r.send("ds:DigestMethod", {"Algorithm"=> SHA256})
|
185
|
+
r.send("ds:DigestValue", digest_document(@doc,SHA256))
|
186
|
+
end
|
187
|
+
si.send("ds:Reference",{"Id"=>"xmldsig-#{uuid}-ref1", "URI"=>"#xmldsig-#{uuid}-keyinfo"}) do |r|
|
188
|
+
r.send("ds:DigestMethod", {"Algorithm"=> SHA256})
|
189
|
+
r.send("ds:DigestValue", digest_document(key_info_element, SHA256, true))
|
190
|
+
end
|
191
|
+
|
192
|
+
si.send("ds:Reference",{"Type"=>"http://uri.etsi.org/01903#SignedProperties", "URI"=>"#xmldsig-#{uuid}-signedprops"}) do |r|
|
193
|
+
r.send("ds:DigestMethod", {"Algorithm"=> SHA256})
|
194
|
+
r.send("ds:DigestValue", digest_document(signed_props_element, SHA256, true))
|
195
|
+
end
|
196
|
+
end
|
156
197
|
|
157
|
-
|
198
|
+
|
199
|
+
builder.doc
|
158
200
|
end
|
159
201
|
|
160
202
|
def digest_document(doc, digest_algorithm=SHA256, strip=false)
|
@@ -162,21 +204,9 @@ module FE
|
|
162
204
|
end
|
163
205
|
|
164
206
|
def canonicalize_document(doc,strip=false)
|
165
|
-
doc
|
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(" "))
|
207
|
+
doc.canonicalize(canon_algorithm(C14N),NAMESPACES.split(" "))
|
173
208
|
end
|
174
209
|
|
175
|
-
def delete_namespaces(element)
|
176
|
-
NAMESPACES.split(" ").each do |ns|
|
177
|
-
element.delete_namespace(ns)
|
178
|
-
end
|
179
|
-
end
|
180
210
|
|
181
211
|
def uuid
|
182
212
|
@uuid ||= SecureRandom.uuid
|
@@ -184,9 +214,7 @@ module FE
|
|
184
214
|
|
185
215
|
def canon_algorithm(element)
|
186
216
|
algorithm = element
|
187
|
-
|
188
|
-
algorithm = element.attribute('Algorithm').value
|
189
|
-
end
|
217
|
+
|
190
218
|
|
191
219
|
case algorithm
|
192
220
|
when "http://www.w3.org/TR/2001/REC-xml-c14n-20010315",
|
@@ -204,6 +232,8 @@ module FE
|
|
204
232
|
algorithm = element
|
205
233
|
if algorithm.is_a?(REXML::Element)
|
206
234
|
algorithm = element.attribute("Algorithm").value
|
235
|
+
elsif algorithm.is_a?(Nokogiri::XML::Element)
|
236
|
+
algorithm = element.xpath("//@Algorithm", "xmlns:ds" => "http://www.w3.org/2000/09/xmldsig#").first.value
|
207
237
|
end
|
208
238
|
|
209
239
|
algorithm = algorithm && algorithm =~ /(rsa-)?sha(.*?)$/i && $2.to_i
|
@@ -218,14 +248,14 @@ module FE
|
|
218
248
|
end
|
219
249
|
|
220
250
|
def compute_signature(private_key, signature_algorithm, document)
|
221
|
-
Base64.encode64(private_key.sign(signature_algorithm, document)).gsub(/\n/, "")
|
251
|
+
Base64.encode64(private_key.sign(signature_algorithm, document)).gsub(/\r|\n/, "")
|
222
252
|
end
|
223
253
|
|
224
254
|
def compute_digest(document, digest_algorithm)
|
225
255
|
digest = digest_algorithm.digest(document)
|
226
256
|
Base64.encode64(digest).strip!
|
227
257
|
end
|
228
|
-
|
258
|
+
|
229
259
|
end
|
230
260
|
|
231
261
|
class JavaSigner
|
data/lib/facturacr/version.rb
CHANGED
@@ -7,103 +7,108 @@ module FE
|
|
7
7
|
|
8
8
|
attr_accessor :document, :root_tag
|
9
9
|
|
10
|
-
def initialize(
|
11
|
-
|
12
|
-
|
13
|
-
|
10
|
+
def initialize(xml_provider)
|
11
|
+
# Backwards compatibility with v0.1.4
|
12
|
+
if xml_provider.is_a?(String)
|
13
|
+
raise ArgumentError, "File: #{xml_provider} does not exist" unless File.exists?(xml_provider)
|
14
|
+
xml_provider = Fe::DataProvider.new(:file, xml_provider)
|
15
|
+
end
|
16
|
+
raise ArgumentError, "Invalid Argument" unless xml_provider.is_a?(FE::DataProvider)
|
17
|
+
|
18
|
+
@doc = Nokogiri::XML(xml_provider.contents) do |config|
|
19
|
+
config.options = Nokogiri::XML::ParseOptions::NOBLANKS | Nokogiri::XML::ParseOptions::NOENT
|
20
|
+
end
|
21
|
+
root_tag = @doc.elements.first.name
|
22
|
+
|
23
|
+
if root_tag.eql?('FacturaElectronica')
|
24
|
+
@document = FE::Invoice.new
|
25
|
+
elsif root_tag.eql?("NotaCreditoElectronica")
|
26
|
+
@document = FE::CreditNote.new
|
27
|
+
elsif root_tag.eql?("NotaDebitoElectronica")
|
28
|
+
@document = FE::DebitNote.new
|
29
|
+
elsif root_tag.eql?("TiqueteElectronico")
|
30
|
+
@document = FE::Ticket.new
|
31
|
+
elsif root_tag.eql?("MensajeReceptor")
|
32
|
+
@document = FE::ReceptionMessage.new
|
33
|
+
end
|
34
|
+
|
35
|
+
if @document.is_a?(FE::Document)
|
36
|
+
@document.date = DateTime.parse(@doc.css("#{root_tag} FechaEmision").text)
|
37
|
+
@key = @doc.css("#{root_tag} Clave").text
|
38
|
+
@document.number = @key[31..40].to_i
|
39
|
+
@document.document_situation = @key[41]
|
40
|
+
@document.security_code = @key[42..-1]
|
41
|
+
@document.condition = @doc.css("#{root_tag} CondicionVenta").text
|
42
|
+
@document.credit_term = @doc.css("#{root_tag} PlazoCredito").text unless @doc.css("#{root_tag} PlazoCredito").empty?
|
43
|
+
@document.payment_type = @doc.css("#{root_tag} MedioPago").first.text
|
44
|
+
|
45
|
+
@issuer = FE::Document::Issuer.new
|
46
|
+
@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
|
47
|
+
@issuer.name = @doc.css("#{root_tag} Emisor Nombre").text
|
48
|
+
@issuer.comercial_name = @doc.css("#{root_tag} Emisor NombreComercial").text unless @doc.css("#{root_tag} Emisor NombreComercial").empty?
|
49
|
+
location = FE::Document::Location.new
|
50
|
+
location.province = @doc.css("#{root_tag} Emisor Ubicacion Provincia").text
|
51
|
+
location.county = @doc.css("#{root_tag} Emisor Ubicacion Canton").text
|
52
|
+
location.district = @doc.css("#{root_tag} Emisor Ubicacion Distrito").text
|
53
|
+
location.others = @doc.css("#{root_tag} Emisor Ubicacion OtrasSenas").text
|
54
|
+
@issuer.location = location
|
55
|
+
|
56
|
+
if !@doc.css("#{root_tag} Emisor Telefono").empty?
|
57
|
+
@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
|
14
58
|
end
|
15
|
-
root_tag
|
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
|
-
elsif root_tag.eql?("TiqueteElectronico")
|
25
|
-
@document = FE::Ticket.new
|
26
|
-
elsif root_tag.eql?("MensajeReceptor")
|
27
|
-
@document = FE::ReceptionMessage.new
|
59
|
+
if !@doc.css("#{root_tag} Emisor Fax").empty?
|
60
|
+
@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
|
28
61
|
end
|
62
|
+
@issuer.email = @doc.css("#{root_tag} Emisor CorreoElectronico").text
|
63
|
+
|
64
|
+
unless @doc.css("#{root_tag} Receptor").empty?
|
65
|
+
@receiver = FE::Document::Receiver.new
|
66
|
+
@receiver.name = @doc.css("#{root_tag} Receptor Nombre").text
|
67
|
+
unless @doc.css("#{root_tag} Receptor Identificacion").empty?
|
68
|
+
@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
|
69
|
+
end
|
29
70
|
|
30
|
-
|
31
|
-
|
32
|
-
@key = @doc.css("#{root_tag} Clave").text
|
33
|
-
@document.number = @key[31..40].to_i
|
34
|
-
@document.document_situation = @key[41]
|
35
|
-
@document.security_code = @key[42..-1]
|
36
|
-
@document.condition = @doc.css("#{root_tag} CondicionVenta").text
|
37
|
-
@document.credit_term = @doc.css("#{root_tag} PlazoCredito").text unless @doc.css("#{root_tag} PlazoCredito").empty?
|
38
|
-
@document.payment_type = @doc.css("#{root_tag} MedioPago").first.text
|
39
|
-
|
40
|
-
@issuer = FE::Document::Issuer.new
|
41
|
-
@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
|
42
|
-
@issuer.name = @doc.css("#{root_tag} Emisor Nombre").text
|
43
|
-
@issuer.comercial_name = @doc.css("#{root_tag} Emisor NombreComercial").text unless @doc.css("#{root_tag} Emisor NombreComercial").empty?
|
44
|
-
location = FE::Document::Location.new
|
45
|
-
location.province = @doc.css("#{root_tag} Emisor Ubicacion Provincia").text
|
46
|
-
location.county = @doc.css("#{root_tag} Emisor Ubicacion Canton").text
|
47
|
-
location.district = @doc.css("#{root_tag} Emisor Ubicacion Distrito").text
|
48
|
-
location.others = @doc.css("#{root_tag} Emisor Ubicacion OtrasSenas").text
|
49
|
-
@issuer.location = location
|
50
|
-
|
51
|
-
if !@doc.css("#{root_tag} Emisor Telefono").empty?
|
52
|
-
@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
|
71
|
+
unless @doc.css("#{root_tag} Receptor IdentificacionExtranjero").empty?
|
72
|
+
@receiver.foreign_id_number = @doc.css("#{root_tag} Receptor IdentificacionExtranjero").text
|
53
73
|
end
|
54
|
-
|
55
|
-
|
74
|
+
@receiver.comercial_name = @doc.css("#{root_tag} Receptor NombreComercial").text unless @doc.css("#{root_tag} Receptor NombreComercial").empty?
|
75
|
+
|
76
|
+
unless @doc.css("#{root_tag} Receptor Ubicacion").empty?
|
77
|
+
location = FE::Document::Location.new
|
78
|
+
location.province = @doc.css("#{root_tag} Receptor Ubicacion Provincia").text
|
79
|
+
location.county = @doc.css("#{root_tag} Receptor Ubicacion Canton").text
|
80
|
+
location.district = @doc.css("#{root_tag} Receptor Ubicacion Distrito").text
|
81
|
+
location.others = @doc.css("#{root_tag} Receptor Ubicacion OtrasSenas").text
|
82
|
+
@receiver.location = location
|
56
83
|
end
|
57
|
-
@issuer.email = @doc.css("#{root_tag} Emisor CorreoElectronico").text
|
58
84
|
|
59
|
-
|
60
|
-
@
|
61
|
-
@receiver.name = @doc.css("#{root_tag} Receptor Nombre").text
|
62
|
-
unless @doc.css("#{root_tag} Receptor Identificacion").empty?
|
63
|
-
@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
|
64
|
-
end
|
65
|
-
|
66
|
-
unless @doc.css("#{root_tag} Receptor IdentificacionExtranjero").empty?
|
67
|
-
@receiver.foreign_id_number = @doc.css("#{root_tag} Receptor IdentificacionExtranjero").text
|
68
|
-
end
|
69
|
-
@receiver.comercial_name = @doc.css("#{root_tag} Receptor NombreComercial").text unless @doc.css("#{root_tag} Receptor NombreComercial").empty?
|
70
|
-
|
71
|
-
unless @doc.css("#{root_tag} Receptor Ubicacion").empty?
|
72
|
-
location = FE::Document::Location.new
|
73
|
-
location.province = @doc.css("#{root_tag} Receptor Ubicacion Provincia").text
|
74
|
-
location.county = @doc.css("#{root_tag} Receptor Ubicacion Canton").text
|
75
|
-
location.district = @doc.css("#{root_tag} Receptor Ubicacion Distrito").text
|
76
|
-
location.others = @doc.css("#{root_tag} Receptor Ubicacion OtrasSenas").text
|
77
|
-
@receiver.location = location
|
78
|
-
end
|
79
|
-
|
80
|
-
if !@doc.css("#{root_tag} Receptor Telefono").empty?
|
81
|
-
@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
|
82
|
-
end
|
83
|
-
if !@doc.css("#{root_tag} Receptor Fax").empty?
|
84
|
-
@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
|
85
|
-
end
|
86
|
-
@receiver.email = @doc.css("#{root_tag} Receptor CorreoElectronico").text unless @doc.css("#{root_tag} Receptor CorreoElectronico").empty?
|
85
|
+
if !@doc.css("#{root_tag} Receptor Telefono").empty?
|
86
|
+
@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
|
87
87
|
end
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
88
|
+
if !@doc.css("#{root_tag} Receptor Fax").empty?
|
89
|
+
@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
|
90
|
+
end
|
91
|
+
@receiver.email = @doc.css("#{root_tag} Receptor CorreoElectronico").text unless @doc.css("#{root_tag} Receptor CorreoElectronico").empty?
|
92
|
+
end
|
93
|
+
@items = []
|
94
|
+
@doc.css("#{root_tag} DetalleServicio LineaDetalle").each do |line|
|
95
|
+
item = FE::Document::Item.new
|
96
|
+
item.line_number = line.css("NumeroLinea").text.to_i
|
97
|
+
item.code = line.css("Codigo Codigo").text
|
98
|
+
item.quantity = line.css("Cantidad").text
|
99
|
+
item.unit = line.css("UnidadMedida").text
|
100
|
+
item.description = line.css("Detalle").text
|
101
|
+
item.unit_price = line.css("PrecioUnitario").text.to_f
|
102
|
+
item.total = line.css("MontoTotal").text.to_f
|
103
|
+
item.discount = line.css("MontoDescuento").text.to_f unless line.css("MontoDescuento").empty?
|
104
|
+
item.discount_reason = line.css("NaturalezaDescuento").text unless line.css("NaturalezaDescuento").empty?
|
105
|
+
item.subtotal = line.css("SubTotal").text.to_f
|
106
|
+
item.net_total = line.css("MontoTotalLinea").text.to_f
|
107
|
+
item.taxes = []
|
108
|
+
line.css("Impuesto").each do |tax|
|
109
|
+
exo = nil
|
110
|
+
t_args = {code: tax.css("Codigo").text, rate: tax.css("Tarifa").text.to_f, total: tax.css("Monto").text.to_f}
|
111
|
+
unless tax.css("Exoneracion").empty?
|
107
112
|
exo = FE::Document::Exoneration.new
|
108
113
|
exo.document_type = line.css("Exoneracion TipoDocumento").text
|
109
114
|
exo.document_number = line.css("Exoneracion NumeroDocumento").text
|
@@ -111,71 +116,71 @@ module FE
|
|
111
116
|
exo.date = DateTime.parse(line.css("Exoneracion FechaEmision").text)
|
112
117
|
exo.total_tax = line.css("Exoneracion MontoImpuesto").text.to_f
|
113
118
|
exo.percentage = line.css("Exoneracion PorcentajeCompra").text.to_i
|
114
|
-
|
119
|
+
t_args[:exoneration] = exo
|
115
120
|
end
|
116
|
-
|
121
|
+
|
122
|
+
item.taxes << FE::Document::Tax.new(t_args)
|
117
123
|
end
|
124
|
+
@items << item
|
125
|
+
end
|
118
126
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
end
|
127
|
+
|
128
|
+
@summary = FE::Document::Summary.new
|
129
|
+
sum = @doc.css("#{root_tag} ResumenFactura")
|
130
|
+
@summary.currency = sum.css("CodigoMoneda").text
|
131
|
+
@summary.exchange_rate = sum.css("TipoCambio").text.to_f
|
132
|
+
@summary.services_taxable_total = sum.css("TotalServGravados").text.to_f
|
133
|
+
@summary.services_exent_total = sum.css("TotalServExentos").text.to_f
|
134
|
+
@summary.goods_taxable_total = sum.css("TotalMercanciasGravadas").text.to_f
|
135
|
+
@summary.goods_exent_total = sum.css("TotalMercanciasExentas").text.to_f
|
136
|
+
@summary.taxable_total = sum.css("TotalGravado").text.to_f
|
137
|
+
@summary.exent_total = sum.css("TotalExento").text.to_f
|
138
|
+
@summary.subtotal = sum.css("TotalVenta").text.to_f
|
139
|
+
@summary.discount_total = sum.css("TotalDescuentos").text.to_f
|
140
|
+
@summary.gross_total = sum.css("TotalVentaNeta").text.to_f
|
141
|
+
@summary.tax_total = sum.css("TotalImpuesto").text.to_f
|
142
|
+
@summary.net_total = sum.css("TotalComprobante").text.to_f
|
143
|
+
|
144
|
+
refs = @doc.css("#{root_tag} InformacionReferencia")
|
145
|
+
@references = []
|
146
|
+
unless refs.empty?
|
147
|
+
refs.each do |ref|
|
148
|
+
reference = FE::Document::Reference.new
|
149
|
+
reference.document_type = ref.css("TipoDoc")
|
150
|
+
reference.number = ref.css("Numero")
|
151
|
+
reference.date = ref.css("FechaEmision")
|
152
|
+
reference.code = ref.css("Codigo")
|
153
|
+
reference.reason = ref.css("Razon")
|
154
|
+
@references << reference
|
148
155
|
end
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
@document.date = DateTime.parse(@doc.css("#{root_tag} FechaEmisionDoc").text)
|
164
|
-
@key = @doc.css("#{root_tag} Clave").text
|
165
|
-
@document.key = @key
|
166
|
-
@document.issuer_id_number = @doc.css("#{root_tag} NumeroCedulaEmisor").text
|
167
|
-
@document.receiver_id_number = @doc.css("#{root_tag} NumeroCedulaReceptor").text
|
168
|
-
@document.message = @doc.css("#{root_tag} Mensaje").text
|
169
|
-
@document.details = @doc.css("#{root_tag} DetalleMensaje").text
|
170
|
-
@document.number = @key[31..40].to_i
|
171
|
-
@document.document_situation = @key[41]
|
172
|
-
@document.security_code = @key[42..-1]
|
173
|
-
@document.total = @doc.css("#{root_tag} TotalFactura").text
|
174
|
-
@document.tax = @doc.css("#{root_tag} MontoTotalImpuesto").text
|
175
|
-
end
|
156
|
+
end
|
157
|
+
|
158
|
+
reg = @doc.css("#{root_tag} Normativa")
|
159
|
+
@regulation = FE::Document::Regulation.new
|
160
|
+
@regulation.number = reg.css("NumeroResolucion").text
|
161
|
+
@regulation.date = reg.css("FechaResolucion").text
|
162
|
+
|
163
|
+
|
164
|
+
@document.issuer = @issuer
|
165
|
+
@document.receiver = @receiver
|
166
|
+
@document.items = @items
|
167
|
+
@document.summary = @summary
|
168
|
+
@document.references = @references
|
169
|
+
@document.regulation = @regulation
|
176
170
|
else
|
177
|
-
|
178
|
-
|
171
|
+
@document.date = DateTime.parse(@doc.css("#{root_tag} FechaEmisionDoc").text)
|
172
|
+
@key = @doc.css("#{root_tag} Clave").text
|
173
|
+
@document.key = @key
|
174
|
+
@document.issuer_id_number = @doc.css("#{root_tag} NumeroCedulaEmisor").text
|
175
|
+
@document.receiver_id_number = @doc.css("#{root_tag} NumeroCedulaReceptor").text
|
176
|
+
@document.message = @doc.css("#{root_tag} Mensaje").text
|
177
|
+
@document.details = @doc.css("#{root_tag} DetalleMensaje").text
|
178
|
+
@document.number = @doc.css("#{root_tag} NumeroConsecutivoReceptor").text[10..-1].to_i
|
179
|
+
@document.document_situation = @key[41]
|
180
|
+
@document.security_code = @key[42..-1]
|
181
|
+
@document.total = @doc.css("#{root_tag} TotalFactura").text
|
182
|
+
@document.tax = @doc.css("#{root_tag} MontoTotalImpuesto").text
|
183
|
+
end
|
179
184
|
end
|
180
185
|
end
|
181
186
|
end
|