Skip to content

Commit

Permalink
Introduce Spree::Discounts
Browse files Browse the repository at this point in the history
This module handles discounts from the order updater.

The system will check all line items and shipments for any applicable
discounts and select the best for each line item / shipment in an
understandable, performant way.
  • Loading branch information
mamhoff committed Mar 18, 2022
1 parent 7976ab9 commit 1119896
Show file tree
Hide file tree
Showing 7 changed files with 132 additions and 17 deletions.
16 changes: 16 additions & 0 deletions core/app/models/spree/discounts/chooser.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# frozen_string_literal: true

module Spree
module Discounts
class Chooser
def initialize(_discountable)
# This signature is here to provide context in case
# this needs to be customized
end

def call(discounts)
[discounts.max_by(&:amount)].compact
end
end
end
end
28 changes: 28 additions & 0 deletions core/app/models/spree/discounts/line_item_updater.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# frozen_string_literal: true

module Spree
module Discounts
class LineItemUpdater
attr_reader :promotions

def initialize(promotions:)
@promotions = promotions
end

def call(line_item)
discounts = promotions.select do |promotion|
promotion.line_item_eligible?(line_item)
end.flat_map do |promotion|
promotion.actions.select do |action|
action.discountable_class == Spree::LineItem
end.map do |action|
action.discount(line_item)
end
end

chosen_discounts = Spree::Config.discount_chooser_class.new(line_item).call(discounts)
line_item.discounts = chosen_discounts
end
end
end
end
52 changes: 51 additions & 1 deletion core/app/models/spree/discounts/order_updater.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,57 @@ def initialize(order)
end

def call
order
update_line_items
update_shipments
end

private

def update_line_items
line_item_promotions = promotions.select do |promotion|
promotion.actions.any? { |promotion_action| promotion_action.discountable_class == Spree::LineItem }
end
line_item_promo_updater = LineItemUpdater.new(promotions: line_item_promotions)
order.line_items.each { |line_item| line_item_promo_updater.call(line_item) }
end

def update_shipments
shipment_promotions = promotions.select do |promotion|
promotion.actions.any? { |promotion_action| promotion_action.discountable_class == Spree::Shipment }
end
shipment_promo_updater = ShipmentUpdater.new(promotions: shipment_promotions)
order.shipments.each { |shipment| shipment_promo_updater.call(shipment) }
end

def promotions
@_promotions ||= begin
preloader = ActiveRecord::Associations::Preloader.new
(connected_order_promotions | sale_promotions).select do |promotion|
promotion.activatable?(order)
end.map do |promotion|
preloader.preload(promotion.rules.select { |r| r.type == "Spree::Promotion::Rules::Product" }, :products)
preloader.preload(promotion.rules.select { |r| r.type == "Spree::Promotion::Rules::Store" }, :stores)
preloader.preload(promotion.rules.select { |r| r.type == "Spree::Promotion::Rules::Taxon" }, :taxons)
preloader.preload(promotion.rules.select { |r| r.type == "Spree::Promotion::Rules::User" }, :users)
preloader.preload(promotion.actions.select { |a| a.respond_to?(:calculator) }, :calculator)
promotion
end
end.select { |promotion| promotion.order_discountable?(order) }
end

def connected_order_promotions
order.promotions.includes(promotion_includes).select(&:active?)
end

def sale_promotions
Spree::Promotion.where(apply_automatically: true).active.includes(promotion_includes)
end

def promotion_includes
[
:promotion_rules,
:promotion_actions,
]
end
end
end
Expand Down
29 changes: 29 additions & 0 deletions core/app/models/spree/discounts/shipment_updater.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# frozen_string_literal: true

module Spree
module Discounts
class ShipmentUpdater
attr_reader :promotions

def initialize(promotions:)
@promotions = promotions
end

def call(shipment)
shipment.discounts = promotions.select do |promotion|
promotion.shipment_eligible?(shipment)
end.flat_map do |promotion|
promotion.actions.select do |action|
action.discountable_class == Spree::Shipment
end.map do |action|
action.discount(shipment)
end
end

chosen_discounts = Spree::Config.discount_chooser_class.new(shipment).call(discounts)
shipment.promo_total = chosen_discounts.sum(&:amount)
shipment.discounts = chosen_discounts
end
end
end
end
4 changes: 2 additions & 2 deletions core/app/models/spree/order_updater.rb
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ def recalculate_adjustments
update_item_promotions
update_order_promotions
else
Spree::Config.promotion_handler_class.new(order).call
Spree::Config.discount_updater_class.new(order).call
end
update_taxes
update_cancellations
Expand Down Expand Up @@ -250,7 +250,7 @@ def update_item_totals
item.adjustment_total = item.adjustments.
select(&:eligible?).
reject(&:included?).
sum(&:amount)
sum(&:amount) + item.discounts.sum(&:amount)

if item.changed?
item.update_columns(
Expand Down
7 changes: 5 additions & 2 deletions core/lib/spree/app_configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -321,8 +321,11 @@ def default_pricing_options
# promotion_chooser_class allows extensions to provide their own PromotionChooser
class_name_attribute :promotion_chooser_class, default: 'Spree::PromotionChooser'

# promotion_handler_class allows extensions to provide their own Promotion Handler
class_name_attribute :promotion_handler_class, default: 'Spree::PromotionHandler::Order'
# promotion_handler_class allows extensions to provide their own Discount Order Updater
class_name_attribute :discount_updater_class, default: 'Spree::Discounts::OrderUpdater'

# discount_chooser_class allows extensions to provide their own discount chooser
class_name_attribute :discount_chooser_class, default: 'Spree::Discounts::Chooser'

class_name_attribute :allocator_class, default: 'Spree::Stock::Allocator::OnHandFirst'

Expand Down
13 changes: 1 addition & 12 deletions core/spec/models/spree/order_contents_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@
include_context "discount changes order total"
end

context "with new discount-based promotion system", pending: "Waiting for implementation" do
context "with new discount-based promotion system" do
around do |example|
with_unfrozen_spree_preference_store do
Spree::Config.promotion_system = :discounts
Expand All @@ -122,17 +122,6 @@
end
end

context "one active order promotion" do
let!(:action) { Spree::Promotion::Actions::CreateAdjustment.create(promotion: promotion, calculator: calculator) }

it "creates valid discount on order" do
subject.add(variant, 1)
expect(subject.order.discounts.to_a.sum(&:amount)).not_to eq 0
end

include_context "discount changes order total"
end

context "one active line item promotion" do
let!(:action) { Spree::Promotion::Actions::CreateItemAdjustments.create(promotion: promotion, calculator: calculator) }

Expand Down

0 comments on commit 1119896

Please sign in to comment.