cfdi40 0.0.4 → 0.0.6
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/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
|