Skip to content

Commit df68c0c

Browse files
authored
Merge pull request #3626 from softr8/admin/improve-stock-items-management
Improving stock items management
2 parents 568f4ed + e76677e commit df68c0c

File tree

10 files changed

+127
-47
lines changed

10 files changed

+127
-47
lines changed

backend/app/controllers/spree/admin/stock_items_controller.rb

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -40,24 +40,33 @@ def load_product
4040
end
4141

4242
def load_stock_management_data
43-
@stock_locations = Spree::StockLocation.accessible_by(current_ability)
44-
@stock_item_stock_locations = params[:stock_location_id].present? ? @stock_locations.where(id: params[:stock_location_id]) : @stock_locations
43+
@stock_locations = Spree::StockLocation.accessible_by(current_ability, :read)
44+
@stock_item_stock_locations = Spree::DeprecatedInstanceVariableProxy.new(
45+
view_context,
46+
:@stock_locations,
47+
:stock_item_stock_locations,
48+
Spree::Deprecation,
49+
"Please, do not use @stock_item_stock_locations anymore in the views, use @stock_locations",
50+
)
4551
@variant_display_attributes = self.class.variant_display_attributes
46-
@variants = Spree::Config.variant_search_class.new(params[:variant_search_term], scope: variant_scope).results
47-
@variants = @variants.includes(:images, stock_items: :stock_location, product: :variant_images)
48-
@variants = @variants.includes(option_values: :option_type)
49-
@variants = @variants.order(id: :desc).page(params[:page]).per(params[:per_page] || Spree::Config[:orders_per_page])
52+
@variants = Spree::Config.variant_search_class.new(params[:variant_search_term], scope: variant_scope).results.
53+
order(id: :desc).page(params[:page]).per(params[:per_page] || Spree::Config[:orders_per_page])
5054
end
5155

5256
def variant_scope
53-
scope = Spree::Variant.accessible_by(current_ability)
54-
if @product
55-
scope = scope.where(
56-
product: @product,
57-
is_master: !@product.has_variants?
57+
scope = Spree::Variant
58+
.accessible_by(current_ability)
59+
.distinct.order(:sku)
60+
.includes(
61+
:images,
62+
stock_items: :stock_location,
63+
product: :variant_images,
64+
option_values: :option_type
5865
)
59-
end
60-
scope = scope.order(:sku)
66+
67+
scope = scope.where(product: @product, is_master: !@product.has_variants?) if @product
68+
scope = scope.by_stock_location(params[:stock_location_id]) if params[:stock_location_id].present?
69+
6170
scope
6271
end
6372

backend/app/views/spree/admin/shared/_variant_search.html.erb

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,14 @@
44
<div class="field-block col-3">
55
<div class="field">
66
<%= label_tag nil, Spree::StockLocation.model_name.human %>
7-
<%= select_tag :stock_location_id, options_from_collection_for_select(stock_locations, :id, :name, params[:stock_location_id]), { include_blank: t('spree.all'), class: 'custom-select fullwidth', "data-placeholder" => t('spree.select_a_stock_location') } %>
7+
<%= select_tag(
8+
:stock_location_id,
9+
options_from_collection_for_select(stock_locations, :id, :name, params[:stock_location_id]),
10+
include_blank: t('spree.all'),
11+
class: 'select2 fullwidth',
12+
"data-placeholder" => t('spree.select_a_stock_location'),
13+
multiple: true,
14+
) %>
815
</div>
916
</div>
1017
<div class="<%= if content_for?(:sidebar) then 'col-6' else 'col-9' end %>">

backend/app/views/spree/admin/stock_items/_stock_management.html.erb

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<% admin_layout "full-width" %>
22

3-
<%= paginate @variants, theme: "solidus_admin" %>
3+
<%= paginate variants, theme: "solidus_admin" %>
44

55
<table class="index stock-table" id="listing_product_stock">
66
<colgroup>
@@ -71,18 +71,30 @@
7171
<col style="width: 15%">
7272
</colgroup>
7373
<% variant.stock_items.each do |item| %>
74-
<% if @stock_item_stock_locations.include?(item.stock_location) %>
75-
<tr class="js-edit-stock-item stock-item-edit-row" data-variant-id="<%= variant.id %>" data-stock-item="<%= item.to_json %>" data-stock-location-name="<%= item.stock_location.name %>" data-track-inventory="<%= variant.should_track_inventory? %>" data-can-edit="<%= can?(:edit, Spree::StockItem) %>" data-variant-sku="<%= variant.sku %>">
76-
<%# This is rendered in JS %>
77-
</tr>
78-
<% end %>
74+
<tr
75+
class="js-edit-stock-item stock-item-edit-row"
76+
data-variant-id="<%= variant.id %>"
77+
data-stock-item="<%= item.to_json %>"
78+
data-stock-location-name="<%= item.stock_location.name %>"
79+
data-track-inventory="<%= variant.should_track_inventory? %>"
80+
data-can-edit="<%= can?(:admin, Spree::StockItem) %>"
81+
data-variant-sku="<%= variant.sku %>"
82+
>
83+
<%# This is rendered in JS %>
84+
</tr>
7985
<% end %>
80-
<% locations_without_items = @stock_item_stock_locations - variant.stock_items.flat_map(&:stock_location) %>
86+
<% locations_without_items = stock_locations - variant.stock_items.flat_map(&:stock_location) %>
8187
<% if locations_without_items.any? && can?(:create, Spree::StockItem) %>
8288
<tr class="js-add-stock-item stock-item-edit-row" data-variant-id="<%= variant.id %>">
8389
<form>
8490
<td class='location-name-cell'>
85-
<%= select_tag :stock_location_id, options_from_collection_for_select(locations_without_items, :id, :name), class: 'custom-select', prompt: t('spree.add_to_stock_location'), id: "variant-stock-location-#{variant.id}" %>
91+
<%= select_tag(
92+
:stock_location_id,
93+
options_from_collection_for_select(locations_without_items, :id, :name),
94+
class: 'custom-select',
95+
prompt: t('spree.add_to_stock_location'),
96+
id: "variant-stock-location-#{variant.id}",
97+
) %>
8698
</td>
8799
<td class="align-center">
88100
<%= check_box_tag :backorderable, 'backorderable', false, id: "variant-backorderable-#{variant.id}" %>
@@ -104,4 +116,4 @@
104116
<% end %>
105117
</table>
106118

107-
<%= paginate @variants, theme: "solidus_admin" %>
119+
<%= paginate variants, theme: "solidus_admin" %>

backend/app/views/spree/admin/stock_items/index.html.erb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@
1919
<% end %>
2020

2121
<% if @variants.any? %>
22-
<%= render partial: 'stock_management', locals: { variants: @variants } %>
22+
<%= render partial: 'stock_management', locals: {
23+
variants: @variants,
24+
stock_locations: @stock_locations,
25+
} %>
2326
<% else %>
2427
<div class="fullwidth no-objects-found">
2528
<%= t('spree.no_variants_found_try_again') %>

backend/spec/controllers/spree/admin/stock_items_controller_spec.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,20 @@ module Admin
7171
)
7272
end
7373
end
74+
75+
context "with specific stock location" do
76+
let(:stock_location) { variant_1.stock_locations.first }
77+
78+
before do
79+
variant_2.stock_items.delete_all
80+
end
81+
82+
it "filters variants by stock locations" do
83+
get :index, params: { stock_location_id: stock_location.id }
84+
expect(assigns(:variants)).to include variant_1
85+
expect(assigns(:variants)).not_to include variant_2
86+
end
87+
end
7488
end
7589
end
7690
end

backend/spec/features/admin/products/stock_management_spec.rb

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -73,17 +73,6 @@
7373
end
7474
end
7575

76-
context "when the admin user can't edit stock items" do
77-
custom_authorization! do |_user|
78-
cannot :edit, Spree::StockItem
79-
end
80-
81-
it "doesn't allow editing", js: true do
82-
click_link "Product Stock"
83-
expect(first("input[type='number']")).to be_disabled
84-
end
85-
end
86-
8776
def adjust_count_on_hand(variant_id, count_on_hand)
8877
within("tr#spree_variant_#{variant_id}") do
8978
find(:css, "input[type='number']").set(count_on_hand)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# frozen_string_literal: true
2+
3+
require 'spec_helper'
4+
5+
describe 'Stock Items Management', js: true do
6+
stub_authorization!
7+
8+
let(:admin_user) { create(:admin_user) }
9+
let!(:variant_1) { create(:variant) }
10+
let!(:variant_2) { create(:variant) }
11+
let!(:stock_location) { create(:stock_location_without_variant_propagation) }
12+
13+
scenario 'User can add a new stock locations to any variant' do
14+
visit spree.admin_stock_items_path
15+
within('.js-add-stock-item', match: :first) do
16+
find('[name="stock_location_id"]').select(stock_location.name)
17+
fill_in('count_on_hand', with: 10)
18+
click_on('Create')
19+
end
20+
expect(page).to have_content("Created successfully")
21+
end
22+
end

core/app/models/spree/variant/scopes.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ def self.prepended(base)
1010
order(Arel.sql("COALESCE((SELECT COUNT(*) FROM #{Spree::LineItem.quoted_table_name} GROUP BY #{Spree::LineItem.quoted_table_name}.variant_id HAVING #{Spree::LineItem.quoted_table_name}.variant_id = #{Spree::Variant.quoted_table_name}.id), 0) DESC"))
1111
}
1212

13+
scope :by_stock_location, ->(stock_location_id) {
14+
joins(:stock_locations).where(spree_stock_locations: { id: stock_location_id })
15+
}
16+
1317
class << self
1418
# Returns variants that match a given option value
1519
#

core/lib/spree/deprecation.rb

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ module Spree
3333
#
3434
# Default deprecator is <tt>Spree::Deprecation</tt>.
3535
class DeprecatedInstanceVariableProxy < ActiveSupport::Deprecation::DeprecationProxy
36-
def initialize(instance, method, var = "@#{method}", deprecator = Spree::Deprecation, message = nil)
36+
def initialize(instance, method_or_var, var = "@#{method}", deprecator = Spree::Deprecation, message = nil)
3737
@instance = instance
38-
@method = method
38+
@method_or_var = method_or_var
3939
@var = var
4040
@deprecator = deprecator
4141
@message = message
@@ -44,12 +44,14 @@ def initialize(instance, method, var = "@#{method}", deprecator = Spree::Depreca
4444
private
4545

4646
def target
47-
@instance.__send__(@method)
47+
return @instance.instance_variable_get(@method_or_var) if @instance.instance_variable_defined?(@method_or_var)
48+
49+
@instance.__send__(@method_or_var)
4850
end
4951

5052
def warn(callstack, called, args)
51-
message = @message || "#{@var} is deprecated! Call #{@method}.#{called} instead of #{@var}.#{called}."
52-
message = [message, "Args: #{args.inspect}"].join(" ")
53+
message = @message || "#{@var} is deprecated! Call #{@method_or_var}.#{called} instead of #{@var}.#{called}."
54+
message = [message, "Args: #{args.inspect}"].join(" ") unless args.empty?
5355

5456
@deprecator.warn(message, callstack)
5557
end

core/spec/models/spree/variant/scopes_spec.rb

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
let!(:variant_1) { create(:variant, product: product) }
88
let!(:variant_2) { create(:variant, product: product) }
99

10-
context ".with_prices" do
10+
describe ".with_prices" do
1111
context "when searching for the default pricing options" do
1212
it "finds all variants" do
1313
expect(Spree::Variant.with_prices).to contain_exactly(product.master, variant_1, variant_2)
@@ -44,15 +44,29 @@
4444
end
4545
end
4646

47-
it ".descend_by_popularity" do
47+
specify ".descend_by_popularity" do
4848
# Requires a product with at least two variants, where one has a higher number of
4949
# orders than the other
5050
Spree::LineItem.delete_all # FIXME leaky database - too many line_items
5151
create(:line_item, variant: variant_1)
5252
expect(Spree::Variant.descend_by_popularity.first).to eq(variant_1)
5353
end
5454

55-
context "finding by option values" do
55+
describe ".by_stock_location" do
56+
let!(:stock_location_1) { create(:stock_location) }
57+
let!(:stock_location_2) { create(:stock_location) }
58+
59+
it "finds variants by stock location" do
60+
variants = Spree::Variant.where(id: [variant_1.id, variant_2.id]) # exclude the master variant
61+
variant_1.stock_items.where.not(stock_location_id: stock_location_1.id).delete_all
62+
variant_2.stock_items.where.not(stock_location_id: stock_location_2.id).delete_all
63+
64+
expect(variants.by_stock_location(stock_location_1.id)).to contain_exactly(variant_1)
65+
expect(variants.by_stock_location(stock_location_2.id)).to contain_exactly(variant_2)
66+
end
67+
end
68+
69+
describe ".has_option" do
5670
let!(:option_type) { create(:option_type, name: "bar") }
5771
let!(:option_value_1) do
5872
option_value = create(:option_value, name: "foo", option_type: option_type)
@@ -68,26 +82,30 @@
6882

6983
let!(:product_variants) { product.variants_including_master }
7084

71-
it "by objects" do
85+
it "finds by option value objects" do
7286
variants = product_variants.has_option(option_type, option_value_1)
87+
7388
expect(variants).to include(variant_1)
7489
expect(variants).not_to include(variant_2)
7590
end
7691

77-
it "by names" do
92+
it "finds by option value names" do
7893
variants = product_variants.has_option("bar", "foo")
94+
7995
expect(variants).to include(variant_1)
8096
expect(variants).not_to include(variant_2)
8197
end
8298

83-
it "by ids" do
99+
it "finds by option value ids" do
84100
variants = product_variants.has_option(option_type.id, option_value_1.id)
101+
85102
expect(variants).to include(variant_1)
86103
expect(variants).not_to include(variant_2)
87104
end
88105

89-
it "by mixed conditions" do
106+
it "finds by option value with mixed conditions" do
90107
variants = product_variants.has_option(option_type.id, "foo", option_value_2)
108+
91109
expect(variants).to be_empty
92110
end
93111
end

0 commit comments

Comments
 (0)