require 'barby' require 'barby/barcode/code_39' require 'barby/outputter/png_outputter' class Product < ActiveRecord::Base ##--- Asociaciones belongs_to :unit belongs_to :category has_and_belongs_to_many :pointsales, join_table: :available_products has_many :sales_details has_many :purchase_details has_many :inventories_moves has_many :pre_sales has_many :pre_purchases has_many :special_prices has_many :pre_transfers has_many :available_products has_many :promotions enum status: [:erased, :active, :inactive] acts_as_taggable_on :sizes, :colors, :styles accepts_nested_attributes_for :available_products audited attr_accessor :skip_sku_validation mount_uploader :img_product, ImageUploader ##--- Validaciones previas de guardar validates :sku, presence: { message: "Debe capturar el SKU del producto." }, length: { maximum: 30, too_long: "El máximo de caracteres para el SKU debe ser %{count}." }, uniqueness: { message: "El SKU ya fue utilizado, favor de especificar otro." }, unless: :skip_sku_validation validates_presence_of :name, message: "Debe capturar el nombre del producto." validates_presence_of :unit_id, message: "Debe seleccionar la unidad de medida correspondiente al producto." validates_presence_of :category_id, message: "Debe seleccionar una línea o sublinea para el producto." validates :barcode, uniqueness: { message: "El código de barras ya fue utilizado, favor de especificar otro." }, allow_blank: true validates_presence_of :name, message: "Debe capturar el nombre del producto." validates :price_sale, presence: { message: "Debe capturar el precio de venta del producto." }, numericality: { greater_than: 0.00 } validate :category_has_subcats, on: [:create, :update] def valid_categories categories.count > 0 end def category_has_subcats if category.present? && category.parent_id.zero? && category.children.present? errors.add(:category_id, "Seleccione una sublínea.") end end def small_img if img_product? img_product.url(:medium).to_s else img = "/images/original/missing.png" end end ##--- Tipo de vistas / consultas scope :vigentes, -> { where.not(products: { status: 0 }).order(" products.status ASC, products.name ASC") } scope :activos, -> { where(status: 1).order("products.name ASC") } scope :activos_children, -> { activos.where(is_parent: false).order("products.name ASC") } scope :vigentes_parents, -> { vigentes.where("parent_id IS NULL") } scope :name_sku_barcode_like, ->(name) { activos.where("is_parent = ? and (name ilike ? or sku ilike ? or barcode ilike ?)", false, "%#{name}%", "%#{name}%", "%#{name}%") } scope :name_sku_barcode_attribute_like, ->(name, attributes_string) { activos.where("is_parent = ? and (name ilike ? or sku ilike ? or barcode ilike ?) #{attributes_string}", false, "%#{name}%", "%#{name}%", "%#{name}%") } # para special_prices scope :name_sku_barcode_like_sp, ->(name) { activos.where("is_parent = ? and (name ilike ? or sku ilike ? or barcode ilike ?)", true, "%#{name}%", "%#{name}%", "%#{name}%") } def name_with_sku sku.to_s + " - " + name.to_s end def get_promotion category_array = if category.parent.present? [category.parent.id, Category.activos.where("parent_id = ?", category.parent.id).pluck(:id)].flatten else [category_id] end product_ids = [id, parent_id] promos = Promotion.where("product_id IN (?) OR category_id IN (?)", product_ids, category_array).order("percent, id DESC").vigentes.first end def display_sku_name_attributes sku.to_s + " | " + name.to_s + " | " + display_attributes.to_s end def full_display show_name = name + "\n" + "SKU: " + sku + "\n" show_name += "\n" + display_attributes.to_s if parent_id.present? show_name += " Código de barras: " + barcode if parent_id.present? && barcode.present? show_name end def stock_in_pointsale(pointsale_id) stock = 0 # checar si hay existencias en los almacenes. warehouses_stock = WarehouseStock.where(product_id: id) warehouses_stock.each do |warehouse| # solamente hacerlo cuando pointsale id sea nil porque es el index de producto stock += warehouse.stock if pointsale_id.zero? end # existencias en puntos de venta. availables = AvailableProduct.where(product_id: id) availables.each do |available| if pointsale_id == available.pointsale_id stock = available.stock elsif pointsale_id.zero? stock += available.stock end end stock end def can_be_deleted? if is_parent children_ids = Product.where(parent_id: id).pluck(:id) in_available = AvailableProduct.where("product_id IN (?) and stock > 0", children_ids).any? in_warehouse = WarehouseStock.where("product_id IN (?) and stock > 0", children_ids).any? else in_warehouse = WarehouseStock.where("product_id = #{id} and stock > 0").any? in_available = AvailableProduct.where("product_id = #{id} and stock > 0").any? end in_available == true || in_warehouse == true ? false : true end def last_sale(pointsale_id) unless pointsale_id.nil? last_time = Pointsale.find(pointsale_id).sales_details.where(product_id: id).last end end def available_in_pointsale?(pointsale_id) if pointsales.exists?(id: pointsale_id) true else false end end def get_available_in_pointsale(pointsale_id) AvailableProduct.find_by(pointsale_id: pointsale_id, product_id: id) end def get_price_sale(pointsale_id) available = get_available_in_pointsale(pointsale_id) if available.present? && available.price_sale.present? available.price_sale else price_sale end end def pointsales_prices AvailableProduct.where("product_id = ? and price_sale IS NOT NULL", id) end def variants_attributes ActsAsTaggableOn::Tagging.where(taggable_id: id, taggable_type: 'Product').distinct(:context).select(:context) end def get_combinations(combinations, attributes) if presentation ##--- crear el array de los arrays de atributos if size_list.count > 0 attributes << size_list end if color_list.count > 0 attributes << color_list end if style_list.count > 0 attributes << style_list end ##--- verificar que atributos tenga mas de una categoria if attributes.count > 1 ##--- Making combinations from arrays first_array, *rest_of_arrays = attributes combinations = first_array.product(*rest_of_arrays) else attributes[0].each do |attribute| combinations << attribute end end combinations end end # rubocop:disable Metrics/BlockLength def save_variants(current_user) Thread.new do combinations = Array.new attributes = Array.new combinations = get_combinations(combinations, attributes) ##--- recorrer combinaciones para crear las variantes de productos unless combinations.nil? combinations.each_with_index do |combination, index| @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" attributes_json = {} if combination.is_a?(Array) combination.each do |attrib| attributes_json = @products_variant.assign_attributes_to_variant(attrib, id, attributes_json) end else attributes_json = @products_variant.assign_attributes_to_variant(combination, id, attributes_json) end @products_variant.attributes_json = attributes_json.to_json @products_variant.save @products_variant.generate_barcode @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 # rubocop:disable Metrics/BlockLength def save_variants_no_thread(current_user) combinations = Array.new attributes = Array.new combinations = get_combinations(combinations, attributes) ##--- recorrer combinaciones para crear las variantes de productos unless combinations.nil? combinations.each_with_index do |combination, index| @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" attributes_json = {} if combination.is_a?(Array) combination.each do |attrib| attributes_json = @products_variant.assign_attributes_to_variant(attrib, id, attributes_json) end else attributes_json = @products_variant.assign_attributes_to_variant(combination, id, attributes_json) end @products_variant.attributes_json = attributes_json.to_json @products_variant.save @products_variant.generate_barcode @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 # 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 get_context = ActsAsTaggableOn::Tagging.where(tag_id: attri_id, taggable_id: parent_id, taggable_type: 'Product').select(:context).first context = get_context.context if id != parent_id if context == "sizes" self.size_list = attri.to_s elsif context == "colors" self.color_list = attri.to_s elsif context == "styles" self.style_list = attri.to_s end end attributes_json[context] = attri.to_s attributes_json end def update_attributes_to_variants(new_sizes, new_colors, new_styles) unless new_sizes.nil? (JSON.parse new_sizes).each do |s| sizes.each do |p| next unless p.id.to_s == s["id"].to_s && p.name.to_s != s["name"].to_s # if p.id.to_s == s["id"].to_s && p.name.to_s != s["name"].to_s size_list.remove(p.name.to_s) variants = children.tagged_with(p.name.to_s, on: :sizes, any: true) variants.each do |v| v.size_list.remove(p.name.to_s) v.size_list.add(s["name"].to_s) v.save(validate: false) end size_list.add(s["name"].to_s) end end end unless new_colors.nil? (JSON.parse new_colors).each do |s| colors.each do |p| next unless p.id.to_s == s["id"].to_s && p.name.to_s != s["name"].to_s # if p.id.to_s == s["id"].to_s && p.name.to_s != s["name"].to_s color_list.remove(p.name.to_s) variants = children.tagged_with(p.name.to_s, on: :colors, any: true) variants.each do |v| v.color_list.remove(p.name.to_s) v.color_list.add(s["name"].to_s) v.save(validate: false) end color_list.add(s["name"].to_s) end end end unless new_styles.nil? (JSON.parse new_styles).each do |s| styles.each do |p| next unless p.id.to_s == s["id"].to_s && p.name.to_s != s["name"].to_s # if p.id.to_s == s["id"].to_s && p.name.to_s != s["name"].to_s style_list.remove(p.name.to_s) variants = children.tagged_with(p.name.to_s, on: :styles, any: true) variants.each do |v| v.style_list.remove(p.name.to_s) v.style_list.add(s["name"].to_s) v.save(validate: false) end style_list.add(s["name"].to_s) end end end if save(validate: false) children.each do |variant| attributes_json = {} attributes_json = variant.assign_attributes_to_variant(variant.size_list, id, attributes_json) unless variant.size_list.count.zero? attributes_json = variant.assign_attributes_to_variant(variant.color_list, id, attributes_json) unless variant.color_list.count.zero? attributes_json = variant.assign_attributes_to_variant(variant.style_list, id, attributes_json) unless variant.style_list.count.zero? variant.attributes_json = attributes_json.to_json variant.save(validate: false) end end end # rubocop:disable Metrics/BlockLength def save_new_attributes(new_sizes, new_colors, new_styles) 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) end end unless new_colors.nil? new_colors.each do |s| color_list.add(s.to_s) save(validate: false) end end unless new_styles.nil? new_styles.each do |s| style_list.add(s.to_s) save(validate: false) 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) 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.barcode = '' 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 @products_variant.attributes_json = attributes.to_json @products_variant.save(validate: false) @products_variant.generate_barcode @products_variant.save(validate: false) end end end # rubocop:enable Metrics/BlockLength def children Product.where("parent_id = ? and status != ? ", id, 0) end def attributes_to_hash JSON.parse attributes_json.gsub('=>', ':') end def display_attributes attributes = "" unless attributes_json.nil? attributes_to_hash.each do |attr_, value| description = I18n.t("dictionary." + attr_) + ": #{value}" attributes = attributes.blank? ? description.to_s : attributes.to_s + " " + description.to_s end end attributes end def attrs_array attributes = [] unless attributes_json.nil? attributes_to_hash.each do |attr_, value| attributes << I18n.t("dictionary." + attr_) + ": #{value.capitalize}" end end attributes end def display_attributes_receipt attributes = "" unless attributes_json.nil? attributes_to_hash.each do |_attr_, value| attributes = attributes.blank? ? value.to_s : attributes.to_s + " " + value.to_s end end attributes.upcase end def self.most_selled_products(period, user) # se envia al user porque no se puede acceder al current, esto es, para que el gerente # vea los mas vendidos de su punto de venta. all_products = Array.new if period == 'week' beg_period = Date.current.beginning_of_week end_period = Date.current.end_of_week elsif period == 'month' beg_period = Date.current.beginning_of_month end_period = Date.current.end_of_month end if user.usertype == "A" || user.usertype == "SS" quantities_top_products = SalesDetail.activos.joins(:sale, :product).where("sales.date_sale between ? and ?", beg_period, end_period).group('products.name').order('sum_quantity desc').limit(10).sum(:quantity) total_top_products = SalesDetail.activos.joins(:sale).where('sales.date_sale between ? and ?', beg_period, end_period).order('sum_quantity desc').limit(10).sum(:quantity) total_sold = SalesDetail.activos.joins(:sale).where('sales.date_sale between ? and ?', beg_period, end_period).sum(:quantity) elsif user.usertype == 'G' quantities_top_products = user.pointsale.sales_details.joins(:sale, :product).where("sales.status != ? and sales.date_sale between ? and ?", 1, beg_period, end_period).group('products.name').order('sum_quantity desc').limit(10).sum(:quantity) total_top_products = user.pointsale.sales_details.joins(:sale).where('sales.status != ? and sales.date_sale between ? and ?', 1, beg_period, end_period).order('sum_quantity desc').limit(10).sum(:quantity) total_sold = user.pointsale.sales_details.joins(:sale).where('sales.status != ? and sales.date_sale between ? and ?', 1, beg_period, end_period).sum(:quantity) end others = total_sold - total_top_products if others > 0 quantities_top_products[:Otros] = others end quantities_top_products end def get_available_children(just_pointsales) children = Product.vigentes.where("parent_id = ?", id).pluck(:id) if just_pointsales AvailableProduct.where("product_id IN (?)", children).joins(:pointsale).select(:pointsale_id, :name).distinct(:pointsale_id) else AvailableProduct.where("product_id IN (?)", children) end end def generate_barcode if barcode.blank? barcode_generated = format('%07d', id) self.barcode = barcode_generated save_path = Rails.public_path.join('barcodes', "#{barcode_generated}.png") File.open(save_path, 'wb') { |f| f.write Barby::Code39.new(barcode_generated).to_png(height: 50, margin: 5) } end end def self.gen_barcodes_existing_prods products = Product.vigentes products.each do |product| product.generate_barcode product.skip_sku_validation = true product.save end end def self.gen_only_barcodes_w_empty products = Product.activos_children.where(barcode: '') products.each do |product| product.skip_sku_validation = true product.barcode = format('%07d', product.id) product.save end puts 'termine' end def self.gen_barcode_img_for_existing_barcodes products = Product.activos_children counter = 0 products.each do |product| if product.barcode? unless File.file?(Rails.public_path.join('barcodes', "#{product.barcode}.png")) save_path = Rails.public_path.join('barcodes', "#{product.barcode}.png") File.open(save_path, 'wb') { |f| f.write Barby::Code39.new(product.barcode).to_png(height: 50, margin: 5) } counter += 1; end else puts "***** NO TIENE BARCODE! #{product.id}" end end puts "TERMINÉ, generé: #{counter} imagenes de barcode" end def self.generate_sku_existing_prods products = Product.vigentes products.each do |product| product.sku = if product.category.parent.present? # es subcategory "#{product.category.parent.category.slice(0..2)}-#{product.id}-#{product.category.gsub(/[aeiou]/i, '').slice(0..2).upcase}-#{product.id}" else "#{product.category.category.slice(0..2)}-#{product.id}" end product.skip_sku_validation = true product.save end end end