Skip to content

Commit

Permalink
[wip] 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 81b8be6 commit a8d4f56
Show file tree
Hide file tree
Showing 11 changed files with 246 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ def filters
]
end
},

{
presentation: t('.filters.shipment_state'),
combinator: 'or',
Expand Down
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
1 change: 0 additions & 1 deletion admin/app/components/solidus_admin/ui/table/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ export default class extends Controller {
showSearch(event) {
this.modeValue = "search"
this.render()
this.searchFieldTarget.focus()
}

search() {
Expand Down
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"
2 changes: 2 additions & 0 deletions admin/config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,6 @@
patch :move
end
end

resources :stock_items, only: [:index]
end
7 changes: 7 additions & 0 deletions admin/lib/solidus_admin/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,13 @@ 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
12 changes: 12 additions & 0 deletions admin/spec/features/stock_items_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# 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" do
visit "/admin/stock_items"
expect(page).to be_axe_clean
end
end
1 change: 1 addition & 0 deletions core/app/models/spree/stock_item.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class StockItem < Spree::Base
after_touch { variant.touch }

self.allowed_ransackable_attributes = ['count_on_hand', 'stock_location_id']
self.allowed_ransackable_associations = %w[variant]

# @return [Array<Spree::InventoryUnit>] the backordered inventory units
# associated with this stock item
Expand Down

0 comments on commit a8d4f56

Please sign in to comment.