From bc5b9cf484c51f1c251c47bbbae6be654574a6ee Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 29 Sep 2023 12:44:30 +0200 Subject: [PATCH 01/11] Add Facade objects for orders For calculating and retrieving intermediate amounts on line items, orders, shipments, and shipping rates, we need some kind of value object that holds a reference to the order and behaves more or less like the real things. I've considered using Ruby Refinements instead of Delegators for this, but they are clunky in that they need to be referenced in every file using them. --- .../discountable/line_item.rb | 23 ++++++++++ .../discountable/order.rb | 19 ++++++++ .../discountable/shipment.rb | 24 ++++++++++ .../discountable/shipping_rate.rb | 23 ++++++++++ .../discountable/line_item_spec.rb | 34 ++++++++++++++ .../discountable/order_spec.rb | 39 ++++++++++++++++ .../discountable/shipment_spec.rb | 45 +++++++++++++++++++ .../discountable/shipping_rate_spec.rb | 35 +++++++++++++++ 8 files changed, 242 insertions(+) create mode 100644 promotions/app/models/solidus_friendly_promotions/discountable/line_item.rb create mode 100644 promotions/app/models/solidus_friendly_promotions/discountable/order.rb create mode 100644 promotions/app/models/solidus_friendly_promotions/discountable/shipment.rb create mode 100644 promotions/app/models/solidus_friendly_promotions/discountable/shipping_rate.rb create mode 100644 promotions/spec/models/solidus_friendly_promotions/discountable/line_item_spec.rb create mode 100644 promotions/spec/models/solidus_friendly_promotions/discountable/order_spec.rb create mode 100644 promotions/spec/models/solidus_friendly_promotions/discountable/shipment_spec.rb create mode 100644 promotions/spec/models/solidus_friendly_promotions/discountable/shipping_rate_spec.rb diff --git a/promotions/app/models/solidus_friendly_promotions/discountable/line_item.rb b/promotions/app/models/solidus_friendly_promotions/discountable/line_item.rb new file mode 100644 index 00000000..d196c553 --- /dev/null +++ b/promotions/app/models/solidus_friendly_promotions/discountable/line_item.rb @@ -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 discounted_amount + amount + discounts.sum(&:amount) + end + end + end +end diff --git a/promotions/app/models/solidus_friendly_promotions/discountable/order.rb b/promotions/app/models/solidus_friendly_promotions/discountable/order.rb new file mode 100644 index 00000000..ef2bd8c1 --- /dev/null +++ b/promotions/app/models/solidus_friendly_promotions/discountable/order.rb @@ -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 diff --git a/promotions/app/models/solidus_friendly_promotions/discountable/shipment.rb b/promotions/app/models/solidus_friendly_promotions/discountable/shipment.rb new file mode 100644 index 00000000..e84a22a7 --- /dev/null +++ b/promotions/app/models/solidus_friendly_promotions/discountable/shipment.rb @@ -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 discounted_amount + amount + discounts.sum(&:amount) + end + end + end +end diff --git a/promotions/app/models/solidus_friendly_promotions/discountable/shipping_rate.rb b/promotions/app/models/solidus_friendly_promotions/discountable/shipping_rate.rb new file mode 100644 index 00000000..bc938e69 --- /dev/null +++ b/promotions/app/models/solidus_friendly_promotions/discountable/shipping_rate.rb @@ -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 discounted_amount + amount + discounts.sum(&:amount) + end + end + end +end diff --git a/promotions/spec/models/solidus_friendly_promotions/discountable/line_item_spec.rb b/promotions/spec/models/solidus_friendly_promotions/discountable/line_item_spec.rb new file mode 100644 index 00000000..2778f522 --- /dev/null +++ b/promotions/spec/models/solidus_friendly_promotions/discountable/line_item_spec.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe SolidusFriendlyPromotions::Discountable::LineItem do + let(:discountable_order) { double(SolidusFriendlyPromotions::Discountable::Order) } + let(:spree_line_item) { build(:line_item, price: 10, quantity: 2) } + + subject(:discountable_line_item) { described_class.new(spree_line_item, order: discountable_order) } + + describe "#order" do + subject { discountable_line_item.order } + + it { is_expected.to eq(discountable_order) } + end + + describe "#discounted_amount" do + subject(:discounted_amount) { discountable_line_item.discounted_amount } + + context "with no discounts" do + it { is_expected.to eq(20) } + end + + context "with discounts" do + let(:discount) { SolidusFriendlyPromotions::ItemDiscount.new(amount: -4) } + + before do + discountable_line_item.discounts << discount + end + + it { is_expected.to eq(16) } + end + end +end diff --git a/promotions/spec/models/solidus_friendly_promotions/discountable/order_spec.rb b/promotions/spec/models/solidus_friendly_promotions/discountable/order_spec.rb new file mode 100644 index 00000000..637b8a05 --- /dev/null +++ b/promotions/spec/models/solidus_friendly_promotions/discountable/order_spec.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe SolidusFriendlyPromotions::Discountable::Order do + subject(:discountable_order) { described_class.new(spree_order) } + + let(:spree_order) { Spree::Order.new } + + describe "#line_items" do + let(:spree_order) { create(:order_with_line_items) } + subject(:line_items) { discountable_order.line_items } + + specify "are converted into Discountable Line Items" do + line_items.each do |line_item| + expect(line_item).to be_a(SolidusFriendlyPromotions::Discountable::LineItem) + end + end + end + + describe "#shipments" do + let(:spree_order) { create(:order_ready_to_ship) } + subject(:shipments) { discountable_order.shipments } + + specify "are converted into Discountable Shipments" do + shipments.each do |shipment| + expect(shipment).to be_a(SolidusFriendlyPromotions::Discountable::Shipment) + end + end + end + + describe "delegation" do + let(:spree_order) { Spree::Order.new(email: "yoda@example.com") } + + it "forwards order attributes" do + expect(subject.email).to eq("yoda@example.com") + end + end +end diff --git a/promotions/spec/models/solidus_friendly_promotions/discountable/shipment_spec.rb b/promotions/spec/models/solidus_friendly_promotions/discountable/shipment_spec.rb new file mode 100644 index 00000000..3c963f7d --- /dev/null +++ b/promotions/spec/models/solidus_friendly_promotions/discountable/shipment_spec.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe SolidusFriendlyPromotions::Discountable::Shipment do + let(:discountable_order) { double(SolidusFriendlyPromotions::Discountable::Order) } + + let(:spree_shipment) { build(:shipment, amount: 20) } + + subject(:discountable_shipment) { described_class.new(spree_shipment, order: discountable_order) } + + describe "#order" do + subject { discountable_shipment.order } + + it { is_expected.to eq(discountable_order) } + end + + describe "#discounted_amount" do + subject(:discounted_amount) { discountable_shipment.discounted_amount } + + context "with no discounts" do + it { is_expected.to eq(20) } + end + + context "with discounts" do + let(:discount) { SolidusFriendlyPromotions::ItemDiscount.new(amount: -4) } + + before do + discountable_shipment.discounts << discount + end + + it { is_expected.to eq(16) } + end + end + + describe "#shipping_rates" do + subject(:shipping_rates) { discountable_shipment.shipping_rates } + + specify "are converted into Discountable Shipments" do + shipping_rates.each do |shipping_rate| + expect(shipping_rate).to be_a(SolidusFriendlyPromotions::Discountable::ShippingRate) + end + end + end +end diff --git a/promotions/spec/models/solidus_friendly_promotions/discountable/shipping_rate_spec.rb b/promotions/spec/models/solidus_friendly_promotions/discountable/shipping_rate_spec.rb new file mode 100644 index 00000000..25cbaf6a --- /dev/null +++ b/promotions/spec/models/solidus_friendly_promotions/discountable/shipping_rate_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe SolidusFriendlyPromotions::Discountable::ShippingRate do + let(:discountable_shipment) { double(SolidusFriendlyPromotions::Discountable::Shipment) } + + let(:spree_shipping_rate) { build(:shipping_rate, amount: 20) } + + subject(:discountable_shipping_rate) { described_class.new(spree_shipping_rate, shipment: discountable_shipment) } + + describe "#shipment" do + subject { discountable_shipping_rate.shipment } + + it { is_expected.to eq(discountable_shipment) } + end + + describe "#discounted_amount" do + subject(:discounted_amount) { discountable_shipping_rate.discounted_amount } + + context "with no discounts" do + it { is_expected.to eq(20) } + end + + context "with discounts" do + let(:discount) { SolidusFriendlyPromotions::ItemDiscount.new(amount: -4) } + + before do + discountable_shipping_rate.discounts << discount + end + + it { is_expected.to eq(16) } + end + end +end From 3cb0c4a96a20dd40ecc5af2c8fbc00a23530e50b Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 29 Sep 2023 12:53:57 +0200 Subject: [PATCH 02/11] Add integration spec for stacking promotions --- .../spec/models/promotion/integration_spec.rb | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/promotions/spec/models/promotion/integration_spec.rb b/promotions/spec/models/promotion/integration_spec.rb index 8ab354e7..5d9d1ab1 100644 --- a/promotions/spec/models/promotion/integration_spec.rb +++ b/promotions/spec/models/promotion/integration_spec.rb @@ -51,6 +51,49 @@ end end + context "with two promotions that should stack", :pending do + let(:shirt) { create(:product, name: "Shirt", price: 30) } + let(:pants) { create(:product, name: "Pants", price: 40) } + + let!(:distributed_amount_promo) do + create(:friendly_promotion, + :with_adjustable_action, + preferred_amount: 10.0, + apply_automatically: true, + lane: :post, + calculator_class: SolidusFriendlyPromotions::Calculators::DistributedAmount) + end + let(:shirts_rule) { SolidusFriendlyPromotions::Rules::LineItemProduct.new(products: [shirt]) } + let(:shirts_calculator) { SolidusFriendlyPromotions::Calculators::Percent.new(preferred_percent: 20) } + let(:shirts_action) { SolidusFriendlyPromotions::Actions::AdjustLineItem.new(calculator: shirts_calculator) } + let!(:shirts_promotion) do + create( + :friendly_promotion, + rules: [shirts_rule], + actions: [shirts_action], + name: "20% off shirts", + apply_automatically: true + ) + end + let(:order) { create(:order) } + + before do + order.contents.add(shirt.master, 1) + order.contents.add(pants.master, 1) + end + + it "does all the right things" do + expect(order.adjustments).to be_empty + # shirt: 30 USD - 20% = 24 USD + # Remaining total: 64 USD + # 10 USD distributed off: 54 USD + expect(order.total).to eq(54.00) + expect(order.item_total).to eq(70.00) + expect(order.item_total_before_tax).to eq(54) + expect(order.line_items.flat_map(&:adjustments).length).to eq(3) + end + end + context "with a shipment-level rule" do let!(:address) { create(:address) } let(:shipping_zone) { create(:global_zone) } From 97e7cb320a5909c6153ed996fa20a2436f038158 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 29 Sep 2023 13:25:05 +0200 Subject: [PATCH 03/11] Refactor system to use Discountable/{Order, Shipment, Lineitem} We need a way to store intermediate amounts on the order we adjust. This accomplishes this in part by storing the discounts on custom delegators to the Solidus objects. --- .../actions/adjust_line_item.rb | 2 +- .../actions/adjust_shipment.rb | 2 +- .../friendly_promotion_discounter.rb | 12 +++++------- .../order_discounter.rb | 18 +++++------------- .../promotion_action.rb | 2 +- .../rules/first_order.rb | 2 +- .../rules/first_repeat_purchase_since.rb | 2 +- .../rules/item_total.rb | 2 +- .../rules/line_item_option_value.rb | 2 +- .../rules/line_item_product.rb | 2 +- .../rules/line_item_taxon.rb | 2 +- .../rules/nth_order.rb | 2 +- .../rules/one_use_per_user.rb | 2 +- .../rules/option_value.rb | 2 +- .../rules/product.rb | 2 +- .../rules/shipping_method.rb | 2 +- .../solidus_friendly_promotions/rules/store.rb | 2 +- .../solidus_friendly_promotions/rules/taxon.rb | 2 +- .../solidus_friendly_promotions/rules/user.rb | 2 +- .../rules/user_logged_in.rb | 2 +- .../rules/user_role.rb | 2 +- .../actions/adjust_shipment_spec.rb | 6 +++--- .../promotion_action_spec.rb | 13 +++++++------ .../rules/first_repeat_purchase_since_spec.rb | 4 ++-- .../rules/nth_order_spec.rb | 2 +- .../rules/option_value_spec.rb | 4 ++-- 26 files changed, 44 insertions(+), 53 deletions(-) diff --git a/promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb b/promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb index 394d0b66..0e2bc9ce 100644 --- a/promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb +++ b/promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb @@ -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 diff --git a/promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb b/promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb index 82d73533..d52354ab 100644 --- a/promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb +++ b/promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb @@ -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 diff --git a/promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb b/promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb index 1401bd81..db0310b3 100644 --- a/promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb +++ b/promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb @@ -5,7 +5,7 @@ class FriendlyPromotionDiscounter attr_reader :order, :promotions, :item_discounter 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 @@ -13,12 +13,10 @@ def initialize(order) 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 - ) + adjust_line_items + adjust_shipments + adjust_shipping_rates + order end private diff --git a/promotions/app/models/solidus_friendly_promotions/order_discounter.rb b/promotions/app/models/solidus_friendly_promotions/order_discounter.rb index cc693364..7e60bf03 100644 --- a/promotions/app/models/solidus_friendly_promotions/order_discounter.rb +++ b/promotions/app/models/solidus_friendly_promotions/order_discounter.rb @@ -7,31 +7,23 @@ def initialize(order) end def call - all_order_discounts = SolidusFriendlyPromotions.config.discounters.filter_map do |discounter| - discounter.new(order).call - end + @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) + chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(item).call(item.discounts) update_adjustments(item, chosen_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) + chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(item).call(item.discounts) update_adjustments(item, chosen_item_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) + chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(item).call(item.discounts) item.discounts = chosen_item_discounts.map do |discount| SolidusFriendlyPromotions::ShippingRateDiscount.new( - shipping_rate: item, + shipping_rate: item.shipping_rate, amount: discount.amount, label: discount.label ) diff --git a/promotions/app/models/solidus_friendly_promotions/promotion_action.rb b/promotions/app/models/solidus_friendly_promotions/promotion_action.rb index 27eb152b..6f4998ce 100644 --- a/promotions/app/models/solidus_friendly_promotions/promotion_action.rb +++ b/promotions/app/models/solidus_friendly_promotions/promotion_action.rb @@ -27,7 +27,7 @@ def can_discount?(object) end def discount(adjustable) - ItemDiscount.new( + adjustable.discounts << ItemDiscount.new( item: adjustable, label: adjustment_label(adjustable), amount: compute_amount(adjustable), diff --git a/promotions/app/models/solidus_friendly_promotions/rules/first_order.rb b/promotions/app/models/solidus_friendly_promotions/rules/first_order.rb index 65ccd992..9d6f81f9 100644 --- a/promotions/app/models/solidus_friendly_promotions/rules/first_order.rb +++ b/promotions/app/models/solidus_friendly_promotions/rules/first_order.rb @@ -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 = {}) diff --git a/promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb b/promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb index dfca9645..2f64e454 100644 --- a/promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb +++ b/promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb @@ -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. diff --git a/promotions/app/models/solidus_friendly_promotions/rules/item_total.rb b/promotions/app/models/solidus_friendly_promotions/rules/item_total.rb index 12df8523..55fc42ec 100644 --- a/promotions/app/models/solidus_friendly_promotions/rules/item_total.rb +++ b/promotions/app/models/solidus_friendly_promotions/rules/item_total.rb @@ -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 = {}) diff --git a/promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb b/promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb index b8252820..a914702a 100644 --- a/promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb +++ b/promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb @@ -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 = {}) diff --git a/promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb b/promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb index b24e8e55..fb16c2fd 100644 --- a/promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb +++ b/promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb @@ -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 = {}) diff --git a/promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb b/promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb index e0fd27b6..204a12e2 100644 --- a/promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb +++ b/promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb @@ -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 = {}) diff --git a/promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb b/promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb index 04cb592b..041cbc53 100644 --- a/promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb +++ b/promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb @@ -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. diff --git a/promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb b/promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb index 5c9d13a8..8393c1ff 100644 --- a/promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb +++ b/promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb @@ -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 = {}) diff --git a/promotions/app/models/solidus_friendly_promotions/rules/option_value.rb b/promotions/app/models/solidus_friendly_promotions/rules/option_value.rb index f8b25aa0..9cd76948 100644 --- a/promotions/app/models/solidus_friendly_promotions/rules/option_value.rb +++ b/promotions/app/models/solidus_friendly_promotions/rules/option_value.rb @@ -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 = {}) diff --git a/promotions/app/models/solidus_friendly_promotions/rules/product.rb b/promotions/app/models/solidus_friendly_promotions/rules/product.rb index c7a5d837..02fadb69 100644 --- a/promotions/app/models/solidus_friendly_promotions/rules/product.rb +++ b/promotions/app/models/solidus_friendly_promotions/rules/product.rb @@ -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 = {}) diff --git a/promotions/app/models/solidus_friendly_promotions/rules/shipping_method.rb b/promotions/app/models/solidus_friendly_promotions/rules/shipping_method.rb index eb745f76..dce6d5af 100644 --- a/promotions/app/models/solidus_friendly_promotions/rules/shipping_method.rb +++ b/promotions/app/models/solidus_friendly_promotions/rules/shipping_method.rb @@ -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) diff --git a/promotions/app/models/solidus_friendly_promotions/rules/store.rb b/promotions/app/models/solidus_friendly_promotions/rules/store.rb index 3c087849..182e9f49 100644 --- a/promotions/app/models/solidus_friendly_promotions/rules/store.rb +++ b/promotions/app/models/solidus_friendly_promotions/rules/store.rb @@ -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 = {}) diff --git a/promotions/app/models/solidus_friendly_promotions/rules/taxon.rb b/promotions/app/models/solidus_friendly_promotions/rules/taxon.rb index 9130cadc..9f20c285 100644 --- a/promotions/app/models/solidus_friendly_promotions/rules/taxon.rb +++ b/promotions/app/models/solidus_friendly_promotions/rules/taxon.rb @@ -17,7 +17,7 @@ def preload_relations preference :match_policy, :string, default: MATCH_POLICIES.first def applicable?(promotable) - promotable.is_a?(Spree::Order) + promotable.is_a?(Discountable::Order) end def eligible?(order, _options = {}) diff --git a/promotions/app/models/solidus_friendly_promotions/rules/user.rb b/promotions/app/models/solidus_friendly_promotions/rules/user.rb index 1cb7e9fd..7c5a170f 100644 --- a/promotions/app/models/solidus_friendly_promotions/rules/user.rb +++ b/promotions/app/models/solidus_friendly_promotions/rules/user.rb @@ -14,7 +14,7 @@ def preload_relations end def applicable?(promotable) - promotable.is_a?(Spree::Order) + promotable.is_a?(Discountable::Order) end def eligible?(order, _options = {}) diff --git a/promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb b/promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb index 5be6e2ea..a866884b 100644 --- a/promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb +++ b/promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb @@ -4,7 +4,7 @@ module SolidusFriendlyPromotions module Rules class UserLoggedIn < PromotionRule def applicable?(promotable) - promotable.is_a?(Spree::Order) + promotable.is_a?(Discountable::Order) end def eligible?(order, _options = {}) diff --git a/promotions/app/models/solidus_friendly_promotions/rules/user_role.rb b/promotions/app/models/solidus_friendly_promotions/rules/user_role.rb index e8df12f7..08354c58 100644 --- a/promotions/app/models/solidus_friendly_promotions/rules/user_role.rb +++ b/promotions/app/models/solidus_friendly_promotions/rules/user_role.rb @@ -9,7 +9,7 @@ class UserRole < PromotionRule preference :match_policy, default: MATCH_POLICIES.first def applicable?(promotable) - promotable.is_a?(Spree::Order) + promotable.is_a?(Discountable::Order) end def eligible?(order, _options = {}) diff --git a/promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb b/promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb index 7fc0206e..887349b3 100644 --- a/promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb +++ b/promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb @@ -15,19 +15,19 @@ subject { action.can_discount?(promotable) } context "with a line item" do - let(:promotable) { Spree::LineItem.new } + let(:promotable) { SolidusFriendlyPromotions::Discountable::LineItem.new(Spree::Order.new, order: double) } it { is_expected.to be false } end context "with a shipment" do - let(:promotable) { Spree::Shipment.new } + let(:promotable) { SolidusFriendlyPromotions::Discountable::Shipment.new(Spree::Shipment.new, order: double) } it { is_expected.to be true } end context "with a shipping rate" do - let(:promotable) { Spree::ShippingRate.new } + let(:promotable) { SolidusFriendlyPromotions::Discountable::ShippingRate.new(Spree::ShippingRate.new, shipment: double) } it { is_expected.to be true } end diff --git a/promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb b/promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb index 20f580a0..93b1cf90 100644 --- a/promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb +++ b/promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb @@ -18,11 +18,12 @@ end describe "#discount" do - subject { action.discount(adjustable) } + subject { action.discount(discountable) } let(:variant) { create(:variant) } let(:order) { create(:order) } - let(:adjustable) { Spree::LineItem.new(order: order, variant: variant, price: 10) } + let(:discountable) { SolidusFriendlyPromotions::Discountable::LineItem.new(line_item, order: SolidusFriendlyPromotions::Discountable::Order.new(order)) } + let(:line_item) { Spree::LineItem.new(order: order, variant: variant, price: 10) } let(:promotion) { SolidusFriendlyPromotions::Promotion.new(name: "20 Perzent off") } let(:action) { described_class.new(promotion: promotion) } @@ -30,15 +31,15 @@ allow(action).to receive(:compute_amount).and_return(-1) end - it "returs an discount to the adjustable" do - expect(subject).to eq( + it "adds a discount to the discountable" do + expect { subject }.to change { discountable.discounts }.from([]).to([ SolidusFriendlyPromotions::ItemDiscount.new( - item: adjustable, + item: discountable, label: "Promotion (20 Perzent off)", source: action, amount: -1 ) - ) + ]) end end end diff --git a/promotions/spec/models/solidus_friendly_promotions/rules/first_repeat_purchase_since_spec.rb b/promotions/spec/models/solidus_friendly_promotions/rules/first_repeat_purchase_since_spec.rb index e23fd17e..8edd838e 100644 --- a/promotions/spec/models/solidus_friendly_promotions/rules/first_repeat_purchase_since_spec.rb +++ b/promotions/spec/models/solidus_friendly_promotions/rules/first_repeat_purchase_since_spec.rb @@ -7,13 +7,13 @@ subject { described_class.new.applicable?(promotable) } context "when the promotable is an order" do - let(:promotable) { Spree::Order.new } + let(:promotable) { SolidusFriendlyPromotions::Discountable::Order.new(Spree::Order.new) } it { is_expected.to be true } end context "when the promotable is not a order" do - let(:promotable) { Spree::LineItem.new } + let(:promotable) { SolidusFriendlyPromotions::Discountable::LineItem.new(Spree::LineItem.new, order: double) } it { is_expected.to be false } end diff --git a/promotions/spec/models/solidus_friendly_promotions/rules/nth_order_spec.rb b/promotions/spec/models/solidus_friendly_promotions/rules/nth_order_spec.rb index d2464d35..a85d4fec 100644 --- a/promotions/spec/models/solidus_friendly_promotions/rules/nth_order_spec.rb +++ b/promotions/spec/models/solidus_friendly_promotions/rules/nth_order_spec.rb @@ -7,7 +7,7 @@ subject { described_class.new.applicable?(promotable) } context "when the promotable is an order" do - let(:promotable) { Spree::Order.new } + let(:promotable) { SolidusFriendlyPromotions::Discountable::Order.new(Spree::Order.new) } it { is_expected.to be true } end diff --git a/promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb b/promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb index 51930937..4e29b4c1 100644 --- a/promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb +++ b/promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb @@ -18,13 +18,13 @@ subject { rule.applicable?(promotable) } context "when promotable is an order" do - let(:promotable) { Spree::Order.new } + let(:promotable) { SolidusFriendlyPromotions::Discountable::Order.new(Spree::Order.new) } it { is_expected.to be true } end context "when promotable is not an order" do - let(:promotable) { Spree::LineItem.new } + let(:promotable) { SolidusFriendlyPromotions::Discountable::LineItem.new(Spree::LineItem.new, order: double) } it { is_expected.to be false } end From dab21903a7eb5a6e67c9d7cd929b7f95c086a9fa Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 29 Sep 2023 13:45:18 +0200 Subject: [PATCH 04/11] Remove unused OrderDiscounts class The responsibilities for this class are now in the Discountable::* classes. --- .../order_discounts.rb | 18 ------------------ .../order_discounts_spec.rb | 8 -------- 2 files changed, 26 deletions(-) delete mode 100644 promotions/app/models/solidus_friendly_promotions/order_discounts.rb delete mode 100644 promotions/spec/models/solidus_friendly_promotions/order_discounts_spec.rb diff --git a/promotions/app/models/solidus_friendly_promotions/order_discounts.rb b/promotions/app/models/solidus_friendly_promotions/order_discounts.rb deleted file mode 100644 index df340659..00000000 --- a/promotions/app/models/solidus_friendly_promotions/order_discounts.rb +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true - -module SolidusFriendlyPromotions - # Simple object to pass back discount data from a promoter. - # - # Will be used by {SolidusFriendlyPromotions::OrderDiscounter} to create or update promotion - # adjustments on an order. - # - # @attr_reader [Integer] order_id the {Spree::Order} these discounts apply to - # @attr_reader [Array] line_item_discounts an array of - # discount data for order's line items - # @attr_reader [Array] shipment_discounts an array of - # discount data for the order's shipments - class OrderDiscounts - include ActiveModel::Model - attr_accessor :order_id, :line_item_discounts, :shipment_discounts, :shipping_rate_discounts - end -end diff --git a/promotions/spec/models/solidus_friendly_promotions/order_discounts_spec.rb b/promotions/spec/models/solidus_friendly_promotions/order_discounts_spec.rb deleted file mode 100644 index 9fe907d0..00000000 --- a/promotions/spec/models/solidus_friendly_promotions/order_discounts_spec.rb +++ /dev/null @@ -1,8 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe SolidusFriendlyPromotions::OrderDiscounts do - it { is_expected.to respond_to :order_id } - it { is_expected.to respond_to :line_item_discounts } - it { is_expected.to respond_to :shipment_discounts } - it { is_expected.to respond_to :shipping_rate_discounts } -end From fa2db19983b484e61056bdc20ce4000d23b8de48 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 29 Sep 2023 13:56:18 +0200 Subject: [PATCH 05/11] Apply promotions by lane --- .../friendly_promotion_discounter.rb | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb b/promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb index db0310b3..ac743555 100644 --- a/promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb +++ b/promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb @@ -2,36 +2,40 @@ module SolidusFriendlyPromotions class FriendlyPromotionDiscounter - attr_reader :order, :promotions, :item_discounter + attr_reader :order, :promotions def initialize(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? - adjust_line_items - adjust_shipments - 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) + adjust_line_items(item_discounter) + adjust_shipments(item_discounter) + adjust_shipping_rates(item_discounter) + 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 - def adjust_shipments + def adjust_shipments(item_discounter) order.shipments.flat_map { |shipment| item_discounter.call(shipment) } end - def adjust_shipping_rates + def adjust_shipping_rates(item_discounter) order.shipments.flat_map(&:shipping_rates).flat_map { |rate| item_discounter.call(rate) } end From eb5490fd52a13696bdd828848135771db8004e03 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 29 Sep 2023 14:00:58 +0200 Subject: [PATCH 06/11] Add item discounts in Promotion Discounter We want to run the promotion chooser on all discounts eligible for an item by lane. For that we need a collection of discounts for that item on that lane. This separates creating the discount and adding it, giving us space to do the selection. --- .../friendly_promotion_discounter.rb | 15 ++++++++++++--- .../promotion_action.rb | 2 +- .../promotion_action_spec.rb | 6 +++--- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb b/promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb index ac743555..98a1676d 100644 --- a/promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb +++ b/promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb @@ -28,15 +28,24 @@ def call 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.flat_map do |line_item| + discounts = item_discounter.call(line_item) + line_item.discounts.concat(discounts) + end end def adjust_shipments(item_discounter) - order.shipments.flat_map { |shipment| item_discounter.call(shipment) } + order.shipments.flat_map do |shipment| + discounts = item_discounter.call(shipment) + shipment.discounts.concat(discounts) + end end def adjust_shipping_rates(item_discounter) - order.shipments.flat_map(&:shipping_rates).flat_map { |rate| item_discounter.call(rate) } + order.shipments.flat_map(&:shipping_rates).flat_map do |rate| + discounts = item_discounter.call(rate) + rate.discounts.concat(discounts) + end end def possible_promotions diff --git a/promotions/app/models/solidus_friendly_promotions/promotion_action.rb b/promotions/app/models/solidus_friendly_promotions/promotion_action.rb index 6f4998ce..27eb152b 100644 --- a/promotions/app/models/solidus_friendly_promotions/promotion_action.rb +++ b/promotions/app/models/solidus_friendly_promotions/promotion_action.rb @@ -27,7 +27,7 @@ def can_discount?(object) end def discount(adjustable) - adjustable.discounts << ItemDiscount.new( + ItemDiscount.new( item: adjustable, label: adjustment_label(adjustable), amount: compute_amount(adjustable), diff --git a/promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb b/promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb index 93b1cf90..940f8060 100644 --- a/promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb +++ b/promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb @@ -31,15 +31,15 @@ allow(action).to receive(:compute_amount).and_return(-1) end - it "adds a discount to the discountable" do - expect { subject }.to change { discountable.discounts }.from([]).to([ + it "returns an discount to the discountable" do + expect(subject).to eq( SolidusFriendlyPromotions::ItemDiscount.new( item: discountable, label: "Promotion (20 Perzent off)", source: action, amount: -1 ) - ]) + ) end end end From 19bdc2c41a39b54096a398e4e5450a565a9465e0 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 29 Sep 2023 14:09:21 +0200 Subject: [PATCH 07/11] Move discount choosing to FriendlyPromotionDiscounter We need the discount choosing to happen per lane, and that can't happen in the OrderDiscounter. --- .../friendly_promotion_discounter.rb | 9 ++++++--- .../solidus_friendly_promotions/order_discounter.rb | 13 +++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb b/promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb index 98a1676d..07a06296 100644 --- a/promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb +++ b/promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb @@ -30,21 +30,24 @@ def adjust_line_items(item_discounter) line_item.variant.product.promotionable? end.flat_map do |line_item| discounts = item_discounter.call(line_item) - line_item.discounts.concat(discounts) + chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(line_item).call(discounts) + line_item.discounts.concat(chosen_item_discounts) end end def adjust_shipments(item_discounter) order.shipments.flat_map do |shipment| discounts = item_discounter.call(shipment) - shipment.discounts.concat(discounts) + chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(shipment).call(discounts) + shipment.discounts.concat(chosen_item_discounts) end end def adjust_shipping_rates(item_discounter) order.shipments.flat_map(&:shipping_rates).flat_map do |rate| discounts = item_discounter.call(rate) - rate.discounts.concat(discounts) + chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(rate).call(discounts) + rate.discounts.concat(chosen_item_discounts) end end diff --git a/promotions/app/models/solidus_friendly_promotions/order_discounter.rb b/promotions/app/models/solidus_friendly_promotions/order_discounter.rb index 7e60bf03..cdb87342 100644 --- a/promotions/app/models/solidus_friendly_promotions/order_discounter.rb +++ b/promotions/app/models/solidus_friendly_promotions/order_discounter.rb @@ -10,20 +10,17 @@ def call @order = FriendlyPromotionDiscounter.new(order).call @order.line_items.each do |item| - chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(item).call(item.discounts) - update_adjustments(item, chosen_item_discounts) + update_adjustments(item.line_item, item.discounts) end @order.shipments.each do |item| - chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(item).call(item.discounts) - update_adjustments(item, chosen_item_discounts) + update_adjustments(item.shipment, item.discounts) end - @order.shipments.flat_map(&:shipping_rates).each do |item| - chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(item).call(item.discounts) - item.discounts = chosen_item_discounts.map do |discount| + @order.shipments.flat_map(&:shipping_rates).each do |shipping_rate| + shipping_rate.shipping_rate.discounts = shipping_rate.discounts.map do |discount| SolidusFriendlyPromotions::ShippingRateDiscount.new( - shipping_rate: item.shipping_rate, + shipping_rate: shipping_rate.shipping_rate, amount: discount.amount, label: discount.label ) From b0805d20274b2d357ca5e5c64b4973ee174c7417 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 29 Sep 2023 14:14:51 +0200 Subject: [PATCH 08/11] Refactor order discounter This has no functional changes, but it renames some variables in this class so things don't seem as confusing. --- .../order_discounter.rb | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/promotions/app/models/solidus_friendly_promotions/order_discounter.rb b/promotions/app/models/solidus_friendly_promotions/order_discounter.rb index cdb87342..d0cfb204 100644 --- a/promotions/app/models/solidus_friendly_promotions/order_discounter.rb +++ b/promotions/app/models/solidus_friendly_promotions/order_discounter.rb @@ -7,28 +7,29 @@ def initialize(order) end def call - @order = FriendlyPromotionDiscounter.new(order).call + discountable_order = FriendlyPromotionDiscounter.new(order).call - @order.line_items.each do |item| - update_adjustments(item.line_item, 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| - update_adjustments(item.shipment, 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 |shipping_rate| - shipping_rate.shipping_rate.discounts = shipping_rate.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: shipping_rate.shipping_rate, + 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 From 56326f3079e09ae7a745b6bf56297a280dfe36bc Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 29 Sep 2023 14:26:17 +0200 Subject: [PATCH 09/11] Remove unused "discounters" configuration option This was used in the order discounter anticipating features we don't have. --- .../install/templates/initializer.rb | 4 ---- promotions/lib/solidus_friendly_promotions/configuration.rb | 1 - 2 files changed, 5 deletions(-) diff --git a/promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb b/promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb index 75343fc0..95a31d53 100644 --- a/promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb +++ b/promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb @@ -51,10 +51,6 @@ # How many promotions should be displayed on the index page in the admin. config.promotions_per_page = 25 - config.discounters = [ - "SolidusFriendlyPromotions::FriendlyPromotionDiscounter" - ] - config.shipment_discount_calculators = [ "SolidusFriendlyPromotions::Calculators::FlatRate", "SolidusFriendlyPromotions::Calculators::FlexiRate", diff --git a/promotions/lib/solidus_friendly_promotions/configuration.rb b/promotions/lib/solidus_friendly_promotions/configuration.rb index 26922dd1..7ec7bd16 100644 --- a/promotions/lib/solidus_friendly_promotions/configuration.rb +++ b/promotions/lib/solidus_friendly_promotions/configuration.rb @@ -14,7 +14,6 @@ class Configuration < Spree::Preferences::Configuration add_class_set :shipment_rules add_class_set :actions - add_class_set :discounters class_name_attribute :discount_chooser_class, default: "SolidusFriendlyPromotions::DiscountChooser" class_name_attribute :promotion_code_batch_mailer_class, From ef769376d421eab844f89efe61236fe6a6a56abf Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 29 Sep 2023 15:05:04 +0200 Subject: [PATCH 10/11] Add discounts to items after calculation Otherwise we get discounted amounts from discounts from the current lane (which we don't want). --- .../friendly_promotion_discounter.rb | 21 +++++++++++-------- .../spec/models/promotion/integration_spec.rb | 2 +- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb b/promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb index 07a06296..e3945e1e 100644 --- a/promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb +++ b/promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb @@ -15,9 +15,12 @@ def call SolidusFriendlyPromotions::Promotion.ordered_lanes.each do |lane, _index| lane_promotions = promotions.select { |promotion| promotion.lane == lane } item_discounter = ItemDiscounter.new(promotions: lane_promotions) - adjust_line_items(item_discounter) - adjust_shipments(item_discounter) - adjust_shipping_rates(item_discounter) + 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 @@ -28,26 +31,26 @@ def call def adjust_line_items(item_discounter) order.line_items.select do |line_item| line_item.variant.product.promotionable? - end.flat_map do |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.discounts.concat(chosen_item_discounts) + [line_item, chosen_item_discounts] end end def adjust_shipments(item_discounter) - order.shipments.flat_map do |shipment| + order.shipments.map do |shipment| discounts = item_discounter.call(shipment) chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(shipment).call(discounts) - shipment.discounts.concat(chosen_item_discounts) + [shipment, chosen_item_discounts] end end def adjust_shipping_rates(item_discounter) - order.shipments.flat_map(&:shipping_rates).flat_map do |rate| + 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.discounts.concat(chosen_item_discounts) + [rate, chosen_item_discounts] end end diff --git a/promotions/spec/models/promotion/integration_spec.rb b/promotions/spec/models/promotion/integration_spec.rb index 5d9d1ab1..b51dc38d 100644 --- a/promotions/spec/models/promotion/integration_spec.rb +++ b/promotions/spec/models/promotion/integration_spec.rb @@ -51,7 +51,7 @@ end end - context "with two promotions that should stack", :pending do + context "with two promotions that should stack" do let(:shirt) { create(:product, name: "Shirt", price: 30) } let(:pants) { create(:product, name: "Pants", price: 40) } From aa366252ec52ba37e52ac25cca1123a5517b571d Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 29 Sep 2023 14:48:47 +0200 Subject: [PATCH 11/11] Change calculators to use #discountable_amount This change allows use to actually use the "lanes" feature in our calculators. --- .../solidus_friendly_promotions/calculators/percent.rb | 2 +- .../calculators/tiered_flat_rate.rb | 2 +- .../solidus_friendly_promotions/discountable/line_item.rb | 2 +- .../solidus_friendly_promotions/discountable/shipment.rb | 2 +- .../discountable/shipping_rate.rb | 2 +- .../distributed_amounts_handler.rb | 2 +- .../models/solidus_friendly_promotions/promotion_action.rb | 2 +- .../calculators/distributed_amount_spec.rb | 3 ++- .../solidus_friendly_promotions/calculators/percent_spec.rb | 2 +- .../calculators/tiered_flat_rate_spec.rb | 5 +++-- .../discountable/line_item_spec.rb | 4 ++-- .../discountable/shipment_spec.rb | 4 ++-- .../discountable/shipping_rate_spec.rb | 4 ++-- .../distributed_amounts_handler_spec.rb | 6 +++++- 14 files changed, 24 insertions(+), 18 deletions(-) diff --git a/promotions/app/models/solidus_friendly_promotions/calculators/percent.rb b/promotions/app/models/solidus_friendly_promotions/calculators/percent.rb index 8864904f..50d51225 100644 --- a/promotions/app/models/solidus_friendly_promotions/calculators/percent.rb +++ b/promotions/app/models/solidus_friendly_promotions/calculators/percent.rb @@ -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 diff --git a/promotions/app/models/solidus_friendly_promotions/calculators/tiered_flat_rate.rb b/promotions/app/models/solidus_friendly_promotions/calculators/tiered_flat_rate.rb index 1fda3e91..f6cda6cc 100644 --- a/promotions/app/models/solidus_friendly_promotions/calculators/tiered_flat_rate.rb +++ b/promotions/app/models/solidus_friendly_promotions/calculators/tiered_flat_rate.rb @@ -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? diff --git a/promotions/app/models/solidus_friendly_promotions/discountable/line_item.rb b/promotions/app/models/solidus_friendly_promotions/discountable/line_item.rb index d196c553..f732710d 100644 --- a/promotions/app/models/solidus_friendly_promotions/discountable/line_item.rb +++ b/promotions/app/models/solidus_friendly_promotions/discountable/line_item.rb @@ -15,7 +15,7 @@ def line_item __getobj__ end - def discounted_amount + def discountable_amount amount + discounts.sum(&:amount) end end diff --git a/promotions/app/models/solidus_friendly_promotions/discountable/shipment.rb b/promotions/app/models/solidus_friendly_promotions/discountable/shipment.rb index e84a22a7..e16c5a96 100644 --- a/promotions/app/models/solidus_friendly_promotions/discountable/shipment.rb +++ b/promotions/app/models/solidus_friendly_promotions/discountable/shipment.rb @@ -16,7 +16,7 @@ def shipment __getobj__ end - def discounted_amount + def discountable_amount amount + discounts.sum(&:amount) end end diff --git a/promotions/app/models/solidus_friendly_promotions/discountable/shipping_rate.rb b/promotions/app/models/solidus_friendly_promotions/discountable/shipping_rate.rb index bc938e69..77a6ca46 100644 --- a/promotions/app/models/solidus_friendly_promotions/discountable/shipping_rate.rb +++ b/promotions/app/models/solidus_friendly_promotions/discountable/shipping_rate.rb @@ -15,7 +15,7 @@ def shipping_rate __getobj__ end - def discounted_amount + def discountable_amount amount + discounts.sum(&:amount) end end diff --git a/promotions/app/models/solidus_friendly_promotions/distributed_amounts_handler.rb b/promotions/app/models/solidus_friendly_promotions/distributed_amounts_handler.rb index 6e0c0630..485b8b22 100644 --- a/promotions/app/models/solidus_friendly_promotions/distributed_amounts_handler.rb +++ b/promotions/app/models/solidus_friendly_promotions/distributed_amounts_handler.rb @@ -29,7 +29,7 @@ def line_item_ids end def elligible_amounts - line_items.map(&:amount) + line_items.map(&:discountable_amount) end def subtotal diff --git a/promotions/app/models/solidus_friendly_promotions/promotion_action.rb b/promotions/app/models/solidus_friendly_promotions/promotion_action.rb index 27eb152b..9ce0c9df 100644 --- a/promotions/app/models/solidus_friendly_promotions/promotion_action.rb +++ b/promotions/app/models/solidus_friendly_promotions/promotion_action.rb @@ -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) diff --git a/promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb b/promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb index 65aca311..1f4cc920 100644 --- a/promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb +++ b/promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb @@ -10,7 +10,8 @@ end let(:rules) { [] } let(:action) { SolidusFriendlyPromotions::Actions::AdjustLineItem.create(calculator: calculator) } - let(:order) { create(:order_with_line_items, line_items_attributes: line_items_attributes) } + let(:spree_order) { create(:order_with_line_items, line_items_attributes: line_items_attributes) } + let(:order) { SolidusFriendlyPromotions::Discountable::Order.new(spree_order) } let(:currency) { "USD" } context "applied to an order" do diff --git a/promotions/spec/models/solidus_friendly_promotions/calculators/percent_spec.rb b/promotions/spec/models/solidus_friendly_promotions/calculators/percent_spec.rb index f7173495..fe98125d 100644 --- a/promotions/spec/models/solidus_friendly_promotions/calculators/percent_spec.rb +++ b/promotions/spec/models/solidus_friendly_promotions/calculators/percent_spec.rb @@ -7,7 +7,7 @@ context "compute" do let(:currency) { "USD" } let(:order) { double(currency: currency) } - let(:line_item) { double("LineItem", amount: 100, order: order) } + let(:line_item) { double("SolidusFriendlyPromotions::Discountable::LineItem", discountable_amount: 100, order: order) } before { subject.preferred_percent = 15 } diff --git a/promotions/spec/models/solidus_friendly_promotions/calculators/tiered_flat_rate_spec.rb b/promotions/spec/models/solidus_friendly_promotions/calculators/tiered_flat_rate_spec.rb index ee438e6c..490dac04 100644 --- a/promotions/spec/models/solidus_friendly_promotions/calculators/tiered_flat_rate_spec.rb +++ b/promotions/spec/models/solidus_friendly_promotions/calculators/tiered_flat_rate_spec.rb @@ -75,7 +75,7 @@ line_items_price: amount ) end - let(:line_item) { order.line_items.first } + let(:line_item) { SolidusFriendlyPromotions::Discountable::LineItem.new(order.line_items.first, order: order) } let(:preferred_currency) { "USD" } before do @@ -109,7 +109,8 @@ context "with a shipment" do subject { calculator.compute(shipment) } - let(:shipment) { Spree::Shipment.new(order: order, amount: shipping_cost) } + let(:spree_shipment) { Spree::Shipment.new(order: order, amount: shipping_cost) } + let(:shipment) { SolidusFriendlyPromotions::Discountable::Shipment.new(spree_shipment, order: order) } let(:line_item_count) { 1 } let(:amount) { 10 } diff --git a/promotions/spec/models/solidus_friendly_promotions/discountable/line_item_spec.rb b/promotions/spec/models/solidus_friendly_promotions/discountable/line_item_spec.rb index 2778f522..c107b821 100644 --- a/promotions/spec/models/solidus_friendly_promotions/discountable/line_item_spec.rb +++ b/promotions/spec/models/solidus_friendly_promotions/discountable/line_item_spec.rb @@ -14,8 +14,8 @@ it { is_expected.to eq(discountable_order) } end - describe "#discounted_amount" do - subject(:discounted_amount) { discountable_line_item.discounted_amount } + describe "#discountable_amount" do + subject(:discountable_amount) { discountable_line_item.discountable_amount } context "with no discounts" do it { is_expected.to eq(20) } diff --git a/promotions/spec/models/solidus_friendly_promotions/discountable/shipment_spec.rb b/promotions/spec/models/solidus_friendly_promotions/discountable/shipment_spec.rb index 3c963f7d..96e58f1f 100644 --- a/promotions/spec/models/solidus_friendly_promotions/discountable/shipment_spec.rb +++ b/promotions/spec/models/solidus_friendly_promotions/discountable/shipment_spec.rb @@ -15,8 +15,8 @@ it { is_expected.to eq(discountable_order) } end - describe "#discounted_amount" do - subject(:discounted_amount) { discountable_shipment.discounted_amount } + describe "#discountable_amount" do + subject(:discountable_amount) { discountable_shipment.discountable_amount } context "with no discounts" do it { is_expected.to eq(20) } diff --git a/promotions/spec/models/solidus_friendly_promotions/discountable/shipping_rate_spec.rb b/promotions/spec/models/solidus_friendly_promotions/discountable/shipping_rate_spec.rb index 25cbaf6a..f6c6d778 100644 --- a/promotions/spec/models/solidus_friendly_promotions/discountable/shipping_rate_spec.rb +++ b/promotions/spec/models/solidus_friendly_promotions/discountable/shipping_rate_spec.rb @@ -15,8 +15,8 @@ it { is_expected.to eq(discountable_shipment) } end - describe "#discounted_amount" do - subject(:discounted_amount) { discountable_shipping_rate.discounted_amount } + describe "#discountable_amount" do + subject(:discountable_amount) { discountable_shipping_rate.discountable_amount } context "with no discounts" do it { is_expected.to eq(20) } diff --git a/promotions/spec/models/solidus_friendly_promotions/distributed_amounts_handler_spec.rb b/promotions/spec/models/solidus_friendly_promotions/distributed_amounts_handler_spec.rb index f3d24490..ee267f90 100644 --- a/promotions/spec/models/solidus_friendly_promotions/distributed_amounts_handler_spec.rb +++ b/promotions/spec/models/solidus_friendly_promotions/distributed_amounts_handler_spec.rb @@ -3,13 +3,17 @@ require "spec_helper" RSpec.describe SolidusFriendlyPromotions::DistributedAmountsHandler, type: :model do - let(:order) do + let(:spree_order) do FactoryBot.create( :order_with_line_items, line_items_attributes: line_items_attributes ) end + let(:order) do + SolidusFriendlyPromotions::Discountable::Order.new(spree_order) + end + let(:handler) { described_class.new(order.line_items, total_amount) }