module Workarea class Order module Queries extend ActiveSupport::Concern included do scope :placed, -> { where(:placed_at.gt => Time.at(0)) } scope :not_placed, -> { where(placed_at: nil) } scope :carts, -> { where(placed_at: nil) } scope :recently_updated, -> { where(:updated_at.gte => 15.minutes.ago) } scope :since, ->(time) { where(:created_at.gte => time) } scope :copied_from, ->(id) { where(copied_from_id: id) } end module ClassMethods # Query for orders that are expired, meaning they had not # been updated, placed, nor completed checkout for longer than the # +Workarea.config.order_expiration_period+. Contrast this with # the +Order.expired_in_checkout+ query, which does factor in orders that # have entered the checkout process. # # @return [Mongoid::Criteria] # def expired Order.where( :updated_at.lt => Time.current - Workarea.config.order_expiration_period, checkout_started_at: nil, placed_at: nil ) end # Query for orders which have expired in checkout, meaning they have been # not been placed or updated for longer than the # +Workarea.config.order_expiration_period+, but have started # checkout. Contrast this with +Order.expired+, which does not # factor in orders that have started checkout. # # @return [Mongoid::Criteria] def expired_in_checkout Order.where( :updated_at.lt => Time.current - Workarea.config.order_expiration_period, :checkout_started_at.lt => Time.current - Workarea.config.order_expiration_period, placed_at: nil ) end # Find a current cart for a session. Returns a new order if one cannot be found. # # @param params [Hash] # @return [Order] # def find_current(params = {}) if params[:id].present? Order.not_placed.find(params[:id].to_s) elsif params[:user_id].present? Order.recently_updated.not_placed.find_by(params.slice(:user_id)) else Order.new(user_id: params[:user_id]) end rescue Mongoid::Errors::DocumentNotFound Order.new(user_id: params[:user_id]) end def recent(user_id, limit = 3) Order .where(user_id: user_id.to_s, :placed_at.exists => true) .excludes(placed_at: nil) .order_by([:placed_at, :desc]) .limit(limit) end def totals(start_time = Time.current - 30.years, end_time = Time.current) cents = Order. where(:placed_at.gte => start_time, :placed_at.lt => end_time). sum('total_price.cents') Money.new(cents || 0) end def total_placed(start_time = Time.current - 30.years, end_time = Time.current) Order. where(:placed_at.gte => start_time, :placed_at.lt => end_time). count end def recent_placed(limit = 5) Order. excludes(placed_at: nil). order_by([:placed_at, :desc]). limit(limit) end def need_reminding Order.where( placed_at: nil, reminded_at: nil, :checkout_started_at.lte => Workarea.config.order_active_period.ago, :email.exists => true, :email.ne => '', :items.exists => true, :items.ne => [] ) end def find_by_token(token) Order.find_by(token: token) rescue nil end # Find the average order value for placed orders # in a given time period. This is defined as the # order total without taxes or shipping costs. # # @param start_time [Time, String] # @param end_time [Time, String] # # @return [Money] # def average_order_value(start_time = 30.years.ago, end_time = Time.current) cents = Order .where(:placed_at.gte => start_time, :placed_at.lt => end_time) .avg('total_price.cents') Money.new(cents || 0) end # TODO: Remove in an appropriate future version; no longer used in Base def abandoned_count(start_time = 30.years.ago, end_time = Time.current) Order .where(:created_at.lte => Workarea.config.order_active_period.ago) .where(:created_at.gte => start_time, :created_at.lt => end_time) .where(:'items.0.sku'.exists => true) .any_of({ placed_at: nil }, { :placed_at.exists => false }) .count end # TODO: Remove in an appropriate future version; no longer used in Base def abandoned_value(start_time = 30.years.ago, end_time = Time.current) cents = Order .where(:created_at.lte => Workarea.config.order_active_period.ago) .where(:created_at.gte => start_time, :created_at.lt => end_time) .any_of({ placed_at: nil }, { :placed_at.exists => false }) .sum('total_value.cents') Money.new(cents || 0) end # TODO: Remove in an appropriate future version; no longer used in Base def abandoned_product_ids(start_time = 30.years.ago, end_time = Time.current, limit = 5) results = Order.collection.aggregate([ { '$match' => { '$and' => [ { 'created_at' => { '$gte' => Time.mongoize(start_time), '$lt' => Time.mongoize(end_time) } }, { 'created_at' => { '$lte' => Time.mongoize(Workarea.config.order_active_period.ago) } } ], '$or' => [ { 'placed_at' => nil }, { 'placed_at' => { '$exists' => false } } ], 'items.0.sku' => { '$exists' => true } }, }, { '$unwind' => '$items' }, { '$group' => { '_id' => '$items.product_id', 'total' => { '$sum' => '$items.total_value.cents' }, 'count' => { '$sum' => 1 } } }, { '$sort' => { 'count' => -1 } }, { '$limit' => limit } ]) results.reduce({}) do |memo, result| memo.merge!( result['_id'] => { count: result['count'], total: Money.new(result['total']) } ) end end end end end end