From a338c974f816cdb260b435b0fb79e601e0cb7637 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 8 Aug 2013 10:38:59 +1000 Subject: [PATCH] Create line item adjustments for product distributions --- app/models/product_distribution.rb | 26 +++++ app/models/spree/order_decorator.rb | 15 +++ spec/factories.rb | 1 + spec/models/order_spec.rb | 31 ++++++ spec/models/product_distribution_spec.rb | 131 +++++++++++++++++++++++ 5 files changed, 204 insertions(+) diff --git a/app/models/product_distribution.rb b/app/models/product_distribution.rb index eefde4dd63..713f640fad 100644 --- a/app/models/product_distribution.rb +++ b/app/models/product_distribution.rb @@ -7,4 +7,30 @@ class ProductDistribution < ActiveRecord::Base validates_presence_of :product_id, :on => :update validates_presence_of :distributor_id, :shipping_method_id validates_uniqueness_of :product_id, :scope => :distributor_id + + + def ensure_correct_adjustment_for(line_item) + if enterprise_fee + adjustment = adjustment_on line_item + create_adjustment_on line_item unless adjustment + end + end + + def adjustment_on(line_item) + adjustments = line_item.adjustments.where(source_id: enterprise_fee) + + raise "Multiple adjustments for this enterprise fee on this line item. This method is not designed to deal with this scenario." if adjustments.count > 1 + + adjustments.first + end + + def create_adjustment_on(line_item) + enterprise_fee.create_adjustment(adjustment_label, line_item, enterprise_fee, true) + end + + + def adjustment_label + "Product distribution by #{distributor.name}" + end + end diff --git a/app/models/spree/order_decorator.rb b/app/models/spree/order_decorator.rb index a5f5f87c0d..f5d05b429c 100644 --- a/app/models/spree/order_decorator.rb +++ b/app/models/spree/order_decorator.rb @@ -13,6 +13,8 @@ Spree::Order.class_eval do before_save :update_line_item_shipping_methods after_create :set_default_shipping_method + register_update_hook :update_distribution_charge! + # -- Scopes scope :managed_by, lambda { |user| if user.has_spree_role?('admin') @@ -22,6 +24,7 @@ Spree::Order.class_eval do end } + # -- Methods def products_available_from_new_distribution # Check that the line_items in the current order are available from a newly selected distribution @@ -56,6 +59,13 @@ Spree::Order.class_eval do save! end + def update_distribution_charge! + line_items.each do |line_item| + pd = product_distribution_for line_item + pd.ensure_correct_adjustment_for line_item + end + end + def set_variant_attributes(variant, attributes) line_item = find_line_item_by_variant(variant) @@ -113,4 +123,9 @@ Spree::Order.class_eval do self.update! end end + + def product_distribution_for(line_item) + line_item.variant.product.product_distribution_for self.distributor + end + end diff --git a/spec/factories.rb b/spec/factories.rb index 4d91282be1..32a9cd770a 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -89,6 +89,7 @@ FactoryGirl.define do product { |pd| Spree::Product.first || FactoryGirl.create(:product) } distributor { |pd| Enterprise.is_distributor.first || FactoryGirl.create(:distributor_enterprise) } shipping_method { |pd| Spree::ShippingMethod.where("name != 'Delivery'").first || FactoryGirl.create(:shipping_method) } + enterprise_fee { |pd| FactoryGirl.create(:enterprise_fee, enterprise: pd.distributor) } end factory :itemwise_shipping_method, :parent => :shipping_method do diff --git a/spec/models/order_spec.rb b/spec/models/order_spec.rb index f9dca32470..016b23099b 100644 --- a/spec/models/order_spec.rb +++ b/spec/models/order_spec.rb @@ -28,6 +28,37 @@ describe Spree::Order do li.max_quantity.should == 3 end + describe "updating the distribution charge" do + let(:order) { build(:order) } + + it "updates distribution charge after save" do + order.should_receive(:update_distribution_charge!).at_least(:once) + order.save! + end + + it "ensures the correct adjustment(s) are created for the product distribution" do + line_item = double(:line_item) + subject.stub(:line_items) { [line_item] } + + product_distribution = double(:product_distribution) + product_distribution.should_receive(:ensure_correct_adjustment_for).with(line_item) + subject.stub(:product_distribution_for) { product_distribution } + + subject.send(:update_distribution_charge!) + end + + it "looks up product distribution enterprise fees for a line item" do + product = double(:product) + variant = double(:variant, product: product) + line_item = double(:line_item, variant: variant) + + product_distribution = double(:product_distribution) + product.should_receive(:product_distribution_for).with(subject.distributor) { product_distribution } + + subject.send(:product_distribution_for, line_item).should == product_distribution + end + end + describe "setting the distributor" do it "sets the distributor when no order cycle is set" do d = create(:distributor_enterprise) diff --git a/spec/models/product_distribution_spec.rb b/spec/models/product_distribution_spec.rb index 51b5b9a8a2..0eba225f38 100644 --- a/spec/models/product_distribution_spec.rb +++ b/spec/models/product_distribution_spec.rb @@ -20,4 +20,135 @@ describe ProductDistribution do pd2 = build(:product_distribution, :product => new_product, :distributor => new_distributor) pd2.should be_valid end + + + describe "adjusting orders" do + context "integration" do + it "creates an adjustment for product distributions" do + # Given an order + distributor = create(:distributor_enterprise) + order = create(:order, distributor: distributor) + + # And a product with a product distribution with an enterprise fee + product = create(:product) + enterprise_fee = create(:enterprise_fee, calculator: build(:calculator)) + enterprise_fee.calculator.preferred_amount = 1.23 + enterprise_fee.calculator.save! + create(:product_distribution, product: product, distributor: distributor, enterprise_fee: enterprise_fee) + + # When I add the product to the order, an adjustment should be made + expect do + op = Spree::OrderPopulator.new order, 'AU' + op.populate products: {product.id => product.master.id}, quantity: 1, distributor_id: distributor.id + end.to change(Spree::Adjustment, :count).by(1) + + # And it should have the correct data + order.reload + order.line_items.count.should == 1 + order.line_items.last.adjustments.count.should == 1 + adjustment = order.line_items.last.adjustments.last + + adjustment.source.should == enterprise_fee + adjustment.originator.should == enterprise_fee + adjustment.label.should == "Product distribution by #{distributor.name}" + adjustment.amount.should == 1.23 + + # And it should have some associated metadata + pending 'Needs metadata spec' + end + end + + describe "ensuring that a line item has the correct adjustment" do + let(:enterprise_fee) { EnterpriseFee.new } + let(:pd) { ProductDistribution.new enterprise_fee: enterprise_fee } + let(:line_item) { double(:line_item) } + let(:adjustment) { double(:adjustment) } + + # TODO: This spec will go away once enterprise_fee is required + it "does nothing if there is no enterprise fee set" do + pd.enterprise_fee = nil + pd.should_receive(:adjustment_on).never + pd.ensure_correct_adjustment_for line_item + end + + describe "adding items to cart" do + it "creates an adjustment for the new item" do + pd.stub(:adjustment_on) { nil } + pd.should_receive(:create_adjustment_on).with(line_item) + + pd.ensure_correct_adjustment_for line_item + end + + it "makes no change to the adjustment of existing items" do + pd.stub(:adjustment_on) { adjustment } + pd.should_receive(:create_adjustment_on).never + + pd.ensure_correct_adjustment_for line_item + end + end + + describe "changing distributor" do + it "clears and re-creates the adjustment on the line item" + end + end + + describe "finding our adjustment on a line item" do + it "returns nil when not present" do + line_item = build(:line_item) + pd = ProductDistribution.new + pd.send(:adjustment_on, line_item).should be_nil + end + + it "returns the adjustment when present" do + # TODO: This spec can be simplified (ie. use the default ProductDistribution factory) + # once Spree's calculators are compatible with LineItem targets, not just Orders. + distributor = create(:distributor_enterprise) + enterprise_fee = create(:enterprise_fee, enterprise: distributor, calculator: build(:calculator)) + + pd = create(:product_distribution, distributor: distributor, enterprise_fee: enterprise_fee) + line_item = create(:line_item) + adjustment = pd.enterprise_fee.create_adjustment('foo', line_item, pd.enterprise_fee, true) + + pd.send(:adjustment_on, line_item).should == adjustment + end + + it "raises an error when there are multiple adjustments for this enterprise fee" do + # TODO: This spec can be simplified (ie. use the default ProductDistribution factory) + # once Spree's calculators are compatible with LineItem targets, not just Orders. + distributor = create(:distributor_enterprise) + enterprise_fee = create(:enterprise_fee, enterprise: distributor, calculator: build(:calculator)) + + pd = create(:product_distribution, distributor: distributor, enterprise_fee: enterprise_fee) + line_item = create(:line_item) + pd.enterprise_fee.create_adjustment('one', line_item, pd.enterprise_fee, true) + pd.enterprise_fee.create_adjustment('two', line_item, pd.enterprise_fee, true) + + expect do + pd.send(:adjustment_on, line_item) + end.to raise_error "Multiple adjustments for this enterprise fee on this line item. This method is not designed to deal with this scenario." + end + end + + describe "creating an adjustment on a line item" do + it "creates the adjustment via the enterprise fee" do + # TODO: This spec can be simplified (ie. use the default ProductDistribution factory) + # once Spree's calculators are compatible with LineItem targets, not just Orders. + distributor = create(:distributor_enterprise) + enterprise_fee = create(:enterprise_fee, enterprise: distributor, calculator: build(:calculator)) + pd = create(:product_distribution, distributor: distributor, enterprise_fee: enterprise_fee) + + pd.stub(:adjustment_label) { 'label' } + line_item = create(:line_item) + + expect { pd.send(:create_adjustment_on, line_item) }.to change(Spree::Adjustment, :count).by(1) + + adjustment = Spree::Adjustment.last + adjustment.label.should == 'label' + adjustment.adjustable.should == line_item + adjustment.source.should == enterprise_fee + adjustment.originator.should == enterprise_fee + adjustment.should be_mandatory + end + end + end end