diff --git a/app/models/spree/stock/adjuster.rb b/app/models/spree/stock/adjuster.rb new file mode 100644 index 0000000000..91e38ed345 --- /dev/null +++ b/app/models/spree/stock/adjuster.rb @@ -0,0 +1,28 @@ +# Used by Prioritizer to adjust item quantities +# see prioritizer_spec for use cases +module Spree + module Stock + class Adjuster + attr_accessor :variant, :need, :status + + def initialize(variant, quantity, status) + @variant = variant + @need = quantity + @status = status + end + + def adjust(item) + if item.quantity >= need + item.quantity = need + @need = 0 + elsif item.quantity < need + @need -= item.quantity + end + end + + def fulfilled? + @need == 0 + end + end + end +end diff --git a/app/models/spree/stock/prioritizer.rb b/app/models/spree/stock/prioritizer.rb new file mode 100644 index 0000000000..cdef38e043 --- /dev/null +++ b/app/models/spree/stock/prioritizer.rb @@ -0,0 +1,47 @@ +module Spree + module Stock + class Prioritizer + attr_reader :packages, :order + + def initialize(order, packages, adjuster_class=Adjuster) + @order = order + @packages = packages + @adjuster_class = adjuster_class + end + + def prioritized_packages + sort_packages + adjust_packages + prune_packages + packages + end + + private + def adjust_packages + order.line_items.each do |line_item| + adjuster = @adjuster_class.new(line_item.variant, line_item.quantity, :on_hand) + + visit_packages(adjuster) + + adjuster.status = :backordered + visit_packages(adjuster) + end + end + + def visit_packages(adjuster) + packages.each do |package| + item = package.find_item adjuster.variant, adjuster.status + adjuster.adjust(item) if item + end + end + + def sort_packages + # order packages by preferred stock_locations + end + + def prune_packages + packages.reject! { |pkg| pkg.empty? } + end + end + end +end diff --git a/spec/models/spree/stock/prioritizer_spec.rb b/spec/models/spree/stock/prioritizer_spec.rb new file mode 100644 index 0000000000..52ce3ae97e --- /dev/null +++ b/spec/models/spree/stock/prioritizer_spec.rb @@ -0,0 +1,111 @@ +require 'spec_helper' + +module Spree + module Stock + describe Prioritizer do + let(:order) { create(:order_with_line_items, line_items_count: 2) } + let(:stock_location) { build(:stock_location) } + let(:variant1) { order.line_items[0].variant } + let(:variant2) { order.line_items[1].variant } + + def pack + package = Package.new(order, stock_location) + yield(package) if block_given? + package + end + + it 'keeps a single package' do + package1 = pack do |package| + package.add variant1, 1, :on_hand + package.add variant2, 1, :on_hand + end + + packages = [package1] + prioritizer = Prioritizer.new(order, packages) + packages = prioritizer.prioritized_packages + packages.size.should eq 1 + end + + it 'removes duplicate packages' do + package1 = pack do |package| + package.add variant1, 1, :on_hand + package.add variant2, 1, :on_hand + end + package2 = pack do |package| + package.add variant1, 1, :on_hand + package.add variant2, 1, :on_hand + end + + packages = [package1, package2] + prioritizer = Prioritizer.new(order, packages) + packages = prioritizer.prioritized_packages + packages.size.should eq 1 + end + + it 'split over 2 packages' do + package1 = pack do |package| + package.add variant1, 1, :on_hand + end + package2 = pack do |package| + package.add variant2, 1, :on_hand + end + + packages = [package1, package2] + prioritizer = Prioritizer.new(order, packages) + packages = prioritizer.prioritized_packages + packages.size.should eq 2 + end + + it '1st has some, 2nd has remaining' do + order.line_items[0].stub(:quantity => 5) + package1 = pack do |package| + package.add variant1, 2, :on_hand + end + package2 = pack do |package| + package.add variant1, 5, :on_hand + end + + packages = [package1, package2] + prioritizer = Prioritizer.new(order, packages) + packages = prioritizer.prioritized_packages + packages.count.should eq 2 + packages[0].quantity.should eq 2 + packages[1].quantity.should eq 3 + end + + it '1st has backorder, 2nd has some' do + order.line_items[0].stub(:quantity => 5) + package1 = pack do |package| + package.add variant1, 5, :backordered + end + package2 = pack do |package| + package.add variant1, 2, :on_hand + end + + packages = [package1, package2] + prioritizer = Prioritizer.new(order, packages) + packages = prioritizer.prioritized_packages + + packages[0].quantity(:backordered).should eq 3 + packages[1].quantity(:on_hand).should eq 2 + end + + it '1st has backorder, 2nd has all' do + order.line_items[0].stub(:quantity => 5) + package1 = pack do |package| + package.add variant1, 3, :backordered + package.add variant2, 1, :on_hand + end + package2 = pack do |package| + package.add variant1, 5, :on_hand + end + + packages = [package1, package2] + prioritizer = Prioritizer.new(order, packages) + packages = prioritizer.prioritized_packages + packages[0].quantity(:backordered).should eq 0 + packages[1].quantity(:on_hand).should eq 5 + end + end + end +end