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.
@@ -0,0 +1,9 @@
1
+ module FE
2
+ class Element
3
+ attr_accessor :version, :document
4
+
5
+ def build_xml(node, document)
6
+ raise "must be implemented in sublcasses"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ module FE
2
+ class Error < StandardError
3
+ attr_reader :messages, :klass
4
+
5
+ def initialize(msg, args = {})
6
+ @klass = args[:class]
7
+ @messages = args[:messages]
8
+ super(msg)
9
+ end
10
+ end
11
+ 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
@@ -1,35 +1,47 @@
1
1
  require 'facturacr/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 = "01"
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
- @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
- }
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
+ 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
+
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
12
37
 
13
- attr_accessor :key, :date, :issuer_id_number, :receiver_id_number, :message, :details, :tax, :total, :number, :receiver_id_type, :security_code, :document_situation, :issuer_id_type
14
-
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")}
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
- "xmlns:xsi"=>"http://www.w3.org/2001/XMLSchema-instance",
42
- "xmlns:xsd"=>"http://www.w3.org/2001/XMLSchema",
43
- "xmlns"=>"https://tribunet.hacienda.go.cr/docs/esquemas/2017/v4.2/mensajeReceptor"
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
98
+
99
+ def version_42?
100
+ @version.eql?("4.2")
101
+ end
67
102
 
103
+ def version_43?
104
+ @version.eql?("4.3")
105
+ end
68
106
 
69
-
107
+
70
108
  def build_xml
71
- raise "Documento inválido: #{errors.messages}" unless valid?
109
+ raise FE::Error.new "Documento inválido", 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
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,7 +3,7 @@ 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)
@@ -11,11 +11,11 @@ module FE
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
@@ -22,14 +22,24 @@ module FE
22
22
 
23
23
  XADES = "http://uri.etsi.org/01903/v1.3.2#"
24
24
  XADES141 = "http://uri.etsi.org/01903/v1.4.1#"
25
- SIGNATURE_POLICY = "https://tribunet.hacienda.go.cr/docs/esquemas/2016/v4/Resolucion%20Comprobantes%20Electronicos%20%20DGT-R-48-2016.pdf"
25
+ SIGNATURE_POLICY_42 = "https://tribunet.hacienda.go.cr/docs/esquemas/2016/v4/Resolucion%20Comprobantes%20Electronicos%20%20DGT-R-48-2016.pdf"
26
26
 
27
- XMLNS_MAP = {
27
+ XMLNS_MAP_42 = {
28
28
  "FacturaElectronica" => "https://tribunet.hacienda.go.cr/docs/esquemas/2017/v4.2/facturaElectronica",
29
29
  "NotaCreditoElectronica" => "https://tribunet.hacienda.go.cr/docs/esquemas/2017/v4.2/notaCreditoElectronica",
30
30
  "TiqueteElectronico" => "https://tribunet.hacienda.go.cr/docs/esquemas/2017/v4.2/tiqueteElectronico",
31
31
  "NotaDebitoElectronica" => "https://tribunet.hacienda.go.cr/docs/esquemas/2017/v4.2/notaDebitoElectronica",
32
32
  "MensajeReceptor" => "https://tribunet.hacienda.go.cr/docs/esquemas/2017/v4.2/mensajeReceptor"
33
+ }
34
+
35
+ XMLNS_MAP_43 = {
36
+ "FacturaElectronica" => "https://cdn.comprobanteselectronicos.go.cr/xml-schemas/v4.3/facturaElectronica",
37
+ "NotaCreditoElectronica" => "https://cdn.comprobanteselectronicos.go.cr/xml-schemas/v4.3/notaCreditoElectronica",
38
+ "TiqueteElectronico" => "https://cdn.comprobanteselectronicos.go.cr/xml-schemas/v4.3/tiqueteElectronico",
39
+ "NotaDebitoElectronica" => "https://cdn.comprobanteselectronicos.go.cr/xml-schemas/v4.3/notaDebitoElectronica",
40
+ "MensajeReceptor" => "https://cdn.comprobanteselectronicos.go.cr/xml-schemas/v4.3/mensajeReceptor",
41
+ "FacturaElectronicaCompra" => "https://cdn.comprobanteselectronicos.go.cr/xml-schemas/v4.3/facturaElectronicaCompra",
42
+ "FacturaElectronicaExportacion" => "https://cdn.comprobanteselectronicos.go.cr/xml-schemas/v4.3/facturaElectronicaExportacion"
33
43
  }
34
44
 
35
45
  def initialize(args = {})
@@ -44,6 +54,9 @@ module FE
44
54
  @x509 = @p12.certificate
45
55
  @output_path = args[:output_path]
46
56
  @document_tag = @doc.elements.first.name
57
+ @version = @doc.elements.first.namespace.href.scan(/v4\..{1}/).first[1..-1]
58
+ @xmlns_map = XMLNS_MAP_42 if @version.eql?("4.2")
59
+ @xmlns_map = XMLNS_MAP_43 if @version.eql?("4.3")
47
60
  end
48
61
 
49
62
  def sign
@@ -96,7 +109,7 @@ module FE
96
109
  def build_key_info_element
97
110
  builder = Nokogiri::XML::Builder.new
98
111
  attributes = {
99
- "xmlns" => XMLNS_MAP[@document_tag],
112
+ "xmlns" => @xmlns_map[@document_tag],
100
113
  "xmlns:ds" => "http://www.w3.org/2000/09/xmldsig#",
101
114
  "xmlns:xsd" => "http://www.w3.org/2001/XMLSchema",
102
115
  "xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance",
@@ -123,7 +136,7 @@ module FE
123
136
  signing_time = DateTime.now.rfc3339
124
137
  builder = Nokogiri::XML::Builder.new
125
138
  attributes = {
126
- "xmlns"=>XMLNS_MAP[@document_tag],
139
+ "xmlns"=>@xmlns_map[@document_tag],
127
140
  "xmlns:ds" => "http://www.w3.org/2000/09/xmldsig#",
128
141
  "xmlns:xades" => "http://uri.etsi.org/01903/v1.3.2#",
129
142
  "xmlns:xsd" => "http://www.w3.org/2001/XMLSchema",
@@ -149,7 +162,7 @@ module FE
149
162
  ssp.send("xades:SignaturePolicyIdentifier") do |spi|
150
163
  spi.send("xades:SignaturePolicyId") do |spi2|
151
164
  spi2.send("xades:SigPolicyId") do |spi3|
152
- spi3.send("xades:Identifier", SIGNATURE_POLICY)
165
+ spi3.send("xades:Identifier", SIGNATURE_POLICY_42)
153
166
  spi3.send("xades:Description")
154
167
  end
155
168
 
@@ -176,7 +189,7 @@ module FE
176
189
 
177
190
  builder = builder = Nokogiri::XML::Builder.new
178
191
  attributes = {
179
- "xmlns"=>XMLNS_MAP[@document_tag],
192
+ "xmlns"=>@xmlns_map[@document_tag],
180
193
  "xmlns:ds" => "http://www.w3.org/2000/09/xmldsig#",
181
194
  "xmlns:xsd" => "http://www.w3.org/2001/XMLSchema",
182
195
  "xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance"
@@ -1,34 +1,47 @@
1
- require 'facturacr/document'
1
+ require 'facturacr/document'
2
2
 
3
3
  module FE
4
-
4
+
5
5
  class Ticket < 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/tiqueteElectronico"
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/tiqueteElectronico"#,
16
+ }
17
+ }
18
+ DOCUMENT_TYPE = "04"
19
+
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 = "04"
29
+ @payment_type = args[:payment_type] || ["01"]
30
+ @document_type = DOCUMENT_TYPE
16
31
  @credit_term = args[:credit_term]
17
32
  @summary = args[:summary]
33
+ @other_charges = args[:other_charges]
18
34
  @regulation = args[:regulation] ||= FE::Document::Regulation.new
19
35
  @security_code = args[:security_code]
20
36
  @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/tiqueteElectronico"
25
- }
37
+ @namespaces = NAMESPACES[@version]
26
38
  @others = args[:others] || []
39
+ @references = args[:references] || []
27
40
  end
28
-
41
+
29
42
  def document_tag
30
43
  "TiqueteElectronico"
31
44
  end
32
-
33
- end
34
- end
45
+
46
+ end
47
+ end