Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Specify inheritance for Spree::Promotion #2572

Merged
merged 1 commit into from
Feb 15, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module Spree
class Promotion
class Promotion < Spree::Base
module Actions
class CreateAdjustment < PromotionAction
include Spree::CalculatedAdjustments
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module Spree
class Promotion
class Promotion < Spree::Base
module Actions
class CreateItemAdjustments < PromotionAction
include Spree::CalculatedAdjustments
Expand Down
268 changes: 136 additions & 132 deletions core/app/models/spree/promotion/actions/create_quantity_adjustments.rb
Original file line number Diff line number Diff line change
@@ -1,135 +1,139 @@
module Spree::Promotion::Actions
class CreateQuantityAdjustments < CreateItemAdjustments
preference :group_size, :integer, default: 1

has_many :line_item_actions, foreign_key: :action_id, dependent: :destroy
has_many :line_items, through: :line_item_actions

##
# Computes the amount for the adjustment based on the line item and any
# other applicable items in the order. The rules for this specific
# adjustment are as follows:
#
# = Setup
#
# We have a quantity group promotion on t-shirts. If a user orders 3
# t-shirts, they get $5 off of each. The shirts come in one size and three
# colours: red, blue, and white.
#
# == Scenario 1
#
# User has 2 red shirts, 1 white shirt, and 1 blue shirt in their
# order. We want to compute the adjustment amount for the white shirt.
#
# *Result:* -$5
#
# *Reasoning:* There are a total of 4 items that are eligible for the
# promotion. Since that is greater than 3, we can discount the items. The
# white shirt has a quantity of 1, therefore it will get discounted by
# +adjustment_amount * 1+ or $5.
#
# === Scenario 1-1
#
# What about the blue shirt? How much does it get discounted?
#
# *Result:* $0
#
# *Reasoning:* We have a total quantity of 4. However, we only apply the
# adjustment to groups of 3. Assuming the white and red shirts have already
# had their adjustment calculated, that means 3 units have been discounted.
# Leaving us with a lonely blue shirt that isn't part of a group of 3.
# Therefore, it does not receive the discount.
#
# == Scenario 2
#
# User has 4 red shirts in their order. What is the amount?
#
# *Result:* -$15
#
# *Reasoning:* The total quantity of eligible items is 4, so we the
# adjustment will be non-zero. However, we only apply it to groups of 3,
# therefore there is one extra item that is not eligible for the
# adjustment. +adjustment_amount * 3+ or $15.
#
def compute_amount(line_item)
adjustment_amount = calculator.compute(PartialLineItem.new(line_item))
if !adjustment_amount.is_a?(BigDecimal)
Spree::Deprecation.warn "#{calculator.class.name}#compute returned #{adjustment_amount.inspect}, it should return a BigDecimal"
end
adjustment_amount ||= BigDecimal(0)
adjustment_amount = adjustment_amount.abs

order = line_item.order
line_items = actionable_line_items(order)

actioned_line_items = order.line_item_adjustments.reload.
select { |a| a.source == self && a.amount < 0 }.
map(&:adjustable)
other_line_items = actioned_line_items - [line_item]

applicable_quantity = total_applicable_quantity(line_items)
used_quantity = total_used_quantity(other_line_items)
usable_quantity = [
applicable_quantity - used_quantity,
line_item.quantity
].min

persist_quantity(usable_quantity, line_item)

amount = adjustment_amount * usable_quantity
[line_item.amount, amount].min * -1
end

private

def actionable_line_items(order)
order.line_items.select do |item|
promotion.line_item_actionable? order, item
end
end

def total_applicable_quantity(line_items)
total_quantity = line_items.sum(&:quantity)
extra_quantity = total_quantity % preferred_group_size

total_quantity - extra_quantity
end

def total_used_quantity(line_items)
line_item_actions.where(
line_item_id: line_items.map(&:id)
).sum(:quantity)
end

def persist_quantity(quantity, line_item)
line_item_action = line_item_actions.where(
line_item_id: line_item.id
).first_or_initialize
line_item_action.quantity = quantity
line_item_action.save!
end

##
# Used specifically for PercentOnLineItem calculator. That calculator uses
# `line_item.amount`, however we might not necessarily want to discount the
# entire amount. This class allows us to determine the discount per
# quantity and then calculate the adjustment amount the way we normally do
# for flat rate adjustments.
class PartialLineItem
def initialize(line_item)
@line_item = line_item
end

def amount
@line_item.price
end

def order
@line_item.order
end

def currency
@line_item.currency
module Spree
class Promotion < Spree::Base
module Actions
class CreateQuantityAdjustments < CreateItemAdjustments
preference :group_size, :integer, default: 1

has_many :line_item_actions, foreign_key: :action_id, dependent: :destroy
has_many :line_items, through: :line_item_actions

##
# Computes the amount for the adjustment based on the line item and any
# other applicable items in the order. The rules for this specific
# adjustment are as follows:
#
# = Setup
#
# We have a quantity group promotion on t-shirts. If a user orders 3
# t-shirts, they get $5 off of each. The shirts come in one size and three
# colours: red, blue, and white.
#
# == Scenario 1
#
# User has 2 red shirts, 1 white shirt, and 1 blue shirt in their
# order. We want to compute the adjustment amount for the white shirt.
#
# *Result:* -$5
#
# *Reasoning:* There are a total of 4 items that are eligible for the
# promotion. Since that is greater than 3, we can discount the items. The
# white shirt has a quantity of 1, therefore it will get discounted by
# +adjustment_amount * 1+ or $5.
#
# === Scenario 1-1
#
# What about the blue shirt? How much does it get discounted?
#
# *Result:* $0
#
# *Reasoning:* We have a total quantity of 4. However, we only apply the
# adjustment to groups of 3. Assuming the white and red shirts have already
# had their adjustment calculated, that means 3 units have been discounted.
# Leaving us with a lonely blue shirt that isn't part of a group of 3.
# Therefore, it does not receive the discount.
#
# == Scenario 2
#
# User has 4 red shirts in their order. What is the amount?
#
# *Result:* -$15
#
# *Reasoning:* The total quantity of eligible items is 4, so we the
# adjustment will be non-zero. However, we only apply it to groups of 3,
# therefore there is one extra item that is not eligible for the
# adjustment. +adjustment_amount * 3+ or $15.
#
def compute_amount(line_item)
adjustment_amount = calculator.compute(PartialLineItem.new(line_item))
if !adjustment_amount.is_a?(BigDecimal)
Spree::Deprecation.warn "#{calculator.class.name}#compute returned #{adjustment_amount.inspect}, it should return a BigDecimal"
end
adjustment_amount ||= BigDecimal(0)
adjustment_amount = adjustment_amount.abs

order = line_item.order
line_items = actionable_line_items(order)

actioned_line_items = order.line_item_adjustments.reload.
select { |a| a.source == self && a.amount < 0 }.
map(&:adjustable)
other_line_items = actioned_line_items - [line_item]

applicable_quantity = total_applicable_quantity(line_items)
used_quantity = total_used_quantity(other_line_items)
usable_quantity = [
applicable_quantity - used_quantity,
line_item.quantity
].min

persist_quantity(usable_quantity, line_item)

amount = adjustment_amount * usable_quantity
[line_item.amount, amount].min * -1
end

private

def actionable_line_items(order)
order.line_items.select do |item|
promotion.line_item_actionable? order, item
end
end

def total_applicable_quantity(line_items)
total_quantity = line_items.sum(&:quantity)
extra_quantity = total_quantity % preferred_group_size

total_quantity - extra_quantity
end

def total_used_quantity(line_items)
line_item_actions.where(
line_item_id: line_items.map(&:id)
).sum(:quantity)
end

def persist_quantity(quantity, line_item)
line_item_action = line_item_actions.where(
line_item_id: line_item.id
).first_or_initialize
line_item_action.quantity = quantity
line_item_action.save!
end

##
# Used specifically for PercentOnLineItem calculator. That calculator uses
# `line_item.amount`, however we might not necessarily want to discount the
# entire amount. This class allows us to determine the discount per
# quantity and then calculate the adjustment amount the way we normally do
# for flat rate adjustments.
class PartialLineItem
def initialize(line_item)
@line_item = line_item
end

def amount
@line_item.price
end

def order
@line_item.order
end

def currency
@line_item.currency
end
end
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion core/app/models/spree/promotion/actions/free_shipping.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module Spree
class Promotion
class Promotion < Spree::Base
module Actions
class FreeShipping < Spree::PromotionAction
def perform(payload = {})
Expand Down
2 changes: 1 addition & 1 deletion core/app/models/spree/promotion/rules/first_order.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module Spree
class Promotion
class Promotion < Spree::Base
module Rules
class FirstOrder < PromotionRule
attr_reader :user, :email
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module Spree
class Promotion
class Promotion < Spree::Base
module Rules
class FirstRepeatPurchaseSince < PromotionRule
preference :days_ago, :integer, default: 365
Expand Down
2 changes: 1 addition & 1 deletion core/app/models/spree/promotion/rules/item_total.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module Spree
class Promotion
class Promotion < Spree::Base
module Rules
# A rule to apply to an order greater than (or greater than or equal to)
# a specific amount
Expand Down
2 changes: 1 addition & 1 deletion core/app/models/spree/promotion/rules/one_use_per_user.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module Spree
class Promotion
class Promotion < Spree::Base
module Rules
class OneUsePerUser < PromotionRule
def applicable?(promotable)
Expand Down
2 changes: 1 addition & 1 deletion core/app/models/spree/promotion/rules/option_value.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module Spree
class Promotion
class Promotion < Spree::Base
module Rules
class OptionValue < PromotionRule
MATCH_POLICIES = %w(any)
Expand Down
2 changes: 1 addition & 1 deletion core/app/models/spree/promotion/rules/product.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module Spree
class Promotion
class Promotion < Spree::Base
module Rules
# A rule to limit a promotion based on products in the order. Can
# require all or any of the products to be present. Valid products
Expand Down
2 changes: 1 addition & 1 deletion core/app/models/spree/promotion/rules/taxon.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module Spree
class Promotion
class Promotion < Spree::Base
module Rules
class Taxon < PromotionRule
has_many :promotion_rule_taxons, class_name: 'Spree::PromotionRuleTaxon', foreign_key: :promotion_rule_id,
Expand Down
2 changes: 1 addition & 1 deletion core/app/models/spree/promotion/rules/user.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module Spree
class Promotion
class Promotion < Spree::Base
module Rules
class User < PromotionRule
has_many :promotion_rule_users, class_name: 'Spree::PromotionRuleUser',
Expand Down
2 changes: 1 addition & 1 deletion core/app/models/spree/promotion/rules/user_logged_in.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module Spree
class Promotion
class Promotion < Spree::Base
module Rules
class UserLoggedIn < PromotionRule
def applicable?(promotable)
Expand Down
2 changes: 1 addition & 1 deletion core/app/models/spree/promotion/rules/user_role.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module Spree
class Promotion
class Promotion < Spree::Base
module Rules
class UserRole < PromotionRule
preference :role_ids, :array, default: []
Expand Down