facturacr 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/.DS_Store +0 -0
  3. data/.gitignore +9 -0
  4. data/.travis.yml +4 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +7 -0
  7. data/README.md +181 -0
  8. data/Rakefile +10 -0
  9. data/bin/console +14 -0
  10. data/bin/setup +7 -0
  11. data/bin/signer/signer.jar +0 -0
  12. data/config/config.yml +10 -0
  13. data/exe/facturacr +3 -0
  14. data/facturacr.gemspec +31 -0
  15. data/lib/facturacr.rb +37 -0
  16. data/lib/facturacr/api.rb +68 -0
  17. data/lib/facturacr/api/document_status.rb +36 -0
  18. data/lib/facturacr/builder.rb +223 -0
  19. data/lib/facturacr/cli.rb +116 -0
  20. data/lib/facturacr/cli/generate.rb +133 -0
  21. data/lib/facturacr/configuration.rb +51 -0
  22. data/lib/facturacr/credit_note.rb +33 -0
  23. data/lib/facturacr/debit_note.rb +33 -0
  24. data/lib/facturacr/document.rb +171 -0
  25. data/lib/facturacr/document/code.rb +4 -0
  26. data/lib/facturacr/document/exoneration.rb +49 -0
  27. data/lib/facturacr/document/fax.rb +24 -0
  28. data/lib/facturacr/document/identification_document.rb +41 -0
  29. data/lib/facturacr/document/issuer.rb +56 -0
  30. data/lib/facturacr/document/item.rb +67 -0
  31. data/lib/facturacr/document/location.rb +49 -0
  32. data/lib/facturacr/document/phone.rb +22 -0
  33. data/lib/facturacr/document/phone_type.rb +37 -0
  34. data/lib/facturacr/document/receiver.rb +59 -0
  35. data/lib/facturacr/document/reference.rb +46 -0
  36. data/lib/facturacr/document/regulation.rb +26 -0
  37. data/lib/facturacr/document/summary.rb +64 -0
  38. data/lib/facturacr/document/tax.rb +44 -0
  39. data/lib/facturacr/invoice.rb +34 -0
  40. data/lib/facturacr/signed_document.rb +19 -0
  41. data/lib/facturacr/signer/signer.rb +245 -0
  42. data/lib/facturacr/ticket.rb +34 -0
  43. data/lib/facturacr/version.rb +3 -0
  44. data/lib/facturacr/xml_document.rb +161 -0
  45. data/resources/FacturaElectronica_V.4.2.xsd +1571 -0
  46. data/resources/NotaCreditoElectronica_V4.2.xsd +1657 -0
  47. data/resources/credit_note.xml +86 -0
  48. data/resources/data.yml +73 -0
  49. data/resources/debit_note.xml +86 -0
  50. data/resources/invoice.xml +94 -0
  51. data/resources/pruebas.xml +37 -0
  52. data/resources/test.p12 +0 -0
  53. metadata +235 -0
@@ -0,0 +1,67 @@
1
+ module FE
2
+ class Document
3
+ class Item
4
+ include ActiveModel::Validations
5
+
6
+ attr_accessor :line_number, :code, :quantity, :unit, :description, :unit_price, :total,
7
+ :discount, :discount_reason, :subtotal, :taxes, :exoneration, :net_total
8
+
9
+ validates :line_number, presence: true
10
+ validates :quantity, presence: true, numericality: {greater_than: 0}
11
+ validates :unit, presence: true
12
+ validates :description, presence: true, length: {maximum: 160 }
13
+ validates :unit_price, presence: true
14
+ validates :total, presence: true
15
+ validates :discount, numericality: { grater_than: 0}, if: ->{ discount.present? }
16
+ validates :discount_reason, presence: true, if: ->{ discount.present? }
17
+ validates :subtotal, presence: true
18
+ validates :net_total, presence: true
19
+
20
+
21
+ def initialize(args={})
22
+ @line_number = args[:line_number]
23
+ @code = args[:code]
24
+ @quantity = args[:quantity]
25
+ @unit = args[:unit]
26
+ @description = args[:description]
27
+ @unit_price = args[:unit_price]
28
+ @total = args[:total]
29
+ @discount = args[:discount]
30
+ @discount_reason = args[:discount_reason]
31
+ @subtotal = args[:subtotal]
32
+ @taxes = args[:taxes] || []
33
+ @net_total = args[:net_total]
34
+ end
35
+
36
+ def build_xml(node)
37
+ raise "Item invalid: #{errors.messages}" unless valid?
38
+ node = Nokogiri::XML::Builder.new if node.nil?
39
+ node.LineaDetalle do |x|
40
+ x.NumeroLinea @line_number
41
+ if @code.present?
42
+ x.Codigo do |x2|
43
+ x2.Tipo "01"
44
+ x2.Codigo @code
45
+ end
46
+ end
47
+ x.Cantidad @quantity
48
+ x.UnidadMedida @unit
49
+ x.Detalle @description
50
+ x.PrecioUnitario @unit_price
51
+ x.MontoTotal @total
52
+ x.Discount @discount if @discount.present?
53
+ x.NaturalezaDescuento @discount_reason if @discount_reason.present?
54
+ x.SubTotal @subtotal
55
+ @taxes.each do |tax|
56
+ tax.build_xml(x)
57
+ end
58
+ if @exoneration.present?
59
+ @exoneration.build_xml(x)
60
+ end
61
+ x.MontoTotalLinea @net_total
62
+ end
63
+ end
64
+
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,49 @@
1
+ require "facturacr/document"
2
+ require 'active_model'
3
+ require 'nokogiri'
4
+
5
+ module FE
6
+ class Document
7
+
8
+
9
+ class Location
10
+ include ActiveModel::Validations
11
+
12
+ attr_accessor :province, :county,:district,:neighborhood, :others
13
+
14
+ validates :province, presence: true, length: { is: 1 }
15
+ validates :county, presence: true, length: { is: 2 }
16
+ validates :district, presence: true, length: { is: 2 }
17
+ validates :neighborhood, length: { is: 2 }, allow_blank: true
18
+ validates :others, presence: true, length: { maximum: 160 }
19
+
20
+ def initialize(args={})
21
+
22
+ @province = args[:province]
23
+ @county = args[:county]
24
+ @district = args[:district]
25
+ @neighborhood = args[:neighborhood]
26
+ @others = args[:others]
27
+
28
+
29
+ end
30
+
31
+ def build_xml(node)
32
+ raise "Location invalid: #{errors.messages}" unless valid?
33
+ node = Nokogiri::XML::Builder.new if node.nil?
34
+ node.Ubicacion do |x|
35
+ x.Provincia @province
36
+ x.Canton @county
37
+ x.Distrito @district
38
+ x.Barrio @neighborhood unless @neighborhood.nil?
39
+ x.OtrasSenas @others
40
+ end
41
+ end
42
+
43
+ def to_xml(builder)
44
+ build_xml(builder).to_xml
45
+ end
46
+
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,22 @@
1
+ require "facturacr/document"
2
+ require 'facturacr/document/phone_type'
3
+ require 'active_model'
4
+
5
+ module FE
6
+ class Document
7
+ class Phone < PhoneType
8
+ include ActiveModel::Validations
9
+
10
+ attr_accessor :country_code, :number
11
+
12
+ validates :country_code, presence: true, length: { maximum: 3 }
13
+ validates :number, presence: true, length: {maximum: 20}
14
+
15
+
16
+ def initialize(args={})
17
+ super('Telefono', args[:country_code],args[:number])
18
+ end
19
+
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,37 @@
1
+ require "facturacr/document"
2
+ require 'active_model'
3
+ require 'nokogiri'
4
+
5
+ module FE
6
+ class Document
7
+ class PhoneType
8
+ include ActiveModel::Validations
9
+
10
+ attr_accessor :tag_name, :country_code, :number
11
+
12
+ validates :tag_name, presence: true, inclusion: ['Telefono','Fax']
13
+ validates :country_code, presence: true, length: { maximum: 3 }
14
+ validates :number, presence: true, length: {maximum: 20}, format: {with: /\d+/}
15
+
16
+
17
+ def initialize(tag_name, country_code, number)
18
+ @tag_name = tag_name
19
+ @country_code = country_code
20
+ @number = number
21
+ end
22
+
23
+ def build_xml(node)
24
+ raise "Invalid Record: #{errors.messages}" unless valid?
25
+ node = Nokogiri::XML::Builder.new if node.nil?
26
+ node.send(tag_name) do |xml|
27
+ xml.CodigoPais country_code
28
+ xml.NumTelefono number
29
+ end
30
+ end
31
+
32
+ def to_xml(builder)
33
+ build_xml(builder).to_xml
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,59 @@
1
+ require "facturacr/document"
2
+ require 'active_model'
3
+ require 'nokogiri'
4
+
5
+ module FE
6
+ class Document
7
+
8
+
9
+ class Receiver
10
+ include ActiveModel::Validations
11
+
12
+ attr_accessor :name, :identification_document,:foreign_id_number, :comercial_name, :location, :phone, :fax, :email
13
+
14
+ validates :name, presence: true, length: { maximum: 80 }
15
+ validates :comercial_name, length: { maximum: 80 }
16
+ validates :foreign_id_number, length: { maximum: 20 }
17
+
18
+
19
+
20
+ def initialize(args={})
21
+
22
+ @name = args[:name]
23
+ @identification_document = args[:identification_document]
24
+ @comercial_name = args[:comercial_name]
25
+ @location = args[:location]
26
+ @phone = args[:phone]
27
+ @fax = args[:fax]
28
+ @email = args[:email]
29
+ @foreign_id_number = args[:foreign_id_number]
30
+
31
+ end
32
+
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
+
40
+ node = Nokogiri::XML::Builder.new if node.nil?
41
+ node.Receptor do |xml|
42
+ xml.Nombre @name
43
+ @identification_document.build_xml(xml) if @identification_document.present?
44
+ xml.IdentificacionExtranjer foreign_id_number if @foreign_id_number.present?
45
+ 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?
49
+ xml.CorreElectronico @email if @email.present?
50
+ end
51
+ end
52
+
53
+ def to_xml(builder)
54
+ build_xml(builder).to_xml
55
+ end
56
+ end
57
+
58
+ end
59
+ end
@@ -0,0 +1,46 @@
1
+ require 'facturacr/document'
2
+
3
+ module FE
4
+ class Document
5
+ class Reference
6
+ include ActiveModel::Validations
7
+
8
+ attr_accessor :document_type, :number, :date, :code, :reason
9
+
10
+ REFERENCE_CODES = {
11
+ "01" => "Anula Documento de referencia",
12
+ "02" => "Corrige texto documento de referencia",
13
+ "03" => "Corrige monto",
14
+ "04" => "Referencia a otro documento",
15
+ "05" => "Sustituye comprobante provisional por contingencia",
16
+ "99" => "Otros"
17
+ }
18
+
19
+ validates :document_type, presence: true, inclusion: FE::Document::DOCUMENT_TYPES.keys
20
+ validates :number, presence: true, length: {is: 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}
24
+
25
+ def initialize(args={})
26
+ @document_type = args[:document_type]
27
+ @number = args[:number]
28
+ @date = args[:date]
29
+ @code = args[:code]
30
+ @reason = args[:reason]
31
+ end
32
+
33
+ def build_xml(node)
34
+ raise "Reference Invalid: #{errors.messages}" unless valid?
35
+ node = Nokogiri::XML::Builder.new if node.nil?
36
+ node.InformacionReferencia do |xml|
37
+ xml.TipoDoc @document_type
38
+ xml.Numero @number
39
+ xml.FechaEmision @date.xmlschema
40
+ xml.Codigo @code
41
+ xml.Razon @reason
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,26 @@
1
+ module FE
2
+ class Document
3
+ class Regulation
4
+ include ActiveModel::Validations
5
+
6
+ attr_accessor :number, :date
7
+
8
+ validates :number, presence: true
9
+ validates :date, presence: true
10
+
11
+ def initialize(args={})
12
+ @number = args[:number] ||= "DGT-R-48-2016"
13
+ @date = args[:date] ||= "20-02-2017 13:22:22"
14
+ end
15
+
16
+ def build_xml(node)
17
+ raise "Regulation Invalid: #{errors.messages}" unless valid?
18
+ node = Nokogiri::XML::Builder.new if node.nil?
19
+ node.Normativa do |xml|
20
+ xml.NumeroResolucion @number
21
+ xml.FechaResolucion @date
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,64 @@
1
+ module FE
2
+ class Document
3
+ class Summary
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
+
12
+ validate :totals_ok?
13
+
14
+ def initialize(args={})
15
+ @currency = args[:currency]
16
+ @exchange_rate = args[:exchange_rate]
17
+ @services_taxable_total = args[:services_taxable_total].to_f
18
+ @services_exent_total = args[:services_exent_total].to_f
19
+ @goods_taxable_total = args[:goods_taxable_total].to_f
20
+ @goods_exent_total = args[:goods_exent_total].to_f
21
+ @taxable_total = args[:taxable_total].to_f
22
+ @exent_total = args[:exent_total].to_f
23
+ @subtotal = args[:subtotal].to_f
24
+ @discount_total = args[:discount_total].to_f
25
+ @gross_total = args[:gross_total].to_f
26
+ @tax_total = args[:tax_total].to_f
27
+ @net_total = args[:net_total].to_f
28
+ end
29
+
30
+ def build_xml(node)
31
+ unless valid?
32
+ raise "Summary invalid: #{errors.messages}"
33
+ end
34
+ node = Nokogiri::XML::Builder.new if node.nil?
35
+
36
+ node.ResumenFactura do |xml|
37
+ xml.CodigoMoneda @currency if @currency.present?
38
+ xml.TipoCambio @exchange_rate if @exchange_rate.present?
39
+ xml.TotalServGravados @services_taxable_total
40
+ xml.TotalServExentos @services_exent_total
41
+ xml.TotalMercanciasGravadas @goods_taxable_total
42
+ xml.TotalMercanciasExentas @goods_exent_total
43
+ xml.TotalGravado @taxable_total
44
+ xml.TotalExento @exent_total
45
+ xml.TotalVenta @subtotal
46
+ xml.TotalDescuentos @discount_total
47
+ xml.TotalVentaNeta @gross_total
48
+ xml.TotalImpuesto @tax_total
49
+ xml.TotalComprobante @net_total
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def totals_ok?
56
+ errors[:taxable_total] << "invalid amount" unless @taxable_total == @services_taxable_total + @goods_taxable_total
57
+ errors[:exent_total] << "invalid amount" unless @exent_total == @services_exent_total + @goods_exent_total
58
+ errors[:subtotal] << "invalid amount" unless @subtotal == @taxable_total + @exent_total
59
+ errors[:gross_total] << "invalid amount" unless @gross_total == @subtotal - @discount_total
60
+ errors[:net_total] << "invalid amount" unless @net_total == @gross_total + @tax_total
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,44 @@
1
+ module FE
2
+ class Document
3
+ class Tax
4
+ include ActiveModel::Validations
5
+
6
+ TAX_CODES = {
7
+ "01"=>"Impuesto General sobre las Ventas",
8
+ "02"=>"Impuesto Selectivo de Consumo",
9
+ "03"=>"Impuesto Único a los combustibles",
10
+ "04"=>"Impuesto específico de bebidas alcohólicas",
11
+ "05"=>"Impuesto Específico sobre las bebidas envasadas sin contenido alcóholico y jabones de tocador",
12
+ "06"=>"Impuesto a los Productos de Tabaco",
13
+ "07"=>"Servicio",
14
+ "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
+ "99"=>"Otros"
21
+ }
22
+ attr_accessor :code, :rate, :total
23
+
24
+ validates :code, presence: true, inclusion: TAX_CODES.keys
25
+ def initialize(args={})
26
+ @code = args[:code]
27
+ @rate = args[:rate]
28
+ @total = args[:total]
29
+ end
30
+
31
+ def build_xml(node)
32
+ raise "Invalida Record: #{errors.messages}" unless valid?
33
+ node = Nokogiri::XML::Builder.new if node.nil?
34
+
35
+ node.Impuesto do |xml|
36
+ xml.Codigo @code
37
+ xml.Tarifa @rate
38
+ xml.Monto @total
39
+ end
40
+ end
41
+
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,34 @@
1
+ require 'facturacr/document'
2
+
3
+ module FE
4
+
5
+ class Invoice < Document
6
+
7
+ def initialize(args={})
8
+ @date = args[:date]
9
+ @issuer = args[:issuer]
10
+ @receiver = args[:receiver]
11
+ @items = args[:items]
12
+ @number = args[:number]
13
+ @condition = args[:condition]
14
+ @payment_type = args[:payment_type] || "01"
15
+ @document_type = "01"
16
+ @credit_term = args[:credit_term]
17
+ @summary = args[:summary]
18
+ @regulation = args[:regulation] ||= FE::Document::Regulation.new
19
+ @security_code = args[:security_code]
20
+ @document_situation = args[:document_situation]
21
+ @namespaces = {
22
+ "xmlns:xsi"=>"http://www.w3.org/2001/XMLSchema-instance",
23
+ "xmlns:xsd"=>"http://www.w3.org/2001/XMLSchema",
24
+ "xmlns"=>"https://tribunet.hacienda.go.cr/docs/esquemas/2017/v4.2/facturaElectronica"#,
25
+ #"xsi:schemaLocation"=>"https://tribunet.hacienda.go.cr/docs/esquemas/2017/v4.2/facturaElectronica https://tribunet.hacienda.go.cr/docs/esquemas/2017/v4.2/facturaElectronica.xsd"
26
+ }
27
+ end
28
+
29
+ def document_tag
30
+ "FacturaElectronica"
31
+ end
32
+
33
+ end
34
+ end