facturacr 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/.DS_Store +0 -0
  3. data/.gitignore +9 -0
  4. data/.travis.yml +4 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +7 -0
  7. data/README.md +181 -0
  8. data/Rakefile +10 -0
  9. data/bin/console +14 -0
  10. data/bin/setup +7 -0
  11. data/bin/signer/signer.jar +0 -0
  12. data/config/config.yml +10 -0
  13. data/exe/facturacr +3 -0
  14. data/facturacr.gemspec +31 -0
  15. data/lib/facturacr.rb +37 -0
  16. data/lib/facturacr/api.rb +68 -0
  17. data/lib/facturacr/api/document_status.rb +36 -0
  18. data/lib/facturacr/builder.rb +223 -0
  19. data/lib/facturacr/cli.rb +116 -0
  20. data/lib/facturacr/cli/generate.rb +133 -0
  21. data/lib/facturacr/configuration.rb +51 -0
  22. data/lib/facturacr/credit_note.rb +33 -0
  23. data/lib/facturacr/debit_note.rb +33 -0
  24. data/lib/facturacr/document.rb +171 -0
  25. data/lib/facturacr/document/code.rb +4 -0
  26. data/lib/facturacr/document/exoneration.rb +49 -0
  27. data/lib/facturacr/document/fax.rb +24 -0
  28. data/lib/facturacr/document/identification_document.rb +41 -0
  29. data/lib/facturacr/document/issuer.rb +56 -0
  30. data/lib/facturacr/document/item.rb +67 -0
  31. data/lib/facturacr/document/location.rb +49 -0
  32. data/lib/facturacr/document/phone.rb +22 -0
  33. data/lib/facturacr/document/phone_type.rb +37 -0
  34. data/lib/facturacr/document/receiver.rb +59 -0
  35. data/lib/facturacr/document/reference.rb +46 -0
  36. data/lib/facturacr/document/regulation.rb +26 -0
  37. data/lib/facturacr/document/summary.rb +64 -0
  38. data/lib/facturacr/document/tax.rb +44 -0
  39. data/lib/facturacr/invoice.rb +34 -0
  40. data/lib/facturacr/signed_document.rb +19 -0
  41. data/lib/facturacr/signer/signer.rb +245 -0
  42. data/lib/facturacr/ticket.rb +34 -0
  43. data/lib/facturacr/version.rb +3 -0
  44. data/lib/facturacr/xml_document.rb +161 -0
  45. data/resources/FacturaElectronica_V.4.2.xsd +1571 -0
  46. data/resources/NotaCreditoElectronica_V4.2.xsd +1657 -0
  47. data/resources/credit_note.xml +86 -0
  48. data/resources/data.yml +73 -0
  49. data/resources/debit_note.xml +86 -0
  50. data/resources/invoice.xml +94 -0
  51. data/resources/pruebas.xml +37 -0
  52. data/resources/test.p12 +0 -0
  53. metadata +235 -0
@@ -0,0 +1,223 @@
1
+ require 'facturacr/document'
2
+
3
+ module FE
4
+ class Builder
5
+
6
+ def id_document(args = {})
7
+ FE::Document::IdentificationDocument.new type: args[:type], number: args[:number]
8
+ end
9
+
10
+ def location(args = {})
11
+ FE::Document::Location.new province: args[:province],county: args[:county], district: args[:district], others: args[:others]
12
+ end
13
+
14
+ def phone(args = {})
15
+ args.reverse_merge!({
16
+ country_code: "506"
17
+ })
18
+ FE::Document::Phone.new country_code: args[:country_code], number: args[:number]
19
+ end
20
+
21
+ def fax(args = {})
22
+ args.reverse_merge!({
23
+ country_code: "506"
24
+ })
25
+
26
+ FE::Document::Fax.new country_code: args[:country_code], number: args[:number]
27
+ end
28
+
29
+ def issuer(args = {})
30
+ if args[:identification_document].is_a?(Hash)
31
+ id_document = self.id_document(args[:identification_document])
32
+ else
33
+ id_document = args[:id_document]
34
+ end
35
+
36
+ if args[:location].is_a?(Hash)
37
+ location = self.location(args[:location])
38
+ else
39
+ location = args[:location]
40
+ end
41
+
42
+ if args[:phone].is_a?(Hash)
43
+ phone = self.phone(args[:phone])
44
+ else
45
+ phone = args[:phone]
46
+ end
47
+
48
+ if args[:fax].is_a?(Hash)
49
+ fax = self.fax(args[:fax])
50
+ else
51
+ fax = args[:fax]
52
+ end
53
+
54
+ FE::Document::Issuer.new name: args[:name], identification_document: id_document, location: location, phone: phone, fax: fax, email: args[:email]
55
+ end
56
+
57
+ def receiver(args={})
58
+ if args[:identification_document].is_a?(Hash)
59
+ id_document = self.id_document(args[:identification_document])
60
+ else
61
+ id_document = args[:id_document]
62
+ end
63
+
64
+ if args[:location].is_a?(Hash)
65
+ location = self.location(args[:location])
66
+ else
67
+ location = args[:location]
68
+ end
69
+
70
+ if args[:phone].is_a?(Hash)
71
+ phone = self.phone(args[:phone])
72
+ else
73
+ phone = args[:phone]
74
+ end
75
+
76
+ if args[:fax].is_a?(Hash)
77
+ fax = self.fax(args[:fax])
78
+ else
79
+ fax = args[:fax]
80
+ end
81
+
82
+ FE::Document::Receiver.new name: args[:name], identification_document: id_document, location: location, phone: phone, fax: fax, email: args[:email]
83
+
84
+ end
85
+
86
+ def summary(args={})
87
+ FE::Document::Summary.new args
88
+ end
89
+
90
+ def item(args={})
91
+ txs = args.delete(:taxes)
92
+ taxes = []
93
+ if txs.is_a?(Array) && txs.first.is_a?(Hash)
94
+ txs.each do |t|
95
+ taxes << FE::Document::Tax.new(code: "01", rate: 13, total: 13)
96
+ end
97
+ else
98
+ taxes = txs
99
+ end
100
+ args[:taxes] = taxes
101
+ FE::Document::Item.new(args)
102
+ end
103
+
104
+ def invoice(args = {})
105
+ if args[:issuer].is_a?(Hash)
106
+ issuer = self.issuer(args[:issuer])
107
+ else
108
+ issuer = args[:issuer]
109
+ end
110
+
111
+ if args[:receiver].is_a?(Hash)
112
+ receiver = self.receiver(args[:receiver])
113
+ else
114
+ receiver = args[:receiver]
115
+ end
116
+
117
+ if args[:items].is_a?(Array) && args[:items].first.is_a?(Hash)
118
+ tmp_items = []
119
+ args[:items].each do |i|
120
+ tmp_items << self.item(i)
121
+ end
122
+ items = tmp_items
123
+ else
124
+ items = args[:items]
125
+ end
126
+
127
+ if args[:summary].is_a?(Hash)
128
+ summary = self.summary(args[:summary])
129
+ else
130
+ summary = args[:summary]
131
+ end
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
+ end
134
+
135
+ def credit_note(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
+
164
+ if args[:references].is_a?(Array) && args[:references].first.is_a?(Hash)
165
+ references = []
166
+ args[:references].each do |r|
167
+ references << self.reference(r)
168
+ end
169
+ else
170
+ references = args[:references]
171
+ end
172
+
173
+ FE::CreditNote.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
174
+ end
175
+
176
+ def debit_note(args = {})
177
+ if args[:issuer].is_a?(Hash)
178
+ issuer = self.issuer(args[:issuer])
179
+ else
180
+ issuer = args[:issuer]
181
+ end
182
+
183
+ if args[:receiver].is_a?(Hash)
184
+ receiver = self.receiver(args[:receiver])
185
+ else
186
+ receiver = args[:receiver]
187
+ end
188
+
189
+ if args[:items].is_a?(Array) && args[:items].first.is_a?(Hash)
190
+ tmp_items = []
191
+ args[:items].each do |i|
192
+ tmp_items << self.item(i)
193
+ end
194
+ items = tmp_items
195
+ else
196
+ items = args[:items]
197
+ end
198
+
199
+ if args[:summary].is_a?(Hash)
200
+ summary = self.summary(args[:summary])
201
+ else
202
+ summary = args[:summary]
203
+ end
204
+
205
+ if args[:references].is_a?(Array) && args[:references].first.is_a?(Hash)
206
+ references = []
207
+ args[:references].each do |r|
208
+ references << self.reference(r)
209
+ end
210
+ else
211
+ references = args[:references]
212
+ end
213
+
214
+ 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
+ end
216
+
217
+
218
+ def reference(args={})
219
+ FE::Document::Reference.new args
220
+ end
221
+
222
+ end
223
+ end
@@ -0,0 +1,116 @@
1
+ require 'thor'
2
+ require 'awesome_print'
3
+ require 'facturacr'
4
+ require 'facturacr/cli/generate'
5
+ require 'fileutils'
6
+ =begin
7
+ require 'facturacr/document'
8
+ require 'facturacr/document/fax'
9
+ require 'facturacr/document/identification_document'
10
+ require 'facturacr/document/issuer'
11
+ require 'facturacr/document/receiver'
12
+ require 'facturacr/document/location'
13
+ require 'facturacr/document/phone_type'
14
+ require 'facturacr/document/phone'
15
+ require 'facturacr/document/item'
16
+ require 'facturacr/document/tax'
17
+ require 'facturacr/document/summary'
18
+ require 'facturacr/document/regulation'
19
+ require 'facturacr/document/reference'
20
+
21
+ require 'facturacr/invoice'
22
+ require 'facturacr/credit_note'
23
+ require 'facturacr/signer/signer'
24
+ require 'facturacr/api'
25
+ require 'facturacr/signed_document'
26
+ require 'facturacr/builder'
27
+ require 'facturacr/xml_document'
28
+ =end
29
+ module FE
30
+
31
+ module Utils
32
+ def self.configure(path)
33
+ FE.configure do |config|
34
+ config.mode = "file"
35
+ config.file_path = path
36
+ config.environment = "test"
37
+ end
38
+ end
39
+ end
40
+
41
+ class CLI < Thor
42
+
43
+ desc "check KEY", "checks a sent document in the api"
44
+ method_option :config_file, aliases: '-c', desc: "default configuration file", default: "tmp/config.yml"
45
+
46
+ def check(key)
47
+ FE::Utils.configure(options[:config_file])
48
+ api = FE::Api.new
49
+ document_status = api.get_document_status(key)
50
+ ap document_status.to_h
51
+ end
52
+
53
+ desc "generate DOCUMENT ARGS", "generate xml documents"
54
+ subcommand "generate", Generate
55
+
56
+ desc "sign_document XML_IN XML_OUT", "signs the xml document and stores the signed document in the output path"
57
+ method_option :config_file, aliases: '-c', desc: "default configuration file", default: "tmp/config.yml"
58
+ def sign_document(xml_in, xml_out)
59
+ FE::Utils.configure(options[:config_file])
60
+ signer = FE::JavaSigner.new FE.configuration.key_path, FE.configuration.key_password, xml_in, xml_out
61
+ signer.sign
62
+ end
63
+
64
+ desc "send_document SIGNED_XML", "sends the SIGNED_XML file to the API"
65
+ method_option :config_file, aliases: '-c', desc: "default configuration file", default: "tmp/config.yml"
66
+ def send_document(path)
67
+ FE::Utils.configure(options[:config_file])
68
+ xml_document = FE::XmlDocument.new(path)
69
+ invoice = xml_document.document
70
+ signed_document = FE::SignedDocument.new(invoice,path)
71
+ api = FE::Api.new
72
+ if api.send_document(signed_document.payload)
73
+ puts "Document Sent".green
74
+ puts "KEY: #{invoice.key}"
75
+ puts "Wait 5 seconds before check..."
76
+ sleep 5
77
+ invoke :check, [invoice.key], :config_file=>options[:config_file]
78
+ else
79
+ puts "ERROR".red
80
+ ap api.errors
81
+ raise "Sending Document Error"
82
+ end
83
+ end
84
+
85
+ desc "test_invoice NUMBER DATA_PATH", "generates a test invoice and sends it to the api"
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
+
97
+ desc "setup PATH", "will create a tmp directory with a sample config file and a sample data file at the specified path."
98
+ def setup(path)
99
+ puts "\n\n SETUP FACTURACR \n\n"
100
+ say "A tmp directory will be created at: #{path}"
101
+ say "config.yml file will be copied to #{path}"
102
+ say "data.yml file will be copied to #{path}"
103
+ answer = ask("Are you sure you want to continue?", :yellow, limited_to: ["y","n"])
104
+ if answer.downcase == "y"
105
+ 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"
108
+ say "Done.", :green
109
+ else
110
+ say "Ok. Bye", :green
111
+ end
112
+ end
113
+
114
+
115
+ end
116
+ end
@@ -0,0 +1,133 @@
1
+ require 'active_support/core_ext/hash/indifferent_access'
2
+ module FE
3
+ class CLI < Thor
4
+ class Generate < Thor
5
+
6
+ desc "invoice", "generates an XML invoice"
7
+ method_option :config_file, aliases: '-c', desc: "default configuration file", default: "tmp/config.yml"
8
+ method_option :number, aliases: '-n', desc: "set the number of the document"
9
+ method_option :sign, type: :boolean, default: false
10
+ method_option :send, type: :boolean, default: false
11
+ method_option :data_path, default: "tmp/data.yml"
12
+ method_option :output_path, desc: "path to save the output"
13
+ def invoice
14
+ data = YAML.load_file(options[:data_path]).with_indifferent_access
15
+ builder = FE::Builder.new
16
+
17
+ invoice = builder.invoice(data[:document])
18
+ invoice.date = Time.now
19
+ invoice.number = options[:number].to_i if options[:number].present?
20
+
21
+ if options[:output_path]
22
+ output_path = output_path
23
+ else
24
+ output_path = "tmp/#{invoice.key}.xml"
25
+ end
26
+
27
+ write(invoice, output_path)
28
+ print_details(invoice)
29
+ sign(output_path, options) if options[:sign]
30
+ send_document("#{output_path}.signed.xml") if options[:sign] && options[:send]
31
+
32
+ end
33
+
34
+
35
+ desc "credit_note", "generates an XML credit note"
36
+ method_option :config_file, aliases: '-c', desc: "default configuration file", default: "tmp/config.yml"
37
+ method_option :number, aliases: '-n', desc: "set the number of the document"
38
+ method_option :sign, type: :boolean, default: false
39
+ method_option :send, type: :boolean, default: false
40
+ method_option :data_path, default: "tmp/data.yml"
41
+ method_option :output_path, desc: "path to save the output"
42
+ method_option :invoice_number, desc: "invoice key", aliases: "-i"
43
+ method_option :invoice_date, desc: "invoice date", aliases: '-d'
44
+ def credit_note
45
+ data = YAML.load_file(options[:data_path]).with_indifferent_access
46
+ builder = FE::Builder.new
47
+
48
+ credit_note = builder.credit_note(data[:document])
49
+ if options[:invoice_number].present?
50
+ date = DateTime.parse(options[:invoice_date])
51
+ credit_note.references = [FE::Document::Reference.new(document_type: "01", code: "01", reason: "Anula documento", number: options[:invoice_number], date: date)]
52
+ end
53
+
54
+ credit_note.date = Time.now
55
+ credit_note.number = options[:number].to_i if options[:number].present?
56
+
57
+ if options[:output_path]
58
+ output_path = output_path
59
+ else
60
+ output_path = "tmp/#{credit_note.key}.xml"
61
+ end
62
+
63
+ write(credit_note, output_path)
64
+ print_details(credit_note)
65
+ sign(output_path, options) if options[:sign]
66
+ send_document("#{output_path}.signed.xml") if options[:sign] && options[:send]
67
+
68
+ end
69
+
70
+ desc "debit_note", "generates an XML debit note"
71
+ method_option :config_file, aliases: '-c', desc: "default configuration file", default: "tmp/config.yml"
72
+ method_option :number, aliases: '-n', desc: "set the number of the document"
73
+ method_option :sign, type: :boolean, default: false
74
+ method_option :send, type: :boolean, default: false
75
+ method_option :data_path, default: "tmp/data.yml"
76
+ method_option :output_path, desc: "path to save the output"
77
+ method_option :invoice_number, desc: "invoice key", aliases: "-i"
78
+ method_option :invoice_date, desc: "invoice date", aliases: '-d'
79
+ def debit_note
80
+ data = YAML.load_file(options[:data_path]).with_indifferent_access
81
+ builder = FE::Builder.new
82
+
83
+ debit_note = builder.debit_note(data[:document])
84
+ if options[:invoice_number].present?
85
+ date = DateTime.parse(options[:invoice_date])
86
+ debit_note.references = [FE::Document::Reference.new(document_type: "01", code: "01", reason: "Anula documento", number: options[:invoice_number], date: date)]
87
+ end
88
+
89
+ debit_note.date = Time.now
90
+ debit_note.number = options[:number].to_i if options[:number].present?
91
+
92
+ if options[:output_path]
93
+ output_path = output_path
94
+ else
95
+ output_path = "tmp/#{debit_note.key}.xml"
96
+ end
97
+
98
+ write(debit_note, output_path)
99
+ print_details(debit_note)
100
+ sign(output_path, options) if options[:sign]
101
+ send_document("#{output_path}.signed.xml") if options[:sign] && options[:send]
102
+
103
+ end
104
+
105
+ no_commands{
106
+ def write(document, output_path)
107
+ File.open(output_path, 'w'){|f| f.write(document.generate.gsub("\n",""))}
108
+ end
109
+
110
+ def print_details(document)
111
+ puts "Details:"
112
+ puts "ISSUER: #{FE::Document::IdentificationDocument::TYPES[document.issuer.identification_document.document_type]} #{document.issuer.identification_document.raw_id_number} - #{document.issuer.name}"
113
+ puts "RECEIVER: #{FE::Document::IdentificationDocument::TYPES[document.receiver.identification_document.document_type]} #{document.receiver.identification_document.raw_id_number} - #{document.receiver.name}"
114
+ puts "KEY: #{document.key}"
115
+ end
116
+
117
+ def sign(path, options)
118
+ puts " => SIGN ..............."
119
+ cli = FE::CLI.new
120
+ cli.options = {config_file: options[:config_file]}
121
+ cli.sign_document("#{path}", "#{path}.signed.xml")
122
+ end
123
+
124
+ def send_document(path)
125
+ puts "=> SEND TO API ................."
126
+ cli = FE::CLI.new
127
+ cli.options = {config_file: options[:config_file]}
128
+ cli.send_document(path)
129
+ end
130
+ }
131
+ end
132
+ end
133
+ end