mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-03-03 02:21:33 +00:00
WIP: Extract order cycle fee calculations to EnterpriseFeeCalculator
This commit is contained in:
@@ -1,5 +1,3 @@
|
||||
require 'open_food_network/enterprise_fee_applicator'
|
||||
|
||||
class OrderCycle < ActiveRecord::Base
|
||||
belongs_to :coordinator, :class_name => 'Enterprise'
|
||||
has_and_belongs_to_many :coordinator_fees, :class_name => 'EnterpriseFee', :join_table => 'coordinator_fees'
|
||||
@@ -165,77 +163,17 @@ class OrderCycle < ActiveRecord::Base
|
||||
exchange_for_distributor(distributor).andand.pickup_instructions
|
||||
end
|
||||
|
||||
|
||||
# -- Fees
|
||||
|
||||
# TODO: The boundary of this class is ill-defined here. OrderCycle should not know about
|
||||
# EnterpriseFeeApplicator. Clients should be able to query it for relevant EnterpriseFees.
|
||||
# This logic would fit better in another service object.
|
||||
|
||||
def fees_for(variant, distributor)
|
||||
per_item_enterprise_fee_applicators_for(variant, distributor).sum do |applicator|
|
||||
# Spree's Calculator interface accepts Orders or LineItems,
|
||||
# so we meet that interface with a struct.
|
||||
# Amount is faked, this is a method on LineItem
|
||||
line_item = OpenStruct.new variant: variant, quantity: 1, amount: variant.price
|
||||
applicator.enterprise_fee.compute_amount(line_item)
|
||||
end
|
||||
def exchanges_carrying(variant, distributor)
|
||||
exchanges.supplying_to(distributor).with_variant(variant)
|
||||
end
|
||||
|
||||
def create_line_item_adjustments_for(line_item)
|
||||
variant = line_item.variant
|
||||
distributor = line_item.order.distributor
|
||||
|
||||
per_item_enterprise_fee_applicators_for(variant, distributor).each do |applicator|
|
||||
applicator.create_line_item_adjustment(line_item)
|
||||
end
|
||||
end
|
||||
|
||||
def create_order_adjustments_for(order)
|
||||
per_order_enterprise_fee_applicators_for(order).each do |applicator|
|
||||
applicator.create_order_adjustment(order)
|
||||
end
|
||||
def exchanges_supplying(order)
|
||||
exchanges.supplying_to(order.distributor).with_any_variant(order.variants)
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
# -- Fees
|
||||
def per_item_enterprise_fee_applicators_for(variant, distributor)
|
||||
fees = []
|
||||
|
||||
exchanges_carrying(variant, distributor).each do |exchange|
|
||||
exchange.enterprise_fees.per_item.each do |enterprise_fee|
|
||||
fees << OpenFoodNetwork::EnterpriseFeeApplicator.new(enterprise_fee, variant, exchange.role)
|
||||
end
|
||||
end
|
||||
|
||||
coordinator_fees.per_item.each do |enterprise_fee|
|
||||
fees << OpenFoodNetwork::EnterpriseFeeApplicator.new(enterprise_fee, variant, 'coordinator')
|
||||
end
|
||||
|
||||
fees
|
||||
end
|
||||
|
||||
def per_order_enterprise_fee_applicators_for(order)
|
||||
fees = []
|
||||
|
||||
exchanges_supplying(order).each do |exchange|
|
||||
exchange.enterprise_fees.per_order.each do |enterprise_fee|
|
||||
fees << OpenFoodNetwork::EnterpriseFeeApplicator.new(enterprise_fee, nil, exchange.role)
|
||||
end
|
||||
end
|
||||
|
||||
coordinator_fees.per_order.each do |enterprise_fee|
|
||||
fees << OpenFoodNetwork::EnterpriseFeeApplicator.new(enterprise_fee, nil, 'coordinator')
|
||||
end
|
||||
|
||||
fees
|
||||
end
|
||||
|
||||
|
||||
# -- Misc
|
||||
|
||||
# If a product without variants is added to an order cycle, and then some variants are added
|
||||
# to that product, then the master variant is still part of the order cycle, but customers
|
||||
# should not be able to purchase it.
|
||||
@@ -246,12 +184,4 @@ class OrderCycle < ActiveRecord::Base
|
||||
distributed_variants.include?(product.master) &&
|
||||
(product.variants & distributed_variants).empty?
|
||||
end
|
||||
|
||||
def exchanges_carrying(variant, distributor)
|
||||
exchanges.supplying_to(distributor).with_variant(variant)
|
||||
end
|
||||
|
||||
def exchanges_supplying(order)
|
||||
exchanges.supplying_to(order.distributor).with_any_variant(order.variants)
|
||||
end
|
||||
end
|
||||
|
||||
75
lib/open_food_network/enterprise_fee_calculator.rb
Normal file
75
lib/open_food_network/enterprise_fee_calculator.rb
Normal file
@@ -0,0 +1,75 @@
|
||||
require 'open_food_network/enterprise_fee_applicator'
|
||||
|
||||
module OpenFoodNetwork
|
||||
class EnterpriseFeeCalculator
|
||||
def initialize(distributor=nil, order_cycle=nil)
|
||||
@distributor = distributor
|
||||
@order_cycle = order_cycle
|
||||
end
|
||||
|
||||
|
||||
def fees_for(variant)
|
||||
per_item_enterprise_fee_applicators_for(variant).sum do |applicator|
|
||||
# Spree's Calculator interface accepts Orders or LineItems,
|
||||
# so we meet that interface with a struct.
|
||||
# Amount is faked, this is a method on LineItem
|
||||
line_item = OpenStruct.new variant: variant, quantity: 1, amount: variant.price
|
||||
applicator.enterprise_fee.compute_amount(line_item)
|
||||
end
|
||||
end
|
||||
|
||||
def create_line_item_adjustments_for(line_item)
|
||||
variant = line_item.variant
|
||||
@distributor = line_item.order.distributor
|
||||
@order_cycle = line_item.order.order_cycle
|
||||
|
||||
per_item_enterprise_fee_applicators_for(variant).each do |applicator|
|
||||
applicator.create_line_item_adjustment(line_item)
|
||||
end
|
||||
end
|
||||
|
||||
def create_order_adjustments_for(order)
|
||||
@distributor = order.distributor
|
||||
@order_cycle = order.order_cycle
|
||||
|
||||
per_order_enterprise_fee_applicators_for(order).each do |applicator|
|
||||
applicator.create_order_adjustment(order)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
def per_item_enterprise_fee_applicators_for(variant)
|
||||
fees = []
|
||||
|
||||
@order_cycle.exchanges_carrying(variant, @distributor).each do |exchange|
|
||||
exchange.enterprise_fees.per_item.each do |enterprise_fee|
|
||||
fees << OpenFoodNetwork::EnterpriseFeeApplicator.new(enterprise_fee, variant, exchange.role)
|
||||
end
|
||||
end
|
||||
|
||||
@order_cycle.coordinator_fees.per_item.each do |enterprise_fee|
|
||||
fees << OpenFoodNetwork::EnterpriseFeeApplicator.new(enterprise_fee, variant, 'coordinator')
|
||||
end
|
||||
|
||||
fees
|
||||
end
|
||||
|
||||
def per_order_enterprise_fee_applicators_for(order)
|
||||
fees = []
|
||||
|
||||
@order_cycle.exchanges_supplying(order).each do |exchange|
|
||||
exchange.enterprise_fees.per_order.each do |enterprise_fee|
|
||||
fees << OpenFoodNetwork::EnterpriseFeeApplicator.new(enterprise_fee, nil, exchange.role)
|
||||
end
|
||||
end
|
||||
|
||||
@order_cycle.coordinator_fees.per_order.each do |enterprise_fee|
|
||||
fees << OpenFoodNetwork::EnterpriseFeeApplicator.new(enterprise_fee, nil, 'coordinator')
|
||||
end
|
||||
|
||||
fees
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -27,7 +27,6 @@ module OpenFoodNetwork
|
||||
|
||||
it "creates an adjustment for an order" do
|
||||
order = create(:order)
|
||||
#line_item = create(:line_item)
|
||||
enterprise_fee = create(:enterprise_fee)
|
||||
product = create(:simple_product)
|
||||
|
||||
|
||||
138
spec/lib/open_food_network/enterprise_fee_calculator_spec.rb
Normal file
138
spec/lib/open_food_network/enterprise_fee_calculator_spec.rb
Normal file
@@ -0,0 +1,138 @@
|
||||
require 'open_food_network/enterprise_fee_calculator'
|
||||
|
||||
module OpenFoodNetwork
|
||||
describe EnterpriseFeeCalculator do
|
||||
describe "integration" do
|
||||
let(:coordinator) { create(:distributor_enterprise) }
|
||||
let(:distributor) { create(:distributor_enterprise) }
|
||||
let(:order_cycle) { create(:simple_order_cycle) }
|
||||
let(:product) { create(:simple_product, price: 10.00) }
|
||||
|
||||
describe "calculating fees for a variant via a particular distribution" do
|
||||
it "sums all the per-item fees for the variant in the specified hub + order cycle" do
|
||||
enterprise_fee1 = create(:enterprise_fee, amount: 20)
|
||||
enterprise_fee2 = create(:enterprise_fee, amount: 3)
|
||||
enterprise_fee3 = create(:enterprise_fee,
|
||||
calculator: Spree::Calculator::FlatRate.new(preferred_amount: 2))
|
||||
|
||||
create(:exchange, order_cycle: order_cycle, sender: coordinator, receiver: distributor, incoming: false,
|
||||
enterprise_fees: [enterprise_fee1, enterprise_fee2, enterprise_fee3], variants: [product.master])
|
||||
|
||||
EnterpriseFeeCalculator.new(distributor, order_cycle).fees_for(product.master).should == 23
|
||||
end
|
||||
|
||||
it "sums percentage fees for the variant" do
|
||||
enterprise_fee1 = create(:enterprise_fee, amount: 20, fee_type: "admin", calculator: Spree::Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 20))
|
||||
|
||||
create(:exchange, order_cycle: order_cycle, sender: coordinator, receiver: distributor, incoming: false,
|
||||
enterprise_fees: [enterprise_fee1], variants: [product.master])
|
||||
|
||||
product.master.price.should == 10.00
|
||||
EnterpriseFeeCalculator.new(distributor, order_cycle).fees_for(product.master).should == 2.00
|
||||
end
|
||||
end
|
||||
|
||||
describe "creating adjustments" do
|
||||
let(:order) { create(:order, distributor: distributor, order_cycle: order_cycle) }
|
||||
let!(:line_item) { create(:line_item, order: order, variant: product.master) }
|
||||
let(:enterprise_fee_line_item) { create(:enterprise_fee) }
|
||||
let(:enterprise_fee_order) { create(:enterprise_fee, calculator: Spree::Calculator::FlatRate.new(preferred_amount: 2)) }
|
||||
let!(:exchange) { create(:exchange, order_cycle: order_cycle, sender: coordinator, receiver: distributor, incoming: false, variants: [product.master]) }
|
||||
|
||||
before { order.reload }
|
||||
|
||||
it "creates adjustments for a line item" do
|
||||
exchange.enterprise_fees << enterprise_fee_line_item
|
||||
|
||||
EnterpriseFeeCalculator.new.create_line_item_adjustments_for line_item
|
||||
|
||||
a = Spree::Adjustment.last
|
||||
a.metadata.fee_name.should == enterprise_fee_line_item.name
|
||||
end
|
||||
|
||||
it "creates adjustments for an order" do
|
||||
exchange.enterprise_fees << enterprise_fee_order
|
||||
|
||||
EnterpriseFeeCalculator.new.create_order_adjustments_for order
|
||||
|
||||
a = Spree::Adjustment.last
|
||||
a.metadata.fee_name.should == enterprise_fee_order.name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "creating adjustments for a line item" do
|
||||
let(:oc) { OrderCycle.new }
|
||||
let(:variant) { double(:variant) }
|
||||
let(:distributor) { double(:distributor) }
|
||||
let(:order) { double(:order, distributor: distributor, order_cycle: oc) }
|
||||
let(:line_item) { double(:line_item, variant: variant, order: order) }
|
||||
|
||||
it "creates an adjustment for each fee" do
|
||||
applicator = double(:enterprise_fee_applicator)
|
||||
applicator.should_receive(:create_line_item_adjustment).with(line_item)
|
||||
|
||||
efc = EnterpriseFeeCalculator.new
|
||||
efc.should_receive(:per_item_enterprise_fee_applicators_for).with(variant) { [applicator] }
|
||||
|
||||
efc.create_line_item_adjustments_for line_item
|
||||
end
|
||||
|
||||
it "makes fee applicators for a line item" do
|
||||
distributor = double(:distributor)
|
||||
ef1 = double(:enterprise_fee)
|
||||
ef2 = double(:enterprise_fee)
|
||||
ef3 = double(:enterprise_fee)
|
||||
incoming_exchange = double(:exchange, role: 'supplier')
|
||||
outgoing_exchange = double(:exchange, role: 'distributor')
|
||||
incoming_exchange.stub_chain(:enterprise_fees, :per_item) { [ef1] }
|
||||
outgoing_exchange.stub_chain(:enterprise_fees, :per_item) { [ef2] }
|
||||
|
||||
oc.stub(:exchanges_carrying) { [incoming_exchange, outgoing_exchange] }
|
||||
oc.stub_chain(:coordinator_fees, :per_item) { [ef3] }
|
||||
|
||||
efc = EnterpriseFeeCalculator.new(distributor, oc)
|
||||
efc.send(:per_item_enterprise_fee_applicators_for, line_item.variant).should ==
|
||||
[OpenFoodNetwork::EnterpriseFeeApplicator.new(ef1, line_item.variant, 'supplier'),
|
||||
OpenFoodNetwork::EnterpriseFeeApplicator.new(ef2, line_item.variant, 'distributor'),
|
||||
OpenFoodNetwork::EnterpriseFeeApplicator.new(ef3, line_item.variant, 'coordinator')]
|
||||
end
|
||||
end
|
||||
|
||||
describe "creating adjustments for an order" do
|
||||
let(:oc) { OrderCycle.new }
|
||||
let(:distributor) { double(:distributor) }
|
||||
let(:order) { double(:order, distributor: distributor, order_cycle: oc) }
|
||||
|
||||
it "creates an adjustment for each fee" do
|
||||
applicator = double(:enterprise_fee_applicator)
|
||||
applicator.should_receive(:create_order_adjustment).with(order)
|
||||
|
||||
efc = EnterpriseFeeCalculator.new
|
||||
efc.should_receive(:per_order_enterprise_fee_applicators_for).with(order) { [applicator] }
|
||||
|
||||
efc.create_order_adjustments_for order
|
||||
end
|
||||
|
||||
it "makes fee applicators for an order" do
|
||||
distributor = double(:distributor)
|
||||
ef1 = double(:enterprise_fee)
|
||||
ef2 = double(:enterprise_fee)
|
||||
ef3 = double(:enterprise_fee)
|
||||
incoming_exchange = double(:exchange, role: 'supplier')
|
||||
outgoing_exchange = double(:exchange, role: 'distributor')
|
||||
incoming_exchange.stub_chain(:enterprise_fees, :per_order) { [ef1] }
|
||||
outgoing_exchange.stub_chain(:enterprise_fees, :per_order) { [ef2] }
|
||||
|
||||
oc.stub(:exchanges_supplying) { [incoming_exchange, outgoing_exchange] }
|
||||
oc.stub_chain(:coordinator_fees, :per_order) { [ef3] }
|
||||
|
||||
efc = EnterpriseFeeCalculator.new(distributor, oc)
|
||||
efc.send(:per_order_enterprise_fee_applicators_for, order).should ==
|
||||
[OpenFoodNetwork::EnterpriseFeeApplicator.new(ef1, nil, 'supplier'),
|
||||
OpenFoodNetwork::EnterpriseFeeApplicator.new(ef2, nil, 'distributor'),
|
||||
OpenFoodNetwork::EnterpriseFeeApplicator.new(ef3, nil, 'coordinator')]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -368,107 +368,6 @@ describe OrderCycle do
|
||||
end
|
||||
end
|
||||
|
||||
describe "calculating fees for a variant via a particular distributor" do
|
||||
it "sums all the per-item fees for the variant in the specified hub + order cycle" do
|
||||
coordinator = create(:distributor_enterprise)
|
||||
distributor = create(:distributor_enterprise)
|
||||
order_cycle = create(:simple_order_cycle)
|
||||
enterprise_fee1 = create(:enterprise_fee, amount: 20)
|
||||
enterprise_fee2 = create(:enterprise_fee, amount: 3)
|
||||
enterprise_fee3 = create(:enterprise_fee,
|
||||
calculator: Spree::Calculator::FlatRate.new(preferred_amount: 2))
|
||||
product = create(:simple_product)
|
||||
|
||||
create(:exchange, order_cycle: order_cycle, sender: coordinator, receiver: distributor, incoming: false,
|
||||
enterprise_fees: [enterprise_fee1, enterprise_fee2, enterprise_fee3], variants: [product.master])
|
||||
|
||||
order_cycle.fees_for(product.master, distributor).should == 23
|
||||
end
|
||||
|
||||
|
||||
it "sums percentage fees for the variant" do
|
||||
coordinator = create(:distributor_enterprise)
|
||||
distributor = create(:distributor_enterprise)
|
||||
order_cycle = create(:simple_order_cycle)
|
||||
enterprise_fee1 = create(:enterprise_fee, amount: 20, fee_type: "admin", calculator: Spree::Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 20))
|
||||
product = create(:simple_product, price: 10.00)
|
||||
|
||||
create(:exchange, order_cycle: order_cycle, sender: coordinator, receiver: distributor, incoming: false,
|
||||
enterprise_fees: [enterprise_fee1], variants: [product.master])
|
||||
|
||||
product.master.price.should == 10.00
|
||||
order_cycle.fees_for(product.master, distributor).should == 2.00
|
||||
end
|
||||
end
|
||||
|
||||
describe "creating adjustments for a line item" do
|
||||
let(:oc) { OrderCycle.new }
|
||||
let(:variant) { double(:variant) }
|
||||
let(:distributor) { double(:distributor) }
|
||||
let(:order) { double(:order, distributor: distributor) }
|
||||
let(:line_item) { double(:line_item, variant: variant, order: order) }
|
||||
|
||||
it "creates an adjustment for each fee" do
|
||||
applicator = double(:enterprise_fee_applicator)
|
||||
applicator.should_receive(:create_line_item_adjustment).with(line_item)
|
||||
oc.should_receive(:per_item_enterprise_fee_applicators_for).with(variant, distributor) { [applicator] }
|
||||
|
||||
oc.send(:create_line_item_adjustments_for, line_item)
|
||||
end
|
||||
|
||||
it "makes fee applicators for a line item" do
|
||||
distributor = double(:distributor)
|
||||
ef1 = double(:enterprise_fee)
|
||||
ef2 = double(:enterprise_fee)
|
||||
ef3 = double(:enterprise_fee)
|
||||
incoming_exchange = double(:exchange, role: 'supplier')
|
||||
outgoing_exchange = double(:exchange, role: 'distributor')
|
||||
incoming_exchange.stub_chain(:enterprise_fees, :per_item) { [ef1] }
|
||||
outgoing_exchange.stub_chain(:enterprise_fees, :per_item) { [ef2] }
|
||||
|
||||
oc.stub(:exchanges_carrying) { [incoming_exchange, outgoing_exchange] }
|
||||
oc.stub_chain(:coordinator_fees, :per_item) { [ef3] }
|
||||
|
||||
oc.send(:per_item_enterprise_fee_applicators_for, line_item.variant, distributor).should ==
|
||||
[OpenFoodNetwork::EnterpriseFeeApplicator.new(ef1, line_item.variant, 'supplier'),
|
||||
OpenFoodNetwork::EnterpriseFeeApplicator.new(ef2, line_item.variant, 'distributor'),
|
||||
OpenFoodNetwork::EnterpriseFeeApplicator.new(ef3, line_item.variant, 'coordinator')]
|
||||
end
|
||||
end
|
||||
|
||||
describe "creating adjustments for an order" do
|
||||
let(:oc) { OrderCycle.new }
|
||||
let(:distributor) { double(:distributor) }
|
||||
let(:order) { double(:order, distributor: distributor) }
|
||||
|
||||
it "creates an adjustment for each fee" do
|
||||
applicator = double(:enterprise_fee_applicator)
|
||||
applicator.should_receive(:create_order_adjustment).with(order)
|
||||
oc.should_receive(:per_order_enterprise_fee_applicators_for).with(order) { [applicator] }
|
||||
|
||||
oc.send(:create_order_adjustments_for, order)
|
||||
end
|
||||
|
||||
it "makes fee applicators for an order" do
|
||||
distributor = double(:distributor)
|
||||
ef1 = double(:enterprise_fee)
|
||||
ef2 = double(:enterprise_fee)
|
||||
ef3 = double(:enterprise_fee)
|
||||
incoming_exchange = double(:exchange, role: 'supplier')
|
||||
outgoing_exchange = double(:exchange, role: 'distributor')
|
||||
incoming_exchange.stub_chain(:enterprise_fees, :per_order) { [ef1] }
|
||||
outgoing_exchange.stub_chain(:enterprise_fees, :per_order) { [ef2] }
|
||||
|
||||
oc.stub(:exchanges_supplying) { [incoming_exchange, outgoing_exchange] }
|
||||
oc.stub_chain(:coordinator_fees, :per_order) { [ef3] }
|
||||
|
||||
oc.send(:per_order_enterprise_fee_applicators_for, order).should ==
|
||||
[OpenFoodNetwork::EnterpriseFeeApplicator.new(ef1, nil, 'supplier'),
|
||||
OpenFoodNetwork::EnterpriseFeeApplicator.new(ef2, nil, 'distributor'),
|
||||
OpenFoodNetwork::EnterpriseFeeApplicator.new(ef3, nil, 'coordinator')]
|
||||
end
|
||||
end
|
||||
|
||||
describe "finding recently closed order cycles" do
|
||||
it "should give the most recently closed order cycle for a distributor" do
|
||||
distributor = create(:distributor_enterprise)
|
||||
|
||||
Reference in New Issue
Block a user