facturacr 0.1.2

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.
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