product.rb 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  1. class Product < ActiveRecord::Base
  2. ##--- Asociaciones
  3. belongs_to :unit
  4. has_and_belongs_to_many :categories
  5. has_and_belongs_to_many :pointsales, join_table: :available_products
  6. has_many :sales_details
  7. has_many :purchase_details
  8. has_many :inventories_moves
  9. has_many :pre_sales
  10. has_many :pre_purchases
  11. has_many :special_prices
  12. has_many :pre_transfers
  13. has_many :available_products
  14. has_many :promotions
  15. enum status: [:erased, :active, :inactive]
  16. acts_as_taggable_on :sizes, :colors, :styles
  17. accepts_nested_attributes_for :available_products
  18. audited
  19. attr_accessor :skip_sku_validation
  20. mount_uploader :img_product, ImageUploader
  21. ##--- Validaciones previas de guardar
  22. validates :sku,
  23. presence: { message: "Debe capturar el SKU del producto." },
  24. length: { maximum: 30, too_long: "El máximo de caracteres para el SKU debe ser %{count}." },
  25. uniqueness: { message: "El SKU ya fue utilizado, favor de especificar otro." },
  26. unless: :skip_sku_validation
  27. validates_presence_of :name, message: "Debe capturar el nombre del producto."
  28. validates_presence_of :unit_id, message: "Debe seleccionar la unidad de medida correspondiente al producto."
  29. validates_presence_of :category_ids, message: "Debe elegir por lo menos una línea de producto relacionada al producto."
  30. validates :barcode, uniqueness: { message: "El código de barras ya fue utilizado, favor de especificar otro." }, allow_blank: true
  31. validates_presence_of :name, message: "Debe capturar el nombre del producto."
  32. validates_presence_of :price_sale, message: "Debe capturar el precio de venta del producto."
  33. def valid_categories
  34. categories.count > 0
  35. end
  36. def small_img
  37. if img_product?
  38. img_product.url(:medium).to_s
  39. else
  40. img = "/images/original/missing.png"
  41. end
  42. end
  43. ##--- Tipo de vistas / consultas
  44. scope :vigentes, -> { where.not(products: { status: 0 }).order(" products.status ASC, products.name ASC") }
  45. scope :activos, -> { where(status: 1).order("products.name ASC") }
  46. scope :activos_children, -> { activos.where(is_parent: false).order("products.name ASC") }
  47. scope :vigentes_parents, -> { vigentes.where("parent_id IS NULL") }
  48. scope :name_sku_barcode_like, ->(name) { activos.where("is_parent = ? and (name ilike ? or sku ilike ? or barcode ilike ?)", false, "%#{name}%", "%#{name}%", "%#{name}%") }
  49. 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}%") }
  50. # para special_prices
  51. scope :name_sku_barcode_like_sp, ->(name) { activos.where("is_parent = ? and (name ilike ? or sku ilike ? or barcode ilike ?)", true, "%#{name}%", "%#{name}%", "%#{name}%") }
  52. def name_with_sku
  53. sku.to_s + " - " + name.to_s
  54. end
  55. def get_promotion
  56. category_id = categories[0].parent.present? ? categories[0].parent.id : categories[0].id
  57. category_array = [category_id, categories.ids].flatten
  58. product_ids = [id, parent_id]
  59. promos = Promotion.where("product_id IN (?) OR category_id IN (?)", product_ids, category_array).order("percent, id DESC").vigentes.first
  60. end
  61. def display_sku_name_attributes
  62. sku.to_s + " | " + name.to_s + " | " + display_attributes.to_s
  63. end
  64. def full_display
  65. show_name = name + "\n" + "SKU: " + sku + "\n"
  66. show_name += "\n" + display_attributes.to_s if parent_id.present?
  67. show_name += " Código de barras: " + barcode if parent_id.present? && barcode.present?
  68. show_name
  69. end
  70. def stock_in_pointsale(pointsale_id)
  71. stock = 0
  72. # checar si hay existencias en los almacenes.
  73. warehouses_stock = WarehouseStock.where(product_id: id)
  74. warehouses_stock.each do |warehouse|
  75. # solamente hacerlo cuando pointsale id sea nil porque es el index de producto
  76. stock += warehouse.stock if pointsale_id.zero?
  77. end
  78. # existencias en puntos de venta.
  79. availables = AvailableProduct.where(product_id: id)
  80. availables.each do |available|
  81. if pointsale_id == available.pointsale_id
  82. stock = available.stock
  83. elsif pointsale_id.zero?
  84. stock += available.stock
  85. end
  86. end
  87. stock
  88. end
  89. def can_be_deleted?
  90. if is_parent
  91. children_ids = Product.where(parent_id: id).pluck(:id)
  92. in_available = AvailableProduct.where("product_id IN (?) and stock > 0", children_ids).any?
  93. in_warehouse = WarehouseStock.where("product_id IN (?) and stock > 0", children_ids).any?
  94. else
  95. in_warehouse = WarehouseStock.where("product_id = #{id} and stock > 0").any?
  96. in_available = AvailableProduct.where("product_id = #{id} and stock > 0").any?
  97. end
  98. in_available == true || in_warehouse == true ? false : true
  99. end
  100. def last_sale(pointsale_id)
  101. unless pointsale_id.nil?
  102. last_time = Pointsale.find(pointsale_id).sales_details.where(product_id: id).last
  103. end
  104. end
  105. def available_in_pointsale?(pointsale_id)
  106. if pointsales.exists?(id: pointsale_id)
  107. true
  108. else
  109. false
  110. end
  111. end
  112. def get_available_in_pointsale(pointsale_id)
  113. AvailableProduct.find_by(pointsale_id: pointsale_id, product_id: id)
  114. end
  115. def get_price_sale(pointsale_id)
  116. available = get_available_in_pointsale(pointsale_id)
  117. if available.present? && available.price_sale.present?
  118. available.price_sale
  119. else
  120. price_sale
  121. end
  122. end
  123. def pointsales_prices
  124. AvailableProduct.where("product_id = ? and price_sale IS NOT NULL", id)
  125. end
  126. def variants_attributes
  127. ActsAsTaggableOn::Tagging.where(taggable_id: id, taggable_type: 'Product').distinct(:context).select(:context)
  128. end
  129. def get_combinations(combinations, attributes)
  130. if presentation
  131. ##--- crear el array de los arrays de atributos
  132. if size_list.count > 0
  133. attributes << size_list
  134. end
  135. if color_list.count > 0
  136. attributes << color_list
  137. end
  138. if style_list.count > 0
  139. attributes << style_list
  140. end
  141. ##--- verificar que atributos tenga mas de una categoria
  142. if attributes.count > 1
  143. ##--- Making combinations from arrays
  144. first_array, *rest_of_arrays = attributes
  145. combinations = first_array.product(*rest_of_arrays)
  146. else
  147. attributes[0].each do |attribute|
  148. combinations << attribute
  149. end
  150. end
  151. combinations
  152. end
  153. end
  154. # rubocop:disable Metrics/BlockLength
  155. def save_variants(current_user)
  156. Thread.new do
  157. combinations = Array.new
  158. attributes = Array.new
  159. combinations = get_combinations(combinations, attributes)
  160. ##--- recorrer combinaciones para crear las variantes de productos
  161. unless combinations.nil?
  162. combinations.each_with_index do |combination, index|
  163. @products_variant = Product.new
  164. @products_variant = dup
  165. @products_variant.parent_id = id
  166. @products_variant.is_parent = false
  167. @products_variant.sku = sku + (index + 1).to_s + "A"
  168. @products_variant.category_ids = category_ids
  169. attributes_json = {}
  170. if combination.is_a?(Array)
  171. combination.each do |attrib|
  172. attributes_json = @products_variant.assign_attributes_to_variant(attrib, id, attributes_json)
  173. end
  174. else
  175. attributes_json = @products_variant.assign_attributes_to_variant(combination, id, attributes_json)
  176. end
  177. @products_variant.attributes_json = attributes_json.to_json
  178. @products_variant.save
  179. if current_user.usertype == 'G'
  180. AvailableProduct.create(product_id: @products_variant.id, pointsale_id: current_user.pointsale_id, stock: 0)
  181. end
  182. end
  183. end
  184. end
  185. end
  186. # rubocop:enable Metrics/BlockLength
  187. def assign_attributes_to_variant(attri, parent_id, attributes_json)
  188. attri_id = ActsAsTaggableOn::Tag.where("lower(name) = lower(?)", attri).select(:id).first
  189. get_context = ActsAsTaggableOn::Tagging.where(tag_id: attri_id, taggable_id: parent_id, taggable_type: 'Product').select(:context).first
  190. context = get_context.context
  191. if id != parent_id
  192. if context == "sizes"
  193. self.size_list = attri.to_s
  194. elsif context == "colors"
  195. self.color_list = attri.to_s
  196. elsif context == "styles"
  197. self.style_list = attri.to_s
  198. end
  199. end
  200. attributes_json[context] = attri.to_s
  201. attributes_json
  202. end
  203. def update_attributes_to_variants(new_sizes, new_colors, new_styles)
  204. unless new_sizes.nil?
  205. (JSON.parse new_sizes).each do |s|
  206. sizes.each do |p|
  207. next unless p.id.to_s == s["id"].to_s && p.name.to_s != s["name"].to_s
  208. # if p.id.to_s == s["id"].to_s && p.name.to_s != s["name"].to_s
  209. size_list.remove(p.name.to_s)
  210. variants = children.tagged_with(p.name.to_s, on: :sizes, any: true)
  211. variants.each do |v|
  212. v.size_list.remove(p.name.to_s)
  213. v.size_list.add(s["name"].to_s)
  214. v.save(validate: false)
  215. end
  216. size_list.add(s["name"].to_s)
  217. end
  218. end
  219. end
  220. unless new_colors.nil?
  221. (JSON.parse new_colors).each do |s|
  222. colors.each do |p|
  223. next unless p.id.to_s == s["id"].to_s && p.name.to_s != s["name"].to_s
  224. # if p.id.to_s == s["id"].to_s && p.name.to_s != s["name"].to_s
  225. color_list.remove(p.name.to_s)
  226. variants = children.tagged_with(p.name.to_s, on: :colors, any: true)
  227. variants.each do |v|
  228. v.color_list.remove(p.name.to_s)
  229. v.color_list.add(s["name"].to_s)
  230. v.save(validate: false)
  231. end
  232. color_list.add(s["name"].to_s)
  233. end
  234. end
  235. end
  236. unless new_styles.nil?
  237. (JSON.parse new_styles).each do |s|
  238. styles.each do |p|
  239. next unless p.id.to_s == s["id"].to_s && p.name.to_s != s["name"].to_s
  240. # if p.id.to_s == s["id"].to_s && p.name.to_s != s["name"].to_s
  241. style_list.remove(p.name.to_s)
  242. variants = children.tagged_with(p.name.to_s, on: :styles, any: true)
  243. variants.each do |v|
  244. v.style_list.remove(p.name.to_s)
  245. v.style_list.add(s["name"].to_s)
  246. v.save(validate: false)
  247. end
  248. style_list.add(s["name"].to_s)
  249. end
  250. end
  251. end
  252. if save(validate: false)
  253. children.each do |variant|
  254. attributes_json = {}
  255. attributes_json = variant.assign_attributes_to_variant(variant.size_list, id, attributes_json) unless variant.size_list.count.zero?
  256. attributes_json = variant.assign_attributes_to_variant(variant.color_list, id, attributes_json) unless variant.color_list.count.zero?
  257. attributes_json = variant.assign_attributes_to_variant(variant.style_list, id, attributes_json) unless variant.style_list.count.zero?
  258. variant.attributes_json = attributes_json.to_json
  259. variant.save(validate: false)
  260. end
  261. end
  262. end
  263. # rubocop:disable Metrics/BlockLength
  264. def save_new_attributes(new_sizes, new_colors, new_styles)
  265. Thread.new do
  266. combinations = Array.new
  267. attributes = Array.new
  268. unless new_sizes.nil?
  269. new_sizes.each do |s|
  270. size_list.add(s.to_s)
  271. save(validate: false)
  272. end
  273. end
  274. unless new_colors.nil?
  275. new_colors.each do |s|
  276. color_list.add(s.to_s)
  277. save(validate: false)
  278. end
  279. end
  280. unless new_styles.nil?
  281. new_styles.each do |s|
  282. style_list.add(s.to_s)
  283. save(validate: false)
  284. end
  285. end
  286. combinations = get_combinations(combinations, attributes)
  287. # self.save(:validate: false)
  288. combinations.each_with_index do |combination, index|
  289. attributes = {}
  290. if combination.is_a?(Array)
  291. combination.each do |c|
  292. attributes = assign_attributes_to_variant(c, id, attributes)
  293. end
  294. else
  295. attributes = assign_attributes_to_variant(combination, id, attributes)
  296. end
  297. next unless children.where("attributes_json = ?", attributes.to_json).select(:id).first.nil?
  298. # if children.where("attributes_json = ?", attributes.to_json).select(:id).first.nil?
  299. @products_variant = Product.new
  300. @products_variant = dup
  301. @products_variant.parent_id = id
  302. @products_variant.is_parent = false
  303. @products_variant.sku = sku + (index + 1).to_s + "A"
  304. @products_variant.category_ids = category_ids
  305. attrs_json = {}
  306. if combination.is_a?(Array)
  307. combination.each do |attrib|
  308. attrs_json = @products_variant.assign_attributes_to_variant(attrib, id, attrs_json)
  309. end
  310. else
  311. attrs_json = @products_variant.assign_attributes_to_variant(combination, id, attrs_json)
  312. end
  313. @products_variant.attributes_json = attributes.to_json
  314. @products_variant.save(validate: false)
  315. end
  316. end
  317. end
  318. # rubocop:enable Metrics/BlockLength
  319. def children
  320. Product.where("parent_id = ? and status != ? ", id, 0)
  321. end
  322. def attributes_to_hash
  323. JSON.parse attributes_json.gsub('=>', ':')
  324. end
  325. def display_attributes
  326. attributes = ""
  327. unless attributes_json.nil?
  328. attributes_to_hash.each do |attr_, value|
  329. description = I18n.t("dictionary." + attr_) + ": #{value}"
  330. attributes = attributes.blank? ? description.to_s : attributes.to_s + " " + description.to_s
  331. end
  332. end
  333. attributes
  334. end
  335. def display_attributes_receipt
  336. attributes = ""
  337. unless attributes_json.nil?
  338. attributes_to_hash.each do |_attr_, value|
  339. attributes = attributes.blank? ? value.to_s : attributes.to_s + " " + value.to_s
  340. end
  341. end
  342. attributes.upcase
  343. end
  344. def self.most_selled_products(period, user)
  345. # se envia al user porque no se puede acceder al current, esto es, para que el gerente
  346. # vea los mas vendidos de su punto de venta.
  347. all_products = Array.new
  348. if period == 'week'
  349. beg_period = Date.current.beginning_of_week
  350. end_period = Date.current.end_of_week
  351. elsif period == 'month'
  352. beg_period = Date.current.beginning_of_month
  353. end_period = Date.current.end_of_month
  354. end
  355. if user.usertype == "A" || user.usertype == "SS"
  356. 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)
  357. 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)
  358. total_sold = SalesDetail.activos.joins(:sale).where('sales.date_sale between ? and ?', beg_period, end_period).sum(:quantity)
  359. elsif user.usertype == 'G'
  360. 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)
  361. 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)
  362. total_sold = user.pointsale.sales_details.joins(:sale).where('sales.status != ? and sales.date_sale between ? and ?', 1, beg_period, end_period).sum(:quantity)
  363. end
  364. others = total_sold - total_top_products
  365. if others > 0
  366. quantities_top_products[:Otros] = others
  367. end
  368. quantities_top_products
  369. end
  370. def get_available_children(just_pointsales)
  371. children = Product.vigentes.where("parent_id = ?", id).pluck(:id)
  372. if just_pointsales
  373. AvailableProduct.where("product_id IN (?)", children).joins(:pointsale).select(:pointsale_id, :name).distinct(:pointsale_id)
  374. else
  375. AvailableProduct.where("product_id IN (?)", children)
  376. end
  377. end
  378. end