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,42 @@
1
+ require "spec_helper"
2
+
3
+ # TODO: Need to supply user argument in the appropriate places
4
+ # - LineItem#sufficient_stock?
5
+ # - Spree::Stock::AvailabilityValidator
6
+ # - Variant#can_supply?
7
+ # - _cart_form (give it the current user)
8
+ describe Spree::Stock::Quantifier, type: :model do
9
+ let(:user) { create(:user) }
10
+ let(:other_user) { create(:user) }
11
+ let(:original_stock_location) { create(:stock_location) }
12
+ let(:variant) { create(:variant) }
13
+
14
+ context "#total_on_hand" do
15
+ before(:each) do
16
+ stock_item = original_stock_location.stock_item_or_create(variant)
17
+ stock_item.adjust_count_on_hand(10)
18
+ Spree::Stock::Reserver.new.reserve(
19
+ variant,
20
+ original_stock_location,
21
+ user,
22
+ 4
23
+ )
24
+ end
25
+ context "with a user argument" do
26
+ it "includes reserved stock for user" do
27
+ subject = Spree::Stock::Quantifier.new(variant, nil, user)
28
+ expect(subject.total_on_hand).to eq(10)
29
+ end
30
+ it "excludes reserved stock for other users" do
31
+ subject = Spree::Stock::Quantifier.new(variant, nil, other_user)
32
+ expect(subject.total_on_hand).to eq(6)
33
+ end
34
+ end
35
+ context "with no user argument" do
36
+ it "excludes reserved stock" do
37
+ subject = Spree::Stock::Quantifier.new(variant)
38
+ expect(subject.total_on_hand).to eq(6)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,103 @@
1
+ require "spec_helper"
2
+
3
+ # Specs ReservedStockItem subclass to StockItem
4
+ describe Spree::Stock::Reserver, type: :model do
5
+ let(:user) { create(:user) }
6
+ let(:original_stock_location) { create(:stock_location) }
7
+ let(:variant) { create(:variant) }
8
+ let(:reserved_stock_location) { Spree::StockLocation.reserved_items_location }
9
+ let(:reserved_stock_item) do
10
+ create(
11
+ :reserved_stock_item,
12
+ variant: variant,
13
+ stock_location: reserved_stock_location,
14
+ original_stock_location: original_stock_location,
15
+ user: user,
16
+ expires_at: 1.day.from_now,
17
+ backorderable: false
18
+ )
19
+ end
20
+ let(:expired_reserved_stock_item) do
21
+ create(
22
+ :reserved_stock_item,
23
+ variant: variant,
24
+ stock_location: reserved_stock_location,
25
+ original_stock_location: original_stock_location,
26
+ user: user,
27
+ expires_at: 1.day.ago,
28
+ backorderable: false
29
+ )
30
+ end
31
+
32
+ subject { Spree::Stock::Reserver.new }
33
+
34
+ context "#reserve" do
35
+ before do
36
+ stock_item = original_stock_location.stock_item_or_create(variant)
37
+ stock_item.adjust_count_on_hand(10)
38
+ end
39
+ it "moves stock items to the reserved stock location" do
40
+ subject.reserve(variant, original_stock_location, user, 4)
41
+ expect(user.reserved_count_on_hand(variant)).to eq 4
42
+ expect(original_stock_location.count_on_hand(variant)).to eq 6
43
+ end
44
+ it "remembers original stock location" do
45
+ subject.reserve(variant, original_stock_location, user, 4)
46
+ reserved_stock_item = user.reserved_stock_item(variant)
47
+ expect(
48
+ reserved_stock_item.original_stock_location
49
+ ).to eq original_stock_location
50
+ end
51
+ it "won't reserve more stock than exists" do
52
+ expect do
53
+ subject.reserve(variant, original_stock_location, user, 1000)
54
+ end.to raise_error Spree::Stock::Reserver::InvalidQuantityError
55
+ end
56
+ it "won't reserve zero stock" do
57
+ expect do
58
+ subject.reserve(variant, original_stock_location, user, 0)
59
+ end.to raise_error Spree::Stock::Reserver::InvalidQuantityError
60
+ end
61
+ end
62
+
63
+ context "#restock" do
64
+ before(:each) do
65
+ reserved_stock_item
66
+ end
67
+ it "can restore all stock to the original stock location" do
68
+ subject.restock(variant, user)
69
+ expect(original_stock_location.count_on_hand(variant)).to eq 10
70
+ expect(user.reserved_count_on_hand(variant)).to eq 0
71
+ end
72
+ it "can restore a quantity of stock to the original stock location" do
73
+ subject.restock(variant, user, 4)
74
+ expect(original_stock_location.count_on_hand(variant)).to eq 4
75
+ expect(user.reserved_count_on_hand(variant)).to eq 6
76
+ end
77
+ it "won't restock more stock than exists"do
78
+ expect do
79
+ subject.restock(variant, user, 1000)
80
+ end.to raise_error Spree::Stock::Reserver::InvalidQuantityError
81
+ end
82
+ it "won't restock zero stock" do
83
+ expect do
84
+ subject.restock(variant, user, 0)
85
+ end.to raise_error Spree::Stock::Reserver::InvalidQuantityError
86
+ end
87
+ end
88
+
89
+ context "#restock_expired" do
90
+ it "restores expired ReservedStockItems" do
91
+ expired_reserved_stock_item
92
+ subject.restock_expired
93
+ expect(original_stock_location.count_on_hand(variant)).to eq 10
94
+ expect(user.reserved_count_on_hand(variant)).to eq 0
95
+ end
96
+ it "doesn't restore unexpired ReservedStockItems" do
97
+ reserved_stock_item
98
+ subject.restock_expired
99
+ expect(original_stock_location.count_on_hand(variant)).to eq 0
100
+ expect(user.reserved_count_on_hand(variant)).to eq 10
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,47 @@
1
+ require "spec_helper"
2
+
3
+ # Specs modifications in stock_location_decorator
4
+ describe Spree::StockLocation, type: :model do
5
+ subject do
6
+ create(
7
+ :stock_location,
8
+ backorderable_default: false,
9
+ propagate_all_variants: false,
10
+ reserved_items: true
11
+ )
12
+ end
13
+
14
+ context "validation" do
15
+ context "when reserved_items is true" do
16
+ it "is invalid if backorderable_default is true" do
17
+ reserved_stock_item = build(
18
+ :stock_location,
19
+ backorderable_default: true,
20
+ propagate_all_variants: false,
21
+ reserved_items: true
22
+ )
23
+ expect(reserved_stock_item).to be_invalid
24
+ end
25
+ it "is invalid if propagate_all_variants is true" do
26
+ reserved_stock_item = build(
27
+ :stock_location,
28
+ backorderable_default: false,
29
+ propagate_all_variants: true,
30
+ reserved_items: true
31
+ )
32
+ expect(reserved_stock_item).to be_invalid
33
+ end
34
+ end
35
+ end
36
+ context ".reserved_items_location" do
37
+ it "can return the existing reserved stock location" do
38
+ subject
39
+ expect(Spree::StockLocation.reserved_items_location).to eq subject
40
+ end
41
+ it "creates reserved stock location if not present" do
42
+ reserved_items_location = Spree::StockLocation.reserved_items_location
43
+ expect(reserved_items_location).to be_a Spree::StockLocation
44
+ expect(reserved_items_location.reserved_items?).to be true
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,65 @@
1
+ require "spec_helper"
2
+
3
+
4
+ describe Spree.user_class, type: :model do
5
+ subject { create(:user) }
6
+ let(:original_stock_location) { create(:stock_location) }
7
+ let(:variant) { create(:variant) }
8
+ let(:reserved_stock_location) { Spree::StockLocation.reserved_items_location }
9
+ let(:reserved_stock_item) do
10
+ create(
11
+ :reserved_stock_item,
12
+ variant: variant,
13
+ stock_location: reserved_stock_location,
14
+ original_stock_location: original_stock_location,
15
+ user: subject,
16
+ expires_at: 1.day.from_now,
17
+ backorderable: false
18
+ )
19
+ end
20
+
21
+ context "#reserved_count_on_hand" do
22
+ it "returns reserved quantity for a variant" do
23
+ reserved_stock_item
24
+ expect(
25
+ subject.reserved_count_on_hand(variant)
26
+ ).to eq 10
27
+ end
28
+ end
29
+
30
+ context "#reserved_stock_item" do
31
+ it "returns the reserved_stock_item for a variant" do
32
+ reserved_stock_item
33
+ expect(
34
+ subject.reserved_stock_item(variant)
35
+ ).to eq reserved_stock_item
36
+ end
37
+ end
38
+
39
+ context "#reserved_stock_item_or_create" do
40
+ it "returns an existing reserved_stock_item" do
41
+ reserved_stock_item
42
+ expect(
43
+ subject.reserved_stock_item_or_create(variant, original_stock_location)
44
+ ).to eq reserved_stock_item
45
+ end
46
+ it "creates and returns a reserved_stock_item if none exists" do
47
+ expect(subject.reserved_stock_item(variant)).to be_nil
48
+ subject.reserved_stock_item_or_create(variant, original_stock_location, 1.week.from_now)
49
+ expect(subject.reserved_stock_item(variant)).not_to be_nil
50
+ end
51
+ end
52
+
53
+ context "when account is destroyed" do
54
+ it "restocks all reserved items" do
55
+ reserved_stock_item
56
+ subject.destroy
57
+ expect(
58
+ reserved_stock_location.stock_items.where(user_id: subject.id).count
59
+ ).to eq 0
60
+ expect(
61
+ original_stock_location.count_on_hand(variant)
62
+ ).to eq 10
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,56 @@
1
+ require "simplecov"
2
+ SimpleCov.start "rails" do
3
+ add_filter "/.gems/"
4
+ end
5
+
6
+ ENV["RAILS_ENV"] ||= "test"
7
+
8
+ begin
9
+ require File.expand_path("../dummy/config/environment", __FILE__)
10
+ rescue LoadError
11
+ puts "Could not load dummy application."\
12
+ "Please ensure you have run `bundle exec rake test_app`"
13
+ exit
14
+ end
15
+
16
+ require "rspec/rails"
17
+
18
+ # Requires supporting ruby files with custom matchers and macros, etc,
19
+ # in spec/support/ and its subdirectories.
20
+ Dir[File.dirname(__FILE__) + "/support/**/*.rb"].each {|f| require f}
21
+ require "pry"
22
+ require "ffaker"
23
+ require "factory_girl_rails"
24
+ require "spree/testing_support/factories"
25
+ require "spree/testing_support/preferences"
26
+ require "spree/api/testing_support/helpers"
27
+ require "spree/api/testing_support/setup"
28
+ require 'rspec/active_model/mocks'
29
+
30
+ RSpec.configure do |config|
31
+ config.fail_fast = false
32
+ config.filter_run focus: true
33
+ config.infer_spec_type_from_file_location!
34
+ config.mock_with :rspec
35
+ config.raise_errors_for_deprecations!
36
+ config.run_all_when_everything_filtered = true
37
+ config.use_transactional_fixtures = true
38
+ config.include FactoryGirl::Syntax::Methods
39
+ config.include Spree::Api::TestingSupport::Helpers, type: :controller
40
+ config.extend Spree::Api::TestingSupport::Setup, type: :controller
41
+ config.include Spree::TestingSupport::Preferences
42
+
43
+ config.expect_with :rspec do |expectations|
44
+ expectations.syntax = :expect
45
+ end
46
+
47
+ config.before(:each) do
48
+ Rails.cache.clear
49
+ reset_spree_preferences
50
+ Spree::Api::Config.requires_authentication = true
51
+ end
52
+ end
53
+
54
+ Dir[File.join(File.dirname(__FILE__), "/support/**/*.rb")].each do |file|
55
+ require file
56
+ end
@@ -0,0 +1,35 @@
1
+ require "active_support/all"
2
+
3
+ # Convenience methods for API specs
4
+ module ApiHelpers
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ routes { Spree::Core::Engine.routes }
9
+ end
10
+
11
+ def api_get(action, params = {}, session = nil, flash = nil)
12
+ api_process(action, params, session, flash, "GET")
13
+ end
14
+
15
+ def api_post(action, params = {}, session = nil, flash = nil)
16
+ api_process(action, params, session, flash, "POST")
17
+ end
18
+
19
+ def api_put(action, params = {}, session = nil, flash = nil)
20
+ api_process(action, params, session, flash, "PUT")
21
+ end
22
+
23
+ def api_delete(action, params = {}, session = nil, flash = nil)
24
+ api_process(action, params, session, flash, "DELETE")
25
+ end
26
+
27
+ def api_process(action, params = {}, session = nil, flash = nil, method = "get")
28
+ scoping = respond_to?(:resource_scoping) ? resource_scoping : {}
29
+ process(action, method, params.merge(scoping).reverse_merge!(format: :json), session, flash)
30
+ end
31
+ end
32
+
33
+ RSpec.configure do |config|
34
+ config.include ApiHelpers, type: :controller
35
+ end
@@ -0,0 +1,14 @@
1
+ RSpec.configure do |config|
2
+ config.before(:suite) do
3
+ DatabaseCleaner.strategy = :transaction
4
+ DatabaseCleaner.clean_with(:truncation)
5
+ end
6
+
7
+ config.before(:each) do
8
+ DatabaseCleaner.start
9
+ end
10
+
11
+ config.after(:each) do
12
+ DatabaseCleaner.clean
13
+ end
14
+ end
@@ -0,0 +1,10 @@
1
+ # TODO: Remove this file borrowed from Solidus. It doesn't seem like a great
2
+ # idea to override have_attributes
3
+ RSpec::Matchers.define :have_attributes do |expected_attributes|
4
+ match do |actual|
5
+ # actual is a Hash object representing an object, like this:
6
+ # { "name" => "Product #1" }
7
+ actual_attributes = actual.keys.map(&:to_sym)
8
+ expected_attributes.map(&:to_sym).all? { |attr| actual_attributes.include?(attr) }
9
+ end
10
+ end