cfdi40 0.0.7 → 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 +4 -4
- data/.rubocop.yml +3 -0
- data/Gemfile.lock +3 -3
- data/README.md +10 -0
- data/README_es-MX.md +11 -0
- data/lib/cfdi40/complemento.rb +4 -2
- data/lib/cfdi40/complemento_concepto.rb +3 -1
- data/lib/cfdi40/comprobante.rb +90 -48
- data/lib/cfdi40/concepto.rb +32 -26
- data/lib/cfdi40/conceptos.rb +2 -0
- data/lib/cfdi40/docto_relacionado.rb +16 -14
- data/lib/cfdi40/emisor.rb +5 -3
- data/lib/cfdi40/impuestos.rb +6 -4
- data/lib/cfdi40/impuestos_dr.rb +2 -0
- data/lib/cfdi40/impuestos_p.rb +2 -1
- data/lib/cfdi40/inst_educativas.rb +10 -8
- data/lib/cfdi40/node.rb +28 -18
- data/lib/cfdi40/pago.rb +9 -7
- data/lib/cfdi40/pagos.rb +10 -10
- data/lib/cfdi40/receptor.rb +9 -7
- data/lib/cfdi40/sat_csd.rb +7 -6
- data/lib/cfdi40/schema_validator.rb +4 -1
- data/lib/cfdi40/totales.rb +13 -11
- data/lib/cfdi40/traslado.rb +7 -5
- data/lib/cfdi40/traslado_dr.rb +7 -7
- data/lib/cfdi40/traslado_p.rb +7 -5
- data/lib/cfdi40/traslados.rb +5 -3
- data/lib/cfdi40/traslados_dr.rb +2 -1
- data/lib/cfdi40/traslados_p.rb +2 -0
- data/lib/cfdi40/version.rb +1 -1
- data/lib/cfdi40/xml_loader.rb +56 -0
- data/lib/cfdi40.rb +7 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ce28369c399d94875c10fa980ead1eb1cd75e0300035ffeb61c7bc310d87b327
|
4
|
+
data.tar.gz: 8d03b666fb2fac0b592032891fadfec3643f64938eb46b2e301bf4a98e68014a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1246bce889af62b67256d2c1278201bfaee2c6ae5693048b7a00b8ec424830192610a8d98df294f5227b47ed3f19a0556f1f07146b057a0b8f65e3c95a8874f0
|
7
|
+
data.tar.gz: d3142214a86217a324b8539bc3455695a257505265e76dc746fa0999d2a2a1ed3486abd8a0e58ef31f4fdd46d828ae9529adcf7d271ce77499657124c855c395
|
data/.rubocop.yml
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
cfdi40 (0.0.
|
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.
|
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.
|
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
@@ -99,6 +99,14 @@ Hasta ahora:
|
|
99
99
|
# Genera CFDI firmado
|
100
100
|
xml_string = cfdi.to_xml
|
101
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
|
102
110
|
|
103
111
|
# Lo que sigue
|
104
112
|
|
@@ -108,6 +116,9 @@ Hasta ahora:
|
|
108
116
|
|
109
117
|
# Cambios
|
110
118
|
|
119
|
+
# 0.0.8
|
120
|
+
* Carga básica de un CFDI desde XML.
|
121
|
+
|
111
122
|
# 0.0.7
|
112
123
|
* Ajustes al CFDi con complemento de pagos por validaciones del PAC.
|
113
124
|
|
data/lib/cfdi40/complemento.rb
CHANGED
@@ -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
|
-
|
10
|
+
children_nodes << @inst_educativas_node
|
9
11
|
@inst_educativas_node
|
10
12
|
end
|
11
13
|
end
|
data/lib/cfdi40/comprobante.rb
CHANGED
@@ -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:
|
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:
|
14
|
-
define_attribute :serie, xml_attribute:
|
15
|
-
define_attribute :folio, xml_attribute:
|
16
|
-
define_attribute :fecha, xml_attribute:
|
17
|
-
define_attribute :sello, xml_attribute:
|
18
|
-
define_attribute :forma_pago, xml_attribute:
|
19
|
-
define_attribute :no_certificado, xml_attribute:
|
20
|
-
define_attribute :certificado, xml_attribute:
|
21
|
-
define_attribute :condiciones_de_pago, xml_attribute:
|
22
|
-
define_attribute :subtotal, xml_attribute:
|
23
|
-
define_attribute :descuento, xml_attribute:
|
24
|
-
define_attribute :moneda, xml_attribute:
|
25
|
-
define_attribute :tipo_cambio, xml_attribute:
|
26
|
-
define_attribute :total, xml_attribute:
|
27
|
-
define_attribute :tipo_de_comprobante, xml_attribute:
|
28
|
-
define_attribute :exportacion, xml_attribute:
|
29
|
-
define_attribute :metodo_pago, xml_attribute:
|
30
|
-
define_attribute :lugar_expedicion, xml_attribute:
|
31
|
-
define_attribute :confirmacion, xml_attribute:
|
32
|
-
|
33
|
-
attr_reader :emisor, :receptor, :
|
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,
|
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(
|
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,
|
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
|
-
|
129
|
-
|
130
|
-
|
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 ==
|
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__),
|
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(
|
216
|
+
transformed.children.to_s.gsub("&", "&").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 =
|
236
|
+
|
237
|
+
@receptor.uso_cfdi = "CP01"
|
193
238
|
@concepto_actividad = Concepto.new
|
194
|
-
@concepto_actividad.clave_prod_serv =
|
239
|
+
@concepto_actividad.clave_prod_serv = "84111506"
|
195
240
|
@concepto_actividad.cantidad = 1
|
196
|
-
@concepto_actividad.clave_unidad =
|
197
|
-
@concepto_actividad.descripcion =
|
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 =
|
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
|
-
#
|
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
|
data/lib/cfdi40/concepto.rb
CHANGED
@@ -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:
|
9
|
-
define_attribute :no_identificacion,xml_attribute:
|
10
|
-
define_attribute :cantidad, xml_attribute:
|
11
|
-
define_attribute :clave_unidad, xml_attribute:
|
12
|
-
define_attribute :unidad, xml_attribute:
|
13
|
-
define_attribute :descripcion, xml_attribute:
|
14
|
-
define_attribute :valor_unitario, xml_attribute:
|
15
|
-
define_attribute :importe, xml_attribute:
|
16
|
-
define_attribute :descuento, xml_attribute:
|
17
|
-
define_attribute :objeto_impuestos, xml_attribute:
|
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 !
|
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 ==
|
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
|
105
|
-
#03
|
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 ==
|
119
|
+
return if objeto_impuestos == "03"
|
108
120
|
|
109
|
-
self.objeto_impuestos = (!@tasa_iva.nil? || !@tasa_ieps.nil? ?
|
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
|
-
|
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
|
-
|
160
|
+
children_nodes << @complemento_concepto_node
|
155
161
|
@complemento_concepto_node
|
156
162
|
end
|
157
163
|
|
data/lib/cfdi40/conceptos.rb
CHANGED
@@ -1,15 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Cfdi40
|
2
4
|
class DoctoRelacionado < Node
|
3
|
-
define_attribute :id_documento, xml_attribute:
|
4
|
-
define_attribute :serie, xml_attribute:
|
5
|
-
define_attribute :folio, xml_attribute:
|
6
|
-
define_attribute :moneda_dr, xml_attribute:
|
7
|
-
define_attribute :equivalencia_dr, xml_attribute:
|
8
|
-
define_attribute :num_parcialidad, xml_attribute:
|
9
|
-
define_attribute :imp_saldo_ant, xml_attribute:
|
10
|
-
define_attribute :imp_pagado, xml_attribute:
|
11
|
-
define_attribute :imp_saldo_insoluto, xml_attribute:
|
12
|
-
define_attribute :objeto_imp_dr, xml_attribute:
|
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)
|
@@ -18,13 +20,13 @@ module Cfdi40
|
|
18
20
|
|
19
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 ==
|
23
|
+
add_traslado if objeto_imp_dr == "02"
|
22
24
|
end
|
23
25
|
|
24
26
|
def add_traslado
|
25
|
-
return unless objeto_imp_dr ==
|
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:
|
4
|
-
define_attribute :nombre, xml_attribute:
|
5
|
-
define_attribute :regimen_fiscal, xml_attribute:
|
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
|
data/lib/cfdi40/impuestos.rb
CHANGED
@@ -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:
|
4
|
-
define_attribute :total_impuestos_trasladados, xml_attribute:
|
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
|
-
|
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
|
data/lib/cfdi40/impuestos_dr.rb
CHANGED
data/lib/cfdi40/impuestos_p.rb
CHANGED
@@ -1,17 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Cfdi40
|
2
4
|
class InstEducativas < Node
|
3
|
-
define_element_name
|
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:
|
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:
|
11
|
-
define_attribute :nombre_alumno, xml_attribute:
|
12
|
-
define_attribute :curp, xml_attribute:
|
13
|
-
define_attribute :nivel_educativo, xml_attribute:
|
14
|
-
define_attribute :aut_rvoe, xml_attribute:
|
15
|
-
define_attribute :rfc_pago, xml_attribute:
|
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
|
-
#
|
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
|
-
|
38
|
-
|
39
|
-
|
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,
|
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
|
-
|
101
|
-
|
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
|
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(
|
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
|
-
public_send(accessor).to_f == 0.0 ?
|
168
|
+
public_send(accessor).to_f == 0.0 ? "0" : format("%0.6f", public_send(accessor).to_f)
|
159
169
|
when :t_ImporteMXN
|
160
|
-
public_send(accessor).to_f == 0.0 ?
|
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:
|
4
|
-
define_attribute :fecha_pago, xml_attribute:
|
5
|
-
define_attribute :forma_pago, xml_attribute:
|
6
|
-
define_attribute :moneda, xml_attribute:
|
7
|
-
define_attribute :tipo_cambio, xml_attribute:
|
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.
|
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:
|
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:
|
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
|
-
|
17
|
-
|
18
|
-
|
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 = [
|
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.
|
70
|
+
@children_nodes.select { |node| node.instance_of?(::Cfdi40::Pago) }
|
71
71
|
end
|
72
72
|
end
|
73
73
|
end
|
data/lib/cfdi40/receptor.rb
CHANGED
@@ -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:
|
4
|
-
define_attribute :nombre, xml_attribute:
|
5
|
-
define_attribute :domicilio_fiscal, xml_attribute:
|
6
|
-
define_attribute :residencia_fiscal, xml_attribute:
|
7
|
-
define_attribute :num_reg_id_trib, xml_attribute:
|
8
|
-
define_attribute :regimen_fiscal, xml_attribute:
|
9
|
-
define_attribute :uso_cfdi, xml_attribute:
|
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
|
data/lib/cfdi40/sat_csd.rb
CHANGED
@@ -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).
|
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 <<
|
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 <<
|
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
|
-
|
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)
|
data/lib/cfdi40/totales.rb
CHANGED
@@ -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:
|
4
|
-
define_attribute :ret_isr, xml_attribute:
|
5
|
-
define_attribute :ret_ieps, xml_attribute:
|
6
|
-
define_attribute :base_iva16, xml_attribute:
|
7
|
-
define_attribute :importe_iva16, xml_attribute:
|
8
|
-
define_attribute :base_iva8, xml_attribute:
|
9
|
-
define_attribute :importe_iva8, xml_attribute:
|
10
|
-
define_attribute :base_iva0, xml_attribute:
|
11
|
-
define_attribute :importe_iva0, xml_attribute:
|
12
|
-
define_attribute :base_iva_excento, xml_attribute:
|
13
|
-
define_attribute :monto_total, xml_attribute:
|
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
|
data/lib/cfdi40/traslado.rb
CHANGED
@@ -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:
|
4
|
-
define_attribute :impuesto, xml_attribute:
|
5
|
-
define_attribute :tipo_factor, xml_attribute:
|
6
|
-
define_attribute :tasa_o_cuota, xml_attribute:
|
7
|
-
define_attribute :importe, xml_attribute:
|
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
|
data/lib/cfdi40/traslado_dr.rb
CHANGED
@@ -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:
|
4
|
-
define_attribute :impuesto_dr, xml_attribute:
|
5
|
-
define_attribute :tipo_factor_dr, xml_attribute:
|
6
|
-
define_attribute :tasa_o_cuota_dr, xml_attribute:
|
7
|
-
define_attribute :importe_dr, xml_attribute:
|
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
|
-
|
data/lib/cfdi40/traslado_p.rb
CHANGED
@@ -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:
|
4
|
-
define_attribute :impuesto_p, xml_attribute:
|
5
|
-
define_attribute :tipo_factor_p, xml_attribute:
|
6
|
-
define_attribute :tasa_o_cuota_p, xml_attribute:
|
7
|
-
define_attribute :importe_p, xml_attribute:
|
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
|
data/lib/cfdi40/traslados.rb
CHANGED
@@ -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 =
|
10
|
+
@traslado_iva.impuesto = "002"
|
9
11
|
@traslado_iva.parent_node = self
|
10
|
-
|
12
|
+
children_nodes << @traslado_iva
|
11
13
|
@traslado_iva
|
12
14
|
end
|
13
15
|
|
data/lib/cfdi40/traslados_dr.rb
CHANGED
data/lib/cfdi40/traslados_p.rb
CHANGED
data/lib/cfdi40/version.rb
CHANGED
@@ -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
|
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.
|
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:
|
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
|