Skip to content
Open
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
6 changes: 6 additions & 0 deletions promotions/app/models/solidus_promotions/benefit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,12 @@ def available_calculators
# @param dry_run [Boolean] whether to collect detailed eligibility information
# @return [Boolean] true when all applicable conditions are eligible
def eligible_by_applicable_conditions?(promotable, dry_run: false)
if dry_run
Spree.deprecator.warn <<~MSG
Passing `dry_run` to `#eligible_by_applicable_conditions` is deprecated. If you want to check promotion
eligibility, use the `SolidusPromotions::PromotionEligibilityChecker` service object instead.
MSG
end
conditions.map do |condition|
next unless condition.applicable?(promotable)
eligible = condition.eligible?(promotable)
Expand Down
6 changes: 6 additions & 0 deletions promotions/app/models/solidus_promotions/load_promotions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
module SolidusPromotions
class LoadPromotions
def initialize(order:, dry_run_promotion: nil)
if dry_run_promotion
Spree.deprecator.warn <<~MSG
Passing `dry_run_promotion` to `SolidusPromotions::LoadPromotions` is deprecated.
Use `Spree::Config.promotions.eligibility_checker_class.new(order: order, promotion: promotion).call` instead.
MSG
end
@order = order
@dry_run_promotion = dry_run_promotion
end
Expand Down
6 changes: 6 additions & 0 deletions promotions/app/models/solidus_promotions/order_adjuster.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ class OrderAdjuster
attr_reader :order, :promotions, :dry_run

def initialize(order, dry_run_promotion: nil)
if dry_run_promotion
Spree.deprecator.warn <<~MSG
Passing `dry_run_promotion` to `SolidusPromotions::OrderAdjuster` is deprecated.
Use `Spree::Config.promotions.eligibility_checker_class.new(order: order, promotion: promotion).call` instead.
MSG
end
@order = order
@dry_run = !!dry_run_promotion
@promotions = SolidusPromotions::LoadPromotions.new(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ class DiscountOrder
attr_reader :order, :promotions, :dry_run

def initialize(order, promotions, dry_run: false)
if dry_run
Spree.deprecator.warn <<~MSG
Passing `dry_run` to `SolidusPromotions::OrderAdjuster::DiscountOrder` is deprecated.
Use `Spree::Config.promotions.eligibility_checker_class.new(order: order, promotion: promotion).call` instead.
MSG
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯 🐝

end
@order = order
@promotions = promotions
@dry_run = dry_run
Expand Down Expand Up @@ -46,27 +52,20 @@ def perform_order_benefits(lane_benefits, lane)
def adjust_line_items(benefits)
order.discountable_line_items.filter_map do |line_item|
next unless line_item.variant.product.promotionable?

discounts = generate_discounts(benefits, line_item)
chosen_discounts = SolidusPromotions.config.discount_chooser_class.new(discounts).call
(line_item.current_lane_discounts - chosen_discounts).each(&:mark_for_destruction)
generate_discounts(benefits, line_item)
end
end

def adjust_shipments(benefits)
order.shipments.map do |shipment|
discounts = generate_discounts(benefits, shipment)
chosen_discounts = SolidusPromotions.config.discount_chooser_class.new(discounts).call
(shipment.current_lane_discounts - chosen_discounts).each(&:mark_for_destruction)
generate_discounts(benefits, shipment)
end
end

def adjust_shipping_rates(benefits)
order.shipments.flat_map(&:shipping_rates).filter_map do |rate|
next unless rate.cost
discounts = generate_discounts(benefits, rate)
chosen_discounts = SolidusPromotions.config.discount_chooser_class.new(discounts).call
(rate.current_lane_discounts - chosen_discounts).each(&:mark_for_destruction)
generate_discounts(benefits, rate)
end
end

Expand All @@ -78,9 +77,11 @@ def eligible_benefits_for_promotable(possible_benefits, promotable)

def generate_discounts(possible_benefits, item)
eligible_benefits = eligible_benefits_for_promotable(possible_benefits, item)
eligible_benefits.filter_map do |benefit|
discounts = eligible_benefits.filter_map do |benefit|
benefit.can_discount?(item) && benefit.discount(item)
end
chosen_discounts = SolidusPromotions.config.discount_chooser_class.new(discounts).call
(item.current_lane_discounts - chosen_discounts).each(&:mark_for_destruction)
end
end
end
Expand Down
1 change: 1 addition & 0 deletions promotions/app/models/solidus_promotions/promotion.rb
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ def products
def eligibility_results
@eligibility_results ||= SolidusPromotions::EligibilityResults.new(self)
end
deprecate eligibility_results: "Please use the `SolidusPromotions::PromotionEligibilityChecker` service object instead", deprecator: Spree.deprecator

private

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

module SolidusPromotions
class PromotionEligibilityChecker
attr_reader :order, :promotion, :results

def initialize(order:, promotion:)
@order = order
@promotion = promotion
@results = SolidusPromotions::EligibilityResults.new(promotion)
end

def call
SolidusPromotions::PromotionLane.set(current_lane: promotion.lane) do
promotion.benefits.any? do |benefit|
# We're running this first and storing the result so the following
# block does not short-circuit on ineligible items, and we get all errors.
order_eligible = applicable_conditions_eligible?(order, benefit)
(
order.line_items.any? do |line_item|
check_item(line_item, benefit)
end || order.shipments.any? do |shipment|
check_item(shipment, benefit)
end
Comment on lines +20 to +24
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(order.line_items + order.shipments).any? do |item|
  check_item(item, benefit)
end && order_eligible

) && order_eligible
end
end
end

def check_item(item, benefit)
benefit.can_discount?(item) &&
applicable_conditions_eligible?(item, benefit)
end

def applicable_conditions_eligible?(item, benefit)
benefit.conditions.map do |condition|
next unless condition.applicable?(item)
eligible = !!condition.eligible?(item)

if condition.eligibility_errors.details[:base].first
code = condition.eligibility_errors.details[:base].first[:error_code]
message = condition.eligibility_errors.full_messages.first
end
results.add(
item: item,
condition: condition,
success: eligible,
code: eligible ? nil : (code || :coupon_code_unknown_error),
message: eligible ? nil : (message || I18n.t(:coupon_code_unknown_error, scope: [:solidus_promotions, :eligibility_errors]))
)

eligible
end.compact.all?
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -77,24 +77,24 @@ def handle_present_promotion
return promotion_usage_limit_exceeded if promotion.usage_limit_exceeded? || promotion_code.usage_limit_exceeded?
return promotion_applied if promotion_exists_on_order?(order, promotion)

# Try applying this promotion, with no effects
Spree::Config.promotions.order_adjuster_class.new(order, dry_run_promotion: promotion).call
# Check promotion applicability
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

*eligibility

checker = Spree::Config.promotions.eligibility_checker_class.new(order: order, promotion: promotion)

if promotion.eligibility_results.success?
if checker.call
order.solidus_order_promotions.create!(
promotion: promotion,
promotion_code: promotion_code
)
order.recalculate
set_success_code :coupon_code_applied
else
set_promotion_eligibility_error(promotion)
set_promotion_eligibility_error(checker.results)
end
end

def set_promotion_eligibility_error(promotion)
eligibility_error = promotion.eligibility_results.detect { |result| !result.success }
set_error_code(eligibility_error.code, error: eligibility_error.message, errors: promotion.eligibility_results.error_messages)
def set_promotion_eligibility_error(results)
eligibility_error = results.detect { |result| !result.success }
set_error_code(eligibility_error.code, error: eligibility_error.message, errors: results.error_messages)
end

def promotion_usage_limit_exceeded
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,20 @@
module SolidusPromotions
module PromotionHandler
class Page
attr_reader :order, :path
attr_reader :order, :path, :checker

def initialize(order, path)
@order = order
@path = path.delete_prefix("/")
@checker = Spree::Config.promotions.eligibility_checker_class.new(order: order, promotion: promotion)
end

delegate :results, to: :checker

def activate
if promotion
Spree::Config.promotions.order_adjuster_class.new(order, dry_run_promotion: promotion).call
if promotion.eligibility_results.success?
order.solidus_promotions << promotion
order.recalculate
end
if promotion && checker.call
order.solidus_promotions << promotion
order.recalculate
end
end

Expand Down
5 changes: 5 additions & 0 deletions promotions/lib/solidus_promotions/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ class Configuration < Spree::Preferences::Configuration

class_name_attribute :order_adjuster_class, default: "SolidusPromotions::OrderAdjuster"

# Service object that checks whether a promotion is eligible for an order.
# @!attribute [rw] eligibility_checker_class
# @return [String] Defaults to "SolidusPromotions::PromotionEligibilityChecker".
class_name_attribute :eligibility_checker_class, default: "SolidusPromotions::PromotionEligibilityChecker"

class_name_attribute :coupon_code_handler_class, default: "SolidusPromotions::PromotionHandler::Coupon"

# The class used to normalize coupon codes before saving or lookup.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@
end
end

describe ".eligibility_checker_class" do
it "is the standard eligibility checker" do
expect(subject.eligibility_checker_class).to eq(SolidusPromotions::PromotionEligibilityChecker)
end
end

describe ".advertiser_class" do
it "is the standard advertiser" do
expect(subject.advertiser_class).to eq(SolidusPromotions::PromotionAdvertiser)
Expand Down
10 changes: 5 additions & 5 deletions promotions/spec/models/solidus_promotions/benefit_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ def line_item_eligible?(_)

it { is_expected.to be true }

context "with dry_run true" do
context "with dry_run true", :silence_deprecations do
let(:dry_run) { true }

it { is_expected.to be true }
Expand All @@ -373,7 +373,7 @@ def line_item_eligible?(_)
let(:promotable) { order.line_items.first }
it { is_expected.to be true }

context "with dry_run true" do
context "with dry_run true", :silence_deprecations do
let(:dry_run) { true }

it { is_expected.to be true }
Expand All @@ -391,7 +391,7 @@ def line_item_eligible?(_)

it { is_expected.to be false }

context "with dry_run true" do
context "with dry_run true", :silence_deprecations do
let(:dry_run) { true }

it { is_expected.to be false }
Expand Down Expand Up @@ -425,14 +425,14 @@ def order_eligible?(_)

it { is_expected.to be false }

it "only asks the first condition and does not collect eligibility errors" do
it "only asks the first condition and does not collect eligibility errors", :silence_deprecations do
expect(taxon_condition).to receive(:order_eligible?).and_call_original
expect(product_condition).not_to receive(:order_eligible?)
subject
expect(promotion.eligibility_results.error_messages).to be_empty
end

context "if dry_run is true" do
context "if dry_run is true", :silence_deprecations do
let(:dry_run) { true }
it { is_expected.to be false }

Expand Down
Loading
Loading