facturacr 1.0.6 → 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.
Files changed (40) hide show
  1. checksums.yaml +5 -5
  2. data/facturacr.gemspec +2 -2
  3. data/lib/facturacr.rb +25 -19
  4. data/lib/facturacr/api.rb +5 -4
  5. data/lib/facturacr/builder.rb +4 -4
  6. data/lib/facturacr/configuration.rb +18 -9
  7. data/lib/facturacr/credit_note.rb +24 -13
  8. data/lib/facturacr/data.rb +41 -0
  9. data/lib/facturacr/data_provider.rb +1 -1
  10. data/lib/facturacr/debit_note.rb +25 -13
  11. data/lib/facturacr/document.rb +120 -69
  12. data/lib/facturacr/document/exoneration.rb +17 -12
  13. data/lib/facturacr/document/fax.rb +1 -4
  14. data/lib/facturacr/document/identification_document.rb +7 -10
  15. data/lib/facturacr/document/issuer.rb +31 -32
  16. data/lib/facturacr/document/item.rb +77 -20
  17. data/lib/facturacr/document/location.rb +19 -23
  18. data/lib/facturacr/document/other_charges.rb +56 -0
  19. data/lib/facturacr/document/other_content.rb +30 -0
  20. data/lib/facturacr/document/other_content/price_smart.rb +58 -0
  21. data/lib/facturacr/document/other_text.rb +4 -8
  22. data/lib/facturacr/document/phone.rb +1 -3
  23. data/lib/facturacr/document/phone_type.rb +6 -9
  24. data/lib/facturacr/document/receiver.rb +28 -19
  25. data/lib/facturacr/document/reference.rb +36 -18
  26. data/lib/facturacr/document/regulation.rb +3 -3
  27. data/lib/facturacr/document/summary.rb +54 -22
  28. data/lib/facturacr/document/tax.rb +43 -21
  29. data/lib/facturacr/element.rb +9 -0
  30. data/lib/facturacr/error.rb +11 -0
  31. data/lib/facturacr/export_invoice.rb +38 -0
  32. data/lib/facturacr/invoice.rb +27 -15
  33. data/lib/facturacr/purchase_invoice.rb +39 -0
  34. data/lib/facturacr/reception_message.rb +73 -28
  35. data/lib/facturacr/signed_document.rb +4 -4
  36. data/lib/facturacr/signer/signer.rb +20 -8
  37. data/lib/facturacr/ticket.rb +27 -14
  38. data/lib/facturacr/version.rb +1 -1
  39. data/lib/facturacr/xml_document.rb +46 -29
  40. metadata +15 -9
@@ -1,25 +1,28 @@
1
1
  module FE
2
2
  class Document
3
- class Exoneration
3
+ class Exoneration < Element
4
4
  include ActiveModel::Validations
5
5
 
6
6
 
7
7
  DOCUMENT_TYPES = {
8
8
  "01" => "Compras Autorizadas",
9
9
  "02" => "Ventas exentas a diplomáticos",
10
- "03" => "Orden de Compra (Instituciones Públicas y otros organismos)",
10
+ "03" => "Autorizado por Ley especial",
11
11
  "04" => "Exenciones Dirección General de Hacienda",
12
- "05" => "Zonas Francas",
12
+ "05" => "Transitorio V",
13
+ "06" => "Transitorio IX",
14
+ "07" => "Transitorio XVII",
13
15
  "99" => "Otros"
14
16
  }.freeze
15
17
  attr_accessor :document_type, :document_number, :institution, :date, :total_tax, :percentage, :net_total
16
18
 
17
19
  validates :document_type, presence: true, inclusion: DOCUMENT_TYPES.keys
18
- validates :document_number, presence: true
19
- validates :institution, presence: true
20
+ validates :document_number, presence: true, length: { maximum: 40 }
21
+ validates :institution, presence: true, length: { maximum: 160 }
20
22
  validates :date, presence: true
21
23
  validates :total_tax,presence: true
22
- validates :percentage, presence: true
24
+ validates :percentage, presence: true, numericality: {greater_than_or_equal_to: 0, less_than_or_equal_to: 100, only_integer: true}
25
+
23
26
 
24
27
  def initialize(args={})
25
28
  @document_type = args[:document_type]
@@ -27,11 +30,12 @@ module FE
27
30
  @institution = args[:institution]
28
31
  @date = args[:date]
29
32
  @total_tax = args[:total_tax]
30
- @percentage = ((@total_tax.to_f / args[:net_total].to_f) * 100).to_i if args[:net_total].present?
33
+ @percentage = args[:percentage]
31
34
  end
32
35
 
33
- def build_xml(node)
34
- raise "Invalid Record: #{errors.messages}" unless valid?
36
+ def build_xml(node, document)
37
+ @document = document
38
+ raise FE::Error.new("invalid exoneration",class: self.class, messages: errors.messages) unless valid?
35
39
  node = Nokogiri::XML::Builder.new if node.nil?
36
40
 
37
41
  node.Exoneracion do |xml|
@@ -39,11 +43,12 @@ module FE
39
43
  xml.NumeroDocumento @document_number
40
44
  xml.NombreInstitucion @institution
41
45
  xml.FechaEmision @date.xmlschema
42
- xml.MontoImpuesto @total_tax
43
- xml.PorcentajeCompra @percentage
46
+ xml.PorcentajeExoneracion @percentage
47
+ xml.MontoExoneracion @total_tax
44
48
  end
45
49
  end
46
50
 
51
+
47
52
  end
48
53
  end
49
- end
54
+ end
@@ -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
 
@@ -20,20 +16,21 @@ module FE
20
16
 
21
17
  @document_type = args[:type]
22
18
  @raw_id_number = args[:number]
23
- @id_number = "%012d" % args[:number]
24
-
19
+ if @raw_id_number
20
+ @id_number = "%012d" % args[:number]
21
+ end
25
22
  end
26
23
 
27
- def build_xml(node)
28
- raise "Invalid Record: #{errors.messages}" unless valid?
24
+ def build_xml(node, document)
25
+ raise FE::Error.new("invalid identification document", class: self.class, messages: errors.messages) unless valid?
29
26
  node = Nokogiri::XML::Builder.new if node.nil?
30
27
  node.Identificacion do |x|
31
28
  x.Tipo document_type
32
29
  x.Numero raw_id_number
33
30
  end
34
31
  end
35
- def to_xml(builder)
36
- build_xml(builder).to_xml
32
+ def to_xml(builder, document)
33
+ build_xml(builder,document).to_xml
37
34
  end
38
35
  end
39
36
 
@@ -1,22 +1,25 @@
1
- require "facturacr/document"
2
- require 'active_model'
3
- require 'nokogiri'
4
-
5
1
  module FE
6
2
  class Document
7
-
8
-
9
- class Issuer
3
+
4
+
5
+ class Issuer < Element
10
6
  include ActiveModel::Validations
11
-
7
+
12
8
  attr_accessor :name, :identification_document, :comercial_name, :location, :phone, :fax, :email
13
-
14
- validates :name, presence: true, length: { maximum: 80 }
9
+
10
+ validates :name, presence: true
15
11
  validates :identification_document, presence: true
16
- validates :comercial_name, length: {maximum: 80}
17
12
  validates :location, presence: true
18
- validates :email, presence: true, format: {with: /\s*\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*\s*/}
13
+ validates :email, presence: true,length: {maximum: 160}, format:{with: /\s*\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*\s*/}
19
14
 
15
+ validates :name, length: { maximum: 80}, if: ->{ document.version_42? }
16
+ validates :comercial_name, length: { maximum: 80 }, if: ->{ document.version_42? }
17
+
18
+ validates :name, length: { maximum: 100}, if: ->{ document.version_43? }
19
+ validates :comercial_name, length: { maximum: 100 }, if: ->{ document.version_43? }
20
+
21
+
22
+
20
23
  def initialize(args={})
21
24
  @name = args[:name]
22
25
  @identification_document = args[:identification_document]
@@ -24,33 +27,29 @@ module FE
24
27
  @location = args[:location]
25
28
  @phone = args[:phone]
26
29
  @fax = args[:fax]
27
- @email = args[:email]
30
+ @email = args[:email]
28
31
  end
29
-
30
- def build_xml(node)
31
- raise "IdentificationDocument is invalid" if @identification_document.nil? || !@identification_document.is_a?(IdentificationDocument)
32
- raise "Location is invalid" if @location.nil? || !@location.is_a?(Location)
33
- raise "Phone is invalid" if !@phone.nil? && !@phone.is_a?(Phone)
34
- raise "Fax is invalid" if !@fax.nil? && !@fax.is_a?(Fax)
35
-
36
- raise "Issuer is invalid: #{errors.messages}" unless valid?
37
-
32
+
33
+ def build_xml(node, document)
34
+ @document = document
35
+ raise FE::Error.new("issuer invalid",class: self.class, messages: errors.messages) unless valid?
36
+
38
37
  node = Nokogiri::XML::Builder.new if node.nil?
39
38
  node.Emisor do |xml|
40
39
  xml.Nombre @name
41
- identification_document.build_xml(xml)
40
+ identification_document.build_xml(xml,document)
42
41
  xml.NombreComercial @comercial_name if @comercial_name
43
- location.build_xml(xml)
44
- phone.build_xml(xml) if phone.present?
45
- fax.build_xml(xml) if fax.present?
42
+ location.build_xml(xml, document)
43
+ phone.build_xml(xml, document) if phone.present?
44
+ fax.build_xml(xml, document) if fax.present?
46
45
  xml.CorreoElectronico @email
47
- end
46
+ end
48
47
  end
49
-
50
- def to_xml(builder)
51
- build_xml(builder).to_xml
48
+
49
+ def to_xml(builder,document)
50
+ build_xml(builder,document).to_xml
52
51
  end
53
52
  end
54
-
53
+
55
54
  end
56
- end
55
+ end
@@ -1,12 +1,12 @@
1
1
  module FE
2
2
  class Document
3
- class Item
3
+ class Item < Element
4
4
  include ActiveModel::Validations
5
5
 
6
- UNITS = %w[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
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
-
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',
@@ -15,26 +15,35 @@ module FE
15
15
  '99' => 'Otros'
16
16
  }.freeze
17
17
 
18
- attr_accessor :line_number, :code_type, :code, :quantity, :unit, :description, :unit_price, :total,
19
- :discount, :discount_reason, :subtotal, :taxes, :net_total
18
+ attr_accessor :line_number,:tariff_item,:code, :comercial_code_type, :comercial_code, :quantity, :unit, :description, :unit_price, :total,
19
+ :discount, :discount_reason, :subtotal,:taxable_base ,:taxes,:net_tax ,:net_total, :exoneration, :document_type
20
20
 
21
+ validates :document_type, presence: true, inclusion: FE::Document::DOCUMENT_TYPES.keys
21
22
  validates :line_number, presence: true
22
- validates :code_type, inclusion: CODE_TYPES.keys, if: -> { code.present? }
23
+ validates :tariff_item, presence: true, length: {is: 12}, if:->{document_type.eql?(FE::ExportInvoice::DOCUMENT_TYPE) && !SERVICE_UNITS.include?(unit) && document.version_43? }
23
24
  validates :quantity, presence: true, numericality: { greater_than: 0 }
24
25
  validates :unit, presence: true, inclusion: UNITS
25
- validates :description, presence: true, length: { maximum: 160 }
26
+ validates :description, presence: true, length: { maximum: 200 }
26
27
  validates :unit_price, presence: true
27
28
  validates :total, presence: true
28
29
  validates :discount, numericality: { grater_than: 0 }, if: -> { discount.present? }
29
30
  validates :discount_reason, presence: true, if: -> { discount.present? }
30
31
  validates :subtotal, presence: true
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: ->{ taxes.map{ |t| t.exoneration.present? }.include?(true) }
31
34
  validates :net_total, presence: true
32
- validates :code, presence: true, length: {maximum: 20}
35
+ validates :comercial_code_type, inclusion: CODE_TYPES.keys, if: -> { comercial_code.present? }
36
+ validates :comercial_code, presence: true, length: {maximum: 20}
37
+ validates :code, presence: true, length: {maximum: 13}, if: :code_is_mandatory?
38
+
39
+ validate :calculations_ok?
33
40
 
34
41
  def initialize(args = {})
42
+ @document_type = args[:document_type]
35
43
  @line_number = args[:line_number]
36
- @code_type = args[:code_type].presence || '01'
37
44
  @code = args[:code]
45
+ @comercial_code_type = args[:comercial_code_type].presence || '01'
46
+ @comercial_code = args[:comercial_code]
38
47
  @quantity = args[:quantity]
39
48
  @unit = args[:unit]
40
49
  @description = args[:description]
@@ -45,17 +54,39 @@ module FE
45
54
  @subtotal = args[:subtotal]
46
55
  @taxes = args[:taxes] || []
47
56
  @net_total = args[:net_total]
57
+ @exoneration = args[:exoneration]
58
+ @net_tax = args[:net_tax]
59
+ @tariff_item = args[:tariff_item]
60
+ @taxable_base = args[:taxable_base]
61
+
48
62
  end
49
63
 
50
- def build_xml(node)
51
- raise "Item invalid: #{errors.messages}" unless valid?
64
+ def build_xml(node, document)
65
+ @document = document
66
+ @document_type = document.document_type
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
+
52
69
  node = Nokogiri::XML::Builder.new if node.nil?
53
70
  node.LineaDetalle do |x|
54
71
  x.NumeroLinea @line_number
55
- if @code.present?
72
+
73
+ x.PartidaArancelaria @tariff_item if @tariff_item.present? && document.version_43?
74
+
75
+ if document.version_43?
76
+ x.Codigo @code if @code.present?
77
+ end
78
+
79
+ if @comercial_code.present? && document.version_43?
80
+ x.CodigoComercial do |x2|
81
+ x2.Tipo @comercial_code_type
82
+ x2.Codigo @comercial_code
83
+ end
84
+ end
85
+
86
+ if @comercial_code.present? && document.version_42?
56
87
  x.Codigo do |x2|
57
- x2.Tipo @code_type
58
- x2.Codigo @code
88
+ x2.Tipo @comercial_code_type
89
+ x2.Codigo @comercial_code
59
90
  end
60
91
  end
61
92
  x.Cantidad @quantity
@@ -63,17 +94,43 @@ module FE
63
94
  x.Detalle @description
64
95
  x.PrecioUnitario @unit_price
65
96
  x.MontoTotal @total
66
- x.MontoDescuento @discount if @discount.present?
67
- x.NaturalezaDescuento @discount_reason if @discount_reason.present?
97
+
98
+ if document.version_42?
99
+ x.MontoDescuento @discount if @discount.present?
100
+ x.NaturalezaDescuento @discount_reason if @discount_reason.present?
101
+ end
102
+ if @discount.present? && document.version_43?
103
+ x.Descuento do |x2|
104
+ x2.MontoDescuento @discount
105
+ x2.NaturalezaDescuento @discount_reason
106
+ end
107
+ end
108
+
68
109
  x.SubTotal @subtotal
110
+
111
+ x.BaseImponible @taxable_base if @taxable_base.present? && document.version_43?
69
112
  @taxes.each do |tax|
70
- tax.build_xml(x)
71
- end
72
- if @exoneration.present?
73
- @exoneration.build_xml(x)
113
+ tax.build_xml(x,document)
74
114
  end
115
+
116
+ x.ImpuestoNeto @net_tax if @net_tax.present?
75
117
  x.MontoTotalLinea @net_total
76
118
  end
119
+
120
+ end
121
+
122
+
123
+
124
+ def calculations_ok?
125
+ errors.add :total, :invalid_amount, message: 'invalid amount' if (@total - (@quantity * @unit_price).round(5)).abs > 1
126
+ end
127
+
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
77
134
  end
78
135
  end
79
136
  end
@@ -1,36 +1,32 @@
1
- require "facturacr/document"
2
- require 'active_model'
3
- require 'nokogiri'
4
-
5
1
  module FE
6
2
  class Document
7
-
8
-
9
- class Location
3
+
4
+
5
+ class Location < Element
10
6
  include ActiveModel::Validations
11
-
7
+
12
8
  attr_accessor :province, :county,:district,:neighborhood, :others
13
-
9
+
14
10
  validates :province, presence: true, length: { is: 1 }
15
11
  validates :county, presence: true, length: { is: 2 }
16
12
  validates :district, presence: true, length: { is: 2 }
17
13
  validates :neighborhood, length: { is: 2 }, allow_blank: true
18
- validates :others, presence: true, length: { maximum: 160 }
19
-
14
+ validates :others, presence: true, length: { maximum: 250 }
15
+
20
16
  def initialize(args={})
21
-
17
+
22
18
  @province = args[:province]
23
19
  @county = args[:county]
24
20
  @district = args[:district]
25
21
  @neighborhood = args[:neighborhood]
26
22
  @others = args[:others]
27
-
28
-
23
+
24
+
29
25
  end
30
-
31
- def build_xml(node)
32
- raise "Location invalid: #{errors.messages}" unless valid?
33
- node = Nokogiri::XML::Builder.new if node.nil?
26
+
27
+ def build_xml(node, document)
28
+ raise FE::Error.new("location invalid",class: self.class, messages: errors.messages) unless valid?
29
+ node = Nokogiri::XML::Builder.new if node.nil?
34
30
  node.Ubicacion do |x|
35
31
  x.Provincia @province
36
32
  x.Canton @county
@@ -39,11 +35,11 @@ module FE
39
35
  x.OtrasSenas @others
40
36
  end
41
37
  end
42
-
43
- def to_xml(builder)
44
- build_xml(builder).to_xml
38
+
39
+ def to_xml(builder,document)
40
+ build_xml(builder,document).to_xml
45
41
  end
46
-
42
+
47
43
  end
48
44
  end
49
- end
45
+ end
@@ -0,0 +1,56 @@
1
+ module FE
2
+ class Document
3
+ class OtherCharges < Element
4
+
5
+ include ActiveModel::Validations
6
+
7
+ OTHER_DOCUMENT_TYPES = {
8
+
9
+ "01" => "Contribución parafiscal",
10
+ "02" => "Timbre de la Cruz Roja",
11
+ "03" => "Timbre de Benemérito Cuerpo de Bomberos de Costa Rica",
12
+ "04" => "Cobro de un tercero",
13
+ "05" => "Costos de Exportación",
14
+ "06" => "Impuesto de servicio 10%",
15
+ "07" => "Timbre de Colegios Profesionales",
16
+ "99" => "Otros Cargos"
17
+ }.freeze
18
+
19
+ attr_accessor :document_type, :collector_id_number, :collector_name, :detail, :percentage, :total_charge
20
+
21
+ validates :document_type, presence: true, inclusion: OTHER_DOCUMENT_TYPES.keys
22
+ validates :collector_id_number, presence: false, if: ->{ document_type.eql?("04") }
23
+ validates :detail, presence: true
24
+ validates :total_charge, presence: true
25
+ validates :collector_name, presence: false, if: ->{ document_type.eql?("04") }
26
+
27
+ def initialize(args={})
28
+ @document_type = args[:document_type]
29
+ @collector_id_number=args[:collector_id_number]
30
+ @collector_name = args[:collector_name]
31
+ @detail = args[:detail]
32
+ @percentage =args[:percentage]
33
+ @total_charge = args[:total_charge]
34
+ end
35
+
36
+ def build_xml(node, document)
37
+ raise FE::Error.new("other charges invalid",class: self.class, messages: errors.messages) unless valid?
38
+
39
+ node = Nokogiri::XML::Builder.new if node.nil?
40
+
41
+ node.OtrosCargos do |xml|
42
+ xml.TipoDocumento @document_type
43
+ xml.NumeroIdentidadTercero @third_id_number if @third_id_number.present?
44
+ xml.NombreTercero @third_name if @third_name.present?
45
+ xml.Detalle @detail
46
+ xml.Porcentaje @percentage if @percentage.present?
47
+ xml.MontoCargo @total_charge
48
+
49
+ end
50
+
51
+ end
52
+
53
+
54
+ end
55
+ end
56
+ end