solidus_reserved_stock 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.rubocop.yml +21 -0
- data/.ruby-gemset +1 -0
- data/.travis.yml +4 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +21 -0
- data/LICENSE.txt +21 -0
- data/README.md +74 -0
- data/Rakefile +26 -0
- data/app/controllers/spree/admin/reserved_stock_items_controller.rb +88 -0
- data/app/controllers/spree/admin/stock_items_controller_decorator.rb +7 -0
- data/app/controllers/spree/admin/users_controller_decorator.rb +16 -0
- data/app/controllers/spree/api/products_controller_decorator.rb +12 -0
- data/app/controllers/spree/api/v1/reserved_stock_items_controller.rb +88 -0
- data/app/controllers/spree/api/v1/validators/reserve_stock_params_validator.rb +37 -0
- data/app/controllers/spree/api/variants_controller_decorator.rb +12 -0
- data/app/helpers/spree/admin/reserved_stock_items_helper.rb +13 -0
- data/app/helpers/spree/admin/stock_locations_helper_decorator.rb +14 -0
- data/app/helpers/spree/api/api_helpers_decorator.rb +7 -0
- data/app/models/spree/product_decorator.rb +18 -0
- data/app/models/spree/reserved_stock_item.rb +48 -0
- data/app/models/spree/stock/coordinator_decorator.rb +28 -0
- data/app/models/spree/stock/prioritizer_decorator.rb +16 -0
- data/app/models/spree/stock/quantifier_decorator.rb +29 -0
- data/app/models/spree/stock/reserver.rb +60 -0
- data/app/models/spree/stock_location_decorator.rb +46 -0
- data/app/models/spree/stock_reservation_ability.rb +11 -0
- data/app/models/spree/user_decorator.rb +42 -0
- data/app/models/spree/variant_decorator.rb +6 -0
- data/app/overrides/spree/admin/stock_items/_stock_management/adjust_number_of_rows.html.erb.deface +2 -0
- data/app/overrides/spree/admin/stock_items/_stock_management/show_user_for_reserved_stock.html.erb.deface +2 -0
- data/app/overrides/spree/admin/users/_sidebar/add_reserved_stock_menu_item.html.erb.deface +6 -0
- data/app/views/spree/admin/reserved_stock_items/_form.html.erb +32 -0
- data/app/views/spree/admin/reserved_stock_items/index.html.erb +104 -0
- data/app/views/spree/admin/reserved_stock_items/new.html.erb +19 -0
- data/app/views/spree/api/products/show.v1.rabl +35 -0
- data/app/views/spree/api/v1/reserved_stock_items/index.v1.rabl +7 -0
- data/app/views/spree/api/v1/reserved_stock_items/show.v1.rabl +5 -0
- data/app/views/spree/api/variants/big.v1.rabl +20 -0
- data/app/views/spree/api/variants/small.v1.rabl +17 -0
- data/bin/console +14 -0
- data/bin/rails +12 -0
- data/bin/setup +7 -0
- data/config/i18n-tasks.yml +103 -0
- data/config/locales/en.yml +23 -0
- data/config/routes.rb +27 -0
- data/db/migrate/20160105203812_add_reserved_items_to_stock_location.rb +6 -0
- data/db/migrate/20160105222821_add_type_to_spree_stock_items.rb +7 -0
- data/db/migrate/20160106215753_add_user_id_to_spree_stock_items.rb +5 -0
- data/db/migrate/20160229223744_add_original_stock_location_id_to_spree_stock_items.rb +5 -0
- data/db/migrate/20160301003702_add_expires_at_to_spree_stock_items.rb +5 -0
- data/db/migrate/20160309025334_modify_stock_item_unique_index.rb +14 -0
- data/lib/generators/solidus_reserved_stock/install/install_generator.rb +22 -0
- data/lib/solidus_reserved_stock/ability_initializer.rb +5 -0
- data/lib/solidus_reserved_stock/engine.rb +15 -0
- data/lib/solidus_reserved_stock/version.rb +18 -0
- data/lib/solidus_reserved_stock.rb +7 -0
- data/lib/tasks/solidus_reserved_stock_tasks.rake +4 -0
- data/solidus_reserved_stock.gemspec +56 -0
- data/spec/controllers/spree/api/stock_locations_controller_spec.rb +24 -0
- data/spec/controllers/spree/api/v1/reserved_stock_items_controller_spec.rb +196 -0
- data/spec/controllers/spree/api/v1/validators/reserve_stock_params_validator_spec.rb +41 -0
- data/spec/controllers/spree/api/variants_controller_spec.rb +62 -0
- data/spec/factories/reserved_stock_item_factory.rb +8 -0
- data/spec/models/spree/reserved_stock_item_spec.rb +101 -0
- data/spec/models/spree/stock/coordinator_decorator_spec.rb +68 -0
- data/spec/models/spree/stock/prioritizer_decorator_spec.rb +29 -0
- data/spec/models/spree/stock/quantifier_decorator_spec.rb +42 -0
- data/spec/models/spree/stock/reserver_spec.rb +103 -0
- data/spec/models/spree/stock_location_decorator_spec.rb +47 -0
- data/spec/models/spree/user_decorator_spec.rb +65 -0
- data/spec/spec_helper.rb +56 -0
- data/spec/support/api_helpers.rb +35 -0
- data/spec/support/database_cleaner.rb +14 -0
- data/spec/support/have_attributes_matcher.rb +10 -0
- metadata +396 -0
@@ -0,0 +1,60 @@
|
|
1
|
+
module Spree
|
2
|
+
module Stock
|
3
|
+
class Reserver
|
4
|
+
class InvalidQuantityError < StandardError; end
|
5
|
+
|
6
|
+
def initialize(reserved_stock_location = nil)
|
7
|
+
@reserved_stock_location = reserved_stock_location || Spree::StockLocation.reserved_items_location
|
8
|
+
end
|
9
|
+
|
10
|
+
# TODO: Use stock transfers.
|
11
|
+
# TODO: Make stock_location optional, and if not present use whatever's available
|
12
|
+
def reserve(variant, original_stock_location, user, quantity, expires_at=nil)
|
13
|
+
if quantity < 1
|
14
|
+
raise InvalidQuantityError.new(Spree.t(:quantity_must_be_positive))
|
15
|
+
end
|
16
|
+
if quantity > original_stock_location.count_on_hand(variant)
|
17
|
+
raise InvalidQuantityError.new(Spree.t(:insufficient_stock_available))
|
18
|
+
end
|
19
|
+
reserved_stock_item = user.reserved_stock_item_or_create(variant, original_stock_location, expires_at)
|
20
|
+
reserved_stock_item.stock_movements.create!(quantity: quantity)
|
21
|
+
original_stock_location.unstock(variant, quantity)
|
22
|
+
reserved_stock_item
|
23
|
+
end
|
24
|
+
|
25
|
+
# TODO: Use stock transfers
|
26
|
+
# TODO: Add locale files
|
27
|
+
def restock(variant, user, quantity=nil)
|
28
|
+
reserved_stock_item = user.reserved_stock_item(variant)
|
29
|
+
raise InvalidQuantityError.new(Spree.t(:no_stock_reserved_for_user_and_variant)) unless reserved_stock_item.present?
|
30
|
+
if quantity
|
31
|
+
if quantity < 1
|
32
|
+
raise InvalidQuantityError.new(Spree.t(:quantity_must_be_positive))
|
33
|
+
end
|
34
|
+
if quantity > reserved_stock_item.count_on_hand
|
35
|
+
raise InvalidQuantityError.new(Spree.t(:insufficient_reserved_stock_available))
|
36
|
+
end
|
37
|
+
end
|
38
|
+
quantity ||= reserved_stock_item.count_on_hand
|
39
|
+
perform_restock(reserved_stock_item, quantity)
|
40
|
+
end
|
41
|
+
|
42
|
+
def restock_expired
|
43
|
+
@reserved_stock_location.expired_stock_items.each do |reserved_stock_item|
|
44
|
+
perform_restock(reserved_stock_item, reserved_stock_item.count_on_hand)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def perform_restock(reserved_stock_item, quantity)
|
51
|
+
variant = reserved_stock_item.variant
|
52
|
+
@reserved_stock_location.unstock(variant, quantity)
|
53
|
+
reserved_stock_item.original_stock_location.move(variant, quantity)
|
54
|
+
reserved_stock_item.reload
|
55
|
+
reserved_stock_item.destroy if reserved_stock_item.count_on_hand == 0
|
56
|
+
reserved_stock_item
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# Validates that the reserved stock location does not have backorderable_default
|
2
|
+
class NotBackOrderableDefaultValidator < ActiveModel::Validator
|
3
|
+
def validate(record)
|
4
|
+
return unless record.reserved_items && record.backorderable_default?
|
5
|
+
record.errors[:backorderable_default] << "backorderable_default must be false for reserved stock locations"
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
# Validates that the reserved stock location does not have propagate_all_variants
|
10
|
+
class NotPropagateAllVariantsValidator < ActiveModel::Validator
|
11
|
+
def validate(record)
|
12
|
+
return unless record.reserved_items && record.propagate_all_variants?
|
13
|
+
record.errors[:propagate_all_variants] << "propagate_all_variants must be false for reserved stock locations"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Class methods to handle stock locations that contain reserved items
|
18
|
+
# Typically I'd expect there to be only one stock location for reserved items,
|
19
|
+
# but at this stage we're not enforcing that.
|
20
|
+
module Spree
|
21
|
+
StockLocation.class_eval do
|
22
|
+
validates_with NotBackOrderableDefaultValidator
|
23
|
+
validates_with NotPropagateAllVariantsValidator
|
24
|
+
|
25
|
+
scope :reserved_items, -> { where(reserved_items: true) }
|
26
|
+
scope :not_reserved_items, -> { where(reserved_items: false) }
|
27
|
+
|
28
|
+
def self.reserved_items_location
|
29
|
+
return reserved_items.first if reserved_items.any?
|
30
|
+
Spree::StockLocation.create(
|
31
|
+
name: "Reserved Items",
|
32
|
+
reserved_items: true,
|
33
|
+
propagate_all_variants: false,
|
34
|
+
backorderable_default: false
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
def expired_stock_items
|
39
|
+
reserved_stock_items.where("expires_at < ?", Time.zone.now)
|
40
|
+
end
|
41
|
+
|
42
|
+
def reserved_stock_items
|
43
|
+
stock_items.where(type: Spree::ReservedStockItem)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
Spree.user_class.class_eval do
|
2
|
+
has_many :reserved_stock_items, foreign_key: :user_id
|
3
|
+
|
4
|
+
before_destroy :restock_all_reserved_items!
|
5
|
+
|
6
|
+
def reserved_count_on_hand(variant)
|
7
|
+
reserved_stock_item(variant).try(:count_on_hand) || 0
|
8
|
+
end
|
9
|
+
|
10
|
+
def reserved_stock_item(variant)
|
11
|
+
reserved_stock_location
|
12
|
+
.stock_items
|
13
|
+
.where(variant: variant)
|
14
|
+
.where(user_id: id)
|
15
|
+
.order(:id)
|
16
|
+
.first
|
17
|
+
end
|
18
|
+
|
19
|
+
def reserved_stock_item_or_create(variant, original_stock_location, expires_at = nil)
|
20
|
+
return unless reserved_stock_location
|
21
|
+
reserved_stock_item(variant) || Spree::ReservedStockItem.create!(
|
22
|
+
variant: variant,
|
23
|
+
user_id: id,
|
24
|
+
stock_location: reserved_stock_location,
|
25
|
+
original_stock_location: original_stock_location,
|
26
|
+
expires_at: expires_at
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def reserved_stock_location
|
33
|
+
Spree::StockLocation.reserved_items_location
|
34
|
+
end
|
35
|
+
|
36
|
+
def restock_all_reserved_items!
|
37
|
+
reserved_stock_items.each do |reserved_stock_item|
|
38
|
+
Spree::Stock::Reserver.new.restock(reserved_stock_item.variant, self)
|
39
|
+
reserved_stock_item.destroy!
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,6 @@
|
|
1
|
+
<!-- insert_bottom "[data-hook='admin_user_tab_options']" -->
|
2
|
+
<% if can?(:manage, Spree::ReservedStockItem) %>
|
3
|
+
<li<%== ' class="active"' if current == :reserved_stock_items %>>
|
4
|
+
<%= link_to_with_icon 'cubes', Spree.t(:"admin.user.reserved_stock_items"), admin_user_reserved_stock_items_path(@user) %>
|
5
|
+
</li>
|
6
|
+
<% end %>
|
@@ -0,0 +1,32 @@
|
|
1
|
+
<div data-hook="admin_reserved_stock_item_form_fields" class="row">
|
2
|
+
<div class="alpha omega twelve columns">
|
3
|
+
<%= f.field_container :original_stock_location do %>
|
4
|
+
<%= f.label :original_stock_location, Spree.t('admin.reserved_stock_item.original_stock_location') %>
|
5
|
+
<%= f.select :original_stock_location_id, @original_stock_locations.to_a.collect{|l|[l.name.humanize, l.id]}, {include_blank: false}, {class: 'select2 fullwidth', "data-placeholder" => Spree.t('admin.reserved_stock_item.select_an_original_stock_location')} %>
|
6
|
+
<%= f.error_message_on :original_stock_location_id %>
|
7
|
+
<% end %>
|
8
|
+
</div>
|
9
|
+
|
10
|
+
<div class="alpha omega twelve columns">
|
11
|
+
<%= f.field_container :variant_id do %>
|
12
|
+
<%= f.label 'variant_id', Spree.t(:variant) %>
|
13
|
+
<%= f.select :variant_id, options_from_collection_for_select(@variants, :id, :name_and_sku), {include_blank: false}, {class: 'select2 fullwidth'} %>
|
14
|
+
<% end %>
|
15
|
+
</div>
|
16
|
+
|
17
|
+
<div class="alpha omega twelve columns">
|
18
|
+
<%= f.field_container :quantity do %>
|
19
|
+
<%= label_tag 'quantity', Spree.t(:quantity) %>
|
20
|
+
<%= number_field_tag 'reserved_stock_item[quantity]', 1, class: 'fullwidth', id: :reserved_stock_item_quantity %>
|
21
|
+
<% end %>
|
22
|
+
</div>
|
23
|
+
|
24
|
+
<div class="alpha omega twelve columns">
|
25
|
+
<%= f.field_container :expires_at do %>
|
26
|
+
<%= f.label :expires_at, Spree.t('admin.reserved_stock_item.expires_at') %>
|
27
|
+
<%= f.error_message_on :expires_at %>
|
28
|
+
<%= f.text_field :expires_at, :value => datepicker_field_value(@reserved_stock_item.expires_at), :class => 'datepicker' %>
|
29
|
+
<% end %>
|
30
|
+
</div>
|
31
|
+
|
32
|
+
</div>
|
@@ -0,0 +1,104 @@
|
|
1
|
+
<% content_for :page_title do %>
|
2
|
+
<%= link_to "#{Spree.t(:"admin.user.reserved_stock_items")} #{@user.email}", edit_admin_user_url(@user) %>
|
3
|
+
<% end %>
|
4
|
+
|
5
|
+
<% content_for :page_actions do %>
|
6
|
+
<li>
|
7
|
+
<%= link_to_with_icon 'arrow-left', Spree.t("admin.reserved_stock_item.back_to_user_list"), admin_users_path, class: 'button' %>
|
8
|
+
</li>
|
9
|
+
<% if can?([:manage], Spree::ReservedStockItem) %>
|
10
|
+
<li>
|
11
|
+
<%= button_link_to Spree.t(:new_reserved_stock_item), spree.new_admin_user_reserved_stock_item_path(@user), :icon => 'plus', :id => 'admin_new_reserved_stock_item_link' %>
|
12
|
+
</li>
|
13
|
+
<% end %>
|
14
|
+
<% end %>
|
15
|
+
|
16
|
+
|
17
|
+
<%= render :partial => 'spree/admin/users/sidebar', :locals => { :current => :reserved_stock_items } %>
|
18
|
+
|
19
|
+
<fieldset data-hook="admin_user_reserved_stock_items" class="alpha twelve columns">
|
20
|
+
<legend><%= Spree.t(:"admin.user.reserved_stock_items") %></legend>
|
21
|
+
|
22
|
+
<% if @reserved_stock_items.any? %>
|
23
|
+
<table class="stock-reserved index" id="listing_items" data-hook="stock-reserved-for-user">
|
24
|
+
<colgroup>
|
25
|
+
<col style="width: 40%" />
|
26
|
+
<col style="width: 10%" />
|
27
|
+
<col style="width: 10%" />
|
28
|
+
<col style="width: 10%" />
|
29
|
+
<col style="width: 5%" />
|
30
|
+
<col style="width: 5%" />
|
31
|
+
</colgroup>
|
32
|
+
<thead>
|
33
|
+
<tr>
|
34
|
+
<th><%= Spree.t(:item) %></th>
|
35
|
+
<th><%= Spree.t(:options) %></th>
|
36
|
+
<th><%= Spree.t(:original_stock_location) %></th>
|
37
|
+
<th><%= Spree.t(:expires_at) %></th>
|
38
|
+
<th><%= Spree.t(:count_on_hand) %></th>
|
39
|
+
</tr>
|
40
|
+
</thead>
|
41
|
+
|
42
|
+
<tbody>
|
43
|
+
<% @reserved_stock_items.each do |item| %>
|
44
|
+
<tr class="<%= cycle('odd', 'even', :name => 'stock_locations')%>" data-variant-id="<%= item.variant.id %>">
|
45
|
+
<td class="align-center no-padding">
|
46
|
+
<div class='variant-container'>
|
47
|
+
<div class='variant-image'>
|
48
|
+
<%= image_tag(item.variant.display_image(fallback: false).attachment(:small)) %>
|
49
|
+
</div>
|
50
|
+
<div class='variant-details'>
|
51
|
+
<table class='stock-variant-field-table'>
|
52
|
+
<tbody>
|
53
|
+
<% @variant_display_attributes.each do |display_attribute| %>
|
54
|
+
<tr>
|
55
|
+
<td><%= Spree.t(display_attribute[:translation_key]) %></td>
|
56
|
+
<td class="fullwidth">
|
57
|
+
<%= item.variant.send(display_attribute[:attr_name]) %>
|
58
|
+
</td>
|
59
|
+
</tr>
|
60
|
+
<% end %>
|
61
|
+
</tbody>
|
62
|
+
</table>
|
63
|
+
</div>
|
64
|
+
</div>
|
65
|
+
</td>
|
66
|
+
<td class="align-center">
|
67
|
+
<table class='stock-variant-field-table'>
|
68
|
+
<% item.variant.option_values.sort_by(&:option_type_name).each do |option_value| %>
|
69
|
+
<tr>
|
70
|
+
<td>
|
71
|
+
<%= option_value.option_type_presentation %>
|
72
|
+
</td>
|
73
|
+
<td><%= option_value.presentation %></td>
|
74
|
+
</tr>
|
75
|
+
<% end %>
|
76
|
+
</table>
|
77
|
+
</td>
|
78
|
+
<td class="align-center">
|
79
|
+
<%= item.original_stock_location.name %>
|
80
|
+
</td>
|
81
|
+
<td class="align-center">
|
82
|
+
<%= item.expires_at %>
|
83
|
+
</td>
|
84
|
+
<td class="align-center">
|
85
|
+
<span><%= item.count_on_hand %></span>
|
86
|
+
</td>
|
87
|
+
<td class="actions">
|
88
|
+
<% if can?(:manage, item) %>
|
89
|
+
<%= link_to_with_icon('undo', Spree.t(:restock), restock_admin_user_reserved_stock_item_path(@user, item), method: :post) %>
|
90
|
+
<% end %>
|
91
|
+
</td>
|
92
|
+
</tr>
|
93
|
+
<% end %>
|
94
|
+
</tbody>
|
95
|
+
</table>
|
96
|
+
<% else %>
|
97
|
+
<div class="alpha twelve columns no-objects-found">
|
98
|
+
<%= Spree.t(:no_resource_found, resource: I18n.t(:other, scope: 'activerecord.models.spree/reserved_stock_item')) %>
|
99
|
+
<% if can?([:manage], Spree::ReservedStockItem) %>
|
100
|
+
<%= link_to Spree.t("add_one"), spree.new_admin_user_reserved_stock_item_path(@user) %>!
|
101
|
+
<% end %>
|
102
|
+
</div>
|
103
|
+
<% end %>
|
104
|
+
</fieldset>
|
@@ -0,0 +1,19 @@
|
|
1
|
+
<% content_for :page_title do %>
|
2
|
+
<%= link_to "#{Spree.t(:editing_user)} #{@user.email}", edit_admin_user_url(@user) %>
|
3
|
+
<% end %>
|
4
|
+
|
5
|
+
<%= render :partial => 'spree/admin/users/sidebar', :locals => { :current => :reserved_stock_items } %>
|
6
|
+
<% content_for :page_actions do %>
|
7
|
+
<li>
|
8
|
+
<%= link_to_with_icon 'arrow-left', Spree.t("admin.reserved_stock_item.back_to_reserved_stock_item_list"), admin_user_reserved_stock_items_path(@user), class: 'button' %>
|
9
|
+
</li>
|
10
|
+
<% end %>
|
11
|
+
|
12
|
+
|
13
|
+
<%= form_for [:admin, @user, @reserved_stock_item] do |f| %>
|
14
|
+
<fieldset>
|
15
|
+
<legend align="center"><%= Spree.t("admin.reserved_stock_item.new") %></legend>
|
16
|
+
<%= render :partial => 'form', :locals => { :f => f } %>
|
17
|
+
<%= render :partial => 'spree/admin/shared/new_resource_links', :locals => { :collection_url => admin_user_reserved_stock_items_path(@user) } %>
|
18
|
+
</fieldset>
|
19
|
+
<% end %>
|
@@ -0,0 +1,35 @@
|
|
1
|
+
object @product
|
2
|
+
cache [I18n.locale, @current_user_roles.include?('admin'), current_currency, root_object]
|
3
|
+
|
4
|
+
attributes *product_attributes
|
5
|
+
|
6
|
+
node :total_on_hand do
|
7
|
+
root_object.total_on_hand(@customer)
|
8
|
+
end
|
9
|
+
|
10
|
+
node(:display_price) { |p| p.display_price.to_s }
|
11
|
+
node(:has_variants) { |p| p.has_variants? }
|
12
|
+
|
13
|
+
child :master => :master do
|
14
|
+
extends "spree/api/variants/small"
|
15
|
+
end
|
16
|
+
|
17
|
+
child :variants => :variants do
|
18
|
+
extends "spree/api/variants/small"
|
19
|
+
end
|
20
|
+
|
21
|
+
child :option_types => :option_types do
|
22
|
+
attributes *option_type_attributes
|
23
|
+
end
|
24
|
+
|
25
|
+
child :product_properties => :product_properties do
|
26
|
+
attributes *product_property_attributes
|
27
|
+
end
|
28
|
+
|
29
|
+
child :classifications => :classifications do
|
30
|
+
attributes :taxon_id, :position
|
31
|
+
|
32
|
+
child(:taxon) do
|
33
|
+
extends "spree/api/taxons/show"
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
object false
|
2
|
+
child(@reserved_stock_items => :reserved_stock_items) do
|
3
|
+
extends 'spree/api/v1/reserved_stock_items/show'
|
4
|
+
end
|
5
|
+
node(:count) { @reserved_stock_items.count }
|
6
|
+
node(:current_page) { params[:page] || 1 }
|
7
|
+
node(:pages) { @reserved_stock_items.num_pages }
|
@@ -0,0 +1,20 @@
|
|
1
|
+
object @variant
|
2
|
+
attributes *variant_attributes
|
3
|
+
|
4
|
+
cache [I18n.locale, Spree::StockLocation.accessible_by(current_ability).pluck(:id).sort.join(":"), 'big_variant', root_object]
|
5
|
+
|
6
|
+
extends "spree/api/variants/small"
|
7
|
+
|
8
|
+
node :total_on_hand do
|
9
|
+
root_object.total_on_hand(@customer)
|
10
|
+
end
|
11
|
+
|
12
|
+
child :variant_properties => :variant_properties do
|
13
|
+
attributes *variant_property_attributes
|
14
|
+
end
|
15
|
+
|
16
|
+
child(root_object.stock_items.accessible_by(current_ability) => :stock_items) do
|
17
|
+
attributes :id, :count_on_hand, :stock_location_id, :backorderable
|
18
|
+
attribute :available? => :available
|
19
|
+
node(:stock_location_name) { |si| si.stock_location.name }
|
20
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
cache [I18n.locale, 'small_variant', root_object]
|
2
|
+
|
3
|
+
attributes *variant_attributes
|
4
|
+
|
5
|
+
node(:display_price) { |p| p.display_price.to_s }
|
6
|
+
node(:options_text) { |v| v.options_text }
|
7
|
+
node(:track_inventory) { |v| v.should_track_inventory? }
|
8
|
+
node(:in_stock) { |v| v.in_stock? }
|
9
|
+
node(:is_backorderable) { |v| v.is_backorderable? }
|
10
|
+
node(:total_on_hand) { |v| v.total_on_hand(@customer) }
|
11
|
+
node(:is_destroyed) { |v| v.destroyed? }
|
12
|
+
|
13
|
+
child :option_values => :option_values do
|
14
|
+
attributes *option_value_attributes
|
15
|
+
end
|
16
|
+
|
17
|
+
child(:images => :images) { extends "spree/api/images/show" }
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "solidus_reserved_stock"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/rails
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# This command will automatically be run when you run "rails" with Rails 4 gems installed from the root of your application.
|
3
|
+
|
4
|
+
ENGINE_ROOT = File.expand_path('../..', __FILE__)
|
5
|
+
ENGINE_PATH = File.expand_path('../../lib/solidus_reserved_stock/engine', __FILE__)
|
6
|
+
|
7
|
+
# Set up gems listed in the Gemfile.
|
8
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
|
9
|
+
require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
|
10
|
+
|
11
|
+
require 'rails/all'
|
12
|
+
require 'rails/engine/commands'
|
data/bin/setup
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
# i18n-tasks finds and manages missing and unused translations: https://github.com/glebm/i18n-tasks
|
2
|
+
|
3
|
+
# The "main" locale.
|
4
|
+
base_locale: en
|
5
|
+
## All available locales are inferred from the data by default. Alternatively, specify them explicitly:
|
6
|
+
# locales: [es, fr]
|
7
|
+
## Reporting locale, default: en. Available: en, ru.
|
8
|
+
# internal_locale: en
|
9
|
+
|
10
|
+
# Read and write translations.
|
11
|
+
data:
|
12
|
+
## Translations are read from the file system. Supported format: YAML, JSON.
|
13
|
+
## Provide a custom adapter:
|
14
|
+
# adapter: I18n::Tasks::Data::FileSystem
|
15
|
+
|
16
|
+
# Locale files or `File.find` patterns where translations are read from:
|
17
|
+
read:
|
18
|
+
## Default:
|
19
|
+
# - config/locales/%{locale}.yml
|
20
|
+
## More files:
|
21
|
+
# - config/locales/**/*.%{locale}.yml
|
22
|
+
## Another gem (replace %#= with %=):
|
23
|
+
# - "<%#= %x[bundle show vagrant].chomp %>/templates/locales/%{locale}.yml"
|
24
|
+
|
25
|
+
# Locale files to write new keys to, based on a list of key pattern => file rules. Matched from top to bottom:
|
26
|
+
# `i18n-tasks normalize -p` will force move the keys according to these rules
|
27
|
+
write:
|
28
|
+
## For example, write devise and simple form keys to their respective files:
|
29
|
+
# - ['{devise, simple_form}.*', 'config/locales/\1.%{locale}.yml']
|
30
|
+
## Catch-all default:
|
31
|
+
# - config/locales/%{locale}.yml
|
32
|
+
|
33
|
+
## Specify the router (see Readme for details). Valid values: conservative_router, pattern_router, or a custom class.
|
34
|
+
# router: convervative_router
|
35
|
+
|
36
|
+
yaml:
|
37
|
+
write:
|
38
|
+
# do not wrap lines at 80 characters
|
39
|
+
line_width: -1
|
40
|
+
|
41
|
+
## Pretty-print JSON:
|
42
|
+
# json:
|
43
|
+
# write:
|
44
|
+
# indent: ' '
|
45
|
+
# space: ' '
|
46
|
+
# object_nl: "\n"
|
47
|
+
# array_nl: "\n"
|
48
|
+
|
49
|
+
# Find translate calls
|
50
|
+
search:
|
51
|
+
## Paths or `File.find` patterns to search in:
|
52
|
+
# paths:
|
53
|
+
# - app/
|
54
|
+
|
55
|
+
## Root directories for relative keys resolution.
|
56
|
+
# relative_roots:
|
57
|
+
# - app/controllers
|
58
|
+
# - app/helpers
|
59
|
+
# - app/mailers
|
60
|
+
# - app/presenters
|
61
|
+
# - app/views
|
62
|
+
|
63
|
+
## Files or `File.fnmatch` patterns to exclude from search. Some files are always excluded regardless of this setting:
|
64
|
+
## %w(*.jpg *.png *.gif *.svg *.ico *.eot *.otf *.ttf *.woff *.woff2 *.pdf *.css *.sass *.scss *.less *.yml *.json)
|
65
|
+
exclude:
|
66
|
+
- app/assets/images
|
67
|
+
- app/assets/fonts
|
68
|
+
|
69
|
+
## Alternatively, the only files or `File.fnmatch patterns` to search in `paths`:
|
70
|
+
## If specified, this settings takes priority over `exclude`, but `exclude` still applies.
|
71
|
+
# include: ["*.rb", "*.html.slim"]
|
72
|
+
|
73
|
+
## Default scanner finds t() and I18n.t() calls.
|
74
|
+
# scanner: I18n::Tasks::Scanners::PatternWithScopeScanner
|
75
|
+
|
76
|
+
## Google Translate
|
77
|
+
# translation:
|
78
|
+
# # Get an API key and set billing info at https://code.google.com/apis/console to use Google Translate
|
79
|
+
# api_key: "AbC-dEf5"
|
80
|
+
|
81
|
+
## Do not consider these keys missing:
|
82
|
+
# ignore_missing:
|
83
|
+
# - 'errors.messages.{accepted,blank,invalid,too_short,too_long}'
|
84
|
+
# - '{devise,simple_form}.*'
|
85
|
+
|
86
|
+
## Consider these keys used:
|
87
|
+
# ignore_unused:
|
88
|
+
# - 'activerecord.attributes.*'
|
89
|
+
# - '{devise,kaminari,will_paginate}.*'
|
90
|
+
# - 'simple_form.{yes,no}'
|
91
|
+
# - 'simple_form.{placeholders,hints,labels}.*'
|
92
|
+
# - 'simple_form.{error_notification,required}.:'
|
93
|
+
|
94
|
+
## Exclude these keys from the `i18n-tasks eq-base' report:
|
95
|
+
# ignore_eq_base:
|
96
|
+
# all:
|
97
|
+
# - common.ok
|
98
|
+
# fr,es:
|
99
|
+
# - common.brand
|
100
|
+
|
101
|
+
## Ignore these keys completely:
|
102
|
+
# ignore:
|
103
|
+
# - kaminari.*
|
@@ -0,0 +1,23 @@
|
|
1
|
+
en:
|
2
|
+
activerecord:
|
3
|
+
models:
|
4
|
+
spree/reserved_stock_item:
|
5
|
+
one: Reserved Stock Item
|
6
|
+
other: Reserved Stock Items
|
7
|
+
spree:
|
8
|
+
admin:
|
9
|
+
user:
|
10
|
+
reserved_stock_items: Reserved Stock Items
|
11
|
+
reserved_stock_item:
|
12
|
+
back_to_user_list: Back to user list
|
13
|
+
back_to_reserved_stock_item_list: Back to reserved stock item list
|
14
|
+
new: New reserved stock item
|
15
|
+
original_stock_location: Original stock location
|
16
|
+
select_an_original_stock_location: Select an original stock location
|
17
|
+
unable_to_create: Unable to reserve stock
|
18
|
+
quantity_must_be_positive: Quantity must be positive
|
19
|
+
no_stock_reserved_for_user_and_variant: User does not have any reserved stock for that variant
|
20
|
+
insufficient_stock_available: Insufficient stock available
|
21
|
+
insufficient_reserved_stock_available: Insufficient reserved stock available
|
22
|
+
new_reserved_stock_item: New reserved stock item
|
23
|
+
successfully_restocked: Successfully restocked
|
data/config/routes.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
Spree::Core::Engine.add_routes do
|
2
|
+
namespace :admin do
|
3
|
+
resources :reserved_stock_items, :except => [:show, :new, :edit]
|
4
|
+
resources :users do
|
5
|
+
resources :reserved_stock_items do
|
6
|
+
member do
|
7
|
+
post :restock
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
namespace :api, defaults: { format: 'json' } do
|
14
|
+
namespace :v1 do
|
15
|
+
resources :reserved_stock_items, only: :index do
|
16
|
+
collection do
|
17
|
+
post :reserve
|
18
|
+
post :restock
|
19
|
+
post :restock_expired
|
20
|
+
end
|
21
|
+
end
|
22
|
+
resources :user do
|
23
|
+
resources :reserved_stock_items, only: :index
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|