solidus_core 1.3.0.beta1 → 1.3.0.rc1
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.
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
|