solidus_volume_pricing 0.1.0

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 (67) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.hound.yml +40 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +8 -0
  6. data/.travis.yml +14 -0
  7. data/CONTRIBUTING.md +81 -0
  8. data/Gemfile +13 -0
  9. data/Guardfile +9 -0
  10. data/LICENSE.md +26 -0
  11. data/README.md +112 -0
  12. data/Rakefile +21 -0
  13. data/app/assets/javascripts/spree/backend/solidus_volume_pricing.js +16 -0
  14. data/app/controllers/spree/admin/variants_controller_decorator.rb +32 -0
  15. data/app/controllers/spree/admin/volume_price_models_controller.rb +27 -0
  16. data/app/controllers/spree/admin/volume_prices_controller.rb +11 -0
  17. data/app/helpers/spree/base_helper_decorator.rb +19 -0
  18. data/app/models/spree/line_item_decorator.rb +27 -0
  19. data/app/models/spree/user_decorator.rb +10 -0
  20. data/app/models/spree/variant_decorator.rb +104 -0
  21. data/app/models/spree/volume_price.rb +35 -0
  22. data/app/models/spree/volume_price_model.rb +8 -0
  23. data/app/overrides/spree/admin/shared/sub_menu/_configuration/add_volume_price_model_admin_menu_links.html.erb.deface +3 -0
  24. data/app/overrides/views_decorator.rb +13 -0
  25. data/app/views/spree/admin/shared/_vp_product_tab.html.erb +3 -0
  26. data/app/views/spree/admin/variants/_edit_fields.html.erb +33 -0
  27. data/app/views/spree/admin/variants/volume_prices.html.erb +39 -0
  28. data/app/views/spree/admin/volume_price_models/_form.html.erb +9 -0
  29. data/app/views/spree/admin/volume_price_models/_list.html.erb +23 -0
  30. data/app/views/spree/admin/volume_price_models/edit.html.erb +12 -0
  31. data/app/views/spree/admin/volume_price_models/index.html.erb +18 -0
  32. data/app/views/spree/admin/volume_price_models/new.html.erb +12 -0
  33. data/app/views/spree/admin/volume_prices/_edit_fields.html.erb +31 -0
  34. data/app/views/spree/admin/volume_prices/_volume_price_fields.html.erb +33 -0
  35. data/app/views/spree/products/_volume_pricing.html.erb +15 -0
  36. data/bin/rails +7 -0
  37. data/config/locales/de.yml +18 -0
  38. data/config/locales/en.yml +18 -0
  39. data/config/locales/pt.yml +18 -0
  40. data/config/locales/ru.yml +18 -0
  41. data/config/locales/sv.yml +18 -0
  42. data/config/locales/tr.yml +18 -0
  43. data/config/routes.rb +12 -0
  44. data/db/migrate/20081119145604_create_volume_prices.rb +16 -0
  45. data/db/migrate/20110203174010_change_display_name_for_volume_prices.rb +9 -0
  46. data/db/migrate/20111206173307_prefix_volume_pricing_table_names.rb +5 -0
  47. data/db/migrate/20121115043422_add_discount_type_column.rb +5 -0
  48. data/db/migrate/20150513200904_add_role_to_volume_price.rb +5 -0
  49. data/db/migrate/20150603143015_create_spree_volume_price_models.rb +18 -0
  50. data/lib/generators/solidus_volume_pricing/install/install_generator.rb +24 -0
  51. data/lib/solidus_volume_pricing.rb +6 -0
  52. data/lib/solidus_volume_pricing/engine.rb +42 -0
  53. data/lib/solidus_volume_pricing/version.rb +18 -0
  54. data/solidus_volume_pricing.gemspec +40 -0
  55. data/spec/controllers/spree/admin/variants_controller_spec.rb +27 -0
  56. data/spec/factories/volume_price_factory.rb +12 -0
  57. data/spec/helpers/base_helper_spec.rb +22 -0
  58. data/spec/models/spree/line_item_spec.rb +34 -0
  59. data/spec/models/spree/order_spec.rb +49 -0
  60. data/spec/models/spree/variant_spec.rb +306 -0
  61. data/spec/models/spree/volume_price_spec.rb +121 -0
  62. data/spec/spec_helper.rb +41 -0
  63. data/spec/support/capybara.rb +12 -0
  64. data/spec/support/database_cleaner.rb +24 -0
  65. data/spec/support/factory_girl.rb +7 -0
  66. data/spec/support/spree.rb +10 -0
  67. metadata +337 -0
@@ -0,0 +1,15 @@
1
+ <% display_percent ||= false %>
2
+ <% if product.price > 0 and product.master.volume_prices.present? %>
3
+ <div id="bulk-discount">
4
+ <h6><%= Spree.t(:bulk_discount) %></h6>
5
+ <table class="table">
6
+ <% product.master.volume_prices.each do |v| %>
7
+ <%= content_tag(:tr) do %>
8
+ <%= content_tag :td, (v.open_ended? ? v.range : "#{v.range.to_range.begin}–#{v.range.to_range.end}") %>
9
+ <%= content_tag :td, Spree::Money.new(v.amount).to_s %>
10
+ <%= content_tag(:td, '%i%' % ((1.0 - v.amount / product.master.price) * 100.0).round) if display_percent %>
11
+ <% end %>
12
+ <% end %>
13
+ </table>
14
+ </div>
15
+ <% end %>
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ ENGINE_ROOT = File.expand_path('../..', __FILE__)
4
+ ENGINE_PATH = File.expand_path('../../lib/solidus_volume_pricing/engine', __FILE__)
5
+
6
+ require 'rails/all'
7
+ require 'rails/engine/commands'
@@ -0,0 +1,18 @@
1
+ ---
2
+ de:
3
+ spree:
4
+ add_volume_price: Neuen Staffelpreis
5
+ position: Position
6
+ discount_type:
7
+ range: Bereich
8
+ volume_prices: Staffelpreise
9
+ volume_pricing: Staffelpreis
10
+ price_discount:
11
+ percent_discount:
12
+ bulk_discount:
13
+ activerecord:
14
+ errors:
15
+ messages:
16
+ could_not_conver_to_range:
17
+ is_not_a_valid_volume_price_type:
18
+ must_be_in_format:
@@ -0,0 +1,18 @@
1
+ ---
2
+ en:
3
+ spree:
4
+ add_volume_price: Add Volume Price
5
+ position: Position
6
+ discount_type: Discount Type
7
+ range: Range
8
+ volume_prices: Volume Prices
9
+ volume_pricing: Volume Pricing
10
+ price_discount: Price Discount
11
+ percent_discount: Percent Discount
12
+ bulk_discount: Bulk Discount
13
+ activerecord:
14
+ errors:
15
+ messages:
16
+ could_not_conver_to_range: "Couldn't convert to Range: %{number}"
17
+ is_not_a_valid_volume_price_type: '%{value} is not a valid Volume Price Type'
18
+ must_be_in_format: 'must be in one of the following formats: (a..b), (a...b), (a+)'
@@ -0,0 +1,18 @@
1
+ ---
2
+ pt:
3
+ spree:
4
+ add_volume_price: Adicionar Preço Volume
5
+ position: Posição
6
+ discount_type: Tipo Desconto
7
+ range: Intervalo
8
+ volume_prices: Preços Volume
9
+ volume_pricing: Preçário Volume
10
+ price_discount: Desconto no Preço
11
+ percent_discount: Desconto em Percentagem
12
+ bulk_discount: Desconto por Quantidade
13
+ activerecord:
14
+ errors:
15
+ messages:
16
+ could_not_conver_to_range:
17
+ is_not_a_valid_volume_price_type:
18
+ must_be_in_format:
@@ -0,0 +1,18 @@
1
+ ---
2
+ ru:
3
+ spree:
4
+ add_volume_price: Добавить цену в зависимости от количества
5
+ position: Позиция в списке
6
+ discount_type:
7
+ range: Диапазон
8
+ volume_prices: Цены в зависимости от количества
9
+ volume_pricing: Цена в зависимости от количества
10
+ price_discount:
11
+ percent_discount:
12
+ bulk_discount:
13
+ activerecord:
14
+ errors:
15
+ messages:
16
+ could_not_conver_to_range:
17
+ is_not_a_valid_volume_price_type:
18
+ must_be_in_format:
@@ -0,0 +1,18 @@
1
+ ---
2
+ sv:
3
+ spree:
4
+ add_volume_price: Lägg till volympris
5
+ position: Position
6
+ discount_type: Rabatt typ
7
+ range: Omfång
8
+ volume_prices: Volympriser
9
+ volume_pricing: Volympris
10
+ price_discount: Rabatt pris
11
+ percent_discount: Procent rabatt
12
+ bulk_discount: Bulk rabatt
13
+ activerecord:
14
+ errors:
15
+ messages:
16
+ could_not_conver_to_range: "Det gick inte att konvertera till omfång: %{number}"
17
+ is_not_a_valid_volume_price_type: '%{value} är inte en giltligt typ för volympris'
18
+ must_be_in_format: 'måste vara i ett av följande format: (a..b), (a...b), (a+)'
@@ -0,0 +1,18 @@
1
+ ---
2
+ tr:
3
+ spree:
4
+ add_volume_price: Toplu Fiyat Ekle
5
+ position: Konum
6
+ discount_type: İndirim Türü
7
+ range: Aralık
8
+ volume_prices: Toplu Fiyatlar
9
+ volume_pricing: Toplu Fiyatlandırma
10
+ price_discount: Fiyat İndirimi
11
+ percent_discount: Yüzdelik İndirim
12
+ bulk_discount:
13
+ activerecord:
14
+ errors:
15
+ messages:
16
+ could_not_conver_to_range:
17
+ is_not_a_valid_volume_price_type:
18
+ must_be_in_format:
@@ -0,0 +1,12 @@
1
+ Spree::Core::Engine.add_routes do
2
+ namespace :admin do
3
+ resources :products do
4
+ resources :variants do
5
+ get :volume_prices, on: :member
6
+ end
7
+ end
8
+
9
+ delete '/volume_prices/:id', to: 'volume_prices#destroy', as: :volume_price
10
+ resources :volume_price_models
11
+ end
12
+ end
@@ -0,0 +1,16 @@
1
+ class CreateVolumePrices < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :volume_prices do |t|
4
+ t.references :variant
5
+ t.string :display
6
+ t.string :range
7
+ t.decimal :amount, precision: 8, scale: 2
8
+ t.integer :position
9
+ t.timestamps
10
+ end
11
+ end
12
+
13
+ def self.down
14
+ drop_table :volume_prices
15
+ end
16
+ end
@@ -0,0 +1,9 @@
1
+ class ChangeDisplayNameForVolumePrices < ActiveRecord::Migration
2
+ def self.up
3
+ rename_column :volume_prices, :display, :name
4
+ end
5
+
6
+ def self.down
7
+ rename_column :volume_prices, :name, :display
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+ class PrefixVolumePricingTableNames < ActiveRecord::Migration
2
+ def change
3
+ rename_table :volume_prices, :spree_volume_prices unless Spree::VolumePrice.table_exists?
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class AddDiscountTypeColumn < ActiveRecord::Migration
2
+ def change
3
+ add_column :spree_volume_prices, :discount_type, :string
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class AddRoleToVolumePrice < ActiveRecord::Migration
2
+ def change
3
+ add_column :spree_volume_prices, :role_id, :integer
4
+ end
5
+ end
@@ -0,0 +1,18 @@
1
+ class CreateSpreeVolumePriceModels < ActiveRecord::Migration
2
+ def change
3
+ create_table :spree_volume_price_models do |t|
4
+ t.string :name
5
+ t.timestamps
6
+ end
7
+
8
+ create_table :spree_variants_volume_price_models do |t|
9
+ t.belongs_to :volume_price_model
10
+ t.belongs_to :variant
11
+ end
12
+
13
+ add_reference :spree_volume_prices, :volume_price_model
14
+
15
+ add_index :spree_variants_volume_price_models, :volume_price_model_id, name: 'volume_price_model_id'
16
+ add_index :spree_variants_volume_price_models, :variant_id, name: 'variant_id'
17
+ end
18
+ end
@@ -0,0 +1,24 @@
1
+ module SolidusVolumePricing
2
+ module Generators
3
+ class InstallGenerator < Rails::Generators::Base
4
+ class_option :auto_run_migrations, type: :boolean, default: false
5
+
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
+ def add_migrations
11
+ run 'bundle exec rake railties:install:migrations FROM=solidus_volume_pricing'
12
+ end
13
+
14
+ def run_migrations
15
+ run_migrations = options[:auto_run_migrations] || ['', 'y', 'Y'].include?(ask 'Would you like to run the migrations now? [Y/n]')
16
+ if run_migrations
17
+ run 'bundle exec rake db:migrate'
18
+ else
19
+ puts 'Skipping rake db:migrate, don\'t forget to run it!'
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,6 @@
1
+ require 'sass/rails'
2
+ require 'deface'
3
+ require 'solidus_core'
4
+ require 'solidus_volume_pricing/engine'
5
+ require 'solidus_volume_pricing/version'
6
+ require 'coffee_script'
@@ -0,0 +1,42 @@
1
+ module SolidusVolumePricing
2
+ class Engine < Rails::Engine
3
+ isolate_namespace Spree
4
+ engine_name 'solidus_volume_pricing'
5
+
6
+ initializer 'solidus_volume_pricing.preferences', before: 'spree.environment' do
7
+ Spree::AppConfiguration.class_eval do
8
+ preference :use_master_variant_volume_pricing, :boolean, default: false
9
+ preference :volume_pricing_role, :string, default: 'wholesale'
10
+ end
11
+ end
12
+
13
+ def self.activate
14
+ Dir.glob(File.join(File.dirname(__FILE__), '../../app/**/*_decorator*.rb')) do |c|
15
+ Rails.configuration.cache_classes ? require(c) : load(c)
16
+ 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
37
+ end
38
+
39
+ config.autoload_paths += %W(#{config.root}/lib)
40
+ config.to_prepare(&method(:activate).to_proc)
41
+ end
42
+ end
@@ -0,0 +1,18 @@
1
+ module SolidusVolumePricing
2
+ module_function
3
+
4
+ # Returns the version of the currently loaded SolidusVolumePricing as a
5
+ # <tt>Gem::Version</tt>.
6
+ def version
7
+ Gem::Version.new VERSION::STRING
8
+ end
9
+
10
+ module VERSION
11
+ MAJOR = 0
12
+ MINOR = 1
13
+ TINY = 0
14
+ PRE = nil
15
+
16
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
17
+ end
18
+ end
@@ -0,0 +1,40 @@
1
+ lib = File.expand_path('../lib/', __FILE__)
2
+ $LOAD_PATH.unshift lib unless $LOAD_PATH.include?(lib)
3
+
4
+ require 'solidus_volume_pricing/version'
5
+
6
+ Gem::Specification.new do |s|
7
+ s.platform = Gem::Platform::RUBY
8
+ s.name = 'solidus_volume_pricing'
9
+ s.version = SolidusVolumePricing.version
10
+ s.summary = 'Allow prices to be configured in quantity ranges for each variant'
11
+ s.description = s.summary
12
+ s.required_ruby_version = '>= 2.2.3'
13
+
14
+ s.author = 'Sean Schofield'
15
+ s.email = 'sean@railsdog.com'
16
+ s.homepage = 'https://github.com/solidusio-contrib/solidus_volume_pricing'
17
+ s.license = 'BSD-3'
18
+
19
+ s.files = `git ls-files`.split("\n")
20
+ s.test_files = `git ls-files -- spec/*`.split("\n")
21
+ s.require_path = 'lib'
22
+ s.requirements << 'none'
23
+
24
+ s.add_runtime_dependency 'solidus_core', '~> 1.0', '< 1.3'
25
+ s.add_runtime_dependency 'deface', '~> 1.0'
26
+
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
+ 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'
40
+ end
@@ -0,0 +1,27 @@
1
+ RSpec.describe Spree::Admin::VariantsController, type: :controller do
2
+ stub_authorization!
3
+
4
+ context 'PUT #update' do
5
+ it 'creates a volume price' do
6
+ variant = create :variant
7
+
8
+ 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
+ }
24
+ end.to change(variant.volume_prices, :count).by(1)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,12 @@
1
+ FactoryGirl.define do
2
+ factory :volume_price, class: Spree::VolumePrice do
3
+ amount 10
4
+ discount_type 'price'
5
+ range '(1..5)'
6
+ association :variant
7
+ end
8
+
9
+ factory :volume_price_model, class: Spree::VolumePriceModel do
10
+ name 'name'
11
+ end
12
+ end
@@ -0,0 +1,22 @@
1
+ RSpec.describe Spree::BaseHelper, type: :helper do
2
+ include Spree::BaseHelper
3
+
4
+ context 'volume pricing' do
5
+ before do
6
+ @variant = create :variant, price: 10
7
+ @variant.volume_prices.create! amount: 1, discount_type: 'dollar', range: '(10+)'
8
+ end
9
+
10
+ it 'gives discounted price' do
11
+ expect(display_volume_price(@variant, 10)).to eq '$9.00'
12
+ end
13
+
14
+ it 'gives discount percent' do
15
+ expect(display_volume_price_earning_percent(@variant, 10)).to eq '10'
16
+ end
17
+
18
+ it 'gives discount amount' do
19
+ expect(display_volume_price_earning_amount(@variant, 10)).to eq '$1.00'
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,34 @@
1
+ RSpec.describe Spree::LineItem, type: :model do
2
+ before do
3
+ @order = create(:order)
4
+ @variant = create(:variant, price: 10)
5
+ @variant.volume_prices.create! amount: 9, discount_type: 'price', range: '(2+)'
6
+ @order.contents.add(@variant, 1)
7
+ @line_item = @order.line_items.first
8
+ @role = create(:role)
9
+ end
10
+
11
+ it 'updates the line item price when the quantity changes to match a range and has no role' do
12
+ expect(@line_item.price.to_f).to be(10.00)
13
+ @order.contents.add(@variant, 1)
14
+ expect(@order.line_items.first.price.to_f).to be(9.00)
15
+ end
16
+
17
+ it 'updates the line item price when the quantity changes to match a range and role matches' do
18
+ @order.user.spree_roles << @role
19
+ Spree::Config.volume_pricing_role = @role.name
20
+ expect(@order.user.has_spree_role? @role.name.to_sym).to be(true)
21
+ @variant.volume_prices.first.update(role_id: @role.id)
22
+ expect(@line_item.price.to_f).to be(10.00)
23
+ @order.contents.add(@variant, 1)
24
+ expect(@order.line_items.first.price.to_f).to be(9.00)
25
+ end
26
+
27
+ it 'does not update the line item price when the variant role and order role don`t match' do
28
+ expect(@order.user.has_spree_role? @role.name.to_sym).to be(false)
29
+ @variant.volume_prices.first.update(role_id: @role.id)
30
+ expect(@line_item.price.to_f).to be(10.00)
31
+ @order.contents.add(@variant, 1)
32
+ expect(@order.line_items.first.price.to_f).to be(10.00)
33
+ end
34
+ end