facturacr 0.1.2 → 0.1.3
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 +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: []
|