diff --git a/api/app/controllers/spree/api/variants_controller.rb b/api/app/controllers/spree/api/variants_controller.rb index 6a2e5c5b370..b0a91c22c40 100644 --- a/api/app/controllers/spree/api/variants_controller.rb +++ b/api/app/controllers/spree/api/variants_controller.rb @@ -66,8 +66,13 @@ def scope end in_stock_only = ActiveRecord::Type::Boolean.new.cast(params[:in_stock_only]) + suppliable_only = ActiveRecord::Type::Boolean.new.cast(params[:suppliable_only]) variants = variants.accessible_by(current_ability, :read) - variants = variants.in_stock if in_stock_only || cannot?(:view_out_of_stock, Spree::Variant) + if in_stock_only || cannot?(:view_out_of_stock, Spree::Variant) + variants = variants.in_stock + elsif suppliable_only + variants = variants.suppliable + end variants end diff --git a/api/spec/requests/spree/api/variants_controller_spec.rb b/api/spec/requests/spree/api/variants_controller_spec.rb index 64c70293b59..a822ce97ab9 100644 --- a/api/spec/requests/spree/api/variants_controller_spec.rb +++ b/api/spec/requests/spree/api/variants_controller_spec.rb @@ -114,6 +114,32 @@ module Spree end end + context "only suplliable variants" do + subject { get spree.api_variants_path, params: { suppliable_only: "true" } } + + context "variant is backorderable" do + before do + variant.stock_items.update_all(count_on_hand: 0, backorderable: true) + end + + it "is not returned in the results" do + subject + expect(json_response["variants"].count).to eq 1 + end + end + + context "variant is unsuppliable" do + before do + variant.stock_items.update_all(count_on_hand: 0, backorderable: false) + end + + it "is returned in the results" do + subject + expect(json_response["variants"].count).to eq 0 + end + end + end + context "all variants" do subject { get spree.api_variants_path, params: { in_stock_only: "false" } } diff --git a/backend/app/assets/javascripts/spree/backend/orders/edit.js b/backend/app/assets/javascripts/spree/backend/orders/edit.js index 9a9f44199fe..72bfdc36496 100644 --- a/backend/app/assets/javascripts/spree/backend/orders/edit.js +++ b/backend/app/assets/javascripts/spree/backend/orders/edit.js @@ -1,6 +1,6 @@ Spree.ready(function () { 'use strict'; - $('[data-hook="add_product_name"]').find('.variant_autocomplete').variantAutocomplete({ in_stock_only: true }); + $('[data-hook="add_product_name"]').find('.variant_autocomplete').variantAutocomplete({ suppliable_only: true }); $("[data-hook='admin_orders_index_search']").find(".variant_autocomplete").variantAutocomplete(); }); diff --git a/backend/app/assets/javascripts/spree/backend/views/cart/line_item_row.js b/backend/app/assets/javascripts/spree/backend/views/cart/line_item_row.js index f4c457e9614..07b582ef785 100644 --- a/backend/app/assets/javascripts/spree/backend/views/cart/line_item_row.js +++ b/backend/app/assets/javascripts/spree/backend/views/cart/line_item_row.js @@ -84,6 +84,6 @@ Spree.Views.Cart.LineItemRow = Backbone.View.extend({ noCancel: this.model.isNew() && this.model.collection.length == 1 }); this.$el.html(html); - this.$("[name=variant_id]").variantAutocomplete({ in_stock_only: true }); + this.$("[name=variant_id]").variantAutocomplete({ suppliable_only: true }); } }); diff --git a/core/app/models/spree/variant.rb b/core/app/models/spree/variant.rb index 5a10249d66a..c680e31d135 100644 --- a/core/app/models/spree/variant.rb +++ b/core/app/models/spree/variant.rb @@ -92,6 +92,8 @@ class Variant < Spree::Base # a parameter, the scope is limited to variants that are in stock in the # provided stock locations. # + # If you want to also include backorderable variants see {[suppliable}} + # # @param stock_locations [Array] the stock locations to check # @return [ActiveRecord::Relation] def self.in_stock(stock_locations = nil) @@ -103,6 +105,22 @@ def self.in_stock(stock_locations = nil) in_stock_variants end + # Returns a scope of Variants which are suppliable. This includes: + # * in_stock variants + # * backorderable variants + # * variants which do not track stock + # + # @return [ActiveRecord::Relation] + def self.suppliable + return all unless Spree::Config.track_inventory_levels + arel_conditions = [ + arel_table[:track_inventory].eq(false), + Spree::StockItem.arel_table[:count_on_hand].gt(0), + Spree::StockItem.arel_table[:backorderable].eq(true) + ] + joins(:stock_items).where(arel_conditions.inject(:or)) + end + self.whitelisted_ransackable_associations = %w[option_values product prices default_price] self.whitelisted_ransackable_attributes = %w[weight sku] diff --git a/core/spec/models/spree/variant_spec.rb b/core/spec/models/spree/variant_spec.rb index 43c0db71557..d73259b2235 100644 --- a/core/spec/models/spree/variant_spec.rb +++ b/core/spec/models/spree/variant_spec.rb @@ -754,6 +754,40 @@ end end + describe ".suppliable" do + subject { Spree::Variant.suppliable } + let!(:in_stock_variant) { create(:variant) } + let!(:out_of_stock_variant) { create(:variant) } + let!(:backordered_variant) { create(:variant) } + let!(:stock_location) { create(:stock_location) } + + before do + in_stock_variant.stock_items.update_all(count_on_hand: 10) + backordered_variant.stock_items.update_all(count_on_hand: 0, backorderable: true) + out_of_stock_variant.stock_items.update_all(count_on_hand: 0, backorderable: false) + end + + it "includes the in stock variant" do + expect( subject ).to include(in_stock_variant) + end + + it "includes out of stock variant" do + expect( subject ).to include(backordered_variant) + end + + it "does not include out of stock variant" do + expect( subject ).not_to include(out_of_stock_variant) + end + + context "inventory levels globally not tracked" do + before { Spree::Config.track_inventory_levels = false } + + it "includes all variants" do + expect( subject ).to include(in_stock_variant, backordered_variant, out_of_stock_variant) + end + end + end + describe "#display_image" do subject { variant.display_image }