solidus_volume_pricing 0.2.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. checksums.yaml +5 -5
  2. data/.circleci/config.yml +35 -0
  3. data/.gitignore +1 -0
  4. data/.rubocop.yml +8 -4
  5. data/.rubocop_todo.yml +126 -0
  6. data/CHANGELOG.md +57 -0
  7. data/Gemfile +18 -4
  8. data/Guardfile +3 -1
  9. data/README.md +53 -48
  10. data/Rakefile +2 -0
  11. data/app/assets/javascripts/spree/backend/solidus_volume_pricing.js +1 -15
  12. data/app/controllers/spree/admin/volume_price_models_controller.rb +2 -1
  13. data/app/controllers/spree/admin/volume_prices_controller.rb +3 -1
  14. data/app/decorators/controllers/solidus_volume_pricing/spree/admin/variants_controller_decorator.rb +42 -0
  15. data/app/decorators/helpers/solidus_volume_pricing/spree/base_helper_decorator.rb +31 -0
  16. data/app/decorators/models/solidus_volume_pricing/spree/line_item_decorator.rb +18 -0
  17. data/app/decorators/models/solidus_volume_pricing/spree/user_decorator.rb +17 -0
  18. data/app/decorators/models/solidus_volume_pricing/spree/variant_decorator.rb +21 -0
  19. data/app/models/solidus_volume_pricing/price_display.rb +47 -0
  20. data/app/models/solidus_volume_pricing/pricer.rb +95 -0
  21. data/app/models/solidus_volume_pricing/pricing_options.rb +20 -0
  22. data/app/models/spree/volume_price.rb +43 -26
  23. data/app/models/spree/volume_price_model.rb +6 -4
  24. data/app/overrides/spree/admin/shared/_settings_sub_menu/add_volume_price_model_admin_menu_links.html.erb.deface +5 -0
  25. data/app/overrides/views_decorator.rb +2 -0
  26. data/app/views/spree/admin/shared/_vp_product_tab.html.erb +2 -2
  27. data/app/views/spree/admin/variants/_edit_fields.html.erb +5 -32
  28. data/app/views/spree/admin/variants/volume_prices.html.erb +9 -36
  29. data/app/views/spree/admin/volume_price_models/_form.html.erb +6 -2
  30. data/app/views/spree/admin/volume_price_models/_list.html.erb +8 -4
  31. data/app/views/spree/admin/volume_price_models/_select.html.erb +17 -0
  32. data/app/views/spree/admin/volume_price_models/edit.html.erb +7 -6
  33. data/app/views/spree/admin/volume_price_models/index.html.erb +7 -5
  34. data/app/views/spree/admin/volume_price_models/new.html.erb +5 -5
  35. data/app/views/spree/admin/volume_prices/_table.html.erb +26 -0
  36. data/app/views/spree/admin/volume_prices/_volume_price_fields.html.erb +6 -6
  37. data/app/views/spree/products/_volume_pricing.html.erb +8 -8
  38. data/bin/rails +3 -2
  39. data/config/locales/de.yml +17 -3
  40. data/config/locales/en.yml +21 -2
  41. data/config/locales/pt.yml +6 -2
  42. data/config/locales/ru.yml +6 -2
  43. data/config/locales/sv.yml +6 -2
  44. data/config/locales/tr.yml +6 -2
  45. data/config/routes.rb +3 -1
  46. data/db/migrate/20081119145604_create_volume_prices.rb +3 -1
  47. data/db/migrate/20110203174010_change_display_name_for_volume_prices.rb +3 -1
  48. data/db/migrate/20111206173307_prefix_volume_pricing_table_names.rb +3 -1
  49. data/db/migrate/20121115043422_add_discount_type_column.rb +3 -1
  50. data/db/migrate/20150513200904_add_role_to_volume_price.rb +3 -1
  51. data/db/migrate/20150603143015_create_spree_volume_price_models.rb +3 -1
  52. data/lib/generators/solidus_volume_pricing/install/install_generator.rb +3 -5
  53. data/lib/solidus_volume_pricing.rb +5 -1
  54. data/lib/solidus_volume_pricing/engine.rb +5 -22
  55. data/lib/solidus_volume_pricing/range_from_string.rb +36 -0
  56. data/lib/solidus_volume_pricing/version.rb +5 -3
  57. data/solidus_volume_pricing.gemspec +6 -14
  58. data/spec/controllers/spree/admin/variants_controller_spec.rb +18 -15
  59. data/spec/factories/volume_price_factory.rb +7 -5
  60. data/spec/features/manage_volume_price_models_feature_spec.rb +5 -3
  61. data/spec/features/manage_volume_prices_feature_spec.rb +6 -4
  62. data/spec/helpers/base_helper_spec.rb +3 -1
  63. data/spec/lib/solidus_volume_pricing/range_from_string_spec.rb +61 -0
  64. data/spec/models/solidus_volume_pricing/pricer_spec.rb +673 -0
  65. data/spec/models/solidus_volume_pricing/pricing_options_spec.rb +57 -0
  66. data/spec/models/spree/line_item_spec.rb +24 -21
  67. data/spec/models/spree/order_spec.rb +4 -2
  68. data/spec/models/spree/variant_spec.rb +3 -303
  69. data/spec/models/spree/volume_price_spec.rb +153 -49
  70. data/spec/spec_helper.rb +5 -38
  71. data/spec/support/shoulda.rb +11 -0
  72. metadata +37 -173
  73. data/.hound.yml +0 -40
  74. data/.travis.yml +0 -12
  75. data/CONTRIBUTING.md +0 -81
  76. data/app/controllers/spree/admin/variants_controller_decorator.rb +0 -32
  77. data/app/helpers/spree/base_helper_decorator.rb +0 -19
  78. data/app/models/spree/line_item_decorator.rb +0 -13
  79. data/app/models/spree/user_decorator.rb +0 -10
  80. data/app/models/spree/variant_decorator.rb +0 -104
  81. data/app/overrides/spree/admin/shared/sub_menu/_configuration/add_volume_price_model_admin_menu_links.html.erb.deface +0 -3
  82. data/app/views/spree/admin/volume_prices/_edit_fields.html.erb +0 -31
  83. data/spec/support/capybara.rb +0 -12
  84. data/spec/support/database_cleaner.rb +0 -21
  85. data/spec/support/factory_girl.rb +0 -7
  86. data/spec/support/spree.rb +0 -10
@@ -1,4 +1,6 @@
1
- Spree::Core::Engine.add_routes do
1
+ # frozen_string_literal: true
2
+
3
+ Spree::Core::Engine.routes.draw do
2
4
  namespace :admin do
3
5
  resources :products do
4
6
  resources :variants do
@@ -1,4 +1,6 @@
1
- class CreateVolumePrices < ActiveRecord::Migration
1
+ # frozen_string_literal: true
2
+
3
+ class CreateVolumePrices < ActiveRecord::Migration[4.2]
2
4
  def self.up
3
5
  create_table :volume_prices do |t|
4
6
  t.references :variant
@@ -1,4 +1,6 @@
1
- class ChangeDisplayNameForVolumePrices < ActiveRecord::Migration
1
+ # frozen_string_literal: true
2
+
3
+ class ChangeDisplayNameForVolumePrices < ActiveRecord::Migration[4.2]
2
4
  def self.up
3
5
  rename_column :volume_prices, :display, :name
4
6
  end
@@ -1,4 +1,6 @@
1
- class PrefixVolumePricingTableNames < ActiveRecord::Migration
1
+ # frozen_string_literal: true
2
+
3
+ class PrefixVolumePricingTableNames < ActiveRecord::Migration[4.2]
2
4
  def change
3
5
  rename_table :volume_prices, :spree_volume_prices unless Spree::VolumePrice.table_exists?
4
6
  end
@@ -1,4 +1,6 @@
1
- class AddDiscountTypeColumn < ActiveRecord::Migration
1
+ # frozen_string_literal: true
2
+
3
+ class AddDiscountTypeColumn < ActiveRecord::Migration[4.2]
2
4
  def change
3
5
  add_column :spree_volume_prices, :discount_type, :string
4
6
  end
@@ -1,4 +1,6 @@
1
- class AddRoleToVolumePrice < ActiveRecord::Migration
1
+ # frozen_string_literal: true
2
+
3
+ class AddRoleToVolumePrice < ActiveRecord::Migration[4.2]
2
4
  def change
3
5
  add_column :spree_volume_prices, :role_id, :integer
4
6
  end
@@ -1,4 +1,6 @@
1
- class CreateSpreeVolumePriceModels < ActiveRecord::Migration
1
+ # frozen_string_literal: true
2
+
3
+ class CreateSpreeVolumePriceModels < ActiveRecord::Migration[4.2]
2
4
  def change
3
5
  create_table :spree_volume_price_models do |t|
4
6
  t.string :name
@@ -1,18 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SolidusVolumePricing
2
4
  module Generators
3
5
  class InstallGenerator < Rails::Generators::Base
4
6
  class_option :auto_run_migrations, type: :boolean, default: false
5
7
 
6
- def add_javascripts
7
- append_file 'vendor/assets/javascripts/spree/backend/all.js', "//= require spree/backend/solidus_volume_pricing\n"
8
- end
9
-
10
8
  def add_migrations
11
9
  run 'bundle exec rake railties:install:migrations FROM=solidus_volume_pricing'
12
10
  end
13
11
 
14
12
  def run_migrations
15
- run_migrations = options[:auto_run_migrations] || ['', 'y', 'Y'].include?(ask 'Would you like to run the migrations now? [Y/n]')
13
+ run_migrations = options[:auto_run_migrations] || ['', 'y', 'Y'].include?(ask('Would you like to run the migrations now? [Y/n]'))
16
14
  if run_migrations
17
15
  run 'bundle exec rake db:migrate'
18
16
  else
@@ -1,6 +1,10 @@
1
- require 'sass/rails'
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/deprecation'
4
+ require 'sassc/rails'
2
5
  require 'deface'
3
6
  require 'solidus_core'
4
7
  require 'solidus_volume_pricing/engine'
5
8
  require 'solidus_volume_pricing/version'
9
+ require 'solidus_volume_pricing/range_from_string'
6
10
  require 'coffee_script'
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SolidusVolumePricing
2
4
  class Engine < Rails::Engine
3
- isolate_namespace Spree
5
+ isolate_namespace ::Spree
4
6
  engine_name 'solidus_volume_pricing'
5
7
 
6
8
  initializer 'solidus_volume_pricing.preferences', before: 'spree.environment' do
7
- Spree::AppConfiguration.class_eval do
9
+ ::Spree::AppConfiguration.class_eval do
8
10
  preference :use_master_variant_volume_pricing, :boolean, default: false
9
11
  preference :volume_pricing_role, :string, default: 'wholesale'
10
12
  end
@@ -14,26 +16,7 @@ module SolidusVolumePricing
14
16
  Dir.glob(File.join(File.dirname(__FILE__), '../../app/**/*_decorator*.rb')) do |c|
15
17
  Rails.configuration.cache_classes ? require(c) : load(c)
16
18
  end
17
-
18
- String.class_eval do
19
- def to_range
20
- case count('.')
21
- when 2
22
- elements = split('..')
23
- return Range.new(elements[0].delete('(').to_i, elements[1].to_i)
24
- when 3
25
- elements = split('...')
26
- return Range.new(elements[0].delete('(').to_i, elements[1].to_i - 1)
27
- else
28
- raise ArgumentError.new(
29
- I18n.t(
30
- :'activerecord.errors.messages.could_not_conver_to_range',
31
- number: self
32
- )
33
- )
34
- end
35
- end
36
- end
19
+ ::Spree::BackendConfiguration::CONFIGURATION_TABS << :volume_price_models
37
20
  end
38
21
 
39
22
  config.autoload_paths += %W(#{config.root}/lib)
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusVolumePricing
4
+ class RangeFromString
5
+ attr_reader :range_string
6
+
7
+ RANGE_FORMAT = /\A\(?(\d+)(\.{2,3})(\d+)\)?\z/.freeze
8
+ OPEN_ENDED = /\(?[0-9]+\+\)?/.freeze
9
+
10
+ def initialize(range_string)
11
+ @range_string = range_string
12
+ end
13
+
14
+ def to_range
15
+ ::Range.new(*options_from_string)
16
+ end
17
+
18
+ private
19
+
20
+ def options_from_string
21
+ case range_string
22
+ when OPEN_ENDED
23
+ [range_string.tr("^0-9", '').to_i, Float::INFINITY]
24
+ when RANGE_FORMAT
25
+ [
26
+ Regexp.last_match[1].to_i,
27
+ Regexp.last_match[3].to_i,
28
+ Regexp.last_match[2].length == 3
29
+ ]
30
+ else
31
+ raise ArgumentError,
32
+ I18n.t(:could_not_convert_to_range, scope: 'activerecord.errors.messages', string: range_string)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SolidusVolumePricing
2
4
  module_function
3
5
 
@@ -8,9 +10,9 @@ module SolidusVolumePricing
8
10
  end
9
11
 
10
12
  module VERSION
11
- MAJOR = 0
12
- MINOR = 2
13
- TINY = 1
13
+ MAJOR = 1
14
+ MINOR = 0
15
+ TINY = 0
14
16
  PRE = nil
15
17
 
16
18
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
@@ -1,4 +1,6 @@
1
- lib = File.expand_path('../lib/', __FILE__)
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
2
4
  $LOAD_PATH.unshift lib unless $LOAD_PATH.include?(lib)
3
5
 
4
6
  require 'solidus_volume_pricing/version'
@@ -21,20 +23,10 @@ Gem::Specification.new do |s|
21
23
  s.require_path = 'lib'
22
24
  s.requirements << 'none'
23
25
 
24
- s.add_runtime_dependency 'solidus_core', '~> 1.3'
25
26
  s.add_runtime_dependency 'deface', '~> 1.0'
27
+ s.add_runtime_dependency 'solidus_backend', '>= 2.0'
28
+ s.add_runtime_dependency 'solidus_core', '>= 2.0'
26
29
 
27
- s.add_development_dependency 'sqlite3', '>= 1.3.10'
28
- s.add_development_dependency 'capybara', '~> 2.4'
29
- s.add_development_dependency 'ffaker', '>= 1.32.1'
30
30
  s.add_development_dependency 'shoulda-matchers'
31
- s.add_development_dependency 'rspec-rails', '~> 3.2'
32
- s.add_development_dependency 'simplecov', '~> 0.9'
33
- s.add_development_dependency 'factory_girl', '~> 4.5'
34
- s.add_development_dependency 'pry-rails', '>= 0.3'
35
- s.add_development_dependency 'poltergeist', '~> 1.6'
36
- s.add_development_dependency 'database_cleaner', '~> 1.4'
37
- s.add_development_dependency 'coffee-rails', '~> 4.0'
38
- s.add_development_dependency 'sass-rails', '~> 5.0'
39
- s.add_development_dependency 'rubocop', '>= 0.24.1'
31
+ s.add_development_dependency 'solidus_extension_dev_tools'
40
32
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  RSpec.describe Spree::Admin::VariantsController, type: :controller do
2
4
  stub_authorization!
3
5
 
@@ -6,21 +8,22 @@ RSpec.describe Spree::Admin::VariantsController, type: :controller do
6
8
  variant = create :variant
7
9
 
8
10
  expect do
9
- spree_put :update,
10
- product_id: variant.product.slug,
11
- id: variant.id,
12
- variant: {
13
- 'volume_prices_attributes' => {
14
- '1335830259720' => {
15
- 'name' => '5-10',
16
- 'discount_type' => 'price',
17
- 'range' => '5..10',
18
- 'amount' => '90',
19
- 'position' => '1',
20
- '_destroy' => 'false'
21
- }
22
- }
23
- }
11
+ put :update, params: {
12
+ product_id: variant.product.slug,
13
+ id: variant.id,
14
+ variant: {
15
+ 'volume_prices_attributes' => {
16
+ '1335830259720' => {
17
+ 'name' => '5-10',
18
+ 'discount_type' => 'price',
19
+ 'range' => '5..10',
20
+ 'amount' => '90',
21
+ 'position' => '1',
22
+ '_destroy' => 'false'
23
+ }
24
+ }
25
+ }
26
+ }
24
27
  end.to change(variant.volume_prices, :count).by(1)
25
28
  end
26
29
  end
@@ -1,12 +1,14 @@
1
- FactoryGirl.define do
1
+ # frozen_string_literal: true
2
+
3
+ FactoryBot.define do
2
4
  factory :volume_price, class: Spree::VolumePrice do
3
- amount 10
4
- discount_type 'price'
5
- range '(1..5)'
5
+ amount { 10 }
6
+ discount_type { 'price' }
7
+ range { '(1..5)' }
6
8
  association :variant
7
9
  end
8
10
 
9
11
  factory :volume_price_model, class: Spree::VolumePriceModel do
10
- name 'name'
12
+ name { 'name' }
11
13
  end
12
14
  end
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
- RSpec.feature 'Managing volume price models' do
5
+ RSpec.describe 'Managing volume price models' do
4
6
  stub_authorization!
5
7
 
6
- scenario 'a admin can create and remove volume price models', :js do
8
+ it 'a admin can create and remove volume price models', :js do
7
9
  visit spree.admin_volume_price_models_path
8
10
  expect(page).to have_content('Volume Price Models')
9
11
 
@@ -20,7 +22,7 @@ RSpec.feature 'Managing volume price models' do
20
22
  within 'tr.volume_price.fields' do
21
23
  expect(page).to have_field('volume_price_model_volume_prices_attributes_0_name', with: '5 pieces discount')
22
24
  page.find('a[data-action="remove"]').click
23
- expect(page).to_not have_field('volume_price_model_volume_prices_attributes_0_name', with: '5 pieces discount')
25
+ expect(page).not_to have_field('volume_price_model_volume_prices_attributes_0_name', with: '5 pieces discount')
24
26
  end
25
27
  end
26
28
  end
@@ -1,11 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
- RSpec.feature 'Managing volume prices' do
5
+ RSpec.describe 'Managing volume prices' do
4
6
  stub_authorization!
5
7
 
6
8
  let(:variant) { create(:variant) }
7
9
 
8
- scenario 'a admin can create and remove volume prices', :js do
10
+ it 'a admin can create and remove volume prices', :js do
9
11
  visit spree.edit_admin_product_path(variant.product)
10
12
  click_on 'Volume Pricing'
11
13
  expect(page).to have_content('Volume Prices')
@@ -19,11 +21,11 @@ RSpec.feature 'Managing volume prices' do
19
21
  within 'tr.volume_price.fields' do
20
22
  expect(page).to have_field('variant_volume_prices_attributes_0_name', with: '5 pieces discount')
21
23
  page.find('a[data-action="remove"]').click
22
- expect(page).to_not have_field('variant_volume_prices_attributes_0_name', with: '5 pieces discount')
24
+ expect(page).not_to have_field('variant_volume_prices_attributes_0_name', with: '5 pieces discount')
23
25
  end
24
26
  end
25
27
 
26
- scenario 'a admin editing a variant has a new volume price already built for her' do
28
+ it 'a admin editing a variant has a new volume price already built for her' do
27
29
  visit spree.edit_admin_product_variant_path(product_id: variant.product, id: variant)
28
30
  within '#volume_prices' do
29
31
  expect(page).to have_field('variant_volume_prices_attributes_0_name')
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  RSpec.describe Spree::BaseHelper, type: :helper do
2
- include Spree::BaseHelper
4
+ include described_class
3
5
 
4
6
  context 'volume pricing' do
5
7
  before do
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe SolidusVolumePricing::RangeFromString do
6
+ describe 'new' do
7
+ subject(:range) { described_class.new(argument).to_range }
8
+
9
+ context 'with a string with two dots' do
10
+ let(:argument) { '1..2' }
11
+
12
+ it { is_expected.to eq(1..2) }
13
+ end
14
+
15
+ context 'with a string with two dots and parens' do
16
+ let(:argument) { '(1..2)' }
17
+
18
+ it { is_expected.to eq(1..2) }
19
+ end
20
+
21
+ context 'with a string with three dots' do
22
+ let(:argument) { '1...2' }
23
+
24
+ it { is_expected.to eq(1...2) }
25
+ end
26
+
27
+ context 'with a string with three dots and parens' do
28
+ let(:argument) { '(1...2)' }
29
+
30
+ it { is_expected.to eq(1...2) }
31
+ end
32
+
33
+ context 'with an open-ended string like #{x}+' do
34
+ let(:argument) { '10+' }
35
+
36
+ it { is_expected.to eq(10..Float::INFINITY) }
37
+ end
38
+
39
+ context 'with an open-ended string like #{x}+ and parens' do
40
+ let(:argument) { '(10+)' }
41
+
42
+ it { is_expected.to eq(10..Float::INFINITY) }
43
+ end
44
+
45
+ context 'with invalid input' do
46
+ let(:argument) { 'system("rm -rf /*")' }
47
+
48
+ it do
49
+ expect { subject }.to raise_error(ArgumentError)
50
+ end
51
+ end
52
+
53
+ context 'with invalid input' do
54
+ let(:argument) { '1..3; puts "Do not run Ruby Code"' }
55
+
56
+ it do
57
+ expect { subject }.to raise_error(ArgumentError)
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,673 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.shared_examples 'having the variant price' do
6
+ it 'uses the variants price' do
7
+ expect(subject).to eq('$10.00')
8
+ end
9
+ end
10
+
11
+ RSpec.describe SolidusVolumePricing::Pricer do
12
+ let(:other_role) { create(:role) }
13
+ let(:role) { create(:role) }
14
+ let(:user) { create(:user) }
15
+ let(:variant) { create(:variant, price: 10) }
16
+
17
+ before do
18
+ stub_spree_preferences(volume_pricing_role: role.name)
19
+ end
20
+
21
+ it 'inherits from default variant pricer' do
22
+ expect(described_class < Spree::Variant::PriceSelector).to be(true)
23
+ end
24
+
25
+ it 'has SolidusVolumePricing::PricingOptions as pricing options class' do
26
+ expect(described_class.pricing_options_class).to eq(SolidusVolumePricing::PricingOptions)
27
+ end
28
+
29
+ describe '#price_for' do
30
+ subject do
31
+ described_class.new(variant).price_for(pricing_options).to_s
32
+ end
33
+
34
+ let(:quantity) { 1 }
35
+
36
+ let(:pricing_options) do
37
+ SolidusVolumePricing::PricingOptions.new(quantity: quantity)
38
+ end
39
+
40
+ context 'discount_type = price' do
41
+ before do
42
+ variant.volume_prices.create!(amount: 7, discount_type: 'price', range: '(10+)')
43
+ end
44
+
45
+ context 'when quantity does not match the range' do
46
+ it_behaves_like 'having the variant price'
47
+ end
48
+
49
+ context 'when quantity matches the range' do
50
+ let(:quantity) { 10 }
51
+
52
+ it 'uses the volume price' do
53
+ expect(subject).to eq('$7.00')
54
+ end
55
+
56
+ context 'when volume price has a role' do
57
+ before do
58
+ variant.volume_prices.first.update(role_id: role.id)
59
+ end
60
+
61
+ context 'when no user is given' do
62
+ it_behaves_like 'having the variant price'
63
+ end
64
+
65
+ context 'when a user is given' do
66
+ let(:pricing_options) do
67
+ SolidusVolumePricing::PricingOptions.new(
68
+ quantity: quantity,
69
+ user: user
70
+ )
71
+ end
72
+
73
+ context 'whose role matches' do
74
+ before { user.spree_roles << role }
75
+
76
+ it 'uses the volume price' do
77
+ expect(subject).to eq('$7.00')
78
+ end
79
+ end
80
+
81
+ context 'whose role does not match' do
82
+ before { user.spree_roles << other_role }
83
+
84
+ it_behaves_like 'having the variant price'
85
+ end
86
+ end
87
+ end
88
+
89
+ context 'of a volume price model instead' do
90
+ let(:quantity) { 6 }
91
+
92
+ before do
93
+ variant.volume_price_models << create(:volume_price_model)
94
+ variant.volume_price_models.first.volume_prices.create!(amount: 5, discount_type: 'price', range: '(5+)')
95
+ end
96
+
97
+ it 'uses the volume price from model' do
98
+ expect(subject).to eq('$5.00')
99
+ end
100
+ end
101
+ end
102
+ end
103
+
104
+ context 'discount_type = dollar' do
105
+ before do
106
+ variant.volume_prices.create!(amount: 1, discount_type: 'dollar', range: '(10+)')
107
+ end
108
+
109
+ context 'when quantity does not match the range' do
110
+ it_behaves_like 'having the variant price'
111
+ end
112
+
113
+ context 'when quantity matches the range' do
114
+ let(:quantity) { 10 }
115
+
116
+ it 'uses the volume price' do
117
+ expect(subject).to eq('$9.00')
118
+ end
119
+
120
+ context 'when volume price has a role' do
121
+ before do
122
+ variant.volume_prices.first.update(role_id: role.id)
123
+ end
124
+
125
+ context 'when no user is given' do
126
+ it_behaves_like 'having the variant price'
127
+ end
128
+
129
+ context 'when a user is given' do
130
+ let(:pricing_options) do
131
+ SolidusVolumePricing::PricingOptions.new(
132
+ quantity: quantity,
133
+ user: user
134
+ )
135
+ end
136
+
137
+ context 'whose role matches' do
138
+ before { user.spree_roles << role }
139
+
140
+ it 'uses the volume price' do
141
+ expect(subject).to eq('$9.00')
142
+ end
143
+ end
144
+
145
+ context 'whose role does not match' do
146
+ before { user.spree_roles << other_role }
147
+
148
+ it_behaves_like 'having the variant price'
149
+ end
150
+ end
151
+ end
152
+ end
153
+
154
+ context 'of a volume price model instead' do
155
+ let(:quantity) { 6 }
156
+
157
+ before do
158
+ variant.volume_price_models << create(:volume_price_model)
159
+ variant.volume_price_models.first.volume_prices.create!(amount: 2, discount_type: 'dollar', range: '(5+)')
160
+ end
161
+
162
+ it 'uses the volume price from model' do
163
+ expect(subject).to eq('$8.00')
164
+ end
165
+ end
166
+ end
167
+
168
+ context 'discount_type = percent' do
169
+ before do
170
+ variant.volume_prices.create!(amount: 0.25, discount_type: 'percent', range: '(10+)')
171
+ end
172
+
173
+ context 'when quantity does not match the range' do
174
+ it_behaves_like 'having the variant price'
175
+ end
176
+
177
+ context 'when quantity matches the range' do
178
+ let(:quantity) { 10 }
179
+
180
+ it 'uses the volume price' do
181
+ expect(subject).to eq('$7.50')
182
+ end
183
+
184
+ context 'when volume price has a role' do
185
+ before do
186
+ variant.volume_prices.first.update(role_id: role.id)
187
+ end
188
+
189
+ context 'when no user is given' do
190
+ it_behaves_like 'having the variant price'
191
+ end
192
+
193
+ context 'when a user is given' do
194
+ let(:pricing_options) do
195
+ SolidusVolumePricing::PricingOptions.new(
196
+ quantity: quantity,
197
+ user: user
198
+ )
199
+ end
200
+
201
+ context 'whose role matches' do
202
+ before { user.spree_roles << role }
203
+
204
+ it 'uses the volume price' do
205
+ expect(subject).to eq('$7.50')
206
+ end
207
+ end
208
+
209
+ context 'whose role does not match' do
210
+ before { user.spree_roles << other_role }
211
+
212
+ it_behaves_like 'having the variant price'
213
+ end
214
+ end
215
+ end
216
+ end
217
+
218
+ context 'of a volume price model instead' do
219
+ let(:quantity) { 6 }
220
+
221
+ it 'uses the volume price from model' do
222
+ variant.volume_price_models << create(:volume_price_model)
223
+ variant.volume_price_models.first.volume_prices.create!(amount: 0.75, discount_type: 'percent', range: '(5+)')
224
+ expect(subject).to eq('$2.50')
225
+ end
226
+ end
227
+ end
228
+
229
+ context 'discount_type is unknown' do
230
+ before do
231
+ variant.volume_prices.create(amount: 7, discount_type: 'foo', range: '(10+)')
232
+ end
233
+
234
+ it_behaves_like 'having the variant price'
235
+ end
236
+
237
+ context 'when use_master_variant_volume_pricing' do
238
+ let(:master) { variant.product.master }
239
+
240
+ before do
241
+ stub_spree_preferences(use_master_variant_volume_pricing: use_master_variant_volume_pricing)
242
+ master.volume_prices.create!(amount: 1.5, discount_type: 'price', range: '(1+)')
243
+ variant.volume_prices.create!(amount: 3.5, discount_type: 'price', range: '(1+)')
244
+ end
245
+
246
+ context 'is enabled' do
247
+ let(:use_master_variant_volume_pricing) { true }
248
+
249
+ context 'when volume prices are present on variant' do
250
+ it 'uses the variant to compute price' do
251
+ expect(subject).to eq('$3.50')
252
+ end
253
+ end
254
+
255
+ context 'when no volume prices present on variant' do
256
+ before do
257
+ variant.volume_prices.delete_all
258
+ end
259
+
260
+ it 'uses the master variant to compute price' do
261
+ expect(subject).to eq('$1.50')
262
+ end
263
+ end
264
+ end
265
+
266
+ context 'is disabled' do
267
+ let(:use_master_variant_volume_pricing) { false }
268
+
269
+ it 'uses the master variant to compute price' do
270
+ expect(subject).to eq('$3.50')
271
+ end
272
+ end
273
+ end
274
+ end
275
+
276
+ describe '#earning_amount' do
277
+ subject do
278
+ described_class.new(variant).earning_amount(pricing_options).to_s
279
+ end
280
+
281
+ let(:pricing_options) do
282
+ SolidusVolumePricing::PricingOptions.new(quantity: quantity)
283
+ end
284
+
285
+ context 'discount_type = price' do
286
+ before do
287
+ variant.volume_prices.create!(amount: 9, discount_type: 'price', range: '(10+)')
288
+ end
289
+
290
+ context 'when quantity matches range' do
291
+ let(:quantity) { 10 }
292
+
293
+ it 'gives amount earning' do
294
+ expect(subject).to eq('$1.00')
295
+ end
296
+
297
+ context 'when volume_price has role' do
298
+ before do
299
+ variant.volume_prices.first.update(role_id: role.id)
300
+ end
301
+
302
+ context 'when user is given' do
303
+ let(:pricing_options) do
304
+ SolidusVolumePricing::PricingOptions.new(
305
+ quantity: quantity,
306
+ user: user
307
+ )
308
+ end
309
+
310
+ context 'whose role matches' do
311
+ before do
312
+ user.spree_roles << role
313
+ end
314
+
315
+ it 'gives amount earning' do
316
+ expect(subject).to eq('$1.00')
317
+ end
318
+ end
319
+
320
+ context 'whose role does not match' do
321
+ before do
322
+ user.spree_roles << other_role
323
+ end
324
+
325
+ it 'gives zero earning amount' do
326
+ expect(subject).to eq('$0.00')
327
+ end
328
+ end
329
+ end
330
+
331
+ context 'when no user is given' do
332
+ it 'gives zero earning amount' do
333
+ expect(subject).to eq('$0.00')
334
+ end
335
+ end
336
+ end
337
+ end
338
+
339
+ context 'when quantity does not match range' do
340
+ let(:quantity) { 1 }
341
+
342
+ it 'gives zero earning amount' do
343
+ expect(subject).to eq('$0.00')
344
+ end
345
+ end
346
+ end
347
+
348
+ context 'discount_type = dollar' do
349
+ before do
350
+ variant.volume_prices.create!(amount: 2.5, discount_type: 'dollar', range: '(10+)')
351
+ end
352
+
353
+ context 'when amount matches range' do
354
+ let(:quantity) { 10 }
355
+
356
+ it 'gives amount earning' do
357
+ expect(subject).to eq('$2.50')
358
+ end
359
+
360
+ context 'when volume_price has role' do
361
+ before do
362
+ variant.volume_prices.first.update(role_id: role.id)
363
+ end
364
+
365
+ context 'when user is given' do
366
+ let(:pricing_options) do
367
+ SolidusVolumePricing::PricingOptions.new(
368
+ quantity: quantity,
369
+ user: user
370
+ )
371
+ end
372
+
373
+ context 'whose role matches' do
374
+ before do
375
+ user.spree_roles << role
376
+ end
377
+
378
+ it 'gives amount earning' do
379
+ expect(subject).to eq('$2.50')
380
+ end
381
+ end
382
+
383
+ context 'whose role does not match' do
384
+ before do
385
+ user.spree_roles << other_role
386
+ end
387
+
388
+ it 'gives zero earning amount' do
389
+ expect(subject).to eq('$0.00')
390
+ end
391
+ end
392
+ end
393
+
394
+ context 'when no user is given' do
395
+ it 'gives zero earning amount' do
396
+ expect(subject).to eq('$0.00')
397
+ end
398
+ end
399
+ end
400
+ end
401
+
402
+ context 'when amount does not match range' do
403
+ let(:quantity) { 1 }
404
+
405
+ it 'gives zero earning amount' do
406
+ expect(subject).to eq('$0.00')
407
+ end
408
+ end
409
+ end
410
+
411
+ context 'discount_type = percent' do
412
+ before do
413
+ variant.volume_prices.create!(amount: 0.75, discount_type: 'percent', range: '(10+)')
414
+ end
415
+
416
+ context 'when amount matches range' do
417
+ let(:quantity) { 10 }
418
+
419
+ it 'gives amount earning' do
420
+ expect(subject).to eq('$7.50')
421
+ end
422
+
423
+ context 'when volume_price has role' do
424
+ before do
425
+ variant.volume_prices.first.update(role_id: role.id)
426
+ end
427
+
428
+ context 'when user is given' do
429
+ let(:pricing_options) do
430
+ SolidusVolumePricing::PricingOptions.new(
431
+ quantity: quantity,
432
+ user: user
433
+ )
434
+ end
435
+
436
+ context 'whose role matches' do
437
+ before do
438
+ user.spree_roles << role
439
+ end
440
+
441
+ it 'gives amount earning' do
442
+ expect(subject).to eq('$7.50')
443
+ end
444
+ end
445
+
446
+ context 'whose role does not match' do
447
+ before do
448
+ user.spree_roles << other_role
449
+ end
450
+
451
+ it 'gives zero earning amount' do
452
+ expect(subject).to eq('$0.00')
453
+ end
454
+ end
455
+ end
456
+
457
+ context 'when no user is given' do
458
+ it 'gives zero earning amount' do
459
+ expect(subject).to eq('$0.00')
460
+ end
461
+ end
462
+ end
463
+ end
464
+
465
+ context 'when amount does not match range' do
466
+ let(:quantity) { 1 }
467
+
468
+ it 'gives zero earning amount' do
469
+ expect(subject).to eq('$0.00')
470
+ end
471
+ end
472
+ end
473
+ end
474
+
475
+ describe '#earning_percent' do
476
+ subject do
477
+ described_class.new(variant).earning_percent(pricing_options)
478
+ end
479
+
480
+ let(:pricing_options) do
481
+ SolidusVolumePricing::PricingOptions.new(quantity: quantity)
482
+ end
483
+
484
+ context 'discount_type = price' do
485
+ before do
486
+ variant.volume_prices.create!(amount: 9, discount_type: 'price', range: '(10+)')
487
+ end
488
+
489
+ context 'when quantity matches range' do
490
+ let(:quantity) { 10 }
491
+
492
+ it 'gives percent of earning' do
493
+ expect(subject).to eq(10)
494
+ end
495
+
496
+ context 'when volume_price has role' do
497
+ before do
498
+ variant.volume_prices.first.update(role_id: role.id)
499
+ end
500
+
501
+ context 'when user is given' do
502
+ let(:pricing_options) do
503
+ SolidusVolumePricing::PricingOptions.new(
504
+ quantity: quantity,
505
+ user: user
506
+ )
507
+ end
508
+
509
+ context 'whose role matches' do
510
+ before do
511
+ user.spree_roles << role
512
+ end
513
+
514
+ it 'gives percent of earning if role matches' do
515
+ expect(subject).to eq(10)
516
+ end
517
+ end
518
+
519
+ context 'whose role doesnt match' do
520
+ before do
521
+ user.spree_roles << other_role
522
+ end
523
+
524
+ it 'gives zero percent earning' do
525
+ expect(subject).to eq(0)
526
+ end
527
+ end
528
+ end
529
+
530
+ context 'when no user is given' do
531
+ it 'gives zero earning amount' do
532
+ expect(subject).to eq(0)
533
+ end
534
+ end
535
+ end
536
+ end
537
+
538
+ context 'when quantity does not match range' do
539
+ let(:quantity) { 1 }
540
+
541
+ it 'gives zero percent earning' do
542
+ expect(subject).to eq(0)
543
+ end
544
+ end
545
+ end
546
+
547
+ context 'discount_type = dollar' do
548
+ before do
549
+ variant.volume_prices.create!(amount: 2.5, discount_type: 'dollar', range: '(10+)')
550
+ end
551
+
552
+ context 'when quantity matches range' do
553
+ let(:quantity) { 10 }
554
+
555
+ it 'gives percent of earning' do
556
+ expect(subject).to eq(25)
557
+ end
558
+
559
+ context 'when volume_price has role' do
560
+ before do
561
+ variant.volume_prices.first.update(role_id: role.id)
562
+ end
563
+
564
+ context 'when user is given' do
565
+ let(:pricing_options) do
566
+ SolidusVolumePricing::PricingOptions.new(
567
+ quantity: quantity,
568
+ user: user
569
+ )
570
+ end
571
+
572
+ context 'whose role matches' do
573
+ before do
574
+ user.spree_roles << role
575
+ end
576
+
577
+ it 'gives percent of earning if role matches' do
578
+ expect(subject).to eq(25)
579
+ end
580
+ end
581
+
582
+ context 'whose role doesnt match' do
583
+ before do
584
+ user.spree_roles << other_role
585
+ end
586
+
587
+ it 'gives zero percent earning' do
588
+ expect(subject).to eq(0)
589
+ end
590
+ end
591
+ end
592
+
593
+ context 'when no user is given' do
594
+ it 'gives zero earning amount' do
595
+ expect(subject).to eq(0)
596
+ end
597
+ end
598
+ end
599
+ end
600
+
601
+ context 'when quantity does not match range' do
602
+ let(:quantity) { 1 }
603
+
604
+ it 'gives zero percent earning' do
605
+ expect(subject).to eq(0)
606
+ end
607
+ end
608
+ end
609
+
610
+ context 'discount_type = percent' do
611
+ before do
612
+ variant.volume_prices.create!(amount: 0.25, discount_type: 'percent', range: '(10+)')
613
+ end
614
+
615
+ context 'when quantity matches range' do
616
+ let(:quantity) { 10 }
617
+
618
+ it 'gives percent of earning' do
619
+ expect(subject).to eq(25)
620
+ end
621
+
622
+ context 'when volume_price has role' do
623
+ before do
624
+ variant.volume_prices.first.update(role_id: role.id)
625
+ end
626
+
627
+ context 'when user is given' do
628
+ let(:pricing_options) do
629
+ SolidusVolumePricing::PricingOptions.new(
630
+ quantity: quantity,
631
+ user: user
632
+ )
633
+ end
634
+
635
+ context 'whose role matches' do
636
+ before do
637
+ user.spree_roles << role
638
+ end
639
+
640
+ it 'gives percent of earning if role matches' do
641
+ expect(subject).to eq(25)
642
+ end
643
+ end
644
+
645
+ context 'whose role doesnt match' do
646
+ before do
647
+ user.spree_roles << other_role
648
+ end
649
+
650
+ it 'gives zero percent earning' do
651
+ expect(subject).to eq(0)
652
+ end
653
+ end
654
+ end
655
+
656
+ context 'when no user is given' do
657
+ it 'gives zero earning amount' do
658
+ expect(subject).to eq(0)
659
+ end
660
+ end
661
+ end
662
+ end
663
+
664
+ context 'when quantity does not match range' do
665
+ let(:quantity) { 1 }
666
+
667
+ it 'gives zero percent earning' do
668
+ expect(subject).to eq(0)
669
+ end
670
+ end
671
+ end
672
+ end
673
+ end