Class: Cfdi40::Comprobante

Inherits:
Node
  • Object
show all
Defined in:
lib/cfdi40/comprobante.rb

Overview

root node

Instance Attribute Summary collapse

Attributes inherited from Node

#children_nodes, #element_name, #parent_node, #readonly, #xml_document, #xml_parent

Instance Method Summary collapse

Methods inherited from Node

#add_attributes_to, #add_child_node, #add_children_to, #add_namespaces_to, #attibute_is_null?, attributes, #clean_cached_xml, #create_xml_node, #current_namespace, default_values, define_attribute, define_element_name, define_namespace, define_reader, define_writer, #delete_child, element_name, #expanded_element_name, formats, #formatted_value, #load_from_ng_node, #lock, namespaces, #set_defaults, verify_class_variables

Constructor Details

#initializeComprobante

Returns a new instance of Comprobante.



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/cfdi40/comprobante.rb', line 38

def initialize
  super
  @errors = []
  @conceptos = Conceptos.new
  @conceptos.parent_node = self
  @emisor = Emisor.new
  @emisor.parent_node = self
  @receptor = Receptor.new
  @receptor.parent_node = self
  @sat_csd = SatCsd.new
  @fecha ||= Time.now
  @children_nodes = [@emisor, @receptor, @conceptos]
  @cfdi_relacionados = []
  @namespace_pagos_on_root = false
  set_defaults
end

Instance Attribute Details

#cadena_originalObject (readonly)

Returns the value of attribute cadena_original.



34
35
36
# File 'lib/cfdi40/comprobante.rb', line 34

def cadena_original
  @cadena_original
end

#cfdi_relacionadosObject (readonly)

Returns the value of attribute cfdi_relacionados.



34
35
36
# File 'lib/cfdi40/comprobante.rb', line 34

def cfdi_relacionados
  @cfdi_relacionados
end

#conceptosObject (readonly)

Returns the value of attribute conceptos.



34
35
36
# File 'lib/cfdi40/comprobante.rb', line 34

def conceptos
  @conceptos
end

#emisorObject (readonly)

Returns the value of attribute emisor.



34
35
36
# File 'lib/cfdi40/comprobante.rb', line 34

def emisor
  @emisor
end

#errorsObject (readonly)

Returns the value of attribute errors.



34
35
36
# File 'lib/cfdi40/comprobante.rb', line 34

def errors
  @errors
end

#key_data=(value) ⇒ Object (writeonly)

Sets the attribute key_data

Parameters:

  • value

    the value to set the attribute key_data to.



35
36
37
# File 'lib/cfdi40/comprobante.rb', line 35

def key_data=(value)
  @key_data = value
end

#key_pass=(value) ⇒ Object (writeonly)

Sets the attribute key_pass

Parameters:

  • value

    the value to set the attribute key_pass to.



35
36
37
# File 'lib/cfdi40/comprobante.rb', line 35

def key_pass=(value)
  @key_pass = value
end

#loaded_xmlObject

Returns the value of attribute loaded_xml.



36
37
38
# File 'lib/cfdi40/comprobante.rb', line 36

def loaded_xml
  @loaded_xml
end

#namespace_pagos_on_root=(value) ⇒ Object (writeonly)

Sets the attribute namespace_pagos_on_root

Parameters:

  • value

    the value to set the attribute namespace_pagos_on_root to.



35
36
37
# File 'lib/cfdi40/comprobante.rb', line 35

def namespace_pagos_on_root=(value)
  @namespace_pagos_on_root = value
end

#private_keyObject (readonly)

Returns the value of attribute private_key.



34
35
36
# File 'lib/cfdi40/comprobante.rb', line 34

def private_key
  @private_key
end

#receptorObject (readonly)

Returns the value of attribute receptor.



34
35
36
# File 'lib/cfdi40/comprobante.rb', line 34

def receptor
  @receptor
end

#sat_csdObject (readonly)

Returns the value of attribute sat_csd.



34
35
36
# File 'lib/cfdi40/comprobante.rb', line 34

def sat_csd
  @sat_csd
end

Instance Method Details

#add_cfdi_relacionado(tipo_relacion, uuid) ⇒ Object



221
222
223
224
225
226
227
228
229
230
# File 'lib/cfdi40/comprobante.rb', line 221

def add_cfdi_relacionado(tipo_relacion, uuid)
  cfdi_relacionados_node = CfdiRelacionados.new
  cfdi_relacionados_node.tipo_relacion = tipo_relacion
  cfdi_relacionados_node.parent_node = self
  cfdi_relacionados_node.add_cfdi(uuid)
  @children_nodes << cfdi_relacionados_node
  @cfdi_relacionados ||= []
  @cfdi_relacionados << cfdi_relacionados_node
  cfdi_relacionados_node
end

#add_concepto(attributes = {}) ⇒ Object

## Required attributes

clave_prod_serv

From SAT catalogue

clave_unidad

From SAT catalogue

cantidad

Must be greather than 0

descripcion

Product or service description

### Price and Taxes attributes

tasa_iva

Decimal between 0 and 1. Nil means exempt. Default value is 0.16

tasa_ieps

Decimal between 0 and 1. Nil means exempt. Default value is null

precio_bruto

Price before apply taxes or gross price. All quantities are calculated based on this price and taxes rate.

precio_neto

Precio after taxes or net price. All quantities are calculated from this prices. When both, precio_neto and precio_bruto exist, precio_neto is used

The most common usage requires only the net price (precio_neto).

## Optional attributes:

no_identificacion
unidad
descuento

PENDING

## Special attributes

### IEDU attributes

IEDU node (path: cfdi:Comprobante/cfdi:Conceptos/cfdi:Concepto/cfdi:ComplementoConcepto/iedu:instEducativas) is generated when one of iedu_nombre_alumno, iedu_curp, iedu_nivel_educativo exist.

iedu_nombre_alumno
iedu_curp
iedu_nivel_educativo
iedu_aut_rvoe
iedu_rfc_pago

Raises:



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/cfdi40/comprobante.rb', line 139

def add_concepto(attributes = {})
  raise Error, "CFDi tipo pago no acepta conceptos" if tipo_de_comprobante == "P"

  concepto = Concepto.new
  concepto.parent_node = @conceptos
  attributes.each do |key, value|
    method_name = "#{key}=".to_sym
    raise Error, ":#{key} no se puede asignar al concepto" unless concepto.respond_to?(method_name)

    concepto.public_send(method_name, value)
  end
  concepto.calculate!
  @conceptos.children_nodes << concepto
  calculate!
  concepto
end

#add_namespace_pagos_to_rootObject

Some PACs require that the namespace pago20 be placed in root node



343
344
345
346
347
348
# File 'lib/cfdi40/comprobante.rb', line 343

def add_namespace_pagos_to_root
  self.class.define_namespace "pago20", "http://www.sat.gob.mx/Pagos20"
  @schema_location += " http://www.sat.gob.mx/Pagos20 " \
                       "http://www.sat.gob.mx/sitio_internet/cfd/Pagos/Pagos20.xsd"
  true
end

#add_pago(attributes = {}) ⇒ Object

TODO: Doc params add_pago monto uuid folio serie num_parcialidad fecha_pago forma_pago importe_saldo_anterior objeto_impuestos

Raises:



199
200
201
202
203
204
# File 'lib/cfdi40/comprobante.rb', line 199

def add_pago(attributes = {})
  raise Error, "CFDi debe ser tipo 'P'" unless tipo_de_comprobante == "P"

  add_node_concepto_actividad_pago
  complemento.add_pago(attributes)
end

#add_splitted_pago(attributes = {}) ⇒ Object

See test_adding_pago_with_n_docto_relacionados in file test/test_cfdi40_rep.rb

Raises:



214
215
216
217
218
219
# File 'lib/cfdi40/comprobante.rb', line 214

def add_splitted_pago(attributes = {})
  raise Error, "CFDi debe ser tipo 'P'" unless tipo_de_comprobante == "P"

  add_node_concepto_actividad_pago
  complemento.add_splitted_pago(attributes)
end

#calculate!Object



291
292
293
294
295
296
297
298
299
# File 'lib/cfdi40/comprobante.rb', line 291

def calculate!
  return false if readonly

  @docxml = nil
  @subtotal = @conceptos.children_nodes.map(&:importe).map(&:to_f).sum
  @total = @conceptos.children_nodes.map(&:importe_neto).map(&:to_f).sum
  add_traslados_summary_node
  true
end

#cert_der=(cert_data) ⇒ Object



62
63
64
65
66
67
68
69
70
# File 'lib/cfdi40/comprobante.rb', line 62

def cert_der=(cert_data)
  @sat_csd ||= SatCsd.new
  @sat_csd.cert_der = cert_data
  emisor.rfc = @sat_csd.rfc
  emisor.nombre ||= @sat_csd.name
  @no_certificado = @sat_csd.no_certificado
  @certificado = @sat_csd.cert64
  true
end

#cert_path=(path) ⇒ Object

Accept a path to read the certificate. Certificate is a X509 file. SAT generates those files in DER format.



58
59
60
# File 'lib/cfdi40/comprobante.rb', line 58

def cert_path=(path)
  self.cert_der = File.read(path)
end

#cfdi_relacionados_nodesObject



232
233
234
# File 'lib/cfdi40/comprobante.rb', line 232

def cfdi_relacionados_nodes
  @cfdi_relacionados
end

#concepto_nodesObject



301
302
303
# File 'lib/cfdi40/comprobante.rb', line 301

def concepto_nodes
  @conceptos.children_nodes
end

#key_path=(path) ⇒ Object



72
73
74
# File 'lib/cfdi40/comprobante.rb', line 72

def key_path=(path)
  @key_data = File.read(path)
end

#load_certObject

Load from attribute ‘Certificado’ when the CFDi is loaded from a string



78
79
80
81
82
83
84
85
86
87
# File 'lib/cfdi40/comprobante.rb', line 78

def load_cert
  return if @sat_csd&.cert64

  @sat_csd ||= SatCsd.new
  @sat_csd.cert_der = OpenSSL::X509::Certificate.new(Base64.decode64(certificado))
  true
rescue StandardError
  # puts "Waring; Unable to load certificate from XML string"
  false
end

#load_concepto(ng_node) ⇒ Object

Load node ‘Concepto’ from a Nokogiri::XML::Element



157
158
159
160
161
162
163
164
# File 'lib/cfdi40/comprobante.rb', line 157

def load_concepto(ng_node)
  concepto = Concepto.new
  concepto.parent_node = @conceptos
  concepto.load_from_ng_node(ng_node)
  concepto.precio_bruto = concepto.valor_unitario.to_f
  @conceptos.children_nodes << concepto
  concepto
end

#load_concepto_rep(ng_node) ⇒ Object

Load node ‘Concepto’ (rep) from a Nokogiri::XML::Element



167
168
169
170
171
172
173
174
# File 'lib/cfdi40/comprobante.rb', line 167

def load_concepto_rep(ng_node)
  concepto = ConceptoRep.new
  concepto.parent_node = @conceptos
  concepto.load_from_ng_node(ng_node)
  concepto.precio_bruto = concepto.valor_unitario.to_f
  @conceptos.children_nodes << concepto
  concepto
end

#load_impuestos(ng_node) ⇒ Object

Load node cfdi:Comprobante/cfdi:Impuestos

Normally this node is calculated but must be read from the XML when a CFDi is loaded



180
181
182
183
184
185
186
187
# File 'lib/cfdi40/comprobante.rb', line 180

def load_impuestos(ng_node)
  impuestos.load_from_ng_node(ng_node)
  ng_iva_node = ng_node.xpath("cfdi:Traslados/cfdi:Traslado[@Impuesto='002']").first
  return true if ng_iva_node.nil?

  impuestos.traslado_iva.load_from_ng_node(ng_iva_node)
  true
end

#load_pagos(pagos_node) ⇒ Object



332
333
334
# File 'lib/cfdi40/comprobante.rb', line 332

def load_pagos(pagos_node)
  complemento.load_pagos(pagos_node)
end

#load_tfd(tfd_node) ⇒ Object



324
325
326
327
328
329
330
# File 'lib/cfdi40/comprobante.rb', line 324

def load_tfd(tfd_node)
  timbre = Cfdi40::Timbre.new
  timbre.load_from_ng_node(tfd_node)
  timbre.parent_node = complemento
  complemento.children_nodes << timbre
  timbre
end

#original_contentObject



278
279
280
281
282
# File 'lib/cfdi40/comprobante.rb', line 278

def original_content
  xml_string = loaded_xml.nil? ? docxml.to_s : loaded_xml

  Cfdi40::OriginalContent.generate(xml_string)
end

#pago_nodesObject



305
306
307
308
309
# File 'lib/cfdi40/comprobante.rb', line 305

def pago_nodes
  return [] unless defined?(@complemento)

  complemento.pago_nodes
end

#remove_cfdi_relacionado(index) ⇒ Object



236
237
238
239
240
241
242
243
244
# File 'lib/cfdi40/comprobante.rb', line 236

def remove_cfdi_relacionado(index)
  return if @cfdi_relacionados.empty?

  nodo = @cfdi_relacionados[index.to_i]
  return unless nodo

  delete_child(nodo)
  @cfdi_relacionados.delete_at(index.to_i)
end

#remove_pago(index) ⇒ Object



206
207
208
209
210
211
# File 'lib/cfdi40/comprobante.rb', line 206

def remove_pago(index)
  return unless defined?(@complemento)
  return if complemento.pagos.pago_nodes.empty?

  complemento.pagos.remove_pago(index.to_i)
end

#signObject

Raises:



89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/cfdi40/comprobante.rb', line 89

def sign
  @sat_csd ||= SatCsd.new
  load_private_key if @sat_csd.private_key.nil?
  return unless @sat_csd.private_key

  raise Error, "Key and certificate not match" unless @sat_csd.valid_pair?

  @cadena_original = original_content
  digest = @sat_csd.private_key.sign(OpenSSL::Digest.new("SHA256"), @cadena_original)
  @sello = Base64.strict_encode64 digest
  lock
  @docxml = nil
end

#signed?Boolean

Returns:

  • (Boolean)


274
275
276
# File 'lib/cfdi40/comprobante.rb', line 274

def signed?
  !docxml.root.attributes["Sello"].nil?
end

#timbreObject



336
337
338
339
340
# File 'lib/cfdi40/comprobante.rb', line 336

def timbre
  return nil unless defined?(@complemento)

  complemento.timbre
end

#to_sObject



246
247
248
# File 'lib/cfdi40/comprobante.rb', line 246

def to_s
  to_xml
end

#to_xmlObject



250
251
252
253
254
255
256
257
# File 'lib/cfdi40/comprobante.rb', line 250

def to_xml
  return loaded_xml if !loaded_xml.nil? && signed?

  sign unless signed?
  return xml_string_ns_pagos_on_root if @namespace_pagos_on_root && pago_nodes.count > 0

  docxml.to_xml
end

#total_impuestos_trasladadosObject

Shortcut to attribute TotalImpuestosTrasladados of impuestos node



285
286
287
288
289
# File 'lib/cfdi40/comprobante.rb', line 285

def total_impuestos_trasladados
  return nil unless impuestos_node

  impuestos_node.total_impuestos_trasladados
end

#total_ivaObject



318
319
320
321
322
# File 'lib/cfdi40/comprobante.rb', line 318

def total_iva
  return 0 unless traslados

  traslados.traslados_iva.map(&:importe).map(&:to_f).sum.round(2)
end

#total_iva_nodeObject



311
312
313
314
315
316
# File 'lib/cfdi40/comprobante.rb', line 311

def total_iva_node
  # TODO: Puede haber más de un nodo, cuando hay varias tasas de iva
  return nil unless impuestos_node

  impuestos_node.traslado_iva
end

#valid?Boolean

Returns:

  • (Boolean)


259
260
261
262
263
264
265
# File 'lib/cfdi40/comprobante.rb', line 259

def valid?
  schema_validator = SchemaValidator.new(to_s)
  return true if schema_validator.valid?

  @errors = schema_validator.errors
  @errors.empty?
end

#valid_signature?Boolean

Returns:

  • (Boolean)


267
268
269
270
271
272
# File 'lib/cfdi40/comprobante.rb', line 267

def valid_signature?
  return false unless signed?

  signature_validator = SignatureValidator.new(to_xml)
  signature_validator.valid?
end