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.
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