solidus_volume_pricing 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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