Class: Workarea::Pricing::Discount

Inherits:
Object
  • Object
show all
Includes:
Mongoid::Document::Taggable, ApplicationDocument, Commentable, Releasable
Defined in:
app/models/workarea/pricing/discount.rb,
app/models/workarea/pricing/discount/order.rb,
app/models/workarea/pricing/discount/product.rb,
app/models/workarea/pricing/discount/category.rb,
app/models/workarea/pricing/discount/shipping.rb,
app/models/workarea/pricing/discount/code_list.rb,
app/models/workarea/pricing/discount/free_gift.rb,
app/models/workarea/pricing/discount/collection.rb,
app/models/workarea/pricing/discount/redemption.rb,
app/models/workarea/pricing/discount/order_total.rb,
app/models/workarea/pricing/discount/reconcile_total.rb,
app/models/workarea/pricing/discount/amount_calculator.rb,
app/models/workarea/pricing/discount/application_group.rb,
app/models/workarea/pricing/discount/buy_some_get_some.rb,
app/models/workarea/pricing/discount/product_attribute.rb,
app/models/workarea/pricing/discount/flat_or_percent_off.rb,
app/models/workarea/pricing/discount/conditions/user_tags.rb,
app/models/workarea/pricing/discount/generated_promo_code.rb,
app/models/workarea/pricing/discount/quantity_fixed_price.rb,
app/models/workarea/pricing/discount/conditions/order_total.rb,
app/models/workarea/pricing/discount/conditions/promo_codes.rb,
app/models/workarea/pricing/discount/conditions/item_quantity.rb,
app/models/workarea/pricing/discount/product_attribute/item_qualifier.rb,
app/models/workarea/pricing/discount/quantity_fixed_price/item_shares.rb,
app/models/workarea/pricing/discount/buy_some_get_some/item_application.rb,
app/models/workarea/pricing/discount/buy_some_get_some/product_application.rb,
app/models/workarea/pricing/discount/buy_some_get_some/order_items_by_product.rb,
app/models/workarea/pricing/discount/quantity_fixed_price/application_calculator.rb

Overview

This is the base class for representing a discount in the system. Available siscounts must be a customized subclass of Discount.

They must implement the #apply method, this is the method called to add the price adjustments necessary to discount the order.

Examples:

Create a new discount

class FooDiscount < Discount
  add_qualifier :foo_qualifies?

  def foo_qualifies?(order)
    order.email == '[email protected]'
  end

  def apply(order)
     total_amount = 10.to_m
     qty_share = total_amount / order.quantity

      order.items.each do |item|
        item_total = qty_share * item.quantity
        item.adjust_pricing(adjustment_data(item_total, quantity))
      end

      order
     end

    order
  end
end

Defined Under Namespace

Modules: Conditions, FlatOrPercentOff Classes: AmountCalculator, ApplicationGroup, BuySomeGetSome, Category, CodeList, Collection, FreeGift, GeneratedPromoCode, MissingConfigurationError, Order, OrderTotal, Product, ProductAttribute, QuantityFixedPrice, ReconcileTotal, Redemption, Shipping

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Commentable

#add_subscription, #remove_subscription

Methods included from Releasable

#changesets_with_children, #destroy, #in_release, #release_changes, #release_originals, #save_changeset, #skip_changeset, #without_release

Methods included from Segmentable

#active?, #active_segment_ids_with_children, #segmented?, #segments

Methods included from Release::Activation

#activate_with?, #create_activation_changeset, #save_activate_with, #was_active?

Methods included from Mongoid::Document::Taggable

included

Methods included from ApplicationDocument

#releasable?

Methods included from Sidekiq::Callbacks

assert_valid_config!, async, disable, enable, inline, #run_callbacks

Methods included from Mongoid::Document

#embedded_children

Instance Attribute Details

#allow_sale_itemsBoolean

Returns:

  • (Boolean)


76
# File 'app/models/workarea/pricing/discount.rb', line 76

field :allow_sale_items, type: Boolean, default: true

#auto_deactivatedBoolean

Returns whether to allow auto deactivation.

Returns:

  • (Boolean)

    whether to allow auto deactivation



81
# File 'app/models/workarea/pricing/discount.rb', line 81

field :auto_deactivate, type: Boolean, default: true

#auto_deactivated_atBoolean

Returns when the discount was last automatically deactivated.

Returns:

  • (Boolean)

    when the discount was last automatically deactivated



86
# File 'app/models/workarea/pricing/discount.rb', line 86

field :auto_deactivated_at, type: Time

#compatible_discount_idsArray

Returns:

  • (Array)


56
# File 'app/models/workarea/pricing/discount.rb', line 56

field :compatible_discount_ids, type: Array, default: []

#excluded_category_idsBoolean

Returns ids of categories that do not qualify for discount.

Returns:

  • (Boolean)

    ids of categories that do not qualify for discount



61
# File 'app/models/workarea/pricing/discount.rb', line 61

field :excluded_category_ids, type: Array, default: []

#excluded_product_idsBoolean

Returns ids of products that do not qualify for discount.

Returns:

  • (Boolean)

    ids of products that do not qualify for discount



66
# File 'app/models/workarea/pricing/discount.rb', line 66

field :excluded_product_ids, type: Array, default: []

#nameString

Returns:



51
# File 'app/models/workarea/pricing/discount.rb', line 51

field :name, type: String, localize: true

#price_levelString

Returns used when creating adjustment data, one of: order, shipping, item.

Returns:

  • (String)

    used when creating adjustment data, one of: order, shipping, item



46
# File 'app/models/workarea/pricing/discount.rb', line 46

class_attribute :price_level

#redemptionsEnumerable

Returns a log entry for each time it was redeemed.

Returns:

  • (Enumerable)

    a log entry for each time it was redeemed



91
92
# File 'app/models/workarea/pricing/discount.rb', line 91

has_many :redemptions,
class_name: 'Workarea::Pricing::Discount::Redemption'

#single_useBoolean

Returns:

  • (Boolean)


71
# File 'app/models/workarea/pricing/discount.rb', line 71

field :single_use, type: Boolean, default: false

Class Method Details

.add_qualifier(method_name) ⇒ Symbol

This macro adds a method to the list of qualification methods for this discount. This method should be a predicate method.

Examples:

Add a qualifier

class FooDiscount < Discount
  add_qualifier :foo_qualifies?

  def foo_qualifies?(order)
    order.email == '[email protected]'
  end
end

Parameters:

  • method_name (Symbol)

Returns:

  • (Symbol)


116
117
118
119
# File 'app/models/workarea/pricing/discount.rb', line 116

def self.add_qualifier(method_name)
  @qualifier_methods ||= []
  @qualifier_methods << method_name
end

.auto_deactivateObject

Deactivate the current scope of discounts, and mark as automatically deactivated.



134
135
136
137
# File 'app/models/workarea/pricing/discount.rb', line 134

def self.auto_deactivate
  where(auto_deactivate: true, active: true)
    .each_by(50) { |d| d.auto_deactivate! }
end

.qualifier_methodsArray<Symbol>

Methods that should be chcked when determining whether this discount qualifies. The result is the return values of each method ANDed together. Used in Discount#qualifies?.

Returns:

  • (Array<Symbol>)


127
128
129
# File 'app/models/workarea/pricing/discount.rb', line 127

def self.qualifier_methods
  @qualifier_methods || []
end

Instance Method Details

#<=>(other) ⇒ Integer

Compare two discounts, used for ensuring discount application order is predictable. Sorts by class using config value discount_application_order then by the discounts id for discounts of the same class.

Parameters:

Returns:

  • (Integer)

    -1, 0, 1.



146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'app/models/workarea/pricing/discount.rb', line 146

def <=>(other)
  self_order = Workarea.config.discount_application_order.index(self.class.name)
  other_order = Workarea.config.discount_application_order.index(other.class.name)

  if self_order.blank? || other_order.blank?
    missing = self_order.blank? ? self.class : other.class

    raise(
      MissingConfigurationError,
      <<-eos.strip_heredoc

      Problem:
        Missing discount application order config for #{missing}
      Summary:
        To determine discount application order, custom discounts must be
        configured so the system knows how to compare discounts in sorting.
      Resolution:
        Check Workarea.config.discount_application_order and ensure that
        all discount class names are added to that list in the desired
        position so they can be properly ordered for application.
      eos
    )
  end

  class_order = self_order <=> other_order

  case class_order
  when -1 then -1
  when 0 then self.id <=> other.id
  when 1 then 1
  end
end

#apply(order) ⇒ Workarea::Order

Create the price adjustments that reduce the order price. This method must be implemented when subclassing (creating a new type of) discount. It’s effects will vary discount to discount.

Parameters:

Returns:

Raises:

  • (NotImplementedError)


279
280
281
282
283
284
# File 'app/models/workarea/pricing/discount.rb', line 279

def apply(order)
  raise(
    NotImplementedError,
    "#{self.class} must implement the #apply method"
  )
end

#auto_deactivate!Object

Automatically deactivates a discount



181
182
183
# File 'app/models/workarea/pricing/discount.rb', line 181

def auto_deactivate!
  update_attributes!(active: false, auto_deactivated_at: Time.current)
end

#auto_deactivated?Boolean

Whether this discount was autodeactivated. Used to show the UI in the admin around what you can do if a discount has been autodeactivated.

Returns:

  • (Boolean)


229
230
231
# File 'app/models/workarea/pricing/discount.rb', line 229

def auto_deactivated?
  !active? && auto_deactivate && auto_deactivated_at.present?
end

#auto_deactivates_atTime

Returns the date of auto deactivation if no redemptions occur

Returns:

  • (Time)


245
246
247
248
# File 'app/models/workarea/pricing/discount.rb', line 245

def auto_deactivates_at
  start = last_redemption.try(:created_at) || updated_at
  start + Workarea.config.discount_staleness_ttl
end

#can_be_used_by?(email) ⇒ Boolean

Whether this email has already redeemed this discount, and is not eligible due to single use.

Parameters:

Returns:

  • (Boolean)


219
220
221
222
# File 'app/models/workarea/pricing/discount.rb', line 219

def can_be_used_by?(email)
  return true if !single_use? || email.blank?
  !redemptions.where(email: email.downcase).exists?
end

#compatible_discountsSet<Workarea::Pricing::Discount>

The set of compatible discounts for creating an undirected graph of discount compatibility for finding ApplicationGroups most efficiently.

Returns:



323
324
325
# File 'app/models/workarea/pricing/discount.rb', line 323

def compatible_discounts
  @compatible_discounts ||= Set.new
end

#compatible_with?(discount) ⇒ Boolean

Whether this discount is compatible with the one passed

Parameters:

Returns:

  • (Boolean)


255
256
257
258
# File 'app/models/workarea/pricing/discount.rb', line 255

def compatible_with?(discount)
  compatible_discount_ids.map(&:to_s).include?(discount.id.to_s) ||
    discount.compatible_discount_ids.map(&:to_s).include?(id.to_s)
end

#excludes_category_id?(category_id) ⇒ Boolean

Whether or not the provided category_id is excluded from the discount

Parameters:

Returns:

  • (Boolean)


311
312
313
# File 'app/models/workarea/pricing/discount.rb', line 311

def excludes_category_id?(category_id)
  excluded_category_ids.include?(category_id.to_s)
end

#excludes_product_id?(product_id) ⇒ Boolean

Whether or not the provided product_id is excluded from the discount

Parameters:

Returns:

  • (Boolean)


302
303
304
# File 'app/models/workarea/pricing/discount.rb', line 302

def excludes_product_id?(product_id)
  excluded_product_ids.include?(product_id.to_s)
end

#has_been_redeemed?(email) ⇒ Boolean

Whether this discount has been redeemed by a certain email address. Used for single use qualification.

Parameters:

Returns:

  • (Boolean)


209
210
211
# File 'app/models/workarea/pricing/discount.rb', line 209

def has_been_redeemed?(email)
  redemptions.where(email: email.downcase).exists?
end

#last_redemptionWorkarea::Pricing::Discount::Redemption?

Find the last redemption for the discount.



237
238
239
# File 'app/models/workarea/pricing/discount.rb', line 237

def last_redemption
  redemptions.desc(:created_at).first
end

#log_redemption(email) ⇒ Workarea::Pricing::Discount::Redemption

Record the redemption of this discount by an email address for an amount. Used for reporting and use tracking.

Parameters:

Returns:



267
268
269
# File 'app/models/workarea/pricing/discount.rb', line 267

def log_redemption(email)
  redemptions.create!(email: email)
end

#qualifies?(order) ⇒ Boolean

Whether this discount qualifies for this order. It does so by checking each qualifier_methods set on the class. If all qualifier_methods return true, the discount qualifies.

Returns false if there are no qualified methods setup.

Returns:

  • (Boolean)


193
194
195
196
197
198
199
200
201
# File 'app/models/workarea/pricing/discount.rb', line 193

def qualifies?(order)
  return false unless self.class.qualifier_methods.present?
  return false unless order.items.present?
  return false unless can_be_used_by?(order.email)

  self.class.qualifier_methods.reduce(true) do |result, method|
    result && send(method, order)
  end
end

#remove_from(order) ⇒ Workarea::Order

Removes the adjustments created by this discount. Used after this discount has been disqualified at the end of discount application.

Parameters:

Returns:



293
294
295
# File 'app/models/workarea/pricing/discount.rb', line 293

def remove_from(order)
  remove_from_items(order.items + order.shippings)
end