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.

Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -1
  3. data/app/assets/images/noimage/large.png +0 -0
  4. data/app/assets/images/noimage/mini.png +0 -0
  5. data/app/assets/images/noimage/product.png +0 -0
  6. data/app/assets/images/noimage/small.png +0 -0
  7. data/app/models/spree/app_configuration.rb +9 -9
  8. data/app/models/spree/country.rb +1 -0
  9. data/app/models/spree/line_item.rb +7 -1
  10. data/app/models/spree/payment_method.rb +1 -1
  11. data/app/models/spree/preferences/preferable.rb +1 -0
  12. data/app/models/spree/preferences/statically_configurable.rb +2 -2
  13. data/app/models/spree/price.rb +30 -2
  14. data/app/models/spree/product.rb +4 -1
  15. data/app/models/spree/stock/inventory_unit_builder.rb +2 -2
  16. data/app/models/spree/tax/tax_location.rb +4 -0
  17. data/app/models/spree/tax_rate.rb +2 -11
  18. data/app/models/spree/variant/price_selector.rb +35 -0
  19. data/app/models/spree/variant/pricing_options.rb +69 -4
  20. data/app/models/spree/variant/vat_price_generator.rb +58 -0
  21. data/app/models/spree/variant.rb +38 -16
  22. data/config/locales/en.yml +22 -1
  23. data/db/migrate/20140410141842_add_many_missing_indexes.rb +15 -13
  24. data/db/migrate/20140410150358_correct_some_polymorphic_index_and_add_more_missing.rb +40 -38
  25. data/db/migrate/20141217215630_update_product_slug_index.rb +4 -2
  26. data/db/migrate/20150723224133_remove_unnecessary_indexes.rb +2 -10
  27. data/db/migrate/20151219020209_add_stock_item_unique_index.rb +2 -2
  28. data/db/migrate/20160509181311_add_country_iso_to_prices.rb +8 -0
  29. data/lib/spree/core/class_constantizer.rb +31 -0
  30. data/lib/spree/core/engine.rb +55 -59
  31. data/lib/spree/core/environment/calculators.rb +6 -1
  32. data/lib/spree/core/environment.rb +5 -2
  33. data/lib/spree/core/environment_extension.rb +16 -12
  34. data/lib/spree/core/price_migrator.rb +32 -0
  35. data/lib/spree/core/search/base.rb +2 -2
  36. data/lib/spree/core/version.rb +1 -1
  37. data/lib/spree/core.rb +4 -0
  38. data/lib/spree/migration_helpers.rb +19 -0
  39. data/lib/spree/money.rb +41 -15
  40. data/lib/spree/promo/environment.rb +2 -1
  41. data/lib/spree/testing_support/controller_requests.rb +22 -7
  42. data/lib/spree/testing_support/factories/state_factory.rb +7 -0
  43. data/lib/spree/testing_support/factories/stock_location_factory.rb +4 -1
  44. data/lib/tasks/migrations/create_vat_prices.rake +11 -0
  45. data/lib/tasks/upgrade.rake +2 -1
  46. data/spec/lib/spree/core/class_constantizer_spec.rb +68 -0
  47. data/spec/lib/spree/core/environment_extension_spec.rb +33 -0
  48. data/spec/lib/spree/core/price_migrator_spec.rb +356 -0
  49. data/spec/lib/spree/core/testing_support/factories/state_factory_spec.rb +9 -0
  50. data/spec/lib/spree/core/testing_support/factories/stock_location_factory_spec.rb +9 -0
  51. data/spec/lib/spree/money_spec.rb +75 -0
  52. data/spec/models/spree/app_configuration_spec.rb +5 -5
  53. data/spec/models/spree/country_spec.rb +16 -0
  54. data/spec/models/spree/line_item_spec.rb +6 -2
  55. data/spec/models/spree/preferences/preferable_spec.rb +5 -0
  56. data/spec/models/spree/preferences/statically_configurable_spec.rb +4 -0
  57. data/spec/models/spree/price_spec.rb +89 -0
  58. data/spec/models/spree/stock/coordinator_spec.rb +9 -0
  59. data/spec/models/spree/stock/splitter/shipping_category_spec.rb +30 -32
  60. data/spec/models/spree/tax/tax_location_spec.rb +14 -5
  61. data/spec/models/spree/tax/taxation_integration_spec.rb +15 -42
  62. data/spec/models/spree/variant/{pricer_spec.rb → price_selector_spec.rb} +41 -1
  63. data/spec/models/spree/variant/pricing_options_spec.rb +87 -4
  64. data/spec/models/spree/variant/vat_price_generator_spec.rb +69 -0
  65. data/spec/models/spree/variant_spec.rb +57 -8
  66. metadata +14 -5
  67. data/app/models/spree/product_scope/scopes.rb +0 -47
  68. data/app/models/spree/variant/pricer.rb +0 -19
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8d35bbf151696ae5b75db7416fff2c8e284ca9b8
4
- data.tar.gz: 8e522de0b131af298e20285e0538f9619f9f1319
3
+ metadata.gz: 9448c7bb96e59ed1cb4f56c29f66060c99a0abd5
4
+ data.tar.gz: e0cde0bf71c48efcaece95c384d457b4dd3c59ef
5
5
  SHA512:
6
- metadata.gz: 0738f730422f1fab50ac319b097afb20c9839a67e9777fc4d2d8aa3eb36c5d5ea2c01e82858945e27e5f2f96d1eaf0525bad5a265d19ecfb4f5d214f268406c8
7
- data.tar.gz: 841e663aa453b91bc9e7c2d01002c33270bbd628aaef244daf840e6410381eca2a0df8606a37c3bd45b690498c8706b673e609f85082773e0fe9047928ebe672
6
+ metadata.gz: a24c63de0a3f6303aa9099fff4340f75c488a830f5a6443eafa9ec3add8a85f7d14f11085d1d7bc567661cd390676fc13c0781514dbc3a7598d31484745a4aa5
7
+ data.tar.gz: de47427d22d7d72d347f79347074844cdf87d1e0f3e5f3d8afbb312077c6be5e422bc0733b4bbaf0c6ddf5d3d151f82e3667c8e748eadc66af2862e2c4f7fd0d
data/Gemfile CHANGED
@@ -1,3 +1,3 @@
1
- eval_gemfile File.expand_path('../../common_spree_dependencies.rb', __FILE__)
1
+ eval(File.read(File.dirname(__FILE__) + '/../common_spree_dependencies.rb'))
2
2
 
3
3
  gemspec
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] variant_pricer_class
285
- # @see Spree::Variant::Pricer
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 pricer class Spree::Variant::Pricer.
288
- attr_writer :variant_pricer_class
289
- def variant_pricer_class
290
- @variant_pricer_class ||= Spree::Variant::Pricer
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 pricer's pricing options class
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: :variant_pricer_class
296
+ delegate :pricing_options_class, to: :variant_price_selector_class
297
297
 
298
298
  # Shortcut for the default pricing options
299
- # @return [variant_pricer_class] An instance of the pricing options class with default desired
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
@@ -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 :payment_methods, through: :store_payment_methods
12
+ has_many :stores, through: :store_payment_methods
13
13
 
14
14
  scope :ordered_by_position, -> { order(:position) }
15
15
 
@@ -97,6 +97,7 @@ module Spree::Preferences::Preferable
97
97
  private
98
98
 
99
99
  def convert_preference_value(value, type)
100
+ return nil if value.nil?
100
101
  case type
101
102
  when :string, :text
102
103
  value.to_s
@@ -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
- super
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
- super
32
+ self[:preferences] = val
33
33
  end
34
34
  end
35
35
  end
@@ -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 = ['amount']
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
@@ -59,7 +59,10 @@ module Spree
59
59
  master || build_master
60
60
  end
61
61
 
62
- MASTER_ATTRIBUTES = [:sku, :price, :currency, :display_amount, :display_price, :weight, :height, :width, :depth, :cost_currency, :price_in, :price_for, :amount_in, :cost_price]
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 |_i|
11
- @order.inventory_units.build(
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,
@@ -25,6 +25,10 @@ module Spree
25
25
  state_id == other.state_id && country_id == other.country_id
26
26
  end
27
27
 
28
+ def country
29
+ Spree::Country.find_by(id: country_id)
30
+ end
31
+
28
32
  def empty?
29
33
  country_id.nil? && state_id.nil?
30
34
  end
@@ -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
- { currency: Spree::Config.currency }
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
- new(currency: line_item.order.try(:currency) || line_item.currency || Spree::Config.currency )
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
@@ -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 pricer class
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.desired_attributes))
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 pricer class for this 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::Pricer] The default pricer class
229
- def pricer
230
- @pricer ||= Spree::Config.variant_pricer_class.new(self)
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::Pricer#price_for
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: :pricer
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
- if is_master?
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
@@ -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/reimbursement_credit:
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
- add_index :spree_adjustments, [:adjustable_id, :adjustable_type]
4
- add_index :spree_adjustments, :eligible
5
- add_index :spree_adjustments, :order_id
6
- add_index :spree_promotions, :code
7
- add_index :spree_promotions, :expires_at
8
- add_index :spree_states, :country_id
9
- add_index :spree_stock_items, :deleted_at
10
- add_index :spree_option_types, :position
11
- add_index :spree_option_values, :position
12
- add_index :spree_product_option_types, :option_type_id
13
- add_index :spree_product_option_types, :product_id
14
- add_index :spree_products_taxons, :position
15
- add_index :spree_promotions, :starts_at
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