cfdi40 0.0.4 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +2 -0
- data/Gemfile.lock +1 -1
- data/README.md +2 -0
- data/README_es-MX.md +68 -1
- data/lib/cfdi40/complemento.rb +18 -0
- data/lib/cfdi40/comprobante.rb +82 -13
- data/lib/cfdi40/concepto.rb +24 -24
- data/lib/cfdi40/docto_relacionado.rb +66 -0
- data/lib/cfdi40/impuestos.rb +2 -2
- data/lib/cfdi40/impuestos_dr.rb +12 -0
- data/lib/cfdi40/impuestos_p.rb +12 -0
- data/lib/cfdi40/node.rb +10 -1
- data/lib/cfdi40/pago.rb +56 -0
- data/lib/cfdi40/pagos.rb +73 -0
- data/lib/cfdi40/totales.rb +15 -0
- data/lib/cfdi40/traslado.rb +2 -2
- data/lib/cfdi40/traslado_dr.rb +18 -0
- data/lib/cfdi40/traslado_p.rb +9 -0
- data/lib/cfdi40/traslados_dr.rb +5 -0
- data/lib/cfdi40/traslados_p.rb +16 -0
- data/lib/cfdi40/version.rb +1 -1
- data/lib/cfdi40.rb +11 -0
- data/lib/xsd/Pagos20.xsd +539 -0
- data/lib/xsd/TimbreFiscalDigitalv11.xsd +83 -0
- data/lib/xsd/catPagos.xsd +9 -0
- data/lib/xsd/cfdv40.xsd +2 -0
- data/lib/xslt/ComercioExterior11.xslt +181 -0
- data/lib/xslt/cadenaoriginal_TFD_1_1.xslt +47 -0
- data/lib/xslt/ecc12.xslt +99 -0
- data/lib/xslt/ine11.xslt +51 -30
- data/lib/xslt/ventavehiculos11.xslt +53 -0
- metadata +20 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 55ca7f3a64dc87b4b19cbb5d389f71ccd624b70e7c5af601d97dd4dd6e1599db
|
4
|
+
data.tar.gz: 9cab2e03e554d96d47ff328c6c520782036ee9b561f11586c42bd353a3112f1d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '0310189ea57acabe4fffc77ed24d1b02aab3d49e0afafd47d1abf7df10856d19a5a00b89d302138c6b43b036e8af5d52671d922b274e10613f1d57d76b732e1e'
|
7
|
+
data.tar.gz: 719ed8d0e569dd621edf7ee702271b7c468c2e6b4606297f650cac39b6cb199d06d881f07b15e56599d214da3481651987348cfc24997e6a1843939ea933c711
|
data/CHANGELOG.md
CHANGED
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
data/README_es-MX.md
CHANGED
@@ -14,7 +14,7 @@ que esta herramienta:
|
|
14
14
|
información esencial para la elaboración del CFDi.
|
15
15
|
* Realice los cálculos complementarios como impuestos,
|
16
16
|
totales, etcétera.
|
17
|
-
* Valide el CFDi contra los
|
17
|
+
* Valide el CFDi contra los XSD
|
18
18
|
* Selle el CFDi.
|
19
19
|
|
20
20
|
# Uso
|
@@ -53,8 +53,75 @@ que esta herramienta:
|
|
53
53
|
# Genera CFDI firmado
|
54
54
|
xml_string = cfdi.to_xml
|
55
55
|
|
56
|
+
## Ejemplo CFDI con complemento de pago
|
57
|
+
|
58
|
+
# Inicia un cfdi
|
59
|
+
cfdi = Cfdi40.new
|
60
|
+
cfdi.tipo_de_comprobante = 'P'
|
61
|
+
|
62
|
+
# Datos del emisor.
|
63
|
+
# RFC y Nombre se extraen del certificado si no existen
|
64
|
+
cfdi.lugar_expedicion = '06000'
|
65
|
+
cfdi.emisor.regimen_fiscal = '612'
|
66
|
+
|
67
|
+
# Datos del receptor
|
68
|
+
cfdi.receptor.nombre = 'JUAN PUEBLO BUENO'
|
69
|
+
cfdi.receptor.rfc = 'XAXX010101000'
|
70
|
+
cfdi.receptor.domicilio_fiscal = '06000'
|
71
|
+
cfdi.receptor.regimen_fiscal = '616'
|
72
|
+
cfdi.receptor.uso_cfdi = 'CP01'
|
73
|
+
|
74
|
+
# Agrega un concepto en pesos,
|
75
|
+
# precio final al cliente (neto)
|
76
|
+
# causa IVA con tasa de 16% (default)
|
77
|
+
cfdi.add_pago(
|
78
|
+
monto: 200.17,
|
79
|
+
uuid: 'e40229b3-5c4b-46fb-9ba8-707df828a5bc',
|
80
|
+
serie: 'A',
|
81
|
+
folio: '12345',
|
82
|
+
num_parcialidad: 2,
|
83
|
+
fecha_pago: '2023-04-01T12:20:34',
|
84
|
+
forma_pago: '01',
|
85
|
+
importe_saldo_anterior: 845.673
|
86
|
+
)
|
87
|
+
|
88
|
+
# Archivos CSD
|
89
|
+
cfdi.cert_path = '/path_to/certificado.cer'
|
90
|
+
cfdi.key_path = '/path_to/llave_privada.key'
|
91
|
+
cfdi.key_pass = 'contraseña'
|
92
|
+
|
93
|
+
# Genera CFDI firmado
|
94
|
+
xml_string = cfdi.to_xml
|
95
|
+
|
96
|
+
|
97
|
+
# Lo que sigue
|
98
|
+
|
99
|
+
* Nodo de Traslados cuando hay tasa de IEPS
|
100
|
+
* Retenciones de impuestos
|
101
|
+
* CFDI de pagos con varios pagos y varios documentos en cada pago
|
102
|
+
|
56
103
|
# Cambios
|
57
104
|
|
105
|
+
# 0.0.6
|
106
|
+
* CFDi con complemento de pagos
|
107
|
+
|
108
|
+
# 0.0.5
|
109
|
+
|
110
|
+
* IVA tasa 0 e IVA excento. Los conceptos con iva tasa 0 deben llevar el
|
111
|
+
nodo de traslados de impuestos. Los conceptos excentos de iva no
|
112
|
+
llevan el nodo de traslado. Al agregar el concepto usar `nil` para el
|
113
|
+
IVA cuando sea excento.
|
114
|
+
* Nuevo cálculo de impuestos e importes. El PAC solo acepta 2 decimales
|
115
|
+
en los nodos de traslado. Esto puede generar discrepancias al sumar
|
116
|
+
los conceptos si no se maneja el mismo número de decimales. Solamente
|
117
|
+
el valor unitario se manejará con 6 decimales.
|
118
|
+
|
119
|
+
# 0.0.4
|
120
|
+
|
121
|
+
* Atributos 'Total' y 'Subtotal' van con 2 decimales. Aunque el anexo 20
|
122
|
+
indica que el tipo es `t_Importe` (6 decimales) el PAC acepta solo 2
|
123
|
+
decimales si el CFDi está en pesos mexicanos.
|
124
|
+
|
58
125
|
# 0.0.3
|
59
126
|
|
60
127
|
* Lee RFC en certificados de personas morales. Los certificados de
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Cfdi40
|
2
|
+
class Complemento < Node
|
3
|
+
# See Comprobante#add_pago
|
4
|
+
def add_pago(attributes={})
|
5
|
+
pagos.totales_node
|
6
|
+
pagos.add_pago(attributes)
|
7
|
+
end
|
8
|
+
|
9
|
+
def pagos
|
10
|
+
return @pagos if defined?(@pagos)
|
11
|
+
|
12
|
+
@pagos = Pagos.new
|
13
|
+
@pagos.parent_node = self
|
14
|
+
@children_nodes << @pagos
|
15
|
+
@pagos
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/cfdi40/comprobante.rb
CHANGED
@@ -60,7 +60,7 @@ module Cfdi40
|
|
60
60
|
@sat_csd ||= SatCsd.new
|
61
61
|
@sat_csd.cert_der = cert_data
|
62
62
|
emisor.rfc = @sat_csd.rfc
|
63
|
-
emisor.nombre
|
63
|
+
emisor.nombre ||= @sat_csd.name
|
64
64
|
@no_certificado = @sat_csd.no_certificado
|
65
65
|
@certificado = @sat_csd.cert64
|
66
66
|
true
|
@@ -82,19 +82,45 @@ module Cfdi40
|
|
82
82
|
@docxml = nil
|
83
83
|
end
|
84
84
|
|
85
|
-
#
|
86
|
-
#
|
87
|
-
#
|
88
|
-
# clave_unidad
|
89
|
-
#
|
90
|
-
# descripcion
|
91
|
-
#
|
92
|
-
#
|
93
|
-
#
|
94
|
-
#
|
85
|
+
# ## Required attributes
|
86
|
+
#
|
87
|
+
# +clave_prod_serv+:: From SAT catalogue
|
88
|
+
# +clave_unidad+:: From SAT catalogue
|
89
|
+
# +cantidad+:: Must be greather than 0
|
90
|
+
# +descripcion+:: Product or service description
|
91
|
+
#
|
92
|
+
# ### Price and Taxes attributes
|
93
|
+
#
|
94
|
+
# +tasa_iva+:: Decimal between 0 and 1. Nil means exempt. Default value is 0.16
|
95
|
+
# +tasa_ieps+:: Decimal between 0 and 1. Nil means exempt. Default value is null
|
96
|
+
# +precio_bruto+:: Price before apply taxes or gross price.
|
97
|
+
# All quantities are calculated based on this price and taxes rate.
|
98
|
+
# +precio_neto+:: Precio after taxes or net price. All quantities are calculated from this prices.
|
99
|
+
# When both, +precio_neto+ and +precio_bruto+ exist, +precio_neto+ is used
|
100
|
+
#
|
101
|
+
# The most common usage requires only the net price (+precio_neto+).
|
102
|
+
#
|
103
|
+
# ## Optional attributes:
|
104
|
+
# +no_identificacion+::
|
105
|
+
# +unidad+::
|
106
|
+
# +descuento+:: PENDING
|
107
|
+
#
|
108
|
+
# ## Special attributes
|
109
|
+
#
|
110
|
+
# ### IEDU attributes
|
111
|
+
#
|
112
|
+
# IEDU node (path: cfdi:Comprobante/cfdi:Conceptos/cfdi:Concepto/cfdi:ComplementoConcepto/iedu:instEducativas) is
|
113
|
+
# generated when one of +iedu_nombre_alumno+, +iedu_curp+, +iedu_nivel_educativo+ exist.
|
114
|
+
#
|
115
|
+
# +iedu_nombre_alumno+::
|
116
|
+
# +iedu_curp+::
|
117
|
+
# +iedu_nivel_educativo+::
|
118
|
+
# +iedu_aut_rvoe+::
|
119
|
+
# +iedu_rfc_pago+::
|
95
120
|
#
|
96
|
-
# TODO: Document accepted attributes and its use
|
97
121
|
def add_concepto(attributes = {})
|
122
|
+
raise Error, 'CFDi tipo pago no acepta conceptos' if tipo_de_comprobante == 'P'
|
123
|
+
|
98
124
|
concepto = Concepto.new
|
99
125
|
concepto.parent_node = @conceptos
|
100
126
|
attributes.each do |key, value|
|
@@ -111,6 +137,23 @@ module Cfdi40
|
|
111
137
|
concepto
|
112
138
|
end
|
113
139
|
|
140
|
+
# TODO: Doc params add_pago
|
141
|
+
# monto
|
142
|
+
# uuid
|
143
|
+
# folio
|
144
|
+
# serie
|
145
|
+
# num_parcialidad
|
146
|
+
# fecha_pago
|
147
|
+
# forma_pago
|
148
|
+
# importe_saldo_anterior
|
149
|
+
# objeto_impuestos
|
150
|
+
def add_pago(attributes = {})
|
151
|
+
raise Error, "CFDi debe ser tipo 'P'" unless tipo_de_comprobante == 'P'
|
152
|
+
|
153
|
+
add_node_concepto_actividad_pago
|
154
|
+
complemento.add_pago(attributes)
|
155
|
+
end
|
156
|
+
|
114
157
|
def to_s
|
115
158
|
to_xml
|
116
159
|
end
|
@@ -143,6 +186,23 @@ module Cfdi40
|
|
143
186
|
|
144
187
|
private
|
145
188
|
|
189
|
+
def add_node_concepto_actividad_pago
|
190
|
+
return if defined?(@concepto_actividad)
|
191
|
+
|
192
|
+
@receptor.uso_cfdi = 'CP01'
|
193
|
+
@concepto_actividad = Concepto.new
|
194
|
+
@concepto_actividad.clave_prod_serv = '84111506'
|
195
|
+
@concepto_actividad.cantidad = 1
|
196
|
+
@concepto_actividad.clave_unidad = 'ACT'
|
197
|
+
@concepto_actividad.descripcion = 'Pago'
|
198
|
+
@concepto_actividad.precio_bruto = 0
|
199
|
+
@concepto_actividad.tasa_iva = nil
|
200
|
+
@concepto_actividad.objeto_impuestos = '01'
|
201
|
+
@concepto_actividad.calculate!
|
202
|
+
@conceptos.add_child_node @concepto_actividad
|
203
|
+
calculate!
|
204
|
+
end
|
205
|
+
|
146
206
|
def docxml
|
147
207
|
return @docxml if defined?(@docxml) && !@docxml.nil?
|
148
208
|
|
@@ -221,7 +281,7 @@ module Cfdi40
|
|
221
281
|
impuestos_node.traslados
|
222
282
|
end
|
223
283
|
|
224
|
-
# Eliminar
|
284
|
+
# TODO: Eliminar
|
225
285
|
def traslado_iva_node
|
226
286
|
impuestos_node.traslado_iva
|
227
287
|
end
|
@@ -232,5 +292,14 @@ module Cfdi40
|
|
232
292
|
@sat_csd ||= SatCsd.new
|
233
293
|
@sat_csd.set_private_key(@key_data, (defined?(@key_pass) ? @key_pass : nil))
|
234
294
|
end
|
295
|
+
|
296
|
+
def complemento
|
297
|
+
return @complemento if defined?(@complemento)
|
298
|
+
|
299
|
+
@complemento = Complemento.new
|
300
|
+
@complemento.parent_node = self
|
301
|
+
@children_nodes << @complemento
|
302
|
+
@complemento
|
303
|
+
end
|
235
304
|
end
|
236
305
|
end
|
data/lib/cfdi40/concepto.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Represents node 'concepto'
|
2
2
|
#
|
3
|
-
# * Attribute +Importe+ represente gross amount. Gross amount
|
3
|
+
# * Attribute +Importe+ represente gross amount. Gross amount is before taxes and the result of multiply
|
4
4
|
# +ValorUnitario+ by +Cantidad+
|
5
5
|
#
|
6
6
|
module Cfdi40
|
@@ -24,7 +24,7 @@ module Cfdi40
|
|
24
24
|
|
25
25
|
def initialize
|
26
26
|
@tasa_iva = 0.16
|
27
|
-
@tasa_ieps =
|
27
|
+
@tasa_ieps = nil
|
28
28
|
super
|
29
29
|
end
|
30
30
|
|
@@ -33,6 +33,7 @@ module Cfdi40
|
|
33
33
|
def calculate!
|
34
34
|
set_defaults
|
35
35
|
assign_objeto_imp
|
36
|
+
# TODO: accept discount
|
36
37
|
if defined?(@precio_neto) && !@precio_neto.nil?
|
37
38
|
calculate_from_net_price
|
38
39
|
elsif defined?(@precio_bruto) && !@precio_bruto.nil?
|
@@ -61,33 +62,28 @@ module Cfdi40
|
|
61
62
|
|
62
63
|
def calculate_from_net_price
|
63
64
|
set_defaults
|
64
|
-
@
|
65
|
-
|
65
|
+
@precio_neto = @precio_neto.round(2)
|
66
|
+
@precio_bruto = (@precio_neto / ((1 + tasa_iva.to_f) * (1 + tasa_ieps.to_f))).round(6)
|
67
|
+
calculate_taxes
|
66
68
|
update_xml_attributes
|
67
69
|
end
|
68
70
|
|
69
|
-
def breakdown_taxes
|
70
|
-
@base_iva = @importe_neto / (1 + tasa_iva)
|
71
|
-
@iva = @importe_neto - @base_iva
|
72
|
-
@base_ieps = @base_iva / (1 + tasa_ieps)
|
73
|
-
@ieps = @base_iva - @base_ieps
|
74
|
-
@importe_bruto = @base_ieps
|
75
|
-
@precio_bruto = @importe_bruto / @cantidad
|
76
|
-
end
|
77
|
-
|
78
71
|
def calculate_from_gross_price
|
79
|
-
@
|
80
|
-
|
72
|
+
@precio_bruto = @precio_bruto.round(6)
|
73
|
+
calculate_taxes
|
74
|
+
# May be can not be rounded with 2 decimals.
|
75
|
+
# Example gross_price = 1.99512
|
76
|
+
@precio_neto = (@importe_neto / cantidad).round(6)
|
81
77
|
update_xml_attributes
|
82
78
|
end
|
83
79
|
|
84
|
-
def
|
85
|
-
@base_ieps = @
|
86
|
-
@ieps = @base_ieps * tasa_ieps
|
87
|
-
@base_iva = @base_ieps + @ieps
|
88
|
-
@iva = @base_iva * tasa_iva
|
89
|
-
@
|
90
|
-
@
|
80
|
+
def calculate_taxes
|
81
|
+
@base_ieps = (@precio_bruto * cantidad).round(2)
|
82
|
+
@ieps = (@base_ieps * tasa_ieps.to_f).round(2)
|
83
|
+
@base_iva = (@base_ieps + @ieps).round(2)
|
84
|
+
@iva = (@base_iva * tasa_iva.to_f).round(2)
|
85
|
+
@importe_bruto = @base_ieps
|
86
|
+
@importe_neto = (@base_iva + @iva).round(2)
|
91
87
|
end
|
92
88
|
|
93
89
|
def update_xml_attributes
|
@@ -96,7 +92,7 @@ module Cfdi40
|
|
96
92
|
end
|
97
93
|
|
98
94
|
def add_info_to_traslado_iva
|
99
|
-
return
|
95
|
+
return if @tasa_iva.nil?
|
100
96
|
|
101
97
|
traslado_iva_node.importe = @iva
|
102
98
|
traslado_iva_node.base = @base_iva
|
@@ -104,9 +100,13 @@ module Cfdi40
|
|
104
100
|
end
|
105
101
|
|
106
102
|
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.
|
106
|
+
|
107
107
|
return if objeto_impuestos == '03'
|
108
108
|
|
109
|
-
self.objeto_impuestos = (
|
109
|
+
self.objeto_impuestos = (!@tasa_iva.nil? || !@tasa_ieps.nil? ? '02' : '01')
|
110
110
|
end
|
111
111
|
|
112
112
|
def impuestos_node
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Cfdi40
|
2
|
+
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'
|
13
|
+
|
14
|
+
def calculate!
|
15
|
+
self.imp_saldo_insoluto = (imp_saldo_ant - imp_pagado).round(2)
|
16
|
+
add_impuestos
|
17
|
+
end
|
18
|
+
|
19
|
+
# Add nodes for 'traslado_dr' and/or 'retencion_dr' and intermetiate nodes
|
20
|
+
def add_impuestos
|
21
|
+
add_traslado if objeto_imp_dr == '02'
|
22
|
+
end
|
23
|
+
|
24
|
+
def add_traslado
|
25
|
+
return unless objeto_imp_dr == '02'
|
26
|
+
|
27
|
+
# Taxes values for IVA rate 0.16 assumming that all 'conceptos' in the realted document
|
28
|
+
# has the same tax rate. This could not be true but the is the most common case.
|
29
|
+
traslado_dr = TrasladoDR.new
|
30
|
+
traslado_dr.monto_pago = imp_pagado.round(2)
|
31
|
+
traslado_dr.calculate!
|
32
|
+
traslados_dr.add_child_node(traslado_dr)
|
33
|
+
traslado_dr
|
34
|
+
end
|
35
|
+
|
36
|
+
# Return a hash. The key is an array [impuesto, tipo_factor, tasa_o_cuot]
|
37
|
+
# and the value is another hash with the keys :base, :importe
|
38
|
+
def traslados_summary
|
39
|
+
return {} unless defined?(@traslados_dr)
|
40
|
+
|
41
|
+
summary = {}
|
42
|
+
@traslados_dr.children_nodes.each do |traslado_dr|
|
43
|
+
key = [traslado_dr.impuesto_dr, traslado_dr.tipo_factor_dr, traslado_dr.tasa_o_cuota_dr]
|
44
|
+
summary[key] ||= { base: 0, importe: 0 }
|
45
|
+
summary[key][:base] += traslado_dr.base_dr.to_f
|
46
|
+
summary[key][:importe] += traslado_dr.importe_dr.to_f
|
47
|
+
end
|
48
|
+
summary
|
49
|
+
end
|
50
|
+
|
51
|
+
def traslados_dr
|
52
|
+
return @traslados_dr if defined?(@traslados_dr)
|
53
|
+
|
54
|
+
@traslados_dr = impuestos_dr.traslados_dr
|
55
|
+
end
|
56
|
+
|
57
|
+
def impuestos_dr
|
58
|
+
return @impuestos_dr if defined?(@impuestos_dr)
|
59
|
+
|
60
|
+
@impuestos_dr = ImpuestosDR.new
|
61
|
+
@impuestos_dr.parent_node = self
|
62
|
+
@children_nodes << @impuestos_dr
|
63
|
+
@impuestos_dr
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/lib/cfdi40/impuestos.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module Cfdi40
|
2
2
|
class Impuestos < Node
|
3
|
-
define_attribute :total_impuestos_retenidos, xml_attribute: 'TotalImpuestosRetenidos', format: :
|
4
|
-
define_attribute :total_impuestos_trasladados, xml_attribute: 'TotalImpuestosTrasladados', format: :
|
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
5
|
|
6
6
|
def traslados
|
7
7
|
return @traslados if defined?(@traslados)
|
data/lib/cfdi40/node.rb
CHANGED
@@ -7,6 +7,7 @@ module Cfdi40
|
|
7
7
|
def initialize
|
8
8
|
self.class.verify_class_variables
|
9
9
|
@children_nodes = []
|
10
|
+
set_defaults
|
10
11
|
end
|
11
12
|
|
12
13
|
# Use class variables to define attributes used to create nodes
|
@@ -87,6 +88,13 @@ module Cfdi40
|
|
87
88
|
instance_variable_get("@#{accessor}".to_sym).nil?
|
88
89
|
end
|
89
90
|
|
91
|
+
def add_child_node(child_node)
|
92
|
+
raise Error, 'child_node must be a Node object' unless child_node.is_a?(Node)
|
93
|
+
|
94
|
+
child_node.parent_node = self
|
95
|
+
@children_nodes << child_node
|
96
|
+
end
|
97
|
+
|
90
98
|
def current_namespace
|
91
99
|
return unless self.class.respond_to?(:namespaces)
|
92
100
|
if self.class.namespaces.empty?
|
@@ -97,7 +105,8 @@ module Cfdi40
|
|
97
105
|
end
|
98
106
|
|
99
107
|
def create_xml_node
|
100
|
-
set_defaults
|
108
|
+
# TODO: Quitar la siguiente linea (set_defaults) si funciona poniendo los defaults en initialize
|
109
|
+
# set_defaults
|
101
110
|
if self.respond_to?(:before_add, true)
|
102
111
|
self.before_add
|
103
112
|
end
|
data/lib/cfdi40/pago.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
module Cfdi40
|
2
|
+
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'
|
8
|
+
|
9
|
+
attr_accessor :uuid, :serie, :folio, :num_parcialidad, :importe_saldo_anterior, :objeto_impuestos
|
10
|
+
|
11
|
+
# Generate docto_relacionado node
|
12
|
+
# Data for dcto_relacionado node is obtained from de accessors
|
13
|
+
# uuid, serie, folio
|
14
|
+
def add_docto_relacionado
|
15
|
+
docto_relacionado = DoctoRelacionado.new
|
16
|
+
docto_relacionado.parent_node = self
|
17
|
+
docto_relacionado.id_documento = uuid
|
18
|
+
docto_relacionado.serie = serie
|
19
|
+
docto_relacionado.folio = folio
|
20
|
+
docto_relacionado.num_parcialidad = num_parcialidad
|
21
|
+
docto_relacionado.imp_saldo_ant = importe_saldo_anterior.round(2)
|
22
|
+
docto_relacionado.imp_pagado = monto
|
23
|
+
docto_relacionado.calculate!
|
24
|
+
@children_nodes << docto_relacionado
|
25
|
+
end
|
26
|
+
|
27
|
+
def add_impuestos_p
|
28
|
+
impuestos_p = ImpuestosP.new
|
29
|
+
add_child_node impuestos_p
|
30
|
+
impuestos_p.traslados_p.add_traslado_p_nodes
|
31
|
+
end
|
32
|
+
|
33
|
+
def docto_relacionados
|
34
|
+
return @docto_relacionados if defined?(@docto_relacionados)
|
35
|
+
|
36
|
+
@docto_relacionados =
|
37
|
+
@children_nodes.select do |child|
|
38
|
+
child if child.class.name == 'Cfdi40::DoctoRelacionado'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def traslados_summary
|
43
|
+
return @summary if defined?(@summary)
|
44
|
+
|
45
|
+
summary = {}
|
46
|
+
docto_relacionados.each do |docto|
|
47
|
+
docto.traslados_summary.each do |key, data|
|
48
|
+
summary[key] ||= {base: 0, importe: 0}
|
49
|
+
summary[key][:base] += data[:base]
|
50
|
+
summary[key][:importe] += data[:importe]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
@summary = summary
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/lib/cfdi40/pagos.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
module Cfdi40
|
2
|
+
class Pagos < Node
|
3
|
+
define_namespace "pago20", "http://www.sat.gob.mx/Pagos20"
|
4
|
+
define_attribute :schema_location,
|
5
|
+
xml_attribute: 'xsi:schemaLocation',
|
6
|
+
readonly: true,
|
7
|
+
default: "http://www.sat.gob.mx/Pagos20 " \
|
8
|
+
"http://www.sat.gob.mx/sitio_internet/cfd/Pagos/Pagos20.xsd"
|
9
|
+
define_attribute :version, xml_attribute: 'Version', readonly: true, default: '2.0'
|
10
|
+
|
11
|
+
def add_pago(attributes={})
|
12
|
+
pago = Pago.new
|
13
|
+
pago.parent_node = self
|
14
|
+
attributes.each do |key, value|
|
15
|
+
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
|
21
|
+
end
|
22
|
+
pago.monto = pago.monto.round(2)
|
23
|
+
pago.add_docto_relacionado
|
24
|
+
pago.add_impuestos_p
|
25
|
+
@children_nodes << pago
|
26
|
+
update_totales
|
27
|
+
true
|
28
|
+
end
|
29
|
+
|
30
|
+
def update_totales
|
31
|
+
update_totales_traslado_iva16
|
32
|
+
update_total_monto
|
33
|
+
end
|
34
|
+
|
35
|
+
def update_totales_traslado_iva16
|
36
|
+
key = ['002', 'Tasa', '0.160000']
|
37
|
+
return if traslados_summary[key].nil?
|
38
|
+
|
39
|
+
totales_node.base_iva16 = traslados_summary[key][:base]
|
40
|
+
totales_node.importe_iva16 = traslados_summary[key][:importe]
|
41
|
+
end
|
42
|
+
|
43
|
+
def update_total_monto
|
44
|
+
totales_node.monto_total = pago_nodes.map(&:monto).sum
|
45
|
+
end
|
46
|
+
|
47
|
+
def traslados_summary
|
48
|
+
@traslados_summary if defined?(@traslados_summary)
|
49
|
+
|
50
|
+
summary = {}
|
51
|
+
pago_nodes.each do |pago|
|
52
|
+
pago.traslados_summary.each do |key, data|
|
53
|
+
summary[key] ||= { base: 0, importe: 0 }
|
54
|
+
summary[key][:base] += data[:base]
|
55
|
+
summary[key][:importe] += data[:importe]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
@traslados_summary = summary
|
59
|
+
end
|
60
|
+
|
61
|
+
def totales_node
|
62
|
+
return @totales_node if defined?(@totales_node)
|
63
|
+
|
64
|
+
@totales_node = Totales.new
|
65
|
+
add_child_node @totales_node
|
66
|
+
@totales_node
|
67
|
+
end
|
68
|
+
|
69
|
+
def pago_nodes
|
70
|
+
@children_nodes.select { |node| node.class.name == 'Cfdi40::Pago' }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Cfdi40
|
2
|
+
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'
|
14
|
+
end
|
15
|
+
end
|
data/lib/cfdi40/traslado.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
module Cfdi40
|
2
2
|
class Traslado < Node
|
3
|
-
define_attribute :base, xml_attribute: 'Base', format: :
|
3
|
+
define_attribute :base, xml_attribute: 'Base', format: :t_ImporteMXN
|
4
4
|
define_attribute :impuesto, xml_attribute: 'Impuesto'
|
5
5
|
define_attribute :tipo_factor, xml_attribute: 'TipoFactor', default: 'Tasa'
|
6
6
|
define_attribute :tasa_o_cuota, xml_attribute: 'TasaOCuota', format: :t_Importe
|
7
|
-
define_attribute :importe, xml_attribute: 'Importe', format: :
|
7
|
+
define_attribute :importe, xml_attribute: 'Importe', format: :t_ImporteMXN
|
8
8
|
end
|
9
9
|
end
|