solidus_reserved_stock 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|