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.
- checksums.yaml +5 -5
- data/.circleci/config.yml +35 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +8 -4
- data/.rubocop_todo.yml +126 -0
- data/CHANGELOG.md +57 -0
- data/Gemfile +18 -4
- data/Guardfile +3 -1
- data/README.md +53 -48
- data/Rakefile +2 -0
- data/app/assets/javascripts/spree/backend/solidus_volume_pricing.js +1 -15
- data/app/controllers/spree/admin/volume_price_models_controller.rb +2 -1
- data/app/controllers/spree/admin/volume_prices_controller.rb +3 -1
- data/app/decorators/controllers/solidus_volume_pricing/spree/admin/variants_controller_decorator.rb +42 -0
- data/app/decorators/helpers/solidus_volume_pricing/spree/base_helper_decorator.rb +31 -0
- data/app/decorators/models/solidus_volume_pricing/spree/line_item_decorator.rb +18 -0
- data/app/decorators/models/solidus_volume_pricing/spree/user_decorator.rb +17 -0
- data/app/decorators/models/solidus_volume_pricing/spree/variant_decorator.rb +21 -0
- data/app/models/solidus_volume_pricing/price_display.rb +47 -0
- data/app/models/solidus_volume_pricing/pricer.rb +95 -0
- data/app/models/solidus_volume_pricing/pricing_options.rb +20 -0
- data/app/models/spree/volume_price.rb +43 -26
- data/app/models/spree/volume_price_model.rb +6 -4
- data/app/overrides/spree/admin/shared/_settings_sub_menu/add_volume_price_model_admin_menu_links.html.erb.deface +5 -0
- data/app/overrides/views_decorator.rb +2 -0
- data/app/views/spree/admin/shared/_vp_product_tab.html.erb +2 -2
- data/app/views/spree/admin/variants/_edit_fields.html.erb +5 -32
- data/app/views/spree/admin/variants/volume_prices.html.erb +9 -36
- data/app/views/spree/admin/volume_price_models/_form.html.erb +6 -2
- data/app/views/spree/admin/volume_price_models/_list.html.erb +8 -4
- data/app/views/spree/admin/volume_price_models/_select.html.erb +17 -0
- data/app/views/spree/admin/volume_price_models/edit.html.erb +7 -6
- data/app/views/spree/admin/volume_price_models/index.html.erb +7 -5
- data/app/views/spree/admin/volume_price_models/new.html.erb +5 -5
- data/app/views/spree/admin/volume_prices/_table.html.erb +26 -0
- data/app/views/spree/admin/volume_prices/_volume_price_fields.html.erb +6 -6
- data/app/views/spree/products/_volume_pricing.html.erb +8 -8
- data/bin/rails +3 -2
- data/config/locales/de.yml +17 -3
- data/config/locales/en.yml +21 -2
- data/config/locales/pt.yml +6 -2
- data/config/locales/ru.yml +6 -2
- data/config/locales/sv.yml +6 -2
- data/config/locales/tr.yml +6 -2
- data/config/routes.rb +3 -1
- data/db/migrate/20081119145604_create_volume_prices.rb +3 -1
- data/db/migrate/20110203174010_change_display_name_for_volume_prices.rb +3 -1
- data/db/migrate/20111206173307_prefix_volume_pricing_table_names.rb +3 -1
- data/db/migrate/20121115043422_add_discount_type_column.rb +3 -1
- data/db/migrate/20150513200904_add_role_to_volume_price.rb +3 -1
- data/db/migrate/20150603143015_create_spree_volume_price_models.rb +3 -1
- data/lib/generators/solidus_volume_pricing/install/install_generator.rb +3 -5
- data/lib/solidus_volume_pricing.rb +5 -1
- data/lib/solidus_volume_pricing/engine.rb +5 -22
- data/lib/solidus_volume_pricing/range_from_string.rb +36 -0
- data/lib/solidus_volume_pricing/version.rb +5 -3
- data/solidus_volume_pricing.gemspec +6 -14
- data/spec/controllers/spree/admin/variants_controller_spec.rb +18 -15
- data/spec/factories/volume_price_factory.rb +7 -5
- data/spec/features/manage_volume_price_models_feature_spec.rb +5 -3
- data/spec/features/manage_volume_prices_feature_spec.rb +6 -4
- data/spec/helpers/base_helper_spec.rb +3 -1
- data/spec/lib/solidus_volume_pricing/range_from_string_spec.rb +61 -0
- data/spec/models/solidus_volume_pricing/pricer_spec.rb +673 -0
- data/spec/models/solidus_volume_pricing/pricing_options_spec.rb +57 -0
- data/spec/models/spree/line_item_spec.rb +24 -21
- data/spec/models/spree/order_spec.rb +4 -2
- data/spec/models/spree/variant_spec.rb +3 -303
- data/spec/models/spree/volume_price_spec.rb +153 -49
- data/spec/spec_helper.rb +5 -38
- data/spec/support/shoulda.rb +11 -0
- metadata +37 -173
- data/.hound.yml +0 -40
- data/.travis.yml +0 -12
- data/CONTRIBUTING.md +0 -81
- data/app/controllers/spree/admin/variants_controller_decorator.rb +0 -32
- data/app/helpers/spree/base_helper_decorator.rb +0 -19
- data/app/models/spree/line_item_decorator.rb +0 -13
- data/app/models/spree/user_decorator.rb +0 -10
- data/app/models/spree/variant_decorator.rb +0 -104
- data/app/overrides/spree/admin/shared/sub_menu/_configuration/add_volume_price_model_admin_menu_links.html.erb.deface +0 -3
- data/app/views/spree/admin/volume_prices/_edit_fields.html.erb +0 -31
- data/spec/support/capybara.rb +0 -12
- data/spec/support/database_cleaner.rb +0 -21
- data/spec/support/factory_girl.rb +0 -7
- data/spec/support/spree.rb +0 -10
data/Rakefile
CHANGED
@@ -1,16 +1,2 @@
|
|
1
|
-
|
1
|
+
// Just here for backwards compatibility
|
2
2
|
|
3
|
-
// spree's version only handles 'input', not 'select', and this breaks solidus_volume_pricing
|
4
|
-
|
5
|
-
$(function () {
|
6
|
-
$('#add_volume_price').click( function() {
|
7
|
-
var target = $(this).data("target"),
|
8
|
-
new_table_row = $(target + ' tr:visible:first');
|
9
|
-
new_table_row.find('div.select2').remove();
|
10
|
-
$('select.select2').select2({
|
11
|
-
allowClear: true,
|
12
|
-
dropdownAutoWidth: true
|
13
|
-
});
|
14
|
-
});
|
15
|
-
|
16
|
-
});
|
@@ -1,10 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Spree
|
2
4
|
module Admin
|
3
5
|
class VolumePricesController < Spree::Admin::BaseController
|
4
6
|
def destroy
|
5
7
|
@volume_price = Spree::VolumePrice.find(params[:id])
|
6
8
|
@volume_price.destroy
|
7
|
-
|
9
|
+
head :ok
|
8
10
|
end
|
9
11
|
end
|
10
12
|
end
|
data/app/decorators/controllers/solidus_volume_pricing/spree/admin/variants_controller_decorator.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SolidusVolumePricing
|
4
|
+
module Spree
|
5
|
+
module Admin
|
6
|
+
module VariantsControllerDecorator
|
7
|
+
def edit
|
8
|
+
@variant.volume_prices.build if @variant.volume_prices.empty?
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
def volume_prices
|
13
|
+
@product = @variant.product
|
14
|
+
@variant.volume_prices.build if @variant.volume_prices.empty?
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
# this loads the variant for the master variant volume price editing
|
20
|
+
def load_resource_instance
|
21
|
+
parent
|
22
|
+
|
23
|
+
if new_actions.include?(params[:action].to_sym)
|
24
|
+
build_resource
|
25
|
+
elsif params[:id]
|
26
|
+
::Spree::Variant.find(params[:id])
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def location_after_save
|
31
|
+
if @product.master.id == @variant.id && params[:variant].key?(:volume_prices_attributes)
|
32
|
+
return volume_prices_admin_product_variant_url(@product, @variant)
|
33
|
+
end
|
34
|
+
|
35
|
+
super
|
36
|
+
end
|
37
|
+
|
38
|
+
::Spree::Admin::VariantsController.prepend self
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SolidusVolumePricing
|
4
|
+
module Spree
|
5
|
+
module BaseHelperDecorator
|
6
|
+
def self.prepended(base)
|
7
|
+
base.module_eval do
|
8
|
+
def display_volume_price(variant, quantity = 1, user = nil)
|
9
|
+
price_display(variant, quantity: quantity, user: user).price_string
|
10
|
+
end
|
11
|
+
|
12
|
+
def display_volume_price_earning_percent(variant, quantity = 1, user = nil)
|
13
|
+
price_display(variant, quantity: quantity, user: user).earning_percent_string
|
14
|
+
end
|
15
|
+
|
16
|
+
def display_volume_price_earning_amount(variant, quantity = 1, user = nil)
|
17
|
+
price_display(variant, quantity: quantity, user: user).earning_amount_string
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def price_display(variant, quantity:, user:)
|
23
|
+
SolidusVolumePricing::PriceDisplay.new(variant, quantity: quantity, user: user)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
::Spree::BaseHelper.prepend self
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SolidusVolumePricing
|
4
|
+
module Spree
|
5
|
+
module LineItemDecorator
|
6
|
+
def set_pricing_attributes
|
7
|
+
if quantity_changed?
|
8
|
+
options = SolidusVolumePricing::PricingOptions.from_line_item(self)
|
9
|
+
self.money_price = SolidusVolumePricing::Pricer.new(variant).price_for(options)
|
10
|
+
end
|
11
|
+
|
12
|
+
super
|
13
|
+
end
|
14
|
+
|
15
|
+
::Spree::LineItem.prepend self
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SolidusVolumePricing
|
4
|
+
module Spree
|
5
|
+
module UserDecorator
|
6
|
+
def resolve_role
|
7
|
+
if has_spree_role? ::Spree::Config.volume_pricing_role.to_sym
|
8
|
+
::Spree::Role.find_by name: ::Spree::Config.volume_pricing_role
|
9
|
+
else
|
10
|
+
::Spree::Role.find_by name: 'user'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
::Spree.user_class.prepend self
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SolidusVolumePricing
|
4
|
+
module Spree
|
5
|
+
module VariantDecorator
|
6
|
+
def self.prepended(base)
|
7
|
+
base.class_eval do
|
8
|
+
has_and_belongs_to_many :volume_price_models
|
9
|
+
has_many :volume_prices, -> { order(position: :asc) }, dependent: :destroy
|
10
|
+
has_many :model_volume_prices, -> { order(position: :asc) }, class_name: '::Spree::VolumePrice', through: :volume_price_models, source: :volume_prices
|
11
|
+
accepts_nested_attributes_for :volume_prices, allow_destroy: true,
|
12
|
+
reject_if: proc { |volume_price|
|
13
|
+
volume_price[:amount].blank? && volume_price[:range].blank?
|
14
|
+
}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
::Spree::Variant.prepend self
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SolidusVolumePricing
|
4
|
+
class PriceDisplay
|
5
|
+
attr_reader :variant, :quantity, :user
|
6
|
+
|
7
|
+
def initialize(variant, quantity: 1, user: nil)
|
8
|
+
@variant = variant
|
9
|
+
@quantity = quantity
|
10
|
+
@user = user
|
11
|
+
end
|
12
|
+
|
13
|
+
def price_string
|
14
|
+
price.to_s
|
15
|
+
end
|
16
|
+
|
17
|
+
def earning_amount_string
|
18
|
+
earning_amount.to_s
|
19
|
+
end
|
20
|
+
|
21
|
+
def earning_percent_string
|
22
|
+
earning_percent.to_s
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def price
|
28
|
+
pricer.price_for(options)
|
29
|
+
end
|
30
|
+
|
31
|
+
def earning_amount
|
32
|
+
pricer.earning_amount(options)
|
33
|
+
end
|
34
|
+
|
35
|
+
def earning_percent
|
36
|
+
pricer.earning_percent(options)
|
37
|
+
end
|
38
|
+
|
39
|
+
def options
|
40
|
+
SolidusVolumePricing::PricingOptions.new(quantity: quantity, user: user)
|
41
|
+
end
|
42
|
+
|
43
|
+
def pricer
|
44
|
+
SolidusVolumePricing::Pricer.new(variant)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SolidusVolumePricing
|
4
|
+
class Pricer < ::Spree::Variant::PriceSelector
|
5
|
+
attr_reader :quantity, :user
|
6
|
+
|
7
|
+
def self.pricing_options_class
|
8
|
+
SolidusVolumePricing::PricingOptions
|
9
|
+
end
|
10
|
+
|
11
|
+
def price_for(pricing_options)
|
12
|
+
extract_options(pricing_options)
|
13
|
+
::Spree::Money.new(computed_price)
|
14
|
+
end
|
15
|
+
|
16
|
+
def earning_amount(pricing_options)
|
17
|
+
extract_options(pricing_options)
|
18
|
+
::Spree::Money.new(computed_earning)
|
19
|
+
end
|
20
|
+
|
21
|
+
def earning_percent(pricing_options)
|
22
|
+
extract_options(pricing_options)
|
23
|
+
computed_earning_percent.round
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def extract_options(pricing_options)
|
29
|
+
@quantity = pricing_options.quantity
|
30
|
+
@user = pricing_options.user
|
31
|
+
end
|
32
|
+
|
33
|
+
def use_master_variant_volume_pricing?
|
34
|
+
::Spree::Config.use_master_variant_volume_pricing && @variant.volume_prices.empty?
|
35
|
+
end
|
36
|
+
|
37
|
+
def variant
|
38
|
+
if use_master_variant_volume_pricing?
|
39
|
+
super.product.master
|
40
|
+
else
|
41
|
+
super
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def volume_prices
|
46
|
+
::Spree::VolumePrice.for_variant(variant, user: user)
|
47
|
+
end
|
48
|
+
|
49
|
+
def volume_price
|
50
|
+
volume_prices.detect do |volume_price|
|
51
|
+
volume_price.include?(quantity)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def computed_price
|
56
|
+
case volume_price&.discount_type
|
57
|
+
when 'price'
|
58
|
+
volume_price.amount
|
59
|
+
when 'dollar'
|
60
|
+
variant.price - volume_price.amount
|
61
|
+
when 'percent'
|
62
|
+
variant.price * (1 - volume_price.amount)
|
63
|
+
else
|
64
|
+
variant.price
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def computed_earning
|
69
|
+
case volume_price&.discount_type
|
70
|
+
when 'price'
|
71
|
+
variant.price - volume_price.amount
|
72
|
+
when 'dollar'
|
73
|
+
volume_price.amount
|
74
|
+
when 'percent'
|
75
|
+
variant.price - (variant.price * (1 - volume_price.amount))
|
76
|
+
else
|
77
|
+
0
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def computed_earning_percent
|
82
|
+
case volume_price&.discount_type
|
83
|
+
when 'price'
|
84
|
+
diff = variant.price - volume_price.amount
|
85
|
+
diff * 100 / variant.price
|
86
|
+
when 'dollar'
|
87
|
+
volume_price.amount * 100 / variant.price
|
88
|
+
when 'percent'
|
89
|
+
volume_price.amount * 100
|
90
|
+
else
|
91
|
+
0
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SolidusVolumePricing
|
4
|
+
class PricingOptions < ::Spree::Variant::PricingOptions
|
5
|
+
attr_accessor :quantity, :user
|
6
|
+
|
7
|
+
def initialize(options = {})
|
8
|
+
super options.except(:quantity, :user)
|
9
|
+
@quantity = options.delete(:quantity)
|
10
|
+
@user = options.delete(:user)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.from_line_item(line_item)
|
14
|
+
pricing_options = super(line_item)
|
15
|
+
pricing_options.quantity = line_item.quantity
|
16
|
+
pricing_options.user = line_item.order.user
|
17
|
+
pricing_options
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -1,35 +1,52 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
belongs_to :
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Spree::VolumePrice < ApplicationRecord
|
4
|
+
belongs_to :variant, touch: true, optional: true
|
5
|
+
belongs_to :volume_price_model, touch: true, optional: true
|
6
|
+
belongs_to :spree_role, class_name: 'Spree::Role', foreign_key: 'role_id', optional: true
|
5
7
|
acts_as_list scope: [:variant_id, :volume_price_model_id]
|
6
8
|
|
7
9
|
validates :amount, presence: true
|
8
10
|
validates :discount_type,
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
11
|
+
presence: true,
|
12
|
+
inclusion: {
|
13
|
+
in: %w(price dollar percent)
|
14
|
+
}
|
15
|
+
|
16
|
+
validate :range_format
|
17
|
+
|
18
|
+
def self.for_variant(variant, user: nil)
|
19
|
+
roles = [nil]
|
20
|
+
if user
|
21
|
+
roles << user.resolve_role&.id
|
22
|
+
end
|
23
|
+
|
24
|
+
where(
|
25
|
+
arel_table[:variant_id].eq(variant.id).
|
26
|
+
or(
|
27
|
+
arel_table[:volume_price_model_id].in(variant.volume_price_model_ids)
|
28
|
+
)
|
29
|
+
).
|
30
|
+
where(role_id: roles).
|
31
|
+
order(position: :asc, amount: :asc)
|
32
|
+
end
|
33
|
+
|
34
|
+
delegate :include?, to: :range_from_string
|
35
|
+
|
36
|
+
def display_range
|
37
|
+
range.gsub(/\.+/, "-").gsub(/\(|\)/, '')
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def range_format
|
43
|
+
if !(SolidusVolumePricing::RangeFromString::RANGE_FORMAT =~ range ||
|
44
|
+
SolidusVolumePricing::RangeFromString::OPEN_ENDED =~ range)
|
45
|
+
errors.add(:range, :must_be_in_format)
|
28
46
|
end
|
29
47
|
end
|
30
48
|
|
31
|
-
|
32
|
-
|
33
|
-
OPEN_ENDED =~ range
|
49
|
+
def range_from_string
|
50
|
+
SolidusVolumePricing::RangeFromString.new(range).to_range
|
34
51
|
end
|
35
52
|
end
|
@@ -1,8 +1,10 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Spree::VolumePriceModel < ApplicationRecord
|
2
4
|
has_many :variants
|
3
5
|
has_many :volume_prices, -> { order(position: :asc) }, dependent: :destroy
|
4
6
|
accepts_nested_attributes_for :volume_prices, allow_destroy: true,
|
5
|
-
|
6
|
-
|
7
|
-
|
7
|
+
reject_if: proc { |volume_price|
|
8
|
+
volume_price[:amount].blank? && volume_price[:range].blank?
|
9
|
+
}
|
8
10
|
end
|