mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-02-27 01:43:22 +00:00
Bring stocck movement and stock location from spree
This commit is contained in:
79
app/models/spree/stock_location.rb
Normal file
79
app/models/spree/stock_location.rb
Normal file
@@ -0,0 +1,79 @@
|
||||
module Spree
|
||||
class StockLocation < ActiveRecord::Base
|
||||
has_many :stock_items, dependent: :delete_all
|
||||
has_many :stock_movements, through: :stock_items
|
||||
|
||||
belongs_to :state, class_name: 'Spree::State'
|
||||
belongs_to :country, class_name: 'Spree::Country'
|
||||
|
||||
validates_presence_of :name
|
||||
|
||||
scope :active, -> { where(active: true) }
|
||||
|
||||
after_create :create_stock_items, :if => "self.propagate_all_variants?"
|
||||
|
||||
# Wrapper for creating a new stock item respecting the backorderable config
|
||||
def propagate_variant(variant)
|
||||
self.stock_items.create!(variant: variant, backorderable: self.backorderable_default)
|
||||
end
|
||||
|
||||
# Return either an existing stock item or create a new one. Useful in
|
||||
# scenarios where the user might not know whether there is already a stock
|
||||
# item for a given variant
|
||||
def set_up_stock_item(variant)
|
||||
self.stock_item(variant) || propagate_variant(variant)
|
||||
end
|
||||
|
||||
def stock_item(variant)
|
||||
stock_items.where(variant_id: variant).order(:id).first
|
||||
end
|
||||
|
||||
def stock_item_or_create(variant)
|
||||
stock_item(variant) || stock_items.create(variant: variant)
|
||||
end
|
||||
|
||||
def count_on_hand(variant)
|
||||
stock_item(variant).try(:count_on_hand)
|
||||
end
|
||||
|
||||
def backorderable?(variant)
|
||||
stock_item(variant).try(:backorderable?)
|
||||
end
|
||||
|
||||
def restock(variant, quantity, originator = nil)
|
||||
move(variant, quantity, originator)
|
||||
end
|
||||
|
||||
def unstock(variant, quantity, originator = nil)
|
||||
move(variant, -quantity, originator)
|
||||
end
|
||||
|
||||
def move(variant, quantity, originator = nil)
|
||||
stock_item_or_create(variant).stock_movements.create!(quantity: quantity,
|
||||
originator: originator)
|
||||
end
|
||||
|
||||
def fill_status(variant, quantity)
|
||||
if item = stock_item(variant)
|
||||
|
||||
if item.count_on_hand >= quantity
|
||||
on_hand = quantity
|
||||
backordered = 0
|
||||
else
|
||||
on_hand = item.count_on_hand
|
||||
on_hand = 0 if on_hand < 0
|
||||
backordered = item.backorderable? ? (quantity - on_hand) : 0
|
||||
end
|
||||
|
||||
[on_hand, backordered]
|
||||
else
|
||||
[0, 0]
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def create_stock_items
|
||||
Variant.find_each { |variant| self.propagate_variant(variant) }
|
||||
end
|
||||
end
|
||||
end
|
||||
25
app/models/spree/stock_movement.rb
Normal file
25
app/models/spree/stock_movement.rb
Normal file
@@ -0,0 +1,25 @@
|
||||
module Spree
|
||||
class StockMovement < ActiveRecord::Base
|
||||
belongs_to :stock_item, class_name: 'Spree::StockItem'
|
||||
belongs_to :originator, polymorphic: true
|
||||
|
||||
|
||||
after_create :update_stock_item_quantity
|
||||
|
||||
validates :stock_item, presence: true
|
||||
validates :quantity, presence: true
|
||||
|
||||
scope :recent, -> { order('created_at DESC') }
|
||||
|
||||
def readonly?
|
||||
!new_record?
|
||||
end
|
||||
|
||||
private
|
||||
def update_stock_item_quantity
|
||||
return unless Spree::Config[:track_inventory_levels]
|
||||
stock_item.adjust_count_on_hand quantity
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
205
spec/models/spree/stock_location_spec.rb
Normal file
205
spec/models/spree/stock_location_spec.rb
Normal file
@@ -0,0 +1,205 @@
|
||||
require 'spec_helper'
|
||||
|
||||
module Spree
|
||||
describe StockLocation do
|
||||
subject { create(:stock_location_with_items, backorderable_default: true) }
|
||||
let(:stock_item) { subject.stock_items.order(:id).first }
|
||||
let(:variant) { stock_item.variant }
|
||||
|
||||
it 'creates stock_items for all variants' do
|
||||
subject.stock_items.count.should eq Variant.count
|
||||
end
|
||||
|
||||
context "handling stock items" do
|
||||
let!(:variant) { create(:variant) }
|
||||
|
||||
context "given a variant" do
|
||||
subject { StockLocation.create(name: "testing", propagate_all_variants: false) }
|
||||
|
||||
context "set up" do
|
||||
it "creates stock item" do
|
||||
subject.should_receive(:propagate_variant)
|
||||
subject.set_up_stock_item(variant)
|
||||
end
|
||||
|
||||
context "stock item exists" do
|
||||
let!(:stock_item) { subject.propagate_variant(variant) }
|
||||
|
||||
it "returns existing stock item" do
|
||||
subject.set_up_stock_item(variant).should == stock_item
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "propagate variants" do
|
||||
let(:stock_item) { subject.propagate_variant(variant) }
|
||||
|
||||
it "creates a new stock item" do
|
||||
expect {
|
||||
subject.propagate_variant(variant)
|
||||
}.to change{ StockItem.count }.by(1)
|
||||
end
|
||||
|
||||
context "passes backorderable default config" do
|
||||
context "true" do
|
||||
before { subject.backorderable_default = true }
|
||||
it { stock_item.backorderable.should be_true }
|
||||
end
|
||||
|
||||
context "false" do
|
||||
before { subject.backorderable_default = false }
|
||||
it { stock_item.backorderable.should be_false }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "propagate all variants" do
|
||||
subject { StockLocation.new(name: "testing") }
|
||||
|
||||
context "true" do
|
||||
before { subject.propagate_all_variants = true }
|
||||
|
||||
specify do
|
||||
subject.should_receive(:propagate_variant).at_least(:once)
|
||||
subject.save!
|
||||
end
|
||||
end
|
||||
|
||||
context "false" do
|
||||
before { subject.propagate_all_variants = false }
|
||||
|
||||
specify do
|
||||
subject.should_not_receive(:propagate_variant)
|
||||
subject.save!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'finds a stock_item for a variant' do
|
||||
stock_item = subject.stock_item(variant)
|
||||
stock_item.count_on_hand.should eq 10
|
||||
end
|
||||
|
||||
it 'finds a stock_item for a variant by id' do
|
||||
stock_item = subject.stock_item(variant.id)
|
||||
stock_item.variant.should eq variant
|
||||
end
|
||||
|
||||
it 'returns nil when stock_item is not found for variant' do
|
||||
stock_item = subject.stock_item(100)
|
||||
stock_item.should be_nil
|
||||
end
|
||||
|
||||
it 'creates a stock_item if not found for a variant' do
|
||||
variant = create(:variant)
|
||||
variant.stock_items.destroy_all
|
||||
variant.save
|
||||
|
||||
stock_item = subject.stock_item_or_create(variant)
|
||||
stock_item.variant.should eq variant
|
||||
end
|
||||
|
||||
it 'finds a count_on_hand for a variant' do
|
||||
subject.count_on_hand(variant).should eq 10
|
||||
end
|
||||
|
||||
it 'finds determines if you a variant is backorderable' do
|
||||
subject.backorderable?(variant).should be_true
|
||||
end
|
||||
|
||||
it 'restocks a variant with a positive stock movement' do
|
||||
originator = double
|
||||
subject.should_receive(:move).with(variant, 5, originator)
|
||||
subject.restock(variant, 5, originator)
|
||||
end
|
||||
|
||||
it 'unstocks a variant with a negative stock movement' do
|
||||
originator = double
|
||||
subject.should_receive(:move).with(variant, -5, originator)
|
||||
subject.unstock(variant, 5, originator)
|
||||
end
|
||||
|
||||
it 'it creates a stock_movement' do
|
||||
expect {
|
||||
subject.move variant, 5
|
||||
}.to change { subject.stock_movements.where(stock_item_id: stock_item).count }.by(1)
|
||||
end
|
||||
|
||||
it 'can be deactivated' do
|
||||
create(:stock_location, :active => true)
|
||||
create(:stock_location, :active => false)
|
||||
Spree::StockLocation.active.count.should eq 1
|
||||
end
|
||||
|
||||
context 'fill_status' do
|
||||
it 'all on_hand with no backordered' do
|
||||
on_hand, backordered = subject.fill_status(variant, 5)
|
||||
on_hand.should eq 5
|
||||
backordered.should eq 0
|
||||
end
|
||||
|
||||
it 'some on_hand with some backordered' do
|
||||
on_hand, backordered = subject.fill_status(variant, 20)
|
||||
on_hand.should eq 10
|
||||
backordered.should eq 10
|
||||
end
|
||||
|
||||
it 'zero on_hand with all backordered' do
|
||||
zero_stock_item = mock_model(StockItem,
|
||||
count_on_hand: 0,
|
||||
backorderable?: true)
|
||||
subject.should_receive(:stock_item).with(variant).and_return(zero_stock_item)
|
||||
|
||||
on_hand, backordered = subject.fill_status(variant, 20)
|
||||
on_hand.should eq 0
|
||||
backordered.should eq 20
|
||||
end
|
||||
|
||||
context 'when backordering is not allowed' do
|
||||
before do
|
||||
@stock_item = mock_model(StockItem, backorderable?: false)
|
||||
subject.should_receive(:stock_item).with(variant).and_return(@stock_item)
|
||||
end
|
||||
|
||||
it 'all on_hand' do
|
||||
@stock_item.stub(count_on_hand: 10)
|
||||
|
||||
on_hand, backordered = subject.fill_status(variant, 5)
|
||||
on_hand.should eq 5
|
||||
backordered.should eq 0
|
||||
end
|
||||
|
||||
it 'some on_hand' do
|
||||
@stock_item.stub(count_on_hand: 10)
|
||||
|
||||
on_hand, backordered = subject.fill_status(variant, 20)
|
||||
on_hand.should eq 10
|
||||
backordered.should eq 0
|
||||
end
|
||||
|
||||
it 'zero on_hand' do
|
||||
@stock_item.stub(count_on_hand: 0)
|
||||
|
||||
on_hand, backordered = subject.fill_status(variant, 20)
|
||||
on_hand.should eq 0
|
||||
backordered.should eq 0
|
||||
end
|
||||
end
|
||||
|
||||
context 'without stock_items' do
|
||||
subject { create(:stock_location) }
|
||||
let(:variant) { create(:base_variant) }
|
||||
|
||||
it 'zero on_hand and backordered', focus: true do
|
||||
subject
|
||||
variant.stock_items.destroy_all
|
||||
on_hand, backordered = subject.fill_status(variant, 1)
|
||||
on_hand.should eq 0
|
||||
backordered.should eq 0
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
48
spec/models/spree/stock_movement_spec.rb
Normal file
48
spec/models/spree/stock_movement_spec.rb
Normal file
@@ -0,0 +1,48 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe Spree::StockMovement do
|
||||
let(:stock_location) { create(:stock_location_with_items) }
|
||||
let(:stock_item) { stock_location.stock_items.order(:id).first }
|
||||
subject { build(:stock_movement, stock_item: stock_item) }
|
||||
|
||||
it 'should belong to a stock item' do
|
||||
subject.should respond_to(:stock_item)
|
||||
end
|
||||
|
||||
it 'is readonly unless new' do
|
||||
subject.save
|
||||
expect {
|
||||
subject.save
|
||||
}.to raise_error(ActiveRecord::ReadOnlyRecord)
|
||||
end
|
||||
|
||||
it 'does not update count on hand when track inventory levels is false' do
|
||||
Spree::Config[:track_inventory_levels] = false
|
||||
subject.quantity = 1
|
||||
subject.save
|
||||
stock_item.reload
|
||||
stock_item.count_on_hand.should == 10
|
||||
end
|
||||
|
||||
context "when quantity is negative" do
|
||||
context "after save" do
|
||||
it "should decrement the stock item count on hand" do
|
||||
subject.quantity = -1
|
||||
subject.save
|
||||
stock_item.reload
|
||||
stock_item.count_on_hand.should == 9
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when quantity is positive" do
|
||||
context "after save" do
|
||||
it "should increment the stock item count on hand" do
|
||||
subject.quantity = 1
|
||||
subject.save
|
||||
stock_item.reload
|
||||
stock_item.count_on_hand.should == 11
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user