Skip to content

Commit

Permalink
Merge pull request #7 from friendlycart/adjustments-by-lane
Browse files Browse the repository at this point in the history
Stacking Promotions
  • Loading branch information
mamhoff authored Oct 2, 2023
2 parents 4fe4a8e + aa36625 commit 4492cb6
Show file tree
Hide file tree
Showing 46 changed files with 374 additions and 106 deletions.
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
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
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
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
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

This file was deleted.

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
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
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
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
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

0 comments on commit 4492cb6

Please sign in to comment.