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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: c8bf5eb68e70263abef81cfdd2749fe4b94ad7f6
4
- data.tar.gz: 2302cca660e93c2d1d4dd2f2072e5a60fafcd129
2
+ SHA256:
3
+ metadata.gz: 5a8fe4de4d95d4c43eadb53200c1ce6b847554a1970fb9cb803ffdd9b3a5ea93
4
+ data.tar.gz: a4ba9d8357de2b21305f5c32577e25433d0c477c4996cfdfdc8b42e1f5665935
5
5
  SHA512:
6
- metadata.gz: 6b718daa726dc9d1171d896e9028891a30500bd683fa6c03c822757fc340e168b5afbb96be365ff3e736538414a17f47d027566cad51aa9e9281c49fdce2cb79
7
- data.tar.gz: aee6da72dac27f6ed88ae05e95b84d0dc3052b45b16099e402ca3e6a6180a2d3b4a03f4a41c220a71fa8c32d456831ed851e3d6e6ad9cb07ab605350b340a037
6
+ metadata.gz: c339e27c622a15a04f9d55924b40bd78529fc31c284517e6dfa6b42473f1fa99993b8667e1a3b3960872c65cef8f674951a5f820564ed3754e159013ac2d0bd6
7
+ data.tar.gz: 3640e8f8d11d531ad16e801820c9eeb1c3671988338d5cc029548839338ade8ca4b99e467864647940a2851441d1c1a8ade3711d19b92b1bbff9011cc154f4a3
@@ -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", "~> 10.0"
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
 
@@ -1,24 +1,25 @@
1
1
  require 'awesome_print'
2
2
 
3
- require 'facturacr'
4
- require 'facturacr/configuration'
5
- require 'facturacr/element'
6
- require 'facturacr/document'
7
- require 'facturacr/invoice'
8
- require 'facturacr/credit_note'
9
- require 'facturacr/export_invoice'
10
- require 'facturacr/purchase_invoice'
11
- require 'facturacr/debit_note'
12
- require 'facturacr/ticket'
13
- require 'facturacr/signed_document'
14
- require 'facturacr/xml_document'
15
- require 'facturacr/api'
16
- require 'facturacr/builder'
17
- require 'facturacr/version'
18
- require 'facturacr/data_provider'
19
- require 'facturacr/signer/signer'
20
- require 'facturacr/reception_message'
21
- require 'facturacr/error'
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
@@ -42,7 +42,7 @@ module FE
42
42
  else
43
43
  url += "/logout"
44
44
  end
45
- response = RestClient.post url, logout_data
45
+ RestClient.post url, logout_data
46
46
  rescue => e
47
47
  puts "LOGOUT ERROR: #{e.message}".red
48
48
  end
@@ -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.exists?(@file_path)
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|
@@ -32,6 +32,7 @@ module FE
32
32
  @security_code = args[:security_code]
33
33
  @document_situation = args[:document_situation]
34
34
  @references = args[:references]
35
+ @other_charges = args[:other_charges]
35
36
  @namespaces = NAMESPACES[@version]
36
37
  @others = args[:others] || []
37
38
  end
@@ -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.exists?(data)
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
@@ -33,6 +33,7 @@ module FE
33
33
  @security_code = args[:security_code]
34
34
  @document_situation = args[:document_situation]
35
35
  @references = args[:references]
36
+ @other_charges = args[:other_charges]
36
37
  @namespaces = NAMESPACES[@version]
37
38
  @others = args[:others] || []
38
39
  end
@@ -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
- require 'facturacr/document/code'
207
- require 'facturacr/document/exoneration'
208
- require 'facturacr/document/fax'
209
- require 'facturacr/document/identification_document'
210
- require 'facturacr/document/issuer'
211
- require 'facturacr/document/item'
212
- require 'facturacr/document/location'
213
- require 'facturacr/document/phone_type'
214
- require 'facturacr/document/phone'
215
- require 'facturacr/document/receiver'
216
- require 'facturacr/document/reference'
217
- require 'facturacr/document/regulation'
218
- require 'facturacr/document/summary'
219
- require 'facturacr/document/tax'
220
- require 'facturacr/document/other_text'
221
- require 'facturacr/document/other_content'
222
- require 'facturacr/document/other_charges'
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={})
@@ -1,7 +1,4 @@
1
- require "facturacr/document"
2
- require 'facturacr/document/phone_type'
3
- require 'active_model'
4
- require 'nokogiri'
1
+ require_relative 'phone_type'
5
2
 
6
3
  module FE
7
4
  class Document
@@ -1,7 +1,3 @@
1
- require "facturacr/document"
2
- require 'active_model'
3
- require 'nokogiri'
4
-
5
1
  module FE
6
2
  class Document
7
3
 
@@ -1,6 +1,3 @@
1
- require 'active_model'
2
- require 'nokogiri'
3
-
4
1
  module FE
5
2
  class Document
6
3
 
@@ -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} #TODO this will be mandatory after 2020-01-01
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? && @exoneration.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,7 +1,3 @@
1
- require "facturacr/document"
2
- require 'active_model'
3
- require 'nokogiri'
4
-
5
1
  module FE
6
2
  class Document
7
3
 
@@ -17,11 +17,7 @@ module FE
17
17
  node.OtroTexto(@xml_attributes) do |xml|
18
18
  xml.text(@content)
19
19
  end
20
- end
21
-
22
-
23
-
24
-
20
+ end
25
21
  end
26
22
  end
27
23
  end
@@ -1,6 +1,4 @@
1
- require "facturacr/document"
2
- require 'facturacr/document/phone_type'
3
- require 'active_model'
1
+ require_relative 'phone_type'
4
2
 
5
3
  module FE
6
4
  class Document
@@ -1,7 +1,3 @@
1
- require "facturacr/document"
2
- require 'active_model'
3
- require 'nokogiri'
4
-
5
1
  module FE
6
2
  class Document
7
3
  class PhoneType < Element
@@ -1,4 +1,3 @@
1
- require "facturacr/document"
2
1
  require 'active_model'
3
2
  require 'nokogiri'
4
3
 
@@ -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
- validates :code, presence: true, length: {is: 2}, inclusion: REFERENCE_CODES.keys, if: ->{ document_type.present? }
42
- validates :reason, presence: true, length: {maximum: 180}, if: ->{ document_type.present? }
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,:total_others_charges, :net_total,
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
- @total_others_charges =args[:total_others_charges].to_f
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? && @currency != "CRC"
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 @total_others_charges
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 + @total_others_charges - @total_iva_returned).round(5)).abs > 0.0005
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={})
@@ -1,4 +1,4 @@
1
- require 'facturacr/document'
1
+ require_relative 'document'
2
2
 
3
3
  module FE
4
4
 
@@ -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.exists?(xml_provider)
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{|c| c[0..1].join("=")}.join(", "))
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
@@ -1,3 +1,3 @@
1
1
  module FE
2
- VERSION = "1.1.0"
2
+ VERSION = "1.1.1"
3
3
  end
@@ -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.exists?(xml_provider)
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
- item.code = line.css("CodigoComercial Codigo").text
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
- else
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.0
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: 2019-09-05 00:00:00.000000000 Z
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: '10.0'
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: '10.0'
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
- rubyforge_project:
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: