class MCFDI::Invoice

Invoice Class the most important class.

Attributes

addenda[RW]
canceled[RW]
certificate[RW]
certificate_number[RW]
complement[RW]
concepts[RW]
created_at[RW]
currency[RW]
exchange_rate[RW]
expedition_place[RW]
folio[RW]
payment_account_num[RW]
payment_conditions[RW]
payment_method[RW]
payment_way[RW]
proof_type[RW]
receptor[RW]
series[RW]
stamp[RW]
subtotal[RW]
taxes[RW]
total[RW]
transmitter[RW]
version[RW]

Public Class Methods

configure(options) click to toggle source

to change default values.

# File lib/m_cfdi/invoice.rb, line 41
def self.configure(options)
  @@defaults = Invoice.rmerge(@@defaults, options)
  @@defaults
end
new(args = {}) click to toggle source
# File lib/m_cfdi/invoice.rb, line 46
def initialize(args = {})
  args = @@defaults.merge(args)
  args.each do |key, value|
    method = "#{key}="
    next unless self.respond_to? method
    send(method, value)
  end
end

Public Instance Methods

addenda=(addenda) click to toggle source
# File lib/m_cfdi/invoice.rb, line 85
def addenda=(addenda)
  addenda = Addenda.new addenda unless addenda.is_a? Addenda
  @addenda = addenda
end
attributes() click to toggle source

return array of presented attributes. if any attribute does not have a value, will not be pushed to the array.

# File lib/m_cfdi/invoice.rb, line 115
def attributes
  a = [
    @version, @created_at, @proof_type, @payment_way, @payment_conditions,
    @subtotal.to_f, @exchange_rate, @currency, @total.to_f, @payment_method,
    @expedition_place, @payment_account_num]
  c = []
  a.each do |v|
    next unless v.present?
    c << v
  end
  c
end
concepts=(data) click to toggle source
# File lib/m_cfdi/invoice.rb, line 65
def concepts=(data)
  @concepts = []
  if data.is_a? Array
    data.map do |c|
      c = Concept.new(c) unless c.is_a? Concept
      @concepts << c
    end
  elsif data.is_a? Hash
    @concepts << Concept.new(data)
  elsif data.is_a? Concept
    @concepts << data
  end
  @concepts
end
created_at=(date) click to toggle source
# File lib/m_cfdi/invoice.rb, line 80
def created_at=(date)
  date = date.strftime('%FT%R:%S') unless date.is_a? String
  @created_at = date
end
original_string() click to toggle source

Cadena Original

# File lib/m_cfdi/invoice.rb, line 129
def original_string
  params = []

  attributes.each { |key| params << key }
  params += @transmitter.original_string
  params << @transmitter.fiscal_regime
  params += @receptor.original_string

  @concepts.each do |concept|
    params += concept.original_string
  end

  if @taxes.transferred.any?
    params += @taxes.transferred_original_string
    params += [@taxes.total_transferred]
  end

  if @taxes.detained.any?
    params += @taxes.detained_original_string
    params += [@taxes.total_detained]
  end

  params.select(&:present?)
  params.map! do |elem|
    if elem.is_a? Float
      elem = format('%.2f', elem)
    else
      elem = elem.to_s
    end
    elem
  end

  "||#{params.join('|')}||"
end
original_string_from_xslt() click to toggle source

save xml to file and generate original string from schema.

# File lib/m_cfdi/invoice.rb, line 91
def original_string_from_xslt
  # fail 'You have to specify schema!' unless # TODO: create configuration.
  xml = "#{Rails.root}/#{@transmitter.rfc}-#{@series}-#{@folio}.xml"
  save_xml(xml)
  sch = "#{Rails.root}/public/sat/schemas/cadenaoriginal_3_2.xslt"
  @original_string_from_xslt ||= `xsltproc #{sch} #{xml}`
  File.delete(xml) if File.exist?(xml)
  @original_string_from_xslt
end
qr_code() click to toggle source

return data of qr code image

# File lib/m_cfdi/invoice.rb, line 316
def qr_code
  t = "?re=#{@transmitter.rfc}"
  t += "&rr=#{@receptor.rfc}"
  t += "&tt=#{format('%6f', @total).rjust(17, '0')}"
  t += "&id=#{@complement.uuid}"
  img = RQRCode::QRCode.new(t).to_img
  img.resize(120, 120).to_data_url
end
receptor=(data) click to toggle source
# File lib/m_cfdi/invoice.rb, line 60
def receptor=(data)
  data = Entity.new(data) unless data.is_a? Entity
  @receptor = data
end
save_xml(name) click to toggle source

Save invoice xml to file

# File lib/m_cfdi/invoice.rb, line 102
def save_xml(name)
  file = File.new(name, 'w+')
  file.write(to_xml)
  file.close
end
to_xml() click to toggle source

return xml of the invoice.

# File lib/m_cfdi/invoice.rb, line 165
def to_xml
  ns = {
    'xmlns:cfdi' => 'http://www.sat.gob.mx/cfd/3',
    'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
    'xsi:schemaLocation' => 'http://www.sat.gob.mx/cfd/3 http://www.sat.gob.mx/sitio_internet/cfd/3/cfdv32.xsd',
    version: @version,
    folio: @folio,
    fecha: @created_at,
    formaDePago: @payment_way,
    subTotal: format('%.2f', @subtotal),
    Moneda: @currency,
    total: format('%.2f', @total),
    metodoDePago: @payment_method,
    tipoDeComprobante: @proof_type,
    LugarExpedicion: @expedition_place
  }
  ns[:condicionesDePago] = @payment_conditions if
    @payment_conditions.present?
  ns[:serie] = @series if @series
  ns[:TipoCambio] = @exchange_rate if @exchange_rate
  ns[:NumCtaPago] = @payment_account_num if @NumCtaPago.present?

  if @addenda
    ns["xmlns:#{@addenda.name}"] = @addenda.namespace
    ns['xsi:schemaLocation'] += (
      ' ' + [@addenda.namespace, @addenda.xsd].join(' '))
  end

  if @certificate_number
    ns[:noCertificado] = @certificate_number
    ns[:certificado] = @certificate
  end

  ns[:sello] = @stamp if @stamp

  @builder = Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
    xml.Comprobante(ns) do
      ins = xml.doc.root.add_namespace_definition('cfdi', 'http://www.sat.gob.mx/cfd/3')
      xml.doc.root.namespace = ins

      xml.Emisor(@transmitter.to_x) do
        xml.DomicilioFiscal(
          @transmitter.address.to_x.reject { |_, v| !v.present? })
        xml.ExpedidoEn(
          @transmitter.issued_in.to_x.reject { |_, v| !v.present? }
        ) if @transmitter.issued_in
        xml.RegimenFiscal(Regimen: @transmitter.fiscal_regime)
      end

      xml.Receptor(@receptor.to_x) do
        xml.Domicilio(@receptor.address.to_x.reject { |_, v| !v.present? })
      end

      xml.Conceptos do
        @concepts.each do |concept|
          concept_complement = nil

          cc = concept.to_x.select { |_, v| v.present? }

          cc = cc.map do |k, v|
            v = format('%.2f', v) if v.is_a? Float
            [k, v]
          end.to_h

          xml.Concepto(cc) { xml.ComplementoConcepto if concept_complement }
        end
      end

      if @taxes.count > 0
        tax_options = {}
        total_trans = format('%.2f', @taxes.total_transferred)
        total_detained = format('%.2f', @taxes.total_detained)
        tax_options[:totalImpuestosTrasladados] = total_trans if
          total_trans.to_i > 0
        tax_options[:totalImpuestosRetenidos] = total_detained if
          total_detained.to_i > 0
        xml.Impuestos(tax_options) do
          if @taxes.transferred.count > 0
            xml.Traslados do
              @taxes.transferred.each do |trans|
                xml.Traslado(impuesto: trans.tax, tasa: format('%.2f', trans.rate),
                             importe: format('%.2f', trans.import))
              end
            end
          end
          if @taxes.detained.count > 0
            xml.Retenciones do
              @taxes.detained.each do |det|
                xml.Retencion(impuesto: det.tax, tasa: format('%.2f', det.rate),
                              importe: format('%.2f', det.import))
              end
            end
          end
        end
      end

      xml.Complemento do
        if @complemento
          schema = 'http://www.sat.gob.mx/TimbreFiscalDigital '\
                   'http://www.sat.gob.mx/TimbreFiscalDigital/'\
                   'TimbreFiscalDigital.xsd'
          ns_tfd = {
            'xsi:schemaLocation' => schema,
            'xmlns:tfd' => 'http://www.sat.gob.mx/TimbreFiscalDigital',
            'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance'
          }
          xml['tfd'].TimbreFiscalDigital(@complemento.to_h.merge ns_tfd)
        end
      end

      if @addenda
        xml.Addenda do
          @addenda.data.each do |k, v|
            if v.is_a? Hash
              xml[@addenda.nombre].send(k, v)
            elsif v.is_a? Array
              xml[@addenda.nombre].send(k, v)
            else
              xml[@addenda.nombre].send(k, v)
            end
          end
        end
      end
    end
  end
  @builder.to_xml
end
total_to_words() click to toggle source

return string with total in words. Example: ( UN MIL CIENTO SESENTA PESOS 29/100 M.N. )

# File lib/m_cfdi/invoice.rb, line 310
def total_to_words
  decimal = format('%.2f', @total).split('.')
  "( #{@total.to_words.upcase} PESOS #{decimal[1]}/100 M.N. )"
end
transmitter=(data) click to toggle source
# File lib/m_cfdi/invoice.rb, line 55
def transmitter=(data)
  data = Entity.new(data) unless data.is_a? Entity
  @transmitter = data
end
validate_original_string() click to toggle source

Validate original string with the original string from schema

# File lib/m_cfdi/invoice.rb, line 109
def validate_original_string
  original_string == original_string_from_xslt
end
validate_xml() click to toggle source

validate generated xml with the schema

# File lib/m_cfdi/invoice.rb, line 294
def validate_xml
  errors = []
  # schema_file = "#{Rails.root}/public/sat/schemas/cfdv32.xsd"
  # xsd = Nokogiri::XML::Schema(File.read(schema_file))
  schema_file = 'http://www.sat.gob.mx/sitio_internet/cfd/3/cfdv32.xsd'
  xsd = Nokogiri::XML::Schema(open(schema_file))
  doc = Nokogiri::XML(to_xml)

  xsd.validate(doc).each do |error|
    errors << error.message
  end
  errors
end