Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# frozen_string_literal: true

module SolidusPromotions
module AdjustmentDiscounts
def discounts_by_lanes(lanes)
adjustments.select do |adjustment|
!adjustment.marked_for_destruction? &&
adjustment.source_type == "SolidusPromotions::Benefit" &&
adjustment.source.promotion.lane.to_sym.in?(lanes.map(&:to_sym))
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# frozen_string_literal: true

module SolidusPromotions
module DiscountedAmount
def discounted_amount
amount + previous_lane_discounts.sum(&:amount)
end

def current_lane_discounts
raise NotCalculatingPromotions unless Promotion.current_lane

discounts_by_lanes([Promotion.current_lane])
end

def previous_lane_discounts
discounts_by_lanes(Promotion.lanes_before_current_lane)
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# frozen_string_literal: true

module SolidusPromotions
class NotCalculatingPromotions < StandardError; end
end
20 changes: 20 additions & 0 deletions promotions/app/models/solidus_promotions/promotion.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,15 @@ def self.ordered_lanes
lanes.keys.sort_by { |lane| lanes[lane] }
end

def self.lanes_before(lane)
ordered_lanes.split(lane.to_s).first
end

def self.lanes_before_current_lane
return ordered_lanes unless current_lane
lanes_before(current_lane)
end

def self.order_activatable?(order)
return false if UNACTIVATABLE_ORDER_STATES.include?(order.state)
return false if order.shipped?
Expand All @@ -74,6 +83,17 @@ def self.order_activatable?(order)
true
end

def self.within_lane(lane, &)
previous_lane = Thread.current[:current_promotion_lane]
Thread.current[:current_promotion_lane] = lane.to_sym
yield
Thread.current[:current_promotion_lane] = previous_lane
end

def self.current_lane
Thread.current[:current_promotion_lane]
end

self.allowed_ransackable_associations = ["codes"]
self.allowed_ransackable_attributes = %w[name customer_label path promotion_category_id lane updated_at]
self.allowed_ransackable_scopes = %i[active with_discarded]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ def reset_quantity_setter
end

Spree::LineItem.prepend self
Spree::LineItem.prepend SolidusPromotions::AdjustmentDiscounts
Spree::LineItem.prepend SolidusPromotions::DiscountedAmount
Spree::LineItem.prepend SolidusPromotions::DiscountableAmount
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,7 @@ def reset_current_discounts
end

Spree::Shipment.prepend self
Spree::Shipment.prepend SolidusPromotions::AdjustmentDiscounts
Spree::Shipment.prepend SolidusPromotions::DiscountedAmount
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ def promo_total
discounts.sum(&:amount)
end

def discounts_by_lanes(lanes)
discounts.select do |discount|
!discount.marked_for_destruction? &&
discount.benefit.promotion.lane.to_sym.in?(lanes.map(&:to_sym))
end
end

Spree::ShippingRate.prepend SolidusPromotions::DiscountedAmount
Spree::ShippingRate.prepend SolidusPromotions::DiscountableAmount
Spree::ShippingRate.prepend self
end
Expand Down
54 changes: 54 additions & 0 deletions promotions/spec/models/solidus_promotions/promotion_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,36 @@
it { is_expected.to eq(%w[pre default post]) }
end

describe ".lanes_before_current_lane" do
let(:lane) { :pre }

subject { SolidusPromotions::Promotion.lanes_before_current_lane }

it { is_expected.to eq(["pre", "default", "post"]) }

context "if lane is given" do
let(:lane) { :pre }

around do |example|
described_class.within_lane(lane) do
example.run
end
end

it { is_expected.to be_empty }

context "if lane is default" do
let(:lane) { :default }
it { is_expected.to eq(["pre"]) }
end

context "if lane is post" do
let(:lane) { :post }
it { is_expected.to eq(["pre", "default"]) }
end
end
end

describe "validations" do
subject(:promotion) { build(:solidus_promotion) }

Expand All @@ -134,6 +164,30 @@
end
end

describe ".within_lane" do
let(:lane) { :pre }

it "runs blocks with current_lane set to lane" do
expect(described_class.current_lane).to be nil
described_class.within_lane(lane) do
expect(described_class.current_lane).to eq(:pre)
end
expect(described_class.current_lane).to be nil
end

it "can be nested" do
expect(described_class.current_lane).to be nil
described_class.within_lane(lane) do
expect(described_class.current_lane).to eq(:pre)
described_class.within_lane("default") do
expect(described_class.current_lane).to eq(:default)
end
expect(described_class.current_lane).to eq(:pre)
end
expect(described_class.current_lane).to be nil
end
end

describe ".coupons" do
subject { described_class.coupons }

Expand Down
142 changes: 142 additions & 0 deletions promotions/spec/models/spree/line_item_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,42 @@
end
end

describe "#discounts_by_lanes" do
let(:tax_rate) { create(:tax_rate) }
let(:pre_lane_promotion) { create(:solidus_promotion, :with_adjustable_benefit, lane: :pre) }
let(:post_lane_promotion) { create(:solidus_promotion, :with_adjustable_benefit, lane: :post) }
let(:line_item) { Spree::LineItem.new(adjustments:) }
let(:adjustments) { [tax_adjustment, pre_lane_adjustment, post_lane_adjustment] }
let(:tax_adjustment) { Spree::Adjustment.new(source: tax_rate, amount: 2) }
let(:pre_lane_adjustment) { Spree::Adjustment.new(source: pre_lane_promotion.benefits.first) }
let(:post_lane_adjustment) { Spree::Adjustment.new(source: post_lane_promotion.benefits.first) }

subject { line_item.discounts_by_lanes(lanes) }

context "if lanes is empty" do
let(:lanes) { [] }
it { is_expected.to be_empty }
end

context "if lanes is all lanes" do
let(:lanes) { SolidusPromotions::Promotion.ordered_lanes }

it { is_expected.to contain_exactly(pre_lane_adjustment, post_lane_adjustment) }
end

context "if lanes is only pre lane" do
let(:lanes) { [:pre] }

it { is_expected.to contain_exactly(pre_lane_adjustment) }
end

context "if lanes is only default lane" do
let(:lanes) { [:default] }

it { is_expected.to be_empty }
end
end

describe "#reset_current_discounts" do
let(:line_item) { Spree::LineItem.new }

Expand All @@ -37,6 +73,112 @@
end
end

describe "#previous_lane_discounts" do
let(:order) { Spree::Order.new }
let(:tax_rate) { create(:tax_rate) }
let(:pre_lane_promotion) { create(:solidus_promotion, :with_adjustable_benefit, lane: :pre) }
let(:post_lane_promotion) { create(:solidus_promotion, :with_adjustable_benefit, lane: :post) }
let(:line_item) { Spree::LineItem.new(adjustments:, order:) }
let(:adjustments) { [tax_adjustment, pre_lane_adjustment, post_lane_adjustment] }
let(:tax_adjustment) { Spree::Adjustment.new(source: tax_rate, amount: 2) }
let(:pre_lane_adjustment) { Spree::Adjustment.new(source: pre_lane_promotion.benefits.first) }
let(:post_lane_adjustment) { Spree::Adjustment.new(source: post_lane_promotion.benefits.first) }

subject { line_item.previous_lane_discounts }

it "contains all adjustments if we're not calculating promotions" do
expect(subject).to contain_exactly(pre_lane_adjustment, post_lane_adjustment)
end

context "if adjustment is marked for destruction" do
before do
pre_lane_adjustment.mark_for_destruction
end

it { is_expected.to contain_exactly(post_lane_adjustment) }
end

context "while calculating promotions" do
around do |example|
SolidusPromotions::Promotion.within_lane(lane) do
example.run
end
end

let(:lane) { "pre" }
it { is_expected.to be_empty }

context "if lane is default" do
let(:lane) { "default" }

it { is_expected.to contain_exactly(pre_lane_adjustment) }
end

context "if lane is post" do
let(:lane) { "post" }

it { is_expected.to contain_exactly(pre_lane_adjustment) }
end
end
end

describe "#current_lane_discounts" do
let(:order) { Spree::Order.new }
let(:tax_rate) { create(:tax_rate) }
let(:pre_lane_promotion) { create(:solidus_promotion, :with_adjustable_benefit, lane: :pre) }
let(:post_lane_promotion) { create(:solidus_promotion, :with_adjustable_benefit, lane: :post) }
let(:line_item) { Spree::LineItem.new(adjustments:, order:) }
let(:adjustments) { [tax_adjustment, pre_lane_adjustment, post_lane_adjustment] }
let(:tax_adjustment) { Spree::Adjustment.new(source: tax_rate, amount: 2) }
let(:pre_lane_adjustment) { Spree::Adjustment.new(source: pre_lane_promotion.benefits.first) }
let(:post_lane_adjustment) { Spree::Adjustment.new(source: post_lane_promotion.benefits.first) }

subject { line_item.current_lane_discounts }

it "raises unless we're doing a promotion calculation" do
expect { subject }.to raise_exception(SolidusPromotions::NotCalculatingPromotions)
end

context "while calculating promotions" do
around do |example|
SolidusPromotions::Promotion.within_lane(lane) do
example.run
end
end

let(:lane) { "pre" }
it { is_expected.to contain_exactly(pre_lane_adjustment) }

context "if lane is default" do
let(:lane) { "default" }

it { is_expected.to be_empty }
end

context "if lane is post" do
let(:lane) { "post" }

it { is_expected.to contain_exactly(post_lane_adjustment) }
end
end
end

describe "#discounted_amount" do
let(:order) { Spree::Order.new }
let(:tax_rate) { create(:tax_rate) }
let(:pre_lane_promotion) { create(:solidus_promotion, :with_adjustable_benefit, lane: :pre) }
let(:post_lane_promotion) { create(:solidus_promotion, :with_adjustable_benefit, lane: :post) }
let(:line_item) { Spree::LineItem.new(adjustments:, order:, price: 14, quantity: 2) }
let(:adjustments) { [tax_adjustment, pre_lane_adjustment, post_lane_adjustment] }
let(:tax_adjustment) { Spree::Adjustment.new(source: tax_rate, amount: 2) }
let(:pre_lane_adjustment) { Spree::Adjustment.new(source: pre_lane_promotion.benefits.first, amount: -3) }
let(:post_lane_adjustment) { Spree::Adjustment.new(source: post_lane_promotion.benefits.first, amount: -2) }

subject { line_item.discounted_amount }

it { is_expected.to eq(23) }
end

describe "changing quantities" do
context "when line item is managed by an automation" do
let(:order) { create(:order) }
Expand Down
Loading
Loading