solidus_reserved_stock 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +21 -0
  5. data/.ruby-gemset +1 -0
  6. data/.travis.yml +4 -0
  7. data/CODE_OF_CONDUCT.md +13 -0
  8. data/Gemfile +21 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +74 -0
  11. data/Rakefile +26 -0
  12. data/app/controllers/spree/admin/reserved_stock_items_controller.rb +88 -0
  13. data/app/controllers/spree/admin/stock_items_controller_decorator.rb +7 -0
  14. data/app/controllers/spree/admin/users_controller_decorator.rb +16 -0
  15. data/app/controllers/spree/api/products_controller_decorator.rb +12 -0
  16. data/app/controllers/spree/api/v1/reserved_stock_items_controller.rb +88 -0
  17. data/app/controllers/spree/api/v1/validators/reserve_stock_params_validator.rb +37 -0
  18. data/app/controllers/spree/api/variants_controller_decorator.rb +12 -0
  19. data/app/helpers/spree/admin/reserved_stock_items_helper.rb +13 -0
  20. data/app/helpers/spree/admin/stock_locations_helper_decorator.rb +14 -0
  21. data/app/helpers/spree/api/api_helpers_decorator.rb +7 -0
  22. data/app/models/spree/product_decorator.rb +18 -0
  23. data/app/models/spree/reserved_stock_item.rb +48 -0
  24. data/app/models/spree/stock/coordinator_decorator.rb +28 -0
  25. data/app/models/spree/stock/prioritizer_decorator.rb +16 -0
  26. data/app/models/spree/stock/quantifier_decorator.rb +29 -0
  27. data/app/models/spree/stock/reserver.rb +60 -0
  28. data/app/models/spree/stock_location_decorator.rb +46 -0
  29. data/app/models/spree/stock_reservation_ability.rb +11 -0
  30. data/app/models/spree/user_decorator.rb +42 -0
  31. data/app/models/spree/variant_decorator.rb +6 -0
  32. data/app/overrides/spree/admin/stock_items/_stock_management/adjust_number_of_rows.html.erb.deface +2 -0
  33. data/app/overrides/spree/admin/stock_items/_stock_management/show_user_for_reserved_stock.html.erb.deface +2 -0
  34. data/app/overrides/spree/admin/users/_sidebar/add_reserved_stock_menu_item.html.erb.deface +6 -0
  35. data/app/views/spree/admin/reserved_stock_items/_form.html.erb +32 -0
  36. data/app/views/spree/admin/reserved_stock_items/index.html.erb +104 -0
  37. data/app/views/spree/admin/reserved_stock_items/new.html.erb +19 -0
  38. data/app/views/spree/api/products/show.v1.rabl +35 -0
  39. data/app/views/spree/api/v1/reserved_stock_items/index.v1.rabl +7 -0
  40. data/app/views/spree/api/v1/reserved_stock_items/show.v1.rabl +5 -0
  41. data/app/views/spree/api/variants/big.v1.rabl +20 -0
  42. data/app/views/spree/api/variants/small.v1.rabl +17 -0
  43. data/bin/console +14 -0
  44. data/bin/rails +12 -0
  45. data/bin/setup +7 -0
  46. data/config/i18n-tasks.yml +103 -0
  47. data/config/locales/en.yml +23 -0
  48. data/config/routes.rb +27 -0
  49. data/db/migrate/20160105203812_add_reserved_items_to_stock_location.rb +6 -0
  50. data/db/migrate/20160105222821_add_type_to_spree_stock_items.rb +7 -0
  51. data/db/migrate/20160106215753_add_user_id_to_spree_stock_items.rb +5 -0
  52. data/db/migrate/20160229223744_add_original_stock_location_id_to_spree_stock_items.rb +5 -0
  53. data/db/migrate/20160301003702_add_expires_at_to_spree_stock_items.rb +5 -0
  54. data/db/migrate/20160309025334_modify_stock_item_unique_index.rb +14 -0
  55. data/lib/generators/solidus_reserved_stock/install/install_generator.rb +22 -0
  56. data/lib/solidus_reserved_stock/ability_initializer.rb +5 -0
  57. data/lib/solidus_reserved_stock/engine.rb +15 -0
  58. data/lib/solidus_reserved_stock/version.rb +18 -0
  59. data/lib/solidus_reserved_stock.rb +7 -0
  60. data/lib/tasks/solidus_reserved_stock_tasks.rake +4 -0
  61. data/solidus_reserved_stock.gemspec +56 -0
  62. data/spec/controllers/spree/api/stock_locations_controller_spec.rb +24 -0
  63. data/spec/controllers/spree/api/v1/reserved_stock_items_controller_spec.rb +196 -0
  64. data/spec/controllers/spree/api/v1/validators/reserve_stock_params_validator_spec.rb +41 -0
  65. data/spec/controllers/spree/api/variants_controller_spec.rb +62 -0
  66. data/spec/factories/reserved_stock_item_factory.rb +8 -0
  67. data/spec/models/spree/reserved_stock_item_spec.rb +101 -0
  68. data/spec/models/spree/stock/coordinator_decorator_spec.rb +68 -0
  69. data/spec/models/spree/stock/prioritizer_decorator_spec.rb +29 -0
  70. data/spec/models/spree/stock/quantifier_decorator_spec.rb +42 -0
  71. data/spec/models/spree/stock/reserver_spec.rb +103 -0
  72. data/spec/models/spree/stock_location_decorator_spec.rb +47 -0
  73. data/spec/models/spree/user_decorator_spec.rb +65 -0
  74. data/spec/spec_helper.rb +56 -0
  75. data/spec/support/api_helpers.rb +35 -0
  76. data/spec/support/database_cleaner.rb +14 -0
  77. data/spec/support/have_attributes_matcher.rb +10 -0
  78. 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,11 @@
1
+ module Spree
2
+ class StockReservationAbility
3
+ include CanCan::Ability
4
+
5
+ def initialize(user)
6
+ if user.has_spree_role?('admin')
7
+ can :manage, Spree::ReservedStockItem
8
+ end
9
+ end
10
+ end
11
+ 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
+ Spree::Variant.class_eval do
2
+ # Adds optional user argument
3
+ def total_on_hand(user = nil)
4
+ Spree::Stock::Quantifier.new(self, nil, user).total_on_hand
5
+ end
6
+ end
@@ -0,0 +1,2 @@
1
+ <!-- replace "erb:contains('row_count = ')" -->
2
+ <%- row_count = (variant.stock_items).size %>
@@ -0,0 +1,2 @@
1
+ <!-- replace "erb:contains('item.stock_location.name')" -->
2
+ <%= stock_location_name(item) %>
@@ -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,5 @@
1
+ object @reserved_stock_item
2
+ attributes :id, :count_on_hand, :expires_at, :original_stock_location_id, :stock_location_id, :user_id, :variant_id
3
+ child(:variant) do
4
+ extends "spree/api/variants/small"
5
+ end
@@ -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,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -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
@@ -0,0 +1,6 @@
1
+ # Designate a stock location as being for reserved stock items
2
+ class AddReservedItemsToStockLocation < ActiveRecord::Migration
3
+ def change
4
+ add_column :spree_stock_locations, :reserved_items, :boolean, default: false
5
+ end
6
+ end
@@ -0,0 +1,7 @@
1
+ # Enable singel-table inheritance for stock items, so we can store
2
+ # ReservedStockItems
3
+ class AddTypeToSpreeStockItems < ActiveRecord::Migration
4
+ def change
5
+ add_column :spree_stock_items, :type, :string
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ class AddUserIdToSpreeStockItems < ActiveRecord::Migration
2
+ def change
3
+ add_reference :spree_stock_items, :user, index: true
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class AddOriginalStockLocationIdToSpreeStockItems < ActiveRecord::Migration
2
+ def change
3
+ add_reference :spree_stock_items, :original_stock_location, index: true
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class AddExpiresAtToSpreeStockItems < ActiveRecord::Migration
2
+ def change
3
+ add_column :spree_stock_items, :expires_at, :datetime
4
+ end
5
+ end