Skip to content
This repository was archived by the owner on Feb 19, 2025. It is now read-only.

Stacking Promotions #7

Merged
merged 11 commits into from
Oct 2, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module SolidusFriendlyPromotions
module Actions
class AdjustLineItem < PromotionAction
def can_discount?(object)
object.is_a? Spree::LineItem
object.is_a? Discountable::LineItem
end

def available_calculators
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module SolidusFriendlyPromotions
module Actions
class AdjustShipment < PromotionAction
def can_discount?(object)
object.is_a?(Spree::Shipment) || object.is_a?(Spree::ShippingRate)
object.is_a?(Discountable::Shipment) || object.is_a?(Discountable::ShippingRate)
end

def available_calculators
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class Percent < Spree::Calculator
def compute(object)
preferred_currency = object.order.currency
currency_exponent = ::Money::Currency.find(preferred_currency).exponent
(object.amount * preferred_percent / 100).round(currency_exponent)
(object.discountable_amount * preferred_percent / 100).round(currency_exponent)
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class TieredFlatRate < Spree::Calculator

def compute_item(object)
_base, amount = preferred_tiers.sort.reverse.detect do |value, _|
object.amount >= value
object.discountable_amount >= value
end

if preferred_currency.casecmp(object.currency).zero?
Expand Down
23 changes: 23 additions & 0 deletions app/models/solidus_friendly_promotions/discountable/line_item.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# frozen_string_literal: true

module SolidusFriendlyPromotions
module Discountable
class LineItem < SimpleDelegator
attr_reader :discounts, :order

def initialize(line_item, order:)
super(line_item)
@order = order
@discounts = []
end

def line_item
__getobj__
end

def discountable_amount
amount + discounts.sum(&:amount)
end
end
end
end
19 changes: 19 additions & 0 deletions app/models/solidus_friendly_promotions/discountable/order.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# frozen_string_literal: true

module SolidusFriendlyPromotions
module Discountable
class Order < SimpleDelegator
attr_reader :line_items, :shipments

def initialize(order)
super
@line_items = order.line_items.map { |line_item| LineItem.new(line_item, order: self) }
@shipments = order.shipments.map { |shipment| Shipment.new(shipment, order: self) }
end

def order
__getobj__
end
end
end
end
24 changes: 24 additions & 0 deletions app/models/solidus_friendly_promotions/discountable/shipment.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# frozen_string_literal: true

module SolidusFriendlyPromotions
module Discountable
class Shipment < SimpleDelegator
attr_reader :discounts, :shipping_rates, :order

def initialize(shipment, order:)
super(shipment)
@order = order
@discounts = []
@shipping_rates = shipment.shipping_rates.map { |shipping_rate| ShippingRate.new(shipping_rate, shipment: self) }
end

def shipment
__getobj__
end

def discountable_amount
amount + discounts.sum(&:amount)
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# frozen_string_literal: true

module SolidusFriendlyPromotions
module Discountable
class ShippingRate < SimpleDelegator
attr_reader :discounts, :shipment

def initialize(shipping_rate, shipment:)
super(shipping_rate)
@shipment = shipment
@discounts = []
end

def shipping_rate
__getobj__
end

def discountable_amount
amount + discounts.sum(&:amount)
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def line_item_ids
end

def elligible_amounts
line_items.map(&:amount)
line_items.map(&:discountable_amount)
end

def subtotal
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,56 @@

module SolidusFriendlyPromotions
class FriendlyPromotionDiscounter
attr_reader :order, :promotions, :item_discounter
attr_reader :order, :promotions

def initialize(order)
@order = order
@order = Discountable::Order.new(order)
@promotions = PromotionEligibility.new(promotable: order, possible_promotions: possible_promotions).call
@item_discounter = ItemDiscounter.new(promotions: promotions)
end

def call
return nil if order.shipped?

OrderDiscounts.new(
order_id: order.id,
line_item_discounts: adjust_line_items,
shipment_discounts: adjust_shipments,
shipping_rate_discounts: adjust_shipping_rates
)
SolidusFriendlyPromotions::Promotion.ordered_lanes.each do |lane, _index|
lane_promotions = promotions.select { |promotion| promotion.lane == lane }
item_discounter = ItemDiscounter.new(promotions: lane_promotions)
line_item_discounts = adjust_line_items(item_discounter)
shipment_discounts = adjust_shipments(item_discounter)
shipping_rate_discounts = adjust_shipping_rates(item_discounter)
(line_item_discounts + shipment_discounts + shipping_rate_discounts).each do |item, chosen_discounts|
item.discounts.concat(chosen_discounts)
end
end

order
end

private

def adjust_line_items
def adjust_line_items(item_discounter)
order.line_items.select do |line_item|
line_item.variant.product.promotionable?
end.flat_map { |line_item| item_discounter.call(line_item) }
end.map do |line_item|
discounts = item_discounter.call(line_item)
chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(line_item).call(discounts)
[line_item, chosen_item_discounts]
end
end

def adjust_shipments
order.shipments.flat_map { |shipment| item_discounter.call(shipment) }
def adjust_shipments(item_discounter)
order.shipments.map do |shipment|
discounts = item_discounter.call(shipment)
chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(shipment).call(discounts)
[shipment, chosen_item_discounts]
end
end

def adjust_shipping_rates
order.shipments.flat_map(&:shipping_rates).flat_map { |rate| item_discounter.call(rate) }
def adjust_shipping_rates(item_discounter)
order.shipments.flat_map(&:shipping_rates).map do |rate|
discounts = item_discounter.call(rate)
chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(rate).call(discounts)
[rate, chosen_item_discounts]
end
end

def possible_promotions
Expand Down
32 changes: 11 additions & 21 deletions app/models/solidus_friendly_promotions/order_discounter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,39 +7,29 @@ def initialize(order)
end

def call
all_order_discounts = SolidusFriendlyPromotions.config.discounters.filter_map do |discounter|
discounter.new(order).call
end
discountable_order = FriendlyPromotionDiscounter.new(order).call

@order.line_items.each do |item|
all_line_item_discounts = all_order_discounts.flat_map(&:line_item_discounts)
item_discounts = all_line_item_discounts.select { |element| element.item == item }
chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(item).call(item_discounts)
update_adjustments(item, chosen_item_discounts)
discountable_order.line_items.each do |discountable_line_item|
update_adjustments(discountable_line_item.line_item, discountable_line_item.discounts)
end

@order.shipments.each do |item|
all_shipment_discounts = all_order_discounts.flat_map(&:shipment_discounts)
item_discounts = all_shipment_discounts.select { |element| element.item == item }
chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(item).call(item_discounts)
update_adjustments(item, chosen_item_discounts)
discountable_order.shipments.each do |discountable_shipment|
update_adjustments(discountable_shipment.shipment, discountable_shipment.discounts)
end

@order.shipments.flat_map(&:shipping_rates).each do |item|
all_item_discounts = all_order_discounts.flat_map(&:shipping_rate_discounts)
item_discounts = all_item_discounts.select { |element| element.item == item }
chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(item).call(item_discounts)
item.discounts = chosen_item_discounts.map do |discount|
discountable_order.shipments.flat_map(&:shipping_rates).each do |discountable_shipping_rate|
spree_shipping_rate = discountable_shipping_rate.shipping_rate
spree_shipping_rate.discounts = discountable_shipping_rate.discounts.map do |discount|
SolidusFriendlyPromotions::ShippingRateDiscount.new(
shipping_rate: item,
shipping_rate: spree_shipping_rate,
amount: discount.amount,
label: discount.label
)
end
end

@order.promo_total = (order.line_items + order.shipments).sum(&:promo_total)
@order
order.promo_total = (order.line_items + order.shipments).sum(&:promo_total)
order
end

private
Expand Down
18 changes: 0 additions & 18 deletions app/models/solidus_friendly_promotions/order_discounts.rb

This file was deleted.

2 changes: 1 addition & 1 deletion app/models/solidus_friendly_promotions/promotion_action.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def discount(adjustable)
# Ensure a negative amount which does not exceed the object's amount
def compute_amount(adjustable)
promotion_amount = calculator.compute(adjustable) || BigDecimal("0")
[adjustable.amount, promotion_amount.abs].min * -1
[adjustable.discountable_amount, promotion_amount.abs].min * -1
end

def adjustment_label(adjustable)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class FirstOrder < PromotionRule
attr_reader :user, :email

def applicable?(promotable)
promotable.is_a?(Spree::Order)
promotable.is_a?(Discountable::Order)
end

def eligible?(order, options = {})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class FirstRepeatPurchaseSince < PromotionRule

# This promotion is applicable to orders only.
def applicable?(promotable)
promotable.is_a?(Spree::Order)
promotable.is_a?(Discountable::Order)
end

# This is never eligible if the order does not have a user, and that user does not have any previous completed orders.
Expand Down
2 changes: 1 addition & 1 deletion app/models/solidus_friendly_promotions/rules/item_total.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def self.operator_options
end

def applicable?(promotable)
promotable.is_a?(Spree::Order)
promotable.is_a?(Discountable::Order)
end

def eligible?(order, _options = {})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class LineItemOptionValue < PromotionRule
preference :eligible_values, :hash

def applicable?(promotable)
promotable.is_a?(Spree::LineItem)
promotable.is_a?(Discountable::LineItem)
end

def eligible?(line_item, _options = {})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class LineItemProduct < PromotionRule
preference :match_policy, :string, default: MATCH_POLICIES.first

def applicable?(promotable)
promotable.is_a?(Spree::LineItem)
promotable.is_a?(Discountable::LineItem)
end

def eligible?(line_item, _options = {})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class LineItemTaxon < PromotionRule

preference :match_policy, :string, default: MATCH_POLICIES.first
def applicable?(promotable)
promotable.is_a?(Spree::LineItem)
promotable.is_a?(Discountable::LineItem)
end

def eligible?(line_item, _options = {})
Expand Down
2 changes: 1 addition & 1 deletion app/models/solidus_friendly_promotions/rules/nth_order.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class NthOrder < PromotionRule

# This promotion is applicable to orders only.
def applicable?(promotable)
promotable.is_a?(Spree::Order)
promotable.is_a?(Discountable::Order)
end

# This is never eligible if the order does not have a user, and that user does not have any previous completed orders.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module SolidusFriendlyPromotions
module Rules
class OneUsePerUser < PromotionRule
def applicable?(promotable)
promotable.is_a?(Spree::Order)
promotable.is_a?(Discountable::Order)
end

def eligible?(order, _options = {})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class OptionValue < PromotionRule
preference :eligible_values, :hash

def applicable?(promotable)
promotable.is_a?(Spree::Order)
promotable.is_a?(Discountable::Order)
end

def eligible?(order, _options = {})
Expand Down
2 changes: 1 addition & 1 deletion app/models/solidus_friendly_promotions/rules/product.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def eligible_products
end

def applicable?(promotable)
promotable.is_a?(Spree::Order)
promotable.is_a?(Discountable::Order)
end

def eligible?(order, _options = {})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class ShippingMethod < PromotionRule
preference :shipping_method_ids, type: :array, default: []

def applicable?(promotable)
promotable.is_a?(Spree::Shipment) || promotable.is_a?(Spree::ShippingRate)
promotable.is_a?(Discountable::Shipment) || promotable.is_a?(Discountable::ShippingRate)
end

def eligible?(promotable)
Expand Down
2 changes: 1 addition & 1 deletion app/models/solidus_friendly_promotions/rules/store.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def preload_relations
end

def applicable?(promotable)
promotable.is_a?(Spree::Order)
promotable.is_a?(Discountable::Order)
end

def eligible?(order, _options = {})
Expand Down
Loading