From 8c3b8c4db5b7c0145fc2e037efa30e503f08593b Mon Sep 17 00:00:00 2001 From: Luis Ramos Date: Wed, 1 Jul 2020 16:38:50 +0100 Subject: [PATCH] Bring estimator from spree_core --- app/models/spree/stock/estimator.rb | 50 +++++++++++ spec/models/spree/stock/estimator_spec.rb | 100 ++++++++++++++++++++++ 2 files changed, 150 insertions(+) create mode 100644 app/models/spree/stock/estimator.rb create mode 100644 spec/models/spree/stock/estimator_spec.rb diff --git a/app/models/spree/stock/estimator.rb b/app/models/spree/stock/estimator.rb new file mode 100644 index 0000000000..b9647f6255 --- /dev/null +++ b/app/models/spree/stock/estimator.rb @@ -0,0 +1,50 @@ +module Spree + module Stock + class Estimator + attr_reader :order, :currency + + def initialize(order) + @order = order + @currency = order.currency + end + + def shipping_rates(package, frontend_only = true) + shipping_rates = Array.new + shipping_methods = shipping_methods(package) + return [] unless shipping_methods + + shipping_methods.each do |shipping_method| + cost = calculate_cost(shipping_method, package) + shipping_rates << shipping_method.shipping_rates.new(:cost => cost) unless cost.nil? + end + + shipping_rates.sort_by! { |r| r.cost || 0 } + + unless shipping_rates.empty? + if frontend_only + shipping_rates.each do |rate| + rate.selected = true and break if rate.shipping_method.frontend? + end + else + shipping_rates.first.selected = true + end + end + + shipping_rates + end + + private + def shipping_methods(package) + shipping_methods = package.shipping_methods + shipping_methods.delete_if { |ship_method| !ship_method.calculator.available?(package) } + shipping_methods.delete_if { |ship_method| !ship_method.include?(order.ship_address) } + shipping_methods.delete_if { |ship_method| !(ship_method.calculator.preferences[:currency].nil? || ship_method.calculator.preferences[:currency] == currency) } + shipping_methods + end + + def calculate_cost(shipping_method, package) + shipping_method.calculator.compute(package) + end + end + end +end diff --git a/spec/models/spree/stock/estimator_spec.rb b/spec/models/spree/stock/estimator_spec.rb new file mode 100644 index 0000000000..e5819827a8 --- /dev/null +++ b/spec/models/spree/stock/estimator_spec.rb @@ -0,0 +1,100 @@ +require 'spec_helper' + +module Spree + module Stock + describe Estimator do + let!(:shipping_method) { create(:shipping_method) } + let(:package) { build(:stock_package_fulfilled) } + let(:order) { package.order } + subject { Estimator.new(order) } + + context "#shipping rates" do + before(:each) do + shipping_method.zones.first.members.create(:zoneable => order.ship_address.country) + ShippingMethod.any_instance.stub_chain(:calculator, :available?).and_return(true) + ShippingMethod.any_instance.stub_chain(:calculator, :compute).and_return(4.00) + ShippingMethod.any_instance.stub_chain(:calculator, :preferences).and_return({:currency => "USD"}) + ShippingMethod.any_instance.stub_chain(:calculator, :marked_for_destruction?) + + package.stub(:shipping_methods => [shipping_method]) + end + + it "returns shipping rates from a shipping method if the order's ship address is in the same zone" do + shipping_rates = subject.shipping_rates(package) + shipping_rates.first.cost.should eq 4.00 + end + + it "does not return shipping rates from a shipping method if the order's ship address is in a different zone" do + shipping_method.zones.each{|z| z.members.delete_all} + shipping_rates = subject.shipping_rates(package) + shipping_rates.should == [] + end + + it "does not return shipping rates from a shipping method if the calculator is not available for that order" do + ShippingMethod.any_instance.stub_chain(:calculator, :available?).and_return(false) + shipping_rates = subject.shipping_rates(package) + shipping_rates.should == [] + end + + it "returns shipping rates from a shipping method if the currency matches the order's currency" do + shipping_rates = subject.shipping_rates(package) + shipping_rates.first.cost.should eq 4.00 + end + + it "does not return shipping rates from a shipping method if the currency is different than the order's currency" do + order.currency = "GBP" + shipping_rates = subject.shipping_rates(package) + shipping_rates.should == [] + end + + it "sorts shipping rates by cost" do + shipping_methods = 3.times.map { create(:shipping_method) } + shipping_methods[0].stub_chain(:calculator, :compute).and_return(5.00) + shipping_methods[1].stub_chain(:calculator, :compute).and_return(3.00) + shipping_methods[2].stub_chain(:calculator, :compute).and_return(4.00) + + subject.stub(:shipping_methods).and_return(shipping_methods) + + expect(subject.shipping_rates(package).map(&:cost)).to eq %w[3.00 4.00 5.00].map(&BigDecimal.method(:new)) + end + + context "general shipping methods" do + let(:shipping_methods) { 2.times.map { create(:shipping_method) } } + + it "selects the most affordable shipping rate" do + shipping_methods[0].stub_chain(:calculator, :compute).and_return(5.00) + shipping_methods[1].stub_chain(:calculator, :compute).and_return(3.00) + + subject.stub(:shipping_methods).and_return(shipping_methods) + + expect(subject.shipping_rates(package).sort_by(&:cost).map(&:selected)).to eq [true, false] + end + + it "selects the most affordable shipping rate and doesn't raise exception over nil cost" do + shipping_methods[0].stub_chain(:calculator, :compute).and_return(1.00) + shipping_methods[1].stub_chain(:calculator, :compute).and_return(nil) + + subject.stub(:shipping_methods).and_return(shipping_methods) + + subject.shipping_rates(package) + end + end + + context "involves backend only shipping methods" do + let(:backend_method) { create(:shipping_method, display_on: "back_end") } + let(:generic_method) { create(:shipping_method) } + + # regression for #3287 + it "doesn't select backend rates even if they're more affordable" do + backend_method.stub_chain(:calculator, :compute).and_return(0.00) + generic_method.stub_chain(:calculator, :compute).and_return(5.00) + + subject.stub(:shipping_methods).and_return([backend_method, generic_method]) + + expect(subject.shipping_rates(package).map(&:selected)).to eq [false, true] + end + end + end + end + end +end