cfdi40 0.0.6 → 0.0.8

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 55ca7f3a64dc87b4b19cbb5d389f71ccd624b70e7c5af601d97dd4dd6e1599db
4
- data.tar.gz: 9cab2e03e554d96d47ff328c6c520782036ee9b561f11586c42bd353a3112f1d
3
+ metadata.gz: ce28369c399d94875c10fa980ead1eb1cd75e0300035ffeb61c7bc310d87b327
4
+ data.tar.gz: 8d03b666fb2fac0b592032891fadfec3643f64938eb46b2e301bf4a98e68014a
5
5
  SHA512:
6
- metadata.gz: '0310189ea57acabe4fffc77ed24d1b02aab3d49e0afafd47d1abf7df10856d19a5a00b89d302138c6b43b036e8af5d52671d922b274e10613f1d57d76b732e1e'
7
- data.tar.gz: 719ed8d0e569dd621edf7ee702271b7c468c2e6b4606297f650cac39b6cb199d06d881f07b15e56599d214da3481651987348cfc24997e6a1843939ea933c711
6
+ metadata.gz: 1246bce889af62b67256d2c1278201bfaee2c6ae5693048b7a00b8ec424830192610a8d98df294f5227b47ed3f19a0556f1f07146b057a0b8f65e3c95a8874f0
7
+ data.tar.gz: d3142214a86217a324b8539bc3455695a257505265e76dc746fa0999d2a2a1ed3486abd8a0e58ef31f4fdd46d828ae9529adcf7d271ce77499657124c855c395
data/.rubocop.yml CHANGED
@@ -16,3 +16,6 @@ Style/StringLiteralsInInterpolation:
16
16
 
17
17
  Layout/LineLength:
18
18
  Max: 120
19
+
20
+ Metrics/MethodLength:
21
+ Max: 20
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cfdi40 (0.0.6)
4
+ cfdi40 (0.0.8)
5
5
  nokogiri (>= 1.10.10)
6
6
 
7
7
  GEM
@@ -10,12 +10,12 @@ GEM
10
10
  ast (2.4.2)
11
11
  json (2.3.1)
12
12
  minitest (5.15.0)
13
- nokogiri (1.14.2-x86_64-linux)
13
+ nokogiri (1.15.7-x86_64-linux)
14
14
  racc (~> 1.4)
15
15
  parallel (1.20.1)
16
16
  parser (3.1.2.1)
17
17
  ast (~> 2.4.1)
18
- racc (1.6.2)
18
+ racc (1.8.1)
19
19
  rainbow (3.0.0)
20
20
  rake (13.0.6)
21
21
  regexp_parser (1.8.2)
data/README.md CHANGED
@@ -49,6 +49,16 @@ install`. To release a new version, update the version number in
49
49
  a git tag for the version, push git commits and the created tag, and
50
50
  push the `.gem` file to [rubygems.org](https://rubygems.org).
51
51
 
52
+ ## Testing
53
+
54
+ Run all test
55
+
56
+ bundle exec rake test
57
+
58
+ Run all test in a file
59
+
60
+ bundle exec ruby -Ilib:test test/test_cfdi40.rb
61
+
52
62
  ## Contributing
53
63
 
54
64
  Bug reports and pull requests are welcome on GitHub at
data/README_es-MX.md CHANGED
@@ -17,6 +17,12 @@ que esta herramienta:
17
17
  * Valide el CFDi contra los XSD
18
18
  * Selle el CFDi.
19
19
 
20
+ Hasta ahora:
21
+
22
+ * Genera CFDIs de ingreso básicos con IVA
23
+ * Genera CFDIs con complemento de pago para colegiaturas
24
+ * Genera CFDIs complementos de pago básicos.
25
+
20
26
  # Uso
21
27
 
22
28
  ## Ejemplo básico
@@ -93,6 +99,14 @@ que esta herramienta:
93
99
  # Genera CFDI firmado
94
100
  xml_string = cfdi.to_xml
95
101
 
102
+ # Cargar un CFDI a partir de un XML:
103
+
104
+ # cfdi.xml es un archivo con un CFDi versión 4.0
105
+ xml_string = File.read('cfdi.xml')
106
+ cfdi = Cfdi40.open(xml_string)
107
+
108
+ Una vez cargado el xml se pueden leer los atributos
109
+ y/o hacer modificaciones
96
110
 
97
111
  # Lo que sigue
98
112
 
@@ -102,6 +116,12 @@ que esta herramienta:
102
116
 
103
117
  # Cambios
104
118
 
119
+ # 0.0.8
120
+ * Carga básica de un CFDI desde XML.
121
+
122
+ # 0.0.7
123
+ * Ajustes al CFDi con complemento de pagos por validaciones del PAC.
124
+
105
125
  # 0.0.6
106
126
  * CFDi con complemento de pagos
107
127
 
@@ -145,7 +165,5 @@ que esta herramienta:
145
165
 
146
166
  # ¿Que sigue?
147
167
 
148
- * Complemento de pagos
149
168
  * Retenciones
150
- * IEPS
151
- * Complemento para colegiaturas
169
+ * Complemento de pagos con retenciones
@@ -1,14 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Cfdi40
2
4
  class Complemento < Node
3
5
  # See Comprobante#add_pago
4
- def add_pago(attributes={})
6
+ def add_pago(attributes = {})
5
7
  pagos.totales_node
6
8
  pagos.add_pago(attributes)
7
9
  end
8
10
 
9
11
  def pagos
10
12
  return @pagos if defined?(@pagos)
11
-
13
+
12
14
  @pagos = Pagos.new
13
15
  @pagos.parent_node = self
14
16
  @children_nodes << @pagos
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Cfdi40
2
4
  class ComplementoConcepto < Node
3
5
  def inst_educativas_node
@@ -5,7 +7,7 @@ module Cfdi40
5
7
 
6
8
  @inst_educativas_node = InstEducativas.new
7
9
  @inst_educativas_node.parent_node = self
8
- self.children_nodes << @inst_educativas_node
10
+ children_nodes << @inst_educativas_node
9
11
  @inst_educativas_node
10
12
  end
11
13
  end
@@ -6,32 +6,31 @@ module Cfdi40
6
6
  define_namespace "xsi", "http://www.w3.org/2001/XMLSchema-instance"
7
7
  define_namespace "cfdi", "http://www.sat.gob.mx/cfd/4"
8
8
  define_attribute :schema_location,
9
- xml_attribute: 'xsi:schemaLocation',
9
+ xml_attribute: "xsi:schemaLocation",
10
10
  readonly: true,
11
11
  default: "http://www.sat.gob.mx/cfd/4 " \
12
12
  "http://www.sat.gob.mx/sitio_internet/cfd/4/cfdv40.xsd"
13
- define_attribute :version, xml_attribute: 'Version', readonly: true, default: '4.0'
14
- define_attribute :serie, xml_attribute: 'Serie'
15
- define_attribute :folio, xml_attribute: 'Folio'
16
- define_attribute :fecha, xml_attribute: 'Fecha'
17
- define_attribute :sello, xml_attribute: 'Sello', readonly: true
18
- define_attribute :forma_pago, xml_attribute: 'FormaPago'
19
- define_attribute :no_certificado, xml_attribute: 'NoCertificado'
20
- define_attribute :certificado, xml_attribute: 'Certificado'
21
- define_attribute :condiciones_de_pago, xml_attribute: 'CondicionesDePago'
22
- define_attribute :subtotal, xml_attribute: 'SubTotal', format: :t_ImporteMXN
23
- define_attribute :descuento, xml_attribute: 'Descuento', format: :t_ImporteMXN
24
- define_attribute :moneda, xml_attribute: 'Moneda', default: 'MXN'
25
- define_attribute :tipo_cambio, xml_attribute: 'TipoCambio'
26
- define_attribute :total, xml_attribute: 'Total', format: :t_ImporteMXN
27
- define_attribute :tipo_de_comprobante, xml_attribute: 'TipoDeComprobante', default: 'I'
28
- define_attribute :exportacion, xml_attribute: 'Exportacion', default: '01'
29
- define_attribute :metodo_pago, xml_attribute: 'MetodoPago'
30
- define_attribute :lugar_expedicion, xml_attribute: 'LugarExpedicion'
31
- define_attribute :confirmacion, xml_attribute: 'Confirmacion'
32
-
33
- attr_reader :emisor, :receptor, :x509_cert, :conceptos, :private_key
34
- attr_reader :errors
13
+ define_attribute :version, xml_attribute: "Version", readonly: true, default: "4.0"
14
+ define_attribute :serie, xml_attribute: "Serie"
15
+ define_attribute :folio, xml_attribute: "Folio"
16
+ define_attribute :fecha, xml_attribute: "Fecha"
17
+ define_attribute :sello, xml_attribute: "Sello", readonly: true
18
+ define_attribute :forma_pago, xml_attribute: "FormaPago"
19
+ define_attribute :no_certificado, xml_attribute: "NoCertificado"
20
+ define_attribute :certificado, xml_attribute: "Certificado"
21
+ define_attribute :condiciones_de_pago, xml_attribute: "CondicionesDePago"
22
+ define_attribute :subtotal, xml_attribute: "SubTotal", format: :t_ImporteMXN
23
+ define_attribute :descuento, xml_attribute: "Descuento", format: :t_ImporteMXN
24
+ define_attribute :moneda, xml_attribute: "Moneda", default: "MXN"
25
+ define_attribute :tipo_cambio, xml_attribute: "TipoCambio"
26
+ define_attribute :total, xml_attribute: "Total", format: :t_ImporteMXN
27
+ define_attribute :tipo_de_comprobante, xml_attribute: "TipoDeComprobante", default: "I"
28
+ define_attribute :exportacion, xml_attribute: "Exportacion", default: "01"
29
+ define_attribute :metodo_pago, xml_attribute: "MetodoPago"
30
+ define_attribute :lugar_expedicion, xml_attribute: "LugarExpedicion"
31
+ define_attribute :confirmacion, xml_attribute: "Confirmacion"
32
+
33
+ attr_reader :emisor, :receptor, :conceptos, :private_key, :sat_csd, :errors
35
34
  attr_writer :key_data, :key_pass
36
35
 
37
36
  def initialize
@@ -70,14 +69,27 @@ module Cfdi40
70
69
  @key_data = File.read(path)
71
70
  end
72
71
 
72
+ # Load from attribute 'Certificado' when the CFDi is
73
+ # loaded from a string
74
+ def load_cert
75
+ return if @sat_csd&.cert64
76
+
77
+ @sat_csd ||= SatCsd.new
78
+ @sat_csd.cert_der = OpenSSL::X509::Certificate.new(Base64.decode64(certificado))
79
+ true
80
+ rescue StandardError
81
+ # puts "Waring; Unable to load certificate from XML string"
82
+ false
83
+ end
84
+
73
85
  def sign
74
86
  @sat_csd ||= SatCsd.new
75
87
  load_private_key if @sat_csd.private_key.nil?
76
88
  return unless @sat_csd.private_key
77
89
 
78
- raise Error, 'Key and certificate not match' unless @sat_csd.valid_pair?
90
+ raise Error, "Key and certificate not match" unless @sat_csd.valid_pair?
79
91
 
80
- digest = @sat_csd.private_key.sign(OpenSSL::Digest.new('SHA256'), original_content)
92
+ digest = @sat_csd.private_key.sign(OpenSSL::Digest.new("SHA256"), original_content)
81
93
  @sello = Base64.strict_encode64 digest
82
94
  @docxml = nil
83
95
  end
@@ -90,7 +102,7 @@ module Cfdi40
90
102
  # +descripcion+:: Product or service description
91
103
  #
92
104
  # ### Price and Taxes attributes
93
- #
105
+ #
94
106
  # +tasa_iva+:: Decimal between 0 and 1. Nil means exempt. Default value is 0.16
95
107
  # +tasa_ieps+:: Decimal between 0 and 1. Nil means exempt. Default value is null
96
108
  # +precio_bruto+:: Price before apply taxes or gross price.
@@ -106,7 +118,7 @@ module Cfdi40
106
118
  # +descuento+:: PENDING
107
119
  #
108
120
  # ## Special attributes
109
- #
121
+ #
110
122
  # ### IEDU attributes
111
123
  #
112
124
  # IEDU node (path: cfdi:Comprobante/cfdi:Conceptos/cfdi:Concepto/cfdi:ComplementoConcepto/iedu:instEducativas) is
@@ -119,17 +131,15 @@ module Cfdi40
119
131
  # +iedu_rfc_pago+::
120
132
  #
121
133
  def add_concepto(attributes = {})
122
- raise Error, 'CFDi tipo pago no acepta conceptos' if tipo_de_comprobante == 'P'
134
+ raise Error, "CFDi tipo pago no acepta conceptos" if tipo_de_comprobante == "P"
123
135
 
124
136
  concepto = Concepto.new
125
137
  concepto.parent_node = @conceptos
126
138
  attributes.each do |key, value|
127
139
  method_name = "#{key}=".to_sym
128
- if concepto.respond_to?(method_name)
129
- concepto.public_send(method_name, value)
130
- else
131
- raise Error, ":#{key} no se puede asignar al concepto"
132
- end
140
+ raise Error, ":#{key} no se puede asignar al concepto" unless concepto.respond_to?(method_name)
141
+
142
+ concepto.public_send(method_name, value)
133
143
  end
134
144
  concepto.calculate!
135
145
  @conceptos.children_nodes << concepto
@@ -137,6 +147,28 @@ module Cfdi40
137
147
  concepto
138
148
  end
139
149
 
150
+ # Load node 'Concepto' from a Nokogiri::XML::Element
151
+ def load_concepto(ng_node)
152
+ concepto = Concepto.new
153
+ concepto.parent_node = @conceptos
154
+ concepto.load_from_ng_node(ng_node)
155
+ @conceptos.children_nodes << concepto
156
+ concepto
157
+ end
158
+
159
+ # Load node cfdi:Comprobante/cfdi:Impuestos
160
+ #
161
+ # Normally this node is calculated but must be read from the
162
+ # XML when a CFDi is loaded
163
+ def load_impuestos(ng_node)
164
+ impuestos.load_from_ng_node(ng_node)
165
+ ng_iva_node = ng_node.xpath("cfdi:Traslados/cfdi:Traslado[@Impuesto='002']").first
166
+ return true if ng_iva_node.nil?
167
+
168
+ impuestos.traslado_iva.load_from_ng_node(ng_iva_node)
169
+ true
170
+ end
171
+
140
172
  # TODO: Doc params add_pago
141
173
  # monto
142
174
  # uuid
@@ -148,7 +180,7 @@ module Cfdi40
148
180
  # importe_saldo_anterior
149
181
  # objeto_impuestos
150
182
  def add_pago(attributes = {})
151
- raise Error, "CFDi debe ser tipo 'P'" unless tipo_de_comprobante == 'P'
183
+ raise Error, "CFDi debe ser tipo 'P'" unless tipo_de_comprobante == "P"
152
184
 
153
185
  add_node_concepto_actividad_pago
154
186
  complemento.add_pago(attributes)
@@ -176,28 +208,41 @@ module Cfdi40
176
208
  end
177
209
 
178
210
  def original_content
179
- xslt_path = File.join(File.dirname(__FILE__), '..', '..', 'lib/xslt/cadenaoriginal_local.xslt')
211
+ xslt_path = File.join(File.dirname(__FILE__), "..", "..", "lib/xslt/cadenaoriginal_local.xslt")
180
212
  xslt = Nokogiri::XSLT(File.open(xslt_path))
181
213
  transformed = xslt.transform(docxml)
182
214
  # The ampersand (&) char must be used in original content
183
215
  # even though the documentation indicates otherwise
184
- transformed.children.to_s.gsub('&amp;', '&').strip
216
+ transformed.children.to_s.gsub("&amp;", "&").strip
217
+ end
218
+
219
+ # Shortcut to attribute TotalImpuestosTrasladados of impuestos node
220
+ def total_impuestos_trasladados
221
+ return nil unless impuestos_node
222
+
223
+ impuestos_node.total_impuestos_trasladados
224
+ end
225
+
226
+ def total_iva_node
227
+ return nil unless impuestos_node
228
+
229
+ impuestos_node.traslado_iva
185
230
  end
186
231
 
187
232
  private
188
233
 
189
234
  def add_node_concepto_actividad_pago
190
235
  return if defined?(@concepto_actividad)
191
-
192
- @receptor.uso_cfdi = 'CP01'
236
+
237
+ @receptor.uso_cfdi = "CP01"
193
238
  @concepto_actividad = Concepto.new
194
- @concepto_actividad.clave_prod_serv = '84111506'
239
+ @concepto_actividad.clave_prod_serv = "84111506"
195
240
  @concepto_actividad.cantidad = 1
196
- @concepto_actividad.clave_unidad = 'ACT'
197
- @concepto_actividad.descripcion = 'Pago'
241
+ @concepto_actividad.clave_unidad = "ACT"
242
+ @concepto_actividad.descripcion = "Pago"
198
243
  @concepto_actividad.precio_bruto = 0
199
244
  @concepto_actividad.tasa_iva = nil
200
- @concepto_actividad.objeto_impuestos = '01'
245
+ @concepto_actividad.objeto_impuestos = "01"
201
246
  @concepto_actividad.calculate!
202
247
  @conceptos.add_child_node @concepto_actividad
203
248
  calculate!
@@ -230,7 +275,6 @@ module Cfdi40
230
275
  impuestos.total_impuestos_trasladados = 0
231
276
  traslados.children_nodes = []
232
277
  traslados_summary.each do |key, value|
233
- #TODO: Sumar los impuestos y agregarlos a los nodos globales de traslados
234
278
  traslado = Traslado.new
235
279
  traslado.parent_node = impuestos
236
280
  traslado.impuesto, traslado.tasa_o_cuota, traslado.tipo_factor = key
@@ -259,9 +303,7 @@ module Cfdi40
259
303
  summary
260
304
  end
261
305
 
262
- # TODO: Este método tiene que ser 'impuestos'
263
- # si nos atenemos a que los que acaban con _node buscan en los hijos
264
- # y los que no terminan con _node crean el nodo
306
+ # Returns a Cfdi40::Node for 'Impuestos'
265
307
  def impuestos
266
308
  return @impuestos if defined?(@impuestos)
267
309
 
@@ -272,7 +314,7 @@ module Cfdi40
272
314
  end
273
315
 
274
316
  def impuestos_node
275
- children_nodes.select { |n| n.is_a?(Impuestos)}.first
317
+ children_nodes.select { |n| n.is_a?(Impuestos) }.first
276
318
  end
277
319
 
278
320
  def traslados
@@ -288,7 +330,7 @@ module Cfdi40
288
330
 
289
331
  def load_private_key
290
332
  return unless defined?(@key_data)
291
-
333
+
292
334
  @sat_csd ||= SatCsd.new
293
335
  @sat_csd.set_private_key(@key_data, (defined?(@key_pass) ? @key_pass : nil))
294
336
  end
@@ -1,20 +1,22 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Represents node 'concepto'
2
4
  #
3
5
  # * Attribute +Importe+ represente gross amount. Gross amount is before taxes and the result of multiply
4
6
  # +ValorUnitario+ by +Cantidad+
5
- #
7
+ #
6
8
  module Cfdi40
7
9
  class Concepto < Node
8
- define_attribute :clave_prod_serv, xml_attribute: 'ClaveProdServ'
9
- define_attribute :no_identificacion,xml_attribute: 'NoIdentificacion'
10
- define_attribute :cantidad, xml_attribute: 'Cantidad', default: 1
11
- define_attribute :clave_unidad, xml_attribute: 'ClaveUnidad'
12
- define_attribute :unidad, xml_attribute: 'Unidad'
13
- define_attribute :descripcion, xml_attribute: 'Descripcion'
14
- define_attribute :valor_unitario, xml_attribute: 'ValorUnitario', format: :t_Importe
15
- define_attribute :importe, xml_attribute: 'Importe', format: :t_Importe
16
- define_attribute :descuento, xml_attribute: 'Descuento', format: :t_Importe
17
- define_attribute :objeto_impuestos, xml_attribute: 'ObjetoImp', default: '01'
10
+ define_attribute :clave_prod_serv, xml_attribute: "ClaveProdServ"
11
+ define_attribute :no_identificacion, xml_attribute: "NoIdentificacion"
12
+ define_attribute :cantidad, xml_attribute: "Cantidad", default: 1
13
+ define_attribute :clave_unidad, xml_attribute: "ClaveUnidad"
14
+ define_attribute :unidad, xml_attribute: "Unidad"
15
+ define_attribute :descripcion, xml_attribute: "Descripcion"
16
+ define_attribute :valor_unitario, xml_attribute: "ValorUnitario", format: :t_Importe
17
+ define_attribute :importe, xml_attribute: "Importe", format: :t_Importe
18
+ define_attribute :descuento, xml_attribute: "Descuento", format: :t_Importe
19
+ define_attribute :objeto_impuestos, xml_attribute: "ObjetoImp", default: "01"
18
20
 
19
21
  attr_accessor :tasa_iva, :tasa_ieps, :precio_neto, :precio_bruto
20
22
  attr_reader :iva, :ieps, :base_iva, :importe_neto, :importe_bruto
@@ -38,7 +40,7 @@ module Cfdi40
38
40
  calculate_from_net_price
39
41
  elsif defined?(@precio_bruto) && !@precio_bruto.nil?
40
42
  calculate_from_gross_price
41
- elsif !self.valor_unitario.nil?
43
+ elsif !valor_unitario.nil?
42
44
  @precio_bruto = valor_unitario
43
45
  calculate_from_gross_price
44
46
  end
@@ -49,7 +51,7 @@ module Cfdi40
49
51
  end
50
52
 
51
53
  def objeto_impuestos?
52
- objeto_impuestos == '02'
54
+ objeto_impuestos == "02"
53
55
  end
54
56
 
55
57
  def traslado_nodes
@@ -58,6 +60,16 @@ module Cfdi40
58
60
  impuestos_node.traslado_nodes
59
61
  end
60
62
 
63
+ def traslado_iva_node
64
+ return nil unless impuestos_node.is_a?(Impuestos)
65
+
66
+ impuestos_node.traslado_iva
67
+ end
68
+
69
+ def load_traslado_iva(ng_node)
70
+ traslado_iva_node.load_from_ng_node(ng_node)
71
+ end
72
+
61
73
  private
62
74
 
63
75
  def calculate_from_net_price
@@ -100,13 +112,13 @@ module Cfdi40
100
112
  end
101
113
 
102
114
  def assign_objeto_imp
103
- #01 No objeto de impuesto.
104
- #02 Sí objeto de impuesto.
105
- #03 Sí objeto del impuesto y no obligado al desglose.
115
+ # 01 No objeto de impuesto.
116
+ # 02 Sí objeto de impuesto.
117
+ # 03 Sí objeto del impuesto y no obligado al desglose.
106
118
 
107
- return if objeto_impuestos == '03'
119
+ return if objeto_impuestos == "03"
108
120
 
109
- self.objeto_impuestos = (!@tasa_iva.nil? || !@tasa_ieps.nil? ? '02' : '01')
121
+ self.objeto_impuestos = (!@tasa_iva.nil? || !@tasa_ieps.nil? ? "02" : "01")
110
122
  end
111
123
 
112
124
  def impuestos_node
@@ -118,16 +130,10 @@ module Cfdi40
118
130
 
119
131
  @impuestos_node = Impuestos.new
120
132
  @impuestos_node.parent_node = self
121
- self.children_nodes << @impuestos_node
133
+ children_nodes << @impuestos_node
122
134
  @impuestos_node
123
135
  end
124
136
 
125
- def traslado_iva_node
126
- return nil unless impuestos_node.is_a?(Impuestos)
127
-
128
- impuestos_node.traslado_iva
129
- end
130
-
131
137
  def add_inst_educativas
132
138
  return unless inst_educativas_present?
133
139
 
@@ -151,7 +157,7 @@ module Cfdi40
151
157
 
152
158
  @complemento_concepto_node = ComplementoConcepto.new
153
159
  @complemento_concepto_node.parent_node = self
154
- self.children_nodes << @complemento_concepto_node
160
+ children_nodes << @complemento_concepto_node
155
161
  @complemento_concepto_node
156
162
  end
157
163
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Cfdi40
2
4
  class Conceptos < Node
3
5
  end
@@ -1,30 +1,32 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Cfdi40
2
4
  class DoctoRelacionado < Node
3
- define_attribute :id_documento, xml_attribute: 'IdDocumento'
4
- define_attribute :serie, xml_attribute: 'Serie'
5
- define_attribute :folio, xml_attribute: 'Folio'
6
- define_attribute :moneda_dr, xml_attribute: 'MonedaDR', default: 'MXN'
7
- define_attribute :equivalencia_dr, xml_attribute: 'EquivalenciaDR'
8
- define_attribute :num_parcialidad, xml_attribute: 'NumParcialidad'
9
- define_attribute :imp_saldo_ant, xml_attribute: 'ImpSaldoAnt', format: :t_ImporteMXN
10
- define_attribute :imp_pagado, xml_attribute: 'ImpPagado', format: :t_ImporteMXN
11
- define_attribute :imp_saldo_insoluto, xml_attribute: 'ImpSaldoInsoluto', format: :t_ImporteMXN
12
- define_attribute :objeto_imp_dr, xml_attribute: 'ObjetoImpDR', default: '02'
5
+ define_attribute :id_documento, xml_attribute: "IdDocumento"
6
+ define_attribute :serie, xml_attribute: "Serie"
7
+ define_attribute :folio, xml_attribute: "Folio"
8
+ define_attribute :moneda_dr, xml_attribute: "MonedaDR", default: "MXN"
9
+ define_attribute :equivalencia_dr, xml_attribute: "EquivalenciaDR", default: "1"
10
+ define_attribute :num_parcialidad, xml_attribute: "NumParcialidad"
11
+ define_attribute :imp_saldo_ant, xml_attribute: "ImpSaldoAnt", format: :t_ImporteMXN
12
+ define_attribute :imp_pagado, xml_attribute: "ImpPagado", format: :t_ImporteMXN
13
+ define_attribute :imp_saldo_insoluto, xml_attribute: "ImpSaldoInsoluto", format: :t_ImporteMXN
14
+ define_attribute :objeto_imp_dr, xml_attribute: "ObjetoImpDR", default: "02"
13
15
 
14
16
  def calculate!
15
17
  self.imp_saldo_insoluto = (imp_saldo_ant - imp_pagado).round(2)
16
18
  add_impuestos
17
19
  end
18
20
 
19
- # Add nodes for 'traslado_dr' and/or 'retencion_dr' and intermetiate nodes
21
+ # Add nodes for 'traslado_dr' and/or 'retencion_dr' and intermediate nodes
20
22
  def add_impuestos
21
- add_traslado if objeto_imp_dr == '02'
23
+ add_traslado if objeto_imp_dr == "02"
22
24
  end
23
25
 
24
26
  def add_traslado
25
- return unless objeto_imp_dr == '02'
27
+ return unless objeto_imp_dr == "02"
26
28
 
27
- # Taxes values for IVA rate 0.16 assumming that all 'conceptos' in the realted document
29
+ # Taxes values for IVA rate 0.16 assumming that all 'conceptos' in the realted document
28
30
  # has the same tax rate. This could not be true but the is the most common case.
29
31
  traslado_dr = TrasladoDR.new
30
32
  traslado_dr.monto_pago = imp_pagado.round(2)
@@ -56,7 +58,7 @@ module Cfdi40
56
58
 
57
59
  def impuestos_dr
58
60
  return @impuestos_dr if defined?(@impuestos_dr)
59
-
61
+
60
62
  @impuestos_dr = ImpuestosDR.new
61
63
  @impuestos_dr.parent_node = self
62
64
  @children_nodes << @impuestos_dr
data/lib/cfdi40/emisor.rb CHANGED
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Cfdi40
2
4
  class Emisor < Node
3
- define_attribute :rfc, xml_attribute: 'Rfc'
4
- define_attribute :nombre, xml_attribute: 'Nombre'
5
- define_attribute :regimen_fiscal, xml_attribute: 'RegimenFiscal'
5
+ define_attribute :rfc, xml_attribute: "Rfc"
6
+ define_attribute :nombre, xml_attribute: "Nombre"
7
+ define_attribute :regimen_fiscal, xml_attribute: "RegimenFiscal"
6
8
  end
7
9
  end
@@ -1,19 +1,21 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Cfdi40
2
4
  class Impuestos < Node
3
- define_attribute :total_impuestos_retenidos, xml_attribute: 'TotalImpuestosRetenidos', format: :t_ImporteMXN
4
- define_attribute :total_impuestos_trasladados, xml_attribute: 'TotalImpuestosTrasladados', format: :t_ImporteMXN
5
+ define_attribute :total_impuestos_retenidos, xml_attribute: "TotalImpuestosRetenidos", format: :t_ImporteMXN
6
+ define_attribute :total_impuestos_trasladados, xml_attribute: "TotalImpuestosTrasladados", format: :t_ImporteMXN
5
7
 
6
8
  def traslados
7
9
  return @traslados if defined?(@traslados)
8
10
 
9
11
  @traslados = Traslados.new
10
12
  @traslados.parent_node = self
11
- self.children_nodes << @traslados
13
+ children_nodes << @traslados
12
14
  @traslados
13
15
  end
14
16
 
15
17
  def traslados_node
16
- children_nodes.select { |n| n.is_a?(Traslados)}.first
18
+ children_nodes.select { |n| n.is_a?(Traslados) }.first
17
19
  end
18
20
 
19
21
  def traslado_nodes
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Cfdi40
2
4
  class ImpuestosDR < Node
3
5
  def traslados_dr
@@ -1,6 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Cfdi40
2
4
  class ImpuestosP < Node
3
-
4
5
  def traslados_p
5
6
  return @traslados_p if defined?(@traslados_p)
6
7
 
@@ -1,17 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Cfdi40
2
4
  class InstEducativas < Node
3
- define_element_name 'instEducativas'
5
+ define_element_name "instEducativas"
4
6
  define_namespace "xsi", "http://www.w3.org/2001/XMLSchema-instance"
5
7
  define_namespace "iedu", "http://www.sat.gob.mx/iedu"
6
8
  define_attribute :schema_location,
7
- xml_attribute: 'xsi:schemaLocation',
9
+ xml_attribute: "xsi:schemaLocation",
8
10
  readonly: true,
9
11
  default: "http://www.sat.gob.mx/iedu http://www.sat.gob.mx/sitio_internet/cfd/iedu/iedu.xsd"
10
- define_attribute :version, xml_attribute: 'version', readonly: true, default: '1.0'
11
- define_attribute :nombre_alumno, xml_attribute: 'nombreAlumno'
12
- define_attribute :curp, xml_attribute: 'CURP'
13
- define_attribute :nivel_educativo, xml_attribute: 'nivelEducativo'
14
- define_attribute :aut_rvoe, xml_attribute: 'autRVOE'
15
- define_attribute :rfc_pago, xml_attribute: 'rfcPago'
12
+ define_attribute :version, xml_attribute: "version", readonly: true, default: "1.0"
13
+ define_attribute :nombre_alumno, xml_attribute: "nombreAlumno"
14
+ define_attribute :curp, xml_attribute: "CURP"
15
+ define_attribute :nivel_educativo, xml_attribute: "nivelEducativo"
16
+ define_attribute :aut_rvoe, xml_attribute: "autRVOE"
17
+ define_attribute :rfc_pago, xml_attribute: "rfcPago"
16
18
  end
17
19
  end
data/lib/cfdi40/node.rb CHANGED
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Cfdi40
2
4
  class Node
3
- # Nokigir XML Document for the xml_node
5
+ # Nokigiri XML Document for the xml_node
4
6
  attr_accessor :xml_document, :xml_parent, :children_nodes, :parent_node
5
7
  attr_writer :element_name
6
8
 
@@ -33,12 +35,10 @@ module Cfdi40
33
35
  attr_accessor accessor.to_sym
34
36
  end
35
37
  @@attributes[name][accessor.to_sym] = xml_attribute
36
- if default
37
- @@default_values[name][accessor.to_sym] = default
38
- end
39
- if format
40
- @@formats[name][accessor.to_sym] = format
41
- end
38
+ @@default_values[name][accessor.to_sym] = default if default
39
+ return unless format
40
+
41
+ @@formats[name][accessor.to_sym] = format
42
42
  end
43
43
 
44
44
  def self.define_namespace(namespace, value)
@@ -89,7 +89,7 @@ module Cfdi40
89
89
  end
90
90
 
91
91
  def add_child_node(child_node)
92
- raise Error, 'child_node must be a Node object' unless child_node.is_a?(Node)
92
+ raise Error, "child_node must be a Node object" unless child_node.is_a?(Node)
93
93
 
94
94
  child_node.parent_node = self
95
95
  @children_nodes << child_node
@@ -97,19 +97,28 @@ module Cfdi40
97
97
 
98
98
  def current_namespace
99
99
  return unless self.class.respond_to?(:namespaces)
100
- if self.class.namespaces.empty?
101
- return parent_node.current_namespace unless parent_node.nil?
102
- end
100
+
101
+ return parent_node.current_namespace if self.class.namespaces.empty? && !parent_node.nil?
103
102
 
104
103
  self.class.namespaces.keys.last
105
104
  end
106
105
 
106
+ # Load attributes from a Nokogiri::XML::Element.
107
+ # Attributes are loaded directly to the instance variable
108
+ def load_from_ng_node(ng_node)
109
+ # TODO: Se puede cargar el certificado
110
+ # x509_cert = OpenSSL::X509::Certificate.new(Base64.decode64(<valor del atributo>))
111
+ self.class.attributes.each do |variable_name, attr_name|
112
+ next if ng_node.attributes[attr_name].nil?
113
+
114
+ instance_variable_set("@#{variable_name}".to_sym, ng_node.attributes[attr_name].value)
115
+ end
116
+ end
117
+
107
118
  def create_xml_node
108
119
  # TODO: Quitar la siguiente linea (set_defaults) si funciona poniendo los defaults en initialize
109
120
  # set_defaults
110
- if self.respond_to?(:before_add, true)
111
- self.before_add
112
- end
121
+ before_add if respond_to?(:before_add, true)
113
122
  xml_node = xml_document.create_element(expanded_element_name)
114
123
  add_namespaces_to(xml_node)
115
124
  add_attributes_to(xml_node)
@@ -119,13 +128,14 @@ module Cfdi40
119
128
 
120
129
  # Returns setted @element_name or use class_name
121
130
  def element_name
122
- return self.class.element_name unless self.class.element_name.nil? || self.class.element_name == ''
131
+ return self.class.element_name unless self.class.element_name.nil? || self.class.element_name == ""
123
132
 
124
- self.class.name.split('::').last
133
+ self.class.name.split("::").last
125
134
  end
126
135
 
127
136
  def expanded_element_name
128
137
  return element_name unless current_namespace
138
+
129
139
  "#{current_namespace}:#{element_name}"
130
140
  end
131
141
 
@@ -155,9 +165,9 @@ module Cfdi40
155
165
  def formated_value(accessor)
156
166
  case self.class.formats[accessor]
157
167
  when :t_Importe
158
- sprintf("%0.6f", public_send(accessor).to_f)
168
+ public_send(accessor).to_f == 0.0 ? "0" : format("%0.6f", public_send(accessor).to_f)
159
169
  when :t_ImporteMXN
160
- sprintf("%0.2f", public_send(accessor).to_f)
170
+ public_send(accessor).to_f == 0.0 ? "0" : format("%0.2f", public_send(accessor).to_f)
161
171
  else
162
172
  public_send(accessor)
163
173
  end
data/lib/cfdi40/pago.rb CHANGED
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Cfdi40
2
4
  class Pago < Node
3
- define_attribute :monto, xml_attribute: 'Monto'
4
- define_attribute :fecha_pago, xml_attribute: 'FechaPago'
5
- define_attribute :forma_pago, xml_attribute: 'FormaDePagoP'
6
- define_attribute :moneda, xml_attribute: 'MonedaP', readonly: true, default: 'MXN'
7
- define_attribute :tipo_cambio, xml_attribute: 'TipoCambioP', readonly: true, default: '1'
5
+ define_attribute :monto, xml_attribute: "Monto"
6
+ define_attribute :fecha_pago, xml_attribute: "FechaPago"
7
+ define_attribute :forma_pago, xml_attribute: "FormaDePagoP"
8
+ define_attribute :moneda, xml_attribute: "MonedaP", readonly: true, default: "MXN"
9
+ define_attribute :tipo_cambio, xml_attribute: "TipoCambioP", readonly: true, default: "1"
8
10
 
9
11
  attr_accessor :uuid, :serie, :folio, :num_parcialidad, :importe_saldo_anterior, :objeto_impuestos
10
12
 
@@ -35,7 +37,7 @@ module Cfdi40
35
37
 
36
38
  @docto_relacionados =
37
39
  @children_nodes.select do |child|
38
- child if child.class.name == 'Cfdi40::DoctoRelacionado'
40
+ child if child.instance_of?(::Cfdi40::DoctoRelacionado)
39
41
  end
40
42
  end
41
43
 
@@ -45,7 +47,7 @@ module Cfdi40
45
47
  summary = {}
46
48
  docto_relacionados.each do |docto|
47
49
  docto.traslados_summary.each do |key, data|
48
- summary[key] ||= {base: 0, importe: 0}
50
+ summary[key] ||= { base: 0, importe: 0 }
49
51
  summary[key][:base] += data[:base]
50
52
  summary[key][:importe] += data[:importe]
51
53
  end
data/lib/cfdi40/pagos.rb CHANGED
@@ -1,23 +1,23 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Cfdi40
2
4
  class Pagos < Node
3
5
  define_namespace "pago20", "http://www.sat.gob.mx/Pagos20"
4
6
  define_attribute :schema_location,
5
- xml_attribute: 'xsi:schemaLocation',
7
+ xml_attribute: "xsi:schemaLocation",
6
8
  readonly: true,
7
9
  default: "http://www.sat.gob.mx/Pagos20 " \
8
10
  "http://www.sat.gob.mx/sitio_internet/cfd/Pagos/Pagos20.xsd"
9
- define_attribute :version, xml_attribute: 'Version', readonly: true, default: '2.0'
11
+ define_attribute :version, xml_attribute: "Version", readonly: true, default: "2.0"
10
12
 
11
- def add_pago(attributes={})
13
+ def add_pago(attributes = {})
12
14
  pago = Pago.new
13
15
  pago.parent_node = self
14
16
  attributes.each do |key, value|
15
17
  method_name = "#{key}=".to_sym
16
- if pago.respond_to?(method_name)
17
- pago.public_send(method_name, value)
18
- else
19
- raise Error, ":#{key} no se puede asignar al nodo Pago"
20
- end
18
+ raise Error, ":#{key} no se puede asignar al nodo Pago" unless pago.respond_to?(method_name)
19
+
20
+ pago.public_send(method_name, value)
21
21
  end
22
22
  pago.monto = pago.monto.round(2)
23
23
  pago.add_docto_relacionado
@@ -33,7 +33,7 @@ module Cfdi40
33
33
  end
34
34
 
35
35
  def update_totales_traslado_iva16
36
- key = ['002', 'Tasa', '0.160000']
36
+ key = ["002", "Tasa", "0.160000"]
37
37
  return if traslados_summary[key].nil?
38
38
 
39
39
  totales_node.base_iva16 = traslados_summary[key][:base]
@@ -67,7 +67,7 @@ module Cfdi40
67
67
  end
68
68
 
69
69
  def pago_nodes
70
- @children_nodes.select { |node| node.class.name == 'Cfdi40::Pago' }
70
+ @children_nodes.select { |node| node.instance_of?(::Cfdi40::Pago) }
71
71
  end
72
72
  end
73
73
  end
@@ -1,11 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Cfdi40
2
4
  class Receptor < Node
3
- define_attribute :rfc, xml_attribute: 'Rfc'
4
- define_attribute :nombre, xml_attribute: 'Nombre'
5
- define_attribute :domicilio_fiscal, xml_attribute: 'DomicilioFiscalReceptor'
6
- define_attribute :residencia_fiscal, xml_attribute: 'ResidenciaFiscal'
7
- define_attribute :num_reg_id_trib, xml_attribute: 'NumRegIdTrib'
8
- define_attribute :regimen_fiscal, xml_attribute: 'RegimenFiscalReceptor'
9
- define_attribute :uso_cfdi, xml_attribute: 'UsoCFDI'
5
+ define_attribute :rfc, xml_attribute: "Rfc"
6
+ define_attribute :nombre, xml_attribute: "Nombre"
7
+ define_attribute :domicilio_fiscal, xml_attribute: "DomicilioFiscalReceptor"
8
+ define_attribute :residencia_fiscal, xml_attribute: "ResidenciaFiscal"
9
+ define_attribute :num_reg_id_trib, xml_attribute: "NumRegIdTrib"
10
+ define_attribute :regimen_fiscal, xml_attribute: "RegimenFiscalReceptor"
11
+ define_attribute :uso_cfdi, xml_attribute: "UsoCFDI"
10
12
  end
11
13
  end
@@ -1,7 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Cfdi40
2
4
  class SatCsd
3
- attr_reader :x509_cert
4
- attr_reader :private_key
5
+ attr_reader :x509_cert, :private_key
5
6
 
6
7
  def cert_path=(path)
7
8
  @cert_path = path
@@ -43,8 +44,8 @@ module Cfdi40
43
44
  def no_certificado
44
45
  return unless x509_cert
45
46
 
46
- s = ''
47
- x509_cert.serial.to_s(16).split('').each_with_index do |c, i|
47
+ s = ""
48
+ x509_cert.serial.to_s(16).chars.each_with_index do |c, i|
48
49
  next if i.even?
49
50
 
50
51
  s += c
@@ -74,9 +75,9 @@ module Cfdi40
74
75
 
75
76
  def key_to_pem(key_der)
76
77
  array_key_pem = []
77
- array_key_pem << '-----BEGIN ENCRYPTED PRIVATE KEY-----'
78
+ array_key_pem << "-----BEGIN ENCRYPTED PRIVATE KEY-----"
78
79
  array_key_pem += Base64.strict_encode64(key_der).scan(/.{1,64}/)
79
- array_key_pem << '-----END ENCRYPTED PRIVATE KEY-----'
80
+ array_key_pem << "-----END ENCRYPTED PRIVATE KEY-----"
80
81
  array_key_pem.join("\n")
81
82
  end
82
83
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Validates Schema Using xsd files
2
4
  module Cfdi40
3
5
  class SchemaValidator
@@ -6,7 +8,8 @@ module Cfdi40
6
8
  # schema = Nokogiri::XML::Schema(File.open("/home/israel/git/cfdi40/lib/xsd/cfdv40.xsd"), options)
7
9
 
8
10
  attr_reader :errors
9
- LOCAL_XSD_PATH = File.join(File.dirname(__FILE__), '..', 'xsd', 'cfdv40.xsd')
11
+
12
+ LOCAL_XSD_PATH = File.join(File.dirname(__FILE__), "..", "xsd", "cfdv40.xsd")
10
13
 
11
14
  # Param xml is xml string
12
15
  def initialize(xml)
@@ -1,15 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Cfdi40
2
4
  class Totales < Node
3
- define_attribute :ret_iva, xml_attribute: 'TotalRetencionesIVA'
4
- define_attribute :ret_isr, xml_attribute: 'TotalRetencionesISR'
5
- define_attribute :ret_ieps, xml_attribute: 'TotalRetencionesIEPS'
6
- define_attribute :base_iva16, xml_attribute: 'TotalTrasladosBaseIVA16'
7
- define_attribute :importe_iva16, xml_attribute: 'TotalTrasladosImpuestoIVA16'
8
- define_attribute :base_iva8, xml_attribute: 'TotalTrasladosBaseIVA8'
9
- define_attribute :importe_iva8, xml_attribute: 'TotalTrasladosImpuestoIVA8'
10
- define_attribute :base_iva0, xml_attribute: 'TotalTrasladosBaseIVA0'
11
- define_attribute :importe_iva0, xml_attribute: 'TotalTrasladosImpuestoIVA0'
12
- define_attribute :base_iva_excento, xml_attribute: 'TotalTrasladosBaseIVAExento'
13
- define_attribute :monto_total, xml_attribute: 'MontoTotalPagos'
5
+ define_attribute :ret_iva, xml_attribute: "TotalRetencionesIVA"
6
+ define_attribute :ret_isr, xml_attribute: "TotalRetencionesISR"
7
+ define_attribute :ret_ieps, xml_attribute: "TotalRetencionesIEPS"
8
+ define_attribute :base_iva16, xml_attribute: "TotalTrasladosBaseIVA16"
9
+ define_attribute :importe_iva16, xml_attribute: "TotalTrasladosImpuestoIVA16"
10
+ define_attribute :base_iva8, xml_attribute: "TotalTrasladosBaseIVA8"
11
+ define_attribute :importe_iva8, xml_attribute: "TotalTrasladosImpuestoIVA8"
12
+ define_attribute :base_iva0, xml_attribute: "TotalTrasladosBaseIVA0"
13
+ define_attribute :importe_iva0, xml_attribute: "TotalTrasladosImpuestoIVA0"
14
+ define_attribute :base_iva_excento, xml_attribute: "TotalTrasladosBaseIVAExento"
15
+ define_attribute :monto_total, xml_attribute: "MontoTotalPagos"
14
16
  end
15
17
  end
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Cfdi40
2
4
  class Traslado < Node
3
- define_attribute :base, xml_attribute: 'Base', format: :t_ImporteMXN
4
- define_attribute :impuesto, xml_attribute: 'Impuesto'
5
- define_attribute :tipo_factor, xml_attribute: 'TipoFactor', default: 'Tasa'
6
- define_attribute :tasa_o_cuota, xml_attribute: 'TasaOCuota', format: :t_Importe
7
- define_attribute :importe, xml_attribute: 'Importe', format: :t_ImporteMXN
5
+ define_attribute :base, xml_attribute: "Base", format: :t_ImporteMXN
6
+ define_attribute :impuesto, xml_attribute: "Impuesto"
7
+ define_attribute :tipo_factor, xml_attribute: "TipoFactor", default: "Tasa"
8
+ define_attribute :tasa_o_cuota, xml_attribute: "TasaOCuota", format: :t_Importe
9
+ define_attribute :importe, xml_attribute: "Importe", format: :t_ImporteMXN
8
10
  end
9
11
  end
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Cfdi40
2
4
  class TrasladoDR < Node
3
- define_attribute :base_dr, xml_attribute: 'BaseDR', format: :t_ImporteMXN
4
- define_attribute :impuesto_dr, xml_attribute: 'ImpuestoDR', default: '002'
5
- define_attribute :tipo_factor_dr, xml_attribute: 'TipoFactorDR', default: 'Tasa'
6
- define_attribute :tasa_o_cuota_dr, xml_attribute: 'TasaOCuotaDR', default: '0.160000', format: :t_Importe
7
- define_attribute :importe_dr, xml_attribute: 'ImporteDR', format: :t_ImporteMXN
5
+ define_attribute :base_dr, xml_attribute: "BaseDR", format: :t_ImporteMXN
6
+ define_attribute :impuesto_dr, xml_attribute: "ImpuestoDR", default: "002"
7
+ define_attribute :tipo_factor_dr, xml_attribute: "TipoFactorDR", default: "Tasa"
8
+ define_attribute :tasa_o_cuota_dr, xml_attribute: "TasaOCuotaDR", default: "0.160000", format: :t_Importe
9
+ define_attribute :importe_dr, xml_attribute: "ImporteDR", format: :t_ImporteMXN
8
10
 
9
11
  attr_accessor :monto_pago
10
12
 
@@ -14,5 +16,3 @@ module Cfdi40
14
16
  end
15
17
  end
16
18
  end
17
-
18
-
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Cfdi40
2
4
  class TrasladoP < Node
3
- define_attribute :base_p, xml_attribute: 'BaseP', format: :t_ImporteMXN
4
- define_attribute :impuesto_p, xml_attribute: 'ImpuestoP', default: '002'
5
- define_attribute :tipo_factor_p, xml_attribute: 'TipoFactorP', default: 'Tasa'
6
- define_attribute :tasa_o_cuota_p, xml_attribute: 'TasaOCuotaP', default: '0.160000', format: :t_Importe
7
- define_attribute :importe_p, xml_attribute: 'ImporteP', format: :t_ImporteMXN
5
+ define_attribute :base_p, xml_attribute: "BaseP", format: :t_ImporteMXN
6
+ define_attribute :impuesto_p, xml_attribute: "ImpuestoP", default: "002"
7
+ define_attribute :tipo_factor_p, xml_attribute: "TipoFactorP", default: "Tasa"
8
+ define_attribute :tasa_o_cuota_p, xml_attribute: "TasaOCuotaP", default: "0.160000", format: :t_Importe
9
+ define_attribute :importe_p, xml_attribute: "ImporteP", format: :t_ImporteMXN
8
10
  end
9
11
  end
@@ -1,13 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Cfdi40
2
4
  class Traslados < Node
3
5
  def traslado_iva
4
6
  return @traslado_iva if defined?(@traslado_iva)
5
-
7
+
6
8
  @traslado_iva = Traslado.new
7
9
  # TODO: FIX magic number
8
- @traslado_iva.impuesto = '002'
10
+ @traslado_iva.impuesto = "002"
9
11
  @traslado_iva.parent_node = self
10
- self.children_nodes << @traslado_iva
12
+ children_nodes << @traslado_iva
11
13
  @traslado_iva
12
14
  end
13
15
 
@@ -1,5 +1,6 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Cfdi40
2
4
  class TrasladosDR < Node
3
5
  end
4
6
  end
5
-
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Cfdi40
2
4
  class TrasladosP < Node
3
5
  def add_traslado_p_nodes
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Cfdi40
4
- VERSION = "0.0.6"
4
+ VERSION = "0.0.8"
5
5
  end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Methods related to create a new Comprobante
4
+ # from an XML (string)
5
+ module Cfdi40
6
+ class XmlLoader
7
+ attr_reader :cfdi, :xml_doc
8
+
9
+ def initialize(xml_string)
10
+ @xml_doc = Nokogiri::XML(xml_string)
11
+ # TODO. validar versión del CFDI definido en xml_doc
12
+ @cfdi = Cfdi40::Comprobante.new
13
+ @cfdi.load_from_ng_node(xml_doc.root)
14
+ @cfdi.load_cert
15
+ load_emisor
16
+ load_receptor
17
+ load_conceptos
18
+ load_impuestos
19
+ @cfdi
20
+ end
21
+
22
+ private
23
+
24
+ def load_conceptos
25
+ n_concepto = 0
26
+ xml_doc.xpath("//cfdi:Concepto").each do |node|
27
+ n_concepto += 1
28
+ concepto = @cfdi.load_concepto(node)
29
+ iva_path = "//cfdi:Concepto[#{n_concepto}]/cfdi:Impuestos[1]/cfdi:Traslados[1]/cfdi:Traslado[@Impuesto='002']"
30
+ iva_node = xml_doc.xpath(iva_path).first
31
+ concepto.load_traslado_iva(iva_node) if iva_node
32
+ end
33
+ end
34
+
35
+ def load_emisor
36
+ ng_emisor_node = xml_doc.xpath("//cfdi:Emisor").first
37
+ return if ng_emisor_node.nil?
38
+
39
+ @cfdi.emisor.load_from_ng_node(ng_emisor_node)
40
+ end
41
+
42
+ def load_receptor
43
+ ng_receptor_node = xml_doc.xpath("//cfdi:Receptor").first
44
+ return if ng_receptor_node.nil?
45
+
46
+ @cfdi.receptor.load_from_ng_node(ng_receptor_node)
47
+ end
48
+
49
+ def load_impuestos
50
+ impuestos_node = xml_doc.xpath("cfdi:Comprobante/cfdi:Impuestos").first
51
+ return if impuestos_node.nil?
52
+
53
+ @cfdi.load_impuestos(impuestos_node)
54
+ end
55
+ end
56
+ end
data/lib/cfdi40.rb CHANGED
@@ -27,7 +27,8 @@ require_relative "cfdi40/traslado_dr"
27
27
  require_relative "cfdi40/impuestos_p"
28
28
  require_relative "cfdi40/traslados_p"
29
29
  require_relative "cfdi40/traslado_p"
30
- require_relative "cfdi40/totales.rb"
30
+ require_relative "cfdi40/totales"
31
+ require_relative "cfdi40/xml_loader"
31
32
 
32
33
  # Leading module and entry point for all features and classes
33
34
  #
@@ -40,4 +41,9 @@ module Cfdi40
40
41
  def self.new
41
42
  Comprobante.new
42
43
  end
44
+
45
+ def self.open(xml_string, mode: :readwrite)
46
+ loader = XmlLoader.new(xml_string)
47
+ loader.cfdi
48
+ end
43
49
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cfdi40
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.6
4
+ version: 0.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Israel Benítez
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-04-16 00:00:00.000000000 Z
11
+ date: 2025-05-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
@@ -68,6 +68,7 @@ files:
68
68
  - lib/cfdi40/traslados_dr.rb
69
69
  - lib/cfdi40/traslados_p.rb
70
70
  - lib/cfdi40/version.rb
71
+ - lib/cfdi40/xml_loader.rb
71
72
  - lib/xsd/Pagos20.xsd
72
73
  - lib/xsd/README.md
73
74
  - lib/xsd/TimbreFiscalDigitalv11.xsd