Skip to content

Commit

Permalink
Merge pull request #2582 from Sinetheta/distribute-all
Browse files Browse the repository at this point in the history
Distribute over eligible line items
  • Loading branch information
jhawthorn authored Feb 28, 2018
2 parents 20a2c01 + 7b837d5 commit 0fc2470
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 48 deletions.
21 changes: 14 additions & 7 deletions core/app/models/spree/calculator/distributed_amount.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,20 @@ class Calculator::DistributedAmount < Calculator
preference :currency, :string, default: -> { Spree::Config[:currency] }

def compute_line_item(line_item)
if line_item && preferred_currency.casecmp(line_item.currency).zero?
Spree::DistributedAmountsHandler.new(
line_item,
preferred_amount
).amount
else
0
return 0 unless line_item
return 0 unless preferred_currency.casecmp(line_item.currency).zero?
return 0 unless calculable.promotion.line_item_actionable?(line_item.order, line_item)
Spree::DistributedAmountsHandler.new(
actionable_line_items(line_item.order),
preferred_amount
).amount(line_item)
end

private

def actionable_line_items(order)
order.line_items.select do |line_item|
calculable.promotion.line_item_actionable?(order, line_item)
end
end
end
Expand Down
54 changes: 28 additions & 26 deletions core/app/models/spree/distributed_amounts_handler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@

module Spree
class DistributedAmountsHandler
attr_reader :line_item, :order, :total_amount
attr_reader :line_items, :total_amount

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

# @return [Float] the weighted adjustment for the initialized line item
def amount
distributed_amounts[@line_item.id].to_f
# @param [LineItem] one of the line_items distributed over
# @return [BigDecimal] the weighted adjustment for this line_item
def amount(line_item)
distributed_amounts[line_item.id].to_d
end

private
Expand All @@ -21,25 +21,27 @@ 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
line_items.map(&:id)
end

def elligible_amounts
line_items.map(&:amount)
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 @@ -4,10 +4,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 @@ -19,11 +65,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
32 changes: 17 additions & 15 deletions core/spec/models/spree/distributed_amounts_handler_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,19 @@
)
end

let(:handler) {
described_class.new(order.line_items, total_amount)
}

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

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

context "when there is only one line item" do
let(:line_items_attributes) { [{ price: 100 }] }
let(:line_item) { order.line_items.first }

it "applies the entire amount to the line item" do
expect(subject).to eq(15)
expect(handler.amount(line_item)).to eq(15)
end
end

Expand All @@ -33,9 +35,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
handler.amount(order.line_items[0]),
handler.amount(order.line_items[1]),
handler.amount(order.line_items[2])
]
).to eq(
[5, 5, 5]
Expand All @@ -48,28 +50,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
handler.amount(order.line_items[0]),
handler.amount(order.line_items[1]),
handler.amount(order.line_items[2])
]
).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
handler.amount(order.line_items[0]),
handler.amount(order.line_items[1]),
handler.amount(order.line_items[2])
]
).to eq(
[7.5, 2.5, 5]
Expand Down

0 comments on commit 0fc2470

Please sign in to comment.