Merge pull request #13087 from mkllnk/stock-location

Remove class Spree::StockLocation
This commit is contained in:
Konrad
2025-01-25 12:41:37 +01:00
committed by GitHub
57 changed files with 124 additions and 480 deletions

View File

@@ -339,7 +339,6 @@ Naming/VariableNumber:
- 'app/models/preference_sections/main_links_section.rb'
- 'lib/spree/core/controller_helpers/common.rb'
- 'spec/controllers/spree/admin/search_controller_spec.rb'
- 'spec/factories/stock_location_factory.rb'
- 'spec/models/spree/stock_item_spec.rb'
- 'spec/models/spree/tax_rate_spec.rb'
- 'spec/requests/api/orders_spec.rb'

View File

@@ -187,18 +187,17 @@ addVariantFromStockLocation = function() {
$('#stock_details').hide();
var variant_id = $('input.variant_autocomplete').val();
var stock_location_id = $(this).data('stock-location-id');
var quantity = $("input.quantity[data-stock-location-id='" + stock_location_id + "']").val();
var quantity = $("input.quantity").val();
var shipment = _.find(shipments, function(shipment){
return shipment.stock_location_id == stock_location_id && (shipment.state == 'ready' || shipment.state == 'pending');
return shipment.state == 'ready' || shipment.state == 'pending';
});
if(shipment==undefined){
$.ajax({
type: "POST",
url: Spree.url(Spree.routes.orders_api + "/" + order_number + "/shipments.json"),
data: { variant_id: variant_id, quantity: quantity, stock_location_id: stock_location_id }
data: { variant_id: variant_id, quantity: quantity }
}).done(function( msg ) {
window.location.reload();
}).error(function( msg ) {

View File

@@ -14,7 +14,7 @@ module Api
def create
variant = scoped_variant(params[:variant_id])
quantity = params[:quantity].to_i
@shipment = get_or_create_shipment(params[:stock_location_id])
@shipment = @order.shipment || @order.shipments.create
@order.contents.add(variant, quantity, @shipment)
@@ -116,10 +116,6 @@ module Api
variant
end
def get_or_create_shipment(stock_location_id)
@order.shipment || @order.shipments.create(stock_location_id:)
end
def shipment_params
return {} unless params.has_key? :shipment

View File

@@ -78,11 +78,6 @@ module VariantStock
on_demand || total_on_hand >= quantity
end
# Moving Spree::StockLocation.fill_status to the variant enables us
# to override this behaviour for variant overrides
# We can have this responsibility here in the variant because there is
# only one stock item per variant
#
# Here we depend only on variant.total_on_hand and variant.on_demand.
# This way, variant_overrides only need to override variant.total_on_hand and variant.on_demand.
def fill_status(quantity)
@@ -107,13 +102,15 @@ module VariantStock
raise_error_if_no_stock_item_available
# Creates a stock movement: it updates stock_item.count_on_hand and fills backorders
#
# This is the original Spree::StockLocation#move,
# except that we raise an error if the stock item is missing,
# because, unlike Spree, we should always have exactly one stock item per variant.
stock_item.stock_movements.create!(quantity:, originator:)
end
# There shouldn't be any other stock items, because we should
# have only one stock location.
def stock_item
stock_items.first
end
private
# Persists the single stock item associated to this variant. As defined in
@@ -139,10 +136,4 @@ module VariantStock
def overwrite_stock_levels(new_level)
stock_item.adjust_count_on_hand(new_level.to_i - stock_item.count_on_hand)
end
# There shouldn't be any other stock items, because we should
# have only one stock location.
def stock_item
stock_items.first
end
end

View File

@@ -35,18 +35,6 @@ module Spree
end
end
# This was refactored from a simpler query because the previous implementation
# lead to issues once users tried to modify the objects returned. That's due
# to ActiveRecord `joins(shipment: :stock_location)` only return readonly
# objects
#
# Returns an array of backordered inventory units as per a given stock item
def self.backordered_for_stock_item(stock_item)
backordered_per_variant(stock_item).select do |unit|
unit.shipment.stock_location == stock_item.stock_location
end
end
def self.finalize_units!(inventory_units)
inventory_units.map do |iu|
iu.update_columns(
@@ -57,8 +45,7 @@ module Spree
end
def find_stock_item
Spree::StockItem.find_by(stock_location_id: shipment.stock_location_id,
variant_id:)
Spree::StockItem.find_by(variant_id:)
end
private

View File

@@ -12,8 +12,8 @@ module Spree
# have inventory assigned via +order.create_proposed_shipment+) or when
# shipment is explicitly passed
#
# In case shipment is passed the stock location should only unstock or
# restock items if the order is completed. That is so because stock items
# In case shipment is passed stock should only be adjusted
# if the order is completed. That is so because stock items
# are always unstocked when the order is completed through +shipment.finalize+
def verify(line_item, shipment = nil)
if order.completed? || shipment.present?
@@ -60,27 +60,24 @@ module Spree
# Returns either one of the shipment:
#
# first unshipped that already includes this variant
# first unshipped that's leaving from a stock_location that stocks this variant
def determine_target_shipment(variant)
target_shipment = order.shipments.detect do |shipment|
(shipment.ready? || shipment.pending?) && shipment.contains?(variant)
end
target_shipment || order.shipments.detect do |shipment|
(shipment.ready? || shipment.pending?) &&
variant.stock_location_ids.include?(shipment.stock_location_id)
shipment.ready? || shipment.pending?
end
end
def add_to_shipment(shipment, variant, quantity)
on_hand, back_order = shipment.stock_location.fill_status(variant, quantity)
on_hand, back_order = variant.fill_status(quantity)
on_hand.times { shipment.set_up_inventory('on_hand', variant, order) }
back_order.times { shipment.set_up_inventory('backordered', variant, order) }
# adding to this shipment, and removing from stock_location
if order.completed?
shipment.stock_location.unstock(variant, quantity, shipment)
variant.move(-quantity, shipment)
end
quantity
@@ -103,9 +100,8 @@ module Spree
end
shipment.destroy if shipment.inventory_units.reload.count == 0
# removing this from shipment, and adding to stock_location
if order.completed? && restock_item
shipment.stock_location.restock variant, removed_quantity, shipment
variant.move(removed_quantity, shipment)
end
removed_quantity

View File

@@ -5,10 +5,10 @@ require 'ostruct'
module Spree
class Shipment < ApplicationRecord
self.belongs_to_required_by_default = false
self.ignored_columns += [:stock_location_id]
belongs_to :order, class_name: 'Spree::Order'
belongs_to :address, class_name: 'Spree::Address'
belongs_to :stock_location, class_name: 'Spree::StockLocation'
has_many :shipping_rates, dependent: :delete_all
has_many :shipping_methods, through: :shipping_rates
@@ -257,7 +257,7 @@ module Spree
end
def to_package
package = OrderManagement::Stock::Package.new(stock_location, order)
package = OrderManagement::Stock::Package.new(order)
grouped_inventory_units = inventory_units.includes(:variant).group_by do |iu|
[iu.variant, iu.state_name]
end
@@ -313,11 +313,11 @@ module Spree
end
def manifest_unstock(item)
stock_location.unstock item.variant, item.quantity, self
item.variant.move(-1 * item.quantity, self)
end
def manifest_restock(item)
stock_location.restock item.variant, item.quantity, self
item.variant.move(item.quantity, self)
end
def generate_shipment_number

View File

@@ -2,20 +2,21 @@
module Spree
class StockItem < ApplicationRecord
self.ignored_columns += [:stock_location_id]
acts_as_paranoid
belongs_to :stock_location, class_name: 'Spree::StockLocation', inverse_of: :stock_items
belongs_to :variant, -> { with_deleted }, class_name: 'Spree::Variant'
has_many :stock_movements, dependent: :destroy
validates :variant_id, uniqueness: { scope: [:stock_location_id, :deleted_at] }
validates :variant_id, uniqueness: { scope: [:deleted_at] }
validates :count_on_hand, numericality: { greater_than_or_equal_to: 0, unless: :backorderable? }
delegate :weight, to: :variant
delegate :name, to: :variant, prefix: true
def backordered_inventory_units
Spree::InventoryUnit.backordered_for_stock_item(self)
Spree::InventoryUnit.backordered_per_variant(self)
end
def adjust_count_on_hand(value)

View File

@@ -1,57 +0,0 @@
# frozen_string_literal: true
module Spree
class StockLocation < ApplicationRecord
self.belongs_to_required_by_default = false
self.ignored_columns += [:backorderable_default, :active]
has_many :stock_items, dependent: :delete_all, inverse_of: :stock_location
has_many :stock_movements, through: :stock_items
belongs_to :state, class_name: 'Spree::State'
belongs_to :country, class_name: 'Spree::Country'
validates :name, presence: true
after_create :create_stock_items
# Wrapper for creating a new stock item respecting the backorderable config
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:)
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)
variant.move(quantity, originator)
end
def fill_status(variant, quantity)
variant.fill_status(quantity)
end
private
def create_stock_items
Variant.find_each { |variant| stock_items.create!(variant:) }
end
end
end

View File

@@ -39,7 +39,6 @@ module Spree
has_many :line_items, inverse_of: :variant, dependent: nil
has_many :stock_items, dependent: :destroy, inverse_of: :variant
has_many :stock_locations, through: :stock_items
has_many :images, -> { order(:position) }, as: :viewable,
dependent: :destroy,
class_name: "Spree::Image"
@@ -268,9 +267,7 @@ module Spree
def create_stock_items
return unless stock_items.empty?
StockLocation.find_each do |stock_location|
stock_items.create!(stock_location:)
end
stock_items.create!
end
def update_weight_from_unit_value

View File

@@ -61,7 +61,7 @@ class ProductScopeQuery
def product_query_includes
[
image: { attachment_attachment: :blob },
variants: [:default_price, :stock_locations, :stock_items, :variant_overrides]
variants: [:default_price, :stock_items, :variant_overrides]
]
end

View File

@@ -11,7 +11,6 @@ module Api
object.variants,
each_serializer: Api::Admin::VariantSerializer,
image: thumb_url,
stock_location: Spree::StockLocation.first
)
end

View File

@@ -6,7 +6,7 @@ module Api
attributes :id, :name, :producer_name, :image, :sku, :import_date, :tax_category_id,
:options_text, :unit_value, :unit_description, :unit_to_display,
:display_as, :display_name, :name_to_display, :variant_overrides_count,
:price, :on_demand, :on_hand, :in_stock, :stock_location_id, :stock_location_name,
:price, :on_demand, :on_hand, :in_stock,
:variant_unit, :variant_unit_scale, :variant_unit_name, :variant_unit_with_scale
has_one :primary_taxon, key: :category_id, embed: :id
@@ -44,18 +44,6 @@ module Api
object.in_stock?
end
def stock_location_id
return if object.stock_items.empty?
options[:stock_location]&.id || object.stock_items.first.stock_location.id
end
def stock_location_name
return if object.stock_items.empty?
options[:stock_location]&.name || object.stock_items.first.stock_location.name
end
def variant_overrides_count
object.variant_overrides.count
end

View File

@@ -2,14 +2,10 @@
module Api
class ShipmentSerializer < ActiveModel::Serializer
attributes :id, :tracking, :number, :order_id, :cost, :shipped_at, :stock_location_name, :state
attributes :id, :tracking, :number, :order_id, :cost, :shipped_at, :state
def order_id
object.order.number
end
def stock_location_name
object.stock_location.name
end
end
end

View File

@@ -1,11 +0,0 @@
# frozen_string_literal: true
# Encapsulates the concept of default stock location that OFN has, as explained
# in https://github.com/openfoodfoundation/openfoodnetwork/wiki/Spree-Upgrade%3A-Stock-locations
class DefaultStockLocation
NAME = 'default'
def self.find_or_create
Spree::StockLocation.find_or_create_by(name: NAME)
end
end

View File

@@ -117,7 +117,7 @@ class ProductsRenderer
# rubocop:disable Rails/FindEach # .each returns an array, .find_each returns nil
distributed_products.variants_relation.
includes(:default_price, :stock_locations, :product).
includes(:default_price, :product).
where(product_id: products).
each { |v| scoper.scope(v) } # Scope results with variant_overrides
# rubocop:enable Rails/FindEach

View File

@@ -30,5 +30,5 @@
var order_number = '#{@order.number}';
var shipments = [];
- @order.shipments.each do |shipment|
shipments.push(#{shipment.to_json(:root => false, :include => [:inventory_units, :stock_location]).html_safe});
shipments.push(#{shipment.to_json(:root => false, :include => [:inventory_units]).html_safe});
= render :partial => 'spree/admin/shared/update_order_state', :handlers => [:erb], :formats => [:js]

View File

@@ -51,10 +51,10 @@
</td>
{{/if}}
<td>
<input class="quantity" id="stock_item_quantity" data-stock-location-id="{{variant.stock_location_id}}" type="number" min="1" value="1">
<input class="quantity" id="stock_item_quantity" type="number" min="1" value="1">
</td>
<td class="actions">
<button class="add_variant no-text icon-plus icon_link with-tip" data-stock-location-id="{{variant.stock_location_id}}" title="Add" data-action="add"></button>
<button class="add_variant no-text icon-plus icon_link with-tip" title="Add" data-action="add"></button>
</td>
{{else}}
<td><%= t('.out_of_stock') %></td>

View File

@@ -1,7 +1,10 @@
class SetDefaultStockLocationOnShipments < ActiveRecord::Migration[4.2]
class SpreeStockLocation < ActiveRecord::Base
end
def up
if Spree::Shipment.where('stock_location_id IS NULL').count > 0
location = DefaultStockLocation.find_or_create
location = SpreeStockLocation.find_or_create_by(name: "default")
Spree::Shipment.where('stock_location_id IS NULL').update_all(stock_location_id: location.id)
end
end

View File

@@ -0,0 +1,7 @@
# frozen_string_literal: true
class AllowNullStockLocationIdOnSpreeStockItems < ActiveRecord::Migration[7.0]
def change
change_column_null :spree_stock_items, :stock_location_id, true
end
end

View File

@@ -822,7 +822,7 @@ ActiveRecord::Schema[7.0].define(version: 2025_01_13_055412) do
end
create_table "spree_stock_items", id: :serial, force: :cascade do |t|
t.integer "stock_location_id", null: false
t.integer "stock_location_id"
t.integer "variant_id", null: false
t.integer "count_on_hand", default: 0, null: false
t.datetime "created_at", precision: nil, null: false

View File

@@ -30,5 +30,4 @@ require File.join(File.dirname(__FILE__), 'default', 'zones')
Rails.logger.info "[db:seed] Seeding Users"
require File.join(File.dirname(__FILE__), 'default', 'users')
DefaultStockLocation.find_or_create
DefaultShippingCategory.find_or_create

View File

@@ -8,7 +8,6 @@ class CatalogItemBuilder < DfcBuilder
if variant.stock_items.empty?
variant.stock_items << Spree::StockItem.new(
stock_location: DefaultStockLocation.find_or_create,
variant:,
)
end

View File

@@ -222,9 +222,6 @@ RSpec.describe SuppliedProductBuilder do
}
it "creates a new Spree::Product and variant" do
# We need this to save stock:
DefaultStockLocation.find_or_create
create(:taxon)
expect(imported_variant).to be_a(Spree::Variant)

View File

@@ -41,8 +41,7 @@ module OrderManagement
end
def build_packer(order)
stock_location = DefaultStockLocation.find_or_create
OrderManagement::Stock::Packer.new(stock_location, order)
OrderManagement::Stock::Packer.new(order)
end
end
end

View File

@@ -5,11 +5,10 @@ module OrderManagement
class Package
ContentItem = Struct.new(:variant, :quantity, :state)
attr_reader :stock_location, :order, :contents
attr_reader :order, :contents
attr_accessor :shipping_rates
def initialize(stock_location, order, contents = [])
@stock_location = stock_location
def initialize(order, contents = [])
@order = order
@contents = contents
@shipping_rates = []
@@ -94,7 +93,6 @@ module OrderManagement
def to_shipment
shipment = Spree::Shipment.new
shipment.order = order
shipment.stock_location = stock_location
shipment.shipping_rates = shipping_rates
contents.each do |item|

View File

@@ -3,22 +3,21 @@
module OrderManagement
module Stock
class Packer
attr_reader :stock_location, :order
attr_reader :order
def initialize(stock_location, order)
@stock_location = stock_location
def initialize(order)
@order = order
end
def package
package = OrderManagement::Stock::Package.new(stock_location, order)
package = OrderManagement::Stock::Package.new(order)
order.line_items.each do |line_item|
next unless stock_location.stock_item(line_item.variant)
variant = line_item.variant
next unless variant.stock_item
OpenFoodNetwork::ScopeVariantToHub.new(order.distributor).scope(variant)
on_hand, backordered = stock_location.fill_status(variant, line_item.quantity)
on_hand, backordered = variant.fill_status(line_item.quantity)
package.add variant, on_hand, :on_hand if on_hand.positive?
package.add variant, backordered, :backordered if backordered.positive?
end

View File

@@ -7,11 +7,10 @@ module OrderManagement
RSpec.describe Package do
context "base tests" do
let(:variant) { build(:variant, weight: 25.0) }
let(:stock_location) { build(:stock_location) }
let(:distributor) { create(:enterprise) }
let(:order) { build(:order, distributor:) }
subject { Package.new(stock_location, order) }
subject { Package.new(order) }
it 'calculates the weight of all the contents' do
subject.add variant, 4
@@ -80,7 +79,7 @@ module OrderManagement
Package::ContentItem.new(variant2, 1),
Package::ContentItem.new(variant3, 1)]
package = Package.new(stock_location, order, contents)
package = Package.new(order, contents)
expect(package.shipping_methods.size).to eq 2
end
@@ -96,7 +95,6 @@ module OrderManagement
shipment = subject.to_shipment
expect(shipment.order).to eq subject.order
expect(shipment.stock_location).to eq subject.stock_location
expect(shipment.inventory_units.size).to eq 3
first_unit = shipment.inventory_units.first
@@ -122,9 +120,7 @@ module OrderManagement
end
context "#shipping_methods and #shipping_categories" do
let(:stock_location) { double(:stock_location) }
subject(:package) { Package.new(stock_location, order, contents) }
subject(:package) { Package.new(order, contents) }
let(:enterprise) { create(:enterprise) }
let(:other_enterprise) { create(:enterprise) }

View File

@@ -7,9 +7,8 @@ module OrderManagement
RSpec.describe Packer do
let(:distributor) { create(:distributor_enterprise) }
let(:order) { create(:order_with_line_items, line_items_count: 5, distributor:) }
let(:stock_location) { create(:stock_location) }
subject { Packer.new(stock_location, order) }
subject { Packer.new(order) }
before { order.line_items.first.variant.update(unit_value: 100) }
@@ -21,7 +20,9 @@ module OrderManagement
end
it 'variants are added as backordered without enough on_hand' do
expect(stock_location).to receive(:fill_status).exactly(5).times.and_return([2, 3])
order.line_items.each do |item|
expect(item.variant).to receive(:fill_status).and_return([2, 3])
end
package = subject.package
expect(package.on_hand.size).to eq 5

View File

@@ -6,12 +6,11 @@ module OrderManagement
module Stock
RSpec.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)
package = Package.new(order)
yield(package) if block_given?
package
end

View File

@@ -42,10 +42,8 @@ RSpec.describe Api::V0::ProductsController, type: :controller do
api_get :show, id: product.to_param
expect(all_attributes.all?{ |attr| json_response.keys.include? attr }).to eq(true)
expect(variants_attributes.all?{ |attr|
json_response['variants'].first.keys.include? attr
} ).to eq(true)
expect(json_response.keys).to include(*all_attributes)
expect(json_response["variants"].first.keys).to include(*variants_attributes)
end
it "returns a 404 error when it cannot find a product" do

View File

@@ -6,9 +6,7 @@ RSpec.describe Api::V0::ShipmentsController, type: :controller do
render_views
let!(:shipment) { create(:shipment) }
let!(:attributes) do
[:id, :tracking, :number, :cost, :shipped_at, :stock_location_name, :order_id]
end
let(:attributes) { %w[id tracking number cost shipped_at order_id] }
let(:current_api_user) { build(:user) }
before do
@@ -31,13 +29,11 @@ RSpec.describe Api::V0::ShipmentsController, type: :controller do
let(:current_api_user) { build(:admin_user) }
let!(:order) { shipment.order }
let(:order_ship_address) { create(:address) }
let!(:stock_location) { DefaultStockLocation.find_or_create }
let!(:variant) { create(:variant) }
let(:params) do
{ quantity: 2,
variant_id: variant.to_param,
order_id: order.number,
stock_location_id: stock_location.to_param,
format: :json }
end
let(:error_message) { "broken shipments creation" }
@@ -109,7 +105,7 @@ RSpec.describe Api::V0::ShipmentsController, type: :controller do
allow_any_instance_of(Spree::Order).to receive_messages(paid?: true, complete?: true)
api_put :ready, order_id: shipment.order.to_param, id: shipment.to_param
expect(attributes.all?{ |attr| json_response.key? attr.to_s }).to be_truthy
expect(json_response.keys).to include(*attributes)
expect(json_response["state"]).to eq("ready")
expect(shipment.reload.state).to eq("ready")
end
@@ -120,7 +116,7 @@ RSpec.describe Api::V0::ShipmentsController, type: :controller do
api_put :ready, order_id: shipment.order.to_param, id: shipment.to_param
expect(attributes.all?{ |attr| json_response.key? attr.to_s }).to be_truthy
expect(json_response.keys).to include(*attributes)
expect(json_response["state"]).to eq("ready")
expect(shipment.reload.state).to eq("ready")
end
@@ -324,7 +320,7 @@ RSpec.describe Api::V0::ShipmentsController, type: :controller do
id: shipment.to_param,
shipment: { tracking: "123123" }
expect(attributes.all?{ |attr| json_response.key? attr.to_s }).to be_truthy
expect(json_response.keys).to include(*attributes)
expect(json_response["state"]).to eq("shipped")
end
end
@@ -424,7 +420,7 @@ RSpec.describe Api::V0::ShipmentsController, type: :controller do
def expect_valid_response
expect(response.status).to eq 200
attributes.all?{ |attr| json_response.key? attr.to_s }
expect(json_response.keys).to include(*attributes)
end
def make_order_contents_fail

View File

@@ -142,7 +142,6 @@ RSpec.describe Spree::Admin::ProductsController, type: :controller do
before do
controller_login_as_admin
create(:stock_location)
end
it "redirects to products when the user hits 'create'" do

View File

@@ -23,9 +23,6 @@ FactoryBot.define do
variant_unit { 'weight' }
variant_unit_scale { 1 }
# ensure stock item will be created for this products master
before(:create) { DefaultStockLocation.find_or_create }
factory :product do
transient do
on_hand { 5 }

View File

@@ -11,7 +11,6 @@ FactoryBot.define do
state { 'pending' }
order
address
stock_location { DefaultStockLocation.find_or_create }
after(:create) do |shipment, _evalulator|
shipment.add_shipping_method(create(:shipping_method), true)
@@ -31,7 +30,6 @@ FactoryBot.define do
state { 'pending' }
order
address
stock_location { DefaultStockLocation.find_or_create }
trait :shipping_method do
transient do

View File

@@ -3,12 +3,11 @@
FactoryBot.define do
factory :stock_package, class: OrderManagement::Stock::Package do
transient do
stock_location { build(:stock_location) }
order { create(:order_with_line_items, line_items_count: 2) }
contents { [] }
end
initialize_with { new(stock_location, order, contents) }
initialize_with { new(order, contents) }
factory :stock_package_fulfilled do
after(:build) do |package, evaluator|

View File

@@ -1,33 +0,0 @@
# frozen_string_literal: true
FactoryBot.define do
factory :stock_location, class: Spree::StockLocation do
# keeps the test stock_location unique
initialize_with { Spree::StockLocation.first || DefaultStockLocation.find_or_create }
address1 { '1600 Pennsylvania Ave NW' }
city { 'Washington' }
zipcode { '20500' }
phone { '(202) 456-1111' }
country { |stock_location| Spree::Country.first || stock_location.association(:country) }
state do |stock_location|
stock_location.country.states.first ||
stock_location.association(:state, country: stock_location.country)
end
factory :stock_location_with_items do
after(:create) do |stock_location, _evaluator|
# variant will add itself to all stock_locations in an after_create
# creating a product will automatically create a master variant
product_1 = create(:product)
product_2 = create(:product)
stock_location.stock_items.where(variant_id: product_1.variants.first.id)
.first.adjust_count_on_hand(10)
stock_location.stock_items.where(variant_id: product_2.variants.first.id)
.first.adjust_count_on_hand(20)
end
end
end
end

View File

@@ -28,9 +28,6 @@ FactoryBot.define do
# create a "standard variant"
product { association :base_product }
# ensure stock item will be created for this variant
before(:create) { DefaultStockLocation.find_or_create }
factory :variant do
transient do
on_demand { false }

View File

@@ -12,7 +12,6 @@ RSpec.describe 'sample_data.rake' do
before do
# Create seed data required by the sample data.
create(:user)
DefaultStockLocation.find_or_create
DefaultShippingCategory.find_or_create
end

View File

@@ -83,7 +83,6 @@ RSpec.describe VariantStock do
it 'returns false' do
variant = build_stubbed(
:variant,
stock_locations: [build_stubbed(:stock_location)]
)
expect(variant.on_demand).to be_falsy
end
@@ -94,9 +93,6 @@ RSpec.describe VariantStock do
let(:variant) do
build_stubbed(
:variant,
stock_locations: [
build_stubbed(:stock_location)
]
)
end
@@ -148,7 +144,6 @@ RSpec.describe VariantStock do
build_stubbed(
:variant,
on_demand: true,
stock_locations: [build_stubbed(:stock_location)]
)
end
let(:stock_item) { Spree::StockItem.new(backorderable: true) }
@@ -172,7 +167,6 @@ RSpec.describe VariantStock do
build_stubbed(
:variant,
on_demand: false,
stock_locations: [build_stubbed(:stock_location)]
)
end

View File

@@ -3,50 +3,8 @@
require 'spec_helper'
RSpec.describe Spree::InventoryUnit do
let(:stock_location) { create(:stock_location_with_items) }
let(:stock_item) { stock_location.stock_items.order(:id).first }
context "#backordered_for_stock_item" do
let(:order) { create(:order) }
let(:shipment) do
shipment = Spree::Shipment.new
shipment.stock_location = stock_location
shipment.shipping_methods << create(:shipping_method)
shipment.order = order
# We don't care about this in this test
allow(shipment).to receive(:ensure_correct_adjustment)
shipment.tap(&:save!)
end
let!(:unit) do
unit = shipment.inventory_units.build
unit.state = 'backordered'
unit.variant_id = stock_item.variant.id
unit.tap(&:save!)
end
# Regression for Spree #3066
it "returns modifiable objects" do
units = Spree::InventoryUnit.backordered_for_stock_item(stock_item)
expect { units.first.save! }.not_to raise_error
end
it "finds inventory units from its stock location " \
"when the unit's variant matches the stock item's variant" do
expect(Spree::InventoryUnit.backordered_for_stock_item(stock_item)).to eq [unit]
end
it "does not find inventory units that don't match the stock item's variant" do
other_variant_unit = shipment.inventory_units.build
other_variant_unit.state = 'backordered'
other_variant_unit.variant = create(:variant)
other_variant_unit.save!
expect(Spree::InventoryUnit.backordered_for_stock_item(stock_item))
.not_to include(other_variant_unit)
end
end
let(:variant) { create(:variant) }
let(:stock_item) { variant.stock_item }
context "variants deleted" do
let!(:unit) do
@@ -60,8 +18,6 @@ RSpec.describe Spree::InventoryUnit do
end
context "#finalize_units!" do
let!(:stock_location) { create(:stock_location) }
let(:variant) { create(:variant) }
let(:inventory_units) {
[
create(:inventory_unit, variant:),

View File

@@ -36,13 +36,13 @@ RSpec.describe Spree::OrderInventory do
before { allow(order).to receive_messages completed?: false }
it "doesn't unstock items" do
expect(shipment.stock_location).not_to receive(:unstock)
expect(line_item.variant).not_to receive(:move)
expect(subject.__send__(:add_to_shipment, shipment, variant, 5)).to eq 5
end
end
it 'should create inventory_units in the necessary states' do
expect(shipment.stock_location).to receive(:fill_status).with(variant, 5).and_return([3, 2])
expect(variant).to receive(:fill_status).with(5).and_return([3, 2])
expect(subject.__send__(:add_to_shipment, shipment, variant, 5)).to eq 5
@@ -55,8 +55,7 @@ RSpec.describe Spree::OrderInventory do
it 'should create stock_movement' do
expect(subject.__send__(:add_to_shipment, shipment, variant, 5)).to eq 5
stock_item = shipment.stock_location.stock_item(variant)
movement = stock_item.stock_movements.last
movement = variant.stock_item.stock_movements.last
expect(movement.quantity).to eq(-5)
end
end
@@ -88,7 +87,7 @@ RSpec.describe Spree::OrderInventory do
before { allow(order).to receive_messages completed?: false }
it "doesn't restock items" do
expect(shipment.stock_location).not_to receive(:restock)
expect(variant).not_to receive(:move)
expect(subject.__send__(:remove_from_shipment, shipment, variant, 1, true)).to eq 1
end
end
@@ -97,7 +96,7 @@ RSpec.describe Spree::OrderInventory do
before { allow(order).to receive_messages completed?: true }
it "doesn't restock items" do
expect(shipment.stock_location).not_to receive(:restock)
expect(variant).not_to receive(:move)
expect(subject.__send__(:remove_from_shipment, shipment, variant, 1, false)).to eq 1
end
end
@@ -105,8 +104,7 @@ RSpec.describe Spree::OrderInventory do
it 'should create stock_movement' do
expect(subject.__send__(:remove_from_shipment, shipment, variant, 1, true)).to eq 1
stock_item = shipment.stock_location.stock_item(variant)
movement = stock_item.stock_movements.last
movement = variant.stock_item.stock_movements.last
expect(movement.quantity).to eq 1
end

View File

@@ -130,10 +130,6 @@ module Spree
let!(:taxon){ create(:taxon) }
let(:supplier){ create(:enterprise) }
before do
create(:stock_location)
end
it "copies properties to the first standard variant" do
product.primary_taxon_id = taxon.id
product.name = "Product1"

View File

@@ -324,8 +324,7 @@ RSpec.describe Spree::Shipment do
allow(shipment).to receive_message_chain(:inventory_units,
:group_by,
map: [unit])
shipment.stock_location = build(:stock_location)
expect(shipment.stock_location).to receive(:restock).with(variant, 1, shipment)
expect(variant).to receive(:move).with(1, shipment)
shipment.after_cancel
end
end
@@ -348,8 +347,7 @@ RSpec.describe Spree::Shipment do
allow(shipment).to receive_message_chain(:inventory_units,
:group_by,
map: [unit])
shipment.stock_location = create(:stock_location)
expect(shipment.stock_location).to receive(:unstock).with(variant, 1, shipment)
expect(variant).to receive(:move).with(-1, shipment)
shipment.after_resume
end

View File

@@ -3,13 +3,9 @@
require 'spec_helper'
RSpec.describe Spree::StockItem do
let(:stock_location) { create(:stock_location_with_items) }
subject { stock_location.stock_items.order(:id).first }
subject(:stock_item) { create(:variant, on_hand: 15).stock_item }
describe "validation" do
let(:stock_item) { stock_location.stock_items.first }
it "requires count_on_hand to be positive if not backorderable" do
stock_item.backorderable = false

View File

@@ -1,104 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
module Spree
RSpec.describe StockLocation do
subject { create(:stock_location_with_items) }
let(:stock_item) { subject.stock_items.order(:id).first }
let(:variant) { stock_item.variant }
it 'creates stock_items for all variants' do
expect(subject.stock_items.count).to eq Variant.count
end
context "handling stock items" do
let!(:variant) { create(:variant) }
context "given a variant" do
context "propagate all variants" do
subject { StockLocation.new(name: "testing") }
specify do
expect(subject.stock_items).to receive(:create!).at_least(:once)
subject.save!
end
end
end
end
it 'finds a stock_item for a variant' do
stock_item = subject.stock_item(variant)
expect(stock_item.count_on_hand).to eq 15
end
it 'finds a stock_item for a variant by id' do
stock_item = subject.stock_item(variant.id)
expect(stock_item.variant).to eq variant
end
it 'returns nil when stock_item is not found for variant' do
stock_item = subject.stock_item(100)
expect(stock_item).to be_nil
end
it 'finds a count_on_hand for a variant' do
expect(subject.count_on_hand(variant)).to eq 15
end
it 'finds determines if you a variant is backorderable' do
expect(subject.backorderable?(variant)).to eq false
end
it 'restocks a variant with a positive stock movement' do
originator = double
expect(subject).to receive(:move).with(variant, 5, originator)
subject.restock(variant, 5, originator)
end
it 'unstocks a variant with a negative stock movement' do
originator = double
expect(subject).to receive(:move).with(variant, -5, originator)
subject.unstock(variant, 5, originator)
end
it 'it creates a stock_movement' do
variant.on_demand = false
expect {
subject.move variant, 5
}.to change { subject.stock_movements.where(stock_item_id: stock_item).count }.by(1)
end
context 'fill_status' do
before { variant.on_demand = false }
it 'is all on_hand if variant is on_demand' do
variant.on_demand = true
on_hand, backordered = subject.fill_status(variant, 25)
expect(on_hand).to eq 25
expect(backordered).to eq 0
end
it 'is all on_hand if on_hand is enough' do
on_hand, backordered = subject.fill_status(variant, 5)
expect(on_hand).to eq 5
expect(backordered).to eq 0
end
it 'is some on_hand if not all available' do
on_hand, backordered = subject.fill_status(variant, 20)
expect(on_hand).to eq 15
expect(backordered).to eq 0
end
it 'is zero on_hand if none available' do
variant.on_hand = 0
on_hand, backordered = subject.fill_status(variant, 20)
expect(on_hand).to eq 0
expect(backordered).to eq 0
end
end
end
end

View File

@@ -3,8 +3,7 @@
require 'spec_helper'
RSpec.describe Spree::StockMovement do
let(:stock_location) { create(:stock_location_with_items) }
let(:stock_item) { stock_location.stock_items.order(:id).first }
let(:stock_item) { create(:variant, on_hand: 15).stock_item }
subject { build(:stock_movement, stock_item:) }
it 'should belong to a stock item' do

View File

@@ -12,7 +12,6 @@ RSpec.describe Spree::Variant do
it { is_expected.to have_many(:inventory_units) }
it { is_expected.to have_many(:line_items) }
it { is_expected.to have_many(:stock_items) }
it { is_expected.to have_many(:stock_locations).through(:stock_items) }
it { is_expected.to have_many(:images) }
it { is_expected.to have_one(:default_price) }
it { is_expected.to have_many(:prices) }

View File

@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Spree::Variant do
# This method is defined in app/models/concerns/variant_stock.rb.
# These methods are defined in app/models/concerns/variant_stock.rb.
# There is a separate spec for that concern but here I want to test
# the interplay of Spree::Variant and VariantOverride.
#
@@ -11,6 +11,38 @@ RSpec.describe Spree::Variant do
# like this one get overridden. Future calls to `variant.move` are then
# handled by the ScopeVariantToHub module which may call the
# VariantOverride.
describe "#fill_status" do
subject(:variant) { create(:variant, on_hand: 15) }
it 'is all on_hand if variant is on_demand' do
variant.on_demand = true
on_hand, backordered = subject.fill_status(25)
expect(on_hand).to eq 25
expect(backordered).to eq 0
end
it 'is all on_hand if on_hand is enough' do
on_hand, backordered = subject.fill_status(5)
expect(on_hand).to eq 5
expect(backordered).to eq 0
end
it 'is some on_hand if not all available' do
on_hand, backordered = subject.fill_status(20)
expect(on_hand).to eq 15
expect(backordered).to eq 0
end
it 'is zero on_hand if none available' do
variant.on_hand = 0
on_hand, backordered = subject.fill_status(20)
expect(on_hand).to eq 0
expect(backordered).to eq 0
end
end
describe "#move" do
subject(:variant) { create(:variant, on_hand: 5) }

View File

@@ -22,10 +22,4 @@ RSpec.describe Api::Admin::VariantSerializer do
expect(serializer.to_json).to match variant.full_name
end
it "serializes the variant stock location id" do
serializer = Api::Admin::VariantSerializer.new variant
expect(serializer.to_json).to match variant.stock_items.first.stock_location.id.to_s
end
end

View File

@@ -1,39 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe DefaultStockLocation do
describe '.find_or_create' do
context 'when a location named default already exists' do
let!(:location) do
country = create(:country)
state = Spree::State.create(name: 'Alabama', country:)
Spree::StockLocation.create!(
name: 'default',
country_id: country.id,
state_id: state.id
)
end
it 'returns the location' do
expect(described_class.find_or_create).to eq(location)
end
it 'does not create any other location' do
expect { described_class.find_or_create }.not_to change { Spree::StockLocation.count }
end
end
context 'when a location named default does not exist' do
it 'returns the location' do
location = described_class.find_or_create
expect(location.name).to eq('default')
end
it 'does not create any other location' do
expect { described_class.find_or_create }
.to change { Spree::StockLocation.count }.from(0).to(1)
end
end
end
end

View File

@@ -10,7 +10,6 @@ RSpec.describe Sets::ProductSet do
subject{ product_set.save }
context 'when the product does not exist yet' do
let!(:stock_location) { create(:stock_location) }
let(:collection_hash) do
{
0 => {

View File

@@ -1100,7 +1100,7 @@ RSpec.describe '
end
it "the user can confirm : line item is then deleted and order is canceled" do
expect_any_instance_of(Spree::StockLocation).to receive(:restock).at_least(1).times
expect_any_instance_of(Spree::Variant).to receive(:move).at_least(1).times
expect do
within(".modal") do
uncheck("send_cancellation_email")
@@ -1113,7 +1113,7 @@ RSpec.describe '
it "the user can confirm + wants to send email confirmation : line item is " \
"then deleted, order is canceled and email is sent" do
expect_any_instance_of(Spree::StockLocation).to receive(:restock).at_least(1).times
expect_any_instance_of(Spree::Variant).to receive(:move).at_least(1).times
expect do
within(".modal") do
check("send_cancellation_email")
@@ -1126,7 +1126,7 @@ RSpec.describe '
it "the user can confirm + uncheck the restock option: line item is then deleted and " \
"order is canceled without retocking" do
expect_any_instance_of(Spree::StockLocation).not_to receive(:restock)
expect_any_instance_of(Spree::Variant).not_to receive(:move)
expect do
within(".modal") do
uncheck("Restock Items: return all items to stock")

View File

@@ -218,7 +218,6 @@ RSpec.describe '
let(:shipment) { order.shipments.first }
it "and by default an Email is sent and the items are restocked" do
expect_any_instance_of(Spree::StockLocation).to receive(:restock).at_least(1).times
expect do
within(".modal") do
click_on("OK")
@@ -226,10 +225,10 @@ RSpec.describe '
expect(page).to have_content "Cannot add item to canceled order"
expect(order.reload.state).to eq("canceled")
end.to have_enqueued_mail(Spree::OrderMailer, :cancel_email)
.and change { Spree::StockItem.pluck(:count_on_hand) }
end
it "and then the order is cancelled and email is not sent when unchecked" do
expect_any_instance_of(Spree::StockLocation).to receive(:restock).at_least(1).times
expect do
within(".modal") do
uncheck("send_cancellation_email")
@@ -237,11 +236,12 @@ RSpec.describe '
end
expect(page).to have_content "Cannot add item to canceled order"
expect(order.reload.state).to eq("canceled")
end.not_to have_enqueued_mail(Spree::OrderMailer, :cancel_email)
end.to have_enqueued_mail(Spree::OrderMailer, :cancel_email).at_most(0).times
.and change { Spree::StockItem.pluck(:count_on_hand) }
end
it "and the items are not restocked when the user uncheck the checkbox to restock items" do
expect_any_instance_of(Spree::StockLocation).not_to receive(:restock)
expect_any_instance_of(Spree::Variant).not_to receive(:move)
expect do
within(".modal") do
uncheck("restock_items")
@@ -250,6 +250,8 @@ RSpec.describe '
expect(page).to have_content "Cannot add item to canceled order"
expect(order.reload.state).to eq("canceled")
end.to have_enqueued_mail(Spree::OrderMailer, :cancel_email)
# Not change stock. Rspec can't combine `to` and `not_to` though.
.and change { Spree::StockItem.pluck(:count_on_hand) }.by([])
end
end
end

View File

@@ -11,7 +11,6 @@ RSpec.describe '
include FileHelper
let!(:taxon) { create(:taxon) }
let!(:stock_location) { create(:stock_location) }
let!(:shipping_category) { DefaultShippingCategory.find_or_create }
let!(:supplier) { create(:supplier_enterprise, name: 'New supplier') }

View File

@@ -13,7 +13,6 @@ RSpec.describe 'As an enterprise user, I can manage my products' do
let!(:taxon) { create(:taxon) }
describe "creating a new product" do
let!(:stock_location) { create(:stock_location) }
let!(:distributor) { create(:distributor_enterprise) }
let!(:shipping_category) { create(:shipping_category) }

View File

@@ -9,8 +9,6 @@ RSpec.describe '
include AuthenticationHelper
include WebHelper
let!(:stock_location) { create(:stock_location) }
describe "product" do
it "creating a new product" do
login_as_admin