solidus_core 1.3.0.beta1 → 1.3.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of solidus_core might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Gemfile +1 -1
- data/app/assets/images/noimage/large.png +0 -0
- data/app/assets/images/noimage/mini.png +0 -0
- data/app/assets/images/noimage/product.png +0 -0
- data/app/assets/images/noimage/small.png +0 -0
- data/app/models/spree/app_configuration.rb +9 -9
- data/app/models/spree/country.rb +1 -0
- data/app/models/spree/line_item.rb +7 -1
- data/app/models/spree/payment_method.rb +1 -1
- data/app/models/spree/preferences/preferable.rb +1 -0
- data/app/models/spree/preferences/statically_configurable.rb +2 -2
- data/app/models/spree/price.rb +30 -2
- data/app/models/spree/product.rb +4 -1
- data/app/models/spree/stock/inventory_unit_builder.rb +2 -2
- data/app/models/spree/tax/tax_location.rb +4 -0
- data/app/models/spree/tax_rate.rb +2 -11
- data/app/models/spree/variant/price_selector.rb +35 -0
- data/app/models/spree/variant/pricing_options.rb +69 -4
- data/app/models/spree/variant/vat_price_generator.rb +58 -0
- data/app/models/spree/variant.rb +38 -16
- data/config/locales/en.yml +22 -1
- data/db/migrate/20140410141842_add_many_missing_indexes.rb +15 -13
- data/db/migrate/20140410150358_correct_some_polymorphic_index_and_add_more_missing.rb +40 -38
- data/db/migrate/20141217215630_update_product_slug_index.rb +4 -2
- data/db/migrate/20150723224133_remove_unnecessary_indexes.rb +2 -10
- data/db/migrate/20151219020209_add_stock_item_unique_index.rb +2 -2
- data/db/migrate/20160509181311_add_country_iso_to_prices.rb +8 -0
- data/lib/spree/core/class_constantizer.rb +31 -0
- data/lib/spree/core/engine.rb +55 -59
- data/lib/spree/core/environment/calculators.rb +6 -1
- data/lib/spree/core/environment.rb +5 -2
- data/lib/spree/core/environment_extension.rb +16 -12
- data/lib/spree/core/price_migrator.rb +32 -0
- data/lib/spree/core/search/base.rb +2 -2
- data/lib/spree/core/version.rb +1 -1
- data/lib/spree/core.rb +4 -0
- data/lib/spree/migration_helpers.rb +19 -0
- data/lib/spree/money.rb +41 -15
- data/lib/spree/promo/environment.rb +2 -1
- data/lib/spree/testing_support/controller_requests.rb +22 -7
- data/lib/spree/testing_support/factories/state_factory.rb +7 -0
- data/lib/spree/testing_support/factories/stock_location_factory.rb +4 -1
- data/lib/tasks/migrations/create_vat_prices.rake +11 -0
- data/lib/tasks/upgrade.rake +2 -1
- data/spec/lib/spree/core/class_constantizer_spec.rb +68 -0
- data/spec/lib/spree/core/environment_extension_spec.rb +33 -0
- data/spec/lib/spree/core/price_migrator_spec.rb +356 -0
- data/spec/lib/spree/core/testing_support/factories/state_factory_spec.rb +9 -0
- data/spec/lib/spree/core/testing_support/factories/stock_location_factory_spec.rb +9 -0
- data/spec/lib/spree/money_spec.rb +75 -0
- data/spec/models/spree/app_configuration_spec.rb +5 -5
- data/spec/models/spree/country_spec.rb +16 -0
- data/spec/models/spree/line_item_spec.rb +6 -2
- data/spec/models/spree/preferences/preferable_spec.rb +5 -0
- data/spec/models/spree/preferences/statically_configurable_spec.rb +4 -0
- data/spec/models/spree/price_spec.rb +89 -0
- data/spec/models/spree/stock/coordinator_spec.rb +9 -0
- data/spec/models/spree/stock/splitter/shipping_category_spec.rb +30 -32
- data/spec/models/spree/tax/tax_location_spec.rb +14 -5
- data/spec/models/spree/tax/taxation_integration_spec.rb +15 -42
- data/spec/models/spree/variant/{pricer_spec.rb → price_selector_spec.rb} +41 -1
- data/spec/models/spree/variant/pricing_options_spec.rb +87 -4
- data/spec/models/spree/variant/vat_price_generator_spec.rb +69 -0
- data/spec/models/spree/variant_spec.rb +57 -8
- metadata +14 -5
- data/app/models/spree/product_scope/scopes.rb +0 -47
- data/app/models/spree/variant/pricer.rb +0 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9448c7bb96e59ed1cb4f56c29f66060c99a0abd5
|
4
|
+
data.tar.gz: e0cde0bf71c48efcaece95c384d457b4dd3c59ef
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a24c63de0a3f6303aa9099fff4340f75c488a830f5a6443eafa9ec3add8a85f7d14f11085d1d7bc567661cd390676fc13c0781514dbc3a7598d31484745a4aa5
|
7
|
+
data.tar.gz: de47427d22d7d72d347f79347074844cdf87d1e0f3e5f3d8afbb312077c6be5e422bc0733b4bbaf0c6ddf5d3d151f82e3667c8e748eadc66af2862e2c4f7fd0d
|
data/Gemfile
CHANGED
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -281,22 +281,22 @@ module Spree
|
|
281
281
|
end
|
282
282
|
|
283
283
|
# Allows implementing custom pricing for variants
|
284
|
-
# @!attribute [rw]
|
285
|
-
# @see Spree::Variant::
|
284
|
+
# @!attribute [rw] variant_price_selector_class
|
285
|
+
# @see Spree::Variant::PriceSelector
|
286
286
|
# @return [Class] an object that conforms to the API of
|
287
|
-
# the standard variant
|
288
|
-
attr_writer :
|
289
|
-
def
|
290
|
-
@
|
287
|
+
# the standard variant price selector class Spree::Variant::PriceSelector.
|
288
|
+
attr_writer :variant_price_selector_class
|
289
|
+
def variant_price_selector_class
|
290
|
+
@variant_price_selector_class ||= Spree::Variant::PriceSelector
|
291
291
|
end
|
292
292
|
|
293
|
-
# Shortcut for getting the variant
|
293
|
+
# Shortcut for getting the variant price selector's pricing options class
|
294
294
|
#
|
295
295
|
# @return [Class] The pricing options class to be used
|
296
|
-
delegate :pricing_options_class, to: :
|
296
|
+
delegate :pricing_options_class, to: :variant_price_selector_class
|
297
297
|
|
298
298
|
# Shortcut for the default pricing options
|
299
|
-
# @return [
|
299
|
+
# @return [variant_price_selector_class] An instance of the pricing options class with default desired
|
300
300
|
# attributes
|
301
301
|
def default_pricing_options
|
302
302
|
@default_pricing_options ||= pricing_options_class.new
|
data/app/models/spree/country.rb
CHANGED
@@ -2,6 +2,7 @@ module Spree
|
|
2
2
|
class Country < Spree::Base
|
3
3
|
has_many :states, -> { order(:name) }, dependent: :destroy
|
4
4
|
has_many :addresses, dependent: :nullify
|
5
|
+
has_many :prices, class_name: "Spree::Price", foreign_key: "country_iso", primary_key: "iso"
|
5
6
|
|
6
7
|
validates :name, :iso_name, presence: true
|
7
8
|
|
@@ -102,14 +102,20 @@ module Spree
|
|
102
102
|
!sufficient_stock?
|
103
103
|
end
|
104
104
|
|
105
|
-
# Sets options on the line item.
|
105
|
+
# Sets options on the line item and updates the price.
|
106
106
|
#
|
107
107
|
# The options can be arbitrary attributes on the LineItem.
|
108
108
|
#
|
109
109
|
# @param options [Hash] options for this line item
|
110
110
|
def options=(options = {})
|
111
111
|
return unless options.present?
|
112
|
+
|
112
113
|
assign_attributes options
|
114
|
+
|
115
|
+
# There's no need to call a pricer if we'll set the price directly.
|
116
|
+
unless options.key?(:price) || options.key?('price')
|
117
|
+
self.money_price = variant.price_for(pricing_options)
|
118
|
+
end
|
113
119
|
end
|
114
120
|
|
115
121
|
def pricing_options
|
@@ -9,7 +9,7 @@ module Spree
|
|
9
9
|
has_many :payments, class_name: "Spree::Payment", inverse_of: :payment_method
|
10
10
|
has_many :credit_cards, class_name: "Spree::CreditCard"
|
11
11
|
has_many :store_payment_methods, inverse_of: :payment_method
|
12
|
-
has_many :
|
12
|
+
has_many :stores, through: :store_payment_methods
|
13
13
|
|
14
14
|
scope :ordered_by_position, -> { order(:position) }
|
15
15
|
|
@@ -22,14 +22,14 @@ module Spree
|
|
22
22
|
if respond_to?(:preference_source) && preference_source
|
23
23
|
self.class.preference_sources[preference_source] || {}
|
24
24
|
else
|
25
|
-
|
25
|
+
self[:preferences]
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
29
|
def preferences=(val)
|
30
30
|
if respond_to?(:preference_source) && preference_source
|
31
31
|
else
|
32
|
-
|
32
|
+
self[:preferences] = val
|
33
33
|
end
|
34
34
|
end
|
35
35
|
end
|
data/app/models/spree/price.rb
CHANGED
@@ -5,14 +5,21 @@ module Spree
|
|
5
5
|
MAXIMUM_AMOUNT = BigDecimal('99_999_999.99')
|
6
6
|
|
7
7
|
belongs_to :variant, -> { with_deleted }, class_name: 'Spree::Variant', touch: true
|
8
|
+
belongs_to :country, class_name: "Spree::Country", foreign_key: "country_iso", primary_key: "iso"
|
9
|
+
|
10
|
+
delegate :product, to: :variant
|
11
|
+
delegate :tax_rates, to: :variant
|
8
12
|
|
9
13
|
validate :check_price
|
10
14
|
validates :amount, allow_nil: true, numericality: {
|
11
15
|
greater_than_or_equal_to: 0,
|
12
16
|
less_than_or_equal_to: MAXIMUM_AMOUNT
|
13
17
|
}
|
18
|
+
validates :currency, inclusion: { in: ::Money::Currency.all.map(&:iso_code), message: :invalid_code }
|
19
|
+
validates :country, presence: true, unless: -> { for_any_country? }
|
14
20
|
|
15
|
-
scope :currently_valid, -> { where(is_default: true) }
|
21
|
+
scope :currently_valid, -> { where(is_default: true).order("country_iso IS NULL") }
|
22
|
+
scope :for_any_country, -> { where(country: nil) }
|
16
23
|
scope :with_default_attributes, -> { where(Spree::Config.default_pricing_options.desired_attributes) }
|
17
24
|
after_save :set_default_price
|
18
25
|
|
@@ -20,7 +27,7 @@ module Spree
|
|
20
27
|
money_methods :amount, :price
|
21
28
|
alias_method :money, :display_amount
|
22
29
|
|
23
|
-
self.whitelisted_ransackable_attributes =
|
30
|
+
self.whitelisted_ransackable_attributes = %w( amount variant_id currency country_iso )
|
24
31
|
|
25
32
|
# An alias for #amount
|
26
33
|
def price
|
@@ -35,8 +42,29 @@ module Spree
|
|
35
42
|
self[:amount] = Spree::LocalizedNumber.parse(price)
|
36
43
|
end
|
37
44
|
|
45
|
+
def net_amount
|
46
|
+
amount / (1 + sum_of_vat_amounts)
|
47
|
+
end
|
48
|
+
|
49
|
+
def for_any_country?
|
50
|
+
country_iso.nil?
|
51
|
+
end
|
52
|
+
|
53
|
+
def display_country
|
54
|
+
if country_iso
|
55
|
+
"#{country_iso} (#{country.name})"
|
56
|
+
else
|
57
|
+
I18n.t(:any_country, scope: [:spree, :admin, :prices])
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
38
61
|
private
|
39
62
|
|
63
|
+
def sum_of_vat_amounts
|
64
|
+
return 0 unless variant.tax_category
|
65
|
+
tax_rates.included_in_price.for_country(country).sum(:amount)
|
66
|
+
end
|
67
|
+
|
40
68
|
def check_price
|
41
69
|
self.currency ||= Spree::Config[:currency]
|
42
70
|
end
|
data/app/models/spree/product.rb
CHANGED
@@ -59,7 +59,10 @@ module Spree
|
|
59
59
|
master || build_master
|
60
60
|
end
|
61
61
|
|
62
|
-
MASTER_ATTRIBUTES = [
|
62
|
+
MASTER_ATTRIBUTES = [
|
63
|
+
:rebuild_vat_prices, :sku, :price, :currency, :display_amount, :display_price, :weight,
|
64
|
+
:height, :width, :depth, :cost_currency, :price_in, :price_for, :amount_in, :cost_price
|
65
|
+
]
|
63
66
|
MASTER_ATTRIBUTES.each do |attr|
|
64
67
|
delegate :"#{attr}", :"#{attr}=", to: :find_or_build_master
|
65
68
|
end
|
@@ -7,8 +7,8 @@ module Spree
|
|
7
7
|
|
8
8
|
def units
|
9
9
|
@order.line_items.flat_map do |line_item|
|
10
|
-
Array.new(line_item.quantity) do
|
11
|
-
|
10
|
+
Array.new(line_item.quantity) do
|
11
|
+
Spree::InventoryUnit.new(
|
12
12
|
pending: true,
|
13
13
|
variant: line_item.variant,
|
14
14
|
line_item: line_item,
|
@@ -1,13 +1,3 @@
|
|
1
|
-
module Spree
|
2
|
-
class DefaultTaxZoneValidator < ActiveModel::Validator
|
3
|
-
def validate(record)
|
4
|
-
if record.included_in_price
|
5
|
-
record.errors.add(:included_in_price, Spree.t(:included_price_validation)) unless Zone.default_tax
|
6
|
-
end
|
7
|
-
end
|
8
|
-
end
|
9
|
-
end
|
10
|
-
|
11
1
|
module Spree
|
12
2
|
class TaxRate < Spree::Base
|
13
3
|
acts_as_paranoid
|
@@ -26,10 +16,11 @@ module Spree
|
|
26
16
|
|
27
17
|
validates :amount, presence: true, numericality: true
|
28
18
|
validates :tax_category_id, presence: true
|
29
|
-
validates_with DefaultTaxZoneValidator
|
30
19
|
|
31
20
|
# Finds all tax rates whose zones match a given address
|
32
21
|
scope :for_address, ->(address) { joins(:zone).merge(Spree::Zone.for_address(address)) }
|
22
|
+
scope :for_country,
|
23
|
+
->(country) { for_address(Spree::Tax::TaxLocation.new(country: country)) }
|
33
24
|
|
34
25
|
# Finds geographically matching tax rates for a tax zone.
|
35
26
|
# We do not know if they are/aren't applicable until we attempt to apply these rates to
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Spree
|
2
|
+
class Variant
|
3
|
+
# This class is responsible for selecting a price for a variant given certain pricing options.
|
4
|
+
# A variant can have multiple or even dynamic prices. The `price_for`
|
5
|
+
# method determines which price applies under the given circumstances.
|
6
|
+
#
|
7
|
+
class PriceSelector
|
8
|
+
# The pricing options represent "given circumstances" for a price: The currency
|
9
|
+
# we need and the country that the price applies to.
|
10
|
+
# Every price selector is designed to work with a particular set of pricing options
|
11
|
+
# embodied in it's pricing options class.
|
12
|
+
#
|
13
|
+
def self.pricing_options_class
|
14
|
+
Spree::Variant::PricingOptions
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :variant
|
18
|
+
|
19
|
+
def initialize(variant)
|
20
|
+
@variant = variant
|
21
|
+
end
|
22
|
+
|
23
|
+
# The variant's price, given a set of pricing options
|
24
|
+
# @param [Spree::Variant::PricingOptions] price_options Pricing Options to abide by
|
25
|
+
# @return [Spree::Money, nil] The most specific price for this set of pricing options.
|
26
|
+
def price_for(price_options)
|
27
|
+
variant.currently_valid_prices.detect do |price|
|
28
|
+
( price.country_iso == price_options.desired_attributes[:country_iso] ||
|
29
|
+
price.country_iso.nil?
|
30
|
+
) && price.currency == price_options.desired_attributes[:currency]
|
31
|
+
end.try!(:money)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -1,30 +1,95 @@
|
|
1
1
|
module Spree
|
2
2
|
class Variant
|
3
|
+
# Instances of this class represent the set of circumstances that influence how expensive a
|
4
|
+
# variant is. For this particular pricing options class, country_iso and currency influence
|
5
|
+
# the price of a variant.
|
6
|
+
#
|
7
|
+
# Pricing options can be instantiated from a line item or from the view context:
|
8
|
+
# @see Spree::LineItem#pricing_options
|
9
|
+
# @see Spree::Core::ControllerHelpers::Pricing#current_pricing_options
|
10
|
+
#
|
3
11
|
class PricingOptions
|
12
|
+
# When editing variants in the admin, this is the standard price the admin interacts with:
|
13
|
+
# The price in the admin's globally configured currency, for the admin's globally configured
|
14
|
+
# country. These options get merged with any options the user provides when instantiating
|
15
|
+
# new pricing options.
|
16
|
+
# @see Spree::Config.default_pricing_options
|
17
|
+
# @see #initialize
|
18
|
+
# @return [Hash] The attributes that admin prices usually have
|
19
|
+
#
|
4
20
|
def self.default_price_attributes
|
5
|
-
{
|
21
|
+
{
|
22
|
+
currency: Spree::Config.currency,
|
23
|
+
country_iso: Spree::Config.admin_vat_country_iso
|
24
|
+
}
|
6
25
|
end
|
7
26
|
|
27
|
+
# This creates the correct pricing options for a line item, taking into account
|
28
|
+
# its currency and tax address country, if available.
|
29
|
+
# @see Spree::LineItem#set_pricing_attributes
|
30
|
+
# @see Spree::LineItem#pricing_options
|
31
|
+
# @return [Spree::Variant::PricingOptions] pricing options for pricing a line item
|
32
|
+
#
|
8
33
|
def self.from_line_item(line_item)
|
9
|
-
|
34
|
+
tax_address = line_item.order.try!(:tax_address)
|
35
|
+
new(
|
36
|
+
currency: line_item.order.try(:currency) || line_item.currency || Spree::Config.currency,
|
37
|
+
country_iso: tax_address && tax_address.country.try!(:iso)
|
38
|
+
)
|
10
39
|
end
|
11
40
|
|
41
|
+
# This creates the correct pricing options for a price, so that we can easily find other prices
|
42
|
+
# with the same pricing-relevant attributes and mark them as non-default.
|
43
|
+
# @see Spree::Price#set_default_price
|
44
|
+
# @return [Spree::Variant::PricingOptions] pricing options for pricing a line item
|
45
|
+
#
|
12
46
|
def self.from_price(price)
|
13
|
-
new(currency: price.currency)
|
47
|
+
new(currency: price.currency, country_iso: price.country_iso)
|
14
48
|
end
|
15
49
|
|
50
|
+
# @return [Hash] The hash of exact desired attributes
|
16
51
|
attr_reader :desired_attributes
|
17
52
|
|
18
53
|
def initialize(desired_attributes = {})
|
19
54
|
@desired_attributes = self.class.default_price_attributes.merge(desired_attributes)
|
20
55
|
end
|
21
56
|
|
57
|
+
# A slightly modified version of the `desired_attributes` Hash. Instead of
|
58
|
+
# having "nil" or an actual country ISO code under the `:country_iso` key,
|
59
|
+
# this creates an array under the country_iso key that includes both the actual
|
60
|
+
# country iso we want and nil as a shorthand for the fallback price.
|
61
|
+
# This is useful so that we can determine the availability of variants by price:
|
62
|
+
# @see Spree::Variant.with_prices
|
63
|
+
# @see Spree::Core::Search::Base#retrieve_products
|
64
|
+
# @return [Hash] arguments to be passed into ActiveRecord.where()
|
65
|
+
#
|
66
|
+
def search_arguments
|
67
|
+
search_arguments = desired_attributes
|
68
|
+
search_arguments[:country_iso] = [desired_attributes[:country_iso], nil].flatten.uniq
|
69
|
+
search_arguments
|
70
|
+
end
|
71
|
+
|
72
|
+
# Shorthand for accessing the currency part of the desired attributes
|
73
|
+
# @return [String,nil] three-digit currency code or nil
|
74
|
+
#
|
22
75
|
def currency
|
23
76
|
desired_attributes[:currency]
|
24
77
|
end
|
25
78
|
|
79
|
+
# Shorthand for accessing the country part of the desired attributes
|
80
|
+
# @return [String,nil] two-digit country code or nil
|
81
|
+
#
|
82
|
+
def country_iso
|
83
|
+
desired_attributes[:country_iso]
|
84
|
+
end
|
85
|
+
|
86
|
+
# Since the current pricing options determine the price to be shown to users,
|
87
|
+
# product pages have to be cached and their caches invalidated using the data
|
88
|
+
# from this object. This method makes it easy to use with Rails `cache` helper.
|
89
|
+
# @return [String] cache key to be used in views
|
90
|
+
#
|
26
91
|
def cache_key
|
27
|
-
desired_attributes.values.map(&:to_s).join("/")
|
92
|
+
desired_attributes.values.select(&:present?).map(&:to_s).join("/")
|
28
93
|
end
|
29
94
|
end
|
30
95
|
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Spree
|
2
|
+
class Variant
|
3
|
+
# This class generates gross prices for all countries that have VAT configured.
|
4
|
+
# The prices will include their respective VAT rates. It will also generate an
|
5
|
+
# export (net) price for any country that doesn't have VAT.
|
6
|
+
# @example
|
7
|
+
# The admin is configured to show German gross prices
|
8
|
+
# (Spree::Config.admin_vat_country_iso == "DE")
|
9
|
+
#
|
10
|
+
# There is VATs configured for Germany (19%) and Finland (24%).
|
11
|
+
# The VAT price generator is run on a variant with a base (German) price of 10.00.
|
12
|
+
# The Price Generator will generate:
|
13
|
+
# - A price for Finland (country_id == "FI"): 10.42
|
14
|
+
# - A price for any other country (country_id == nil): 8.40
|
15
|
+
#
|
16
|
+
class VatPriceGenerator
|
17
|
+
attr_reader :variant
|
18
|
+
|
19
|
+
def initialize(variant)
|
20
|
+
@variant = variant
|
21
|
+
end
|
22
|
+
|
23
|
+
def run
|
24
|
+
# Early return if there is no VAT rates in the current store.
|
25
|
+
return if !variant.tax_category || variant_vat_rates.empty?
|
26
|
+
|
27
|
+
country_isos_requiring_price.each do |country_iso|
|
28
|
+
# Don't re-create the default price
|
29
|
+
next if variant.default_price && variant.default_price.country_iso == country_iso
|
30
|
+
|
31
|
+
foreign_price = variant.prices.find_or_initialize_by(
|
32
|
+
country_iso: country_iso,
|
33
|
+
currency: variant.default_price.currency,
|
34
|
+
)
|
35
|
+
|
36
|
+
foreign_price.amount = variant.default_price.net_amount * (1 + vat_for_country_iso(country_iso))
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
# nil is added to the array so we always have an export price.
|
43
|
+
def country_isos_requiring_price
|
44
|
+
return [nil] unless variant.tax_category
|
45
|
+
[nil] + variant_vat_rates.map(&:zone).flat_map(&:countries).flat_map(&:iso)
|
46
|
+
end
|
47
|
+
|
48
|
+
def vat_for_country_iso(country_iso)
|
49
|
+
return 0 unless variant.tax_category
|
50
|
+
variant_vat_rates.for_country(Spree::Country.find_by(iso: country_iso)).sum(:amount)
|
51
|
+
end
|
52
|
+
|
53
|
+
def variant_vat_rates
|
54
|
+
@variant_vat_rates ||= variant.tax_category.tax_rates.where(included_in_price: true)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/app/models/spree/variant.rb
CHANGED
@@ -17,6 +17,7 @@ module Spree
|
|
17
17
|
acts_as_paranoid
|
18
18
|
acts_as_list scope: :product
|
19
19
|
|
20
|
+
attr_writer :rebuild_vat_prices
|
20
21
|
include Spree::DefaultPrice
|
21
22
|
|
22
23
|
belongs_to :product, -> { with_deleted }, touch: true, class_name: 'Spree::Product', inverse_of: :variants
|
@@ -26,6 +27,7 @@ module Spree
|
|
26
27
|
:meta_description, :meta_keywords, :shipping_category,
|
27
28
|
to: :product
|
28
29
|
delegate :tax_category, to: :product, prefix: true
|
30
|
+
delegate :tax_rates, to: :tax_category
|
29
31
|
|
30
32
|
has_many :inventory_units, inverse_of: :variant
|
31
33
|
has_many :line_items, inverse_of: :variant
|
@@ -43,9 +45,19 @@ module Spree
|
|
43
45
|
has_many :prices,
|
44
46
|
class_name: 'Spree::Price',
|
45
47
|
dependent: :destroy,
|
46
|
-
inverse_of: :variant
|
48
|
+
inverse_of: :variant,
|
49
|
+
autosave: true
|
50
|
+
|
51
|
+
has_many :currently_valid_prices,
|
52
|
+
-> { currently_valid },
|
53
|
+
class_name: 'Spree::Price',
|
54
|
+
dependent: :destroy,
|
55
|
+
inverse_of: :variant,
|
56
|
+
autosave: true
|
47
57
|
|
48
58
|
before_validation :set_cost_currency
|
59
|
+
before_validation :set_price
|
60
|
+
before_validation :build_vat_prices, if: -> { rebuild_vat_prices? || new_record? }
|
49
61
|
|
50
62
|
validate :check_price
|
51
63
|
|
@@ -90,10 +102,10 @@ module Spree
|
|
90
102
|
|
91
103
|
# Returns variants that have a price for the given pricing options
|
92
104
|
#
|
93
|
-
# @param pricing_options A Pricing Options object as defined on the
|
105
|
+
# @param pricing_options A Pricing Options object as defined on the price selector class
|
94
106
|
# @return [ActiveRecord::Relation]
|
95
107
|
def self.with_prices(pricing_options = Spree::Config.default_pricing_options)
|
96
|
-
joins(:prices).merge(Spree::Price.currently_valid.where(pricing_options.
|
108
|
+
joins(:prices).merge(Spree::Price.currently_valid.where(pricing_options.search_arguments))
|
97
109
|
end
|
98
110
|
|
99
111
|
# @return [Spree::TaxCategory] the variant's tax category
|
@@ -222,20 +234,20 @@ module Spree
|
|
222
234
|
option_values.detect { |o| o.option_type.name == opt_name }.try(:presentation)
|
223
235
|
end
|
224
236
|
|
225
|
-
# Returns an instance of the globally configured variant
|
237
|
+
# Returns an instance of the globally configured variant price selector class for this variant.
|
226
238
|
# It's cached so we don't create too many objects.
|
227
239
|
#
|
228
|
-
# @return [Spree::Variant::
|
229
|
-
def
|
230
|
-
@
|
240
|
+
# @return [Spree::Variant::PriceSelector] The default price selector class
|
241
|
+
def price_selector
|
242
|
+
@price_selector ||= Spree::Config.variant_price_selector_class.new(self)
|
231
243
|
end
|
232
244
|
|
233
245
|
# Chooses an appropriate price for the given pricing options
|
234
246
|
#
|
235
|
-
# @see Spree::Variant::
|
247
|
+
# @see Spree::Variant::PriceSelector#price_for
|
236
248
|
# @param [Spree::Config.pricing_options_class] An instance of pricing options
|
237
249
|
# @return [Spree::Money] The chosen price as a Money object
|
238
|
-
delegate :price_for, to: :
|
250
|
+
delegate :price_for, to: :price_selector
|
239
251
|
|
240
252
|
# Returns the difference in price from the master variant
|
241
253
|
def price_difference_from_master(pricing_options = Spree::Config.default_pricing_options)
|
@@ -333,6 +345,10 @@ module Spree
|
|
333
345
|
|
334
346
|
private
|
335
347
|
|
348
|
+
def rebuild_vat_prices?
|
349
|
+
@rebuild_vat_prices != "0" && @rebuild_vat_prices
|
350
|
+
end
|
351
|
+
|
336
352
|
def set_master_out_of_stock
|
337
353
|
if product.master && product.master.in_stock?
|
338
354
|
product.master.stock_items.update_all(backorderable: false)
|
@@ -341,14 +357,16 @@ module Spree
|
|
341
357
|
end
|
342
358
|
|
343
359
|
# Ensures a new variant takes the product master price when price is not supplied
|
360
|
+
def set_price
|
361
|
+
if price.nil? && Spree::Config[:require_master_price] && !is_master?
|
362
|
+
raise 'No master variant found to infer price' unless product && product.master
|
363
|
+
self.price = product.master.price
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
344
367
|
def check_price
|
345
|
-
if price.nil? && Spree::Config[:require_master_price]
|
346
|
-
|
347
|
-
errors.add :price, 'Must supply price for variant or master.price for product.'
|
348
|
-
else
|
349
|
-
raise 'No master variant found to infer price' unless product && product.master
|
350
|
-
self.price = product.master.price
|
351
|
-
end
|
368
|
+
if price.nil? && Spree::Config[:require_master_price] && is_master?
|
369
|
+
errors.add :price, 'Must supply price for variant or master.price for product.'
|
352
370
|
end
|
353
371
|
end
|
354
372
|
|
@@ -362,6 +380,10 @@ module Spree
|
|
362
380
|
end
|
363
381
|
end
|
364
382
|
|
383
|
+
def build_vat_prices
|
384
|
+
VatPriceGenerator.new(self).run
|
385
|
+
end
|
386
|
+
|
365
387
|
def set_position
|
366
388
|
update_column(:position, product.variants.maximum(:position).to_i + 1)
|
367
389
|
end
|
data/config/locales/en.yml
CHANGED
@@ -134,6 +134,10 @@ en:
|
|
134
134
|
name: Name
|
135
135
|
preference_source: Preference Source
|
136
136
|
type: Provider
|
137
|
+
spree/price:
|
138
|
+
currency: Currency
|
139
|
+
amount: Price
|
140
|
+
is_default: Currently Valid
|
137
141
|
spree/product:
|
138
142
|
available_on: Available On
|
139
143
|
cost_currency: Cost Currency
|
@@ -189,7 +193,7 @@ en:
|
|
189
193
|
number: Number
|
190
194
|
reimbursement_status: Status
|
191
195
|
total: Total
|
192
|
-
spree/
|
196
|
+
spree/reimbursement/credit:
|
193
197
|
amount: Amount
|
194
198
|
spree/reimbursement_type:
|
195
199
|
name: Name
|
@@ -395,6 +399,9 @@ en:
|
|
395
399
|
spree/payment_method:
|
396
400
|
one: Payment Method
|
397
401
|
other: Payment Methods
|
402
|
+
spree/price:
|
403
|
+
one: Price
|
404
|
+
other: Prices
|
398
405
|
spree/product:
|
399
406
|
one: Product
|
400
407
|
other: Products
|
@@ -523,6 +530,10 @@ en:
|
|
523
530
|
attributes:
|
524
531
|
currency:
|
525
532
|
must_match_order_currency: "Must match order currency"
|
533
|
+
spree/price:
|
534
|
+
attributes:
|
535
|
+
currency:
|
536
|
+
invalid_code: "is not a valid currency code"
|
526
537
|
spree/promotion:
|
527
538
|
attributes:
|
528
539
|
apply_automatically:
|
@@ -763,6 +774,16 @@ en:
|
|
763
774
|
use_product_tax_category: Use Product Tax Category
|
764
775
|
pricing: Pricing
|
765
776
|
pricing_hint: These values are populated from the product details page and can be overridden below
|
777
|
+
prices:
|
778
|
+
any_country: "Any Country"
|
779
|
+
index:
|
780
|
+
amount_greater_than: Amount greater than
|
781
|
+
amount_less_than: Amount less than
|
782
|
+
new_price: New Price
|
783
|
+
edit:
|
784
|
+
edit_price: Edit Price
|
785
|
+
new:
|
786
|
+
new_price: New Price
|
766
787
|
administration: Administration
|
767
788
|
agree_to_privacy_policy: Agree to Privacy Policy
|
768
789
|
agree_to_terms_of_service: Agree to Terms of Service
|
@@ -1,17 +1,19 @@
|
|
1
1
|
class AddManyMissingIndexes < ActiveRecord::Migration
|
2
|
+
include Spree::MigrationHelpers
|
3
|
+
|
2
4
|
def change
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
5
|
+
safe_add_index :spree_adjustments, [:adjustable_id, :adjustable_type]
|
6
|
+
safe_add_index :spree_adjustments, :eligible
|
7
|
+
safe_add_index :spree_adjustments, :order_id
|
8
|
+
safe_add_index :spree_promotions, :code
|
9
|
+
safe_add_index :spree_promotions, :expires_at
|
10
|
+
safe_add_index :spree_states, :country_id
|
11
|
+
safe_add_index :spree_stock_items, :deleted_at
|
12
|
+
safe_add_index :spree_option_types, :position
|
13
|
+
safe_add_index :spree_option_values, :position
|
14
|
+
safe_add_index :spree_product_option_types, :option_type_id
|
15
|
+
safe_add_index :spree_product_option_types, :product_id
|
16
|
+
safe_add_index :spree_products_taxons, :position
|
17
|
+
safe_add_index :spree_promotions, :starts_at
|
16
18
|
end
|
17
19
|
end
|