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.

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