facturacr 1.1.0 → 1.1.1
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 +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:
|