facturacr 1.0.9 → 1.1.0

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.
@@ -1,6 +1,6 @@
1
1
  module FE
2
2
  class Document
3
- class OtherContent
3
+ class OtherContent < Element
4
4
  include ActiveModel::Validations
5
5
  attr_accessor :implementation
6
6
 
@@ -11,16 +11,16 @@ module FE
11
11
  @implementation = args[:implementation]
12
12
  end
13
13
 
14
- def build_xml(node)
15
- raise "Invalid Record: #{errors.messages}" unless valid?
14
+ def build_xml(node, document)
15
+ raise FE::Error.new("other content",class: self.class, messages: errors.messages) unless valid?
16
16
  node = Nokogiri::XML::Builder.new if node.nil?
17
17
  node.OtroContenido do |xml|
18
- @implementation.build_xml(xml)
18
+ @implementation.build_xml(xml, document)
19
19
  end
20
20
  end
21
21
 
22
- def to_xml(builder)
23
- build_xml(builder).to_xml
22
+ def to_xml(builder,document)
23
+ build_xml(builder,document).to_xml
24
24
  end
25
25
 
26
26
  end
@@ -1,6 +1,6 @@
1
1
  module FE
2
2
  class Document
3
- class OtherText
3
+ class OtherText < Element
4
4
  attr_accessor :xml_attributes, :content
5
5
 
6
6
 
@@ -10,8 +10,8 @@ module FE
10
10
  @content = args[:content]
11
11
  end
12
12
 
13
- def build_xml(node)
14
- raise "xml_attributes is not a Hash" unless @xml_attributes.is_a?(Hash)
13
+ def build_xml(node, document)
14
+ raise FE::Error.new("xml_attribues is not a hash",class: self.class) unless @xml_attributes.is_a?(Hash)
15
15
 
16
16
  node = Nokogiri::XML::Builder.new if node.nil?
17
17
  node.OtroTexto(@xml_attributes) do |xml|
@@ -4,7 +4,7 @@ require 'nokogiri'
4
4
 
5
5
  module FE
6
6
  class Document
7
- class PhoneType
7
+ class PhoneType < Element
8
8
  include ActiveModel::Validations
9
9
 
10
10
  attr_accessor :tag_name, :country_code, :number
@@ -20,8 +20,9 @@ module FE
20
20
  @number = number
21
21
  end
22
22
 
23
- def build_xml(node)
24
- raise "Invalid Record: #{errors.messages}" unless valid?
23
+ def build_xml(node, document)
24
+ raise FE::Error.new("phone type invalid",class: self.class, messages: errors.messages) unless valid?
25
+
25
26
  node = Nokogiri::XML::Builder.new if node.nil?
26
27
  node.send(tag_name) do |xml|
27
28
  xml.CodigoPais country_code
@@ -29,8 +30,8 @@ module FE
29
30
  end
30
31
  end
31
32
 
32
- def to_xml(builder)
33
- build_xml(builder).to_xml
33
+ def to_xml(builder,document)
34
+ build_xml(builder, document).to_xml
34
35
  end
35
36
  end
36
37
  end
@@ -6,15 +6,25 @@ module FE
6
6
  class Document
7
7
 
8
8
 
9
- class Receiver
9
+ class Receiver < Element
10
10
  include ActiveModel::Validations
11
11
 
12
- attr_accessor :name, :identification_document,:foreign_id_number, :comercial_name, :location, :phone, :fax, :email
12
+ attr_accessor :name, :identification_document,:foreign_id_number, :comercial_name, :location, :phone, :fax, :email,:other_foreign_signs, :document_type
13
13
 
14
- validates :name, presence: true, length: { maximum: 80 }
15
- validates :comercial_name, length: { maximum: 80 }
16
- validates :foreign_id_number, length: { maximum: 20 }
14
+ validates :name, presence: true
15
+
16
+ validates :identification_document, presence: true, if: -> {document_type.eql?("01") || document_type.eql?("08")}
17
+ validates :foreign_id_number, length: { maximum: 20 }, if: -> {document_type.eql?("01") || document_type.eql?("08")}
18
+ validates :other_foreign_signs,length: { maximum: 300 }
19
+ validates :email, length: {maximum: 160}, format:{with: /\s*\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*\s*/}, if: ->{ email.present? }
20
+
17
21
 
22
+ validates :name, length: { maximum: 80}, if: ->{ document.version_42? }
23
+ validates :comercial_name, length: { maximum: 80 }, if: ->{ document.version_42? }
24
+
25
+
26
+ validates :name, length: { maximum: 100}, if: ->{ document.version_43? }
27
+ validates :comercial_name, length: { maximum: 100 }, if: ->{ document.version_43? }
18
28
 
19
29
 
20
30
  def initialize(args={})
@@ -27,31 +37,31 @@ module FE
27
37
  @fax = args[:fax]
28
38
  @email = args[:email]
29
39
  @foreign_id_number = args[:foreign_id_number]
40
+ @other_foreign_signs= args[:other_foreign_signs]
30
41
 
31
42
  end
32
43
 
33
- def build_xml(node)
34
- raise "IdentificationDocument is invalid" if !@identification_document.nil? && !@identification_document.is_a?(IdentificationDocument)
35
- raise "Location is invalid" if !@location.nil? && !@location.is_a?(Location)
36
- raise "Phone is invalid" if !@phone.nil? && !@phone.is_a?(Phone)
37
- raise "Fax is invalid" if !@fax.nil? && !@fax.is_a?(Fax)
38
- raise "Reciever is invalid: #{errors.messages}" unless valid?
39
-
44
+ def build_xml(node, document)
45
+ @document = document
46
+ @document_type = document.document_type
47
+ raise FE::Error.new("receiver invalid",class: self.class, messages: errors.messages) unless valid?
48
+
40
49
  node = Nokogiri::XML::Builder.new if node.nil?
41
50
  node.Receptor do |xml|
42
51
  xml.Nombre @name
43
- @identification_document.build_xml(xml) if @identification_document.present?
52
+ @identification_document.build_xml(xml,@document) if @identification_document.present?
44
53
  xml.IdentificacionExtranjero foreign_id_number if @foreign_id_number.present?
45
54
  xml.NombreComercial @comercial_name if @comercial_name.present?
46
- @location.build_xml(xml) if @location.present?
47
- @phone.build_xml(xml) if @phone.present?
48
- @fax.build_xml(xml) if @fax.present?
55
+ @location.build_xml(xml,@document) if @location.present?
56
+ @phone.build_xml(xml, @document) if @phone.present?
57
+ @fax.build_xml(xml, @document) if @fax.present?
49
58
  xml.CorreoElectronico @email if @email.present?
59
+ xml.OtrasSenasExtranjero @other_foreign_signs if @other_foreign_signs.present? && @document.version_43?
50
60
  end
51
61
  end
52
62
 
53
- def to_xml(builder)
54
- build_xml(builder).to_xml
63
+ def to_xml(builder, document)
64
+ build_xml(builder, document).to_xml
55
65
  end
56
66
  end
57
67
 
@@ -2,11 +2,11 @@ require 'facturacr/document'
2
2
 
3
3
  module FE
4
4
  class Document
5
- class Reference
5
+ class Reference < Element
6
6
  include ActiveModel::Validations
7
-
7
+
8
8
  attr_accessor :document_type, :number, :date, :code, :reason
9
-
9
+
10
10
  REFERENCE_CODES = {
11
11
  "01" => "Anula Documento de referencia",
12
12
  "02" => "Corrige texto documento de referencia",
@@ -14,14 +14,33 @@ module FE
14
14
  "04" => "Referencia a otro documento",
15
15
  "05" => "Sustituye comprobante provisional por contingencia",
16
16
  "99" => "Otros"
17
- }
17
+ }.freeze
18
18
 
19
- validates :document_type, presence: true, inclusion: FE::Document::DOCUMENT_TYPES.keys
20
- validates :number, presence: true, length: {maximum: 50}
21
- validates :date, presence: true
22
- validates :code, presence: true, length: {is: 2}, inclusion: REFERENCE_CODES.keys
23
- validates :reason, presence: true, length: {maximum: 180}
19
+ DOCUMENT_TYPES = {
20
+ "01"=> "Factura Electronica",
21
+ "02"=> "Nota de débito",
22
+ "03"=> "Nota de crédito",
23
+ "04"=> "Tiquete Electrónico",
24
+ "05"=> "Nota de despacho",
25
+ "06"=> "Contrato",
26
+ "07"=> "Procedimiento",
27
+ "08"=> "Factura Electrónica de compra",
28
+ "09"=> "Factura Electronica de exportación",
29
+ "10"=> "Sustituye factura rechazada por el Ministerio de Hacienda",
30
+ "11"=> "Sustituye factura rechazada por el Receptor del comprobante",
31
+ "12"=> "Sustituye Factura de exportación",
32
+ "13"=> "Facturación mes vencido",
33
+ "99"=> "Otros"
34
+
35
+ }.freeze
24
36
 
37
+
38
+ validates :document_type, presence: true, inclusion: DOCUMENT_TYPES.keys
39
+ validates :number, presence: true, length: {maximum: 50}, if: ->{ document_type.present? }
40
+ 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? }
43
+
25
44
  def initialize(args={})
26
45
  @document_type = args[:document_type]
27
46
  @number = args[:number]
@@ -29,9 +48,9 @@ module FE
29
48
  @code = args[:code]
30
49
  @reason = args[:reason]
31
50
  end
32
-
33
- def build_xml(node)
34
- raise "Reference Invalid: #{errors.messages}" unless valid?
51
+
52
+ def build_xml(node, document)
53
+ raise FE::Error.new("reference invalid",class: self.class, messages: errors.messages) unless valid?
35
54
  node = Nokogiri::XML::Builder.new if node.nil?
36
55
  node.InformacionReferencia do |xml|
37
56
  xml.TipoDoc @document_type
@@ -43,4 +62,4 @@ module FE
43
62
  end
44
63
  end
45
64
  end
46
- end
65
+ end
@@ -1,6 +1,6 @@
1
1
  module FE
2
2
  class Document
3
- class Regulation
3
+ class Regulation < Element
4
4
  include ActiveModel::Validations
5
5
 
6
6
  attr_accessor :number, :date
@@ -13,8 +13,8 @@ module FE
13
13
  @date = args[:date] ||= "20-02-2017 13:22:22"
14
14
  end
15
15
 
16
- def build_xml(node)
17
- raise "Regulation Invalid: #{errors.messages}" unless valid?
16
+ def build_xml(node, document)
17
+ raise FE::Error.new("regulation invalid",class: self.class, messages: errors.messages) unless valid?
18
18
  node = Nokogiri::XML::Builder.new if node.nil?
19
19
  node.Normativa do |xml|
20
20
  xml.NumeroResolucion @number
@@ -1,64 +1,96 @@
1
1
  module FE
2
2
  class Document
3
- class Summary
3
+ class Summary < Element
4
4
  include ActiveModel::Validations
5
-
6
- attr_accessor :currency, :exchange_rate, :services_taxable_total, :services_exent_total,
7
- :goods_taxable_total,:goods_exent_total, :taxable_total, :exent_total,
8
- :subtotal, :discount_total, :gross_total, :tax_total, :net_total
9
-
10
- validates :exchange_rate, presence: true, if: -> { currency.present? }
11
-
5
+
6
+ attr_accessor :currency, :exchange_rate, :services_taxable_total, :services_exent_total, :services_exonerate_total,
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,
9
+ :with_credit_card, :document_type, :has_exoneration, :medical_services_condition
10
+
11
+ validates :currency, presence: true
12
+ validates :exchange_rate, presence: true, if: -> { currency.present? && currency != "CRC" }
13
+
14
+ validates :services_exonerate_total, presence: true, if: -> { document.version_43? && !document_type.eql?(FE::ExportInvoice::DOCUMENT_TYPE) && has_exoneration}
15
+ validates :goods_exonerate_total, presence: true, if: -> { document.version_43? && !document_type.eql?(FE::ExportInvoice::DOCUMENT_TYPE) && has_exoneration}
16
+ validates :exonerate_total, presence: true, if: -> { document.version_43? && !document_type.eql?(FE::ExportInvoice::DOCUMENT_TYPE) && has_exoneration}
17
+
18
+ validates :total_iva_returned, presence: true, if: -> { document.version_43? && medical_services_condition }
12
19
  validate :totals_ok?
13
-
20
+
14
21
  def initialize(args={})
15
22
  @currency = args[:currency]
16
23
  @exchange_rate = args[:exchange_rate]
17
24
  @services_taxable_total = args[:services_taxable_total].to_f
18
25
  @services_exent_total = args[:services_exent_total].to_f
26
+ @services_exonerate_total = args[:services_exonerate_total].to_f
19
27
  @goods_taxable_total = args[:goods_taxable_total].to_f
20
28
  @goods_exent_total = args[:goods_exent_total].to_f
29
+ @goods_exonerate_total = args[:goods_exonerate_total].to_f
21
30
  @taxable_total = args[:taxable_total].to_f
22
31
  @exent_total = args[:exent_total].to_f
32
+ @exonerate_total = args[:exonerate_total].to_f
23
33
  @subtotal = args[:subtotal].to_f
24
34
  @discount_total = args[:discount_total].to_f
25
35
  @gross_total = args[:gross_total].to_f
26
36
  @tax_total = args[:tax_total].to_f
37
+ @total_iva_returned = args[:total_iva_returned].to_f
38
+ @total_others_charges =args[:total_others_charges].to_f
27
39
  @net_total = args[:net_total].to_f
40
+ @has_exoneration = args[:has_exoneration] || false
41
+ @medical_services_condition = args[:medical_services_condition] || false
28
42
  end
29
-
30
- def build_xml(node)
31
- unless valid?
32
- raise "Summary invalid: #{errors.messages}"
33
- end
43
+
44
+ def build_xml(node, document)
45
+ @document = document
46
+ @document_type = document.document_type
47
+ raise FE::Error.new("summary invalid",class: self.class, messages: errors.messages) unless valid?
34
48
  node = Nokogiri::XML::Builder.new if node.nil?
35
-
49
+
36
50
  node.ResumenFactura do |xml|
37
- xml.CodigoMoneda @currency if @currency.present?
38
- xml.TipoCambio @exchange_rate if @exchange_rate.present?
51
+ if document.version_42?
52
+ xml.CodigoMoneda @currency if @currency.present?
53
+ xml.TipoCambio @exchange_rate if @exchange_rate.present?
54
+ elsif document.version_43? && @currency.present? && @currency != "CRC"
55
+ xml.CodigoTipoMoneda do |x|
56
+ x.CodigoMoneda @currency
57
+ x.TipoCambio @exchange_rate
58
+ end
59
+ end
60
+
39
61
  xml.TotalServGravados @services_taxable_total
40
62
  xml.TotalServExentos @services_exent_total
63
+ xml.TotalServExonerado @services_exonerate_total if @services_exonerate_total && document.version_43? && !document_type.eql?(FE::ExportInvoice::DOCUMENT_TYPE)
41
64
  xml.TotalMercanciasGravadas @goods_taxable_total
42
65
  xml.TotalMercanciasExentas @goods_exent_total
66
+ xml.TotalMercExonerada @goods_exonerate_total if @goods_exonerate_total.present? && document.version_43? && !document_type.eql?(FE::ExportInvoice::DOCUMENT_TYPE)
43
67
  xml.TotalGravado @taxable_total
44
68
  xml.TotalExento @exent_total
69
+ xml.TotalExonerado @exonerate_total if @exonerate_total.present? && document.version_43? && !document_type.eql?(FE::ExportInvoice::DOCUMENT_TYPE)
45
70
  xml.TotalVenta @subtotal
46
71
  xml.TotalDescuentos @discount_total
47
72
  xml.TotalVentaNeta @gross_total
48
73
  xml.TotalImpuesto @tax_total
74
+ if document.version_43?
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
77
+ end
49
78
  xml.TotalComprobante @net_total
50
79
  end
51
80
  end
52
-
81
+
53
82
  private
54
-
83
+
55
84
  def totals_ok?
56
85
  errors.add :taxable_total, :invalid_amount, message: 'invalid amount' if (@taxable_total - (@services_taxable_total + @goods_taxable_total).round(5)).abs > 0.0005
57
86
  errors.add :exent_total, :invalid_amount, message: 'invalid amount' if (@exent_total - (@services_exent_total + @goods_exent_total).round(5)).abs > 0.0005
58
- errors.add :subtotal, :invalid_amount, message: 'invalid amount' if (@subtotal - (@taxable_total + @exent_total).round(5)).abs > 0.0005
87
+ if document.version_43?
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
+ end
90
+ errors.add :subtotal, :invalid_amount, message: 'invalid amount' if (@subtotal - (@taxable_total + @exent_total + @exonerate_total).round(5)).abs > 0.0005
59
91
  errors.add :gross_total, :invalid_amount, message: 'invalid amount' if (@gross_total - (@subtotal - @discount_total).round(5)).abs > 0.0005
60
- errors.add :net_total, :invalid_amount, message: 'invalid amount' if (@net_total - (@gross_total + @tax_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
61
93
  end
62
94
  end
63
95
  end
64
- end
96
+ end
@@ -1,54 +1,76 @@
1
1
  module FE
2
2
  class Document
3
- class Tax
3
+ class Tax < Element
4
4
  include ActiveModel::Validations
5
-
5
+
6
6
  TAX_CODES = {
7
- "01"=>"Impuesto General sobre las Ventas",
7
+ "01"=>"Impuesto al Valor Agregado",
8
8
  "02"=>"Impuesto Selectivo de Consumo",
9
9
  "03"=>"Impuesto Único a los combustibles",
10
10
  "04"=>"Impuesto específico de bebidas alcohólicas",
11
11
  "05"=>"Impuesto Específico sobre las bebidas envasadas sin contenido alcóholico y jabones de tocador",
12
12
  "06"=>"Impuesto a los Productos de Tabaco",
13
- "07"=>"Servicio",
13
+ "07"=>"IVA (cálculo especial)",
14
+ "08"=>"IVA Régimen de Bienes Usados (Factor)",
14
15
  "12"=>"Impuesto específico al cemento",
15
- "98"=>"Otros",
16
- "08"=>"Impuesto General sobre las ventas diplomáticos",
17
- "09"=>"Impuesto general sobre las ventas Compras autorizadas",
18
- "10"=>"Impuesto general sobre las ventas instituciones públicas y otros organismos",
19
- "11"=>"Impuesto Selectivo de Consumo Compras Autorizadas",
20
16
  "99"=>"Otros"
21
- }
22
- attr_accessor :code, :rate, :total, :exoneration
23
-
17
+ }.freeze
18
+ RATE_CODES ={
19
+ "01"=>"Tarifa 0% (Exento)",
20
+ "02"=>"Tarifa reducida 1%",
21
+ "03"=>"Tarifa reducida 2%",
22
+ "04"=>"Tarifa reducida 4%",
23
+ "05"=>"Transitorio 0%",
24
+ "06"=>"Transitorio 4% ",
25
+ "07"=>"Transitorio 8% ",
26
+ "08"=>"Tarifa general 13%"
27
+ }.freeze
28
+ attr_accessor :code, :rate_code ,:rate, :iva_factor, :total, :exoneration, :total_exportation
29
+
30
+ validates :rate_code, inclusion: RATE_CODES.keys, presence: true, if:->{ document.version_43?}
24
31
  validates :code, presence: true, inclusion: TAX_CODES.keys
32
+ # validates :total_exportation, presence: false, if:->{:document_type.eql?("01") || :document_type.eql?("08") || :document_type.eql?("04")}
25
33
  # It is a mandatory field when a tax is added. And it is a decimal number that can be composed of 4 integers and 2 decimals
26
34
  validates :rate, presence: true, format: { with: /\A\d{1,4}(\.\d{0,2})?\z/ }
27
35
  # It is a mandatory field when a tax is added, it is obtained from the multiplication of the "subtotal" field by "tax rate"
28
36
  # And is a decimal number that can be composed of 13 integers and 5 decimals
29
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")}
39
+ # validates :iva_factor, presence: true, if: ->{ }
30
40
 
31
41
  def initialize(args={})
32
42
  @code = args[:code]
43
+ @rate_code = args[:rate_code]
33
44
  @rate = args[:rate]
45
+ @iva_factor = args[:iva_factor]
34
46
  @total = args[:total]
35
47
  @exoneration = args[:exoneration]
48
+ @total_exportation = args[:total_exportation]
49
+
36
50
  end
37
-
38
- def build_xml(node)
39
- raise "Invalida Record: #{errors.messages}" unless valid?
51
+
52
+
53
+ def build_xml(node, document)
54
+ @document = document
55
+ raise FE::Error.new("tax invalid",class: self.class, messages: errors.messages) unless valid?
40
56
  node = Nokogiri::XML::Builder.new if node.nil?
41
-
57
+
42
58
  node.Impuesto do |xml|
43
59
  xml.Codigo @code
60
+ xml.CodigoTarifa @rate_code if @rate_code.present? && document.version_43?
44
61
  xml.Tarifa @rate
62
+ xml.FactorIva @iva_factor if @iva_factor.present? && document.version_43?
45
63
  xml.Monto @total
46
- if @exoneration.present?
47
- @exoneration.build_xml(xml)
48
- end
64
+ xml.MontoExportacion @total_exportation if @total_exportation.present? && document.version_43?
65
+
66
+ if exoneration.present?
67
+ exoneration.build_xml(xml,document)
68
+ end
69
+
49
70
  end
50
71
  end
51
-
72
+
73
+
52
74
  end
53
75
  end
54
- end
76
+ end