Class: Cfdi40::Comprobante
Overview
root node
Instance Attribute Summary collapse
-
#cadena_original ⇒ Object
readonly
Returns the value of attribute cadena_original.
-
#conceptos ⇒ Object
readonly
Returns the value of attribute conceptos.
-
#emisor ⇒ Object
readonly
Returns the value of attribute emisor.
-
#errors ⇒ Object
readonly
Returns the value of attribute errors.
-
#key_data ⇒ Object
writeonly
Sets the attribute key_data.
-
#key_pass ⇒ Object
writeonly
Sets the attribute key_pass.
-
#loaded_xml ⇒ Object
Returns the value of attribute loaded_xml.
-
#namespace_pagos_on_root ⇒ Object
writeonly
Sets the attribute namespace_pagos_on_root.
-
#private_key ⇒ Object
readonly
Returns the value of attribute private_key.
-
#receptor ⇒ Object
readonly
Returns the value of attribute receptor.
-
#sat_csd ⇒ Object
readonly
Returns the value of attribute sat_csd.
Attributes inherited from Node
#children_nodes, #element_name, #parent_node, #readonly, #xml_document, #xml_parent
Instance Method Summary collapse
-
#add_concepto(attributes = {}) ⇒ Object
## Required attributes.
-
#add_namespace_pagos_to_root ⇒ Object
Some PACs require that the namespace pago20 be placed in root node.
-
#add_pago(attributes = {}) ⇒ Object
TODO: Doc params add_pago monto uuid folio serie num_parcialidad fecha_pago forma_pago importe_saldo_anterior objeto_impuestos.
-
#add_splitted_pago(attributes = {}) ⇒ Object
See test_adding_pago_with_n_docto_relacionados in file test/test_cfdi40_rep.rb.
- #calculate! ⇒ Object
- #cert_der=(cert_data) ⇒ Object
-
#cert_path=(path) ⇒ Object
Accept a path to read the certificate.
- #concepto_nodes ⇒ Object
-
#initialize ⇒ Comprobante
constructor
A new instance of Comprobante.
- #key_path=(path) ⇒ Object
-
#load_cert ⇒ Object
Load from attribute ‘Certificado’ when the CFDi is loaded from a string.
-
#load_concepto(ng_node) ⇒ Object
Load node ‘Concepto’ from a Nokogiri::XML::Element.
-
#load_impuestos(ng_node) ⇒ Object
Load node cfdi:Comprobante/cfdi:Impuestos.
- #load_pagos(pagos_node) ⇒ Object
- #load_tfd(tfd_node) ⇒ Object
- #original_content ⇒ Object
- #pago_nodes ⇒ Object
- #remove_pago(index) ⇒ Object
- #sign ⇒ Object
- #signed? ⇒ Boolean
- #timbre ⇒ Object
- #to_s ⇒ Object
- #to_xml ⇒ Object
-
#total_impuestos_trasladados ⇒ Object
Shortcut to attribute TotalImpuestosTrasladados of impuestos node.
- #total_iva ⇒ Object
- #total_iva_node ⇒ Object
- #valid? ⇒ Boolean
- #valid_signature? ⇒ Boolean
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
#initialize ⇒ Comprobante
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
# 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] @namespace_pagos_on_root = false set_defaults end |
Instance Attribute Details
#cadena_original ⇒ Object (readonly)
Returns the value of attribute cadena_original.
34 35 36 |
# File 'lib/cfdi40/comprobante.rb', line 34 def cadena_original @cadena_original end |
#conceptos ⇒ Object (readonly)
Returns the value of attribute conceptos.
34 35 36 |
# File 'lib/cfdi40/comprobante.rb', line 34 def conceptos @conceptos end |
#emisor ⇒ Object (readonly)
Returns the value of attribute emisor.
34 35 36 |
# File 'lib/cfdi40/comprobante.rb', line 34 def emisor @emisor end |
#errors ⇒ Object (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
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
35 36 37 |
# File 'lib/cfdi40/comprobante.rb', line 35 def key_pass=(value) @key_pass = value end |
#loaded_xml ⇒ Object
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
35 36 37 |
# File 'lib/cfdi40/comprobante.rb', line 35 def namespace_pagos_on_root=(value) @namespace_pagos_on_root = value end |
#private_key ⇒ Object (readonly)
Returns the value of attribute private_key.
34 35 36 |
# File 'lib/cfdi40/comprobante.rb', line 34 def private_key @private_key end |
#receptor ⇒ Object (readonly)
Returns the value of attribute receptor.
34 35 36 |
# File 'lib/cfdi40/comprobante.rb', line 34 def receptor @receptor end |
#sat_csd ⇒ Object (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_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_netoandprecio_brutoexist,precio_netois used
The most common usage requires only the net price (precio_neto).
## Optional attributes:
no_identificacionunidaddescuento-
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_alumnoiedu_curpiedu_nivel_educativoiedu_aut_rvoeiedu_rfc_pago
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
# File 'lib/cfdi40/comprobante.rb', line 138 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_root ⇒ Object
Some PACs require that the namespace pago20 be placed in root node
307 308 309 310 311 312 |
# File 'lib/cfdi40/comprobante.rb', line 307 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
188 189 190 191 192 193 |
# File 'lib/cfdi40/comprobante.rb', line 188 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
203 204 205 206 207 208 |
# File 'lib/cfdi40/comprobante.rb', line 203 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
255 256 257 258 259 260 261 262 263 |
# File 'lib/cfdi40/comprobante.rb', line 255 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
61 62 63 64 65 66 67 68 69 |
# File 'lib/cfdi40/comprobante.rb', line 61 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.
57 58 59 |
# File 'lib/cfdi40/comprobante.rb', line 57 def cert_path=(path) self.cert_der = File.read(path) end |
#concepto_nodes ⇒ Object
265 266 267 |
# File 'lib/cfdi40/comprobante.rb', line 265 def concepto_nodes @conceptos.children_nodes end |
#key_path=(path) ⇒ Object
71 72 73 |
# File 'lib/cfdi40/comprobante.rb', line 71 def key_path=(path) @key_data = File.read(path) end |
#load_cert ⇒ Object
Load from attribute ‘Certificado’ when the CFDi is loaded from a string
77 78 79 80 81 82 83 84 85 86 |
# File 'lib/cfdi40/comprobante.rb', line 77 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
156 157 158 159 160 161 162 163 |
# File 'lib/cfdi40/comprobante.rb', line 156 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_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
169 170 171 172 173 174 175 176 |
# File 'lib/cfdi40/comprobante.rb', line 169 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
296 297 298 |
# File 'lib/cfdi40/comprobante.rb', line 296 def load_pagos(pagos_node) complemento.load_pagos(pagos_node) end |
#load_tfd(tfd_node) ⇒ Object
288 289 290 291 292 293 294 |
# File 'lib/cfdi40/comprobante.rb', line 288 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_content ⇒ Object
242 243 244 245 246 |
# File 'lib/cfdi40/comprobante.rb', line 242 def original_content xml_string = loaded_xml.nil? ? docxml.to_s : loaded_xml Cfdi40::OriginalContent.generate(xml_string) end |
#pago_nodes ⇒ Object
269 270 271 272 273 |
# File 'lib/cfdi40/comprobante.rb', line 269 def pago_nodes return [] unless defined?(@complemento) complemento.pago_nodes end |
#remove_pago(index) ⇒ Object
195 196 197 198 199 200 |
# File 'lib/cfdi40/comprobante.rb', line 195 def remove_pago(index) return unless defined?(@complemento) return if complemento.pagos.pago_nodes.empty? complemento.pagos.remove_pago(index.to_i) end |
#sign ⇒ Object
88 89 90 91 92 93 94 95 96 97 98 99 100 |
# File 'lib/cfdi40/comprobante.rb', line 88 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
238 239 240 |
# File 'lib/cfdi40/comprobante.rb', line 238 def signed? !docxml.root.attributes["Sello"].nil? end |
#timbre ⇒ Object
300 301 302 303 304 |
# File 'lib/cfdi40/comprobante.rb', line 300 def timbre return nil unless defined?(@complemento) complemento.timbre end |
#to_s ⇒ Object
210 211 212 |
# File 'lib/cfdi40/comprobante.rb', line 210 def to_s to_xml end |
#to_xml ⇒ Object
214 215 216 217 218 219 220 221 |
# File 'lib/cfdi40/comprobante.rb', line 214 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_trasladados ⇒ Object
Shortcut to attribute TotalImpuestosTrasladados of impuestos node
249 250 251 252 253 |
# File 'lib/cfdi40/comprobante.rb', line 249 def total_impuestos_trasladados return nil unless impuestos_node impuestos_node.total_impuestos_trasladados end |
#total_iva ⇒ Object
282 283 284 285 286 |
# File 'lib/cfdi40/comprobante.rb', line 282 def total_iva return 0 unless traslados traslados.traslados_iva.map(&:importe).map(&:to_f).sum.round(2) end |
#total_iva_node ⇒ Object
275 276 277 278 279 280 |
# File 'lib/cfdi40/comprobante.rb', line 275 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
223 224 225 226 227 228 229 |
# File 'lib/cfdi40/comprobante.rb', line 223 def valid? schema_validator = SchemaValidator.new(to_s) return true if schema_validator.valid? @errors = schema_validator.errors @errors.empty? end |
#valid_signature? ⇒ Boolean
231 232 233 234 235 236 |
# File 'lib/cfdi40/comprobante.rb', line 231 def valid_signature? return false unless signed? signature_validator = SignatureValidator.new(to_xml) signature_validator.valid? end |