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

Distribute over eligible line items #2582

Merged
merged 6 commits into from
Feb 28, 2018
Merged
Show file tree
Hide file tree
Changes from 5 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
1 change: 1 addition & 0 deletions core/app/models/spree/calculator/distributed_amount.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ def compute_line_item(line_item)
if line_item && preferred_currency.casecmp(line_item.currency).zero?
Spree::DistributedAmountsHandler.new(
line_item,
calculable.promotion,
preferred_amount
).amount
else
Expand Down
52 changes: 29 additions & 23 deletions core/app/models/spree/distributed_amounts_handler.rb
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
module Spree
class DistributedAmountsHandler
attr_reader :line_item, :order, :total_amount
attr_reader :line_item, :order, :promotion, :total_amount

def initialize(line_item, total_amount)
def initialize(line_item, promotion, total_amount)
@line_item = line_item
@order = line_item.order
@promotion = promotion
@total_amount = total_amount
end

# @return [Float] the weighted adjustment for the initialized line item
# @return [BigDecimal] the weighted adjustment for the initialized line item
def amount
distributed_amounts[@line_item.id].to_f
distributed_amounts[@line_item.id].to_d
end

private
Expand All @@ -19,25 +20,30 @@ def amount
# @return [Hash<Integer, BigDecimal>] a hash of line item IDs and their
# corresponding weighted adjustments
def distributed_amounts
remaining_amount = @total_amount

@order.line_items.each_with_index.map do |line_item, i|
if i == @order.line_items.length - 1
# If this is the last line item on the order we want to use the
# remaining preferred amount to ensure our total adjustment is what
# has been set as the preferred amount.
[line_item.id, remaining_amount]
else
# Calculate the weighted amount by getting this line item's share of
# the order's total and multiplying it with the preferred amount.
weighted_amount = ((line_item.amount / @order.item_total) * total_amount).round(2)

# Subtract this line item's weighted amount from the total.
remaining_amount -= weighted_amount

[line_item.id, weighted_amount]
end
end.to_h
Hash[line_item_ids.zip(allocated_amounts)]
end

def line_item_ids
@order.line_items.map(&:id)
end

def elligible_amounts
@order.line_items.map do |line_item|
elligible = promotion.line_item_actionable?(line_item.order, line_item)
elligible ? line_item.amount : 0
end
end

def subtotal
elligible_amounts.sum
end

def weights
elligible_amounts.map { |amount| amount.to_f / subtotal.to_f }
end

def allocated_amounts
@total_amount.to_money.allocate(weights).map(&:to_money)
end
end
end
48 changes: 48 additions & 0 deletions core/spec/models/spree/calculator/distributed_amount_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,56 @@
require 'shared_examples/calculator_shared_examples'

RSpec.describe Spree::Calculator::DistributedAmount, type: :model do
context 'applied to an order' do
let(:calculator) { Spree::Calculator::DistributedAmount.new }
let(:promotion) {
create :promotion,
name: '15 spread'
}
let(:order) {
create :completed_order_with_promotion,
promotion: promotion,
line_items_attributes: [{ price: 20 }, { price: 30 }, { price: 100 }]
}

before do
calculator.preferred_amount = 15
Spree::Promotion::Actions::CreateItemAdjustments.create!(calculator: calculator, promotion: promotion)
order.recalculate
end

it 'correctly distributes the entire discount' do
expect(order.promo_total).to eq(-15)
expect(order.line_items.map(&:adjustment_total)).to eq([-2, -3, -10])
end

context 'with product promotion rule' do
let(:first_product) { order.line_items.first.product }

before do
rule = Spree::Promotion::Rules::Product.create!(
promotion: promotion,
product_promotion_rules: [
Spree::ProductPromotionRule.new(product: first_product),
],
)
promotion.rules << rule
promotion.save!
order.recalculate
end

it 'still distributes the entire discount' do
expect(order.promo_total).to eq(-15)
expect(order.line_items.map(&:adjustment_total)).to eq([-15, 0, 0])
end
end
end

describe "#compute_line_item" do
subject { calculator.compute_line_item(order.line_items.first) }

let(:calculator) { Spree::Calculator::DistributedAmount.new }
let(:promotion) { create(:promotion) }

let(:order) do
FactoryBot.create(
Expand All @@ -17,11 +63,13 @@
before do
calculator.preferred_amount = 15
calculator.preferred_currency = currency
Spree::Promotion::Actions::CreateItemAdjustments.create!(calculator: calculator, promotion: promotion)
end

context "when the order currency matches the store's currency" do
let(:currency) { "USD" }
it { is_expected.to eq 5 }
it { is_expected.to be_a BigDecimal }
end

context "when the order currency does not match the store's currency" do
Expand Down
27 changes: 14 additions & 13 deletions core/spec/models/spree/distributed_amounts_handler_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@
line_items_attributes: line_items_attributes
)
end
let(:promotion) { FactoryBot.build(:promotion) }

describe "#amount" do
let(:total_amount) { 15 }

subject { described_class.new(line_item, total_amount).amount }
subject { described_class.new(line_item, promotion, total_amount).amount }

context "when there is only one line item" do
let(:line_items_attributes) { [{ price: 100 }] }
Expand All @@ -31,9 +32,9 @@
it "evenly distributes the total amount" do
expect(
[
described_class.new(order.line_items[0], total_amount).amount,
described_class.new(order.line_items[1], total_amount).amount,
described_class.new(order.line_items[2], total_amount).amount
described_class.new(order.line_items[0], promotion, total_amount).amount,
described_class.new(order.line_items[1], promotion, total_amount).amount,
described_class.new(order.line_items[2], promotion, total_amount).amount
]
).to eq(
[5, 5, 5]
Expand All @@ -46,28 +47,28 @@
it "applies the remainder of the total amount to the last item" do
expect(
[
described_class.new(order.line_items[0], total_amount).amount,
described_class.new(order.line_items[1], total_amount).amount,
described_class.new(order.line_items[2], total_amount).amount
described_class.new(order.line_items[0], promotion, total_amount).amount,
described_class.new(order.line_items[1], promotion, total_amount).amount,
described_class.new(order.line_items[2], promotion, total_amount).amount
]
).to eq(
).to match_array(
[3.33, 3.33, 3.34]
)
end
end
end

context "and the line items are not equally priced" do
context "and the line items do not have equal subtotal amounts" do
let(:line_items_attributes) do
[{ price: 150 }, { price: 50 }, { price: 100 }]
[{ price: 50, quantity: 3 }, { price: 50, quantity: 1 }, { price: 50, quantity: 2 }]
end

it "distributes the total amount relative to the item's price" do
expect(
[
described_class.new(order.line_items[0], total_amount).amount,
described_class.new(order.line_items[1], total_amount).amount,
described_class.new(order.line_items[2], total_amount).amount
described_class.new(order.line_items[0], promotion, total_amount).amount,
described_class.new(order.line_items[1], promotion, total_amount).amount,
described_class.new(order.line_items[2], promotion, total_amount).amount
]
).to eq(
[7.5, 2.5, 5]
Expand Down