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