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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c93f265a23f6c466d800ce90e2258525844b3f15451971b0140f8c2ee826d2c5
4
- data.tar.gz: 1fcd506dfa5eefbffa1fec8ad3e98543cbc0045936859ca171b42caf46ff6649
3
+ metadata.gz: 55ca7f3a64dc87b4b19cbb5d389f71ccd624b70e7c5af601d97dd4dd6e1599db
4
+ data.tar.gz: 9cab2e03e554d96d47ff328c6c520782036ee9b561f11586c42bd353a3112f1d
5
5
  SHA512:
6
- metadata.gz: 8a13afec5754aeb24cf6168f66b23b658e79cb8d8cbf93c74a11bc592ae5913f77b3d6d5da45e19b69384c94a9221bb4f7b9897e0bc6672f7360b71a0538d759
7
- data.tar.gz: 473a8ce2434b628e1fb61e8342cbdcdc1972fb00350a382e5b2b113c0d16018b804cd6d05438a2b26ddb54ba121491f9df94a75f976247eea682d7fcd7e461f2
6
+ metadata.gz: '0310189ea57acabe4fffc77ed24d1b02aab3d49e0afafd47d1abf7df10856d19a5a00b89d302138c6b43b036e8af5d52671d922b274e10613f1d57d76b732e1e'
7
+ data.tar.gz: 719ed8d0e569dd621edf7ee702271b7c468c2e6b4606297f650cac39b6cb199d06d881f07b15e56599d214da3481651987348cfc24997e6a1843939ea933c711
data/CHANGELOG.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # Cfdi40
2
2
 
3
+ Please see `README_es-MX.md`
4
+
3
5
  ## [Unreleased]
4
6
 
5
7
  ### Added
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cfdi40 (0.0.4)
4
+ cfdi40 (0.0.6)
5
5
  nokogiri (>= 1.10.10)
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -12,6 +12,8 @@ TODO: Document, document, document
12
12
  ## Features
13
13
 
14
14
  * XML generation and sign.
15
+ * Node 'iedu'
16
+ * Concept is calculated from gross price or net price
15
17
 
16
18
  ## Future features
17
19
 
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 CSD
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
@@ -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 = @sat_csd.name
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
- # clave_prod_serv
86
- # no_identificacion
87
- # cantidad
88
- # clave_unidad
89
- # unidad
90
- # descripcion
91
- # valor_unitario
92
- # importe
93
- # descuento
94
- # objeto_imp
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
@@ -1,6 +1,6 @@
1
1
  # Represents node 'concepto'
2
2
  #
3
- # * Attribute +Importe+ represente gross amount. Gross amount id before taxes and the result of multiply
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 = 0
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
- @importe_neto = precio_neto * cantidad
65
- breakdown_taxes
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
- @importe_bruto = @precio_bruto * cantidad
80
- add_taxes
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 add_taxes
85
- @base_ieps = @importe_bruto
86
- @ieps = @base_ieps * tasa_ieps
87
- @base_iva = @base_ieps + @ieps
88
- @iva = @base_iva * tasa_iva
89
- @importe_neto = @base_iva + @iva
90
- @precio_neto = @importe_neto / cantidad
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 unless @iva > 0
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 = (@tasa_iva > 0 || @tasa_ieps > 0 ? '02' : '01')
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
@@ -1,7 +1,7 @@
1
1
  module Cfdi40
2
2
  class Impuestos < Node
3
- define_attribute :total_impuestos_retenidos, xml_attribute: 'TotalImpuestosRetenidos', format: :t_Importe
4
- define_attribute :total_impuestos_trasladados, xml_attribute: 'TotalImpuestosTrasladados', format: :t_Importe
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)
@@ -0,0 +1,12 @@
1
+ module Cfdi40
2
+ class ImpuestosDR < Node
3
+ def traslados_dr
4
+ return @traslados_dr if defined?(@traslados_dr)
5
+
6
+ @traslados_dr = TrasladosDR.new
7
+ @traslados_dr.parent_node = self
8
+ @children_nodes << @traslados_dr
9
+ @traslados_dr
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module Cfdi40
2
+ class ImpuestosP < Node
3
+
4
+ def traslados_p
5
+ return @traslados_p if defined?(@traslados_p)
6
+
7
+ @traslados_p = TrasladosP.new
8
+ add_child_node @traslados_p
9
+ @traslados_p
10
+ end
11
+ end
12
+ end
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
@@ -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
@@ -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
@@ -1,9 +1,9 @@
1
1
  module Cfdi40
2
2
  class Traslado < Node
3
- define_attribute :base, xml_attribute: 'Base', format: :t_Importe
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: :t_Importe
7
+ define_attribute :importe, xml_attribute: 'Importe', format: :t_ImporteMXN
8
8
  end
9
9
  end