facturacr 0.1.2 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +59 -52
- data/lib/facturacr/api.rb +9 -2
- data/lib/facturacr/builder.rb +39 -0
- data/lib/facturacr/cli/generate.rb +81 -5
- data/lib/facturacr/cli.rb +12 -20
- data/lib/facturacr/document/reference.rb +1 -1
- data/lib/facturacr/reception_message.rb +118 -0
- data/lib/facturacr/ticket.rb +1 -2
- data/lib/facturacr/version.rb +1 -1
- data/lib/facturacr/xml_document.rb +135 -115
- data/lib/facturacr.rb +1 -0
- data/resources/reception_message.xml +86 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5fe54ad4526251387084b74247d3897b87781a56
|
4
|
+
data.tar.gz: 549cdc504c1ee37a4cac7c15cc424031fe043c0f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0dcf0d1781a0be675f4a33053dd834b6c60a1e6e0d6e8a2a4d7b82bdbe3ae63aa8549664b6e4f281104175daa50969eb520c8447051ab827359dd9700e14a343
|
7
|
+
data.tar.gz: 8ca8978495e86e774cdb566fb6162d6ea112c369e84b70ce46f7c54100b05dfd31757a4e473c4eec48813e267c823f42d5050b845886f73c6fac8a21eb0a58b0
|
data/README.md
CHANGED
@@ -30,45 +30,45 @@ O instale el gem
|
|
30
30
|
Para generar documentos:
|
31
31
|
|
32
32
|
```ruby
|
33
|
-
|
33
|
+
require 'facturacr'
|
34
34
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
35
|
+
id_document = FE::Document::IdentificationDocument.new type: "01", number: "112345678"
|
36
|
+
phone = FE::Document::Phone.new country_code: "506", "22222222"
|
37
|
+
location = FE::Document::Location.new province: "1",county: "01", district: "01", others: "Otras señas"
|
38
|
+
issuer = FE::Document::Issuer.new name: "EMISON EJEMPLO", identification_document: id_document, location: location, phone: phone, email: "emisor@ejemplo.com"
|
39
39
|
|
40
|
-
|
41
|
-
|
40
|
+
reciever_id_document = FE::Document::IdentificationDocument.new type: "02", number: "3102123456"
|
41
|
+
receiver = FE::Document::Receiver.new name: "RECEPTOR EJEMPLO", identification_document: id_document
|
42
42
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
43
|
+
items = []
|
44
|
+
items << FE::Document::Item.new(code: "001", line_number: 1, quantity: 1, unit: "Sp", description: "Desarrollo de Software y Mantenimiento", unit_price: 300, subtotal: 300, net_total: 300, total: 300)
|
45
|
+
taxes = [FE::Document::Tax.new(code: "01", rate: 13, total: (100 * 0.13))]
|
46
|
+
items << FE::Document::Item.new(code: "002", line_number: 2, quantity: 2, unit: "Unid", description: "Impresora de POS", unit_price: 50, total: 100, taxes: taxes, net_total: 113, subtotal: 100)
|
47
|
+
summary = FE::Document::Summary.new currency: "USD", exchange_rate: 575, services_exent_total: 300, goods_taxable_total: 100, exent_total: 300, taxable_total: 100, subtotal: 400, gross_total: 400, tax_total: 13, net_total: 413
|
48
48
|
|
49
49
|
|
50
|
-
|
50
|
+
invoice = FE::Invoice.new date: date, issuer: issuer, receiver: receiver, number: number, items: items, condition: condition, credit_term: credit_term, summary: summary, security_code: "12345678", document_situation: "1"
|
51
51
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
52
|
+
# Para generar el XML como string
|
53
|
+
xml = invoice.generate
|
54
|
+
|
55
|
+
# Escribir el archivo
|
56
|
+
File.open("/path/to/file.xml","w"){|f| f.write(xml)}
|
57
57
|
```
|
58
58
|
|
59
59
|
Para configurar el API / Firmador:
|
60
60
|
```ruby
|
61
61
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
62
|
+
FE.configure do |config|
|
63
|
+
config.api_username "su_usuario_api_atv"
|
64
|
+
config.api_password = "su_password_api_atv"
|
65
|
+
config.key_path = "tmp/llave_criptografica.p12"
|
66
|
+
config.key_password = "99999999999999"
|
67
|
+
# api hacienda: valores default
|
68
|
+
config.api_client_id = 'api-stag'
|
69
|
+
config.documents_endpoint = "https://api.comprobanteselectronicos.go.cr/recepcion-sandbox/v1"
|
70
|
+
config.authentication_endpoint = "https://idp.comprobanteselectronicos.go.cr/auth/realms/rut-stag/protocol/openid-connect/token"s
|
71
|
+
end
|
72
72
|
```
|
73
73
|
|
74
74
|
Para firmar documentos. (Debe tener java instalado)
|
@@ -81,27 +81,27 @@ Para firmar documentos. (Debe tener java instalado)
|
|
81
81
|
|
82
82
|
Para enviar documentos al API
|
83
83
|
```ruby
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
84
|
+
|
85
|
+
api = FE::API.new
|
86
|
+
# document is FE::Document
|
87
|
+
signed_document = FE::SignedDocument.new(document,path)
|
88
|
+
api = FE::Api.new
|
89
|
+
if api.send_document(signed_document.payload)
|
90
|
+
puts "Document sent!"
|
91
|
+
else
|
92
|
+
puts "Error: #{api.errors}"
|
93
|
+
end
|
94
94
|
```
|
95
95
|
|
96
96
|
Para chequear el estatus del documento
|
97
97
|
```ruby
|
98
98
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
99
|
+
api = FE::API.new
|
100
|
+
FE::Utils.configure(options[:config_file])
|
101
|
+
api = FE::Api.new
|
102
|
+
document_status = api.get_document_status(key)
|
103
|
+
puts document_status.to_h
|
104
|
+
#=> {key: "50601011600310112345600100010100000000011999999999", date: "2016-01-01T00:00:00-0600", status: "aceptado", datails: ""}
|
105
105
|
```
|
106
106
|
|
107
107
|
## Lína de comando
|
@@ -112,15 +112,22 @@ Para utilziar la línea de comando y generar documentos de prueba, debe generar
|
|
112
112
|
|
113
113
|
Modifique los archivos config.yml y data.yml para utilziar la línea de comando
|
114
114
|
|
115
|
-
|
115
|
+
Resumen de la funcionalidad de la línea de comando
|
116
116
|
|
117
|
-
$ facturacr
|
118
|
-
|
117
|
+
$ facturacr help
|
119
118
|
Commands:
|
120
|
-
facturacr
|
121
|
-
facturacr generate
|
122
|
-
facturacr generate
|
123
|
-
facturacr generate
|
119
|
+
facturacr check KEY # checks a sent document in the api
|
120
|
+
facturacr generate DOCUMENT ARGS # generate xml documents
|
121
|
+
facturacr generate credit_note # generates an XML credit note
|
122
|
+
facturacr generate debit_note # generates an XML debit note
|
123
|
+
facturacr generate help [COMMAND] # Describe subcommands or one specific subcommand
|
124
|
+
facturacr generate invoice # generates an XML invoice
|
125
|
+
facturacr generate reception_message # generate an XML reception message
|
126
|
+
facturacr generate ticket # generates an XML ticket
|
127
|
+
facturacr help [COMMAND] # Describe available commands or one specific command
|
128
|
+
facturacr send_document SIGNED_XML # sends the SIGNED_XML file to the API
|
129
|
+
facturacr setup PATH # will create a tmp directory with a sample config file and a sample data file at the specified path.
|
130
|
+
facturacr sign XML_IN XML_OUT # signs the xml document and stores the signed document in the output path
|
124
131
|
|
125
132
|
Por ejemplo, para generar una factura
|
126
133
|
|
@@ -156,7 +163,7 @@ Adicionalmente se pueden especificar las opciones para firma y enviar a hacienda
|
|
156
163
|
|
157
164
|
Para firmar documentos
|
158
165
|
|
159
|
-
$ facturacr
|
166
|
+
$ facturacr sign /path/to/unsigned.xml /path/to/signed.xml
|
160
167
|
|
161
168
|
Para enviar documentos
|
162
169
|
|
data/lib/facturacr/api.rb
CHANGED
@@ -24,7 +24,8 @@ module FE
|
|
24
24
|
|
25
25
|
@token
|
26
26
|
rescue => e
|
27
|
-
|
27
|
+
puts "AUTH ERROR: #{e.message}".red
|
28
|
+
raise e
|
28
29
|
end
|
29
30
|
|
30
31
|
|
@@ -45,7 +46,13 @@ module FE
|
|
45
46
|
|
46
47
|
def get_document(key)
|
47
48
|
authenticate
|
48
|
-
response = RestClient.get "#{@document_endpoint}/
|
49
|
+
response = RestClient.get "#{@document_endpoint}/comprobantes/#{key}", {:Authorization=> "bearer #{@token}", content_type: :json}
|
50
|
+
JSON.parse(response)
|
51
|
+
end
|
52
|
+
|
53
|
+
def get_documents
|
54
|
+
authenticate
|
55
|
+
response = RescClient.get "#{@document_endpoint}/comprobantes", {:Authorization => "bearer #{@token}", content_type: :json }
|
49
56
|
JSON.parse(response)
|
50
57
|
end
|
51
58
|
|
data/lib/facturacr/builder.rb
CHANGED
@@ -132,6 +132,38 @@ module FE
|
|
132
132
|
FE::Invoice.new date: args[:date], issuer: issuer, receiver: receiver, number: args[:number], items: items, condition: args[:condition], credit_term: args[:credit_term], summary: summary, security_code: args[:security_code], document_situation: args[:document_situation]
|
133
133
|
end
|
134
134
|
|
135
|
+
def ticket(args = {})
|
136
|
+
if args[:issuer].is_a?(Hash)
|
137
|
+
issuer = self.issuer(args[:issuer])
|
138
|
+
else
|
139
|
+
issuer = args[:issuer]
|
140
|
+
end
|
141
|
+
|
142
|
+
if args[:receiver].is_a?(Hash)
|
143
|
+
receiver = self.receiver(args[:receiver])
|
144
|
+
else
|
145
|
+
receiver = args[:receiver]
|
146
|
+
end
|
147
|
+
|
148
|
+
if args[:items].is_a?(Array) && args[:items].first.is_a?(Hash)
|
149
|
+
tmp_items = []
|
150
|
+
args[:items].each do |i|
|
151
|
+
tmp_items << self.item(i)
|
152
|
+
end
|
153
|
+
items = tmp_items
|
154
|
+
else
|
155
|
+
items = args[:items]
|
156
|
+
end
|
157
|
+
|
158
|
+
if args[:summary].is_a?(Hash)
|
159
|
+
summary = self.summary(args[:summary])
|
160
|
+
else
|
161
|
+
summary = args[:summary]
|
162
|
+
end
|
163
|
+
FE::Ticket.new date: args[:date], issuer: issuer, receiver: receiver, number: args[:number], items: items, condition: args[:condition], credit_term: args[:credit_term], summary: summary, security_code: args[:security_code], document_situation: args[:document_situation]
|
164
|
+
end
|
165
|
+
|
166
|
+
|
135
167
|
def credit_note(args = {})
|
136
168
|
if args[:issuer].is_a?(Hash)
|
137
169
|
issuer = self.issuer(args[:issuer])
|
@@ -214,6 +246,13 @@ module FE
|
|
214
246
|
FE::DebitNote.new date: args[:date], issuer: issuer, receiver: receiver, number: args[:number], items: items, condition: args[:condition], credit_term: args[:credit_term], summary: summary, security_code: args[:security_code], document_situation: args[:document_situation], references: references
|
215
247
|
end
|
216
248
|
|
249
|
+
def reception_message(args={})
|
250
|
+
FE::ReceptionMessage.new number: args[:number], date: args[:date], key: args[:key],
|
251
|
+
issuer_id_number: args[:issuer_id_number], issuer_id_type: args[:issuer_id_type],
|
252
|
+
receiver_id_number: args[:receiver_id_number], receiver_id_type: args[:receiver_id_type],
|
253
|
+
message: args[:message], details: args[:details], tax: args[:tax], total: args[:total], security_code: args[:security_code], document_situation: args[:document_situation]
|
254
|
+
end
|
255
|
+
|
217
256
|
|
218
257
|
def reference(args={})
|
219
258
|
FE::Document::Reference.new args
|
@@ -19,7 +19,7 @@ module FE
|
|
19
19
|
invoice.number = options[:number].to_i if options[:number].present?
|
20
20
|
|
21
21
|
if options[:output_path]
|
22
|
-
output_path = output_path
|
22
|
+
output_path = options[:output_path]
|
23
23
|
else
|
24
24
|
output_path = "tmp/#{invoice.key}.xml"
|
25
25
|
end
|
@@ -55,7 +55,7 @@ module FE
|
|
55
55
|
credit_note.number = options[:number].to_i if options[:number].present?
|
56
56
|
|
57
57
|
if options[:output_path]
|
58
|
-
output_path = output_path
|
58
|
+
output_path = options[:output_path]
|
59
59
|
else
|
60
60
|
output_path = "tmp/#{credit_note.key}.xml"
|
61
61
|
end
|
@@ -90,7 +90,7 @@ module FE
|
|
90
90
|
debit_note.number = options[:number].to_i if options[:number].present?
|
91
91
|
|
92
92
|
if options[:output_path]
|
93
|
-
output_path = output_path
|
93
|
+
output_path = options[:output_path]
|
94
94
|
else
|
95
95
|
output_path = "tmp/#{debit_note.key}.xml"
|
96
96
|
end
|
@@ -102,6 +102,82 @@ module FE
|
|
102
102
|
|
103
103
|
end
|
104
104
|
|
105
|
+
desc "ticket", "generates an XML ticket"
|
106
|
+
method_option :config_file, aliases: '-c', desc: "default configuration file", default: "tmp/config.yml"
|
107
|
+
method_option :number, aliases: '-n', desc: "set the number of the document"
|
108
|
+
method_option :sign, type: :boolean, default: false
|
109
|
+
method_option :send, type: :boolean, default: false
|
110
|
+
method_option :data_path, default: "tmp/data.yml"
|
111
|
+
method_option :output_path, desc: "path to save the output"
|
112
|
+
def ticket
|
113
|
+
data = YAML.load_file(options[:data_path]).with_indifferent_access
|
114
|
+
builder = FE::Builder.new
|
115
|
+
|
116
|
+
ticket = builder.ticket(data[:document])
|
117
|
+
ticket.date = Time.now
|
118
|
+
ticket.number = options[:number].to_i if options[:number].present?
|
119
|
+
|
120
|
+
if options[:output_path]
|
121
|
+
output_path = options[:output_path]
|
122
|
+
else
|
123
|
+
output_path = "tmp/#{ticket.key}.xml"
|
124
|
+
end
|
125
|
+
|
126
|
+
write(ticket, output_path)
|
127
|
+
print_details(ticket)
|
128
|
+
sign(output_path, options) if options[:sign]
|
129
|
+
send_document("#{output_path}.signed.xml") if options[:sign] && options[:send]
|
130
|
+
|
131
|
+
end
|
132
|
+
desc "reception_message", "generate an XML reception message"
|
133
|
+
method_option :xml_in, aliases: '-i', desc: "path to the xml invoice to accept"
|
134
|
+
method_option :number, aliases: '-n', desc: "the number of the document"
|
135
|
+
method_option :output_path, desc: "path to save the output"
|
136
|
+
method_option :sign, type: :boolean, default: false
|
137
|
+
method_option :send, type: :boolean, default: false
|
138
|
+
method_option :action, aliases: '-a', desc: "action 1=total 2=partial 3=reject", limited_to: [1,2,3]
|
139
|
+
method_option :details, desc: "details to include"
|
140
|
+
method_option :document_situation, desc: "the situation of the document", default: "1"
|
141
|
+
method_option :config_file, aliases: '-c', desc: "default configuration file", default: "tmp/config.yml"
|
142
|
+
def reception_message
|
143
|
+
doc = XmlDocument.new(options[:xml_in])
|
144
|
+
|
145
|
+
builder = FE::Builder.new
|
146
|
+
|
147
|
+
# Roles of receiver and issuer are inverted because they are extracted from the document.
|
148
|
+
# The ISSUER of the reception message is the RECEIVER of the document
|
149
|
+
message = builder.reception_message({
|
150
|
+
key: doc.document.key,
|
151
|
+
date: doc.document.date,
|
152
|
+
number: options[:number],
|
153
|
+
issuer_id_number: doc.document.receiver.identification_document.id_number,
|
154
|
+
issuer_id_type: doc.document.receiver.identification_document.document_type,
|
155
|
+
receiver_id_number: doc.document.issuer.identification_document.id_number,
|
156
|
+
receiver_id_type: doc.document.issuer.identification_document.document_type,
|
157
|
+
message: options[:action],
|
158
|
+
tax: doc.document.summary.tax_total,
|
159
|
+
total: doc.document.summary.net_total,
|
160
|
+
document_situation: options[:document_situation]
|
161
|
+
})
|
162
|
+
|
163
|
+
message.security_code = (SecureRandom.random_number * (10**8)).round.to_s
|
164
|
+
if options[:output_path]
|
165
|
+
output_path = options[:output_path]
|
166
|
+
else
|
167
|
+
output_path = "tmp/msj-receptor-#{message.key}.xml"
|
168
|
+
end
|
169
|
+
write(message, output_path)
|
170
|
+
puts "Mensaje Receptor"
|
171
|
+
puts "Accion: #{FE::ReceptionMessage::MESSAGE_TYPES[message.message]}"
|
172
|
+
puts "Factura: #{doc.document.key}"
|
173
|
+
puts "Ced. Receptor: #{message.receiver_id_number}"
|
174
|
+
puts "Ced. Emisor: #{message.issuer_id_number}"
|
175
|
+
|
176
|
+
sign(output_path, options) if options[:sign]
|
177
|
+
send_document("#{output_path}.signed.xml") if options[:sign] && options[:send]
|
178
|
+
|
179
|
+
end
|
180
|
+
|
105
181
|
no_commands{
|
106
182
|
def write(document, output_path)
|
107
183
|
File.open(output_path, 'w'){|f| f.write(document.generate.gsub("\n",""))}
|
@@ -115,10 +191,10 @@ module FE
|
|
115
191
|
end
|
116
192
|
|
117
193
|
def sign(path, options)
|
118
|
-
puts "
|
194
|
+
puts "=> SIGN ................."
|
119
195
|
cli = FE::CLI.new
|
120
196
|
cli.options = {config_file: options[:config_file]}
|
121
|
-
cli.
|
197
|
+
cli.sign("#{path}", "#{path}.signed.xml")
|
122
198
|
end
|
123
199
|
|
124
200
|
def send_document(path)
|
data/lib/facturacr/cli.rb
CHANGED
@@ -53,9 +53,9 @@ module FE
|
|
53
53
|
desc "generate DOCUMENT ARGS", "generate xml documents"
|
54
54
|
subcommand "generate", Generate
|
55
55
|
|
56
|
-
desc "
|
56
|
+
desc "sign XML_IN XML_OUT", "signs the xml document and stores the signed document in the output path"
|
57
57
|
method_option :config_file, aliases: '-c', desc: "default configuration file", default: "tmp/config.yml"
|
58
|
-
def
|
58
|
+
def sign(xml_in, xml_out)
|
59
59
|
FE::Utils.configure(options[:config_file])
|
60
60
|
signer = FE::JavaSigner.new FE.configuration.key_path, FE.configuration.key_password, xml_in, xml_out
|
61
61
|
signer.sign
|
@@ -66,15 +66,18 @@ module FE
|
|
66
66
|
def send_document(path)
|
67
67
|
FE::Utils.configure(options[:config_file])
|
68
68
|
xml_document = FE::XmlDocument.new(path)
|
69
|
-
|
70
|
-
|
69
|
+
document = xml_document.document
|
70
|
+
if document.is_a?(FE::ReceptionMessage)
|
71
|
+
document.receiver_id_type = "02"
|
72
|
+
end
|
73
|
+
signed_document = FE::SignedDocument.new(document,path)
|
71
74
|
api = FE::Api.new
|
72
75
|
if api.send_document(signed_document.payload)
|
73
76
|
puts "Document Sent".green
|
74
|
-
puts "KEY: #{
|
77
|
+
puts "KEY: #{document.key}"
|
75
78
|
puts "Wait 5 seconds before check..."
|
76
79
|
sleep 5
|
77
|
-
invoke :check, [
|
80
|
+
invoke :check, [document.key], :config_file=>options[:config_file]
|
78
81
|
else
|
79
82
|
puts "ERROR".red
|
80
83
|
ap api.errors
|
@@ -82,18 +85,7 @@ module FE
|
|
82
85
|
end
|
83
86
|
end
|
84
87
|
|
85
|
-
|
86
|
-
method_option :config_file, aliases: '-c', desc: "default configuration file", default: "tmp/config.yml"
|
87
|
-
def test_invoice(number, data_path)
|
88
|
-
FE::Utils.configure(options[:config_file])
|
89
|
-
puts "Generating document ..."
|
90
|
-
invoke :generate_invoice, [data_path,"tmp/unsigned_#{number}.xml"], number: number
|
91
|
-
puts "Signing document ..."
|
92
|
-
invoke :sign_document, ["tmp/unsigned_#{number}.xml","tmp/#{number}.xml"]
|
93
|
-
puts "Sending ..."
|
94
|
-
invoke :send_document, ["tmp/#{number}.xml"]
|
95
|
-
end
|
96
|
-
|
88
|
+
|
97
89
|
desc "setup PATH", "will create a tmp directory with a sample config file and a sample data file at the specified path."
|
98
90
|
def setup(path)
|
99
91
|
puts "\n\n SETUP FACTURACR \n\n"
|
@@ -103,8 +95,8 @@ module FE
|
|
103
95
|
answer = ask("Are you sure you want to continue?", :yellow, limited_to: ["y","n"])
|
104
96
|
if answer.downcase == "y"
|
105
97
|
FileUtils.mkdir_p "#{path}/tmp"
|
106
|
-
FileUtils.cp "resources/data.yml", "#{path}/tmp/data.yml"
|
107
|
-
FileUtils.cp "config/config.yml", "#{path}/tmp/config.yml"
|
98
|
+
FileUtils.cp "#{FE.root}/resources/data.yml", "#{path}/tmp/data.yml"
|
99
|
+
FileUtils.cp "#{FE.root}/config/config.yml", "#{path}/tmp/config.yml"
|
108
100
|
say "Done.", :green
|
109
101
|
else
|
110
102
|
say "Ok. Bye", :green
|
@@ -17,7 +17,7 @@ module FE
|
|
17
17
|
}
|
18
18
|
|
19
19
|
validates :document_type, presence: true, inclusion: FE::Document::DOCUMENT_TYPES.keys
|
20
|
-
validates :number, presence: true, length: {
|
20
|
+
validates :number, presence: true, length: {maximum: 50}
|
21
21
|
validates :date, presence: true
|
22
22
|
validates :code, presence: true, length: {is: 2}, inclusion: REFERENCE_CODES.keys
|
23
23
|
validates :reason, presence: true, length: {maximum: 180}
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
|
3
|
+
module FE
|
4
|
+
class ReceptionMessage
|
5
|
+
include ActiveModel::Validations
|
6
|
+
|
7
|
+
MESSAGE_TYPES = {
|
8
|
+
"1" => "Aceptado",
|
9
|
+
"2" => "Aceptacion Parcial",
|
10
|
+
"3" => "Rechazado"
|
11
|
+
}
|
12
|
+
attr_accessor :key, :date, :issuer_id_number, :receiver_id_number, :message, :details, :tax, :total, :number, :receiver_id_type, :security_code, :document_situation
|
13
|
+
|
14
|
+
validates :date, presence: true
|
15
|
+
validates :issuer_id_number, presence: true, length: {is: 12}
|
16
|
+
validates :receiver_id_number, presence: true, length: {is: 12}
|
17
|
+
validates :message, presence: true, inclusion: MESSAGE_TYPES.keys
|
18
|
+
validates :tax, numericality: true, if: -> { tax.present? }
|
19
|
+
validates :total, presence: true, numericality: true
|
20
|
+
validates :number, presence: true
|
21
|
+
validates :security_code, presence: true, length: {is: 8}
|
22
|
+
|
23
|
+
def initialize(args = {})
|
24
|
+
@key = args[:key]
|
25
|
+
@date = args[:date]
|
26
|
+
@issuer_id_number = args[:issuer_id_number]
|
27
|
+
@receiver_id_type = args[:receiver_id_type]
|
28
|
+
@receiver_id_number = args[:receiver_id_number]
|
29
|
+
@message = args[:message].to_s
|
30
|
+
@details = args[:details]
|
31
|
+
@tax = args[:tax]
|
32
|
+
@total = args[:total]
|
33
|
+
@number = args[:number].to_i
|
34
|
+
@security_code = args[:security_code]
|
35
|
+
@document_situation = args[:document_situation]
|
36
|
+
@namespaces = {
|
37
|
+
"xmlns:xsi"=>"http://www.w3.org/2001/XMLSchema-instance",
|
38
|
+
"xmlns:xsd"=>"http://www.w3.org/2001/XMLSchema",
|
39
|
+
"xmlns"=>"https://tribunet.hacienda.go.cr/docs/esquemas/2017/v4.2/mensajeReceptor"
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
def key
|
44
|
+
raise "Documento inválido: #{errors.messages}" unless valid?
|
45
|
+
country = "506"
|
46
|
+
day = "%02d" % @date.day
|
47
|
+
month = "%02d" % @date.month
|
48
|
+
year = "%02d" % (@date.year - 2000)
|
49
|
+
id_number = @receiver_id_number.rjust(12,"0")
|
50
|
+
|
51
|
+
situation = @document_situation
|
52
|
+
security_code = @security_code
|
53
|
+
|
54
|
+
result = "#{country}#{day}#{month}#{year}#{id_number}#{sequence}#{situation}#{security_code}"
|
55
|
+
raise "The key is invalid: #{result}" unless result.length.eql?(50)
|
56
|
+
|
57
|
+
result
|
58
|
+
end
|
59
|
+
|
60
|
+
def headquarters
|
61
|
+
@headquarters ||= "001"
|
62
|
+
end
|
63
|
+
|
64
|
+
def terminal
|
65
|
+
@terminal ||= "00001"
|
66
|
+
end
|
67
|
+
|
68
|
+
def sequence
|
69
|
+
if @message.eql?("1")
|
70
|
+
@document_type = "05"
|
71
|
+
elsif @message.eql?("2")
|
72
|
+
@document_type = "06"
|
73
|
+
elsif @message.eql?("3")
|
74
|
+
@document_type = "07"
|
75
|
+
end
|
76
|
+
cons = ("%010d" % @number)
|
77
|
+
"#{headquarters}#{terminal}#{@document_type}#{cons}"
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
|
82
|
+
def build_xml
|
83
|
+
raise "Documento inválido: #{errors.messages}" unless valid?
|
84
|
+
builder = Nokogiri::XML::Builder.new
|
85
|
+
|
86
|
+
builder.MensajeReceptor(@namespaces) do |xml|
|
87
|
+
xml.Clave key
|
88
|
+
xml.NumeroCedulaEmisor @issuer_id_number
|
89
|
+
xml.FechaEmisionDoc @date.xmlschema
|
90
|
+
xml.Mensaje @message
|
91
|
+
xml.DetalleMensaje @details if @details
|
92
|
+
xml.MontoTotalImpuesto @tax if @tax
|
93
|
+
xml.TotalFactura @total
|
94
|
+
xml.NumeroCedulaReceptor @receiver_id_number
|
95
|
+
xml.NumConsecutivoReceptor sequence
|
96
|
+
end
|
97
|
+
|
98
|
+
builder
|
99
|
+
end
|
100
|
+
|
101
|
+
def generate
|
102
|
+
build_xml.to_xml(:save_with => Nokogiri::XML::Node::SaveOptions::AS_XML)
|
103
|
+
end
|
104
|
+
|
105
|
+
def api_payload
|
106
|
+
payload = {}
|
107
|
+
payload[:clave] = key
|
108
|
+
payload[:fecha] = @date.xmlschema
|
109
|
+
payload[:emisor] = {
|
110
|
+
tipoIdentificacion: @receiver_id_type,
|
111
|
+
numeroIdentificacion: @receiver_id_number
|
112
|
+
}
|
113
|
+
payload
|
114
|
+
end
|
115
|
+
|
116
|
+
|
117
|
+
end
|
118
|
+
end
|
data/lib/facturacr/ticket.rb
CHANGED
@@ -21,8 +21,7 @@ module FE
|
|
21
21
|
@namespaces = {
|
22
22
|
"xmlns:xsi"=>"http://www.w3.org/2001/XMLSchema-instance",
|
23
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"
|
24
|
+
"xmlns"=>"https://tribunet.hacienda.go.cr/docs/esquemas/2017/v4.2/tiqueteElectronico"
|
26
25
|
}
|
27
26
|
end
|
28
27
|
|
data/lib/facturacr/version.rb
CHANGED
@@ -21,138 +21,158 @@ module FE
|
|
21
21
|
@document = FE::CreditNote.new
|
22
22
|
elsif root_tag.eql?("NotaDebitoElectronica")
|
23
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
|
24
28
|
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
29
|
|
34
|
-
@
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
@issuer.location = location
|
30
|
+
if @document.is_a?(FE::Document)
|
31
|
+
@document.date = DateTime.parse(@doc.css("#{root_tag} FechaEmision").text)
|
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
|
44
39
|
|
45
|
-
|
46
|
-
@issuer.
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
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
|
52
50
|
|
53
|
-
|
54
|
-
|
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
|
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
|
58
53
|
end
|
59
|
-
|
60
|
-
|
61
|
-
@receiver.foreign_id_number = @doc.css("#{root_tag} Receptor IdentificacionExtranjero").text
|
54
|
+
if !@doc.css("#{root_tag} Emisor Fax").empty?
|
55
|
+
@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
|
62
56
|
end
|
63
|
-
@
|
57
|
+
@issuer.email = @doc.css("#{root_tag} Emisor CorreoElectronico").text
|
58
|
+
|
59
|
+
unless @doc.css("#{root_tag} Receptor").empty?
|
60
|
+
@receiver = FE::Document::Receiver.new
|
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
|
64
65
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
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
|
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?
|
73
70
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
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)
|
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?
|
99
87
|
end
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
item.
|
88
|
+
@items = []
|
89
|
+
@doc.css("#{root_tag} DetalleServicio LineaDetalle").each do |line|
|
90
|
+
item = FE::Document::Item.new
|
91
|
+
item.line_number = line.css("NumeroLinea").text.to_i
|
92
|
+
item.code = line.css("Codigo Codigo").text
|
93
|
+
item.quantity = line.css("Cantidad").text
|
94
|
+
item.unit = line.css("UnidadMedida").text
|
95
|
+
item.description = line.css("Detalle").text
|
96
|
+
item.unit_price = line.css("PrecioUnitario").text.to_f
|
97
|
+
item.total = line.css("MontoTotal").text.to_f
|
98
|
+
item.discount = line.css("MontoDescuento").text.to_f unless line.css("MontoDescuento").empty?
|
99
|
+
item.discount_reason = line.css("NaturalezaDescuento").text unless line.css("NaturalezaDescuento").empty?
|
100
|
+
item.subtotal = line.css("SubTotal").text.to_f
|
101
|
+
item.net_total = line.css("MontoTotalLinea").text.to_f
|
102
|
+
item.taxes = []
|
103
|
+
line.css("Impuesto").each do |tax|
|
104
|
+
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)
|
105
|
+
end
|
106
|
+
unless line.css("Exoneracion").empty?
|
107
|
+
exo = FE::Document::Exoneration.new
|
108
|
+
exo.document_type = line.css("Exoneracion TipoDocumento").text
|
109
|
+
exo.document_number = line.css("Exoneracion NumeroDocumento").text
|
110
|
+
exo.institution = line.css("Exoneracion NombreInstitucion").text
|
111
|
+
exo.date = DateTime.parse(line.css("Exoneracion FechaEmision").text)
|
112
|
+
exo.total_tax = line.css("Exoneracion MontoImpuesto").text.to_f
|
113
|
+
exo.percentage = line.css("Exoneracion PorcentajeCompra").text.to_i
|
114
|
+
item.exoneration = exo
|
115
|
+
end
|
116
|
+
@items << item
|
109
117
|
end
|
110
|
-
@items << item
|
111
|
-
end
|
112
118
|
|
113
119
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
120
|
+
@summary = FE::Document::Summary.new
|
121
|
+
sum = @doc.css("#{root_tag} ResumenFactura")
|
122
|
+
@summary.currency = sum.css("CodigoMoneda").text
|
123
|
+
@summary.exchange_rate = sum.css("TipoCambio").text.to_f
|
124
|
+
@summary.services_taxable_total = sum.css("TotalServGravados").text.to_f
|
125
|
+
@summary.services_exent_total = sum.css("TotalServExentos").text.to_f
|
126
|
+
@summary.goods_taxable_total = sum.css("TotalMercanciasGravadas").text.to_f
|
127
|
+
@summary.goods_exent_total = sum.css("TotalMercanciasExentas").text.to_f
|
128
|
+
@summary.taxable_total = sum.css("TotalGravado").text.to_f
|
129
|
+
@summary.exent_total = sum.css("TotalExento").text.to_f
|
130
|
+
@summary.subtotal = sum.css("TotalVenta").text.to_f
|
131
|
+
@summary.discount_total = sum.css("TotalDescuentos").text.to_f
|
132
|
+
@summary.gross_total = sum.css("TotalVentaNeta").text.to_f
|
133
|
+
@summary.tax_total = sum.css("TotalImpuesto").text.to_f
|
134
|
+
@summary.net_total = sum.css("TotalComprobante").text.to_f
|
129
135
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
136
|
+
refs = @doc.css("#{root_tag} InformacionReferencia")
|
137
|
+
@references = []
|
138
|
+
unless refs.empty?
|
139
|
+
refs.each do |ref|
|
140
|
+
reference = FE::Document::Reference.new
|
141
|
+
reference.document_type = ref.css("TipoDoc")
|
142
|
+
reference.number = ref.css("Numero")
|
143
|
+
reference.date = ref.css("FechaEmision")
|
144
|
+
reference.code = ref.css("Codigo")
|
145
|
+
reference.reason = ref.css("Razon")
|
146
|
+
@references << reference
|
147
|
+
end
|
141
148
|
end
|
142
|
-
end
|
143
149
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
150
|
+
reg = @doc.css("#{root_tag} Normativa")
|
151
|
+
@regulation = FE::Document::Regulation.new
|
152
|
+
@regulation.number = reg.css("NumeroResolucion").text
|
153
|
+
@regulation.date = reg.css("FechaResolucion").text
|
148
154
|
|
149
155
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
+
@document.issuer = @issuer
|
157
|
+
@document.receiver = @receiver
|
158
|
+
@document.items = @items
|
159
|
+
@document.summary = @summary
|
160
|
+
@document.references = @references
|
161
|
+
@document.regulation = @regulation
|
162
|
+
else
|
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
176
|
else
|
157
177
|
raise "#{xml_path} does not exist"
|
158
178
|
end
|
data/lib/facturacr.rb
CHANGED
@@ -0,0 +1,86 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<NotaCreditoElectronica xmlns="https://tribunet.hacienda.go.cr/docs/esquemas/2017/v4.2/notaCreditoElectronica" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
3
|
+
<Clave>50601041800310263679500100001030000000111199999999</Clave>
|
4
|
+
<NumeroConsecutivo>00100001030000000111</NumeroConsecutivo>
|
5
|
+
<FechaEmision>2018-04-01T01:00:18-06:00</FechaEmision>
|
6
|
+
<Emisor>
|
7
|
+
<Nombre>EMISOR EJEMPLO</Nombre>
|
8
|
+
<Identificacion>
|
9
|
+
<Tipo>02</Tipo>
|
10
|
+
<Numero>3102123456</Numero>
|
11
|
+
</Identificacion>
|
12
|
+
<Ubicacion>
|
13
|
+
<Provincia>1</Provincia>
|
14
|
+
<Canton>01</Canton>
|
15
|
+
<Distrito>01</Distrito>
|
16
|
+
<OtrasSenas>Direccion</OtrasSenas>
|
17
|
+
</Ubicacion>
|
18
|
+
<Telefono>
|
19
|
+
<CodigoPais>506</CodigoPais>
|
20
|
+
<NumTelefono>22222222</NumTelefono>
|
21
|
+
</Telefono>
|
22
|
+
<CorreoElectronico>email@ejemplo.com</CorreoElectronico>
|
23
|
+
</Emisor>
|
24
|
+
<Receptor>
|
25
|
+
<Nombre>RECEPTOR EJEMPLO</Nombre>
|
26
|
+
<Identificacion>
|
27
|
+
<Tipo>01</Tipo>
|
28
|
+
<Numero>112345678</Numero>
|
29
|
+
</Identificacion>
|
30
|
+
</Receptor>
|
31
|
+
<CondicionVenta>01</CondicionVenta>
|
32
|
+
<MedioPago>01</MedioPago>
|
33
|
+
<DetalleServicio>
|
34
|
+
<LineaDetalle>
|
35
|
+
<NumeroLinea>1</NumeroLinea>
|
36
|
+
<Cantidad>1</Cantidad>
|
37
|
+
<UnidadMedida>Sp</UnidadMedida>
|
38
|
+
<Detalle>Honorarios por servicios profesionales</Detalle>
|
39
|
+
<PrecioUnitario>300</PrecioUnitario>
|
40
|
+
<MontoTotal>300</MontoTotal>
|
41
|
+
<SubTotal>300</SubTotal>
|
42
|
+
<MontoTotalLinea>300</MontoTotalLinea>
|
43
|
+
</LineaDetalle>
|
44
|
+
<LineaDetalle>
|
45
|
+
<NumeroLinea>2</NumeroLinea>
|
46
|
+
<Cantidad>2</Cantidad>
|
47
|
+
<UnidadMedida>Unid</UnidadMedida>
|
48
|
+
<Detalle>Articulo de Venta</Detalle>
|
49
|
+
<PrecioUnitario>50</PrecioUnitario>
|
50
|
+
<MontoTotal>100</MontoTotal>
|
51
|
+
<SubTotal>100</SubTotal>
|
52
|
+
<Impuesto>
|
53
|
+
<Codigo>01</Codigo>
|
54
|
+
<Tarifa>13</Tarifa>
|
55
|
+
<Monto>13</Monto>
|
56
|
+
</Impuesto>
|
57
|
+
<MontoTotalLinea>113</MontoTotalLinea>
|
58
|
+
</LineaDetalle>
|
59
|
+
</DetalleServicio>
|
60
|
+
<ResumenFactura>
|
61
|
+
<CodigoMoneda>USD</CodigoMoneda>
|
62
|
+
<TipoCambio>575</TipoCambio>
|
63
|
+
<TotalServGravados>0.0</TotalServGravados>
|
64
|
+
<TotalServExentos>300.0</TotalServExentos>
|
65
|
+
<TotalMercanciasGravadas>100.0</TotalMercanciasGravadas>
|
66
|
+
<TotalMercanciasExentas>0.0</TotalMercanciasExentas>
|
67
|
+
<TotalGravado>100.0</TotalGravado>
|
68
|
+
<TotalExento>300.0</TotalExento>
|
69
|
+
<TotalVenta>400.0</TotalVenta>
|
70
|
+
<TotalDescuentos>0.0</TotalDescuentos>
|
71
|
+
<TotalVentaNeta>400.0</TotalVentaNeta>
|
72
|
+
<TotalImpuesto>13.0</TotalImpuesto>
|
73
|
+
<TotalComprobante>413.0</TotalComprobante>
|
74
|
+
</ResumenFactura>
|
75
|
+
<InformacionReferencia>
|
76
|
+
<TipoDoc>01</TipoDoc>
|
77
|
+
<Numero>50601041800310263679500100001010000000109199999999</Numero>
|
78
|
+
<FechaEmision>2018-04-01T00:19:27-06:00</FechaEmision>
|
79
|
+
<Codigo>01</Codigo>
|
80
|
+
<Razon>Anula documento</Razon>
|
81
|
+
</InformacionReferencia>
|
82
|
+
<Normativa>
|
83
|
+
<NumeroResolucion>DGT-R-48-2016</NumeroResolucion>
|
84
|
+
<FechaResolucion>20-02-2017 13:22:22</FechaResolucion>
|
85
|
+
</Normativa>
|
86
|
+
</NotaCreditoElectronica>
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: facturacr
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Josef Sauter
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-04-
|
11
|
+
date: 2018-04-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -196,6 +196,7 @@ files:
|
|
196
196
|
- lib/facturacr/document/summary.rb
|
197
197
|
- lib/facturacr/document/tax.rb
|
198
198
|
- lib/facturacr/invoice.rb
|
199
|
+
- lib/facturacr/reception_message.rb
|
199
200
|
- lib/facturacr/signed_document.rb
|
200
201
|
- lib/facturacr/signer/signer.rb
|
201
202
|
- lib/facturacr/ticket.rb
|
@@ -208,6 +209,7 @@ files:
|
|
208
209
|
- resources/debit_note.xml
|
209
210
|
- resources/invoice.xml
|
210
211
|
- resources/pruebas.xml
|
212
|
+
- resources/reception_message.xml
|
211
213
|
- resources/test.p12
|
212
214
|
homepage: https://github.com/apokalipto/facturacr
|
213
215
|
licenses: []
|