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.
- checksums.yaml +5 -5
- data/facturacr.gemspec +2 -2
- data/lib/facturacr.rb +25 -19
- data/lib/facturacr/api.rb +5 -4
- data/lib/facturacr/builder.rb +4 -4
- data/lib/facturacr/configuration.rb +18 -9
- data/lib/facturacr/credit_note.rb +24 -13
- data/lib/facturacr/data.rb +41 -0
- data/lib/facturacr/data_provider.rb +1 -1
- data/lib/facturacr/debit_note.rb +25 -13
- data/lib/facturacr/document.rb +120 -69
- data/lib/facturacr/document/exoneration.rb +17 -12
- data/lib/facturacr/document/fax.rb +1 -4
- data/lib/facturacr/document/identification_document.rb +7 -10
- data/lib/facturacr/document/issuer.rb +31 -32
- data/lib/facturacr/document/item.rb +77 -20
- data/lib/facturacr/document/location.rb +19 -23
- data/lib/facturacr/document/other_charges.rb +56 -0
- data/lib/facturacr/document/other_content.rb +30 -0
- data/lib/facturacr/document/other_content/price_smart.rb +58 -0
- data/lib/facturacr/document/other_text.rb +4 -8
- data/lib/facturacr/document/phone.rb +1 -3
- data/lib/facturacr/document/phone_type.rb +6 -9
- data/lib/facturacr/document/receiver.rb +28 -19
- data/lib/facturacr/document/reference.rb +36 -18
- data/lib/facturacr/document/regulation.rb +3 -3
- data/lib/facturacr/document/summary.rb +54 -22
- data/lib/facturacr/document/tax.rb +43 -21
- data/lib/facturacr/element.rb +9 -0
- data/lib/facturacr/error.rb +11 -0
- data/lib/facturacr/export_invoice.rb +38 -0
- data/lib/facturacr/invoice.rb +27 -15
- data/lib/facturacr/purchase_invoice.rb +39 -0
- data/lib/facturacr/reception_message.rb +73 -28
- data/lib/facturacr/signed_document.rb +4 -4
- data/lib/facturacr/signer/signer.rb +20 -8
- data/lib/facturacr/ticket.rb +27 -14
- data/lib/facturacr/version.rb +1 -1
- data/lib/facturacr/xml_document.rb +46 -29
- metadata +15 -9
@@ -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
|
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"=>"
|
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
|
-
|
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
|
-
|
39
|
-
|
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 @
|
47
|
-
|
48
|
-
|
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
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'facturacr/document'
|
2
|
+
|
3
|
+
module FE
|
4
|
+
|
5
|
+
class ExportInvoice < Document
|
6
|
+
validates :receiver, presence: true, if: -> { version.eql?("4.3") }
|
7
|
+
|
8
|
+
DOCUMENT_TYPE = "09"
|
9
|
+
def initialize(args={})
|
10
|
+
@version = args[:version]
|
11
|
+
@economic_activity = args[:economic_activity]
|
12
|
+
@date = args[:date]
|
13
|
+
@issuer = args[:issuer]
|
14
|
+
@receiver = args[:receiver]
|
15
|
+
@items = args[:items]
|
16
|
+
@number = args[:number]
|
17
|
+
@condition = args[:condition]
|
18
|
+
@payment_type = args[:payment_type] || ["01"]
|
19
|
+
@document_type = DOCUMENT_TYPE
|
20
|
+
@credit_term = args[:credit_term]
|
21
|
+
@summary = args[:summary]
|
22
|
+
@security_code = args[:security_code]
|
23
|
+
@document_situation = args[:document_situation]
|
24
|
+
@other_charges = args[:other_charges]
|
25
|
+
@namespaces = {
|
26
|
+
"xmlns:xsi"=>"http://www.w3.org/2001/XMLSchema-instance",
|
27
|
+
"xmlns:xsd"=>"http://www.w3.org/2001/XMLSchema",
|
28
|
+
"xmlns"=>"https://cdn.comprobanteselectronicos.go.cr/xml-schemas/v4.3/facturaElectronicaExportacion"#,
|
29
|
+
}
|
30
|
+
@others = args[:others] || []
|
31
|
+
end
|
32
|
+
|
33
|
+
def document_tag
|
34
|
+
"FacturaElectronicaExportacion"
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
data/lib/facturacr/invoice.rb
CHANGED
@@ -1,35 +1,47 @@
|
|
1
|
-
|
1
|
+
require_relative 'document'
|
2
2
|
|
3
3
|
module FE
|
4
|
-
|
4
|
+
|
5
5
|
class Invoice < Document
|
6
|
-
|
6
|
+
NAMESPACES ={
|
7
|
+
"4.2" => {
|
8
|
+
"xmlns:xsi"=>"http://www.w3.org/2001/XMLSchema-instance",
|
9
|
+
"xmlns:xsd"=>"http://www.w3.org/2001/XMLSchema",
|
10
|
+
"xmlns"=>"https://tribunet.hacienda.go.cr/docs/esquemas/2017/v4.2/facturaElectronica"#,
|
11
|
+
},
|
12
|
+
"4.3" => {
|
13
|
+
"xmlns:xsi"=>"http://www.w3.org/2001/XMLSchema-instance",
|
14
|
+
"xmlns:xsd"=>"http://www.w3.org/2001/XMLSchema",
|
15
|
+
"xmlns"=>"https://cdn.comprobanteselectronicos.go.cr/xml-schemas/v4.3/facturaElectronica"#,
|
16
|
+
}}
|
17
|
+
validates :receiver, presence: true, if: -> { version.eql?("4.3") }
|
18
|
+
|
19
|
+
DOCUMENT_TYPE = "01"
|
7
20
|
def initialize(args={})
|
21
|
+
@version = args[:version]
|
22
|
+
@economic_activity = args[:economic_activity]
|
8
23
|
@date = args[:date]
|
9
24
|
@issuer = args[:issuer]
|
10
25
|
@receiver = args[:receiver]
|
11
26
|
@items = args[:items]
|
12
27
|
@number = args[:number]
|
13
28
|
@condition = args[:condition]
|
14
|
-
@payment_type = args[:payment_type] || "01"
|
15
|
-
@document_type =
|
29
|
+
@payment_type = args[:payment_type] || ["01"]
|
30
|
+
@document_type = DOCUMENT_TYPE
|
16
31
|
@credit_term = args[:credit_term]
|
17
32
|
@summary = args[:summary]
|
18
33
|
@regulation = args[:regulation] ||= FE::Document::Regulation.new
|
19
34
|
@security_code = args[:security_code]
|
20
35
|
@document_situation = args[:document_situation]
|
21
|
-
@
|
22
|
-
|
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
|
-
}
|
36
|
+
@other_charges = args[:other_charges]
|
37
|
+
@namespaces = NAMESPACES[@version]
|
27
38
|
@others = args[:others] || []
|
39
|
+
@references = args[:references] || []
|
28
40
|
end
|
29
|
-
|
41
|
+
|
30
42
|
def document_tag
|
31
43
|
"FacturaElectronica"
|
32
44
|
end
|
33
|
-
|
34
|
-
end
|
35
|
-
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'facturacr/document'
|
2
|
+
|
3
|
+
module FE
|
4
|
+
|
5
|
+
class PurchaseInvoice < Document
|
6
|
+
DOCUMENT_TYPE = "08"
|
7
|
+
|
8
|
+
validates :receiver, presence: true, if: -> { version.eql?("4.3") }
|
9
|
+
|
10
|
+
def initialize(args={})
|
11
|
+
@version = args[:version]
|
12
|
+
@economic_activity = args[:economic_activity]
|
13
|
+
@date = args[:date]
|
14
|
+
@issuer = args[:issuer]
|
15
|
+
@receiver = args[:receiver]
|
16
|
+
@items = args[:items]
|
17
|
+
@number = args[:number]
|
18
|
+
@condition = args[:condition]
|
19
|
+
@payment_type = args[:payment_type] || ["01"]
|
20
|
+
@document_type = DOCUMENT_TYPE
|
21
|
+
@credit_term = args[:credit_term]
|
22
|
+
@summary = args[:summary]
|
23
|
+
@security_code = args[:security_code]
|
24
|
+
@document_situation = args[:document_situation]
|
25
|
+
@other_charges = args[:other_charges]
|
26
|
+
@namespaces = {
|
27
|
+
"xmlns:xsi"=>"http://www.w3.org/2001/XMLSchema-instance",
|
28
|
+
"xmlns:xsd"=>"http://www.w3.org/2001/XMLSchema",
|
29
|
+
"xmlns"=>"https://cdn.comprobanteselectronicos.go.cr/xml-schemas/v4.3/facturaElectronicaCompra"#,
|
30
|
+
}
|
31
|
+
@others = args[:others] || []
|
32
|
+
end
|
33
|
+
|
34
|
+
def document_tag
|
35
|
+
"FacturaElectronicaCompra"
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
@@ -3,27 +3,58 @@ require 'active_model'
|
|
3
3
|
module FE
|
4
4
|
class ReceptionMessage
|
5
5
|
include ActiveModel::Validations
|
6
|
-
|
6
|
+
|
7
7
|
MESSAGE_TYPES = {
|
8
8
|
"1" => "Aceptado",
|
9
9
|
"2" => "Aceptacion Parcial",
|
10
10
|
"3" => "Rechazado"
|
11
11
|
}.freeze
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
|
13
|
+
TAX_CONDITION={
|
14
|
+
"01" => "Genera crédito IVA",
|
15
|
+
"02" => "Genera Crédito parcial del IVA",
|
16
|
+
"03" => "Bienes de Capital",
|
17
|
+
"04" => "Gasto corriente",
|
18
|
+
"05" => "Proporcionalidad"
|
19
|
+
}.freeze
|
20
|
+
|
21
|
+
NAMESPACES = {
|
22
|
+
"4.2" => {
|
23
|
+
"xmlns:xsi"=>"http://www.w3.org/2001/XMLSchema-instance",
|
24
|
+
"xmlns:xsd"=>"http://www.w3.org/2001/XMLSchema",
|
25
|
+
"xmlns"=>"https://tribunet.hacienda.go.cr/docs/esquemas/2017/v4.2/mensajeReceptor"
|
26
|
+
},
|
27
|
+
"4.3" => {
|
28
|
+
"xmlns:xsi"=>"http://www.w3.org/2001/XMLSchema-instance",
|
29
|
+
"xmlns:xsd"=>"http://www.w3.org/2001/XMLSchema",
|
30
|
+
"xmlns"=>"https://cdn.comprobanteselectronicos.go.cr/xml-schemas/v4.3/mensajeReceptor"
|
31
|
+
}
|
32
|
+
}
|
33
|
+
attr_writer :headquarters, :terminal, :key
|
34
|
+
attr_accessor :key, :date, :issuer_id_number, :receiver_id_number, :message, :details, :economic_activity,
|
35
|
+
:tax_condition,:creditable_tax, :applicable_expense,:tax, :total, :number, :receiver_id_type, :security_code,
|
36
|
+
:document_situation, :issuer_id_type, :original_version, :version
|
37
|
+
|
38
|
+
validates :version, presence: true
|
39
|
+
validates :original_version, presence: true, if: -> { version_43? }
|
15
40
|
validates :date, presence: true
|
16
41
|
validates :issuer_id_number, presence: true, length: {is: 12}
|
17
42
|
validates :receiver_id_number, presence: true, length: {is: 12}
|
18
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") && !tax_condition.blank?}
|
19
45
|
validates :tax, numericality: true, if: -> { tax.present? }
|
20
46
|
validates :total, presence: true, numericality: true
|
21
47
|
validates :number, presence: true
|
22
48
|
validates :security_code, presence: true, length: {is: 8}
|
23
49
|
validates :issuer_id_type, presence: true
|
24
50
|
validates :receiver_id_type, presence: true
|
25
|
-
|
51
|
+
validates :economic_activity, presence: true, if: ->{ version_43? && tax_condition != "05"}
|
52
|
+
validates :creditable_tax, presence: true, if: -> { version_43? && tax_condition != "05" && (creditable_tax.present? && tax != creditable_tax) }
|
53
|
+
validates :applicable_expense, presence: true, if: -> { version_43? && tax_condition != "05" && (applicable_expense.present? && tax != applicable_expense) }
|
54
|
+
|
26
55
|
def initialize(args = {})
|
56
|
+
@version = args[:version]
|
57
|
+
@original_version = args[:original_version]
|
27
58
|
@key = args[:key]
|
28
59
|
@date = args[:date]
|
29
60
|
@issuer_id_type = args[:issuer_id_type]
|
@@ -37,22 +68,22 @@ module FE
|
|
37
68
|
@number = args[:number].to_i
|
38
69
|
@security_code = args[:security_code]
|
39
70
|
@document_situation = args[:document_situation]
|
40
|
-
@namespaces =
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
71
|
+
@namespaces = NAMESPACES[@version]
|
72
|
+
@tax_condition = args[:tax_condition]
|
73
|
+
@economic_activity = args[:economic_activity]
|
74
|
+
@creditable_tax = args[:creditable_tax]
|
75
|
+
@applicable_expense = args[:applicable_expense]
|
45
76
|
end
|
46
|
-
|
47
|
-
|
77
|
+
|
78
|
+
|
48
79
|
def headquarters
|
49
80
|
@headquarters ||= "001"
|
50
81
|
end
|
51
|
-
|
82
|
+
|
52
83
|
def terminal
|
53
84
|
@terminal ||= "00001"
|
54
|
-
end
|
55
|
-
|
85
|
+
end
|
86
|
+
|
56
87
|
def sequence
|
57
88
|
if @message.eql?("1")
|
58
89
|
@document_type = "05"
|
@@ -64,13 +95,20 @@ module FE
|
|
64
95
|
cons = ("%010d" % @number)
|
65
96
|
"#{headquarters}#{terminal}#{@document_type}#{cons}"
|
66
97
|
end
|
67
|
-
|
68
|
-
|
69
|
-
|
98
|
+
|
99
|
+
def version_42?
|
100
|
+
@version.eql?("4.2")
|
101
|
+
end
|
102
|
+
|
103
|
+
def version_43?
|
104
|
+
@version.eql?("4.3")
|
105
|
+
end
|
106
|
+
|
107
|
+
|
70
108
|
def build_xml
|
71
|
-
raise "Documento inválido
|
109
|
+
raise FE::Error.new "Documento inválido #{errors.messages}", class: self.class, messages: errors.messages unless valid?
|
72
110
|
builder = Nokogiri::XML::Builder.new
|
73
|
-
|
111
|
+
|
74
112
|
builder.MensajeReceptor(@namespaces) do |xml|
|
75
113
|
xml.Clave @key
|
76
114
|
xml.NumeroCedulaEmisor @issuer_id_number
|
@@ -78,20 +116,27 @@ module FE
|
|
78
116
|
xml.Mensaje @message
|
79
117
|
xml.DetalleMensaje @details if @details
|
80
118
|
xml.MontoTotalImpuesto @tax.to_f if @tax
|
119
|
+
if version_43? && @original_version.eql?("4.3")
|
120
|
+
xml.CodigoActividad @economic_activity if @economic_activity.present?
|
121
|
+
xml.CondicionImpuesto @tax_condition if @tax_condition.present?
|
122
|
+
xml.MontoImpuestoAcreditar @creditable_tax.to_f if @creditable_tax.present?
|
123
|
+
xml.MontoTotalDeGastoAplicable @applicable_expense.to_f if @applicable_expense.present?
|
124
|
+
end
|
125
|
+
|
81
126
|
xml.TotalFactura @total
|
82
127
|
xml.NumeroCedulaReceptor @receiver_id_number
|
83
128
|
xml.NumeroConsecutivoReceptor sequence
|
84
129
|
end
|
85
|
-
|
130
|
+
|
86
131
|
builder
|
87
132
|
end
|
88
|
-
|
133
|
+
|
89
134
|
def generate
|
90
135
|
build_xml.to_xml(:save_with => Nokogiri::XML::Node::SaveOptions::AS_XML)
|
91
136
|
end
|
92
|
-
|
137
|
+
|
93
138
|
def api_payload
|
94
|
-
|
139
|
+
|
95
140
|
payload = {}
|
96
141
|
payload[:clave] = @key
|
97
142
|
payload[:fecha] = @date.xmlschema
|
@@ -106,7 +151,7 @@ module FE
|
|
106
151
|
payload[:consecutivoReceptor] = sequence
|
107
152
|
payload
|
108
153
|
end
|
109
|
-
|
154
|
+
|
110
155
|
def infer_id_type(id_number)
|
111
156
|
if id_number.to_i.to_s.size == 9
|
112
157
|
"01"
|
@@ -116,7 +161,7 @@ module FE
|
|
116
161
|
"03"
|
117
162
|
end
|
118
163
|
end
|
119
|
-
|
164
|
+
|
120
165
|
end
|
121
|
-
|
122
|
-
end
|
166
|
+
|
167
|
+
end
|
@@ -3,19 +3,19 @@ require 'base64'
|
|
3
3
|
module FE
|
4
4
|
class SignedDocument
|
5
5
|
attr_accessor :document, :base64, :payload
|
6
|
-
|
6
|
+
|
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.
|
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)
|
14
|
-
|
14
|
+
|
15
15
|
@document = document
|
16
16
|
@base64 = Base64.encode64(xml_provider.contents).gsub("\n","");
|
17
17
|
@payload = document.api_payload
|
18
18
|
@payload[:comprobanteXml] = @base64
|
19
19
|
end
|
20
20
|
end
|
21
|
-
end
|
21
|
+
end
|