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
@@ -0,0 +1,30 @@
1
+ module FE
2
+ class Document
3
+ class OtherContent < Element
4
+ include ActiveModel::Validations
5
+ attr_accessor :implementation
6
+
7
+ validates :implementation, presence: true
8
+
9
+
10
+ def initialize(args={})
11
+ @implementation = args[:implementation]
12
+ end
13
+
14
+ def build_xml(node, document)
15
+ raise FE::Error.new("other content",class: self.class, messages: errors.messages) unless valid?
16
+ node = Nokogiri::XML::Builder.new if node.nil?
17
+ node.OtroContenido do |xml|
18
+ @implementation.build_xml(xml, document)
19
+ end
20
+ end
21
+
22
+ def to_xml(builder,document)
23
+ build_xml(builder,document).to_xml
24
+ end
25
+
26
+ end
27
+ end
28
+ end
29
+
30
+ require 'facturacr/document/other_content/price_smart'
@@ -0,0 +1,58 @@
1
+ module FE
2
+ class Document
3
+ class OtherContent
4
+ class PriceSmart
5
+ include ActiveModel::Validations
6
+ attr_accessor :transaction_type, :order_number, :order_date, :supplier_number, :store_code
7
+
8
+ TYPES = ['purchase', 'expense']
9
+ TYPES.each do |t|
10
+ define_method "#{t}?" do
11
+ transaction_type.eql?(t)
12
+ end
13
+ end
14
+
15
+ NAMESPACES = {
16
+ "xsi:schemaLocation" => "https://invoicer.ekomercio.com/esquemas https://invoices/ekomercio.com/esquemas/ComplementoPricesmartCR_V1_0.xsd",
17
+ "xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance",
18
+ "xmlns:retail" => "https://invoicer.ekomercio.com/esquemas"
19
+ }
20
+
21
+ validates :transaction_type, presence: true, inclusion: TYPES
22
+ validates :supplier_number, presence: true, length: {maximum: 12}
23
+ validates :store_code, presence: true
24
+ validates :order_number, presence: true, if: ->{ purchase? }
25
+ validates :order_date, presence: true, if: ->{ purchase? }
26
+
27
+ def initialize(args={})
28
+ @transaction_type = args[:transaction_type]
29
+ @supplier_number = args[:supplier_number]
30
+ @store_code = args[:store_code]
31
+ @order_number = args[:order_number]
32
+ @order_date = args[:order_date]
33
+ end
34
+
35
+ def build_xml(node)
36
+ raise "Invalid Record: #{errors.messages}" unless valid?
37
+ node = Nokogiri::XML::Builder.new if node.nil?
38
+ node['retail'].Complemento(NAMESPACES) do |xml|
39
+ xml['retail'].NumeroVendedor @supplier_number
40
+ if purchase?
41
+ xml['retail'].OrdenDeCompra do |xml2|
42
+ xml2['retail'].NumeroOrden @order_number
43
+ xml2['retail'].FechaOrden @order_date.xmlschema
44
+ end
45
+ end
46
+ xml['retail'].LugarDeEntrega do |xml2|
47
+ xml2['retail'].GLNLugarDeEntrega @store_code
48
+ end
49
+ end
50
+ end
51
+
52
+ def to_xml(builder)
53
+ build_xml(builder)
54
+ end
55
+ end
56
+ end
57
+ end
58
+ 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,18 +10,14 @@ 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|
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,10 +1,6 @@
1
- require "facturacr/document"
2
- require 'active_model'
3
- require 'nokogiri'
4
-
5
1
  module FE
6
2
  class Document
7
- class PhoneType
3
+ class PhoneType < Element
8
4
  include ActiveModel::Validations
9
5
 
10
6
  attr_accessor :tag_name, :country_code, :number
@@ -20,8 +16,9 @@ module FE
20
16
  @number = number
21
17
  end
22
18
 
23
- def build_xml(node)
24
- raise "Invalid Record: #{errors.messages}" unless valid?
19
+ def build_xml(node, document)
20
+ raise FE::Error.new("phone type invalid",class: self.class, messages: errors.messages) unless valid?
21
+
25
22
  node = Nokogiri::XML::Builder.new if node.nil?
26
23
  node.send(tag_name) do |xml|
27
24
  xml.CodigoPais country_code
@@ -29,8 +26,8 @@ module FE
29
26
  end
30
27
  end
31
28
 
32
- def to_xml(builder)
33
- build_xml(builder).to_xml
29
+ def to_xml(builder,document)
30
+ build_xml(builder, document).to_xml
34
31
  end
35
32
  end
36
33
  end
@@ -1,4 +1,3 @@
1
- require "facturacr/document"
2
1
  require 'active_model'
3
2
  require 'nokogiri'
4
3
 
@@ -6,15 +5,25 @@ module FE
6
5
  class Document
7
6
 
8
7
 
9
- class Receiver
8
+ class Receiver < Element
10
9
  include ActiveModel::Validations
11
10
 
12
- attr_accessor :name, :identification_document,:foreign_id_number, :comercial_name, :location, :phone, :fax, :email
11
+ attr_accessor :name, :identification_document,:foreign_id_number, :comercial_name, :location, :phone, :fax, :email,:other_foreign_signs, :document_type
13
12
 
14
- validates :name, presence: true, length: { maximum: 80 }
15
- validates :comercial_name, length: { maximum: 80 }
16
- validates :foreign_id_number, length: { maximum: 20 }
13
+ validates :name, presence: true
14
+
15
+ validates :identification_document, presence: true, if: -> {document_type.eql?("01") || document_type.eql?("08")}
16
+ validates :foreign_id_number, length: { maximum: 20 }, if: -> {document_type.eql?("01") || document_type.eql?("08")}
17
+ validates :other_foreign_signs,length: { maximum: 300 }
18
+ validates :email, length: {maximum: 160}, format:{with: /\s*\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*\s*/}, if: ->{ email.present? }
19
+
17
20
 
21
+ validates :name, length: { maximum: 80}, if: ->{ document.version_42? }
22
+ validates :comercial_name, length: { maximum: 80 }, if: ->{ document.version_42? }
23
+
24
+
25
+ validates :name, length: { maximum: 100}, if: ->{ document.version_43? }
26
+ validates :comercial_name, length: { maximum: 100 }, if: ->{ document.version_43? }
18
27
 
19
28
 
20
29
  def initialize(args={})
@@ -27,31 +36,31 @@ module FE
27
36
  @fax = args[:fax]
28
37
  @email = args[:email]
29
38
  @foreign_id_number = args[:foreign_id_number]
39
+ @other_foreign_signs= args[:other_foreign_signs]
30
40
 
31
41
  end
32
42
 
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
-
43
+ def build_xml(node, document)
44
+ @document = document
45
+ @document_type = document.document_type
46
+ raise FE::Error.new("receiver invalid",class: self.class, messages: errors.messages) unless valid?
47
+
40
48
  node = Nokogiri::XML::Builder.new if node.nil?
41
49
  node.Receptor do |xml|
42
50
  xml.Nombre @name
43
- @identification_document.build_xml(xml) if @identification_document.present?
51
+ @identification_document.build_xml(xml,@document) if @identification_document.present?
44
52
  xml.IdentificacionExtranjero foreign_id_number if @foreign_id_number.present?
45
53
  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?
54
+ @location.build_xml(xml,@document) if @location.present?
55
+ @phone.build_xml(xml, @document) if @phone.present?
56
+ @fax.build_xml(xml, @document) if @fax.present?
49
57
  xml.CorreoElectronico @email if @email.present?
58
+ xml.OtrasSenasExtranjero @other_foreign_signs if @other_foreign_signs.present? && @document.version_43?
50
59
  end
51
60
  end
52
61
 
53
- def to_xml(builder)
54
- build_xml(builder).to_xml
62
+ def to_xml(builder, document)
63
+ build_xml(builder, document).to_xml
55
64
  end
56
65
  end
57
66
 
@@ -1,12 +1,10 @@
1
- require 'facturacr/document'
2
-
3
1
  module FE
4
2
  class Document
5
- class Reference
3
+ class Reference < Element
6
4
  include ActiveModel::Validations
7
-
5
+
8
6
  attr_accessor :document_type, :number, :date, :code, :reason
9
-
7
+
10
8
  REFERENCE_CODES = {
11
9
  "01" => "Anula Documento de referencia",
12
10
  "02" => "Corrige texto documento de referencia",
@@ -14,14 +12,34 @@ module FE
14
12
  "04" => "Referencia a otro documento",
15
13
  "05" => "Sustituye comprobante provisional por contingencia",
16
14
  "99" => "Otros"
17
- }
15
+ }.freeze
18
16
 
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}
17
+ DOCUMENT_TYPES = {
18
+ "01"=> "Factura Electronica",
19
+ "02"=> "Nota de débito",
20
+ "03"=> "Nota de crédito",
21
+ "04"=> "Tiquete Electrónico",
22
+ "05"=> "Nota de despacho",
23
+ "06"=> "Contrato",
24
+ "07"=> "Procedimiento",
25
+ "08"=> "Factura Electrónica de compra",
26
+ "09"=> "Factura Electronica de exportación",
27
+ "10"=> "Sustituye factura rechazada por el Ministerio de Hacienda",
28
+ "11"=> "Sustituye factura rechazada por el Receptor del comprobante",
29
+ "12"=> "Sustituye Factura de exportación",
30
+ "13"=> "Facturación mes vencido",
31
+ "99"=> "Otros"
32
+
33
+ }.freeze
24
34
 
35
+
36
+ validates :document_type, presence: true, inclusion: DOCUMENT_TYPES.keys
37
+ validates :date, presence: true
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" }
42
+
25
43
  def initialize(args={})
26
44
  @document_type = args[:document_type]
27
45
  @number = args[:number]
@@ -29,18 +47,18 @@ module FE
29
47
  @code = args[:code]
30
48
  @reason = args[:reason]
31
49
  end
32
-
33
- def build_xml(node)
34
- raise "Reference Invalid: #{errors.messages}" unless valid?
50
+
51
+ def build_xml(node, document)
52
+ raise FE::Error.new("reference invalid",class: self.class, messages: errors.messages) unless valid?
35
53
  node = Nokogiri::XML::Builder.new if node.nil?
36
54
  node.InformacionReferencia do |xml|
37
55
  xml.TipoDoc @document_type
38
- xml.Numero @number
56
+ xml.Numero @number if @number.present?
39
57
  xml.FechaEmision @date.xmlschema
40
- xml.Codigo @code
41
- xml.Razon @reason
58
+ xml.Codigo @code if @code.present?
59
+ xml.Razon @reason if @reason.present?
42
60
  end
43
61
  end
44
62
  end
45
63
  end
46
- end
64
+ 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_other_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_other_charges = args[:total_other_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: #{ errors.messages.map{|k,v| "#{k}=#{v.join(". ")}"}.join("; ")}",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 || 1
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_other_charges if @total_other_charges > 0
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_other_charges - @total_iva_returned).round(5)).abs > 0.0005
61
93
  end
62
94
  end
63
95
  end
64
- end
96
+ end