facturacr 1.1.0 → 1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/facturacr.gemspec +1 -1
- data/lib/facturacr.rb +20 -19
- data/lib/facturacr/api.rb +1 -1
- data/lib/facturacr/builder.rb +4 -4
- data/lib/facturacr/configuration.rb +1 -1
- data/lib/facturacr/credit_note.rb +1 -0
- data/lib/facturacr/data.rb +41 -0
- data/lib/facturacr/data_provider.rb +1 -1
- data/lib/facturacr/debit_note.rb +1 -0
- data/lib/facturacr/document.rb +39 -26
- data/lib/facturacr/document/exoneration.rb +1 -0
- data/lib/facturacr/document/fax.rb +1 -4
- data/lib/facturacr/document/identification_document.rb +0 -4
- data/lib/facturacr/document/issuer.rb +0 -3
- data/lib/facturacr/document/item.rb +19 -13
- data/lib/facturacr/document/location.rb +0 -4
- data/lib/facturacr/document/other_text.rb +1 -5
- data/lib/facturacr/document/phone.rb +1 -3
- data/lib/facturacr/document/phone_type.rb +0 -4
- data/lib/facturacr/document/receiver.rb +0 -1
- data/lib/facturacr/document/reference.rb +7 -8
- data/lib/facturacr/document/summary.rb +8 -8
- data/lib/facturacr/document/tax.rb +1 -1
- data/lib/facturacr/invoice.rb +1 -1
- data/lib/facturacr/reception_message.rb +8 -8
- data/lib/facturacr/signed_document.rb +1 -1
- data/lib/facturacr/signer/signer.rb +1 -2
- data/lib/facturacr/version.rb +1 -1
- data/lib/facturacr/xml_document.rb +27 -23
- metadata +8 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 5a8fe4de4d95d4c43eadb53200c1ce6b847554a1970fb9cb803ffdd9b3a5ea93
|
4
|
+
data.tar.gz: a4ba9d8357de2b21305f5c32577e25433d0c477c4996cfdfdc8b42e1f5665935
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c339e27c622a15a04f9d55924b40bd78529fc31c284517e6dfa6b42473f1fa99993b8667e1a3b3960872c65cef8f674951a5f820564ed3754e159013ac2d0bd6
|
7
|
+
data.tar.gz: 3640e8f8d11d531ad16e801820c9eeb1c3671988338d5cc029548839338ade8ca4b99e467864647940a2851441d1c1a8ade3711d19b92b1bbff9011cc154f4a3
|
data/facturacr.gemspec
CHANGED
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.require_paths = ["lib"]
|
19
19
|
|
20
20
|
spec.add_development_dependency "bundler", "~> 1.10"
|
21
|
-
spec.add_development_dependency "rake", "
|
21
|
+
spec.add_development_dependency "rake", ">= 12.3.3"
|
22
22
|
spec.add_development_dependency "minitest", '~> 5.11'
|
23
23
|
spec.add_development_dependency "minitest-colorize", '~> 0.0'
|
24
24
|
|
data/lib/facturacr.rb
CHANGED
@@ -1,24 +1,25 @@
|
|
1
1
|
require 'awesome_print'
|
2
2
|
|
3
|
-
require 'facturacr'
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
3
|
+
#require 'facturacr'
|
4
|
+
require_relative 'facturacr/configuration'
|
5
|
+
require_relative 'facturacr/element'
|
6
|
+
require_relative 'facturacr/invoice'
|
7
|
+
require_relative 'facturacr/credit_note'
|
8
|
+
require_relative 'facturacr/export_invoice'
|
9
|
+
require_relative 'facturacr/purchase_invoice'
|
10
|
+
require_relative 'facturacr/debit_note'
|
11
|
+
require_relative 'facturacr/ticket'
|
12
|
+
require_relative 'facturacr/document'
|
13
|
+
require_relative 'facturacr/signed_document'
|
14
|
+
require_relative 'facturacr/xml_document'
|
15
|
+
require_relative 'facturacr/api'
|
16
|
+
require_relative 'facturacr/builder'
|
17
|
+
require_relative 'facturacr/version'
|
18
|
+
require_relative 'facturacr/data_provider'
|
19
|
+
require_relative 'facturacr/signer/signer'
|
20
|
+
require_relative 'facturacr/reception_message'
|
21
|
+
require_relative 'facturacr/error'
|
22
|
+
require_relative 'facturacr/data'
|
22
23
|
|
23
24
|
module FE
|
24
25
|
class << self
|
data/lib/facturacr/api.rb
CHANGED
data/lib/facturacr/builder.rb
CHANGED
@@ -134,7 +134,7 @@ module FE
|
|
134
134
|
else
|
135
135
|
summary = args[:summary]
|
136
136
|
end
|
137
|
-
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]
|
137
|
+
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], version: args[:version], economic_activity: args[:economic_activity]
|
138
138
|
end
|
139
139
|
|
140
140
|
def ticket(args = {})
|
@@ -165,7 +165,7 @@ module FE
|
|
165
165
|
else
|
166
166
|
summary = args[:summary]
|
167
167
|
end
|
168
|
-
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]
|
168
|
+
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], version: args[:version], economic_activity: args[:economic_activity]
|
169
169
|
end
|
170
170
|
|
171
171
|
|
@@ -207,7 +207,7 @@ module FE
|
|
207
207
|
references = args[:references]
|
208
208
|
end
|
209
209
|
|
210
|
-
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
|
210
|
+
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, version: args[:version], economic_activity: args[:economic_activity]
|
211
211
|
end
|
212
212
|
|
213
213
|
def debit_note(args = {})
|
@@ -248,7 +248,7 @@ module FE
|
|
248
248
|
references = args[:references]
|
249
249
|
end
|
250
250
|
|
251
|
-
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
|
251
|
+
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, version: args[:version], economic_activity: args[:economic_activity]
|
252
252
|
end
|
253
253
|
|
254
254
|
def reception_message(args={})
|
@@ -29,7 +29,7 @@ module FE
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def read_config_file
|
32
|
-
if file? && @file_path && File.
|
32
|
+
if file? && @file_path && File.exist?(@file_path)
|
33
33
|
template = ERB.new(File.read(@file_path))
|
34
34
|
result = YAML.load(template.result(binding))
|
35
35
|
result[@environment].each do |k,v|
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'rest-client'
|
3
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
4
|
+
|
5
|
+
module FE
|
6
|
+
class Data
|
7
|
+
|
8
|
+
ENDPOINT = "https://api.hacienda.go.cr"
|
9
|
+
|
10
|
+
def self.contributor(id_number)
|
11
|
+
response = RestClient.get "#{ENDPOINT}/fe/ae?identificacion=#{id_number}"
|
12
|
+
return JSON.parse(response.body).with_indifferent_access
|
13
|
+
rescue => e
|
14
|
+
puts "FE::Data.contributor(#{id_number}) #{e.message}"
|
15
|
+
return nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.exchange_rate(currency = "USD")
|
19
|
+
if currency.eql?("USD")
|
20
|
+
path = "tc/dolar"
|
21
|
+
elsif currency.eql?("EUR")
|
22
|
+
path = "tc/euro"
|
23
|
+
else
|
24
|
+
raise "#{currency} is not a valid argument"
|
25
|
+
end
|
26
|
+
response = RestClient.get "#{ENDPOINT}/indicadores/#{path}"
|
27
|
+
return JSON.parse(response.body).with_indifferent_access
|
28
|
+
rescue => e
|
29
|
+
puts "FE::Data.exchange_rate(#{currency}) #{e.message}"
|
30
|
+
return nil
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.exonerations(id_number)
|
34
|
+
response = RestClient.get "#{ENDPOINT}/fe/ex?identificacion=#{id_number}"
|
35
|
+
return JSON.parse(response.body).with_indifferent_access
|
36
|
+
rescue => e
|
37
|
+
puts "FE::Data.exonerations(#{id_number}) #{e.message}"
|
38
|
+
return nil
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -8,7 +8,7 @@ module FE
|
|
8
8
|
def initialize(source, data)
|
9
9
|
source = source.to_s.to_sym
|
10
10
|
raise ArgumentError, "source (#{source}) is not valid" if !SOURCES.include?(source)
|
11
|
-
raise ArgumentError, "#{data} does not exist" if source.eql?(:file) && !File.
|
11
|
+
raise ArgumentError, "#{data} does not exist" if source.eql?(:file) && !File.exist?(data)
|
12
12
|
|
13
13
|
if source.eql?(:string)
|
14
14
|
@contents = data
|
data/lib/facturacr/debit_note.rb
CHANGED
data/lib/facturacr/document.rb
CHANGED
@@ -41,12 +41,12 @@ module FE
|
|
41
41
|
"3" => "Sin Internet"
|
42
42
|
}.freeze
|
43
43
|
|
44
|
+
attr_writer :headquarters, :terminal, :key
|
44
45
|
attr_accessor :serial, :date, :issuer, :receiver, :condition, :credit_term,
|
45
46
|
:payment_type, :service_type, :reference_information,
|
46
47
|
:regulation, :number, :document_type, :security_code,
|
47
48
|
:items, :references, :namespaces, :summary, :document_situation,
|
48
49
|
:headquarters, :terminal, :others, :key, :economic_activity, :other_charges, :version
|
49
|
-
|
50
50
|
validates :version, presence: true
|
51
51
|
validates :economic_activity, presence: true, if: ->{ version.eql?("4.3") }
|
52
52
|
validates :date, presence: true
|
@@ -63,7 +63,8 @@ module FE
|
|
63
63
|
validates :references, presence: true, if: -> {document_type.eql?("02") || document_type.eql?("03")}
|
64
64
|
validates :items, presence:true
|
65
65
|
validate :payment_types_ok?
|
66
|
-
|
66
|
+
validate :other_charges_ok?, if: -> {@other_charges.present?}
|
67
|
+
|
67
68
|
def initialize
|
68
69
|
raise FE::Error "Subclasses must implement this method"
|
69
70
|
end
|
@@ -103,17 +104,17 @@ module FE
|
|
103
104
|
cons = ("%010d" % @number)
|
104
105
|
"#{headquarters}#{terminal}#{@document_type}#{cons}"
|
105
106
|
end
|
106
|
-
|
107
|
+
|
107
108
|
def version_42?
|
108
109
|
@version.eql?("4.2")
|
109
110
|
end
|
110
|
-
|
111
|
+
|
111
112
|
def version_43?
|
112
113
|
@version.eql?("4.3")
|
113
114
|
end
|
114
115
|
|
115
116
|
def build_xml
|
116
|
-
raise FE::Error.new "Documento inválido", class: self.class, messages: errors.messages unless valid?
|
117
|
+
raise FE::Error.new "Documento inválido #{errors.messages}", class: self.class, messages: errors.messages unless valid?
|
117
118
|
builder = Nokogiri::XML::Builder.new(encoding: 'UTF-8')
|
118
119
|
|
119
120
|
builder.send(document_tag, @namespaces) do |xml|
|
@@ -138,8 +139,13 @@ module FE
|
|
138
139
|
end
|
139
140
|
end
|
140
141
|
|
142
|
+
if other_charges.present?
|
143
|
+
@other_charges.each do |other_charge|
|
144
|
+
other_charge.build_xml(xml, self)
|
145
|
+
end
|
146
|
+
end
|
141
147
|
|
142
|
-
other_charges.build_xml(xml,self) if other_charges.present? && version_43? # see this
|
148
|
+
#other_charges.build_xml(xml,self) if other_charges.present? && version_43? # see this
|
143
149
|
|
144
150
|
summary.build_xml(xml, self)
|
145
151
|
|
@@ -187,6 +193,15 @@ module FE
|
|
187
193
|
|
188
194
|
private
|
189
195
|
|
196
|
+
def other_charges_ok?
|
197
|
+
if @other_charges.is_a?(Array)
|
198
|
+
errors.add :other_charges, "invalid other_charges: the length can't be greater than 15" if @other_charges.length > 15
|
199
|
+
errors.add :other_charges, "invalid other_charges: not included" unless @other_charges.all? {|i| FE::Document::OtherCharges::OTHER_DOCUMENT_TYPES.include?(i.document_type)}
|
200
|
+
else
|
201
|
+
errors.add :other_charges, "invalid other_charges: not array"
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
190
205
|
def payment_types_ok?
|
191
206
|
errors.add :payment_type, "missing payment type" if @payment_type.nil?
|
192
207
|
if @payment_type.is_a?(Array)
|
@@ -198,25 +213,23 @@ module FE
|
|
198
213
|
end
|
199
214
|
|
200
215
|
end
|
201
|
-
|
202
|
-
|
203
|
-
|
204
216
|
end
|
205
217
|
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
218
|
+
require_relative 'document/code'
|
219
|
+
require_relative 'document/exoneration'
|
220
|
+
require_relative 'document/fax'
|
221
|
+
require_relative 'document/identification_document'
|
222
|
+
require_relative 'document/issuer'
|
223
|
+
require_relative 'document/item'
|
224
|
+
require_relative 'document/location'
|
225
|
+
require_relative 'document/phone_type'
|
226
|
+
require_relative 'document/phone'
|
227
|
+
require_relative 'document/fax'
|
228
|
+
require_relative 'document/receiver'
|
229
|
+
require_relative 'document/reference'
|
230
|
+
require_relative 'document/regulation'
|
231
|
+
require_relative 'document/summary'
|
232
|
+
require_relative 'document/tax'
|
233
|
+
require_relative 'document/other_text'
|
234
|
+
require_relative 'document/other_content'
|
235
|
+
require_relative 'document/other_charges'
|
@@ -21,6 +21,7 @@ module FE
|
|
21
21
|
validates :institution, presence: true, length: { maximum: 160 }
|
22
22
|
validates :date, presence: true
|
23
23
|
validates :total_tax,presence: true
|
24
|
+
validates :percentage, presence: true, numericality: {greater_than_or_equal_to: 0, less_than_or_equal_to: 100, only_integer: true}
|
24
25
|
|
25
26
|
|
26
27
|
def initialize(args={})
|
@@ -6,7 +6,7 @@ module FE
|
|
6
6
|
UNITS = %w[ Al Alc Cm I Os Spe St Sp m kg s A K mol cd m² m³ m/s m/s² 1/m kg/m³ A/m² A/m mol/m³ cd/m² 1 rad sr Hz N Pa J W C V F Ω S Wb T H °C lm
|
7
7
|
lx Bq Gy Sv kat Pa·s N·m N/m rad/s rad/s² W/m² J/K J/(kg·K) J/kg W/(m·K) J/m³ V/m C/m³ C/m² F/m H/m J/mol J/(mol·K)
|
8
8
|
C/kg Gy/s W/sr W/(m²·sr) kat/m³ min h d º ´ ´´ L t Np B eV u ua Unid Gal g Km ln cm mL mm Oz Otros].freeze
|
9
|
-
SERVICE_UNITS = %w[Al Alc Os Spe Sp St]
|
9
|
+
SERVICE_UNITS = %w[Al Alc Os Spe Sp St min h I Cm]
|
10
10
|
CODE_TYPES = {
|
11
11
|
'01' => 'Código del producto del vendedor',
|
12
12
|
'02' => 'Código del producto del comprador',
|
@@ -30,12 +30,12 @@ module FE
|
|
30
30
|
validates :discount_reason, presence: true, if: -> { discount.present? }
|
31
31
|
validates :subtotal, presence: true
|
32
32
|
validates :taxable_base, presence: true, if: ->{ taxes.map{ |t| t.code.eql?("07")}.include?(true) && document.version_43? }
|
33
|
-
validates :net_tax,presence:true, if: ->{ exoneration.present? }
|
33
|
+
validates :net_tax,presence:true, if: ->{ taxes.map{ |t| t.exoneration.present? }.include?(true) }
|
34
34
|
validates :net_total, presence: true
|
35
35
|
validates :comercial_code_type, inclusion: CODE_TYPES.keys, if: -> { comercial_code.present? }
|
36
36
|
validates :comercial_code, presence: true, length: {maximum: 20}
|
37
|
-
validates :code, length: {maximum: 13}
|
38
|
-
|
37
|
+
validates :code, presence: true, length: {maximum: 13}, if: :code_is_mandatory?
|
38
|
+
|
39
39
|
validate :calculations_ok?
|
40
40
|
|
41
41
|
def initialize(args = {})
|
@@ -57,21 +57,21 @@ module FE
|
|
57
57
|
@exoneration = args[:exoneration]
|
58
58
|
@net_tax = args[:net_tax]
|
59
59
|
@tariff_item = args[:tariff_item]
|
60
|
-
|
60
|
+
@taxable_base = args[:taxable_base]
|
61
61
|
|
62
62
|
end
|
63
63
|
|
64
64
|
def build_xml(node, document)
|
65
65
|
@document = document
|
66
66
|
@document_type = document.document_type
|
67
|
-
raise FE::Error.new("item invalid",class: self.class, messages: errors.messages) unless valid?
|
67
|
+
raise FE::Error.new("item invalid: #{ errors.messages.map{|k,v| "#{k}=#{v.join(". ")}"}.join("; ")}",class: self.class, messages: errors.messages) unless valid?
|
68
68
|
|
69
69
|
node = Nokogiri::XML::Builder.new if node.nil?
|
70
70
|
node.LineaDetalle do |x|
|
71
71
|
x.NumeroLinea @line_number
|
72
72
|
|
73
73
|
x.PartidaArancelaria @tariff_item if @tariff_item.present? && document.version_43?
|
74
|
-
|
74
|
+
|
75
75
|
if document.version_43?
|
76
76
|
x.Codigo @code if @code.present?
|
77
77
|
end
|
@@ -94,7 +94,7 @@ module FE
|
|
94
94
|
x.Detalle @description
|
95
95
|
x.PrecioUnitario @unit_price
|
96
96
|
x.MontoTotal @total
|
97
|
-
|
97
|
+
|
98
98
|
if document.version_42?
|
99
99
|
x.MontoDescuento @discount if @discount.present?
|
100
100
|
x.NaturalezaDescuento @discount_reason if @discount_reason.present?
|
@@ -113,19 +113,25 @@ module FE
|
|
113
113
|
tax.build_xml(x,document)
|
114
114
|
end
|
115
115
|
|
116
|
-
x.ImpuestoNeto @net_tax if @net_tax.present?
|
116
|
+
x.ImpuestoNeto @net_tax if @net_tax.present?
|
117
117
|
x.MontoTotalLinea @net_total
|
118
118
|
end
|
119
119
|
|
120
120
|
end
|
121
|
-
|
122
|
-
|
123
|
-
|
121
|
+
|
122
|
+
|
123
|
+
|
124
124
|
def calculations_ok?
|
125
125
|
errors.add :total, :invalid_amount, message: 'invalid amount' if (@total - (@quantity * @unit_price).round(5)).abs > 1
|
126
126
|
end
|
127
127
|
|
128
|
-
|
128
|
+
def code_is_mandatory?
|
129
|
+
if Time.zone.now >= Time.zone.parse("2020-12-01").beginning_of_day
|
130
|
+
true
|
131
|
+
else
|
132
|
+
false
|
133
|
+
end
|
134
|
+
end
|
129
135
|
end
|
130
136
|
end
|
131
137
|
end
|
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'facturacr/document'
|
2
|
-
|
3
1
|
module FE
|
4
2
|
class Document
|
5
3
|
class Reference < Element
|
@@ -36,10 +34,11 @@ module FE
|
|
36
34
|
|
37
35
|
|
38
36
|
validates :document_type, presence: true, inclusion: DOCUMENT_TYPES.keys
|
39
|
-
validates :number, presence: true, length: {maximum: 50}, if: ->{ document_type.present? }
|
40
37
|
validates :date, presence: true
|
41
|
-
|
42
|
-
validates :
|
38
|
+
|
39
|
+
validates :number, presence: true, length: {maximum: 50}, if: ->{ document_type.present? && document_type != "13"}
|
40
|
+
validates :code, presence: true, length: {is: 2}, inclusion: REFERENCE_CODES.keys, if: ->{ document_type.present? && document_type != "13" }
|
41
|
+
validates :reason, presence: true, length: {maximum: 180}, if: ->{ document_type.present? && document_type != "13" }
|
43
42
|
|
44
43
|
def initialize(args={})
|
45
44
|
@document_type = args[:document_type]
|
@@ -54,10 +53,10 @@ module FE
|
|
54
53
|
node = Nokogiri::XML::Builder.new if node.nil?
|
55
54
|
node.InformacionReferencia do |xml|
|
56
55
|
xml.TipoDoc @document_type
|
57
|
-
xml.Numero @number
|
56
|
+
xml.Numero @number if @number.present?
|
58
57
|
xml.FechaEmision @date.xmlschema
|
59
|
-
xml.Codigo @code
|
60
|
-
xml.Razon @reason
|
58
|
+
xml.Codigo @code if @code.present?
|
59
|
+
xml.Razon @reason if @reason.present?
|
61
60
|
end
|
62
61
|
end
|
63
62
|
end
|
@@ -5,7 +5,7 @@ module FE
|
|
5
5
|
|
6
6
|
attr_accessor :currency, :exchange_rate, :services_taxable_total, :services_exent_total, :services_exonerate_total,
|
7
7
|
:goods_taxable_total,:goods_exent_total,:goods_exonerate_total, :taxable_total, :exent_total,:exonerate_total,
|
8
|
-
:subtotal, :discount_total, :gross_total, :tax_total,:total_iva_returned,:
|
8
|
+
:subtotal, :discount_total, :gross_total, :tax_total,:total_iva_returned,:total_other_charges, :net_total,
|
9
9
|
:with_credit_card, :document_type, :has_exoneration, :medical_services_condition
|
10
10
|
|
11
11
|
validates :currency, presence: true
|
@@ -35,7 +35,7 @@ module FE
|
|
35
35
|
@gross_total = args[:gross_total].to_f
|
36
36
|
@tax_total = args[:tax_total].to_f
|
37
37
|
@total_iva_returned = args[:total_iva_returned].to_f
|
38
|
-
@
|
38
|
+
@total_other_charges = args[:total_other_charges].to_f
|
39
39
|
@net_total = args[:net_total].to_f
|
40
40
|
@has_exoneration = args[:has_exoneration] || false
|
41
41
|
@medical_services_condition = args[:medical_services_condition] || false
|
@@ -44,17 +44,17 @@ module FE
|
|
44
44
|
def build_xml(node, document)
|
45
45
|
@document = document
|
46
46
|
@document_type = document.document_type
|
47
|
-
raise FE::Error.new("summary invalid",class: self.class, messages: errors.messages) unless valid?
|
47
|
+
raise FE::Error.new("summary invalid: #{ errors.messages.map{|k,v| "#{k}=#{v.join(". ")}"}.join("; ")}",class: self.class, messages: errors.messages) unless valid?
|
48
48
|
node = Nokogiri::XML::Builder.new if node.nil?
|
49
49
|
|
50
50
|
node.ResumenFactura do |xml|
|
51
51
|
if document.version_42?
|
52
52
|
xml.CodigoMoneda @currency if @currency.present?
|
53
53
|
xml.TipoCambio @exchange_rate if @exchange_rate.present?
|
54
|
-
elsif document.version_43? && @currency.present?
|
54
|
+
elsif document.version_43? && @currency.present? #&& @currency != "CRC"
|
55
55
|
xml.CodigoTipoMoneda do |x|
|
56
56
|
x.CodigoMoneda @currency
|
57
|
-
x.TipoCambio @exchange_rate
|
57
|
+
x.TipoCambio @exchange_rate || 1
|
58
58
|
end
|
59
59
|
end
|
60
60
|
|
@@ -73,7 +73,7 @@ module FE
|
|
73
73
|
xml.TotalImpuesto @tax_total
|
74
74
|
if document.version_43?
|
75
75
|
xml.TotalIVADevuelto @total_iva_returned if @medical_services_condition && !document_type.eql?(FE::ExportInvoice::DOCUMENT_TYPE) && !document_type.eql?(FE::PurchaseInvoice::DOCUMENT_TYPE)
|
76
|
-
xml.TotalOtrosCargos @
|
76
|
+
xml.TotalOtrosCargos @total_other_charges if @total_other_charges > 0
|
77
77
|
end
|
78
78
|
xml.TotalComprobante @net_total
|
79
79
|
end
|
@@ -87,9 +87,9 @@ module FE
|
|
87
87
|
if document.version_43?
|
88
88
|
errors.add :exonerate_total, :invalid_amount, message: 'invalid amount' if (@exonerate_total - (@services_exonerate_total + @goods_exonerate_total).round(5)).abs > 0.0005
|
89
89
|
end
|
90
|
-
errors.add :subtotal, :invalid_amount, message: 'invalid amount' if (@subtotal - (@taxable_total + @exent_total + @exonerate_total).round(5)).abs > 0.0005
|
90
|
+
errors.add :subtotal, :invalid_amount, message: 'invalid amount' if (@subtotal - (@taxable_total + @exent_total + @exonerate_total ).round(5)).abs > 0.0005
|
91
91
|
errors.add :gross_total, :invalid_amount, message: 'invalid amount' if (@gross_total - (@subtotal - @discount_total).round(5)).abs > 0.0005
|
92
|
-
errors.add :net_total, :invalid_amount, message: "invalid amount" if (@net_total - (@gross_total + @tax_total + @
|
92
|
+
errors.add :net_total, :invalid_amount, message: "invalid amount" if (@net_total - (@gross_total + @tax_total + @total_other_charges - @total_iva_returned).round(5)).abs > 0.0005
|
93
93
|
end
|
94
94
|
end
|
95
95
|
end
|
@@ -35,7 +35,7 @@ module FE
|
|
35
35
|
# It is a mandatory field when a tax is added, it is obtained from the multiplication of the "subtotal" field by "tax rate"
|
36
36
|
# And is a decimal number that can be composed of 13 integers and 5 decimals
|
37
37
|
validates :total, presence: true, format: { with: /\A\d{1,13}(\.\d{0,5})?\z/ }
|
38
|
-
validates :exoneration, presence:false, if: ->{:document_type.eql?("09")}
|
38
|
+
#validates :exoneration, presence:false, if: ->{:document_type.eql?("09")}
|
39
39
|
# validates :iva_factor, presence: true, if: ->{ }
|
40
40
|
|
41
41
|
def initialize(args={})
|
data/lib/facturacr/invoice.rb
CHANGED
@@ -30,18 +30,18 @@ module FE
|
|
30
30
|
"xmlns"=>"https://cdn.comprobanteselectronicos.go.cr/xml-schemas/v4.3/mensajeReceptor"
|
31
31
|
}
|
32
32
|
}
|
33
|
-
|
33
|
+
attr_writer :headquarters, :terminal, :key
|
34
34
|
attr_accessor :key, :date, :issuer_id_number, :receiver_id_number, :message, :details, :economic_activity,
|
35
35
|
:tax_condition,:creditable_tax, :applicable_expense,:tax, :total, :number, :receiver_id_type, :security_code,
|
36
36
|
:document_situation, :issuer_id_type, :original_version, :version
|
37
|
-
|
37
|
+
|
38
38
|
validates :version, presence: true
|
39
39
|
validates :original_version, presence: true, if: -> { version_43? }
|
40
40
|
validates :date, presence: true
|
41
41
|
validates :issuer_id_number, presence: true, length: {is: 12}
|
42
42
|
validates :receiver_id_number, presence: true, length: {is: 12}
|
43
43
|
validates :message, presence: true, inclusion: MESSAGE_TYPES.keys
|
44
|
-
validates :tax_condition, inclusion: TAX_CONDITION.keys, presence:true, if: ->{ version_43? && original_version.eql?("4.3")}
|
44
|
+
validates :tax_condition, inclusion: TAX_CONDITION.keys, presence:true, if: ->{ version_43? && original_version.eql?("4.3") && !tax_condition.blank?}
|
45
45
|
validates :tax, numericality: true, if: -> { tax.present? }
|
46
46
|
validates :total, presence: true, numericality: true
|
47
47
|
validates :number, presence: true
|
@@ -99,14 +99,14 @@ module FE
|
|
99
99
|
def version_42?
|
100
100
|
@version.eql?("4.2")
|
101
101
|
end
|
102
|
-
|
102
|
+
|
103
103
|
def version_43?
|
104
104
|
@version.eql?("4.3")
|
105
105
|
end
|
106
|
-
|
106
|
+
|
107
107
|
|
108
108
|
def build_xml
|
109
|
-
raise FE::Error.new "Documento inválido", class: self.class, messages: errors.messages unless valid?
|
109
|
+
raise FE::Error.new "Documento inválido #{errors.messages}", class: self.class, messages: errors.messages unless valid?
|
110
110
|
builder = Nokogiri::XML::Builder.new
|
111
111
|
|
112
112
|
builder.MensajeReceptor(@namespaces) do |xml|
|
@@ -118,11 +118,11 @@ module FE
|
|
118
118
|
xml.MontoTotalImpuesto @tax.to_f if @tax
|
119
119
|
if version_43? && @original_version.eql?("4.3")
|
120
120
|
xml.CodigoActividad @economic_activity if @economic_activity.present?
|
121
|
-
xml.CondicionImpuesto @tax_condition
|
121
|
+
xml.CondicionImpuesto @tax_condition if @tax_condition.present?
|
122
122
|
xml.MontoImpuestoAcreditar @creditable_tax.to_f if @creditable_tax.present?
|
123
123
|
xml.MontoTotalDeGastoAplicable @applicable_expense.to_f if @applicable_expense.present?
|
124
124
|
end
|
125
|
-
|
125
|
+
|
126
126
|
xml.TotalFactura @total
|
127
127
|
xml.NumeroCedulaReceptor @receiver_id_number
|
128
128
|
xml.NumeroConsecutivoReceptor sequence
|
@@ -7,7 +7,7 @@ module FE
|
|
7
7
|
def initialize(document, xml_provider)
|
8
8
|
# Backwards compatibility with v0.1.4
|
9
9
|
if xml_provider.is_a?(String)
|
10
|
-
raise ArgumentError, "File: #{xml_provider} does not exist" unless File.
|
10
|
+
raise ArgumentError, "File: #{xml_provider} does not exist" unless File.exist?(xml_provider)
|
11
11
|
xml_provider = FE::DataProvider.new(:file, xml_provider)
|
12
12
|
end
|
13
13
|
raise ArgumentError, "Invalid Argument" unless xml_provider.is_a?(FE::DataProvider)
|
@@ -132,7 +132,6 @@ module FE
|
|
132
132
|
|
133
133
|
def build_signed_properties_element
|
134
134
|
cert_digest = compute_digest(@x509.to_der,algorithm(SHA256))
|
135
|
-
policy_digest = compute_digest(@x509.to_der,algorithm(SHA256))
|
136
135
|
signing_time = DateTime.now.rfc3339
|
137
136
|
builder = Nokogiri::XML::Builder.new
|
138
137
|
attributes = {
|
@@ -153,7 +152,7 @@ module FE
|
|
153
152
|
xcd.send("ds:DigestValue", cert_digest)
|
154
153
|
end
|
155
154
|
c.send("xades:IssuerSerial") do |is|
|
156
|
-
is.send("ds:X509IssuerName", @x509.issuer.to_a.reverse.map{|
|
155
|
+
is.send("ds:X509IssuerName", @x509.issuer.to_a.reverse.map{|x| x[0..1].join("=")}.join(", "))
|
157
156
|
is.send("ds:X509SerialNumber", @x509.serial.to_s)
|
158
157
|
end
|
159
158
|
end
|
data/lib/facturacr/version.rb
CHANGED
@@ -2,15 +2,15 @@ require 'nokogiri'
|
|
2
2
|
|
3
3
|
require 'facturacr/document'
|
4
4
|
|
5
|
-
module FE
|
5
|
+
module FE
|
6
6
|
class XmlDocument
|
7
|
-
|
7
|
+
|
8
8
|
attr_accessor :document, :root_tag, :doc, :xml
|
9
|
-
|
9
|
+
|
10
10
|
def initialize(xml_provider)
|
11
11
|
# Backwards compatibility with v0.1.4
|
12
12
|
if xml_provider.is_a?(String)
|
13
|
-
raise ArgumentError, "File: #{xml_provider} does not exist" unless File.
|
13
|
+
raise ArgumentError, "File: #{xml_provider} does not exist" unless File.exist?(xml_provider)
|
14
14
|
xml_provider = FE::DataProvider.new(:file, xml_provider)
|
15
15
|
end
|
16
16
|
raise ArgumentError, "Invalid Argument" unless xml_provider.is_a?(FE::DataProvider)
|
@@ -19,7 +19,7 @@ module FE
|
|
19
19
|
config.options = Nokogiri::XML::ParseOptions::NOBLANKS | Nokogiri::XML::ParseOptions::NOENT
|
20
20
|
end
|
21
21
|
root_tag = @doc.elements.first.name
|
22
|
-
|
22
|
+
|
23
23
|
if root_tag.eql?('FacturaElectronica')
|
24
24
|
@document = FE::Invoice.new
|
25
25
|
elsif root_tag.eql?("NotaCreditoElectronica")
|
@@ -30,8 +30,10 @@ module FE
|
|
30
30
|
@document = FE::Ticket.new
|
31
31
|
elsif root_tag.eql?("MensajeReceptor")
|
32
32
|
@document = FE::ReceptionMessage.new
|
33
|
+
else
|
34
|
+
@document = nil
|
33
35
|
end
|
34
|
-
|
36
|
+
|
35
37
|
if @document.is_a?(FE::Document)
|
36
38
|
@document.version = @doc.elements.first.namespace.href.scan(/v4\..{1}/).first[1..-1]
|
37
39
|
@document.date = DateTime.parse(@doc.css("#{root_tag} FechaEmision").first&.text)
|
@@ -58,7 +60,7 @@ module FE
|
|
58
60
|
location.district = @doc.css("#{root_tag} Emisor Ubicacion Distrito").text
|
59
61
|
location.others = @doc.css("#{root_tag} Emisor Ubicacion OtrasSenas").text
|
60
62
|
@issuer.location = location
|
61
|
-
|
63
|
+
|
62
64
|
if !@doc.css("#{root_tag} Emisor Telefono").empty?
|
63
65
|
@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
|
64
66
|
end
|
@@ -66,19 +68,19 @@ module FE
|
|
66
68
|
@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
|
67
69
|
end
|
68
70
|
@issuer.email = @doc.css("#{root_tag} Emisor CorreoElectronico").text
|
69
|
-
|
71
|
+
|
70
72
|
unless @doc.css("#{root_tag} Receptor").empty?
|
71
73
|
@receiver = FE::Document::Receiver.new
|
72
74
|
@receiver.name = @doc.css("#{root_tag} Receptor Nombre").text
|
73
75
|
unless @doc.css("#{root_tag} Receptor Identificacion").empty?
|
74
76
|
@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
|
75
77
|
end
|
76
|
-
|
78
|
+
|
77
79
|
unless @doc.css("#{root_tag} Receptor IdentificacionExtranjero").empty?
|
78
80
|
@receiver.foreign_id_number = @doc.css("#{root_tag} Receptor IdentificacionExtranjero").text
|
79
81
|
end
|
80
82
|
@receiver.comercial_name = @doc.css("#{root_tag} Receptor NombreComercial").text unless @doc.css("#{root_tag} Receptor NombreComercial").empty?
|
81
|
-
|
83
|
+
|
82
84
|
unless @doc.css("#{root_tag} Receptor Ubicacion").empty?
|
83
85
|
location = FE::Document::Location.new
|
84
86
|
location.province = @doc.css("#{root_tag} Receptor Ubicacion Provincia").text
|
@@ -87,7 +89,7 @@ module FE
|
|
87
89
|
location.others = @doc.css("#{root_tag} Receptor Ubicacion OtrasSenas").text
|
88
90
|
@receiver.location = location
|
89
91
|
end
|
90
|
-
|
92
|
+
|
91
93
|
if !@doc.css("#{root_tag} Receptor Telefono").empty?
|
92
94
|
@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
|
93
95
|
end
|
@@ -103,7 +105,9 @@ module FE
|
|
103
105
|
if @document.version_42?
|
104
106
|
item.code = line.css("Codigo Codigo").text
|
105
107
|
elsif @document.version_43?
|
106
|
-
|
108
|
+
code = line > "Codigo"
|
109
|
+
item.code = code.text
|
110
|
+
item.comercial_code = line.css("CodigoComercial Codigo").text
|
107
111
|
end
|
108
112
|
item.quantity = line.css("Cantidad").text
|
109
113
|
item.unit = line.css("UnidadMedida").text
|
@@ -128,13 +132,13 @@ module FE
|
|
128
132
|
exo.percentage = line.css("Exoneracion PorcentajeCompra").text.to_i
|
129
133
|
t_args[:exoneration] = exo
|
130
134
|
end
|
131
|
-
|
135
|
+
|
132
136
|
item.taxes << FE::Document::Tax.new(t_args)
|
133
137
|
end
|
134
138
|
@items << item
|
135
139
|
end
|
136
140
|
|
137
|
-
|
141
|
+
|
138
142
|
@summary = FE::Document::Summary.new
|
139
143
|
sum = @doc.css("#{root_tag} ResumenFactura")
|
140
144
|
if @document.version_42?
|
@@ -155,7 +159,7 @@ module FE
|
|
155
159
|
@summary.gross_total = sum.css("TotalVentaNeta").text.to_f
|
156
160
|
@summary.tax_total = sum.css("TotalImpuesto").text.to_f
|
157
161
|
@summary.net_total = sum.css("TotalComprobante").text.to_f
|
158
|
-
|
162
|
+
|
159
163
|
refs = @doc.css("#{root_tag} InformacionReferencia")
|
160
164
|
@references = []
|
161
165
|
unless refs.empty?
|
@@ -169,20 +173,20 @@ module FE
|
|
169
173
|
@references << reference
|
170
174
|
end
|
171
175
|
end
|
172
|
-
|
176
|
+
|
173
177
|
reg = @doc.css("#{root_tag} Normativa")
|
174
178
|
@regulation = FE::Document::Regulation.new
|
175
179
|
@regulation.number = reg.css("NumeroResolucion").text
|
176
180
|
@regulation.date = reg.css("FechaResolucion").text
|
177
|
-
|
178
|
-
|
181
|
+
|
182
|
+
|
179
183
|
@document.issuer = @issuer
|
180
184
|
@document.receiver = @receiver
|
181
185
|
@document.items = @items
|
182
186
|
@document.summary = @summary
|
183
187
|
@document.references = @references
|
184
|
-
@document.regulation = @regulation
|
185
|
-
|
188
|
+
@document.regulation = @regulation
|
189
|
+
elsif @document.present?
|
186
190
|
@document.date = DateTime.parse(@doc.css("#{root_tag} FechaEmisionDoc").text)
|
187
191
|
@key = @doc.css("#{root_tag} Clave").text
|
188
192
|
@document.key = @key
|
@@ -195,11 +199,11 @@ module FE
|
|
195
199
|
@document.security_code = @key[42..-1]
|
196
200
|
@document.total = @doc.css("#{root_tag} TotalFactura").text
|
197
201
|
@document.tax = @doc.css("#{root_tag} MontoTotalImpuesto").text
|
198
|
-
end
|
202
|
+
end
|
199
203
|
end
|
200
|
-
|
204
|
+
|
201
205
|
def has_tax_node?
|
202
206
|
@doc.css("#{root_tag} ResumenFactura TotalImpuesto").any?
|
203
207
|
end
|
204
208
|
end
|
205
|
-
end
|
209
|
+
end
|
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: 1.1.
|
4
|
+
version: 1.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Josef Sauter
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-12-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -28,16 +28,16 @@ dependencies:
|
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
33
|
+
version: 12.3.3
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - "
|
38
|
+
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
40
|
+
version: 12.3.3
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: minitest
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -178,6 +178,7 @@ files:
|
|
178
178
|
- lib/facturacr/cli/generate.rb
|
179
179
|
- lib/facturacr/configuration.rb
|
180
180
|
- lib/facturacr/credit_note.rb
|
181
|
+
- lib/facturacr/data.rb
|
181
182
|
- lib/facturacr/data_provider.rb
|
182
183
|
- lib/facturacr/debit_note.rb
|
183
184
|
- lib/facturacr/document.rb
|
@@ -240,10 +241,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
240
241
|
- !ruby/object:Gem::Version
|
241
242
|
version: '0'
|
242
243
|
requirements: []
|
243
|
-
|
244
|
-
rubygems_version: 2.4.6
|
244
|
+
rubygems_version: 3.0.6
|
245
245
|
signing_key:
|
246
246
|
specification_version: 4
|
247
247
|
summary: Facturación Electrónica de Costa Rica
|
248
248
|
test_files: []
|
249
|
-
has_rdoc:
|