Jacqueline Maldonado 7 years ago
parent
commit
3b51e75b77

+ 80 - 24
app/controllers/application_controller.rb

@@ -37,15 +37,32 @@ class ApplicationController < ActionController::Base
 
   def find
     query = params[:query]
-    if query.include? ':'
-      # buscar con atributos
-      product_name = query[0, query.index(':') - 1]
-      attribute =  query[query.index(':') + 1, query.length]
+    if query.include? ':' # search with attributes
+      query_array = query.split(':')
+      product_name = query_array[0]
+      query_array.shift # delete the name of the product from the array to iterate the attributes
+      attrs_query_string = ''
+      query_array.each do |attribute|
+        if attribute.present?
+          attr_type = case attribute[0]
+                      when 'c'
+                        'colors'
+                      when 't'
+                        'sizes'
+                      when 'e'
+                        'styles'
+                      end
+          attribute[0] = "" # delete the attribute type character
+          attrs_query_string.concat(" AND attributes_json::json->>'#{attr_type}' ilike '%#{attribute}%'")
+        else
+          next
+        end
+      end
     else
       product_name = query
     end
 
-    render json: query.include?(":") ? Product.name_sku_barcode_attribute_like(product_name, attribute).limit(30).to_json(methods: [:small_img, :display_attributes]) : Product.name_sku_barcode_like(params[:query]).limit(30).to_json(methods: [:small_img, :display_attributes])
+    render json: query.include?(":") ? Product.name_sku_barcode_attribute_like(product_name, attrs_query_string).limit(30).to_json(methods: [:small_img, :display_attributes]) : Product.name_sku_barcode_like(params[:query]).limit(30).to_json(methods: [:small_img, :display_attributes])
   end
 
   # para special_prices
@@ -58,39 +75,78 @@ class ApplicationController < ActionController::Base
 
   def find_from_stock
     query = params[:query]
-    if query.include? ':'
-      # buscar con atributos
-      product_name = query[0, query.index(':') - 1]
-      attribute =  query[query.index(':') + 1, query.length]
+    if query.include? ':' # search with attributes
+      query_array = query.split(':')
+      product_name = query_array[0]
+      query_array.shift # delete the name of the product from the array to iterate the attributes
+      attrs_query_string = ''
+      query_array.each do |attribute|
+        if attribute.present?
+          attr_type = case attribute[0]
+                      when 'c'
+                        'colors'
+                      when 't'
+                        'sizes'
+                      when 'e'
+                        'styles'
+                      end
+          attribute[0] = "" # delete the attribute type character
+          attrs_query_string.concat(" AND attributes_json::json->>'#{attr_type}' ilike '%#{attribute}%'")
+        else
+          next
+        end
+      end
     else
       product_name = query
     end
 
     if current_user.usertype == 'S'
-      render json: query.include?(":") ? Warehouse.find(current_user.warehouse_id).products.name_sku_barcode_attribute_like(product_name, attribute).where("stock > 0").limit(30).to_json(methods: [:small_img, :display_attributes]) : Warehouse.find(current_user.warehouse_id).products.name_sku_barcode_like(params[:query]).where("stock > 0").limit(30).to_json(methods: [:small_img, :display_attributes])
+      render json: query.include?(":") ? Warehouse.find(current_user.warehouse_id).products.name_sku_barcode_attribute_like(product_name, attrs_query_string).where("stock > 0").limit(30).to_json(methods: [:small_img, :display_attributes]) : Warehouse.find(current_user.warehouse_id).products.name_sku_barcode_like(params[:query]).where("stock > 0").limit(30).to_json(methods: [:small_img, :display_attributes])
     else
-      render json: query.include?(":") ? Pointsale.find(current_user.pointsale_id).products.name_sku_barcode_attribute_like(product_name, attribute).where("stock > 0").limit(30).to_json(methods: [:small_img, :display_attributes]) : Pointsale.find(current_user.pointsale_id).products.name_sku_barcode_like(params[:query]).where("stock > 0").limit(30).to_json(methods: [:small_img, :display_attributes])
+      render json: query.include?(":") ? Pointsale.find(current_user.pointsale_id).products.name_sku_barcode_attribute_like(product_name, attrs_query_string).where("stock > 0").limit(30).to_json(methods: [:small_img, :display_attributes]) : Pointsale.find(current_user.pointsale_id).products.name_sku_barcode_like(params[:query]).where("stock > 0").limit(30).to_json(methods: [:small_img, :display_attributes])
     end
   end
 
+  # rubocop:disable Metrics/BlockNesting
   def find_from_stock_by_pointsale
-    id = params[:pointsale_id][2, params[:pointsale_id].length]
-
-    query = params[:query]
-    if query.include? ':'
-      # buscar con atributos
-      product_name = query[0, query.index(':') - 1]
-      attribute =  query[query.index(':') + 1, query.length]
-    else
-      product_name = query
-    end
+    if params[:pointsale_id].present?
+      id = params[:pointsale_id][2, params[:pointsale_id].length]
+      query = params[:query]
+      if query.include? ':' # search with attributes
+        query_array = query.split(':')
+        product_name = query_array[0]
+        query_array.shift # delete the name of the product from the array to iterate the attributes
+        attrs_query_string = ''
+        query_array.each do |attribute|
+          if attribute.present?
+            attr_type = case attribute[0]
+                        when 'c'
+                          'colors'
+                        when 't'
+                          'sizes'
+                        when 'e'
+                          'styles'
+                        end
+            attribute[0] = "" # delete the attribute type character
+            attrs_query_string.concat(" AND attributes_json::json->>'#{attr_type}' ilike '%#{attribute}%'")
+          else
+            next
+          end
+        end
+      else
+        product_name = query
+      end
 
-    if params[:pointsale_id].first == 'P'
-      render json: query.include?(":") ? Pointsale.find(id).products.name_sku_barcode_attribute_like(product_name, attribute).where("stock > 0").limit(30).to_json(methods: [:small_img, :display_attributes]) : Pointsale.find(id).products.name_sku_barcode_like(params[:query]).where("stock > 0").limit(30).to_json(methods: [:small_img, :display_attributes])
+      if params[:pointsale_id].first == 'P'
+        render json: query.include?(":") ? Pointsale.find(id).products.name_sku_barcode_attribute_like(product_name, attrs_query_string).where("stock > 0").limit(30).to_json(methods: [:small_img, :display_attributes]) : Pointsale.find(id).products.name_sku_barcode_like(params[:query]).where("stock > 0").limit(30).to_json(methods: [:small_img, :display_attributes])
+      else
+        render json: query.include?(":") ? Warehouse.find(id).products.name_sku_barcode_attribute_like(product_name, attrs_query_string).where("stock > 0").limit(30).to_json(methods: [:small_img, :display_attributes]) : Warehouse.find(id).products.name_sku_barcode_like(params[:query]).where("stock > 0").limit(30).to_json(methods: [:small_img, :display_attributes])
+      end
     else
-      render json: query.include?(":") ? Warehouse.find(id).products.name_sku_barcode_attribute_like(product_name, attribute).where("stock > 0").limit(30).to_json(methods: [:small_img, :display_attributes]) : Warehouse.find(id).products.name_sku_barcode_like(params[:query]).where("stock > 0").limit(30).to_json(methods: [:small_img, :display_attributes])
+      render json: {}
     end
   end
+  # rubocop:enable Metrics/BlockNesting
 
   def get_subcategories
     render json: params[:category_id] != '0' ? Category.activos.where("parent_id = ?", params[:category_id]) : Category.activos.where('parent_id != 0')

+ 6 - 0
app/controllers/cash_outs_controller.rb

@@ -184,6 +184,12 @@ class CashOutsController < ApplicationController
       payment["incoming"] = "0" if payment["incoming"].nil?
       payment["outgoing"] = "0" if payment["outgoing"].nil?
       payment["total"] = payment["incoming"].to_f - payment["outgoing"].to_f
+
+      # si es el efectivo sumarle el fondo
+      if payment["payment_method_id"].to_i == @cash_payment_method_id
+        payment["total"] += @initial_cash
+      end
+
       @cash_out.cash_out_details.build
     end
   end

+ 220 - 250
app/controllers/pointsales_controller.rb

@@ -1,252 +1,222 @@
 class PointsalesController < ApplicationController
-	##--- Abilities
-	load_and_authorize_resource
-
-	##--- Breadcrum_rails
-	add_breadcrumb I18n.t("breadcrumbs." + controller_name), :pointsales_path
-	add_breadcrumb "Nuevo Punto de venta" , :new_pointsale_path, only: :new
-	add_breadcrumb "Detalle del Punto de venta" , :pointsale_path, only: :show
-
-	before_action :set_pointsale, only: [:show, :edit, :update, :destroy, :update_products]
-	before_action :get_filters, only: [:index, :show, :edit, :new]
-	# before_action :get_available_products, only: [:edit, :update]
-
-	# GET /pointsales
-	# GET /pointsales.json
-	def index
-		@pointsales = Pointsale.vigentes
-	end
-
-	# GET /pointsales/1
-	# GET /pointsales/1.json
-	def show
-	end
-
-	# GET /pointsales/new
-	def new
-		@pointsale = Pointsale.new
-		# @pointsale.users.new
-	end
-
-	# GET /pointsales/1/edit
-	def edit
-		@todo = params[:todo]
-		add_breadcrumb ( @todo.present? ? "Actualizar productos del punto de venta " : "Editar Punto de venta") , :edit_pointsale_path, only: :edit
-	  	respond_to do |format|
-	    	format.html
-	    	format.json { render json: AvailableProductsDatatable.new(view_context, @pointsale) }
-	  	end
-	end
-
-	# POST /pointsales
-	# POST /pointsales.json
-	def create
-		@pointsale = Pointsale.new(pointsale_params)
-
-		@pointsale.skip_name_validation =  false
-		@pointsale.skip_products_validation =  false
-		@pointsale.users.first.skip_validations_from_pointsale = true
-		@pointsale.audit_comment = "El punto de venta " + @pointsale.name + " fue creado."
-		@pointsale.users.first.usertype = 'G'
-		@pointsale.users.first.status = 1
-		@pointsale.prefix.upcase!
-		respond_to do |format|
-			if @pointsale.save
-				@pointsale.users.first.pointsale_id = @pointsale.id
-				main_cash_register = CashRegister.new
-				main_cash_register.name = @pointsale.name + " Principal"
-				main_cash_register.description = "Caja registradora principal"
-				main_cash_register.pointsale_id = @pointsale.id
-				main_cash_register.main = 'yes'
-				main_cash_register.status = 'active'
-				main_cash_register.save
-
-				format.html { redirect_to pointsales_url, success: "El punto de venta " + @pointsale.name + " fue creado." }
-				format.json { render :show, status: :created, location: @pointsale }
-			else
-				format.html { render :new }
-				format.json { render json: @pointsale.errors, status: :unprocessable_entity }
-			end
-		end
-	end
-
-	# PATCH/PUT /pointsales/1
-	# PATCH/PUT /pointsales/1.json
-	def update
-		respond_to do |format|
-			if @pointsale.update(pointsale_params)
-				@pointsale.audit_comment = "El punto de venta #{@pointsale.name} fue modificado."
-				format.html { redirect_to pointsales_url, success: "El punto de venta #{@pointsale.name} fue modificado." }
-				format.json { render :show, status: :ok, location: @pointsale }
-			else
-				format.html { render :edit }
-				format.json { render json: @pointsale.errors, status: :unprocessable_entity }
-			end
-		end
-	end
-
-	def update_status
-	    pointsale = Pointsale.find(params[:pointsale_id])
-	    puts pointsale.active?
-    	if pointsale.active?
-    		pointsale.status = 2
-    	elsif pointsale.inactive?
-    		pointsale.status = 1
-    	end
-	    respond_to do |format|
-	    	if pointsale.save(:validate => false)
-	    		pointsale.users.each do |user|
-	    			if !user.erased?
-	    				user.status = pointsale.status
-	    				user.save(:validate => false)
-	    			end
-	    		end
-	        	format.html { redirect_to pointsales_url, warning: "El punto de venta " + pointsale.name + " fue "+ (pointsale.active? ? "activado" : "desactivado")+"." }
-				# format.json { render :show, status: :ok, location: @pointsale }
-				format.json { head :no_content }
-			else
-				format.html { redirect_to pointsales_url }
-				format.json { render json: @pointsale.errors, status: :unprocessable_entity }
-			end
-	    end
-	end
-
-	def transfer_stock
-		respond_to do |format|
-			destiny = params[:destiny][2, params[:destiny].length]
-
-			availables = AvailableProduct.where('id in (?)', params[:all_ids])
-
-			AvailableProduct.find(params[:ids_to_transfer]).each do |available|
-				if params[:destiny].first == 'P'
-					# es a punto de venta
-					stockProduct = AvailableProduct.find_by(:product_id => available.product_id, :pointsale_id => destiny)
-					if stockProduct.present?
-						stockProduct.stock += available.stock
-					else
-						stockProduct = available.dup
-						stockProduct.pointsale_id = destiny
-					end
-				else
-					# es a almacen
-					stockProduct = WarehouseStock.find_by(:product_id => available.product_id, :warehouse_id => destiny)
-					if stockProduct.present?
-						stockProduct.stock += available.stock
-					else
-						stockProduct = WarehouseStock.new
-						stockProduct.warehouse_id = destiny
-						stockProduct.product_id = available.product_id
-						stockProduct.stock_min = available.stock_min
-						stockProduct.stock_max = available.stock_max
-						stockProduct.stock = available.stock
-					end
-				end
-				stockProduct.save
-				available.destroy
-		 	end
-
-		 	availables_with_zero = params[:all_ids] - params[:ids_to_transfer]
-		 	if availables_with_zero.length > 0
-		 		AvailableProduct.where(id: availables_with_zero).delete_all
-		 	end
-		 	format.json { head :ok }
-		end
-	end
-
-  	def assign_or_delete_products
-  		@todo = params[:todo]
-  		if @todo == 'delete'
-  			@products = AvailableProduct.find(params[:ids])
-  		elsif @todo == 'assign'
-  			@products = Product.find(params[:ids])
-  		end
-
-	    @pointsale = Pointsale.find(params[:pointsale_id])
-  	end
-
-  	def assign_products_to_pointsale
-  		respond_to do |format|
-		    @products = JSON.parse params[:products]
-		    @products.each do | product |
-		    	available = AvailableProduct.new
-		    	available.product_id = product['id']
-		    	available.pointsale_id = params[:pointsale_id]
-		    	available.stock_min = 0
-		    	available.stock_max = 0
-		    	available.stock = product['stock']
-		    	available.save
-		    end
-		    format.json { head :ok }
-  		end
-  	end
-
-  	def delete_products_from_pointsale
-  		respond_to do |format|
-  			@ids = params[:ids]
-			@to_delete_with_stock = AvailableProduct.where("id in (?) and stock > 0", @ids)
-			@ids_array = Array.new
-			@ids.each do |id|
-				@ids_array << id
-			end
-			@ids_array = @ids_array.to_s
-
-			if @to_delete_with_stock.blank?
-				AvailableProduct.where(id: @ids).delete_all
-			end
-			format.js
-  		end
-  	end
-
-	# DELETE /pointsales/1
-	# DELETE /pointsales/1.json
-	def destroy
-		respond_to do |format|
-			@pointsale.skip_name_validation =  true
-			@pointsale.skip_products_validation =  true
-			@pointsale.audit_comment = "El punto de venta " + @pointsale.name + " fue eliminado."
-			if @pointsale.update_attributes(:status => 0)
-				CashRegister.where(:pointsale_id => @pointsale.id).update_all(:status => 'erased')
-				format.html { redirect_to pointsales_url, warning: "El punto de venta " + @pointsale.name + " fue eliminado." }
-				format.json { head :no_content }
-			else
-				format.html { render :edit }
-				format.json { render json: @pointsale.errors, status: :unprocessable_entity }
-			end
-		end
-	end
-
-	# GET /pointsales/1/update_products
-	# def update_products
-	#   respond_to do |format|
-	#     if @pointsale.save
-	#       #format.html { redirect_to @pointsale, notice: 'El punto de venta fue creado.' }
-	#       format.html { redirect_to pointsales_url, success: "El punto de venta " + @pointsale.name + " fue creado." }
-	#       format.json { render :show, status: :created, location: @pointsale }
-	#     else
-	#       format.html { render :update_products }
-	#       format.json { render json: @pointsale.errors, status: :unprocessable_entity }
-	#     end
-	#   end
-	# end
-
-
-	private
-		# Use callbacks to share common setup or constraints between actions.
-		def set_pointsale
-			@pointsale = Pointsale.find(params[:id])
-		end
-
-		def get_filters
-			if params[:current_page].blank?
-				@current_page = 1
-			else
-				@current_page = params[:current_page]
-			end
-			@filter = params[:filter]
-		end
-
-		# Never trust parameters from the scary internet, only allow the white list through.
-		def pointsale_params
-			params.require(:pointsale).permit(:name, :address, :notes, :status, :prefix, :img_pointsale, :img_pointsale_cache, :ticket_footer, :product_ids => [], users_attributes:[:userid, :first_name, :last_name, :password, :email, :password_confirmation])
-		end
+  ##--- Abilities
+  load_and_authorize_resource
+
+  ##--- Breadcrum_rails
+  add_breadcrumb I18n.t("breadcrumbs." + controller_name), :pointsales_path
+  add_breadcrumb "Nuevo Punto de venta", :new_pointsale_path, only: :new
+  add_breadcrumb "Detalle del Punto de venta", :pointsale_path, only: :show
+
+  before_action :set_pointsale, only: [:show, :edit, :update, :destroy, :update_products]
+  before_action :get_filters, only: [:index, :show, :edit, :new]
+  # before_action :get_available_products, only: [:edit, :update]
+
+  # GET /pointsales
+  # GET /pointsales.json
+  def index
+    @pointsales = Pointsale.vigentes
+  end
+
+  # GET /pointsales/1
+  # GET /pointsales/1.json
+  def show; end
+
+  # GET /pointsales/new
+  def new
+    @pointsale = Pointsale.new
+  end
+
+  # GET /pointsales/1/edit
+  def edit
+    @todo = params[:todo]
+    add_breadcrumb (@todo.present? ? "Actualizar productos del punto de venta " : "Editar Punto de venta"), :edit_pointsale_path, only: :edit
+    respond_to do |format|
+      format.html
+      format.json { render json: AvailableProductsDatatable.new(view_context, @pointsale) }
+    end
+  end
+
+  # POST /pointsales
+  # POST /pointsales.json
+  def create
+    @pointsale = Pointsale.new(pointsale_params)
+    @pointsale.skip_name_validation = false
+    @pointsale.skip_products_validation = false
+    @pointsale.users.first.skip_validations_from_pointsale = true
+    @pointsale.audit_comment = "El punto de venta " + @pointsale.name + " fue creado."
+    @pointsale.users.first.usertype = 'G'
+    @pointsale.users.first.status = 1
+    @pointsale.prefix.upcase!
+    respond_to do |format|
+      if @pointsale.save
+        @pointsale.users.first.pointsale_id = @pointsale.id
+        main_cash_register = CashRegister.new
+        main_cash_register.name = @pointsale.name + " Principal"
+        main_cash_register.description = "Caja registradora principal"
+        main_cash_register.pointsale_id = @pointsale.id
+        main_cash_register.main = 'yes'
+        main_cash_register.status = 'active'
+        main_cash_register.save
+
+        format.html { redirect_to pointsales_url, success: "El punto de venta " + @pointsale.name + " fue creado." }
+        format.json { render :show, status: :created, location: @pointsale }
+      else
+        format.html { render :new }
+        format.json { render json: @pointsale.errors, status: :unprocessable_entity }
+      end
+    end
+  end
+
+  # PATCH/PUT /pointsales/1
+  # PATCH/PUT /pointsales/1.json
+  def update
+    respond_to do |format|
+      if @pointsale.update(pointsale_params)
+        @pointsale.audit_comment = "El punto de venta #{@pointsale.name} fue modificado."
+        format.html { redirect_to pointsales_url, success: "El punto de venta #{@pointsale.name} fue modificado." }
+        format.json { render :show, status: :ok, location: @pointsale }
+      else
+        format.html { render :edit }
+        format.json { render json: @pointsale.errors, status: :unprocessable_entity }
+      end
+    end
+  end
+
+  def update_status
+    pointsale = Pointsale.find(params[:pointsale_id])
+    if pointsale.active?
+      pointsale.status = 2
+    elsif pointsale.inactive?
+      pointsale.status = 1
+    end
+    respond_to do |format|
+      if pointsale.save(validate: false)
+        pointsale.users.each do |user|
+          unless user.erased?
+            user.status = pointsale.status
+            user.save(validate: false)
+          end
+        end
+        format.html { redirect_to pointsales_url, warning: "El punto de venta " + pointsale.name + " fue " + (pointsale.active? ? "activado" : "desactivado") + "." }
+        # format.json { render :show, status: :ok, location: @pointsale }
+        format.json { head :no_content }
+      else
+        format.html { redirect_to pointsales_url }
+        format.json { render json: @pointsale.errors, status: :unprocessable_entity }
+      end
+    end
+  end
+
+  # rubocop:disable Metrics/BlockLength
+  def transfer_stock
+    respond_to do |format|
+      destiny = params[:destiny][2, params[:destiny].length]
+      availables = AvailableProduct.where('id in (?)', params[:all_ids])
+
+      AvailableProduct.find(params[:ids_to_transfer]).each do |available|
+        if params[:destiny].first == 'P' # es a punto de venta
+          stock_product = AvailableProduct.find_by(product_id: available.product_id, pointsale_id: destiny)
+          if stock_product.present?
+            stock_product.stock += available.stock
+          else
+            stock_product = available.dup
+            stock_product.pointsale_id = destiny
+          end
+        else
+          # es a almacen
+          stock_product = WarehouseStock.find_by(product_id: available.product_id, warehouse_id: destiny)
+          if stock_product.present?
+            stock_product.stock += available.stock
+          else
+            stock_product = WarehouseStock.new(warehouse_id: destiny, product_id: available.product_id, stock_min: available.stock_min, stock_max: available.stock_max, stock: available.stock)
+          end
+        end
+        stock_product.save
+        available.destroy
+      end
+
+      availables_with_zero = params[:all_ids] - params[:ids_to_transfer]
+      unless availables_with_zero.empty?
+        AvailableProduct.where(id: availables_with_zero).delete_all
+      end
+      format.json { head :ok }
+    end
+  end
+  # rubocop:enable Metrics/BlockLength
+
+  def assign_or_delete_products
+    @todo = params[:todo]
+    if @todo == 'delete'
+      @products = AvailableProduct.find(params[:ids])
+    elsif @todo == 'assign'
+      @products = Product.find(params[:ids])
+    end
+    @pointsale = Pointsale.find(params[:pointsale_id])
+  end
+
+  def assign_products_to_pointsale
+    respond_to do |format|
+      @products = JSON.parse params[:products]
+      @products.each do |product|
+        available = AvailableProduct.new
+        available.product_id = product['id']
+        available.pointsale_id = params[:pointsale_id]
+        available.stock_min = 0
+        available.stock_max = 0
+        available.stock = product['stock']
+        available.save
+      end
+      format.json { head :ok }
+    end
+  end
+
+  def delete_products_from_pointsale
+    respond_to do |format|
+      @ids = params[:ids]
+      @to_delete_with_stock = AvailableProduct.where("id in (?) and stock > 0", @ids)
+      @ids_array = Array.new
+      @ids.each do |id|
+        @ids_array << id
+      end
+      @ids_array = @ids_array.to_s
+
+      if @to_delete_with_stock.blank?
+        AvailableProduct.where(id: @ids).delete_all
+      end
+      format.js
+    end
+  end
+
+  # DELETE /pointsales/1
+  # DELETE /pointsales/1.json
+  def destroy
+    respond_to do |format|
+      @pointsale.skip_name_validation = true
+      @pointsale.skip_products_validation = true
+      @pointsale.audit_comment = "El punto de venta " + @pointsale.name + " fue eliminado."
+      if @pointsale.update_attributes(status: 0)
+        CashRegister.where(pointsale_id: @pointsale.id).update_all(status: 'erased')
+        format.html { redirect_to pointsales_url, warning: "El punto de venta " + @pointsale.name + " fue eliminado." }
+        format.json { head :no_content }
+      else
+        format.html { render :edit }
+        format.json { render json: @pointsale.errors, status: :unprocessable_entity }
+      end
+    end
+  end
+
+  private
+
+  def set_pointsale
+    @pointsale = Pointsale.find(params[:id])
+  end
+
+  def get_filters
+    @current_page = params[:current_page].blank? ? 1 : params[:current_page]
+    @filter = params[:filter]
+  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])
+  end
 end

+ 10 - 2
app/controllers/products_controller.rb

@@ -83,11 +83,13 @@ class ProductsController < ApplicationController
 
   # GET /products/1/edit
   def edit
-    @with_presentation = true
+    has_variants = @product.children.any?
+    @with_presentation = has_variants ? true : false # si aun no tiene variantes, permitir clickear el switch
   end
 
   # POST /products
   # POST /products.json
+  # rubocop:disable Metrics/BlockLength
   def create
     @with_presentation = true
     @product = Product.new(product_params)
@@ -98,7 +100,7 @@ class ProductsController < ApplicationController
     @product.audit_comment = message
     respond_to do |format|
       if @product.save
-        @product.save_variants
+        @product.save_variants(current_user)
         ##--- Para cuando se agrega un producto desde purchase
         if params[:remoto] == "true"
           ##--- Guardar pre_purchase
@@ -117,6 +119,10 @@ class ProductsController < ApplicationController
           format.json { head :no_content }
           format.js
         else
+          # crear available product para el p.v, cuando es sin variantes
+          if current_user.usertype == 'G' && !@product.presentation && !@product.is_parent
+            AvailableProduct.create(product_id: @product.id, pointsale_id: current_user.pointsale_id, stock: 0)
+          end
           format.html { redirect_to products_url, success: message }
           format.json { render :show, status: :created, location: @product }
         end
@@ -126,12 +132,14 @@ class ProductsController < ApplicationController
       end
     end
   end
+  # rubocop:enable Metrics/BlockLength
 
   # PATCH/PUT /products/1
   # PATCH/PUT /products/1.json
   def update
     respond_to do |format|
       @product.skip_sku_validation = true
+      @product.is_parent = params[:product][:presentation] == 'true' ? true : false
       message = 'El producto ' + @product.sku + ' fue modificado.'
       @product.audit_comment = message
       if @product.update(product_params)

+ 2 - 1
app/controllers/sales_controller.rb

@@ -325,7 +325,8 @@ class SalesController < ApplicationController
 
   def add_haggle
     @pre_sale = PreSale.find(params[:pre_sale])
-    @suggested_haggle = (@pos_config.haggle_in_sale_percent.to_f / 100) * @pre_sale.unit_price
+    @haggle_percent = current_user.pointsale.haggle_percent.zero? ? @pos_config.haggle_in_sale_percent : current_user.pointsale.haggle_percent
+    @suggested_haggle = (@haggle_percent.to_f / 100) * @pre_sale.unit_price
   end
 
   def create_haggle

+ 27 - 32
app/controllers/users_controller.rb

@@ -2,31 +2,26 @@ class UsersController < ApplicationController
   ##--- Breadcrum_rails
   add_breadcrumb I18n.t("breadcrumbs." + controller_name), :users_path
   add_breadcrumb "Cambio de contraseña", :pwdchange_path, only: :update_password
-  add_breadcrumb "Nuevo Usuario" , :new_user_path, only: :new
-  add_breadcrumb "Editar usuario"  , :edit_user_path, only: :edit
+  add_breadcrumb "Nuevo Usuario", :new_user_path, only: :new
+  add_breadcrumb "Editar usuario", :edit_user_path, only: :edit
 
   before_action :set_user, only: [:edit, :update]
   before_action :get_filters, only: [:index, :show, :edit, :new]
 
   def index
-    if current_user.usertype == 'A'
-      @users = User.includes(:pointsale, :warehouse).where('status > 0').order("id desc")
-    else
-      @users = User.includes(:pointsale, :warehouse).where('status > 0 and pointsale_id = ?', current_user.pointsale_id).order("id desc")
-    end
+    @users = current_user.usertype == 'A' ? User.includes(:pointsale, :warehouse).where('status > 0').order("id desc") : User.includes(:pointsale, :warehouse).where('status > 0 and pointsale_id = ?', current_user.pointsale_id).order("id desc")
   end
 
   def new
     @user = User.new
   end
 
-  def edit
-  end
+  def edit; end
 
   def update
     respond_to do |format|
       @user.skip_validations_when_edit = true
-      message =  "Usuario #{@user.userid} ha sido modificado."
+      message = "Usuario #{@user.userid} ha sido modificado."
       @user.audit_comment = message
       if @user.update(user_params)
         format.html { redirect_to users_path, success: message }
@@ -41,10 +36,14 @@ class UsersController < ApplicationController
   def create
     @user = User.new(user_params)
     respond_to do |format|
-      message = "Usuario #{@user.userid} creado y asignado al " + ( @user.pointsale.present? ? "punto de venta #{@user.pointsale.name}" : "almacén #{@user.warehouse.name}")
+      message = if @user.usertype == 'A'
+                  "usuario #{@user.userid} con perfil ADMINISTRADOR creado."
+                else
+                  "Usuario #{@user.userid} creado y asignado al " + (@user.pointsale.present? ? "punto de venta #{@user.pointsale.name}" : "almacén #{@user.warehouse.name}")
+                end
       @user.audit_comment = message
       if @user.save
-        format.html { redirect_to users_path, success: message}
+        format.html { redirect_to users_path, success: message }
         format.json { render :show, status: :created, location: @user }
       else
         format.html { render :new }
@@ -62,9 +61,9 @@ class UsersController < ApplicationController
     respond_to do |format|
       message = "La contraseña de " + @user.full_name + " fue modificada."
       @user.audit_comment = message
-      if  @user.update_with_password(user_params)
+      if @user.update_with_password(user_params)
         # Sign in the user by passing validation in case their password changed
-        sign_in @user, :bypass => true
+        sign_in @user, bypass: true
         format.html { redirect_to root_path, success: message }
         # format.json { render :show, status: :created, location: @user }
       else
@@ -82,9 +81,9 @@ class UsersController < ApplicationController
       user.status = "active"
     end
     respond_to do |format|
-      message = "El usuario " + user.userid + " fue "+ (user.active? ? "activado" : "desactivado")+"."
+      message = "El usuario " + user.userid + " fue " + (user.active? ? "activado" : "desactivado") + "."
       user.audit_comment = message
-      if user.save(:validate => false)
+      if user.save(validate: false)
         format.html { redirect_to users_path, warning: message }
         format.json { head :no_content }
       else
@@ -96,22 +95,18 @@ class UsersController < ApplicationController
 
   private
 
-    # Use callbacks to share common setup or constraints between actions.
-    def set_user
-      @user = User.find(params[:id])
-    end
+  # Use callbacks to share common setup or constraints between actions.
+  def set_user
+    @user = User.find(params[:id])
+  end
 
-    def get_filters
-      if params[:current_page].blank?
-        @current_page = 1
-      else
-        @current_page = params[:current_page]
-      end
-      @filter = params[:filter]
-    end
+  def get_filters
+    @current_page = params[:current_page].blank? ? 1 : params[:current_page]
+    @filter = params[:filter]
+  end
 
-    def user_params
-      # NOTE: Using `strong_parameters` gem
-      params.require(:user).permit(:current_password, :password, :password_confirmation, :pointsale_id, :warehouse_id, :usertype, :userid, :first_name, :last_name, :email )
-    end
+  def user_params
+    # NOTE: Using `strong_parameters` gem
+    params.require(:user).permit(:current_password, :password, :password_confirmation, :pointsale_id, :warehouse_id, :usertype, :userid, :first_name, :last_name, :email)
+  end
 end

+ 31 - 23
app/datatables/available_products_datatable.rb

@@ -1,12 +1,12 @@
 class AvailableProductsDatatable
-  delegate :params,:fa_icon, :available_products_path, to: :@view
-  
+  delegate :params, :fa_icon, :available_products_path, to: :@view
+
   def initialize(view, pointsale)
     @view = view
     @pointsale = pointsale
   end
 
-  def as_json(options = {})
+  def as_json(*)
     {
       sEcho: params[:sEcho].to_i,
       iTotalRecords: @pointsale.products.activos_children.size,
@@ -15,39 +15,39 @@ class AvailableProductsDatatable
     }
   end
 
-private
+  private
 
   def data
     if params[:table] == 'in_pointsale'
-      products.map do | available |
+      products.map do |available|
         category = available.product.categories[0]
         {
           'DT_RowId' => "available_#{available.id}",
           '0' => '<input class="form-control checkboxes" type="checkbox"/>',
-          '1' => available.product.sku,          
+          '1' => available.product.sku,
           '2' => get_display_name(available.product),
-          '3' => (category.parent_id == 0 ? category.category : category.parent.category),
+          '3' => (category.parent_id.zero? ? category.category : category.parent.category),
           '4' => (category.parent_id != 0 ? category.category : ' '),
           '5' => available.stock
-        }.compact.reject { |k, v| v.nil? } 
+        }.compact.reject { |_k, v| v.nil? }
       end
 
     else
-      products.map do | product |
+      products.map do |product|
         category = product.categories[0]
         {
           'DT_RowId' => "product_#{product.id}",
           '0' => '<input class="form-control checkboxes" type="checkbox"/>',
           '1' => product.sku,
-          '2' => get_display_name(product),          
-          '3' => (category.parent_id == 0 ? category.category : category.parent.category),
+          '2' => get_display_name(product),
+          '3' => (category.parent_id.zero? ? category.category : category.parent.category),
           '4' => (category.parent_id != 0 ? category.category : ' ')
-        }.compact.reject { |k, v| v.nil? } 
+        }.compact.reject { |_k, v| v.nil? }
       end
     end
   end
 
-  def get_display_name(product) 
+  def get_display_name(product)
     name = "<label style='margin-bottom:0px'><strong>#{product.name}</strong></label> <br>"
 
     if product.display_attributes.present?
@@ -55,13 +55,13 @@ private
     end
 
     if product.barcode.present?
-      name += "<i class='fa fa-barcode'></i>: #{product.barcode} <br>" 
+      name += "<i class='fa fa-barcode'></i>: #{product.barcode} <br>"
     end
 
     if product.description.present?
       name += "Descripción: #{product.description}"
     end
-    return name
+    name
   end
 
   def products
@@ -69,23 +69,32 @@ private
   end
 
   def fetch_products
-
     if params[:table] == 'in_pointsale'
-      products = AvailableProduct.activos.where(:pointsale_id => params[:id]).activos
+      products = AvailableProduct.activos.where(pointsale_id: params[:id]).activos
     else
       products_in_pointsale = @pointsale.products.activos_children.pluck(:id)
-      products = Product.activos_children.where.not(id: products_in_pointsale)      
+      products = Product.activos_children.where.not(id: products_in_pointsale)
     end
 
     products = products.page(page).per_page(per_page)
-    unless params[:busqueda].blank?
-      products = products.where("products.sku ilike :search or products.name ilike :search", search: "%#{params[:busqueda]}%").order('products.name')
+    search = params[:busqueda]
+    name_searched = if search.include? ':'
+                      search[0, search.index(':') - 1]
+                    else
+                      search
+                    end
+    unless search.blank?
+      products = products.where("products.sku ilike :search or products.name ilike :search", search: "%#{name_searched}%").order('products.name')
+      if search.include? ':'
+        attribute = search[search.index(':') + 1, search.length]
+        products = products.where('attributes_json ilike :attribute', attribute: "%#{attribute}%")
+      end
     end
     products
   end
 
   def page
-    params[:start].to_i/per_page + 1
+    params[:start].to_i / per_page + 1
   end
 
   def per_page
@@ -100,5 +109,4 @@ private
   def sort_direction
     params[:sSortDir_0] == "desc" ? "desc" : "asc"
   end
-
-end
+end

+ 2 - 2
app/models/ability.rb

@@ -42,9 +42,9 @@ class Ability
 
     elsif user.usertype == "G"
       # Cajas registradoras
-      can :manage, [CashRegister, Purchase, Product, PrePurchase, Seller, Sale, Expense, ProductWaste, Transfer, OpenCashRegister, CashOut, Supplier, Customer, Credit, CreditPayment, Commission, Sellerscommission, ProductsReturn]
+      can :manage, [CashRegister, Purchase, Product, PrePurchase, Seller, Sale, Expense, ProductWaste, Transfer, OpenCashRegister, CashOut, Supplier, Customer, Credit, CreditPayment, Commission, Sellerscommission, ProductsReturn, Category]
       # Categorias
-      can :read, [Category, SpecialPrice, Expensesconcept, Credit, CreditPayment, Unit]
+      can :read, [SpecialPrice, Expensesconcept, Credit, CreditPayment, Unit]
       # Clientes
       can :cru, [Customer, BillingInformation, Pointsale, User, Warehouse, Credit, CreditPayment, Commission, Sellerscommission]
 

+ 55 - 46
app/models/product.rb

@@ -52,10 +52,10 @@ class Product < ActiveRecord::Base
   ##--- Tipo de vistas / consultas
   scope :vigentes, -> { where("status != 0").order(" status ASC, name ASC") }
   scope :activos, -> { where("status = 1").order(" name ASC") }
-  scope :activos_children, -> { where("status = 1 and is_parent = false ").order("name ASC") }
+  scope :activos_children, -> { where("status = 1 and is_parent = false ").order("products.name ASC") }
   scope :vigentes_parents, -> { where("status != 0 and parent_id IS NULL ").order(" status ASC, name ASC") }
   scope :name_sku_barcode_like, ->(name) { where("status = 1 and is_parent = false and (name ilike ? or sku ilike ? or barcode ilike ?)", "%#{name}%", "%#{name}%", "%#{name}%").order("name") }
-  scope :name_sku_barcode_attribute_like, ->(name, attribute) { where("status = 1 and is_parent = false and (name ilike ? or sku ilike ? or barcode ilike ?) and attributes_json ilike ?", "%#{name}%", "%#{name}%", "%#{name}%", "%#{attribute}%").order("name") }
+  scope :name_sku_barcode_attribute_like, ->(name, attributes_string) { where("status = 1 and is_parent = false and (name ilike ? or sku ilike ? or barcode ilike ?) #{attributes_string}", "%#{name}%", "%#{name}%", "%#{name}%").order("name") }
   # para special_prices
   scope :name_sku_barcode_like_sp, ->(name) { where("status = 1 and is_parent = true and (name ilike ? or sku ilike ? or barcode ilike ?)", "%#{name}%", "%#{name}%", "%#{name}%").order("name") }
 
@@ -163,7 +163,8 @@ class Product < ActiveRecord::Base
     end
   end
 
-  def save_variants
+  # rubocop:disable Metrics/BlockLength
+  def save_variants(current_user)
     Thread.new do
       combinations = Array.new
       attributes = Array.new
@@ -190,10 +191,14 @@ class Product < ActiveRecord::Base
 
           @products_variant.attributes_json = attributes_json.to_json
           @products_variant.save
+          if current_user.usertype == 'G'
+            AvailableProduct.create(product_id: @products_variant.id, pointsale_id: current_user.pointsale_id, stock: 0)
+          end
         end
       end
     end
   end
+  # rubocop:enable Metrics/BlockLength
 
   def assign_attributes_to_variant(attri, parent_id, attributes_json)
     attri_id = ActsAsTaggableOn::Tag.where("lower(name) = lower(?)", attri).select(:id).first
@@ -276,61 +281,65 @@ class Product < ActiveRecord::Base
     end
   end
 
+  # rubocop:disable Metrics/BlockLength
   def save_new_attributes(new_sizes, new_colors, new_styles)
-    combinations = Array.new
-    attributes = Array.new
+    Thread.new do
+      combinations = Array.new
+      attributes = Array.new
 
-    unless new_sizes.nil?
-      new_sizes.each do |s|
-        size_list.add(s.to_s)
-        save(validate: false)
+      unless new_sizes.nil?
+        new_sizes.each do |s|
+          size_list.add(s.to_s)
+          save(validate: false)
+        end
       end
-    end
-    unless new_colors.nil?
-      new_colors.each do |s|
-        color_list.add(s.to_s)
-        save(validate: false)
+      unless new_colors.nil?
+        new_colors.each do |s|
+          color_list.add(s.to_s)
+          save(validate: false)
+        end
       end
-    end
-    unless new_styles.nil?
-      new_styles.each do |s|
-        style_list.add(s.to_s)
-        save(validate: false)
+      unless new_styles.nil?
+        new_styles.each do |s|
+          style_list.add(s.to_s)
+          save(validate: false)
+        end
       end
-    end
 
-    combinations = get_combinations(combinations, attributes)
-    # self.save(:validate: false)
-    combinations.each_with_index do |combination, index|
-      attributes = {}
-      if combination.is_a?(Array)
-        combination.each do |c|
-          attributes = assign_attributes_to_variant(c, id, attributes)
+      combinations = get_combinations(combinations, attributes)
+      # self.save(:validate: false)
+      combinations.each_with_index do |combination, index|
+        attributes = {}
+        if combination.is_a?(Array)
+          combination.each do |c|
+            attributes = assign_attributes_to_variant(c, id, attributes)
+          end
+        else
+          attributes = assign_attributes_to_variant(combination, id, attributes)
         end
-      else
-        attributes = assign_attributes_to_variant(combination, id, attributes)
-      end
 
-      next unless children.where("attributes_json = ?", attributes.to_json).select(:id).first.nil?
-      # if children.where("attributes_json = ?", attributes.to_json).select(:id).first.nil?
-      @products_variant = Product.new
-      @products_variant = dup
-      @products_variant.parent_id = id
-      @products_variant.is_parent = false
-      @products_variant.sku = sku + (index + 1).to_s + "A"
-      @products_variant.category_ids = category_ids
-      attrs_json = {}
-      if combination.is_a?(Array)
-        combination.each do |attrib|
-          attrs_json = @products_variant.assign_attributes_to_variant(attrib, id, attrs_json)
+        next unless children.where("attributes_json = ?", attributes.to_json).select(:id).first.nil?
+        # if children.where("attributes_json = ?", attributes.to_json).select(:id).first.nil?
+        @products_variant = Product.new
+        @products_variant = dup
+        @products_variant.parent_id = id
+        @products_variant.is_parent = false
+        @products_variant.sku = sku + (index + 1).to_s + "A"
+        @products_variant.category_ids = category_ids
+        attrs_json = {}
+        if combination.is_a?(Array)
+          combination.each do |attrib|
+            attrs_json = @products_variant.assign_attributes_to_variant(attrib, id, attrs_json)
+          end
+        else
+          attrs_json = @products_variant.assign_attributes_to_variant(combination, id, attrs_json)
         end
-      else
-        attrs_json = @products_variant.assign_attributes_to_variant(combination, id, attrs_json)
+        @products_variant.attributes_json = attributes.to_json
+        @products_variant.save(validate: false)
       end
-      @products_variant.attributes_json = attributes.to_json
-      @products_variant.save(validate: false)
     end
   end
+  # rubocop:enable Metrics/BlockLength
 
   def children
     Product.where("parent_id = ? and status != 0 ", id)

+ 51 - 55
app/models/user.rb

@@ -1,68 +1,64 @@
 class User < ActiveRecord::Base
+  # Include default devise modules. Others available are:
+  # :confirmable, :lockable, :timeoutable and :omniauthable
+  devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable
+  devise :session_limitable # other devise options
 
-	# Include default devise modules. Others available are:
-	# :confirmable, :lockable, :timeoutable and :omniauthable
-	devise :database_authenticatable, :registerable,
-				 :recoverable, :rememberable, :trackable, :validatable
-	devise :session_limitable # other devise options
+  before_update :warehouse_or_pointsale
 
-	before_update :warehouse_or_pointsale
+  #-- Associations
+  belongs_to :pointsale
+  belongs_to :warehouse
+  has_many :pre_sales
+  has_many :product_wastes
+  has_many :pre_purchases
+  has_many :open_cash_registers
+  has_many :credit_payments
+  has_many :cash_outs
+  has_many :special_prices
+  has_many :transfers
+  has_many :commissions
+  has_many :products_returns
 
-	#-- Associations
-	belongs_to :pointsale
-	belongs_to :warehouse
-	has_many :pre_sales
-	has_many :product_wastes
-	has_many :pre_purchases
-	has_many :open_cash_registers
-	has_many :credit_payments
-	has_many :cash_outs
-	has_many :special_prices
-	has_many :transfers
-	has_many :commissions
-	has_many :products_returns
+  enum status: [:erased, :active, :inactive]
+  ##--- Llevar registro de Actividad del usuario
+  audited
 
-	enum status: [ :erased, :active, :inactive ]
-	##--- Llevar registro de Actividad del usuario
-	audited
+  ##--- Validaciones previas de guardar
+  attr_accessor :skip_validations_from_pointsale
+  attr_accessor :skip_validations_when_edit
+  validates_presence_of :userid, message: "Debe especificar el nombre de usuario, es con el que el usuario iniciará sesión."
+  validates :password, presence: { message: "Debe especificar la contraseña nueva" },  length: { within: Devise.password_length, too_short: "La contraseña no puede ser menor a %{count}." }, unless: :skip_validations_when_edit
+  validates :password_confirmation, presence: { message: "Debe confirmar la contraseña nueva" }, unless: :skip_validations_when_edit
+  # validates_presence_of :pointsale_id, message: "Debe seleccionar el punto de venta al que pertenecera el usuario.", on: [:create, :update], unless: :skip_validations_from_pointsale
+  validates_presence_of :usertype, message: "Debe seleccionar el tipo de usuario, con este se determinara los permisos que tendrá dentro del sistema.", on: [:create, :update], unless: :skip_validations_from_pointsale
 
-	##--- Validaciones previas de guardar
-	attr_accessor :skip_validations_from_pointsale
-	attr_accessor :skip_validations_when_edit
-	validates_presence_of :userid, message: "Debe especificar el nombre de usuario, es con el que el usuario iniciará sesión."
-	validates :password, :presence => { :message => "Debe especificar la contraseña nueva"},  length: { :within => Devise.password_length, too_short: "La contraseña no puede ser menor a %{count}." }, unless: :skip_validations_when_edit
-	validates :password_confirmation, :presence => { :message => "Debe confirmar la contraseña nueva"}, unless: :skip_validations_when_edit
-	# validates_presence_of :pointsale_id, message: "Debe seleccionar el punto de venta al que pertenecerá el usuario.", :on => [:create, :update], unless: :skip_validations_from_pointsale
-	validates_presence_of :usertype, message: "Debe seleccionar el tipo de usuario, con este se determinara los permisos que tendrá dentro del sistema.", :on => [:create, :update], unless: :skip_validations_from_pointsale
+  validates :pointsale_id, presence: { message: "Debe seleccionar un almacén o un punto de venta al que pertenecerá el usuario." }, if: proc { |c| c.warehouse_id.blank? && c.usertype != 'A' }, on: [:create, :update], unless: :skip_validations_from_pointsale
+  validates :warehouse_id, presence: { message: "Debe seleccionar un almacén o un punto de venta al que pertenecerá el usuario." }, if: proc { |c| c.pointsale_id.blank? && c.usertype != 'A' }, on: [:create, :update], unless: :skip_validations_from_pointsale
 
-  validates :pointsale_id , presence: { message: "Debe seleccionar un almacén o un punto de venta al que pertenecerá el usuario." },
-  :if => Proc.new {|c| c.warehouse_id.blank?}, :on => [:create, :update], unless: :skip_validations_from_pointsale
-  validates :warehouse_id , presence: { message: "Debe seleccionar un almacén o un punto de venta al que pertenecerá el usuario." },
-  :if => Proc.new {|c| c.pointsale_id.blank?}, :on => [:create, :update], unless: :skip_validations_from_pointsale
+  validates :userid, uniqueness: { message: "El usuario ya fue utilizado, favor de especificar otro." }
 
-	validates :userid, uniqueness: { message: "El usuario ya fue utilizado, favor de especificar otro." }
+  def full_name
+    "#{first_name} #{last_name}"
+  end
 
-	def full_name
-		"#{first_name} #{last_name}"
-	end
+  def get_open_cash_register
+    open_cash_registers.where(status: 0).last
+  end
 
-	def get_open_cash_register
-		self.open_cash_registers.where(:status => 0).last
-	end
+  def warehouse_or_pointsale
+    if pointsale_id.present?
+      self.warehouse_id = nil
+    elsif warehouse_id.present?
+      self.pointsale_id = nil
+    end
+  end
 
-	def warehouse_or_pointsale
-		if pointsale_id.present?
-			self.warehouse_id = nil
-		elsif warehouse_id.present?
-			self.pointsale_id = nil
-		end
-	end
+  def active_for_authentication?
+    super && active?
+  end
 
-	def active_for_authentication?
-		super && active?
-	end
-
-	def inactive_message
-		"Usuario desactivado, contacte al administrador para activar su cuenta"
-	end
+  def inactive_message
+    "Usuario desactivado, contacte al administrador para activar su cuenta"
+  end
 end

+ 4 - 4
app/views/cash_outs/_form.html.erb

@@ -79,12 +79,12 @@
         <div class="table-scrollable">
             <table class="table table-hover table-striped table-bordered" id="payment_methods_table">
                 <thead>
-                    <tr class="uppercase">
+                    <tr>
                         <th> # </th>
                         <th> Método de pago </th>
-                        <th> Total de ingresos </th>
-                        <th> Total de egresos </th>
-                        <th>Total</th>
+                        <th> Total de ingresos (ventas) </th>
+                        <th> Total de egresos (gastos) </th>
+                        <th>Total (ventas y efectivo inicial)</th>
                     </tr>
                 </thead>
                 <tbody>

+ 7 - 2
app/views/pointsales/_assign_delete_prods.html.erb

@@ -45,7 +45,10 @@
 											<div class="col-md-12">
 												<div class="note note-success">
 													<h4>Productos disponibles para asignar al punto de venta</h4>
-													<p> En esta tabla se muestran todos los productos disponibles para asignarse al punto de venta para vender. </p>
+													<p> En esta tabla se muestran todos los productos disponibles para asignarse al punto de venta para vender.</p><br>
+													<p> Para hacer una busqueda avanzada utiliza el siguiente formato, nombre del producto :atributo a buscar<br>
+														ejemplo. <strong>blusa :verde</strong> (Solo se puede buscar un atributo, ya sea estilo, talla ó color).
+													</p>
 												</div>
 											</div>
 											<div class="col-md-12">
@@ -79,7 +82,9 @@
 											<div class="col-md-12">
 												<div class="note note-success">
 													<h4>Productos asignados al punto de venta</h4>
-													<p> En esta tabla se muestran todos los productos que se han asignado al punto de venta para vender. </p>
+													<p> En esta tabla se muestran todos los productos que se han asignado al punto de venta para vender. </p><br>
+													<p> Para hacer una busqueda avanzada utiliza el siguiente formato, nombre del producto :atributo a buscar<br>
+														ejemplo. <strong>blusa :verde</strong> (Solo se puede buscar un atributo, ya sea estilo, talla ó color).
 												</div>
 											</div>
 											<div class="col-md-12">

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

@@ -31,6 +31,12 @@
 					<%= f.text_field :address, {:class=>"form-control"} %>
 				</div>
 			</div>
+			<div class="form-group">
+				<%= f.label :haggle_percent, {:class=>"col-md-3 control-label"} do %> Porcentaje de regateo para ventas <% end %>
+				<div class="col-md-2">
+					<%= f.number_field :haggle_percent, {:class=>"form-control" }  %>
+				</div>
+			</div>
 			<div class="form-group">
 				<%= f.label :notes, "Encabezado para el ticket", {:class=>"col-md-3 control-label"} %>
 				<div class="col-md-9">

+ 21 - 21
app/views/product_wastes/new.html.erb

@@ -22,7 +22,7 @@
 				<!-- BEGIN PAGE BREADCRUMBS -->
 				<ul class="page-breadcrumb breadcrumb">
 					<%= render_breadcrumbs :tag => :li, :separator => ' <i class="fa fa-circle"></i> ' %>
-				</ul>    
+				</ul>
 				<!-- END PAGE BREADCRUMBS -->
 				<!-- BEGIN PAGE CONTENT INNER -->
 				<div class="page-content-inner">
@@ -38,37 +38,37 @@
 									</div>
 								</div>
 								<div class="portlet-body form form-horizontal">
-									<div id="error_explanation"></div> 
+									<div id="error_explanation"></div>
 										<div class="form-body">
 											<h4 class="form-section"> Agregar producto</h4>
 											<div class="row">
 												<div class="col-md-12">
 													<div class="form-group">
 														<%= label_tag "product",  {:class=>"col-md-3 control-label"} do %>Producto <span class="required">*</span>
-														<% end %> 
-														<div class="col-md-6"> 
-															<input class="form-control" type="text" id="typeahead" data-option-url="/find_products_from_stock/%QUERY"> 
-														</div>		                    		
+														<% end %>
+														<div class="col-md-6">
+															<input class="form-control" type="text" id="typeahead">
+														</div>
 													</div>
 													<div class="form-group">
 														<%= label_tag "quantity",  {:class=>"col-md-3 control-label"} do %>Cantidad mermada <span class="required">*</span>
-														<% end %> 
+														<% end %>
 														<div class="col-md-9">
 															<%= number_field_tag "quantity", "", class: 'form-control input-small' %>
 														</div>
 													</div>
 													<div class="form-group">
 														<%= label_tag "reason", {:class=>"col-md-3 control-label"} do %> Motivo <span class="required">*</span>
-														<% end %> 
+														<% end %>
 														<div class="col-md-6">
 															<%= text_area_tag "reason", "", class: 'form-control', rows: 5 %>
 														</div>
-													</div> 
+													</div>
 													<div class="form-group">
 														<div class="col-md-offset-3 col-md-9">
 															<button id="products_new" disabled class="btn btn-success" type="button" onclick="addRow()"> Agregar <i class="fa fa-plus"></i> </button>
 														</div>
-													</div>									                   	
+													</div>
 												</div>
 											</div>
 											<!-- lista de productos -->
@@ -94,11 +94,11 @@
 													<%= render 'form' %>
 												</div>
 											</div>
-										</div>	
+										</div>
 										<div class="form-actions">
 										</div>
-									</div>              	
-								</div>  
+									</div>
+								</div>
 							</div>
 						</div>
 					</div>
@@ -119,7 +119,7 @@
 		},
 		queryTokenizer: Bloodhound.tokenizers.whitespace,
 		remote: {
-			url: $('#typeahead').data('option-url'),
+			url: '/find_products_from_stock?query=%QUERY',
 			wildcard: '%QUERY'
 		}
 	});
@@ -129,11 +129,11 @@
 	$('#typeahead').typeahead(
 		{
 			minLength: 3
-		}, 
+		},
 		{
 			displayKey: 'name',
 			source: bloodhound.ttAdapter(),
-			limit: Infinity,    
+			limit: Infinity,
 			templates: {
 				empty: [
 					'<div class="empty-message">',
@@ -151,7 +151,7 @@
 									'<h4 class="media-heading"><strong>{{name}}</strong> | {{sku}}</h4>' +
 									'<p>{{barcode}}</p>' +
 									'<p>{{description}}</p>' +
-									'<p>{{display_attributes}}</p>' +                  
+									'<p>{{display_attributes}}</p>' +
 							'</div>' +
 						'</div>')
 			}
@@ -169,13 +169,13 @@
 			$('#product_waste_quantity').val($('#quantity').val());
 			$('#product_waste_reason').val($('#reason').val());
 			$('#new_product_waste').submit();
-		$('#typeahead').typeahead('val',''); 
+		$('#typeahead').typeahead('val','');
 				$('#products_new').attr('disabled', true);
 				$('#quantity').val("");
 				$('#reason').val("");
 		} else {
 		toastr["error"]("Error, Se debe indicar la cantidad.");
-		} 
+		}
 	}
 
 		function deleteRow(input) {
@@ -190,10 +190,10 @@
 				success: function(xhr, status, error) {
 					table.row(input.closest('tr')).remove().draw();
 				}
-			});    
+			});
 		}
 
-	 
+
 </script>
 
 

+ 1 - 1
app/views/products/_form.html.erb

@@ -53,7 +53,7 @@
 						<%= f.label :unit_id, "Unidad de medida", {:class=>"col-md-3 control-label"} do %>Unidad de medida <span class="required">*</span>
 						<% end %>
 						<div class="col-md-9">
-							<%= f.collection_select :unit_id, Unit.all, :id, :unit, {:prompt => "Seleccione"}, {:class => "form-control input-medium"}   %>
+							<%= f.collection_select :unit_id, Unit.vigentes, :id, :unit, {:prompt => "Seleccione"}, {:class => "form-control input-medium"}   %>
 						</div>
 					</div>
 				</div>

+ 1 - 1
app/views/products/_form_remote.html.erb

@@ -59,7 +59,7 @@
 						<span class="required">*</span>
 					<% end %>
 					<div class="col-md-9">
-						<%= f.collection_select :unit_id, Unit.all, :id, :unit, {:prompt => "Seleccione"}, {:class => "form-control input-medium"}   %>
+						<%= f.collection_select :unit_id, Unit.vigentes, :id, :unit, {:prompt => "Seleccione"}, {:class => "form-control input-medium"}   %>
 					</div>
 				</div>
 			</div>

+ 3 - 6
app/views/products/edit_variants.html.erb

@@ -104,7 +104,7 @@
 												Son <strong id="variantes"></strong> variantes del producto.
 											</div>
 										</div>
-										<% if @product.sizes.count > 0 %>
+
 										<div class="form-group ">
 											<%= label_tag :size_list, "Tallas", {:class=>"col-md-3 control-label"} %>
 											<div class="col-md-9 div_sizes">
@@ -116,8 +116,7 @@
 												<%= hidden_field_tag "new_size_list", @product.sizes.to_json.to_s %>
 											</div>
 										</div>
-										<% end %>
-										<% if @product.colors.count > 0 %>
+
 										<div class="form-group ">
 											<%= label_tag :color_list, "Colores", {:class=>"col-md-3 control-label"} %>
 											<div class="col-md-9 div_colors">
@@ -129,8 +128,7 @@
 												<%= hidden_field_tag "new_color_list", @product.colors.to_json %>
 											</div>
 										</div>
-										<% end %>
-										<% if @product.styles.count > 0 %>
+
 										<div class="form-group ">
 											<%= label_tag :style_list, "Estilos", {:class=>"col-md-3 control-label"} %>
 											<div class="col-md-9 div_styles">
@@ -142,7 +140,6 @@
 												<%= hidden_field_tag "new_style_list", @product.styles.to_json %>
 											</div>
 										</div>
-										<% end %>
 										<hr>
 										<div class="form-actions">
 											<div class="row">

+ 1 - 1
app/views/products/index.html.erb

@@ -44,7 +44,7 @@
 												<span class="caption-subject bold uppercase">Lista de productos</span>
 											</div>
 											<div class="actions">
-												<% if current_user.usertype == 'A' %>
+												<% if current_user.usertype == 'A' || current_user.usertype == 'G' %>
 													<%= link_to new_product_path(:filter => @filter, :current_page => @current_page), {:class=>"btn bold green pull-right filtros"} do %>
 														Nuevo Producto <i class="fa fa-plus"></i>
 													<% end %>

+ 6 - 6
app/views/products/product_track.html.erb

@@ -60,7 +60,7 @@
 											<div class="col-md-6">
 												<div class="search-label uppercase">Producto <span class="required">*</span></div>
 												<div class="col-md-12">
-													<input class="form-control" type="text" id="typeahead" data-option-url="/find_products/%QUERY"> 
+													<input class="form-control" type="text" id="typeahead">
 													<%= hidden_field_tag :product_id %>
 												</div>
 											</div>
@@ -121,7 +121,7 @@
 			},
 			queryTokenizer: Bloodhound.tokenizers.whitespace,
 			remote: {
-				url: "/find_products/" +"%QUERY",
+				url: '/find_products?query=%QUERY',
 				wildcard: '%QUERY'
 			}
 		});
@@ -130,7 +130,7 @@
 		$('#typeahead').typeahead(
 			{
 				minLength: 3
-			}, 
+			},
 			{
 				displayKey: 'name',
 				source: bloodhound.ttAdapter(),
@@ -152,7 +152,7 @@
 									'<h4 class="media-heading"><strong>{{sku}}</strong> | {{name}}</h4>' +
 									'<p>{{barcode}}</p>' +
 									'<p>{{description}}</p>' +
-									'<p>{{display_attributes}}</p>' +                    
+									'<p>{{display_attributes}}</p>' +
 								'</div>' +
 							'</div>')
 				}
@@ -160,7 +160,7 @@
 
 		$('#typeahead').bind('typeahead:selected', function(event, datum, name) {
 			selectedProduct = datum;
-			$('#btn_search_by_period').attr('disabled', false);       
+			$('#btn_search_by_period').attr('disabled', false);
 		});
 		//buscar ventas por periodo de tiempo
 		function findSalesByPeriod() {
@@ -193,7 +193,7 @@
 						success: function(data) {
 							App.unblockUI($("#track_detail"));
 						}
-					});       
+					});
 				} else {
 					toastr["error"]("Es necesario indicar un producto.");
 				}

+ 4 - 4
app/views/products_returns/_form.html.erb

@@ -90,7 +90,7 @@
                   <div class="form-group">
                     <label class="col-md-1 control-label" for="typeahead" style="padding-right:0px">Producto</label>
                     <div class="col-md-4">
-                      <input class="form-control" type="text" id="typeahead" data-option-url="/find_products/%QUERY">
+                      <input class="form-control" type="text" id="typeahead">
                     </div>
                     <button type="button" id="btn_search_by_period" class="btn green" style="margin-left:10px" onclick="clearTypeahead()">Limpiar</button>
                   </div>
@@ -248,7 +248,7 @@
                     <span class="required">*</span>
                   </label>
                   <div class="col-md-4">
-                    <input class="form-control" type="text" id="typeahead2" data-option-url="/find_products_from_stock/%QUERY">
+                    <input class="form-control" type="text" id="typeahead2">
                   </div>
                   <button id="products_new" class="btn btn-success" type="button" onclick="addnewProd()"> Agregar <i class="fa fa-plus"></i> </button>
                 </div>
@@ -340,7 +340,7 @@
     },
     queryTokenizer: Bloodhound.tokenizers.whitespace,
     remote: {
-      url: "/find_products/" +"%QUERY",
+      url: '/find_products?query=%QUERY',
       wildcard: '%QUERY'
     }
   });
@@ -388,7 +388,7 @@
     },
     queryTokenizer: Bloodhound.tokenizers.whitespace,
     remote: {
-      url: $('#typeahead2').data('option-url'),
+      url: '/find_products_from_stock?query=%QUERY',
       wildcard: '%QUERY'
     }
   });

+ 78 - 78
app/views/purchases/_form.html.erb

@@ -11,7 +11,7 @@
 				<span class="input-group-addon"><i class="fa fa-barcode"></i></span>
 				<%= f.text_field :purchase_code, {:class=>"form-control", :readonly => true} %>
 			</div>
-		</div>  
+		</div>
 		<!-- fecha -->
 		<div class="form-group">
 			<%= f.label :purchase_date, {:class=>"col-md-2 control-label"} do %> Fecha
@@ -21,26 +21,26 @@
 				<span class="input-group-addon"><i class="fa fa-calendar"></i></span>
 				<%= f.date_field :purchase_date, {:value=>Date.today, :class=>"form-control", :readonly => true} %>
 			</div>
-		</div> 
+		</div>
 		<!-- tipo de destino -->
 		<div class="form-group">
-			<%= label_tag 'destiny', {:class=>"col-md-2 control-label"} do %> Destino del producto 
+			<%= label_tag 'destiny', {:class=>"col-md-2 control-label"} do %> Destino del producto
 			<span class="required">*</span>
 			<% end %>
 			<div class="col-md-3" style="padding-left:0px;padding-right:0px">
 				<%= check_box_tag('destiny', 'warehouse', (@destiny == 'warehouse' ? true : false),
-					{ 
+					{
 						class: "make-switch",
 						disabled: @disable_destiny,
 						data: {
 							on_color: "success",
 							off_color: "warning",
-							on_text: "Almacén", 
+							on_text: "Almacén",
 							off_text: "Punto de venta"
 						}
 					}) %>
 			</div>
-		</div>       
+		</div>
 		<!-- punto de venta -->
 		<div class= "form-group <%=(@destiny == 'pointsale' ? '' : 'hidden')%>"  id='pointsale_div'>
 			<%= f.label :pointsale_id, "Punto de venta", {:class=>"col-md-2 control-label"} do %> Punto de venta
@@ -50,7 +50,7 @@
 				<%= f.collection_select :pointsale_id, Pointsale.activos, :id, :name, {:prompt => "Seleccione"}, {:class => "form-control select2", :disabled => @disable_pointsale} %>
 				<%= f.hidden_field :pointsale_id, {:id => 'pointsale'} %>
 			</div>
-		</div>   
+		</div>
 		<!-- almacen -->
 		<div class= "form-group <%=(@destiny == 'warehouse' ? '' : 'hidden')%>"  id='warehouse_div'>
 			<%= f.label :warehouse_id, "Almacén", {:class=>"col-md-2 control-label"} do %> Almacén
@@ -60,7 +60,7 @@
 				<%= f.collection_select :warehouse_id, Warehouse.activos, :id, :name, {:prompt => "Seleccione"}, {:class => "form-control select2", :disabled => @disable_warehouse} %>
 				<%= f.hidden_field :warehouse_id, {:id => 'warehouse'} %>
 			</div>
-		</div>            
+		</div>
 		<!-- proveedor -->
 		<div class="form-group">
 			<%= f.label :supplier_id, "Proveedor", {:class=>"col-md-2 control-label"} do %> Proveedor
@@ -70,24 +70,24 @@
 				<%= f.collection_select :supplier_id, Supplier.activos, :id, :nick_name, {:prompt => "Seleccione"}, {:class => "form-control select2", :disabled => @disable_supplier} %>
 				<%= f.hidden_field :supplier_id, {:id => 'supplier'} %>
 			</div>
-		</div> 
+		</div>
 		<div class="form-group">
-			<%= f.label :is_in_dollars, {:class=>"col-md-2 control-label"} do %> tipo de Moneda 
+			<%= f.label :is_in_dollars, {:class=>"col-md-2 control-label"} do %> tipo de Moneda
 				<span class="required">*</span>
 			<% end %>
 			<div class="col-md-3"  style="padding-left:0px;padding-right:0px">
-				<%= f.check_box(:is_in_dollars,   
+				<%= f.check_box(:is_in_dollars,
 					{
 						class: "make-switch",
 						disabled: @disable_is_in_dollars,
 						data: {
 							on_color: "success",
 							off_color: "warning",
-							on_text: "Dolares", 
+							on_text: "Dolares",
 							off_text: "Pesos"
 						}
 					},
-					"true", "false"  
+					"true", "false"
 				) %>
 			</div>
 			<%= f.hidden_field :is_in_dollars, {:id => 'is_in_dollars'} %>
@@ -97,34 +97,34 @@
 			<span class="required">*</span>
 			<% end %>
 			<div class="col-md-3 input-group">
-				<span class="input-group-addon"><i class="fa fa-usd"></i></span>        
+				<span class="input-group-addon"><i class="fa fa-usd"></i></span>
 				<%= f.number_field :exchange, {:class=>"form-control", :step=>"any"} %>
-			</div>      
+			</div>
 		</div>
 		<!-- boton para resetear datos -->
 		<div class="col-md-offset-10 col-md-2">
 			<button id="reset_pre_purchases" type="button" class="btn btn-warning" <%= @disabled_button ? 'disabled' : '' %> onclick="deletePrePurchases()">Restaurar compra</button>
-		</div>    
+		</div>
 		<!-- agregar productos -->
 		<h4 class="form-section"> Agregar producto</h4>
 		<div class="row">
-			<div class='col-md-12'> 
+			<div class='col-md-12'>
 				<div class="col-md-10">
 					<div class="form-group">
 						<label class="col-md-2 control-label" for="typeahead"> Producto
 							<span class="required">*</span>
 						</label>
-						<div class="col-md-4"> 
-							<input class="form-control" type="text" id="typeahead" data-option-url="/find_products/%QUERY"> 
+						<div class="col-md-4">
+							<input class="form-control" type="text" id="typeahead">
 						</div>
-						<button id="products_new" disabled class="btn btn-success" type="button" onclick="addRow()"> <i class="fa fa-plus"></i> Agregar </button>  
-					</div>          
+						<button id="products_new" disabled class="btn btn-success" type="button" onclick="addRow()"> <i class="fa fa-plus"></i> Agregar </button>
+					</div>
 				</div>
 				<div class="col-md-2">
-					<button id="products_add_new" class="btn btn-success" type="button" onclick="addNewProduct()">  <i class="fa fa-plus"></i> Nuevo Producto </button>           
+					<button id="products_add_new" class="btn btn-success" type="button" onclick="addNewProduct()">  <i class="fa fa-plus"></i> Nuevo Producto </button>
 				</div>
 			</div>
-		</div> 
+		</div>
 		<!-- lista de productos -->
 		<h4 class="form-section"> Lista de productos</h4>
 		<div class="portlet-body">
@@ -159,7 +159,7 @@
 				 </div>
 			 </div>
 			<div class="col-md-offset-1 col-md-5">
-					<div class="well">         
+					<div class="well">
 							<div class="row static-info align-reverse">
 									<div class="col-md-4 name"> Sub Total: </div>
 									<div class="col-md-8 value " id="amount_div">
@@ -175,7 +175,7 @@
 									<div class="col-md-8 value " id="tax_div">
 											<div class="input-group">
 												<span class="input-group-addon">$ </span>
-												<%= f.text_field :tax, {:class=>"form-control descto", :readonly => true, :id => "tax"}  %> 
+												<%= f.text_field :tax, {:class=>"form-control descto", :readonly => true, :id => "tax"}  %>
 												<span class="input-group-addon"></span>
 											</div>
 									</div>
@@ -186,32 +186,32 @@
 											<div class="input-group">
 												<span class="input-group-addon">$ </span>
 												<%= f.text_field :total, {:class=>"form-control descto", :readonly => true, :id => "total"}  %>
-												<span class="input-group-addon"></span> 
+												<span class="input-group-addon"></span>
 											</div>
 									</div>
 							</div>
 					</div>
 			</div>
 			<div class="col-md-offset-7 col-md-5 hidden" id="total_in_mxn_div">
-					<div class="well">         
+					<div class="well">
 						<div class="row static-info align-reverse">
 								<div class="col-md-4 name"> Total en pesos: </div>
 								<div class="col-md-8 value ">
 										<div class="input-group">
 											<span class="input-group-addon">$ </span>
 												<%= text_field_tag :total_in_mxn, '' , :class=>"form-control", :readonly => true %>
-											<span class="input-group-addon">MXN</span> 
+											<span class="input-group-addon">MXN</span>
 										</div>
 								</div>
 						</div>
 					</div>
-			</div>      
-		</div>   
+			</div>
+		</div>
 		<!-- acciones del form -->
 		<div class="form-actions">
 			<div class="row">
 				<div class="col-md-offset-2 col-md-9">
-					<%= f.submit 'Guardar', {:class=>"btn green"} %> 
+					<%= f.submit 'Guardar', {:class=>"btn green"} %>
 					<%= link_to 'Cancelar', purchases_path(:filter => @filter, :current_page => @current_page), {:class=>"btn default"} %>
 				</div>
 			</div>
@@ -239,12 +239,12 @@
 		$('#purchase_is_in_dollars').on('switchChange.bootstrapSwitch', function(event, state) {
 			showExchangeField(state);
 			$('#is_in_dollars').val(state);
-		});    
+		});
 
 		$("#new_pre_purchase").bind('ajax:complete', function() {
 			calculateTotals();
-			$('#purchase_is_in_dollars').bootstrapSwitch('disabled', true);  
-			$('#destiny').bootstrapSwitch('disabled', true);  
+			$('#purchase_is_in_dollars').bootstrapSwitch('disabled', true);
+			$('#destiny').bootstrapSwitch('disabled', true);
 		});
 
 		$('#purchase_supplier_id').on('change', function() {
@@ -276,7 +276,7 @@
 
 		$('body').barcodeListener().on('barcode.valid', function(e, code) {
 			findProductByBarcode(code);
-		}); 
+		});
 
 			var bloodhound = new Bloodhound({
 				datumTokenizer: function (d) {
@@ -284,7 +284,7 @@
 				},
 				queryTokenizer: Bloodhound.tokenizers.whitespace,
 				remote: {
-					url: "/find_products/" +"%QUERY",
+					url: '/find_products?query=%QUERY',
 					wildcard: '%QUERY'
 				}
 			});
@@ -293,7 +293,7 @@
 		$('#typeahead').typeahead(
 			{
 				minLength: 3
-			}, 
+			},
 			{
 				displayKey: 'name',
 				source: bloodhound.ttAdapter(),
@@ -313,9 +313,9 @@
 								'</div>' +
 								'<div class="media-body">' +
 										'<h4 class="media-heading"><strong>{{sku}}</strong> | {{name}}</h4>' +
-										'<p>{{barcode}}</p>' +                    
+										'<p>{{barcode}}</p>' +
 										'<p>{{description}}</p>' +
-										'<p>{{display_attributes}}</p>' +                    
+										'<p>{{display_attributes}}</p>' +
 								'</div>' +
 							'</div>')
 				}
@@ -330,7 +330,7 @@
 			var warehouse = $('#purchase_warehouse_id').val();
 
 			if( (supplier && pointsale) || (supplier && warehouse) ) {
-				$('#products_new').attr('disabled', false);       
+				$('#products_new').attr('disabled', false);
 			} else {
 				toastr["error"]("Se debe seleccionar proveedor y destino de la compra.");
 			}
@@ -345,7 +345,7 @@
 			if( (supplier && pointsale) || (supplier && warehouse) ) {
 				$.ajax({
 					type: "GET",
-					url: "/products/new", 
+					url: "/products/new",
 					dataType: "script",
 					data: {
 						remoto: true,
@@ -355,7 +355,7 @@
 						exchange: exchange
 					},
 					success: function(xhr, status, error) {}
-				});   
+				});
 			} else {
 				toastr["error"]("Se debe seleccionar proveedor y destino de la compra.");
 			}
@@ -375,7 +375,7 @@
 				$('#pre_purchase_total').val(0);
 
 				$('#new_pre_purchase').submit();
-				$('#typeahead').typeahead('val',''); 
+				$('#typeahead').typeahead('val','');
 				$('#products_new').attr('disabled', true);
 			}
 		}
@@ -393,11 +393,11 @@
 					table.row(input.closest('tr')).remove().draw();
 					calculateTotals();
 				}
-			});    
+			});
 		}
 
 		function updateQuantity(input) {
-			if(input.val()) {       
+			if(input.val()) {
 				clearTimeout(timeout);
 				timeout = setTimeout(function () {
 					tax = 0;
@@ -413,9 +413,9 @@
 						taxPercent = parseFloat($('#tax_percent').val()) / 100
 						tax =  (taxPercent  * amount) / (1 + taxPercent);
 					}
-					input.closest('tr').find('td:eq(6)').text(Math.round(tax * 100) / 100); 
-					input.closest('tr').find('td:eq(7)').text(Math.round(amount * 100) / 100);  
-					calculateTotals(); 
+					input.closest('tr').find('td:eq(6)').text(Math.round(tax * 100) / 100);
+					input.closest('tr').find('td:eq(7)').text(Math.round(amount * 100) / 100);
+					calculateTotals();
 
 					var idText = input.closest('tr').attr('id');
 					var prePurchaseId = idText.substring(idText.lastIndexOf('_') + 1, idText.length);
@@ -427,8 +427,8 @@
 							data: {pre_purchase: {quantity: quantity }},
 							success: function(xhr, status, error) {
 							}
-					});           
-				}, 700);       
+					});
+				}, 700);
 			}
 		}
 
@@ -456,8 +456,8 @@
 										taxPercent = parseFloat($('#tax_percent').val()) / 100
 										tax =  (taxPercent  * amount) / (1 + taxPercent);
 									}
-									input.closest('tr').find('td:eq(6)').text(Math.round(tax * 100) / 100);  
-									input.closest('tr').find('td:eq(7)').text(Math.round(amount * 100) / 100);  
+									input.closest('tr').find('td:eq(6)').text(Math.round(tax * 100) / 100);
+									input.closest('tr').find('td:eq(7)').text(Math.round(amount * 100) / 100);
 									var preIdText = input.closest('tr').attr('id');
 									var prePurchaseId = preIdText.substring(preIdText.lastIndexOf('_') + 1, preIdText.length);
 									$.ajax({
@@ -466,25 +466,25 @@
 											dataType: "json",
 											data: {pre_purchase: {quantity: quantity }},
 											success: function(xhr, status, error) {
-												calculateTotals(); 
+												calculateTotals();
 											}
-									});     
+									});
 							}
-					});                
-				}, 600);    
+					});
+				}, 600);
 			}
 		}
 
 		function calculateTotals() {
 			var amount = 0;
 			var tax    = 0;
-			var total  = 0;  
+			var total  = 0;
 			$('#products_table tbody tr td:nth-child(7)').each(function() {
 				 tax+= parseFloat($(this).text());
-			});  
+			});
 			$('#products_table tbody tr td:nth-child(8)').each(function() {
 				 amount+= parseFloat($(this).text());
-			});  
+			});
 			amount = amount - tax;
 			total = amount + tax;
 			$('#tax').val(Math.round(tax * 100) / 100);
@@ -508,20 +508,20 @@
 					$('#purchase_purchase_code').val('');
 					$("#purchase_supplier_id").select2().select2("val", null);
 					$("#purchase_supplier_id").attr('disabled',false);
-					$("#purchase_pointsale_id").select2().select2("val", null); 
-					$("#purchase_pointsale_id").attr('disabled',false); 
-					$("#purchase_warehouse_id").select2().select2("val", null); 
-					$("#purchase_warehouse_id").attr('disabled',false);                        
+					$("#purchase_pointsale_id").select2().select2("val", null);
+					$("#purchase_pointsale_id").attr('disabled',false);
+					$("#purchase_warehouse_id").select2().select2("val", null);
+					$("#purchase_warehouse_id").attr('disabled',false);
 					$("#reset_pre_purchases").attr('disabled',true);
-					$('#destiny').bootstrapSwitch('disabled',false); 
+					$('#destiny').bootstrapSwitch('disabled',false);
 					$('#purchase_is_in_dollars').bootstrapSwitch('disabled', false);
-					$('#purchase_is_in_dollars').bootstrapSwitch('state', false);            
+					$('#purchase_is_in_dollars').bootstrapSwitch('state', false);
 					calculateTotals();
 				}
 			});
 		}
 
-		function addPurchase() {     
+		function addPurchase() {
 			$('#purchase_form').submit();
 		}
 
@@ -533,8 +533,8 @@
 					dataType: 'text',
 					success: function(data) {
 						$('#purchase_purchase_code').val(data);
-					},      
-				});         
+					},
+				});
 			} else if($('#purchase_warehouse_id').val()) {
 				$.ajax({
 					type: "get",
@@ -542,8 +542,8 @@
 					dataType: 'text',
 					success: function(data) {
 						$('#purchase_purchase_code').val(data);
-					},      
-				});   
+					},
+				});
 			}
 		}
 
@@ -564,8 +564,8 @@
 					dataType: 'script',
 					success: function(data) {
 						calculateTotals();
-					},      
-				});             
+					},
+				});
 			} else {
 				toastr["error"]("Se debe seleccionar proveedor y destino de la compra.");
 			}
@@ -578,7 +578,7 @@
 				$('#products_table tbody tr').each(function() {
 					$(this).find('td:eq(0)').html($(this).find('td:eq(0)').html().replace('#', counter));
 					counter++;
-				}); 
+				});
 			}
 		}
 
@@ -588,18 +588,18 @@
 			if (state) {
 				$('#warehouse_div').removeClass('hidden');
 				$("#purchase_warehouse_id").select2();
-				$('#pointsale_div').addClass('hidden');   
+				$('#pointsale_div').addClass('hidden');
 				<% if current_user.usertype == 'A' %>
 					$('#purchase_pointsale_id').select2('val', null);
-				<% end %>     
+				<% end %>
 			} else {
 				$('#pointsale_div').removeClass('hidden');
-				$("#purchase_pointsale_id").select2();        
+				$("#purchase_pointsale_id").select2();
 				$('#warehouse_div').addClass('hidden');
 				<% if current_user.usertype == 'A' %>
 					$('#purchase_warehouse_id').select2('val', null);
 				<% end %>
-			}      
+			}
 		}
 
 		function showExchangeField(state) {
@@ -617,7 +617,7 @@
 				$('#tax_div div span:last-child').html('MXN');
 				$('#total_div div span:last-child').html('MXN');
 				$("#total_in_mxn_div").addClass('hidden');
-			} 
+			}
 		}
 </script>
 

+ 1 - 1
app/views/sales/_add_haggle.html.erb

@@ -27,7 +27,7 @@
                             <%= number_field_tag :haggle_percent, '', class: "form-control input-mask_decimal", min:0, max:100, step: "any" %>
                             <span class="input-group-addon"> % </span>
                         </div>
-                        <span class="help-block">Recomendado: <strong><%= @pos_config.haggle_in_sale_percent %></strong> %</span>
+                        <span class="help-block">Recomendado: <strong><%= @haggle_percent %></strong> %</span>
                     </div>
                 </div>
                 <div class="form-group">

+ 8 - 3
app/views/sales/_form.html.erb

@@ -153,7 +153,11 @@
 			<div class="col-md-12">
 				<div class="note note-info">
 					<h4 class="block">¡Nota!</h4>
-					<p> Para hacer una búsqueda avanzada utiliza el siguiente formato, nombre del producto :atributo a buscar<br> ejemplo. <strong>blusa :verde</strong> (Sólo se puede buscar un atributo, ya sea estilo, talla o color).</p>
+					<p>
+						Para hacer una búsqueda avanzada utiliza el siguiente formato: <br>
+						Nombre del producto:(inicial del atributo a buscar)atributo a buscar<br>
+						Ejemplo: <strong>Blusa:cverde:tch:elargo</strong> (Donde C es Color, T es Talla y E es Estilo, pueden tener cualquier orden, siempre y cuando tenga ese formato)
+					</p>
 				</div>
 			</div>
 			<div class='col-md-12'>
@@ -162,7 +166,8 @@
 						<span class="required">*</span>
 					</label>
 					<div class="col-md-4">
-						<input class="form-control" type="text" id="typeahead" data-option-url="/find_products_from_stock/%QUERY">
+					<!-- -->
+						<input class="form-control" type="text" id="typeahead">
 					</div>
 					<button id="products_new" disabled class="btn btn-success" type="button" onclick="addRow()"> Agregar <i class="fa fa-plus"></i> </button>
 				</div>
@@ -380,7 +385,7 @@
 		},
 		queryTokenizer: Bloodhound.tokenizers.whitespace,
 		remote: {
-			url: $('#typeahead').data('option-url'),
+			url: '/find_products_from_stock?query=%QUERY',
 			wildcard: '%QUERY'
 		}
 	});

+ 2 - 2
app/views/transfers/_form.html.erb

@@ -58,7 +58,7 @@
             <span class="required">*</span>
           </label>
           <div class="col-md-4">
-            <input class="form-control" type="text" id="typeahead" data-option-url="/find_products_from_stock/ID/%QUERY">
+            <input class="form-control" type="text" id="typeahead">
           </div>
           <button id="products_new" disabled class="btn btn-success" type="button" onclick="addRow()"> Agregar <i class="fa fa-plus"></i> </button>
         </div>
@@ -121,7 +121,7 @@
       },
       queryTokenizer: Bloodhound.tokenizers.whitespace,
       remote: {
-        url: "/find_from_stock_by_pointsale/ID/" + "%QUERY",
+        url: "/find_from_stock_by_pointsale?pointsale_id=ID&query=%QUERY",
         wildcard: '%QUERY',
         replace: function(url, uriEncodedQuery) {
             origin = $('#origin_id').val();

+ 1 - 1
app/views/units/_form.html.erb

@@ -16,7 +16,7 @@
     </div>
     <div class="form-actions">
       <div class="row">
-        <div class="col-md-offset-3 col-md-9">
+        <div class="col-md-9">
           <%= f.submit 'Guardar', { class: "btn green" } %>
         </div>
       </div>

+ 70 - 33
app/views/users/_form.html.erb

@@ -52,14 +52,14 @@
 								<%= f.label :usertype, "", {:class=>"col-md-3 control-label"} do %> Tipo de usuario
 									<span class="required">*</span><% end %>
 								<div class="col-md-4">
-									<%= f.select :usertype, (current_user.usertype=="A" ? Rails.application.config.usertypes_for_admin : Rails.application.config.usertypes_for_manager), {:prompt => "Seleccione"}, { :class => 'form-control select2' } %> 
+									<%= f.select :usertype, (current_user.usertype=="A" ? Rails.application.config.usertypes_for_admin : Rails.application.config.usertypes_for_manager), {:prompt => "Seleccione"}, { :class => 'form-control select2' } %>
 								</div>
-							</div>               
+							</div>
 							<div class="form-group hidden" id="pointsale_div">
 								<%= f.label :pointsale_id, "", {:class=>"col-md-3 control-label"} do %> Punto de venta
 									<span class="required">*</span><% end %>
 								<div class="col-md-4">
-									<%= f.collection_select :pointsale_id, Pointsale.activos, :id, :name, {:prompt => "Seleccione", :selected => (current_user.usertype == 'G' ? current_user.pointsale_id : @user.pointsale_id) }, {:class => "form-control select2", 
+									<%= f.collection_select :pointsale_id, Pointsale.activos, :id, :name, {:prompt => "Seleccione", :selected => (current_user.usertype == 'G' ? current_user.pointsale_id : @user.pointsale_id) }, {:class => "form-control select2",
 										:disabled => (true if current_user.usertype == 'G')  } %>
 									<%= f.hidden_field :pointsale_id, :value => (current_user.usertype == 'G' ? current_user.pointsale_id : @user.pointsale_id), :id => 'pointsale_id' %>
 								</div>
@@ -71,17 +71,17 @@
 									<div class="col-md-4">
 										<%= f.collection_select :warehouse_id, Warehouse.activos, :id, :name, {:prompt => "Seleccione"}, {:class => "form-control select2" } %>
 									</div>
-								</div>               
-							<% end %>             
+								</div>
+							<% end %>
 							<div class="form-group">
 								<%= f.label :userid, "Nombre de usuario", {:class=>"col-md-3 control-label"} do %> Usuario
 									<span class="required">*</span>
 								<% end %>
 								<div class="col-md-4">
 									<%= f.text_field :userid, {:class=>"form-control", :disabled => @user.persisted?} %>
-									<span class="help-block"> El usuario no debe de contener espacios. </span>                
+									<span class="help-block"> El usuario no debe de contener espacios. </span>
 								</div>
-							</div>  
+							</div>
 							<% unless @user.persisted? %>
 								<div class="form-group">
 									<%= f.label :password, "Contraseña", {:class=>"col-md-3 control-label"} do %> Contraseña
@@ -98,13 +98,13 @@
 									<div class="col-md-4">
 										<%= f.password_field :password_confirmation, {:class=>"form-control"} %>
 									</div>
-								</div>              
+								</div>
 							<% end %>
 						</div>
 						<div class="tab-pane" id="tab2">
 							<h3 class="block">Proporciona los datos particulares del usuario</h3>
 							<div class="form-group">
-								<%= f.label :first_name, "Nombre", {:class=>"col-md-3 control-label"} do %> Nombre 
+								<%= f.label :first_name, "Nombre", {:class=>"col-md-3 control-label"} do %> Nombre
 									<span class="required">*</span>
 								<% end %>
 								<div class="col-md-4">
@@ -121,7 +121,7 @@
 							</div>
 							<div class="form-group">
 								<%= f.label :email, "Correo", {:class=>"col-md-3 control-label"} do %> Correo electrónico <span class="required">*</span>
-								<% end %> 
+								<% end %>
 								<div class="col-md-4">
 									<div class="input-icon">
 										<i class="fa fa-envelope"></i>
@@ -144,19 +144,19 @@
 								<div class="col-md-4">
 									<span class="form-control-static" data-display="user[pointsale_id]"></span>
 								</div>
-							</div> 
+							</div>
 							<div class="form-group hidden" id="warehouse_info_div">
 								<label class="control-label col-md-3">Almacén</label>
 								<div class="col-md-4">
 									<span class="form-control-static" data-display="user[warehouse_id]"></span>
 								</div>
-							</div>               
+							</div>
 							<div class="form-group">
 								<label class="control-label col-md-3">Tipo de usuario</label>
 								<div class="col-md-4">
 									<span class="form-control-static" data-display="user[usertype]"></span>
 								</div>
-							</div>                            
+							</div>
 							<h4 class="form-section">Datos particulares del usuario</h4>
 							<div class="form-group">
 								<label class="control-label col-md-3">Nombre:</label>
@@ -169,7 +169,7 @@
 								<div class="col-md-4">
 									<span class="form-control-static" data-display="user[last_name]"></span>
 								</div>
-							</div>              
+							</div>
 							<div class="form-group">
 								<label class="control-label col-md-3">Correo electrónico:</label>
 								<div class="col-md-4">
@@ -195,46 +195,83 @@
 				</div>
 			</div>
 		</div>
-	<% end %>  
+	<% end %>
 </div>
 <script type="text/javascript">
 	$(document).on("page:change", function() {
 		App.init();
 
 	<% if @user.persisted? %>
-		<% if @user.pointsale.present? %>
+		<% if @user.pointsale.present? && @user.usertype != 'A'%>
 			$('#pointsale_info_div').removeClass('hidden');
 			$('#pointsale_div').removeClass('hidden');
 			$("#user_pointsale_id").select2();
-		<% else %>
+		<% elsif @user.warehouse.present? && @user.usertype != 'A'%>
 			$('#warehouse_info_div').removeClass('hidden');
-			$('#warehouse_div').removeClass('hidden'); 
+			$('#warehouse_div').removeClass('hidden');
 			$("#user_warehouse_id").select2();
 		<% end %>
 	<% end %>
 
-	});  
+	});
 
 	$('#user_pointsale_id').on('change', function() {
 		$('#pointsale_id').val($(this).val());
 	});
 
 	$('#user_usertype').on('change', function() {
-		if($(this).val() == 'S') {
-			$('#warehouse_info_div').removeClass('hidden');
-			$('#pointsale_info_div').addClass('hidden');  
-			$('#user_pointsale_id').select2('val', null);      
-			$('#warehouse_div').removeClass('hidden');
-			$("#user_warehouse_id").select2();
-			$('#pointsale_div').addClass('hidden');  
-		} else {
-			$('#pointsale_info_div').removeClass('hidden');
-			$('#warehouse_info_div').addClass('hidden');  
-			$('#user_warehouse_id').select2('val', null);
-			$('#pointsale_div').removeClass('hidden');
-			$("#user_pointsale_id").select2();
-			$('#warehouse_div').addClass('hidden');  
+		var type = $(this).val();
+		switch(type) {
+			case 'A':
+				$('#pointsale_info_div').addClass('hidden');
+				$('#warehouse_info_div').addClass('hidden');
+				$('#warehouse_div').addClass('hidden');
+				$('#pointsale_div').addClass('hidden');
+				$('#user_warehouse_id').select2('val', null);
+				$('#user_pointsale_id').select2('val', null);
+				break;
+			case 'G':
+				$('#pointsale_info_div').removeClass('hidden');
+				$('#warehouse_info_div').addClass('hidden');
+				$('#user_warehouse_id').select2('val', null);
+				$('#pointsale_div').removeClass('hidden');
+				$("#user_pointsale_id").select2();
+				$('#warehouse_div').addClass('hidden');
+				break;
+			case 'S':
+				$('#warehouse_info_div').removeClass('hidden');
+				$('#pointsale_info_div').addClass('hidden');
+				$('#user_pointsale_id').select2('val', null);
+				$('#warehouse_div').removeClass('hidden');
+				$("#user_warehouse_id").select2();
+				$('#pointsale_div').addClass('hidden');
+				break;
+			case 'C':
+				$('#pointsale_info_div').removeClass('hidden');
+				$('#warehouse_info_div').addClass('hidden');
+				$('#user_warehouse_id').select2('val', null);
+				$('#pointsale_div').removeClass('hidden');
+				$("#user_pointsale_id").select2();
+				$('#warehouse_div').addClass('hidden');
+				break;
 		}
+
+
+		// if($(this).val() == 'S') {
+		// 	$('#warehouse_info_div').removeClass('hidden');
+		// 	$('#pointsale_info_div').addClass('hidden');
+		// 	$('#user_pointsale_id').select2('val', null);
+		// 	$('#warehouse_div').removeClass('hidden');
+		// 	$("#user_warehouse_id").select2();
+		// 	$('#pointsale_div').addClass('hidden');
+		// } else {
+		// 	$('#pointsale_info_div').removeClass('hidden');
+		// 	$('#warehouse_info_div').addClass('hidden');
+		// 	$('#user_warehouse_id').select2('val', null);
+		// 	$('#pointsale_div').removeClass('hidden');
+		// 	$("#user_pointsale_id").select2();
+		// 	$('#warehouse_div').addClass('hidden');
+		// }
 	});
 
 </script>

+ 1 - 0
config/application.rb

@@ -25,6 +25,7 @@ module Pos
     config.action_view.embed_authenticity_token_in_remote_forms = true
     config.active_record.raise_in_transactional_callbacks = true
     config.usertypes_for_admin = {
+      "Administrador" => "A",
       "Gerente" => "G",
       "Caja" => "C",
       "Almacenista" => "S"

+ 7 - 1
config/navigation.rb

@@ -21,6 +21,8 @@ module SimpleNavigationn
             else
               list << content_tag(:li, item.name[:text], class: 'active ') if item.selected?
             end
+          else
+            next
           end
           # rubocop:enable Style/IfInsideElse
         end
@@ -139,7 +141,11 @@ SimpleNavigation::Configuration.run do |navigation|
       primary.item :products, { icon: "fa fa-fw fa-cubes", text: "Productos" }, products_path, class: 'menu-dropdown classic-menu-dropdown' do |sub_nav|
         sub_nav.dom_attributes = { class: 'dropdown-menu pull-left' }
         sub_nav.item :units, 'Unidades de medida', units_path
-        sub_nav.item :list_categories, 'Lista de líneas de productos', categories_path
+        sub_nav.item :divider_before_categories, '#', divider: true
+        sub_nav.item :new_category, 'Nueva línea de producto', new_category_path
+        sub_nav.item :list_categories, 'Lista de líneas de producto', categories_path
+        sub_nav.item :divider_before_products, '#', divider: true
+        sub_nav.item :new_product, 'Nuevo Producto', new_product_path
         sub_nav.item :list_products, 'Lista de productos', products_path
         sub_nav.item :divider_after_list_products, '#', divider: true
         sub_nav.item :stock_by_pointsale, 'Existencias', stock_by_pointsale_path

+ 3 - 3
config/routes.rb

@@ -228,12 +228,12 @@ Rails.application.routes.draw do
   get 'total_invested_in_pointsale' => 'available_products#total_invested_in_pointsale', :format => :json
 
   # find de productos para el typeahead
-  get 'find_products/:query' => 'application#find', :format => :json
+  get 'find_products' => 'application#find', :format => :json
   # para special_prices
   get 'find_products_sp/:query' => 'application#find_sp', :format => :json
   #
-  get 'find_products_from_stock/:query' => 'application#find_from_stock', :format => :json
-  get 'find_from_stock_by_pointsale/:pointsale_id/:query' => 'application#find_from_stock_by_pointsale', :format => :json
+  get 'find_products_from_stock' => 'application#find_from_stock', :format => :json
+  get 'find_from_stock_by_pointsale' => 'application#find_from_stock_by_pointsale', :format => :json
 
   get "getcategories/:category_id" => "application#get_subcategories", :as => "get_subcategories", :format => :json
   get 'get_max_product_id' => 'application#get_max_product_id', :format => :json

+ 5 - 0
db/migrate/20180724174236_add_haggle_percent_to_pointsale.rb

@@ -0,0 +1,5 @@
+class AddHagglePercentToPointsale < ActiveRecord::Migration
+  def change
+  	add_column :pointsales, :haggle_percent, :decimal, precision: 10, scale: 2, :default => 0
+  end
+end