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 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