Skip to content

Commit

Permalink
Add stock_items/index component with dedicated actions
Browse files Browse the repository at this point in the history
  • Loading branch information
rainerdema committed Dec 4, 2023
1 parent b362408 commit df59704
Show file tree
Hide file tree
Showing 8 changed files with 287 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<%= page do %>
<%= page_header do %>
<%= page_header_title title %>
<% end %>

<%= render component('ui/table').new(
id: stimulus_id,
data: {
class: Spree::StockItem,
rows: @page.records,
prev: prev_page_path,
next: next_page_path,
columns: columns,
batch_actions: batch_actions,
},
search: {
name: :q,
value: params[:q],
url: solidus_admin.stock_items_path,
searchbar_key: :variant_product_name_or_variant_sku_or_variant_option_values_name_or_variant_option_values_presentation_cont,
filters: filters,
scopes: scopes,
},
) %>
<% end %>
157 changes: 157 additions & 0 deletions admin/app/components/solidus_admin/stock_items/index/component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
# frozen_string_literal: true

class SolidusAdmin::StockItems::Index::Component < SolidusAdmin::BaseComponent
include SolidusAdmin::Layout::PageHelpers

def initialize(page:)
@page = page
end

def title
Spree::StockItem.model_name.human.pluralize
end

def prev_page_path
solidus_admin.url_for(**request.params, page: @page.number - 1, only_path: true) unless @page.first?
end

def next_page_path
solidus_admin.url_for(**request.params, page: @page.next_param, only_path: true) unless @page.last?
end

def batch_actions
[]
end

def scopes
[
{ label: t('.scopes.all_stock_items'), name: 'all', default: true },
{ label: t('.scopes.back_orderable'), name: 'back_orderable' },
{ label: t('.scopes.out_of_stock'), name: 'out_of_stock' },
{ label: t('.scopes.low_stock'), name: 'low_stock' },
{ label: t('.scopes.in_stock'), name: 'in_stock' },
]
end

def filters
[
{
presentation: t('.filters.stock_locations'),
combinator: 'or',
attribute: "stock_location_id",
predicate: "eq",
options: Spree::StockLocation.all.map do |stock_location|
[
stock_location.name.titleize,
stock_location.id
]
end
},
{
presentation: t('.filters.variants'),
combinator: 'or',
attribute: "variant_id",
predicate: "eq",
options: Spree::Variant.all.map do |variant|
[
variant.descriptive_name,
variant.id
]
end
},
]
end

def columns
[
image_column,
name_column,
sku_column,
variant_column,
stock_location_column,
back_orderable_column,
count_on_hand_column,
]
end

def image_column
{
col: { class: "w-[72px]" },
header: tag.span('aria-label': t('.image'), role: 'text'),
data: ->(stock_item) do
image = stock_item.variant.gallery.images.first or return

render(
component('ui/thumbnail').new(
src: image.url(:small),
alt: stock_item.variant.name
)
)
end
}
end

def name_column
{
header: :name,
data: ->(stock_item) do
content_tag :div, stock_item.variant.name
end
}
end

def sku_column
{
header: :sku,
data: ->(stock_item) do
content_tag :div, stock_item.variant.sku
end
}
end

def variant_column
{
header: :variant,
data: ->(stock_item) do
content_tag(:div, class: "space-y-0.5") do
safe_join(
stock_item.variant.option_values.sort_by(&:option_type_name).map do |option_value|
render(component('ui/badge').new(name: "#{option_value.option_type_presentation}: #{option_value.presentation}"))
end
)
end
end
}
end

def stock_location_column
{
header: :stock_location,
data: ->(stock_item) do
link_to stock_item.stock_location.name, spree.admin_stock_location_stock_movements_path(stock_item.stock_location.id, q: { variant_sku_eq: stock_item.variant.sku })
end
}
end

def back_orderable_column
{
header: :back_orderable,
data: ->(stock_item) do
if stock_item.backorderable?
component('ui/badge').new(name: t('.yes'), color: :green)
else
component('ui/badge').new(name: t('.no'), color: :graphite_light)
end
end
}
end

def count_on_hand_column
{
header: :count_on_hand,
data: ->(stock_item) do
content_tag :div, stock_item.count_on_hand
end
}
end
end
12 changes: 12 additions & 0 deletions admin/app/components/solidus_admin/stock_items/index/component.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
en:
"yes": "Yes"
"no": "No"
filters:
stock_locations: Stock Locations
variants: Variants
scopes:
all_stock_items: All
back_orderable: Back Orderable
out_of_stock: Out Of Stock
low_stock: Low Stock
in_stock: In Stock
26 changes: 26 additions & 0 deletions admin/app/controllers/solidus_admin/stock_items_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# frozen_string_literal: true

module SolidusAdmin
class StockItemsController < SolidusAdmin::BaseController
include SolidusAdmin::ControllerHelpers::Search

search_scope(:all, default: true) { _1 }
search_scope(:back_orderable) { _1.where(backorderable: true) }
search_scope(:out_of_stock) { _1.where('count_on_hand <= 0') }
search_scope(:low_stock) { _1.where('count_on_hand > 0 AND count_on_hand < ?', SolidusAdmin::Config[:low_stock_value]) }
search_scope(:in_stock) { _1.where('count_on_hand > 0') }

def index
stock_items = apply_search_to(
Spree::StockItem.order(created_at: :desc, id: :desc),
param: :q,
)

set_page_and_extract_portion_from(stock_items)

respond_to do |format|
format.html { render component('stock_items/index').new(page: @page) }
end
end
end
end
4 changes: 4 additions & 0 deletions admin/config/locales/stock_items.en.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
en:
solidus_admin:
stock_items:
title: "Stock Items"
1 change: 1 addition & 0 deletions admin/config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,5 @@
admin_resources :tax_categories, only: [:index, :destroy]
admin_resources :tax_rates, only: [:index, :destroy]
admin_resources :payment_methods, only: [:index, :destroy], sortable: true
admin_resources :stock_items, only: [:index]
end
6 changes: 6 additions & 0 deletions admin/lib/solidus_admin/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@ class Configuration < Spree::Preferences::Configuration
# meaning it will search by product name or product variants sku.
preference :product_search_key, :string, default: :name_or_variants_including_master_sku_cont

# @!attribute [rw] low_stock_value
# @return [Integer] The low stock value determines the threshold at which products are considered low in stock.
# Products with a count_on_hand less than or equal to this value will be considered low in stock.
# Default: 10
preference :low_stock_value, :integer, default: 10

preference :storefront_product_path_proc, :proc, default: ->(_version) {
->(product) { "/products/#{product.slug}" }
}
Expand Down
56 changes: 56 additions & 0 deletions admin/spec/features/stock_items_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# frozen_string_literal: true

require 'spec_helper'

describe "Stock Items", :js, type: :feature do
before { sign_in create(:admin_user, email: '[email protected]') }

it "lists stock items and allows navigating through scopes" do
non_backorderable = create(:stock_item, backorderable: false)
backorderable = create(:stock_item, backorderable: true)
out_of_stock = begin
item = create(:stock_item, backorderable: false)
item.reduce_count_on_hand_to_zero
item
end
low_stock = begin
item = create(:stock_item, backorderable: false)
item.set_count_on_hand(5)
item
end

visit "/admin/stock_items"

# `All` default scope
expect(page).to have_content(non_backorderable.variant.sku)
expect(page).to have_content(backorderable.variant.sku)
expect(page).to have_content(out_of_stock.variant.sku)
expect(page).to have_content(low_stock.variant.sku)

click_on 'Back Orderable'
expect(page).to_not have_content(non_backorderable.variant.sku)
expect(page).to have_content(backorderable.variant.sku)
expect(page).to_not have_content(out_of_stock.variant.sku)
expect(page).to_not have_content(low_stock.variant.sku)

click_on 'Out Of Stock'
expect(page).to_not have_content(non_backorderable.variant.sku)
expect(page).to_not have_content(backorderable.variant.sku)
expect(page).to have_content(out_of_stock.variant.sku)
expect(page).to_not have_content(low_stock.variant.sku)

click_on 'Low Stock'
expect(page).to_not have_content(non_backorderable.variant.sku)
expect(page).to_not have_content(backorderable.variant.sku)
expect(page).to_not have_content(out_of_stock.variant.sku)
expect(page).to have_content(low_stock.variant.sku)

click_on 'In Stock'
expect(page).to have_content(non_backorderable.variant.sku)
expect(page).to have_content(backorderable.variant.sku)
expect(page).to_not have_content(out_of_stock.variant.sku)
expect(page).to have_content(low_stock.variant.sku)

expect(page).to be_axe_clean
end
end

0 comments on commit df59704

Please sign in to comment.