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

Refactor to FriendlyPromotion::FriendlyPromotionAdjuster #76

Merged
merged 5 commits into from
Nov 6, 2023
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
Expand Up @@ -30,7 +30,7 @@ def reset_current_discounts
end

def apply_shipping_promotions
if Spree::Config.promotion_adjuster_class <= SolidusFriendlyPromotions::OrderDiscounter
if Spree::Config.promotion_adjuster_class <= SolidusFriendlyPromotions::FriendlyPromotionAdjuster
recalculate
else
super
Expand Down
19 changes: 0 additions & 19 deletions app/models/solidus_friendly_promotions/discount_chooser.rb

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# frozen_string_literal: true

module SolidusFriendlyPromotions
class FriendlyPromotionAdjuster
attr_reader :order, :promotions, :dry_run

def initialize(order, dry_run_promotion: nil)
@order = order
@dry_run = !!dry_run_promotion
@promotions = LoadPromotions.new(order: order, dry_run_promotion: dry_run_promotion).call
end

def call
order.reset_current_discounts

return order if order.shipped?
discounted_order = DiscountOrder.new(order, promotions, dry_run: dry_run).call

PersistDiscountedOrder.new(discounted_order).call unless dry_run

order.reset_current_discounts
order
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# frozen_string_literal: true

module SolidusFriendlyPromotions
class FriendlyPromotionAdjuster
class ChooseDiscounts
attr_reader :discounts

def initialize(discounts)
@discounts = discounts
end

def call
Array.wrap(
discounts.min_by do |discount|
[discount.amount, -discount.source&.id.to_i]
end
)
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# frozen_string_literal: true

module SolidusFriendlyPromotions
class FriendlyPromotionAdjuster
class DiscountOrder
attr_reader :order, :promotions, :dry_run

def initialize(order, promotions, dry_run: false)
@order = order
@promotions = promotions
@dry_run = dry_run
end

def call
return order if order.shipped?

SolidusFriendlyPromotions::Promotion.ordered_lanes.each do |lane, _index|
lane_promotions = eligible_promotions_for_promotable(promotions.select { |promotion| promotion.lane == lane }, order)
line_item_discounts = adjust_line_items(lane_promotions)
shipment_discounts = adjust_shipments(lane_promotions)
shipping_rate_discounts = adjust_shipping_rates(lane_promotions)
(line_item_discounts + shipment_discounts + shipping_rate_discounts).each do |item, chosen_discounts|
item.current_discounts.concat(chosen_discounts)
end
end

order
end

private

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

def adjust_shipments(promotions)
order.shipments.map do |shipment|
discounts = generate_discounts(promotions, shipment)
chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(discounts).call
[shipment, chosen_item_discounts]
end
end

def adjust_shipping_rates(promotions)
order.shipments.flat_map(&:shipping_rates).select(&:cost).map do |rate|
discounts = generate_discounts(promotions, rate)
chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(discounts).call
[rate, chosen_item_discounts]
end
end

def eligible_promotions_for_promotable(possible_promotions, promotable)
possible_promotions.select do |candidate|
candidate.eligible_by_applicable_rules?(promotable, dry_run: dry_run)
end
end

def generate_discounts(possible_promotions, item)
eligible_promotions = eligible_promotions_for_promotable(possible_promotions, item)
eligible_promotions.flat_map do |promotion|
promotion.actions.select do |action|
action.can_discount?(item)
end.map do |action|
action.discount(item)
end.compact
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# frozen_string_literal: true

module SolidusFriendlyPromotions
class FriendlyPromotionAdjuster
class LoadPromotions
def initialize(order:, dry_run_promotion: nil)
@order = order
@dry_run_promotion = dry_run_promotion
end

def call
promos = connected_order_promotions | sale_promotions
promos << dry_run_promotion if dry_run_promotion
promos.flat_map(&:actions).group_by(&:preload_relations).each do |preload_relations, actions|
preload(records: actions, associations: preload_relations)
end
promos.flat_map(&:rules).group_by(&:preload_relations).each do |preload_relations, rules|
preload(records: rules, associations: preload_relations)
end
promos.reject { |promotion| promotion.usage_limit_exceeded?(excluded_orders: [order]) }
end

private

attr_reader :order, :dry_run_promotion

def preload(records:, associations:)
ActiveRecord::Associations::Preloader.new(records: records, associations: associations).call
end

def connected_order_promotions
eligible_connected_promotion_ids = order.friendly_order_promotions.select do |order_promotion|
order_promotion.promotion_code.nil? || !order_promotion.promotion_code.usage_limit_exceeded?(excluded_orders: [order])
end.map(&:promotion_id)
order.friendly_promotions.active(reference_time).where(id: eligible_connected_promotion_ids).includes(promotion_includes)
end

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

def reference_time
order.completed_at || Time.current
end

def promotion_includes
[
:rules,
:actions
]
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# frozen_string_literal: true

module SolidusFriendlyPromotions
class FriendlyPromotionAdjuster
class PersistDiscountedOrder
def initialize(order)
@order = order
end

def call
order.line_items.each do |line_item|
update_adjustments(line_item, line_item.current_discounts)
end

order.shipments.each do |shipment|
update_adjustments(shipment, shipment.current_discounts)
end

order.shipments.flat_map(&:shipping_rates).each do |shipping_rate|
shipping_rate.discounts = shipping_rate.current_discounts.map do |discount|
SolidusFriendlyPromotions::ShippingRateDiscount.create!(
shipping_rate: shipping_rate,
amount: discount.amount,
label: discount.label,
promotion_action: discount.source
)
end
end
order.reset_current_discounts
order.promo_total = (order.line_items + order.shipments).sum(&:promo_total)
order
end

private

attr_reader :order

# Walk through the discounts for an item and update adjustments for it. Once
# all of the discounts have been added as adjustments, remove any old tax
# adjustments that weren't touched.
#
# @private
# @param [#adjustments] item a {Spree::LineItem} or {Spree::Shipment}
# @param [Array<SolidusFriendlyPromotions::ItemDiscount>] item_discounts a list of calculated discounts for an item
# @return [void]
def update_adjustments(item, item_discounts)
promotion_adjustments = item.adjustments.select(&:promotion?)

active_adjustments = item_discounts.map do |item_discount|
update_adjustment(item, item_discount)
end
item.update(promo_total: active_adjustments.sum(&:amount))
# Remove any tax adjustments tied to promotion actions which no longer match.
unmatched_adjustments = promotion_adjustments - active_adjustments

item.adjustments.destroy(unmatched_adjustments)
end

# Update or create a new tax adjustment on an item.
#
# @private
# @param [#adjustments] item a {Spree::LineItem} or {Spree::Shipment}
# @param [SolidusFriendlyPromotions::ItemDiscount] tax_item calculated discounts for an item
# @return [Spree::Adjustment] the created or updated tax adjustment
def update_adjustment(item, discount_item)
adjustment = item.adjustments.detect do |item_adjustment|
item_adjustment.source == discount_item.source
end

adjustment ||= item.adjustments.new(
source: discount_item.source,
order_id: item.is_a?(Spree::Order) ? item.id : item.order_id,
label: discount_item.label,
eligible: true
)
adjustment.update!(amount: discount_item.amount)
adjustment
end
end
end
end

This file was deleted.

Loading