product.rb 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
  1. require 'barby'
  2. require 'barby/barcode/code_39'
  3. require 'barby/outputter/png_outputter'
  4. class Product < ActiveRecord::Base
  5. ##--- Asociaciones
  6. belongs_to :unit
  7. belongs_to :category
  8. has_and_belongs_to_many :pointsales, join_table: :available_products
  9. has_many :sales_details
  10. has_many :purchase_details
  11. has_many :inventories_moves
  12. has_many :pre_sales
  13. has_many :pre_purchases
  14. has_many :special_prices
  15. has_many :pre_transfers
  16. has_many :available_products
  17. has_many :promotions
  18. enum status: [:erased, :active, :inactive]
  19. acts_as_taggable_on :sizes, :colors, :styles
  20. accepts_nested_attributes_for :available_products
  21. audited
  22. attr_accessor :skip_sku_validation
  23. mount_uploader :img_product, ImageUploader
  24. ##--- Validaciones previas de guardar
  25. validates :sku,
  26. presence: { message: "Debe capturar el SKU del producto." },
  27. length: { maximum: 30, too_long: "El máximo de caracteres para el SKU debe ser %{count}." },
  28. uniqueness: { message: "El SKU ya fue utilizado, favor de especificar otro." },
  29. unless: :skip_sku_validation
  30. validates_presence_of :name, message: "Debe capturar el nombre del producto."
  31. validates_presence_of :unit_id, message: "Debe seleccionar la unidad de medida correspondiente al producto."
  32. validates_presence_of :category_id, message: "Debe seleccionar una línea o sublinea para el producto."
  33. validates :barcode, uniqueness: { message: "El código de barras ya fue utilizado, favor de especificar otro." }, allow_blank: true
  34. validates_presence_of :name, message: "Debe capturar el nombre del producto."
  35. validates :price_sale,
  36. presence: { message: "Debe capturar el precio de venta del producto." },
  37. numericality: { greater_than: 0.00 }
  38. validate :category_has_subcats, on: [:create, :update]
  39. def valid_categories
  40. categories.count > 0
  41. end
  42. def category_has_subcats
  43. if category.present? && category.parent_id.zero? && category.children.present?
  44. errors.add(:category_id, "Seleccione una sublínea.")
  45. end
  46. end
  47. def small_img
  48. if img_product?
  49. img_product.url(:medium).to_s
  50. else
  51. img = "/images/original/missing.png"
  52. end
  53. end
  54. ##--- Tipo de vistas / consultas
  55. scope :vigentes, -> { where.not(products: { status: 0 }).order(" products.status ASC, products.name ASC") }
  56. scope :activos, -> { where(status: 1).order("products.name ASC") }
  57. scope :activos_children, -> { activos.where(is_parent: false).order("products.name ASC") }
  58. scope :vigentes_parents, -> { vigentes.where("parent_id IS NULL") }
  59. scope :name_sku_barcode_like, ->(name) { activos.where("is_parent = ? and (name ilike ? or sku ilike ? or barcode ilike ?)", false, "%#{name}%", "%#{name}%", "%#{name}%") }
  60. 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}%") }
  61. # para special_prices
  62. scope :name_sku_barcode_like_sp, ->(name) { activos.where("is_parent = ? and (name ilike ? or sku ilike ? or barcode ilike ?)", true, "%#{name}%", "%#{name}%", "%#{name}%") }
  63. def name_with_sku
  64. sku.to_s + " - " + name.to_s
  65. end
  66. def get_promotion
  67. category_array = if category.parent.present?
  68. [category.parent.id, Category.activos.where("parent_id = ?", category.parent.id).pluck(:id)].flatten
  69. else
  70. [category_id]
  71. end
  72. product_ids = [id, parent_id]
  73. promos = Promotion.where("product_id IN (?) OR category_id IN (?)", product_ids, category_array).order("percent, id DESC").vigentes.first
  74. end
  75. def display_sku_name_attributes
  76. sku.to_s + " | " + name.to_s + " | " + display_attributes.to_s
  77. end
  78. def full_display
  79. show_name = name + "\n" + "SKU: " + sku + "\n"
  80. show_name += "\n" + display_attributes.to_s if parent_id.present?
  81. show_name += " Código de barras: " + barcode if parent_id.present? && barcode.present?
  82. show_name
  83. end
  84. def stock_in_pointsale(pointsale_id)
  85. stock = 0
  86. # checar si hay existencias en los almacenes.
  87. warehouses_stock = WarehouseStock.where(product_id: id)
  88. warehouses_stock.each do |warehouse|
  89. # solamente hacerlo cuando pointsale id sea nil porque es el index de producto
  90. stock += warehouse.stock if pointsale_id.zero?
  91. end
  92. # existencias en puntos de venta.
  93. availables = AvailableProduct.where(product_id: id)
  94. availables.each do |available|
  95. if pointsale_id == available.pointsale_id
  96. stock = available.stock
  97. elsif pointsale_id.zero?
  98. stock += available.stock
  99. end
  100. end
  101. stock
  102. end
  103. def can_be_deleted?
  104. if is_parent
  105. children_ids = Product.where(parent_id: id).pluck(:id)
  106. in_available = AvailableProduct.where("product_id IN (?) and stock > 0", children_ids).any?
  107. in_warehouse = WarehouseStock.where("product_id IN (?) and stock > 0", children_ids).any?
  108. else
  109. in_warehouse = WarehouseStock.where("product_id = #{id} and stock > 0").any?
  110. in_available = AvailableProduct.where("product_id = #{id} and stock > 0").any?
  111. end
  112. in_available == true || in_warehouse == true ? false : true
  113. end
  114. def last_sale(pointsale_id)
  115. unless pointsale_id.nil?
  116. last_time = Pointsale.find(pointsale_id).sales_details.where(product_id: id).last
  117. end
  118. end
  119. def available_in_pointsale?(pointsale_id)
  120. if pointsales.exists?(id: pointsale_id)
  121. true
  122. else
  123. false
  124. end
  125. end
  126. def get_available_in_pointsale(pointsale_id)
  127. AvailableProduct.find_by(pointsale_id: pointsale_id, product_id: id)
  128. end
  129. def get_price_sale(pointsale_id)
  130. available = get_available_in_pointsale(pointsale_id)
  131. if available.present? && available.price_sale.present?
  132. available.price_sale
  133. else
  134. price_sale
  135. end
  136. end
  137. def pointsales_prices
  138. AvailableProduct.where("product_id = ? and price_sale IS NOT NULL", id)
  139. end
  140. def variants_attributes
  141. ActsAsTaggableOn::Tagging.where(taggable_id: id, taggable_type: 'Product').distinct(:context).select(:context)
  142. end
  143. def get_combinations(combinations, attributes)
  144. if presentation
  145. ##--- crear el array de los arrays de atributos
  146. if size_list.count > 0
  147. attributes << size_list
  148. end
  149. if color_list.count > 0
  150. attributes << color_list
  151. end
  152. if style_list.count > 0
  153. attributes << style_list
  154. end
  155. ##--- verificar que atributos tenga mas de una categoria
  156. if attributes.count > 1
  157. ##--- Making combinations from arrays
  158. first_array, *rest_of_arrays = attributes
  159. combinations = first_array.product(*rest_of_arrays)
  160. else
  161. attributes[0].each do |attribute|
  162. combinations << attribute
  163. end
  164. end
  165. combinations
  166. end
  167. end
  168. # rubocop:disable Metrics/BlockLength
  169. def save_variants(current_user)
  170. Thread.new do
  171. combinations = Array.new
  172. attributes = Array.new
  173. combinations = get_combinations(combinations, attributes)
  174. ##--- recorrer combinaciones para crear las variantes de productos
  175. unless combinations.nil?
  176. combinations.each_with_index do |combination, index|
  177. @products_variant = Product.new
  178. @products_variant = dup
  179. @products_variant.parent_id = id
  180. @products_variant.is_parent = false
  181. @products_variant.sku = sku + (index + 1).to_s + "A"
  182. attributes_json = {}
  183. if combination.is_a?(Array)
  184. combination.each do |attrib|
  185. attributes_json = @products_variant.assign_attributes_to_variant(attrib, id, attributes_json)
  186. end
  187. else
  188. attributes_json = @products_variant.assign_attributes_to_variant(combination, id, attributes_json)
  189. end
  190. @products_variant.attributes_json = attributes_json.to_json
  191. @products_variant.save
  192. @products_variant.generate_barcode
  193. @products_variant.save
  194. if current_user.usertype == 'G'
  195. AvailableProduct.create(product_id: @products_variant.id, pointsale_id: current_user.pointsale_id, stock: 0)
  196. end
  197. end
  198. end
  199. end
  200. end
  201. # rubocop:enable Metrics/BlockLength
  202. # rubocop:disable Metrics/BlockLength
  203. def save_variants_no_thread(current_user)
  204. combinations = Array.new
  205. attributes = Array.new
  206. combinations = get_combinations(combinations, attributes)
  207. ##--- recorrer combinaciones para crear las variantes de productos
  208. unless combinations.nil?
  209. combinations.each_with_index do |combination, index|
  210. @products_variant = Product.new
  211. @products_variant = dup
  212. @products_variant.parent_id = id
  213. @products_variant.is_parent = false
  214. @products_variant.sku = sku + (index + 1).to_s + "A"
  215. attributes_json = {}
  216. if combination.is_a?(Array)
  217. combination.each do |attrib|
  218. attributes_json = @products_variant.assign_attributes_to_variant(attrib, id, attributes_json)
  219. end
  220. else
  221. attributes_json = @products_variant.assign_attributes_to_variant(combination, id, attributes_json)
  222. end
  223. @products_variant.attributes_json = attributes_json.to_json
  224. @products_variant.save
  225. @products_variant.generate_barcode
  226. @products_variant.save
  227. if current_user.usertype == 'G'
  228. AvailableProduct.create(product_id: @products_variant.id, pointsale_id: current_user.pointsale_id, stock: 0)
  229. end
  230. end
  231. end
  232. end
  233. # rubocop:enable Metrics/BlockLength
  234. def assign_attributes_to_variant(attri, parent_id, attributes_json)
  235. attri_id = ActsAsTaggableOn::Tag.where("lower(name) = lower(?)", attri).select(:id).first
  236. get_context = ActsAsTaggableOn::Tagging.where(tag_id: attri_id, taggable_id: parent_id, taggable_type: 'Product').select(:context).first
  237. context = get_context.context
  238. if id != parent_id
  239. if context == "sizes"
  240. self.size_list = attri.to_s
  241. elsif context == "colors"
  242. self.color_list = attri.to_s
  243. elsif context == "styles"
  244. self.style_list = attri.to_s
  245. end
  246. end
  247. attributes_json[context] = attri.to_s
  248. attributes_json
  249. end
  250. def update_attributes_to_variants(new_sizes, new_colors, new_styles)
  251. unless new_sizes.nil?
  252. (JSON.parse new_sizes).each do |s|
  253. sizes.each do |p|
  254. next unless p.id.to_s == s["id"].to_s && p.name.to_s != s["name"].to_s
  255. # if p.id.to_s == s["id"].to_s && p.name.to_s != s["name"].to_s
  256. size_list.remove(p.name.to_s)
  257. variants = children.tagged_with(p.name.to_s, on: :sizes, any: true)
  258. variants.each do |v|
  259. v.size_list.remove(p.name.to_s)
  260. v.size_list.add(s["name"].to_s)
  261. v.save(validate: false)
  262. end
  263. size_list.add(s["name"].to_s)
  264. end
  265. end
  266. end
  267. unless new_colors.nil?
  268. (JSON.parse new_colors).each do |s|
  269. colors.each do |p|
  270. next unless p.id.to_s == s["id"].to_s && p.name.to_s != s["name"].to_s
  271. # if p.id.to_s == s["id"].to_s && p.name.to_s != s["name"].to_s
  272. color_list.remove(p.name.to_s)
  273. variants = children.tagged_with(p.name.to_s, on: :colors, any: true)
  274. variants.each do |v|
  275. v.color_list.remove(p.name.to_s)
  276. v.color_list.add(s["name"].to_s)
  277. v.save(validate: false)
  278. end
  279. color_list.add(s["name"].to_s)
  280. end
  281. end
  282. end
  283. unless new_styles.nil?
  284. (JSON.parse new_styles).each do |s|
  285. styles.each do |p|
  286. next unless p.id.to_s == s["id"].to_s && p.name.to_s != s["name"].to_s
  287. # if p.id.to_s == s["id"].to_s && p.name.to_s != s["name"].to_s
  288. style_list.remove(p.name.to_s)
  289. variants = children.tagged_with(p.name.to_s, on: :styles, any: true)
  290. variants.each do |v|
  291. v.style_list.remove(p.name.to_s)
  292. v.style_list.add(s["name"].to_s)
  293. v.save(validate: false)
  294. end
  295. style_list.add(s["name"].to_s)
  296. end
  297. end
  298. end
  299. if save(validate: false)
  300. children.each do |variant|
  301. attributes_json = {}
  302. attributes_json = variant.assign_attributes_to_variant(variant.size_list, id, attributes_json) unless variant.size_list.count.zero?
  303. attributes_json = variant.assign_attributes_to_variant(variant.color_list, id, attributes_json) unless variant.color_list.count.zero?
  304. attributes_json = variant.assign_attributes_to_variant(variant.style_list, id, attributes_json) unless variant.style_list.count.zero?
  305. variant.attributes_json = attributes_json.to_json
  306. variant.save(validate: false)
  307. end
  308. end
  309. end
  310. # rubocop:disable Metrics/BlockLength
  311. def save_new_attributes(new_sizes, new_colors, new_styles)
  312. Thread.new do
  313. combinations = Array.new
  314. attributes = Array.new
  315. unless new_sizes.nil?
  316. new_sizes.each do |s|
  317. size_list.add(s.to_s)
  318. save(validate: false)
  319. end
  320. end
  321. unless new_colors.nil?
  322. new_colors.each do |s|
  323. color_list.add(s.to_s)
  324. save(validate: false)
  325. end
  326. end
  327. unless new_styles.nil?
  328. new_styles.each do |s|
  329. style_list.add(s.to_s)
  330. save(validate: false)
  331. end
  332. end
  333. combinations = get_combinations(combinations, attributes)
  334. # self.save(:validate: false)
  335. combinations.each_with_index do |combination, index|
  336. attributes = {}
  337. if combination.is_a?(Array)
  338. combination.each do |c|
  339. attributes = assign_attributes_to_variant(c, id, attributes)
  340. end
  341. else
  342. attributes = assign_attributes_to_variant(combination, id, attributes)
  343. end
  344. next unless children.where("attributes_json = ?", attributes.to_json).select(:id).first.nil?
  345. # if children.where("attributes_json = ?", attributes.to_json).select(:id).first.nil?
  346. @products_variant = Product.new
  347. @products_variant = dup
  348. @products_variant.parent_id = id
  349. @products_variant.is_parent = false
  350. @products_variant.sku = sku + (index + 1).to_s + "A"
  351. @products_variant.barcode = ''
  352. attrs_json = {}
  353. if combination.is_a?(Array)
  354. combination.each do |attrib|
  355. attrs_json = @products_variant.assign_attributes_to_variant(attrib, id, attrs_json)
  356. end
  357. else
  358. attrs_json = @products_variant.assign_attributes_to_variant(combination, id, attrs_json)
  359. end
  360. @products_variant.attributes_json = attributes.to_json
  361. @products_variant.save(validate: false)
  362. @products_variant.generate_barcode
  363. @products_variant.save(validate: false)
  364. end
  365. end
  366. end
  367. # rubocop:enable Metrics/BlockLength
  368. def children
  369. Product.where("parent_id = ? and status != ? ", id, 0)
  370. end
  371. def attributes_to_hash
  372. JSON.parse attributes_json.gsub('=>', ':')
  373. end
  374. def display_attributes
  375. attributes = ""
  376. unless attributes_json.nil?
  377. attributes_to_hash.each do |attr_, value|
  378. description = I18n.t("dictionary." + attr_) + ": #{value}"
  379. attributes = attributes.blank? ? description.to_s : attributes.to_s + " " + description.to_s
  380. end
  381. end
  382. attributes
  383. end
  384. def attrs_array
  385. attributes = []
  386. unless attributes_json.nil?
  387. attributes_to_hash.each do |attr_, value|
  388. attributes << I18n.t("dictionary." + attr_) + ": #{value.capitalize}"
  389. end
  390. end
  391. attributes
  392. end
  393. def display_attributes_receipt
  394. attributes = ""
  395. unless attributes_json.nil?
  396. attributes_to_hash.each do |_attr_, value|
  397. attributes = attributes.blank? ? value.to_s : attributes.to_s + " " + value.to_s
  398. end
  399. end
  400. attributes.upcase
  401. end
  402. def self.most_selled_products(period, user)
  403. # se envia al user porque no se puede acceder al current, esto es, para que el gerente
  404. # vea los mas vendidos de su punto de venta.
  405. all_products = Array.new
  406. if period == 'week'
  407. beg_period = Date.current.beginning_of_week
  408. end_period = Date.current.end_of_week
  409. elsif period == 'month'
  410. beg_period = Date.current.beginning_of_month
  411. end_period = Date.current.end_of_month
  412. end
  413. if user.usertype == "A" || user.usertype == "SS"
  414. 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)
  415. 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)
  416. total_sold = SalesDetail.activos.joins(:sale).where('sales.date_sale between ? and ?', beg_period, end_period).sum(:quantity)
  417. elsif user.usertype == 'G'
  418. 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)
  419. 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)
  420. total_sold = user.pointsale.sales_details.joins(:sale).where('sales.status != ? and sales.date_sale between ? and ?', 1, beg_period, end_period).sum(:quantity)
  421. end
  422. others = total_sold - total_top_products
  423. if others > 0
  424. quantities_top_products[:Otros] = others
  425. end
  426. quantities_top_products
  427. end
  428. def get_available_children(just_pointsales)
  429. children = Product.vigentes.where("parent_id = ?", id).pluck(:id)
  430. if just_pointsales
  431. AvailableProduct.where("product_id IN (?)", children).joins(:pointsale).select(:pointsale_id, :name).distinct(:pointsale_id)
  432. else
  433. AvailableProduct.where("product_id IN (?)", children)
  434. end
  435. end
  436. def generate_barcode
  437. if barcode.blank?
  438. barcode_generated = format('%07d', id)
  439. self.barcode = barcode_generated
  440. save_path = Rails.public_path.join('barcodes', "#{barcode_generated}.png")
  441. unless Dir.exist?(Rails.public_path.join("barcodes"))
  442. Dir.mkdir(Rails.public_path.join("barcodes"))
  443. end
  444. File.open(save_path, 'wb') { |f| f.write Barby::Code39.new(barcode_generated).to_png(height: 50, margin: 5) }
  445. end
  446. end
  447. def self.gen_barcodes_existing_prods
  448. products = Product.vigentes
  449. products.each do |product|
  450. product.generate_barcode
  451. product.skip_sku_validation = true
  452. product.save
  453. end
  454. end
  455. def self.gen_only_barcodes_w_empty
  456. products = Product.activos_children.where(barcode: '')
  457. products.each do |product|
  458. product.skip_sku_validation = true
  459. product.barcode = format('%07d', product.id)
  460. product.save
  461. end
  462. puts 'termine'
  463. end
  464. def self.gen_barcode_img_for_existing_barcodes
  465. products = Product.activos_children
  466. counter = 0
  467. products.each do |product|
  468. if product.barcode?
  469. unless File.file?(Rails.public_path.join('barcodes', "#{product.barcode}.png"))
  470. save_path = Rails.public_path.join('barcodes', "#{product.barcode}.png")
  471. File.open(save_path, 'wb') { |f| f.write Barby::Code39.new(product.barcode).to_png(height: 50, margin: 5) }
  472. counter += 1;
  473. end
  474. else
  475. puts "***** NO TIENE BARCODE! #{product.id}"
  476. end
  477. end
  478. puts "TERMINÉ, generé: #{counter} imagenes de barcode"
  479. end
  480. def self.generate_sku_existing_prods
  481. products = Product.vigentes
  482. products.each do |product|
  483. product.sku = if product.category.parent.present?
  484. # es subcategory
  485. "#{product.category.parent.category.slice(0..2)}-#{product.id}-#{product.category.gsub(/[aeiou]/i, '').slice(0..2).upcase}-#{product.id}"
  486. else
  487. "#{product.category.category.slice(0..2)}-#{product.id}"
  488. end
  489. product.skip_sku_validation = true
  490. product.save
  491. end
  492. end
  493. end