Bläddra i källkod

Generate General Public Invoice

Jacqueline Maldonado 7 år sedan
förälder
incheckning
478cac4bf5
34 ändrade filer med 1123 tillägg och 154 borttagningar
  1. 9 0
      Gemfile
  2. 26 0
      Gemfile.lock
  3. 120 0
      app/controllers/application_controller.rb
  4. 211 6
      app/controllers/cash_outs_controller.rb
  5. 1 1
      app/controllers/customers_controller.rb
  6. 1 1
      app/controllers/pointsales_controller.rb
  7. 1 1
      app/controllers/units_controller.rb
  8. 8 0
      app/helpers/application_helper.rb
  9. 9 0
      app/models/invoice_detail.rb
  10. 85 77
      app/views/billing_information/_form.html.erb
  11. 36 0
      app/views/cash_outs/_general_public_invoice.html.erb
  12. 138 0
      app/views/cash_outs/cfdi_gpi.pdf.erb
  13. 5 0
      app/views/cash_outs/general_public_invoice.js.erb
  14. 1 0
      app/views/cash_outs/generate_gpi.js.erb
  15. 32 22
      app/views/cash_outs/index.html.erb
  16. 5 0
      app/views/cash_outs/print_invoice.js.erb
  17. 85 0
      app/views/layouts/cfdi.html.erb.html
  18. 36 0
      app/views/pointsales/_form.html.erb
  19. 64 0
      config/application.rb
  20. 3 0
      config/routes.rb
  21. 7 0
      db/migrate/20181213183811_add_sales_invoice_column.rb
  22. 17 0
      db/migrate/20181213185244_create_invoice_details.rb
  23. 8 0
      db/migrate/20181213203152_add_invoice_columns_to_pointsale.rb
  24. 5 0
      db/migrate/20181213203326_add_column_to_billing_information.rb
  25. 5 0
      db/migrate/20181217171552_add_key_column_to_payment_method.rb
  26. 5 0
      db/migrate/20181217172147_add_key_column_to_products.rb
  27. 5 0
      db/migrate/20181217175547_add_key_column_to_unit.rb
  28. 72 45
      db/schema.rb
  29. 37 1
      db/seeds.rb
  30. BIN
      facturacion/aaa010101aaa/aaa010101aaa__csd_01.cer
  31. BIN
      facturacion/aaa010101aaa/aaa010101aaa__csd_01.key
  32. 3 0
      facturacion/cancelado.xml
  33. 3 0
      facturacion/generaxml.rb
  34. 80 0
      facturacion/timbrado.xml

+ 9 - 0
Gemfile

@@ -38,6 +38,15 @@ gem 'jbuilder', '~> 2.0'
 # Gema para modificar la forma de mostrar el error resaltado en los input de la forma
 gem 'nokogiri'
 
+# dependencia de timbrado
+gem 'savon'
+
+# timbrado cfdi ProFact
+gem 'timbradocfdi', '~> 0.0.2'
+
+# numeros a letras CFDI
+gem 'number_to_words', '~> 1.2', '>= 1.2.1'
+
 ### javascripts y css
 # Use jquery as the JavaScript library
 gem 'sprockets-rails', require: 'sprockets/railtie'

+ 26 - 0
Gemfile.lock

@@ -48,6 +48,9 @@ GEM
       tzinfo (~> 1.1)
     acts-as-taggable-on (3.5.0)
       activerecord (>= 3.2, < 5)
+    akami (1.3.1)
+      gyoku (>= 0.4.0)
+      nokogiri
     arel (6.0.3)
     ast (2.3.0)
     audited (4.2.0)
@@ -118,6 +121,11 @@ GEM
       sass (>= 3.2)
     globalid (0.3.6)
       activesupport (>= 4.1.0)
+    gyoku (1.3.1)
+      builder (>= 2.1.2)
+    httpi (2.4.2)
+      rack
+      socksify
     i18n (0.7.0)
     iniparse (1.4.2)
     jbuilder (2.3.2)
@@ -148,6 +156,8 @@ GEM
     multi_json (1.11.2)
     nokogiri (1.6.6.2)
       mini_portile (~> 0.6.0)
+    nori (2.6.0)
+    number_to_words (1.2.1)
     orm_adapter (0.5.0)
     overcommit (0.37.0)
       childprocess (~> 0.5.8)
@@ -227,6 +237,14 @@ GEM
       sprockets (>= 2.8, < 4.0)
       sprockets-rails (>= 2.0, < 4.0)
       tilt (>= 1.1, < 3)
+    savon (2.11.1)
+      akami (~> 1.2)
+      builder (>= 2.1.2)
+      gyoku (~> 1.2)
+      httpi (~> 2.3)
+      nokogiri (>= 1.4.0)
+      nori (~> 2.4)
+      wasabi (~> 3.4)
     select2-rails (4.0.1)
       thor (~> 0.14)
     simple-line-icons-rails (0.0.1)
@@ -236,6 +254,7 @@ GEM
     simple_navigation_renderers (1.0.2)
       simple-navigation (~> 3.11)
     slop (3.6.0)
+    socksify (1.7.1)
     spring (1.4.0)
     sprockets (3.4.0)
       rack (> 1, < 3)
@@ -246,6 +265,7 @@ GEM
     thor (0.19.1)
     thread_safe (0.3.5)
     tilt (2.0.2)
+    timbradocfdi (0.0.2)
     toastr_rails (2.1.1)
     turbolinks (2.5.3)
       coffee-rails
@@ -263,6 +283,9 @@ GEM
     unicode-display_width (1.1.2)
     warden (1.2.4)
       rack (>= 1.0)
+    wasabi (3.5.0)
+      httpi (~> 2.0)
+      nokogiri (>= 1.4.2)
     web-console (2.2.1)
       activemodel (>= 4.0)
       binding_of_caller (>= 0.7.2)
@@ -306,6 +329,7 @@ DEPENDENCIES
   mini_magick
   momentjs-rails (>= 2.9.0)
   nokogiri
+  number_to_words (~> 1.2, >= 1.2.1)
   overcommit
   paperclip
   pdfjs_viewer-rails
@@ -318,11 +342,13 @@ DEPENDENCIES
   responders
   rubocop (~> 0.46.0)
   sass-rails (~> 5.0)
+  savon
   select2-rails
   simple-line-icons-rails
   simple_navigation_renderers
   spring
   sprockets-rails
+  timbradocfdi (~> 0.0.2)
   toastr_rails
   turbolinks
   twitter-typeahead-rails

+ 120 - 0
app/controllers/application_controller.rb

@@ -265,6 +265,126 @@ class ApplicationController < ActionController::Base
     render json: products
   end
 
+  ## ======= invoice methods =======
+  def check_directorys(t, move_type)
+    unless Dir.exist?(Rails.public_path.join("invoice", "cfdi")) # carpeta invoice
+      Dir.mkdir(Rails.public_path.join("invoice", "cfdi"))
+    end
+    unless Dir.exist?(Rails.public_path.join("invoice/cfdi", t.strftime('%Y'))) # carpeta cfdi
+      Dir.mkdir(Rails.public_path.join("invoice/cfdi", t.strftime("%Y")))
+    end
+    unless Dir.exist?(Rails.public_path.join("invoice/cfdi/#{t.strftime('%Y')}", t.strftime("%m"))) # carpeta del año
+      Dir.mkdir(Rails.public_path.join("invoice/cfdi/#{t.strftime('%Y')}", t.strftime("%m")))
+    end
+    unless Dir.exist?(Rails.public_path.join("invoice/cfdi/#{t.strftime('%Y')}", t.strftime("%m"), "xml")) # carpeta del mes
+      Dir.mkdir(Rails.public_path.join("invoice/cfdi/#{t.strftime('%Y')}/#{t.strftime('%m')}", "xml"))
+    end
+    unless Dir.exist?(Rails.public_path.join("invoice/cfdi/#{t.strftime('%Y')}", t.strftime("%m"), "xml", move_type)) # carpeta de tipo de factura
+      Dir.mkdir(Rails.public_path.join("invoice/cfdi/#{t.strftime('%Y')}/#{t.strftime('%m')}/xml", move_type))
+    end
+    unless Dir.exist?(Rails.public_path.join("invoice/cfdi/#{t.strftime('%Y')}", t.strftime("%m"), "pdf")) # carpeta para pdf
+      Dir.mkdir(Rails.public_path.join("invoice/cfdi/#{t.strftime('%Y')}/#{t.strftime('%m')}", "pdf"))
+    end
+    unless Dir.exist?(Rails.public_path.join("invoice/cfdi/#{t.strftime('%Y')}", t.strftime("%m"), "pdf", move_type))
+      Dir.mkdir(Rails.public_path.join("invoice/cfdi/#{t.strftime('%Y')}/#{t.strftime('%m')}/pdf", move_type))
+    end
+    unless Dir.exist?(Rails.public_path.join("invoice/cfdi/#{t.strftime('%Y')}", t.strftime("%m"), "qr"))
+      Dir.mkdir(Rails.public_path.join("invoice/cfdi/#{t.strftime('%Y')}/#{t.strftime('%m')}", "qr"))
+    end
+    unless Dir.exist?(Rails.public_path.join("invoice/cfdi/#{t.strftime('%Y')}", t.strftime("%m"), "qr", move_type))
+      Dir.mkdir(Rails.public_path.join("invoice/cfdi/#{t.strftime('%Y')}/#{t.strftime('%m')}/qr", move_type))
+    end
+  end
+
+  def check_stamper_status
+    response = 500
+    uri = URI(Rails.application.config.issuing['stamper_uri'])
+    use_ssl = true
+    http = Net::HTTP.new(uri.host, uri.port)
+    http.use_ssl = use_ssl
+    http.verify_mode = OpenSSL::SSL::VERIFY_NONE
+    http.start do
+      req = Net::HTTP::Get.new("/")
+      response = http.request(req)
+      return response.code
+    end
+  rescue
+    return 500
+  end
+
+  def create_invoice_qr(t, file_name, binary_qr, move_type)
+    File.open(Rails.public_path.join("invoice/cfdi/#{t.strftime('%Y')}/#{t.strftime('%m')}/qr/#{move_type}", file_name), 'wb') do |file|
+      binary_data = Base64.decode64(binary_qr)
+      file << binary_data
+    end
+  end
+
+  def create_xml_stamped(t, file_name, stamped_xml, move_type)
+    File.open(Rails.public_path.join("invoice/cfdi/#{t.strftime('%Y')}/#{t.strftime('%m')}/xml/#{move_type}", file_name), 'w') do |file|
+      file << stamped_xml << "\n"
+    end
+  end
+
+  def get_attributes(created_at, amount, total, folio, payment_method_key, postal_code, serie, comprobante, pay_method)
+    attributes = {
+      "Version" => Rails.application.config.issuing['Version'].to_s,
+      "Serie" => serie,
+      "Folio" => folio,
+      "Fecha" => created_at,
+      "FormaPago" => payment_method_key,
+      "SubTotal" => amount,
+      "Moneda" => "MXN",
+      "Total" => total,
+      "TipoDeComprobante" => comprobante,
+      "MetodoPago" => pay_method,
+      "LugarExpedicion" => postal_code.to_s
+    }
+  end
+
+  def get_namespaces(type)
+    if type == "invoice" # factura
+      namespaces = {
+        "xmlns:tfd" => "http://www.sat.gob.mx/TimbreFiscalDigital",
+        "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/cfdv33.xsd"
+      }
+    elsif type == "complement" # complemento de pago
+      namespaces = {
+        "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/cfdv33.xsd http://www.sat.gob.mx/Pagos http://www.sat.gob.mx/sitio_internet/cfd/Pagos/Pagos10.xsd"
+      }
+    end
+  end
+
+  def remove_invoice_files(uuid, file_id, url)
+    unless url.blank?
+      if File.exist?(Rails.root.join(url, "pdf", "#{uuid}_#{file_id}.pdf"))
+        File.delete(Rails.root.join(url, "pdf", "#{uuid}_#{file_id}.pdf"))
+      end
+      if File.exist?(Rails.root.join(url, "xml", "#{uuid}_#{file_id}.xml"))
+        File.delete(Rails.root.join(url, "xml", "#{uuid}_#{file_id}.xml"))
+      end
+    end
+  end
+
+  def remove_xml_origin(save_path)
+    if File.exist?(save_path)
+      File.delete(save_path)
+    end
+  end
+
+  def send_invoice(mail_attributes)
+    mail_response = InvoiceMailer.invoice_email_in(mail_attributes["name"], mail_attributes["email"], mail_attributes["attachments_name"], mail_attributes["attachments_url"]).deliver_now!
+    if mail_response.status.to_i == 250
+      return " Factura electrónica enviada exitosamente"
+    else
+      return " Error al enviar la factura electrónica"
+    end
+  rescue
+    return " [Revisar configuración de servicio de email]"
+  end
+  ## /////// invoice methods ///////
+
   protected
 
   def configure_permitted_parameters

+ 211 - 6
app/controllers/cash_outs_controller.rb

@@ -6,12 +6,11 @@ class CashOutsController < ApplicationController
   add_breadcrumb I18n.t("breadcrumbs." + controller_name), :cash_outs_path
   add_breadcrumb "Nueva Corte de caja ", :new_cash_out_path, only: :new
   add_breadcrumb "Detalle del corte de caja ", :cash_out_path, only: :show
-  add_breadcrumb "Editar corte de caja ", :edit_cash_out_path, only: :edit
   add_breadcrumb "Cajas abiertas ", :opened_cash_registers_path, only: :opened_cash_registers
 
-  before_action :set_cash_out, only: [:show, :edit, :update, :destroy]
+  before_action :set_cash_out, only: [:show]
   before_action :set_data, only: [:new, :create]
-  before_action :get_filters, only: [:index, :show, :edit, :new]
+  before_action :get_filters, only: [:index, :show, :new]
   # GET /cash_outs.json
   def index
     case current_user.usertype
@@ -27,8 +26,8 @@ class CashOutsController < ApplicationController
   # GET /cash_outs/1
   # GET /cash_outs/1.json
   def show
-    @incomings = CashRegistersMove.where("open_cash_register_id = (?) and move_type = '1' and status = 1", @cash_out.open_cash_register.id).order('created_at')
-    @outgoings = CashRegistersMove.where("open_cash_register_id = (?) and move_type = '0' and status = 1", @cash_out.open_cash_register.id).order('created_at')
+    @incomings = CashRegistersMove.includes(:open_cash_register, :credit_payment, :payment_method).where(open_cash_register_id: @cash_out.open_cash_register.id, move_type: 1, status: 1).order('created_at')
+    @outgoings = CashRegistersMove.includes(:payment_method).where(open_cash_register_id: @cash_out.open_cash_register.id, move_type: 0, status: 1).order('created_at')
 
     @sales_total = @incomings.sum(:quantity)
     @expenses_total = @outgoings.sum(:quantity)
@@ -141,6 +140,202 @@ class CashOutsController < ApplicationController
     end
   end
 
+  def general_public_invoice
+    @cash_out = CashOut.find(params[:cash_out_id])
+    crms = CashRegistersMove.where(open_cash_register_id: @cash_out.open_cash_register_id, move_type: 1, status: 1).map(&:sale_id)
+    @sales = Sale.where(id: crms, saletype: 1, require_invoice: false).order(id: :asc)
+    @sales_amount = @sales.sum(:amount)
+    @sales_total = @sales.sum(:total)
+    @sales_tax = @sales.sum(:tax)
+
+    respond_to do |format|
+      format.js
+    end
+  end
+
+  def generate_gpi
+    cash_out = CashOut.find(params[:cash_out_id])
+    subtotal = params[:amount_for_invoice].to_f
+    total_tax = params[:tax_for_invoice].to_f
+    total = subtotal + total_tax
+    sales = CashRegistersMove.where(open_cash_register_id: cash_out.open_cash_register_id, move_type: 1, status: 1).map(&:sale_id)
+    result = invoice_cash_out(cash_out, subtotal, total_tax, total.round(2), sales)
+    respond_to do |format|
+      flash[:notice] = result["invoice"]
+      format.js
+    end
+  end
+
+  def invoice_cash_out(cash_out, subtotal, total_tax, total, sales)
+    customer = Customer.find_by(is_public: true) # => publico general
+    invoice = ''
+    response = Array.new
+    concepts = []
+    sales = Sale.where(id: sales)
+    sales.each do |sale|
+      concepts << {
+        "Quantity" => format('%0.2f', 1).to_s,
+        "UnitKey" => "ACT",
+        "IdNumber" => sale.sale_code,
+        "ProductKey" => "01010101",
+        "Description" => "Venta Publico General - #{sale.sale_code}",
+        "Price" => format('%0.2f', sale.amount).to_s,
+        "Amount" => format('%0.2f', sale.amount).to_s,
+        "Tax" => format('%0.2f', sale.tax).to_s
+      }
+    end
+    save_path = false
+    save_path = create_invoice(cash_out, subtotal, total_tax, total, concepts)
+
+    xml = true
+    stamp = true
+    if save_path.blank? || save_path == false
+      invoice = "No se pudo generar el XML para la factura electrónica"
+      xml = false
+    end
+
+    if check_stamper_status.to_i != 200
+      stamp = false
+    end
+
+    if xml == true && stamp == true
+      response = stamper(save_path, cash_out, customer, concepts)
+    end
+
+    if xml == true && stamp == false
+      response = {
+        "code" => 1,
+        "message" => "Servicio de timbrado no esta disponible"
+      }
+    end
+
+    if xml == false && stamp == true
+      response = {
+        "code" => 2,
+        "message" => "XML no se pudo generar"
+      }
+    end
+
+    if xml == false && stamp == false
+      response = {
+        "code" => 3,
+        "message" => "XML no se pudo generar y servicio de timbrado no disponible"
+      }
+    end
+
+    pdf_path = nil
+    if response["code"].to_i.zero?
+      pdf_path = response["message"]
+      invoice = " Factura electrónica generada."
+    else
+      invoice = "#{response['code']} : #{response['message']}"
+    end
+    result = { "invoice" => invoice, "pdf_path" => pdf_path }
+    result
+  end
+
+  def create_invoice(cash_out, subtotal, total_tax, total, concepts)
+    pointsale = current_user.pointsale
+    save_path = Rails.public_path.join('invoice/xml_origin', "cfdi_gpi_#{cash_out.id}.xml")
+    builder = Nokogiri::XML::Builder.new do |xml|
+      xml.Comprobante(get_namespaces("invoice").merge(get_gpi_attributes(cash_out, subtotal, total, pointsale.postal_code))) do
+        cfdi = xml.doc.root.add_namespace_definition('cfdi', 'http://www.sat.gob.mx/cfd/3')
+        xml.doc.root.namespace = cfdi
+        xml.Emisor(Rfc: pointsale.federal_taxpayer_registration.to_s, Nombre: pointsale.business_name.to_s, RegimenFiscal: pointsale.tax_regime.to_s)
+        xml.Receptor(Rfc: "XAXX010101000", UsoCFDI: "P01")
+        xml.Conceptos do
+          concepts.each do |concept|
+            xml.Concepto(ClaveProdServ: concept['ProductKey'].to_s, ClaveUnidad: concept['UnitKey'].to_s, Cantidad: concept['Quantity'].to_s, NoIdentificacion: concept['IdNumber'].to_s, Descripcion: concept['Description'].to_s, ValorUnitario: concept['Price'].to_s, Importe: concept['Amount'].to_s) do
+              if concept['Tax'].to_f > 0.00
+                xml.Impuestos { xml.Traslados { xml.Traslado(TipoFactor: "Tasa", TasaOCuota: "#{@pos_config.tax_percent / 100}0000", Impuesto: "002", Base: concept['Amount'].to_s, Importe: format('%0.2f', concept['Tax']).to_s) } }
+              end
+            end
+          end
+        end
+        if total_tax > 0.00
+          xml.Impuestos(TotalImpuestosTrasladados: format('%0.2f', total_tax).to_s) { xml.Traslados { xml.Traslado(TipoFactor: "Tasa", TasaOCuota: "#{@pos_config.tax_percent / 100}0000", Impuesto: Rails.application.config.issuing['Impuesto'].to_s, Importe: format('%0.2f', total_tax).to_s) } }
+        end
+      end
+    end
+
+    File.open(save_path, 'w') do |file|
+      file << builder.to_xml
+    end
+    save_path
+  rescue
+    return false
+  end
+
+  def get_gpi_attributes(cash_out, subtotal, total, postal_code)
+    attributes = {
+      "Version" => Rails.application.config.issuing['Version'].to_s,
+      "Serie" => Rails.application.config.issuing['SerieContado'].to_s,
+      # "Folio" => "",
+      "Fecha" => cash_out.created_at.strftime("%FT%T").to_s,
+      "FormaPago" => "01",
+      "SubTotal" => format('%0.2f', subtotal).to_s,
+      "Moneda" => "MXN",
+      "Total" => format('%0.2f', total).to_s,
+      "TipoDeComprobante" => Rails.application.config.issuing['CveIngreso'].to_s,
+      "MetodoPago" => Rails.application.config.issuing['MetodoPago'].to_s,
+      "LugarExpedicion" => postal_code.to_s
+    }
+  end
+
+  def stamper(save_path, cash_out, customer, concepts)
+    response = Array.new
+    @stamper = Timbradocfdi::Generator.new(Rails.application.config.issuing['Password'].to_s)
+    t = Time.now
+    check_directorys(t, "ingreso")
+    result = @stamper.timbraCFDI(save_path, cash_out.id).to_json
+    result_json = JSON.parse(result)
+    if result_json['code'].to_i.zero?
+      cash_out_id = cash_out.id
+      @doc = Nokogiri::XML(result_json['xml'])
+      uuid = @doc.xpath("//@UUID")
+      create_xml_stamped(t, "#{uuid}_gpi_#{cash_out_id}.xml", result_json['xml'], "ingreso")
+      create_invoice_qr(t, "qr_#{uuid}_gpi_#{cash_out_id}.png", result_json['qr'], "ingreso")
+      remove_xml_origin(save_path)
+      cash_out.update(invoice_num: uuid)
+      cfdi_type = @doc.xpath("//cfdi:Comprobante//@TipoDeComprobante")
+      invoice_reason = "Venta Publico General"
+      rfc_receptor = @doc.xpath("//cfdi:Receptor//@Rfc")
+      generating_date = @doc.xpath("//@Fecha").to_s
+      stamping_date = @doc.xpath("//@FechaTimbrado").to_s
+      storing_url = "invoice/cfdi/#{t.strftime('%Y')}/#{t.strftime('%m')}/"
+      InvoiceDetail.create(uuid: uuid, cfdi_type: cfdi_type, invoice_reason: invoice_reason, rfc_receptor: rfc_receptor, generating_date: generating_date, stamping_date: stamping_date, storing_url: storing_url)
+      qr_path = "invoice/cfdi/#{t.strftime('%Y')}/#{t.strftime('%m')}/qr/ingreso/qr_#{uuid}_gpi_#{cash_out_id}.png"
+      create_pdf_invoice(t, uuid, cash_out_id, concepts, @doc, result_json['details'], qr_path)
+      pdf_path = "invoice/cfdi/#{t.strftime('%Y')}/#{t.strftime('%m')}/pdf/ingreso/#{uuid}_gpi_#{cash_out_id}.pdf"
+      response = {
+        "code" => result_json['code'].to_i,
+        "message" => pdf_path
+      }
+    else
+      response = {
+        "code" => result_json['code'].to_i,
+        "message" => result_json['message']
+      }
+    end
+  rescue
+    response = {
+      "code" => -1,
+      "message" => "Ha ocurrido un error inesperado durante el timbrado de la factura"
+    }
+  end
+
+  def print_invoice
+    uuid = CashOut.find(params[:cash_out_id]).invoice_num
+    storing_url = InvoiceDetail.find_by(uuid: uuid).storing_url
+    respond_to do |format|
+      unless storing_url.blank?
+        pdf_path = "#{storing_url}pdf/ingreso/#{uuid}_gpi_#{params[:cash_out_id]}.pdf"
+        format.js { render action: "print_invoice", locals: { pdf_path: pdf_path } }
+      end
+      format.json { head :no_content }
+    end
+  end
+
   private
 
   def set_data
@@ -177,9 +372,19 @@ class CashOutsController < ApplicationController
     end
   end
 
+  def create_pdf_invoice(t, uuid, cash_out_id, concepts, doc, details, qr_path)
+    unless File.exist?(Rails.public_path.join("invoice/cfdi/#{t.strftime('%Y')}/#{t.strftime('%m')}/pdf/ingreso", "#{uuid}_gpi_#{cash_out_id}.pdf"))
+      pdf = render_to_string pdf: "#{uuid}_gpi_#{cash_out_id}", template: "cash_outs/cfdi_gpi.pdf.erb", layout: 'cfdi.html.erb', locals: { doc: doc, uuid: uuid, details: details, qr_path: qr_path, user: current_user, concepts: concepts, pos_config: @pos_config }, show_as_html: params.key?('debug'), page_width: '216mm', page_height: '279mm'
+      save_path = Rails.public_path.join("invoice/cfdi/#{t.strftime('%Y')}/#{t.strftime('%m')}/pdf/ingreso", "#{uuid}_gpi_#{cash_out_id}.pdf")
+      File.open(save_path, 'wb') do |file|
+        file << pdf
+      end
+    end
+  end
+
   # Use callbacks to share common setup or constraints between actions.
   def set_cash_out
-    @cash_out = CashOut.find(params[:id])
+    @cash_out = CashOut.includes(:cash_out_details, cash_out_details: :payment_method).find(params[:id])
   end
 
   def get_filters

+ 1 - 1
app/controllers/customers_controller.rb

@@ -130,6 +130,6 @@ class CustomersController < ApplicationController
 
   # Never trust parameters from the scary internet, only allow the white list through.
   def customer_params
-    params.require(:customer).permit(:nick_name, :phone, :email, :credit, :credit_limit, :time_limit, :notes, :status, billing_information_attributes: [:id, :name, :rfc, :address, :num_ext, :num_int, :zipcode, :state_id, :county_id, :city, :suburb], contact_attributes: [:id, :name, :last_name, :phone, :email])
+    params.require(:customer).permit(:nick_name, :phone, :email, :credit, :credit_limit, :time_limit, :notes, :status, billing_information_attributes: [:id, :name, :rfc, :address, :num_ext, :num_int, :zipcode, :state_id, :county_id, :city, :suburb, :cfdi_use_key], contact_attributes: [:id, :name, :last_name, :phone, :email])
   end
 end

+ 1 - 1
app/controllers/pointsales_controller.rb

@@ -207,6 +207,6 @@ class PointsalesController < ApplicationController
   end
 
   def pointsale_params
-    params.require(:pointsale).permit(:name, :address, :notes, :status, :prefix, :img_pointsale, :img_pointsale_cache, :ticket_footer, :haggle_percent, users_attributes: [:userid, :first_name, :last_name, :password, :email, :password_confirmation])
+    params.require(:pointsale).permit(:name, :address, :notes, :status, :prefix, :img_pointsale, :img_pointsale_cache, :ticket_footer, :haggle_percent, :tax_regime, :business_name, :federal_taxpayer_registration, :postal_code, users_attributes: [:userid, :first_name, :last_name, :password, :email, :password_confirmation])
   end
 end

+ 1 - 1
app/controllers/units_controller.rb

@@ -75,6 +75,6 @@ class UnitsController < ApplicationController
   end
 
   def unit_params
-    params.require(:unit).permit(:unit, :status)
+    params.require(:unit).permit(:unit, :status, :unit_key)
   end
 end

+ 8 - 0
app/helpers/application_helper.rb

@@ -11,4 +11,12 @@ module ApplicationHelper
   def wicked_pdf_barcode_image(img, options)
     image_tag "file://#{Rails.root.join('public', 'barcodes', img)}", options
   end
+
+  def wicked_pdf_image_tag_invoice(img, options = {})
+    image_tag "file://#{Rails.root.join('public', 'images', img)}", options
+  end
+
+  def wicked_pdf_image_tag_qr(img, options = {})
+    image_tag "file://#{Rails.root.join('public', img)}", options
+  end
 end

+ 9 - 0
app/models/invoice_detail.rb

@@ -0,0 +1,9 @@
+class InvoiceDetail < ActiveRecord::Base
+  # has_and_belongs_to_many :credits, join_table: :invoice_credits
+
+  audited
+  enum status: [:cancelled, :active]
+
+  scope :activas, -> { where(invoice_details: { status: 1 }) }
+  scope :complementos, -> { activas.where(cfdi_type: "P") }
+end

+ 85 - 77
app/views/billing_information/_form.html.erb

@@ -1,79 +1,87 @@
-			<h4 class="form-section">Información de facturación <small class="text-muted"> opcional</small></h4>
-			<div class="row">
-				<div class="form-group">
-					<%= b.label :name, "Razón social", {:class=>"col-md-3 control-label"} do %> Razón Social <span class="required">*</span>
-					<% end %> 
-					<div class="col-md-9">
-						<%= b.text_field :name, {:class=>"form-control input-xlarge"} %>
-					</div>
-				</div>
-				<div class="form-group">
-					<%= b.label :rfc, "RFC", {:class=>"col-md-3 control-label"} do %> RFC <span class="required">*</span>
-					<% end %> 
-					<div class="col-md-9">
-						<%= b.text_field :rfc, {:class=>"form-control input-small"} %>
-					</div>
-				</div>
-				<div class="form-group">
-					<%= b.label :address,  {:class=>"col-md-3 control-label"} do %> Dirección <!-- <span class="required">*</span> -->
-					<% end %> 
-					<div class="col-md-9">
-						<%= b.text_field :address, {:class=>"form-control input-xlarge"} %>
-					</div>
-				</div>
-				<div class="form-group">
-					<%= b.label :num_ext,  {:class=>"col-md-3 control-label"} do %> No. Exterior <!-- <span class="required">*</span> -->
-					<% end %> 
-					<div class="col-md-9">
-						<%= b.text_field :num_ext, {:class=>"form-control input-small"} %>
-					</div>
-				</div>
-				<div class="form-group">
-					<%= b.label :num_int, "No. Interior", {:class=>"col-md-3 control-label"} %> 
-					<div class="col-md-9">
-						<%= b.text_field :num_int, {:class=>"form-control input-small"} %>
-					</div>
-				</div>
-				<div class="form-group">
-					<%= b.label :zipcode, {:class=>"col-md-3 control-label"} do %> Código postal <span class="required">*</span>
-					<% end %> 
-					<div class="col-md-9">
-						<%= b.text_field :zipcode, {:class=>"form-control input-small mask_number", :value => ((info.billing_information.nil? || info.billing_information.zipcode == 0) ? '' : info.billing_information.zipcode) } %>
-					</div>
-				</div>
+<h4 class="form-section">Información de facturación <small class="text-muted"> opcional</small></h4>
+<div class="row">
+  <div class="form-group">
+    <%= b.label :name, "Razón social", { class: "col-md-3 control-label" } do %> Razón Social <span class="required">*</span>
+    <% end %>
+    <div class="col-md-9">
+      <%= b.text_field :name, { class: "form-control input-xlarge" } %>
+    </div>
+  </div>
+  <div class="form-group">
+    <%= b.label :cfdi_use_key, "UsoCFDI", { class: "col-md-3 control-label" } do %>
+      Uso de CFDI <span class="required">*</span>
+    <% end %>
+    <div class="col-md-9">
+      <%= b.select :cfdi_use_key, Rails.application.config.cfdiuse, { include_blank: "Seleccione", selected: ("Seleccione") }, { class: 'form-control input-xlarge' } %>
+    </div>
+  </div>
+  <div class="form-group">
+    <%= b.label :rfc, "RFC", { class: "col-md-3 control-label" } do %> RFC <span class="required">*</span>
+    <% end %>
+    <div class="col-md-9">
+      <%= b.text_field :rfc, { class: "form-control input-medium" } %>
+    </div>
+  </div>
+  <div class="form-group">
+    <%= b.label :address,  { class: "col-md-3 control-label" } do %> Dirección <!-- <span class="required">*</span> -->
+    <% end %>
+    <div class="col-md-9">
+      <%= b.text_field :address, { class: "form-control input-xlarge" } %>
+    </div>
+  </div>
+  <div class="form-group">
+    <%= b.label :num_ext,  { class: "col-md-3 control-label" } do %> No. Exterior <!-- <span class="required">*</span> -->
+    <% end %>
+    <div class="col-md-9">
+      <%= b.text_field :num_ext, { class: "form-control input-small" } %>
+    </div>
+  </div>
+  <div class="form-group">
+    <%= b.label :num_int, "No. Interior", { class: "col-md-3 control-label" } %>
+    <div class="col-md-9">
+      <%= b.text_field :num_int, { class: "form-control input-small" } %>
+    </div>
+  </div>
+  <div class="form-group">
+    <%= b.label :zipcode, { class: "col-md-3 control-label" } do %> Código postal <span class="required">*</span>
+    <% end %>
+    <div class="col-md-9">
+      <%= b.text_field :zipcode, { class: "form-control input-small mask_number", :value => ((info.billing_information.nil? || info.billing_information.zipcode == 0) ? '' : info.billing_information.zipcode) } %>
+    </div>
+  </div>
 
-				<div class="form-group">
-					<%= b.label :state_id,  {:class=>"col-md-3 control-label"} do %>Estado <span class="required">*</span>
-					<% end %> 
-					<div class="col-md-9">
-						<%= b.collection_select(:state_id, SpmxState.all, :id, :name , options ={:include_blank => "Seleccione"}, :class => "form-control input-medium state_id") %>
-					</div>
-				</div>
+  <div class="form-group">
+    <%= b.label :state_id,  { class: "col-md-3 control-label" } do %>Estado <span class="required">*</span>
+    <% end %>
+    <div class="col-md-9">
+      <%= b.collection_select(:state_id, SpmxState.all, :id, :name , options ={:include_blank => "Seleccione" }, :class => "form-control input-medium state_id") %>
+    </div>
+  </div>
 
-				<div class="form-group">
-					<%= b.label :county_id, {:class=>"col-md-3 control-label"} do %> Municipio <span class="required">*</span>
-					<% end %> 
-					<div class="col-md-9">
-						
-						<%= b.collection_select(:county_id, SpmxCounty.where(((info.billing_information.nil? || info.billing_information.state_id.nil?) ? "" : "state_id = #{info.billing_information.state_id}") ) , "id", "name", options ={:include_blank => "Seleccione"} , html_options ={ "data-opt-id" => "county_id", "data-option-dependent" => true, 
-							"data-option-observed" => "state_id",
-							"data-option-url" => "/getcounties/:state_id:.json",
-							"data-option-key-method" => :id,
-							"data-option-value-method" => :name, :class => "form-control input-medium county_id nestedselect"} ) %>
-					</div>
-				</div>
-				<div class="form-group">
-					<%= b.label :city,  {:class=>"col-md-3 control-label"} do %>Ciudad o localidad <!-- <span class="required">*</span> -->
-					<% end %> 
-					<div class="col-md-9">
-						<%= b.text_field :city, {:class=>"form-control input-xlarge"} %>
-					</div>
-				</div>
-				<div class="form-group">
-					<%= b.label :suburb,  {:class=>"col-md-3 control-label"} do %>Colonia <!-- <span class="required">*</span> -->
-					<% end %> 
-					<div class="col-md-9">
-						<%= b.text_field :suburb, {:class=>"form-control input-xlarge"} %>
-					</div>
-				</div>
-			</div>
+  <div class="form-group">
+    <%= b.label :county_id, { class: "col-md-3 control-label" } do %> Municipio <span class="required">*</span>
+    <% end %>
+    <div class="col-md-9">
+
+      <%= b.collection_select(:county_id, SpmxCounty.where(((info.billing_information.nil? || info.billing_information.state_id.nil?) ? "" : "state_id = #{info.billing_information.state_id}") ) , "id", "name", options ={:include_blank => "Seleccione" } , html_options ={ "data-opt-id" => "county_id", "data-option-dependent" => true,
+        "data-option-observed" => "state_id",
+        "data-option-url" => "/getcounties/:state_id:.json",
+        "data-option-key-method" => :id,
+        "data-option-value-method" => :name, :class => "form-control input-medium county_id nestedselect" } ) %>
+    </div>
+  </div>
+  <div class="form-group">
+    <%= b.label :city,  { class: "col-md-3 control-label" } do %>Ciudad o localidad <!-- <span class="required">*</span> -->
+    <% end %>
+    <div class="col-md-9">
+      <%= b.text_field :city, { class: "form-control input-xlarge" } %>
+    </div>
+  </div>
+  <div class="form-group">
+    <%= b.label :suburb,  { class: "col-md-3 control-label" } do %>Colonia <!-- <span class="required">*</span> -->
+    <% end %>
+    <div class="col-md-9">
+      <%= b.text_field :suburb, { class: "form-control input-xlarge" } %>
+    </div>
+  </div>
+</div>

+ 36 - 0
app/views/cash_outs/_general_public_invoice.html.erb

@@ -0,0 +1,36 @@
+<div class="portlet-body form">
+  <%= form_tag(generate_gpi_path, method: "GET", remote: true, class: "form-horizontal", id: "general_public_invoice_form", onsubmit: "closeDialog()") do %>
+    <%= hidden_field_tag :generate, true %>
+    <div id="error_explanation"></div>
+    <div class="row">
+      <div class="col-md-12">
+        <!-- caja registradora -->
+        <div class="form-group">
+          <%= label_tag :total_for_invoice, "Total de ventas a contado", { class: "col-md-4 control-label" } do %> Total de ventas a contado <% end %>
+          <div class="col-md-8">
+            <div class="input-group">
+              <span class="input-group-addon"> $ </span>
+              <%= number_field_tag :total_for_invoice, format('%0.2f', @sales_total), { disabled: true, class: "form-control input-small" } %>
+              <%= hidden_field_tag :total_for_invoice, @sales_total %>
+              <%= hidden_field_tag :amount_for_invoice, @sales_amount %>
+              <%= hidden_field_tag :tax_for_invoice, @sales_tax %>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+    <h4 class="form-section" style="margin:20px 0px 10px 0px"></h4>
+    <div class="actions col-md-offset-3">
+      <!-- <button type="button" class="btn btn-success" onclick="submitForm()" id="gpibutton">Generar</button> -->
+      <%= submit_tag 'Generar factura', class: "btn btn-success" %>
+      <button type="button" class="btn default" onclick="closeDialog()" id="cancel">Cancelar</button>
+    </div>
+  <% end %>
+</div>
+
+<script>
+  function closeDialog() {
+    $(".modal-body").html('');
+    $('#dialog').modal('toggle');
+  }
+</script>

+ 138 - 0
app/views/cash_outs/cfdi_gpi.pdf.erb

@@ -0,0 +1,138 @@
+<div class='pdf-body' style="margin:50px;">
+  <header class='document-header'>
+  </header>
+  <section>
+    <article class="left-left logo" style="height:100px; width: 50%;margin-top: 2cm;">
+      <% if user.pointsale.img_pointsale? %>
+        <%= wicked_pdf_image_tag user.pointsale.img_pointsale_url, { width: 300, height: 80 } %>
+      <% end %>
+    </article>
+    <article style="width: 40%;float: right;text-align: right; margin-top: 2cm;">
+      <p style="margin: 5px; font-size: 14px; color: red;"><b>FACTURA</b></p>
+      <p style="margin: 5px; font-size: 11px;">Tipo de comprobante:&nbsp;<b><%= Rails.application.config.issuing['CveIngreso'].to_s %></b></p>
+      <p style="margin: 5px; font-size: 11px;">Serie: <b><%= doc.xpath("//@Serie")%></b> Folio: <b><%= doc.xpath("//@Folio")%></b></p>
+      <p style="margin: 5px; font-size: 11px;">Folio Fiscal (UUID):</b></p>
+      <p style="margin: 5px; font-size: 11px;"><b><%= doc.xpath("//cfdi:Complemento//@UUID")%></b></p>
+      <p style="margin: 5px; font-size: 11px;">No. de Serie del Certificado del CSD:</p>
+      <p style="margin: 5px; font-size: 11px;"><b><%= doc.xpath("//@NoCertificadoSAT") %></b></p>
+      <p style="margin: 5px; font-size: 11px;">C.P., fecha y hora de emision: </p>
+      <p style="margin: 5px; font-size: 11px;"><b><%= "#{doc.xpath('//cfdi:Comprobante/@LugarExpedicion')}, #{doc.xpath('//cfdi:Comprobante/@Fecha')}" %></b></p>
+      <p style="margin: 5px; font-size: 11px;">Id. Usu. - Usuario </p>
+      <p style="margin: 5px; font-size: 11px;"><b><%= "#{user.id} - #{user.first_name} #{user.last_name}" %></b></p>
+    </article>
+  </section>
+  <section style="margin-bottom: 5px;">
+    <article style="width: 40%;">
+    <fieldset style="border-color: #f2f2f2;"><legend><b>Emisor</b></legend>
+      <p style="margin: 5px; font-size: 10px;"><b><%= doc.xpath("//cfdi:Emisor/@Rfc")%></b></p>
+      <p style="margin: 5px; font-size: 10px;"><%= doc.xpath('//cfdi:Emisor/@Nombre') %></p>
+      <p style="margin: 5px; font-size: 10px;"><b><%= "#{doc.xpath('//cfdi:Emisor/@RegimenFiscal')} - #{Rails.application.config.issuing['regimenFiscal']}"%></b></p>
+    </fieldset>
+    </article>
+    <article style="width: 40%;">
+    <fieldset style="border-color: #f2f2f2;"><legend><b>Receptor</b></legend>
+      <p style="margin: 5px; font-size: 10px;"><b><%= doc.xpath("//cfdi:Receptor/@Rfc")%></b></p>
+    </fieldset>
+    </article>
+  </section>
+  <section class="products" style="height: auto;">
+    <table cellspacing="0" class="table">
+      <thead>
+        <tr>
+          <th style="text-align:center;font-weight:bold;width:5%;font-size:10px; padding: 2px;">Clave Prod.</th>
+          <th style="text-align:center;font-weight:bold;width:5%;font-size:10px; padding: 2px;">Cantidad</th>
+          <th style="text-align:center;font-weight:bold;width:5%;font-size:10px; padding: 2px;">Unidad de medida</th>
+          <th style="text-align:center;font-weight:bold;width:20%;font-size:10px; padding: 2px;">Descripcion</th>
+          <th colspan="2" style="text-align:center;font-weight:bold;width:10%;font-size:10px; padding: 2px;">Impuesto</th>
+          <th style="text-align:center;font-weight:bold;width:10%;font-size:10px; padding: 2px;">Precio unitario</th>
+          <th style="text-align:center;font-weight:bold;width:10%;font-size:10px; padding: 2px;">Importe</th>
+        </tr>
+      </thead>
+      <tbody>
+        <% concepts.each do |concept| %>
+        <tr>
+          <td style="font-size: 9px;">
+            <%= concept['ProductKey'] %>
+          </td>
+          <td style="font-size: 9px;">
+            <%= concept['Quantity'] %>
+          </td>
+          <td style="font-size: 9px;">
+            <%= concept['UnitKey'] %>
+          </td>
+          <td style="font-size: 9px; overflow:hidden;text-overflow:'';white-space:nowrap;max-width:200px;">
+            <%= concept['Description'] %>
+          </td>
+          <td style="font-size: 9px;">
+            <%= if concept['Tax'].to_f > 0.00; "002"; end; %>
+          </td>
+          <td style="font-size: 9px;">
+            <%= if concept['Tax'].to_f > 0.00 ; "$#{concept["Tax"].to_s}"; end; %>
+          </td>
+          <td style="font-size: 9px;">
+            <%= "$#{concept['Price']}" %>
+          </td>
+          <td style="font-size: 9px;">
+            <%= "$#{concept['Amount']}" %>
+          </td>
+        </tr>
+        <% end %>
+      </tbody>
+    </table>
+  </section>
+  <hr>
+  <section style="word-wrap: break-word;">
+    <article style="float:right;width: 17%;">
+      <p style="text-align:right; margin: 5px;font-size: 9px; background-color: #f2f2f2;">$ <%= doc.xpath("//cfdi:Comprobante/@SubTotal") %></p>
+      <p style="text-align:right; margin: 5px;font-size: 9px;">
+        <%= "#{doc.xpath('//cfdi:Impuestos/@TotalImpuestosTrasladados')}" if "$#{doc.xpath('//cfdi:Impuestos/@TotalImpuestosTrasladados')}"%><br></p>
+      <p style="text-align:right; margin: 5px;font-size: 9px; background-color: #f2f2f2;"><b>$<%= doc.xpath("//cfdi:Comprobante/@Total") %></b></p>
+    </article>
+    <article style="float:right; width: 10%;">
+      <p style="text-align:right; margin: 5px; font-size: 9px;"><b>Subtotal:</b></p>
+      <p style="text-align:right; margin: 5px; font-size: 9px;">IVA(16%):</p>
+      <p style="text-align:right; margin: 5px; font-size:9px;"><b>Total:</b></p>
+    </article>
+    <article style="float:right; width: 40%;">
+      <p style="font-size: 11px;"><b>Total con letra:</b></p>
+      <% numeros = doc.xpath("//cfdi:Comprobante/@Total").to_s.split('.') %>
+      <p style="font-size: 11px;">
+        <%= "#{numeros[0].to_i.to_words.split.each { |x| x.capitalize! }.join(' ')} Pesos #{numeros[1]}/100 M.N." %>
+      </p>
+    </article>
+    <article style="width: 30%;float:right;">
+      <p style="font-size: 10px;"><%= "#{doc.xpath('//@MetodoPago')}: Pago en una sola exhibición" %></p>
+      <% payment_method = PaymentMethod.find_by(payment_method_key: "#{doc.xpath('//cfdi:Comprobante/@FormaPago')}") %>
+      <p style="font-size: 10px;"><%= "#{doc.xpath('//cfdi:Comprobante/@FormaPago')} - #{payment_method.method}" %> </p>
+    </article>
+  </section>
+  <section style="word-wrap: break-word;">
+    <article style="width: 100%;">
+      <hr>
+      <p style="font-size: 9px;"><br><b>Sello Digital del CFDI:</b><br><%= doc.xpath("//@SelloCFD") %></p>
+      <p style="font-size: 9px;"><b>Sello del SAT:</b><br><%= doc.xpath("//@SelloSAT") %>
+      </p>
+    </article>
+  </section>
+  <section style="word-wrap: break-word;">
+    <article style="float:left;width: 20%; padding-top: 10px;">
+      <!-- < % wicked_pdf_image_tag("invoice_qr/qr_#{uuid}.png", width: "120", height: "120") %> -->
+      <%= wicked_pdf_image_tag_qr("#{qr_path}", width: "120", height: "120") %>
+    </article>
+    <article style="float:left;width: 80%">
+      <p style="font-size: 9px;">
+        <b >Cadena Original del complemento de certificación digital del SAT:</b><br>
+        <%= details %>
+      </p>
+      <p style="font-size: 9px;">
+        <b>No de Serie del Certificado del SAT:</b> <%= doc.xpath("//@NoCertificadoSAT") %>
+      </p>
+      <p style="font-size: 9px;">
+        <b>Fecha y hora de certificación:</b> <%= doc.xpath("//@FechaTimbrado") %>
+      </p>
+    </article>
+  </section>
+  <footer style="text-align:center;font-size:9px; margin-bottom: 3cm;">
+      <b>Este documento es una representación impresa de un CFDI</b>
+  </footer>
+</div>

+ 5 - 0
app/views/cash_outs/general_public_invoice.js.erb

@@ -0,0 +1,5 @@
+$('#dialog h3.modal-title').html("Factura a público general");
+// Render the edit form
+$('.modal-body').html('<%= j render("cash_outs/general_public_invoice") %>');
+// Show the dynamic dialog
+$('#dialog').modal("show");

+ 1 - 0
app/views/cash_outs/generate_gpi.js.erb

@@ -0,0 +1 @@
+location.reload();

+ 32 - 22
app/views/cash_outs/index.html.erb

@@ -115,29 +115,39 @@
                     </thead>
                     <tbody>
                       <% @cash_outs.each_with_index do |cash_out, key| %>
-                      <tr>
-                        <td><%= cash_out.id %></td>
-                        <% if current_user.usertype == "A" || current_user.usertype == "SS" %>
-                          <td>
-                          <%= OpenCashRegister.get_pointsale(cash_out.open_cash_register_id, "open_cash_register").name %>
-                          </td>
-                        <% end %>
-                        <td><%= cash_out.open_cash_register.cash_register.name %> </td>
-                        <td><%= number_to_currency(cash_out.amount_in, precision: 2) %> </td>
-                        <td><%= number_to_currency(cash_out.amount_out, precision: 2)  %></td>
-                        <td><%= number_to_currency(cash_out.amount_in - cash_out.amount_out, precision: 2)  %></td>
-                        <td><%= l(cash_out.created_at, :format => '%d/%B/%Y') %></td>
-                        <td><%= cash_out.user.first_name %></td>
-                        <td><%= (cash_out.received_by.blank? ? "" : cash_out.received_by.first_name) %></td>
-                        <td class="text-center">
-                          <%= link_to cash_out, {:class=>"btn btn-icon-only default filtros", :title=>"Ver corte de caja"} do %>
-                            <i class="fa fa-search"></i>
-                          <% end %>
-                          <%= link_to print_cash_out_receipt_path(cash_out.id, format: 'pdf'), {:class=>"btn btn-icon-only default", :target => "blank"} do %>
-                            <i class="fa fa-print"></i>
+                        <% horas_transcurridas = ((DateTime.now.to_time - cash_out.created_at.to_time).to_i/60)/60 %>
+                        <tr>
+                          <td><%= cash_out.id %></td>
+                          <% if current_user.usertype == "A" || current_user.usertype == "SS" %>
+                            <td>
+                            <%= OpenCashRegister.get_pointsale(cash_out.open_cash_register_id, "open_cash_register").name %>
+                            </td>
                           <% end %>
-                        </td>
-                      </tr>
+                          <td><%= cash_out.open_cash_register.cash_register.name %> </td>
+                          <td><%= number_to_currency(cash_out.amount_in, precision: 2) %> </td>
+                          <td><%= number_to_currency(cash_out.amount_out, precision: 2)  %></td>
+                          <td><%= number_to_currency(cash_out.amount_in - cash_out.amount_out, precision: 2)  %></td>
+                          <td><%= l(cash_out.created_at, :format => '%d/%B/%Y') %></td>
+                          <td><%= cash_out.user.first_name %></td>
+                          <td><%= (cash_out.received_by.blank? ? "" : cash_out.received_by.first_name) %></td>
+                          <td class="text-center">
+                            <%= link_to cash_out, {:class=>"btn btn-icon-only default filtros", :title=>"Ver corte de caja"} do %>
+                              <i class="fa fa-search"></i>
+                            <% end %>
+                            <%= link_to print_cash_out_receipt_path(cash_out.id, format: 'pdf'), {:class=>"btn btn-icon-only default", :target => "blank"} do %>
+                              <i class="fa fa-print"></i>
+                            <% end %>
+                            <% if cash_out.invoice_num.blank? && (cash_out.amount_in - cash_out.amount_out) > 0.0 && horas_transcurridas < 72 %>
+                              <%= link_to general_public_invoice_path(cash_out.id), remote: true, class: "btn btn-icon-only green", title: "Generar factura a público general" do %>
+                                <i class="fa fa-envelope"></i>
+                              <% end %>
+                            <% elsif cash_out.invoice_num.present? %>
+                              <%= link_to print_gpi_path(cash_out.id), remote: true, class: "btn btn-icon-only default blue-madison", target: "blank", title: "Reimprimir factura" do %>
+                                <i class="fa fa-file-text-o"></i>
+                              <% end %>
+                            <% end %>
+                          </td>
+                        </tr>
                       <% end %>
                     </tbody>
                   </table>

+ 5 - 0
app/views/cash_outs/print_invoice.js.erb

@@ -0,0 +1,5 @@
+var invoice_window = window.open('<%= publicroot.reduced_path(file: "#{pdf_path}") %>', '_blank', '');
+
+// invoice_window.onload = function() {
+//  invoice_window.print();
+// };

+ 85 - 0
app/views/layouts/cfdi.html.erb.html

@@ -0,0 +1,85 @@
+<!doctype html>
+<html>
+  <head>
+    <meta charset='utf-8' />
+  <style type="text/css">
+    body {
+        width: 216mm;
+        height: auto;
+        font-family: 'OpenSansRegular';
+        font-size: 11px;
+      }
+      .left-right{
+        float: left;
+        text-align: right;
+      }
+      .left-left{
+        float: left;
+        text-align: left;
+      }
+      .logo{
+        width: 70%;
+        height: 5%;
+        text-align: left;
+      }
+      @page{
+        size: a4;
+        margin: 1.5cm 2cm;
+      }
+      table {
+        border-collapse: collapse;
+      }
+      th{
+        background-color: #D8D8D8;
+        height: 20px;
+        /*border: 1px solid;*/
+      }
+      tr{
+        padding: 0px;
+        height: 10px;
+      }
+      table td{
+        max-height: auto;
+        word-wrap: break-word;
+        vertical-align: top;
+        padding: 1px;
+        /*border-right: 1px solid;*/
+      }
+      table td, table th {
+        text-align: center;
+      }
+      td{
+        overflow: hidden;
+        text-overflow:'';
+        white-space: nowrap;
+      }
+      hr{
+        width: 100%;
+        background-color: #f2f2f2;
+      }
+      /*div.alwaysbreak { page-break-before: always; }
+      div.nobreak:before { clear:both; }*/
+      /*div.nobreak { page-break-inside: avoid; }*/
+      /*table tr:first-child th {
+        border-top: 0;
+      }
+      table tr:last-child td {
+        border-bottom: 0;
+      }
+      table tr td:first-child,
+      table tr th:first-child {
+        border-left: 0;
+      }
+      table tr td:last-child,
+      table tr th:last-child {
+        border-right: 0;
+      }*/
+
+  </style>
+  </head>
+  <body>
+  <page>
+    <%= yield %>
+  </page>
+  </body>
+</html>

+ 36 - 0
app/views/pointsales/_form.html.erb

@@ -26,12 +26,48 @@
 				</div>
 			</div>
 			<div class="form-group">
+        <%= f.label :business_name, "Razón social", {:class=>"col-md-3 control-label"} do %> Razón social
+          <span class="required">*</span>
+        <% end %>
+        <div class="col-md-9">
+          <%= f.text_field :business_name, {:class=>"form-control input-large"} %>
+          <span class="help-block">La razón social se utilizará para la generación de facturas</span>
+        </div>
+      </div>
+      <div class="form-group">
+        <%= f.label :federal_taxpayer_registration, "RFC", {:class=>"col-md-3 control-label"} do %> RFC
+          <span class="required">*</span>
+        <% end %>
+        <div class="col-md-9">
+          <%= f.text_field :federal_taxpayer_registration, {:class=>"form-control input-medium uppercase"} %>
+          <span class="help-block">El rfc que se capture se utilizará para la generación de facturas</span>
+        </div>
+      </div>
+      <div class="form-group">
+        <%= f.label :tax_regime, "", { class: "col-md-3 control-label" } do %> Régimen físcal
+          <span class="required">*</span>
+        <% end %>
+        <div class="col-md-4">
+          <%= f.select :tax_regime, (Rails.application.config.taxregime), { prompt: "Seleccione" }, { class: 'form-control select2'} %>
+          <span class="help-block">El régimen físcal es requerido para la generación de facturas</span>
+        </div>
+      </div>
+			<div class="form-group">
 				<%= f.label :address, "Dirección", { class: "col-md-3 col-sm-3 control-label" } %>
 				<div class="col-md-4 col-sm-6">
 					<%= f.text_field :address, { class: "form-control" } %>
 				</div>
 			</div>
 			<div class="form-group">
+        <%= f.label :postal_code, "Código postal", {:class=>"col-md-3 control-label"} do %> Código postal
+         <span class="required">*</span>
+        <% end %>
+        <div class="col-md-9">
+          <%= f.text_field :postal_code, {:class=>"form-control input-small"} %>
+          <span class="help-block">El código postal se utilizará para la generación de facturas</span>
+        </div>
+      </div>
+			<div class="form-group">
 				<%= f.label :haggle_percent, { class: "col-md-3 col-sm-3 control-label" } do %> Porcentaje de regateo para ventas <% end %>
 				<div class="col-md-2 col-sm-6">
 					<%= f.number_field :haggle_percent, { class: "form-control input-xsmall" } %>

+ 64 - 0
config/application.rb

@@ -48,5 +48,69 @@ module Pos
     config.support_mails = [
       "daniel.mendoza@sml.mx"
     ]
+
+    # Datos generales para el proceso de facturas y notas de credito
+    config.issuing = {
+      "Version" => "3.3",
+      "CveIngreso" => "I",
+      "CveEgreso" => "E",
+      "CvePago" => "P",
+      "Password" =>  "mvpNUXmQfK8=", # contrasenia de integrador proporcionada por el proveedor ProFact
+      "MetodoPago" => "PUE", # Metodo de pago usado para las facturas y notas de credito Pago en una sola exhibicion
+      "SerieContado" => "B", # Serie para factura de venta de contado
+      "SerieCredito" => "A", # Serie para factura de venta a credito
+      "SerieNota" => "C", # Serie para notas de credito
+      "stamper_uri" => "https://cfdi33-pruebas.buzoncfdi.mx:1443/Timbrado.asmx?wsdl",
+      "Impuesto" => "002", # Clave del impuesto aplicado 002: IVA
+      "TipoRelacionNC" => "03" # Relaciin de facturas con nota de credito
+    }
+
+    config.taxregime = {
+      "General de Ley Personas Morales" => "601",
+      "Personas Morales con Fines no Lucrativos" => "603",
+      "Sueldos y Salarios e Ingresos Asimilados a Salarios" => "605",
+      "Arrendamiento" => "606",
+      "Demás ingresos" => "608",
+      "Consolidación" => "609",
+      "Residentes en el Extranjero sin Establecimiento Permanente en México" => "610",
+      "Ingresos por Dividendos (socios y accionistas)" => "611",
+      "Personas Físicas con Actividades Empresariales y Profesionales" => "612",
+      "Ingresos por intereses" => "614",
+      "Sin obligaciones fiscales" => "616",
+      "Sociedades Cooperativas de Producción que optan por diferir sus ingresos" => "620",
+      "Incorporación Fiscal" => "621",
+      "Actividades Agrícolas, Ganaderas, Silvícolas y Pesqueras" => "622",
+      "Opcional para Grupos de Sociedades" => "623",
+      "Coordinados" => "624",
+      "Hidrocarburos" => "628",
+      "Régimen de Enajenación o Adquisición de Bienes" => "607",
+      "De los Regímenes Fiscales Preferentes y de las Empresas Multinacionales" => "629",
+      "Enajenación de acciones en bolsa de valores" => "630",
+      "Régimen de los ingresos por obtención de premios" => "615"
+    }
+
+    config.cfdiuse = {
+      "Adquisición de mercancias" => "G01",
+      "Devoluciones, descuentos o bonificaciones" => "G02",
+      "Gastos en general" => "G03",
+      "Construcciones" => "I01",
+      "Mobilario y equipo de oficina por inversiones" => "I02",
+      "Equipo de transporte" => "I03",
+      "Equipo de computo y accesorios" => "I04",
+      "Dados, troqueles, moldes, matrices y herramental" => "I05",
+      "Comunicaciones telefónicas" => "I06",
+      "Comunicaciones satelitales" => "I07",
+      "Otra maquinaria y equipo" => "I08",
+      "Honorarios médicos, dentales y gastos hospitalarios." => "D01",
+      "Gastos médicos por incapacidad o discapacidad" => "D02",
+      "Gastos funerales." => "D03",
+      "Donativos." => "D04",
+      "Intereses reales efectivamente pagados por créditos hipotecarios (casa habitación)." => "D05",
+      "Aportaciones voluntarias al SAR." => "D06",
+      "Primas por seguros de gastos médicos." => "D07",
+      "Gastos de transportación escolar obligatoria." => "D08",
+      "Depósitos en cuentas para el ahorro, primas que tengan como base planes de pensiones." => "D09",
+      "Pagos por servicios educativos (colegiaturas)" => "D10"
+    }
   end
 end

+ 3 - 0
config/routes.rb

@@ -114,6 +114,9 @@ Rails.application.routes.draw do
   post 'update_all_variants_prices' => 'available_products#update_all_variants_prices', defaults: { format: 'json' }
 
   resources :cash_outs
+  get 'general_public_invoice/:cash_out_id' => 'cash_outs#general_public_invoice', format: :js, as: "general_public_invoice"
+  get 'generate_gpi/:cash_out_id' => 'cash_outs#generate_gpi', format: :js, as: 'generate_gpi'
+  get 'print_gpi/:cash_out_id' => 'cash_outs#print_invoice', as: 'print_gpi'
   get 'get_open_cash_registers' => 'cash_outs#get_open_cash_registers', defaults: { format: 'js' }
   post 'select_open_cash_to_close/:open_cash_register_id' => 'cash_outs#select_open_cash_to_close', defaults: { format: 'js' }
   get 'find_cash_outs_by_date/:pointsale_id/:begin_date/:end_date' => 'cash_outs#find_cash_outs_by_date', defaults: { format: 'js' }

+ 7 - 0
db/migrate/20181213183811_add_sales_invoice_column.rb

@@ -0,0 +1,7 @@
+class AddSalesInvoiceColumn < ActiveRecord::Migration
+  def change
+    add_column :cash_outs, :invoice_num, :string, null: true unless column_exists?(:cash_outs, :invoice_num)
+    add_column :sales, :invoice_num, :string, null: true unless column_exists?(:sales, :invoice_num)
+    add_column :sales, :require_invoice, :boolean, default: false unless column_exists?(:sales, :require_invoice)
+  end
+end

+ 17 - 0
db/migrate/20181213185244_create_invoice_details.rb

@@ -0,0 +1,17 @@
+class CreateInvoiceDetails < ActiveRecord::Migration
+  def change
+    drop_table :invoice_details if table_exists? :invoice_details
+    create_table :invoice_details do |t|
+      t.belongs_to :sale, index: true
+      t.string :uuid, null: false
+      t.string :cfdi_type, limit: 1, null: false
+      t.string :invoice_reason
+      t.string :rfc_receptor
+      t.datetime :generating_date
+      t.datetime :stamping_date
+      t.string :storing_url
+      t.integer :status, limit: 1, default: 1
+      t.timestamps null: false
+    end
+  end
+end

+ 8 - 0
db/migrate/20181213203152_add_invoice_columns_to_pointsale.rb

@@ -0,0 +1,8 @@
+class AddInvoiceColumnsToPointsale < ActiveRecord::Migration
+  def change
+    add_column :pointsales, :federal_taxpayer_registration, :string
+    add_column :pointsales, :business_name, :string
+    add_column :pointsales, :tax_regime, :integer
+    add_column :pointsales, :postal_code, :integer
+  end
+end

+ 5 - 0
db/migrate/20181213203326_add_column_to_billing_information.rb

@@ -0,0 +1,5 @@
+class AddColumnToBillingInformation < ActiveRecord::Migration
+  def change
+    add_column :billing_informations, :cfdi_use_key, :string, default: "G03"
+  end
+end

+ 5 - 0
db/migrate/20181217171552_add_key_column_to_payment_method.rb

@@ -0,0 +1,5 @@
+class AddKeyColumnToPaymentMethod < ActiveRecord::Migration
+  def change
+    add_column :payment_methods, :payment_method_key, :string, null:true, default: '99'
+  end
+end

+ 5 - 0
db/migrate/20181217172147_add_key_column_to_products.rb

@@ -0,0 +1,5 @@
+class AddKeyColumnToProducts < ActiveRecord::Migration
+  def change
+    add_column :products, :product_service_key, :string, null: true, default: '01010101'
+  end
+end

+ 5 - 0
db/migrate/20181217175547_add_key_column_to_unit.rb

@@ -0,0 +1,5 @@
+class AddKeyColumnToUnit < ActiveRecord::Migration
+  def change
+    add_column :units, :unit_key, :string, default: ""
+  end
+end

+ 72 - 45
db/schema.rb

@@ -11,7 +11,7 @@
 #
 # It's strongly recommended that you check this file into your version control system.
 
-ActiveRecord::Schema.define(version: 20181122030513) do
+ActiveRecord::Schema.define(version: 20181217175547) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "plpgsql"
@@ -55,21 +55,22 @@ ActiveRecord::Schema.define(version: 20181122030513) do
   add_index "available_products", ["product_id"], name: "index_available_products_on_product_id", using: :btree
 
   create_table "billing_informations", force: :cascade do |t|
-    t.integer  "customer_id",             default: 0,  null: false
-    t.string   "name",        limit: 255, default: "", null: false
-    t.string   "rfc",         limit: 13,  default: "", null: false
-    t.string   "address",     limit: 255, default: "", null: false
-    t.integer  "zipcode",                 default: 0,  null: false
-    t.string   "num_ext",                 default: "", null: false
-    t.string   "num_int",                 default: "", null: false
-    t.integer  "state_id",                default: 0,  null: false
-    t.integer  "county_id",               default: 0,  null: false
-    t.string   "city",                    default: "", null: false
-    t.string   "suburb",                  default: "", null: false
-    t.integer  "status",                  default: 1,  null: false
-    t.datetime "created_at",                           null: false
-    t.datetime "updated_at",                           null: false
-    t.integer  "supplier_id",             default: 0,  null: false
+    t.integer  "customer_id",              default: 0,     null: false
+    t.string   "name",         limit: 255, default: "",    null: false
+    t.string   "rfc",          limit: 13,  default: "",    null: false
+    t.string   "address",      limit: 255, default: "",    null: false
+    t.integer  "zipcode",                  default: 0,     null: false
+    t.string   "num_ext",                  default: "",    null: false
+    t.string   "num_int",                  default: "",    null: false
+    t.integer  "state_id",                 default: 0,     null: false
+    t.integer  "county_id",                default: 0,     null: false
+    t.string   "city",                     default: "",    null: false
+    t.string   "suburb",                   default: "",    null: false
+    t.integer  "status",                   default: 1,     null: false
+    t.datetime "created_at",                               null: false
+    t.datetime "updated_at",                               null: false
+    t.integer  "supplier_id",              default: 0,     null: false
+    t.string   "cfdi_use_key",             default: "G03"
   end
 
   add_index "billing_informations", ["county_id"], name: "index_billing_informations_on_county_id", using: :btree
@@ -103,6 +104,7 @@ ActiveRecord::Schema.define(version: 20181122030513) do
     t.decimal  "cash_fund",             precision: 10, scale: 2, default: 0.0
     t.decimal  "physical_cash",         precision: 10, scale: 2, default: 0.0, null: false
     t.string   "observations"
+    t.string   "invoice_num"
   end
 
   add_index "cash_outs", ["open_cash_register_id"], name: "index_cash_outs_on_open_cash_register_id", using: :btree
@@ -288,6 +290,22 @@ ActiveRecord::Schema.define(version: 20181122030513) do
   add_index "inventories_moves", ["purchase_id"], name: "index_inventories_moves_on_purchase_id", using: :btree
   add_index "inventories_moves", ["sale_id"], name: "index_inventories_moves_on_sale_id", using: :btree
 
+  create_table "invoice_details", force: :cascade do |t|
+    t.integer  "sale_id"
+    t.string   "uuid",                                  null: false
+    t.string   "cfdi_type",       limit: 1,             null: false
+    t.string   "invoice_reason"
+    t.string   "rfc_receptor"
+    t.datetime "generating_date"
+    t.datetime "stamping_date"
+    t.string   "storing_url"
+    t.integer  "status",          limit: 2, default: 1
+    t.datetime "created_at",                            null: false
+    t.datetime "updated_at",                            null: false
+  end
+
+  add_index "invoice_details", ["sale_id"], name: "index_invoice_details_on_sale_id", using: :btree
+
   create_table "open_cash_registers", force: :cascade do |t|
     t.integer  "cash_register_id"
     t.integer  "user_id"
@@ -302,21 +320,22 @@ ActiveRecord::Schema.define(version: 20181122030513) do
   add_index "open_cash_registers", ["user_id"], name: "index_open_cash_registers_on_user_id", using: :btree
 
   create_table "payment_methods", force: :cascade do |t|
-    t.string   "method",                 null: false
-    t.integer  "status",     default: 1, null: false
-    t.datetime "created_at",             null: false
-    t.datetime "updated_at",             null: false
+    t.string   "method",                            null: false
+    t.integer  "status",             default: 1,    null: false
+    t.datetime "created_at",                        null: false
+    t.datetime "updated_at",                        null: false
     t.integer  "reference"
-    t.integer  "isCash",     default: 0, null: false
+    t.integer  "isCash",             default: 0,    null: false
+    t.string   "payment_method_key", default: "99"
   end
 
   create_table "pointsales", force: :cascade do |t|
     t.string   "name"
     t.string   "address"
     t.text     "notes"
-    t.integer  "status",                                              default: 1
-    t.datetime "created_at",                                                        null: false
-    t.datetime "updated_at",                                                        null: false
+    t.integer  "status",                                                 default: 1
+    t.datetime "created_at",                                                           null: false
+    t.datetime "updated_at",                                                           null: false
     t.text     "prefix"
     t.string   "img_pointsale_file_name"
     t.string   "img_pointsale_content_type"
@@ -324,7 +343,11 @@ ActiveRecord::Schema.define(version: 20181122030513) do
     t.datetime "img_pointsale_updated_at"
     t.string   "ticket_footer"
     t.string   "img_pointsale"
-    t.decimal  "haggle_percent",             precision: 10, scale: 2, default: 0.0
+    t.decimal  "haggle_percent",                precision: 10, scale: 2, default: 0.0
+    t.string   "federal_taxpayer_registration"
+    t.string   "business_name"
+    t.integer  "tax_regime"
+    t.integer  "postal_code"
   end
 
   create_table "pos_configs", force: :cascade do |t|
@@ -421,24 +444,24 @@ ActiveRecord::Schema.define(version: 20181122030513) do
   add_index "product_wastes", ["product_id"], name: "index_product_wastes_on_product_id", using: :btree
 
   create_table "products", force: :cascade do |t|
-    t.string   "sku",                      limit: 30,                           default: "",    null: false
-    t.string   "name",                     limit: 255,                          default: "",    null: false
-    t.text     "description",                                                   default: "",    null: false
+    t.string   "sku",                      limit: 30,                           default: "",         null: false
+    t.string   "name",                     limit: 255,                          default: "",         null: false
+    t.text     "description",                                                   default: "",         null: false
     t.decimal  "price_base",                           precision: 10, scale: 2, default: 0.0
     t.decimal  "price_sale",                           precision: 10, scale: 2, default: 0.0
     t.string   "img_product_file_name"
     t.string   "img_product_content_type"
     t.integer  "img_product_file_size"
     t.datetime "img_product_updated_at"
-    t.boolean  "presentation",                                                  default: false, null: false
-    t.boolean  "inventory",                                                     default: true,  null: false
+    t.boolean  "presentation",                                                  default: false,      null: false
+    t.boolean  "inventory",                                                     default: true,       null: false
     t.decimal  "content"
-    t.integer  "status",                                                        default: 1,     null: false
-    t.datetime "created_at",                                                                    null: false
-    t.datetime "updated_at",                                                                    null: false
+    t.integer  "status",                                                        default: 1,          null: false
+    t.datetime "created_at",                                                                         null: false
+    t.datetime "updated_at",                                                                         null: false
     t.integer  "unit_id"
-    t.integer  "include_purchase_tax",                                          default: 0,     null: false
-    t.integer  "include_sale_tax",                                              default: 0,     null: false
+    t.integer  "include_purchase_tax",                                          default: 0,          null: false
+    t.integer  "include_sale_tax",                                              default: 0,          null: false
     t.string   "barcode"
     t.integer  "parent_id"
     t.text     "attributes_json"
@@ -447,6 +470,7 @@ ActiveRecord::Schema.define(version: 20181122030513) do
     t.decimal  "price_base_dollars",                   precision: 10, scale: 2
     t.string   "img_product"
     t.integer  "category_id"
+    t.string   "product_service_key",                                           default: "01010101"
   end
 
   add_index "products", ["unit_id"], name: "index_products_on_unit_id", using: :btree
@@ -560,19 +584,21 @@ ActiveRecord::Schema.define(version: 20181122030513) do
     t.integer  "customer_id"
     t.integer  "user_id"
     t.integer  "open_cash_register_id"
-    t.decimal  "amount",                          precision: 10, scale: 2, default: 0.0, null: false
-    t.decimal  "tax",                             precision: 10, scale: 2, default: 0.0, null: false
-    t.decimal  "discount",                        precision: 10, scale: 2, default: 0.0, null: false
-    t.decimal  "total",                           precision: 10, scale: 2, default: 0.0, null: false
-    t.integer  "status",                                                   default: 1,   null: false
-    t.date     "date_sale",                                                              null: false
-    t.datetime "created_at",                                                             null: false
-    t.datetime "updated_at",                                                             null: false
+    t.decimal  "amount",                          precision: 10, scale: 2, default: 0.0,   null: false
+    t.decimal  "tax",                             precision: 10, scale: 2, default: 0.0,   null: false
+    t.decimal  "discount",                        precision: 10, scale: 2, default: 0.0,   null: false
+    t.decimal  "total",                           precision: 10, scale: 2, default: 0.0,   null: false
+    t.integer  "status",                                                   default: 1,     null: false
+    t.date     "date_sale",                                                                null: false
+    t.datetime "created_at",                                                               null: false
+    t.datetime "updated_at",                                                               null: false
     t.integer  "saletype",              limit: 2
     t.integer  "seller_id"
     t.string   "sale_code"
     t.date     "expiration_date"
     t.string   "credit_note"
+    t.string   "invoice_num"
+    t.boolean  "require_invoice",                                          default: false
   end
 
   add_index "sales", ["customer_id"], name: "index_sales_on_customer_id", using: :btree
@@ -750,8 +776,9 @@ ActiveRecord::Schema.define(version: 20181122030513) do
   create_table "units", force: :cascade do |t|
     t.string   "unit"
     t.integer  "status"
-    t.datetime "created_at", null: false
-    t.datetime "updated_at", null: false
+    t.datetime "created_at",              null: false
+    t.datetime "updated_at",              null: false
+    t.string   "unit_key",   default: ""
   end
 
   create_table "users", force: :cascade do |t|

+ 37 - 1
db/seeds.rb

@@ -21,7 +21,43 @@
 	PosConfig.create(cancel_partial_payment: 30, refund_sale: 30, days_cancel_sale: 30, days_cancel_purchase: 30, tax_percent: 16, time_zone: "Mountain Time (US & Canada)", gain_margin: 20, reserve_sale_percent: 20, days_cancel_reserved: 30, haggle_in_sale_percent: 10, commission_percent: 10)
 
 	# METODOS DE PAGO
-	PaymentMethod.create([{method: "Tarjeta", status: 1, reference: 1, isCash: 0}, {method: "Efectivo", status: 1, reference: 0, isCash: 1}])
+	PaymentMethod.create([
+    {
+      method: "Tarjeta de debito",
+      status: 1,
+      reference: 1,
+      isCash: 0,
+      payment_method_key: "28"
+    },
+    {
+      method: "Efectivo",
+      status: 1,
+      reference: 0,
+      isCash: 1,
+      payment_method_key: "01"
+    },
+    {
+      method: "Transferencia bancaria",
+      status: 1,
+      reference: 1,
+      isCash: 0,
+      payment_method_key: "03"
+    },
+    {
+      method: "Tarjeta de credito",
+      status: 1,
+      reference: 1,
+      isCash: 0,
+      payment_method_key: "04"
+    },
+    {
+      method: "Cheque",
+      status: 1,
+      reference: 1,
+      isCash: 0,
+      payment_method_key: "02"
+    }
+  ])
 
 	# CLIENTE
 	Customer.create(nick_name: "Publico general", phone: "0000000000", email: 'cliente@clientes.com', credit: false, credit_limit: 0, time_limit: 0, notes: "ninguna", status: 1, is_public: 1)

BIN
facturacion/aaa010101aaa/aaa010101aaa__csd_01.cer


BIN
facturacion/aaa010101aaa/aaa010101aaa__csd_01.key


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 3 - 0
facturacion/cancelado.xml


+ 3 - 0
facturacion/generaxml.rb

@@ -0,0 +1,3 @@
+File.open('prueba.xml', 'w') do |f2|
+  f2.puts "Por que la vida \n puede ser maravillosa"
+end

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 80 - 0
facturacion/timbrado.xml