Class: MCFDI::Invoice

Inherits:
Base
  • Object
show all
Defined in:
lib/m_cfdi/invoice.rb

Overview

Invoice Class the most important class.

Constant Summary collapse

@@defaults =

default values.

{
  tax_rate: 0.16, currency: 'pesos', version: '3.2', subtotal: 0.0,
  exchange_rate: 1, concepts: [], taxes: Taxes.new, proof_type: 'ingreso',
  total: 0.0
}

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Base

attr_accessor, attributes, #to_h

Constructor Details

#initialize(args = {}) ⇒ Invoice

Returns a new instance of Invoice.



46
47
48
49
50
51
52
53
# 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

Instance Attribute Details

#addendaObject

Returns the value of attribute addenda.



26
27
28
# File 'lib/m_cfdi/invoice.rb', line 26

def addenda
  @addenda
end

#canceledObject

Returns the value of attribute canceled.



26
27
28
# File 'lib/m_cfdi/invoice.rb', line 26

def canceled
  @canceled
end

#certificateObject

Returns the value of attribute certificate.



26
27
28
# File 'lib/m_cfdi/invoice.rb', line 26

def certificate
  @certificate
end

#certificate_numberObject

Returns the value of attribute certificate_number.



26
27
28
# File 'lib/m_cfdi/invoice.rb', line 26

def certificate_number
  @certificate_number
end

#complementObject

Returns the value of attribute complement.



26
27
28
# File 'lib/m_cfdi/invoice.rb', line 26

def complement
  @complement
end

#conceptsObject

Returns the value of attribute concepts.



26
27
28
# File 'lib/m_cfdi/invoice.rb', line 26

def concepts
  @concepts
end

#created_atObject

Returns the value of attribute created_at.



26
27
28
# File 'lib/m_cfdi/invoice.rb', line 26

def created_at
  @created_at
end

#currencyObject

Returns the value of attribute currency.



26
27
28
# File 'lib/m_cfdi/invoice.rb', line 26

def currency
  @currency
end

#exchange_rateObject

Returns the value of attribute exchange_rate.



26
27
28
# File 'lib/m_cfdi/invoice.rb', line 26

def exchange_rate
  @exchange_rate
end

#expedition_placeObject

Returns the value of attribute expedition_place.



26
27
28
# File 'lib/m_cfdi/invoice.rb', line 26

def expedition_place
  @expedition_place
end

#folioObject

Returns the value of attribute folio.



26
27
28
# File 'lib/m_cfdi/invoice.rb', line 26

def folio
  @folio
end

#payment_account_numObject

Returns the value of attribute payment_account_num.



26
27
28
# File 'lib/m_cfdi/invoice.rb', line 26

def 
  @payment_account_num
end

#payment_conditionsObject

Returns the value of attribute payment_conditions.



26
27
28
# File 'lib/m_cfdi/invoice.rb', line 26

def payment_conditions
  @payment_conditions
end

#payment_methodObject

Returns the value of attribute payment_method.



26
27
28
# File 'lib/m_cfdi/invoice.rb', line 26

def payment_method
  @payment_method
end

#payment_wayObject

Returns the value of attribute payment_way.



26
27
28
# File 'lib/m_cfdi/invoice.rb', line 26

def payment_way
  @payment_way
end

#proof_typeObject

Returns the value of attribute proof_type.



26
27
28
# File 'lib/m_cfdi/invoice.rb', line 26

def proof_type
  @proof_type
end

#receptorObject

Returns the value of attribute receptor.



26
27
28
# File 'lib/m_cfdi/invoice.rb', line 26

def receptor
  @receptor
end

#seriesObject

Returns the value of attribute series.



26
27
28
# File 'lib/m_cfdi/invoice.rb', line 26

def series
  @series
end

#stampObject

Returns the value of attribute stamp.



26
27
28
# File 'lib/m_cfdi/invoice.rb', line 26

def stamp
  @stamp
end

#subtotalObject

Returns the value of attribute subtotal.



26
27
28
# File 'lib/m_cfdi/invoice.rb', line 26

def subtotal
  @subtotal
end

#taxesObject

Returns the value of attribute taxes.



26
27
28
# File 'lib/m_cfdi/invoice.rb', line 26

def taxes
  @taxes
end

#totalObject

Returns the value of attribute total.



26
27
28
# File 'lib/m_cfdi/invoice.rb', line 26

def total
  @total
end

#transmitterObject

Returns the value of attribute transmitter.



26
27
28
# File 'lib/m_cfdi/invoice.rb', line 26

def transmitter
  @transmitter
end

#versionObject

Returns the value of attribute version.



26
27
28
# File 'lib/m_cfdi/invoice.rb', line 26

def version
  @version
end

Class Method Details

.configure(options) ⇒ Object

to change default values.



41
42
43
44
# File 'lib/m_cfdi/invoice.rb', line 41

def self.configure(options)
  @@defaults = Invoice.rmerge(@@defaults, options)
  @@defaults
end

Instance Method Details

#attributesObject

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



115
116
117
118
119
120
121
122
123
124
125
126
# 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

#original_stringObject

Cadena Original



129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# 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_xsltObject

save xml to file and generate original string from schema.



91
92
93
94
95
96
97
98
99
# 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_codeObject

return data of qr code image



314
315
316
317
318
319
320
321
# File 'lib/m_cfdi/invoice.rb', line 314

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

#save_xml(name) ⇒ Object

Save invoice xml to file



102
103
104
105
106
# 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_xmlObject

return xml of the invoice.



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
# 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_wordsObject

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



308
309
310
311
# File 'lib/m_cfdi/invoice.rb', line 308

def total_to_words
  decimal = format('%.2f', @total).split('.')
  "( #{@total.to_words.upcase} PESOS #{decimal[1]}/100 M.N. )"
end

#validate_original_stringObject

Validate original string with the original string from schema



109
110
111
# File 'lib/m_cfdi/invoice.rb', line 109

def validate_original_string
  original_string == original_string_from_xslt
end

#validate_xmlObject

validate generated xml with the schema



294
295
296
297
298
299
300
301
302
303
304
# 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))
  doc = Nokogiri::XML(to_xml)

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