solidus_core 3.0.6 → 3.1.0

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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/app/helpers/spree/base_helper.rb +1 -1
  3. data/app/helpers/spree/products_helper.rb +1 -1
  4. data/app/models/concerns/spree/active_storage_adapter/attachment.rb +2 -4
  5. data/app/models/concerns/spree/default_price.rb +63 -10
  6. data/app/models/spree/adjustment.rb +6 -5
  7. data/app/models/spree/customer_return.rb +3 -2
  8. data/app/models/spree/image/active_storage_attachment.rb +2 -7
  9. data/app/models/spree/image/paperclip_attachment.rb +2 -2
  10. data/app/models/spree/line_item.rb +2 -2
  11. data/app/models/spree/order.rb +11 -6
  12. data/app/models/spree/order_shipping.rb +9 -6
  13. data/app/models/spree/price.rb +1 -1
  14. data/app/models/spree/product/scopes.rb +5 -5
  15. data/app/models/spree/product.rb +12 -1
  16. data/app/models/spree/promotion/rules/item_total.rb +50 -6
  17. data/app/models/spree/promotion.rb +2 -2
  18. data/app/models/spree/promotion_code.rb +3 -3
  19. data/app/models/spree/refund.rb +0 -8
  20. data/app/models/spree/shipping_rate_tax.rb +1 -1
  21. data/app/models/spree/stock/availability.rb +11 -3
  22. data/app/models/spree/stock/simple_coordinator.rb +0 -10
  23. data/app/models/spree/stock_location.rb +1 -1
  24. data/app/models/spree/store_credit.rb +6 -9
  25. data/app/models/spree/tax_calculator/shipping_rate.rb +1 -1
  26. data/app/models/spree/taxon/active_storage_attachment.rb +2 -2
  27. data/app/models/spree/taxon/paperclip_attachment.rb +3 -3
  28. data/app/models/spree/variant/price_selector.rb +16 -3
  29. data/app/models/spree/variant.rb +26 -16
  30. data/config/locales/en.yml +2 -0
  31. data/db/migrate/20210312061050_change_column_null_on_prices.rb +7 -0
  32. data/lib/generators/solidus/install/install_generator.rb +1 -1
  33. data/lib/generators/solidus/install/templates/config/initializers/spree.rb.tt +3 -1
  34. data/lib/generators/solidus/update/templates/config/initializers/new_solidus_defaults.rb.tt +30 -0
  35. data/lib/generators/solidus/update/update_generator.rb +112 -0
  36. data/lib/generators/spree/dummy/templates/rails/application.rb.tt +0 -1
  37. data/lib/generators/spree/dummy/templates/rails/database.yml +78 -35
  38. data/lib/spree/app_configuration.rb +62 -0
  39. data/lib/spree/core/engine.rb +7 -16
  40. data/lib/spree/core/product_filters.rb +1 -1
  41. data/lib/spree/core/search/base.rb +1 -1
  42. data/lib/spree/core/state_machines/order.rb +1 -1
  43. data/lib/spree/core/validators/email.rb +1 -1
  44. data/lib/spree/core/version.rb +5 -1
  45. data/lib/spree/core/versioned_value.rb +75 -0
  46. data/lib/spree/core.rb +17 -0
  47. data/lib/spree/permitted_attributes.rb +1 -1
  48. data/lib/spree/preferences/configuration.rb +62 -0
  49. data/lib/spree/preferences/preferable.rb +8 -0
  50. data/lib/spree/preferences/preferable_class_methods.rb +5 -3
  51. data/lib/spree/preferences/preference_differentiator.rb +28 -0
  52. data/lib/spree/testing_support/blacklist_urls.rb +1 -1
  53. data/lib/spree/testing_support/dummy_app/database.yml +42 -22
  54. data/lib/spree/testing_support/dummy_app.rb +33 -19
  55. data/lib/tasks/solidus/delete_prices_with_nil_amount.rake +8 -0
  56. metadata +11 -8
  57. data/app/models/spree/tax/shipping_rate_taxer.rb +0 -24
  58. data/lib/tasks/solidus/check_orders_with_invalid_email.rake +0 -18
  59. data/lib/tasks/upgrade.rake +0 -15
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2d1ce3955d5e6602144201d2672f85af4d0c9c27ea04ed25b3bd13945e1a22c7
4
- data.tar.gz: 8726f50bdd8922267dcda829078e4ea4a70a39766098076b1f792145fdfb18b4
3
+ metadata.gz: b6d8a64c0ba51b637a6a5eb82952d5920f388413f74d271920c41b6c34b00b5f
4
+ data.tar.gz: a92bbdc37bd0a9f9a53fe943542ba0f4890af67290faf937de4a57583cdb1ffa
5
5
  SHA512:
6
- metadata.gz: 9517bfb7a7df1a91a16f4c03a1455016be0a328bf2cb58f50da97270d2b221a084539df35be76df57ec7928a23cf3049a0a8dbcfd0d1e60a47242630d462c694
7
- data.tar.gz: e5742706f8c34b9ca310787c6ded79a4203eb3bce38127db4f31ef3276b23cedb2a7849b8226b5110f73124a5dab3e570f8202a38d583cb6c4f9c775b14ab392
6
+ metadata.gz: 0dd269d1a10a861a6d76cba306cf3b42b758773d4fe4e3265986e0abfe318e40ba2468d6431ec89d472b72591a23e19684a20afa37bb48f0be71371e79736e0f
7
+ data.tar.gz: c524310230c534ebc135368fbf5d1b75def4f3b3672f8b4818b85d6e40d2fd04f1bfb03e37d0218f37d5b5bb7167499c2deff8bbd2593a8571f9b1de211d665c
@@ -130,7 +130,7 @@ module Spree
130
130
  end
131
131
 
132
132
  def display_price(product_or_variant)
133
- product_or_variant.price_for(current_pricing_options).to_html
133
+ product_or_variant.price_for_options(current_pricing_options)&.money&.to_html
134
134
  end
135
135
 
136
136
  def pretty_time(time, format = :long)
@@ -38,7 +38,7 @@ module Spree
38
38
  .with_prices(current_pricing_options)
39
39
  .all? { |variant_with_prices| variant_with_prices.price_same_as_master?(current_pricing_options) }
40
40
 
41
- variant.price_for(current_pricing_options).to_html
41
+ variant.price_for_options(current_pricing_options)&.money&.to_html
42
42
  end
43
43
 
44
44
  # Converts line breaks in product description into <p> tags.
@@ -30,9 +30,7 @@ module Spree
30
30
  size = style_to_size(style)
31
31
  @attachment.variant(
32
32
  resize_to_limit: size,
33
- saver: {
34
- strip: true
35
- }
33
+ strip: true
36
34
  ).processed
37
35
  end
38
36
 
@@ -60,7 +58,7 @@ module Spree
60
58
  end
61
59
 
62
60
  def normalize_styles(styles)
63
- styles.transform_values { |v| v.split('x').map(&:to_i) }
61
+ styles.transform_values { |v| v.split('x') }
64
62
  end
65
63
 
66
64
  def style_to_size(style)
@@ -5,23 +5,76 @@ module Spree
5
5
  extend ActiveSupport::Concern
6
6
 
7
7
  included do
8
- has_one :default_price,
9
- -> { with_discarded.currently_valid.with_default_attributes },
10
- class_name: 'Spree::Price',
11
- inverse_of: :variant,
12
- dependent: :destroy,
13
- autosave: true
8
+ delegate :display_price, :display_amount, :price, to: :default_price, allow_nil: true
9
+ delegate :price=, to: :default_price_or_build
10
+
11
+ # @see Spree::Variant::PricingOptions.default_price_attributes
12
+ def self.default_price_attributes
13
+ Spree::Config.default_pricing_options.desired_attributes
14
+ end
14
15
  end
15
16
 
16
- def find_or_build_default_price
17
- default_price || build_default_price(Spree::Config.default_pricing_options.desired_attributes)
17
+ # Returns `#prices` prioritized for being considered as default price
18
+ #
19
+ # @return [ActiveRecord::Relation<Spree::Price>]
20
+ def currently_valid_prices
21
+ prices.currently_valid
18
22
  end
19
23
 
20
- delegate :display_price, :display_amount, :price, to: :find_or_build_default_price
21
- delegate :price=, to: :find_or_build_default_price
24
+ # Returns {#default_price} or builds it from {Spree::Variant.default_price_attributes}
25
+ #
26
+ # @return [Spree::Price, nil]
27
+ # @see Spree::Variant.default_price_attributes
28
+ def default_price_or_build
29
+ default_price ||
30
+ prices.build(self.class.default_price_attributes)
31
+ end
32
+
33
+ # Select from {#prices} the one to be considered as the default
34
+ #
35
+ # This method works with the in-memory association, so non-persisted prices
36
+ # are taken into account. Discarded prices are also considered.
37
+ #
38
+ # A price is a candidate to be considered as the default when it meets
39
+ # {Spree::Variant.default_price_attributes} criteria. When more than one candidate is
40
+ # found, non-persisted records take preference. When more than one persisted
41
+ # candidate exists, the one most recently updated is taken or, in case of
42
+ # race condition, the one with higher id.
43
+ #
44
+ # @return [Spree::Price, nil]
45
+ # @see Spree::Variant.default_price_attributes
46
+ def default_price
47
+ prioritized_default(
48
+ prices_meeting_criteria_to_be_default(
49
+ (prices + prices.with_discarded).uniq
50
+ )
51
+ )
52
+ end
22
53
 
23
54
  def has_default_price?
24
55
  default_price.present? && !default_price.discarded?
25
56
  end
57
+
58
+ private
59
+
60
+ def prices_meeting_criteria_to_be_default(prices)
61
+ criteria = self.class.default_price_attributes.transform_keys(&:to_s)
62
+ prices.select do |price|
63
+ contender = price.attributes.slice(*criteria.keys)
64
+ criteria == contender
65
+ end
66
+ end
67
+
68
+ def prioritized_default(prices)
69
+ prices.min do |prev, succ|
70
+ contender_one, contender_two = [succ, prev].map do |item|
71
+ [
72
+ item.updated_at || Time.zone.now,
73
+ item.id || Float::INFINITY
74
+ ]
75
+ end
76
+ contender_one <=> contender_two
77
+ end
78
+ end
26
79
  end
27
80
  end
@@ -55,11 +55,12 @@ module Spree
55
55
  #
56
56
  # @param excluded_orders [Array<Spree::Order>] Orders to exclude from query
57
57
  # @return [ActiveRecord::Relation] Scoped Adjustments
58
- def self.in_completed_orders(excluded_orders: [])
59
- joins(:order).
60
- merge(Spree::Order.complete).
61
- where.not(spree_orders: { id: excluded_orders }).
62
- distinct
58
+ def self.in_completed_orders(excluded_orders: [], exclude_canceled: false)
59
+ result = joins(:order)
60
+ .merge(Spree::Order.complete)
61
+ .where.not(spree_orders: { id: excluded_orders })
62
+ .distinct
63
+ exclude_canceled ? result.merge(Spree::Order.not_canceled) : result
63
64
  end
64
65
 
65
66
  def finalize!
@@ -40,7 +40,8 @@ module Spree
40
40
  # Temporarily tie a customer_return to one order
41
41
  def order
42
42
  return nil if return_items.blank?
43
- return_items.first.inventory_unit.order
43
+
44
+ return_items.first.inventory_unit&.order
44
45
  end
45
46
 
46
47
  def fully_reimbursed?
@@ -65,7 +66,7 @@ module Spree
65
66
  end
66
67
 
67
68
  def return_items_belong_to_same_order
68
- if return_items.reject{ |return_item| return_item.inventory_unit.order_id == order_id }.any?
69
+ if return_items.reject{ |return_item| return_item.inventory_unit&.order_id == order_id }.any?
69
70
  errors.add(:base, I18n.t('spree.return_items_cannot_be_associated_with_multiple_orders'))
70
71
  end
71
72
  end
@@ -12,13 +12,8 @@ module Spree::Image::ActiveStorageAttachment
12
12
  validate :supported_content_type
13
13
 
14
14
  has_attachment :attachment,
15
- styles: {
16
- mini: '48x48>',
17
- small: '400x400>',
18
- product: '680x680>',
19
- large: '1200x1200>'
20
- },
21
- default_style: :product
15
+ styles: Spree::Config.product_image_styles,
16
+ default_style: Spree::Config.product_image_style_default
22
17
 
23
18
  def supported_content_type
24
19
  unless attachment.content_type.in?(Spree::Config.allowed_image_mime_types)
@@ -7,8 +7,8 @@ module Spree::Image::PaperclipAttachment
7
7
  validate :no_attachment_errors
8
8
 
9
9
  has_attached_file :attachment,
10
- styles: { mini: '48x48>', small: '400x400>', product: '680x680>', large: '1200x1200>' },
11
- default_style: :product,
10
+ styles: Spree::Config.product_image_styles,
11
+ default_style: Spree::Config.product_image_style_default,
12
12
  default_url: 'noimage/:style.png',
13
13
  url: '/spree/products/:id/:style/:basename.:extension',
14
14
  path: ':rails_root/public/spree/products/:id/:style/:basename.:extension',
@@ -123,7 +123,7 @@ module Spree
123
123
  # a price for this line item, even if there is no existing price
124
124
  # for the associated line item in the order currency.
125
125
  unless options.key?(:price) || options.key?('price')
126
- self.money_price = variant.price_for(pricing_options)
126
+ self.money_price = variant.price_for_options(pricing_options)&.money
127
127
  end
128
128
  end
129
129
 
@@ -149,7 +149,7 @@ module Spree
149
149
  # Set price, cost_price and currency.
150
150
  def set_pricing_attributes
151
151
  self.cost_price ||= variant.cost_price
152
- self.money_price = variant.price_for(pricing_options) if price.nil?
152
+ self.money_price = variant.price_for_options(pricing_options)&.money if price.nil?
153
153
  true
154
154
  end
155
155
 
@@ -51,7 +51,7 @@ module Spree
51
51
  end
52
52
 
53
53
  self.whitelisted_ransackable_associations = %w[shipments user order_promotions promotions bill_address ship_address line_items]
54
- self.whitelisted_ransackable_attributes = %w[bill_address_name completed_at created_at email number state payment_state shipment_state total store_id]
54
+ self.whitelisted_ransackable_attributes = %w[completed_at created_at email number state payment_state shipment_state total store_id]
55
55
 
56
56
  attr_reader :coupon_code
57
57
  attr_accessor :temporary_address
@@ -128,7 +128,7 @@ module Spree
128
128
  before_create :create_token
129
129
  before_create :link_by_email
130
130
 
131
- validates :email, presence: true, if: :require_email
131
+ validates :email, presence: true, if: :email_required?
132
132
  validates :email, 'spree/email' => true, allow_blank: true
133
133
  validates :guest_token, presence: { allow_nil: true }
134
134
  validates :number, presence: true, uniqueness: { allow_blank: true, case_sensitive: true }
@@ -269,15 +269,15 @@ module Spree
269
269
  end
270
270
 
271
271
  def contents
272
- @contents ||= Spree::OrderContents.new(self)
272
+ @contents ||= Spree::Config.order_contents_class.new(self)
273
273
  end
274
274
 
275
275
  def shipping
276
- @shipping ||= Spree::OrderShipping.new(self)
276
+ @shipping ||= Spree::Config.order_shipping_class.new(self)
277
277
  end
278
278
 
279
279
  def cancellations
280
- @cancellations ||= Spree::OrderCancellations.new(self)
280
+ @cancellations ||= Spree::Config.order_cancellations_class.new(self)
281
281
  end
282
282
 
283
283
  # Associates the specified user with the order.
@@ -750,10 +750,15 @@ module Spree
750
750
  end
751
751
 
752
752
  # Determine if email is required (we don't want validation errors before we hit the checkout)
753
- def require_email
753
+ def email_required?
754
754
  true unless new_record? || ['cart', 'address'].include?(state)
755
755
  end
756
756
 
757
+ def require_email
758
+ Spree::Deprecation.warn "Use email_required? instead", caller(1)
759
+ email_required?
760
+ end
761
+
757
762
  def ensure_inventory_units
758
763
  if has_checkout_step?("delivery")
759
764
  inventory_validator = Spree::Stock::InventoryValidator.new
@@ -62,15 +62,18 @@ class Spree::OrderShipping
62
62
  end
63
63
 
64
64
  inventory_units.map(&:shipment).uniq.each do |shipment|
65
- if shipment.inventory_units.reload.all? { |iu| iu.shipped? || iu.canceled? }
66
- shipment.update!(state: "shipped", shipped_at: Time.current, tracking: tracking_number)
67
- else
68
- shipment.update!(tracking: tracking_number)
69
- end
65
+ # Temporarily propagate the tracking number to the shipment as well
66
+ # TODO: Remove tracking numbers from shipments.
67
+ shipment.update!(tracking: tracking_number)
68
+
69
+ next unless shipment.inventory_units.reload.all? { |iu| iu.shipped? || iu.canceled? }
70
+ # TODO: make OrderShipping#ship_shipment call Shipment#ship! rather than
71
+ # having Shipment#ship! call OrderShipping#ship_shipment. We only really
72
+ # need this `update_columns` for the specs, until we make that change.
73
+ shipment.update_columns(state: 'shipped', shipped_at: Time.current)
70
74
  end
71
75
 
72
76
  send_shipment_emails(carton) if stock_location.fulfillable? && !suppress_mailer # e.g. digital gift cards that aren't actually shipped
73
- @order.shipments.reload
74
77
  @order.recalculate
75
78
 
76
79
  carton
@@ -13,7 +13,7 @@ module Spree
13
13
  delegate :tax_rates, to: :variant
14
14
 
15
15
  validate :check_price
16
- validates :amount, allow_nil: true, numericality: {
16
+ validates :amount, numericality: {
17
17
  greater_than_or_equal_to: 0,
18
18
  less_than_or_equal_to: MAXIMUM_AMOUNT
19
19
  }
@@ -29,25 +29,25 @@ module Spree
29
29
  scope :descend_by_name, -> { order(name: :desc) }
30
30
 
31
31
  add_search_scope :ascend_by_master_price do
32
- joins(master: :default_price).select('spree_products.* , spree_prices.amount')
32
+ joins(master: :prices).select('spree_products.* , spree_prices.amount')
33
33
  .order(Spree::Price.arel_table[:amount].asc)
34
34
  end
35
35
 
36
36
  add_search_scope :descend_by_master_price do
37
- joins(master: :default_price).select('spree_products.* , spree_prices.amount')
37
+ joins(master: :prices).select('spree_products.* , spree_prices.amount')
38
38
  .order(Spree::Price.arel_table[:amount].desc)
39
39
  end
40
40
 
41
41
  add_search_scope :price_between do |low, high|
42
- joins(master: :default_price).where(Price.table_name => { amount: low..high })
42
+ joins(master: :prices).where(Price.table_name => { amount: low..high })
43
43
  end
44
44
 
45
45
  add_search_scope :master_price_lte do |price|
46
- joins(master: :default_price).where("#{price_table_name}.amount <= ?", price)
46
+ joins(master: :prices).where("#{price_table_name}.amount <= ?", price)
47
47
  end
48
48
 
49
49
  add_search_scope :master_price_gte do |price|
50
- joins(master: :default_price).where("#{price_table_name}.amount >= ?", price)
50
+ joins(master: :prices).where("#{price_table_name}.amount >= ?", price)
51
51
  end
52
52
 
53
53
  # This scope selects products in taxon AND all its descendants
@@ -45,7 +45,6 @@ module Spree
45
45
 
46
46
  has_many :variants,
47
47
  -> { where(is_master: false).order(:position) },
48
- inverse_of: :product,
49
48
  class_name: 'Spree::Variant'
50
49
 
51
50
  has_many :variants_including_master,
@@ -61,6 +60,17 @@ module Spree
61
60
  has_many :line_items, through: :variants_including_master
62
61
  has_many :orders, through: :line_items
63
62
 
63
+ scope :sort_by_master_default_price_amount_asc, -> {
64
+ with_default_price.order('spree_prices.amount ASC')
65
+ }
66
+ scope :sort_by_master_default_price_amount_desc, -> {
67
+ with_default_price.order('spree_prices.amount DESC')
68
+ }
69
+ scope :with_default_price, -> {
70
+ left_joins(master: :prices)
71
+ .where(master: { spree_prices: Spree::Config.default_pricing_options.desired_attributes })
72
+ }
73
+
64
74
  def find_or_build_master
65
75
  master || build_master
66
76
  end
@@ -85,6 +95,7 @@ module Spree
85
95
  :has_default_price?,
86
96
  :images,
87
97
  :price_for,
98
+ :price_for_options,
88
99
  :rebuild_vat_prices=,
89
100
  to: :find_or_build_master
90
101
 
@@ -5,12 +5,38 @@ module Spree
5
5
  module Rules
6
6
  # A rule to apply to an order greater than (or greater than or equal to)
7
7
  # a specific amount
8
+ #
9
+ # To add extra operators please override `self.operators_map` or any other helper method.
10
+ # To customize the error message you can also override `ineligible_message`.
8
11
  class ItemTotal < PromotionRule
12
+ include ActiveSupport::Deprecation::DeprecatedConstantAccessor
13
+
9
14
  preference :amount, :decimal, default: 100.00
10
15
  preference :currency, :string, default: ->{ Spree::Config[:currency] }
11
- preference :operator, :string, default: '>'
16
+ preference :operator, :string, default: 'gt'
17
+
18
+ # The list of allowed operators names mapped to their symbols.
19
+ def self.operators_map
20
+ {
21
+ gte: :>=,
22
+ gt: :>,
23
+ }
24
+ end
25
+
26
+ def self.operator_options
27
+ operators_map.map do |name, _method|
28
+ [I18n.t(name, scope: 'spree.item_total_rule.operators'), name]
29
+ end
30
+ end
12
31
 
13
- OPERATORS = ['gt', 'gte']
32
+ # @deprecated
33
+ OPERATORS = operators_map.keys.map(&:to_s)
34
+ deprecate_constant(
35
+ :OPERATORS,
36
+ :operators_map,
37
+ message: "OPERATORS is deprecated! Use `operators_map.keys.map(&:to_s)` instead.",
38
+ deprecator: Spree::Deprecation,
39
+ )
14
40
 
15
41
  def applicable?(promotable)
16
42
  promotable.is_a?(Spree::Order)
@@ -18,8 +44,8 @@ module Spree
18
44
 
19
45
  def eligible?(order, _options = {})
20
46
  return false unless order.currency == preferred_currency
21
- item_total = order.item_total
22
- unless item_total.send(preferred_operator == 'gte' ? :>= : :>, BigDecimal(preferred_amount.to_s))
47
+
48
+ unless total_for_order(order).send(operator, threshold)
23
49
  eligibility_errors.add(:base, ineligible_message, error_code: ineligible_error_code)
24
50
  end
25
51
 
@@ -28,15 +54,33 @@ module Spree
28
54
 
29
55
  private
30
56
 
57
+ def operator
58
+ self.class.operators_map.fetch(
59
+ preferred_operator.to_sym,
60
+ preferred_operator_default,
61
+ )
62
+ end
63
+
64
+ def total_for_order(order)
65
+ order.item_total
66
+ end
67
+
68
+ def threshold
69
+ BigDecimal(preferred_amount.to_s)
70
+ end
71
+
31
72
  def formatted_amount
32
73
  Spree::Money.new(preferred_amount, currency: preferred_currency).to_s
33
74
  end
34
75
 
35
76
  def ineligible_message
36
- if preferred_operator == 'gte'
77
+ case preferred_operator.to_s
78
+ when 'gte'
37
79
  eligibility_error_message(:item_total_less_than, amount: formatted_amount)
38
- else
80
+ when 'gt'
39
81
  eligibility_error_message(:item_total_less_than_or_equal, amount: formatted_amount)
82
+ else
83
+ eligibility_error_message(:item_total_doesnt_match_with_operator, amount: formatted_amount, operator: preferred_operator)
40
84
  end
41
85
  end
42
86
 
@@ -47,7 +47,7 @@ module Spree
47
47
  where(table[:expires_at].eq(nil).or(table[:expires_at].gt(time)))
48
48
  end
49
49
  scope :has_actions, -> do
50
- joins(:promotion_actions)
50
+ joins(:promotion_actions).distinct
51
51
  end
52
52
  scope :applied, -> { joins(:order_promotions).distinct }
53
53
 
@@ -192,7 +192,7 @@ module Spree
192
192
  def usage_count(excluded_orders: [])
193
193
  Spree::Adjustment.promotion.
194
194
  eligible.
195
- in_completed_orders(excluded_orders: excluded_orders).
195
+ in_completed_orders(excluded_orders: excluded_orders, exclude_canceled: true).
196
196
  where(source_id: actions).
197
197
  count(:order_id)
198
198
  end
@@ -5,12 +5,12 @@ class Spree::PromotionCode < Spree::Base
5
5
  belongs_to :promotion_code_batch, class_name: "Spree::PromotionCodeBatch", optional: true
6
6
  has_many :adjustments
7
7
 
8
- before_validation :normalize_code
9
-
10
8
  validates :value, presence: true, uniqueness: { allow_blank: true, case_sensitive: true }
11
9
  validates :promotion, presence: true
12
10
  validate :promotion_not_apply_automatically, on: :create
13
11
 
12
+ before_save :normalize_code
13
+
14
14
  self.whitelisted_ransackable_attributes = ['value']
15
15
 
16
16
  # Whether the promotion code has exceeded its usage restrictions
@@ -30,7 +30,7 @@ class Spree::PromotionCode < Spree::Base
30
30
  def usage_count(excluded_orders: [])
31
31
  adjustments.
32
32
  eligible.
33
- in_completed_orders(excluded_orders: excluded_orders).
33
+ in_completed_orders(excluded_orders: excluded_orders, exclude_canceled: true).
34
34
  count(:order_id)
35
35
  end
36
36
 
@@ -31,14 +31,6 @@ module Spree
31
31
  end
32
32
  end
33
33
 
34
- # Sets this price's amount to a new value, parsing it if the new value is
35
- # a string.
36
- #
37
- # @param price [String, #to_d] a new amount
38
- def amount=(price)
39
- self[:amount] = Spree::LocalizedNumber.parse(price)
40
- end
41
-
42
34
  def description
43
35
  payment.payment_method.name
44
36
  end
@@ -5,7 +5,7 @@ module Spree
5
5
  # @attr [Spree::ShippingRate] shipping_rate The shipping rate to be taxed
6
6
  # @attr [Spree::TaxRate] tax_rate The tax rate used to calculate the tax amount
7
7
  # @since 1.3.0
8
- # @see Spree::Tax::ShippingRateTaxer
8
+ # @see Spree::Stock::Estimator
9
9
  class ShippingRateTax < Spree::Base
10
10
  belongs_to :shipping_rate, class_name: "Spree::ShippingRate", optional: true
11
11
  belongs_to :tax_rate, class_name: "Spree::TaxRate", optional: true
@@ -19,7 +19,7 @@ module Spree
19
19
  # Get the on_hand stock quantities
20
20
  # @return [Hash<Integer=>Spree::StockQuantities>] A map of stock_location_ids to the stock quantities available in that location
21
21
  def on_hand_by_stock_location_id
22
- counts_on_hand.to_a.group_by do |(_, stock_location_id), _|
22
+ quantities_by_location_id = counts_on_hand.to_a.group_by do |(_, stock_location_id), _|
23
23
  stock_location_id
24
24
  end.transform_values do |values|
25
25
  Spree::StockQuantities.new(
@@ -31,12 +31,13 @@ module Spree
31
31
  end.to_h
32
32
  )
33
33
  end
34
+ restore_location_order(quantities_by_location_id)
34
35
  end
35
36
 
36
- # Get the on_hand stock quantities
37
+ # Get the backorderable stock quantities
37
38
  # @return [Hash<Integer=>Spree::StockQuantities>] A map of stock_location_ids to the stock quantities available in that location
38
39
  def backorderable_by_stock_location_id
39
- backorderables.group_by(&:second).transform_values do |variant_ids|
40
+ quantities_by_location_id = backorderables.group_by(&:second).transform_values do |variant_ids|
40
41
  Spree::StockQuantities.new(
41
42
  variant_ids.map do |variant_id, _|
42
43
  variant = @variant_map[variant_id]
@@ -44,6 +45,7 @@ module Spree
44
45
  end.to_h
45
46
  )
46
47
  end
48
+ restore_location_order(quantities_by_location_id)
47
49
  end
48
50
 
49
51
  private
@@ -67,6 +69,12 @@ module Spree
67
69
  where(variant_id: @variants).
68
70
  where(stock_location_id: @stock_locations)
69
71
  end
72
+
73
+ def restore_location_order(quantities_by_location_id)
74
+ sorted_location_ids = @stock_locations.map(&:id)
75
+
76
+ quantities_by_location_id.sort_by { |key, _value| sorted_location_ids.index(key) }.to_h
77
+ end
70
78
  end
71
79
  end
72
80
  end
@@ -92,16 +92,6 @@ module Spree
92
92
  end
93
93
  end
94
94
 
95
- def sort_availability(availability)
96
- sorted_availability = availability.sort_by do |stock_location_id, _|
97
- @stock_locations.find_index do |stock_location|
98
- stock_location.id == stock_location_id
99
- end
100
- end
101
-
102
- Hash[sorted_availability]
103
- end
104
-
105
95
  def get_units(quantities)
106
96
  # Change our raw quantities back into inventory units
107
97
  quantities.flat_map do |variant, quantity|
@@ -26,7 +26,7 @@ module Spree
26
26
  validates_uniqueness_of :code, allow_blank: true, case_sensitive: false
27
27
 
28
28
  scope :active, -> { where(active: true) }
29
- scope :order_default, -> { order(default: :desc, name: :asc) }
29
+ scope :order_default, -> { order(default: :desc, position: :asc) }
30
30
 
31
31
  after_create :create_stock_items, if: :propagate_all_variants?
32
32
  after_save :ensure_one_default
@@ -40,14 +40,6 @@ class Spree::StoreCredit < Spree::PaymentSource
40
40
  extend Spree::DisplayMoney
41
41
  money_methods :amount, :amount_used, :amount_authorized
42
42
 
43
- # Sets this store credit's amount to a new value,
44
- # parsing it as a localized number if the new value is a string.
45
- #
46
- # @param number [String, #to_d] a new amount
47
- def amount=(number)
48
- self[:amount] = Spree::LocalizedNumber.parse(number)
49
- end
50
-
51
43
  def amount_remaining
52
44
  return 0.0.to_d if invalidated?
53
45
  amount - amount_used - amount_authorized
@@ -158,7 +150,12 @@ class Spree::StoreCredit < Spree::PaymentSource
158
150
  end
159
151
 
160
152
  def generate_authorization_code
161
- "#{id}-SC-#{Time.current.utc.strftime('%Y%m%d%H%M%S%6N')}"
153
+ [
154
+ id,
155
+ 'SC',
156
+ Time.current.utc.strftime('%Y%m%d%H%M%S%6N'),
157
+ SecureRandom.uuid
158
+ ].join('-')
162
159
  end
163
160
 
164
161
  def editable?
@@ -9,7 +9,7 @@ module Spree
9
9
  # looking to provide their own calculator should adhere to the API of this
10
10
  # class.
11
11
  #
12
- # @see Spree::Tax::ShippingRateTaxer
12
+ # @see Spree::Stock::Estimator
13
13
  class ShippingRate
14
14
  include Spree::Tax::TaxHelpers
15
15