spree_core 6.0.1 → 6.0.2

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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/app/models/concerns/spree/admin_user_methods.rb +23 -0
  4. data/app/models/concerns/spree/user_roles.rb +9 -4
  5. data/app/models/spree/order.rb +3 -1
  6. data/app/models/spree/order_promotion.rb +3 -1
  7. data/app/models/spree/payment.rb +16 -1
  8. data/app/models/spree/payment_method/store_credit.rb +3 -1
  9. data/app/models/spree/payment_method.rb +14 -1
  10. data/app/models/spree/payment_source.rb +3 -1
  11. data/app/models/spree/promotion/actions/create_item_adjustments.rb +1 -1
  12. data/app/models/spree/promotion.rb +10 -1
  13. data/app/models/spree/promotion_handler/cart.rb +7 -1
  14. data/app/models/spree/promotion_handler/coupon.rb +1 -29
  15. data/app/models/spree/refresh_token.rb +26 -0
  16. data/app/models/spree/variant.rb +1 -1
  17. data/app/services/spree/promotions/detach_from_owner.rb +72 -0
  18. data/app/services/spree/stock_reservations/reserve.rb +6 -2
  19. data/app/workflows/spree/carts/recalculate.rb +5 -1
  20. data/db/migrate/20260809150001_promote_shipping_categories_to_delivery_profiles.rb +15 -6
  21. data/db/migrate/20261030100001_scope_payment_sources_profile_id_uniqueness_to_payment_method.rb +38 -0
  22. data/db/migrate/20261031000001_add_last_active_at_to_spree_admin_users.rb +7 -0
  23. data/db/migrate/20261031100001_delete_orphaned_spree_option_value_variants.rb +10 -0
  24. data/lib/generators/spree/dummy/dummy_generator.rb +0 -1
  25. data/lib/generators/spree/dummy/templates/rails/database.yml +6 -9
  26. data/lib/generators/spree/dummy/templates/rails/test.rb +9 -5
  27. data/lib/generators/spree/dummy_model/templates/migration.rb.tt +1 -1
  28. data/lib/spree/core/version.rb +1 -1
  29. data/lib/spree/core.rb +0 -3
  30. data/lib/spree/testing_support/common_rake.rb +3 -2
  31. data/lib/spree/testing_support/lifecycle_events.rb +1 -3
  32. data/lib/spree/upgrades/5_6_to_6_0/manifest.yml +2 -1
  33. data/lib/tasks/delivery_migration.rake +17 -1
  34. data/lib/tasks/remove_orphan_order_promotions.rake +20 -0
  35. metadata +12 -78
  36. data/lib/generators/spree/authentication/dummy/dummy_generator.rb +0 -54
  37. data/lib/generators/spree/authentication/dummy/templates/authentication_helpers.rb.tt +0 -52
  38. data/lib/generators/spree/authentication/dummy/templates/create_spree_admin_users.rb.tt +0 -33
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2407d5fb2d24d9dd893a8c9dae11cfd8aba5a4b1d621a82938a2cb7009581936
4
- data.tar.gz: f3384773e3442f383e979f60d58b7b06672a72065cd8d117a114ef49827fda5e
3
+ metadata.gz: 95b9df74c721a763fd81c0ca389f4bf996400ed8e1a262ca031228387157be16
4
+ data.tar.gz: ff8cd8cc1294aaa4eea49a2fbcd0c6319b15ed59d77988af865d48ae56782bf2
5
5
  SHA512:
6
- metadata.gz: ebc83d9db9ebc13afea53ae0b184789f32a81121dcd9608461d93fb7e2e62b020cef774e69de3404fc0d23adb62df13f770281eacd6fd4671e5c0f5328ecae5b
7
- data.tar.gz: 99ecc2439e9a73c1f805898c35e48079cd573b947110951c1a8349b26934e7ea8023b55a53cfdf1b3af94a5013290c52e5a097e4e5dff61d1249af8ca3327fb7
6
+ metadata.gz: 94d512c61c6b2e6aa3c8672c883ea406af8efb8c509fe7ba0f0a49d58c02aa767d23a9ff82e8532965e32217130aeff39074c78d5b8d0896de6f8dac9e7e19e7
7
+ data.tar.gz: cb944067256bc44223bca95b0c00bcf17f242745c2885ea063d7549592ebdbb013c0138359fb60326b662ae4b4ed583fdba553fc2f42378fa3952049b69d7af2
data/README.md CHANGED
@@ -110,6 +110,6 @@ bundle exec rspec
110
110
 
111
111
  ## Documentation
112
112
 
113
- - [Official Documentation](https://docs.spreecommerce.org)
113
+ - [Official Documentation](https://spreecommerce.org/docs)
114
114
  - [API Reference](https://api.spreecommerce.org)
115
- - [Guides](https://docs.spreecommerce.org/developer)
115
+ - [Guides](https://spreecommerce.org/docs/developer)
@@ -2,6 +2,8 @@ module Spree
2
2
  module AdminUserMethods
3
3
  extend ActiveSupport::Concern
4
4
 
5
+ STATUSES = %w[active locked deactivated].freeze
6
+
5
7
  include Spree::Actor
6
8
  include Spree::PrefixedId
7
9
  include Spree::SignedIdAttachments
@@ -43,6 +45,8 @@ module Spree
43
45
 
44
46
  # Associations
45
47
  has_many :identities, class_name: 'Spree::UserIdentity', as: :user, dependent: :destroy
48
+ # Signed-in sessions, on the admin and the seller API alike.
49
+ has_many :refresh_tokens, class_name: 'Spree::RefreshToken', as: :user, dependent: :delete_all
46
50
  # The order operations became polymorphic in 6.0, so these read through
47
51
  # the association rather than a bare foreign key: matching on the id
48
52
  # alone would hand this user the rows an API key with the same numeric
@@ -84,6 +88,25 @@ module Spree
84
88
  Spree::Store.current.users.where.not(id: id).exists?
85
89
  end
86
90
 
91
+ # Whether the account can sign in. Duck-typed: a custom admin user class
92
+ # may lack deactivation or lockout.
93
+ # @return [String] +active+, +locked+ or +deactivated+
94
+ def status
95
+ if try(:deactivated?)
96
+ 'deactivated'
97
+ elsif try(:locked?)
98
+ 'locked'
99
+ else
100
+ 'active'
101
+ end
102
+ end
103
+
104
+ # Sessions that can still be refreshed, most recently used first.
105
+ # @return [ActiveRecord::Relation<Spree::RefreshToken>]
106
+ def active_sessions
107
+ refresh_tokens.active.order(created_at: :desc)
108
+ end
109
+
87
110
  # Returns the full name of the user
88
111
  # @return [String]
89
112
  def full_name
@@ -57,10 +57,15 @@ module Spree
57
57
  has_spree_role?(Spree::Role::ADMIN_ROLE, resource)
58
58
  end
59
59
 
60
- # Returns the user who invited the current user
61
- # @return [Spree.admin_user_class]
62
- def invited_by
63
- invitations.first&.inviter
60
+ # Who sent the invitation this user accepted to get a role on +resource+.
61
+ # Nil for a user given a role without one, such as the store owner.
62
+ #
63
+ # @param resource [Spree::Base] the store or seller; defaults to the current store
64
+ # @return [Object, nil] an actor (see Spree.actor_classes)
65
+ def invited_by(resource = nil)
66
+ resource ||= Spree::Store.current
67
+
68
+ invitations.accepted.where(resource: resource).order(:created_at).first&.inviter
64
69
  end
65
70
 
66
71
  # Whether this user belongs to any marketplace seller. The seller login
@@ -337,8 +337,10 @@ module Spree
337
337
  scope :reverse_chronological, -> { order(Arel.sql('spree_orders.completed_at IS NULL'), completed_at: :desc, created_at: :desc) }
338
338
 
339
339
  def self.search(query)
340
+ # Ransack passes `true` when the scope is called as a bare flag (`q[search]=1`)
341
+ return none unless query.is_a?(String) && query.present?
342
+
340
343
  sanitized_query = sanitize_query_for_search(query)
341
- return none if query.blank?
342
344
 
343
345
  query_pattern = "%#{sanitized_query}%"
344
346
 
@@ -8,7 +8,7 @@ module Spree
8
8
  belongs_to :cart, class_name: 'Spree::Cart', optional: true, inverse_of: :order_promotions
9
9
  belongs_to :promotion, class_name: 'Spree::Promotion'
10
10
 
11
- delegate :name, :description, :code, to: :promotion
11
+ delegate :name, :description, :code, to: :promotion, allow_nil: true
12
12
  delegate :currency, to: :owner
13
13
 
14
14
  validates :order, uniqueness: { scope: :promotion }, allow_nil: true
@@ -35,6 +35,8 @@ module Spree
35
35
  end
36
36
 
37
37
  def amount
38
+ return BigDecimal(0) if promotion.nil?
39
+
38
40
  owner.discounts.promotion.where(promotion_action_id: promotion.actions.ids).sum(:amount)
39
41
  end
40
42
 
@@ -27,6 +27,11 @@ module Spree
27
27
  INVALID_STATES = INVALID_STATUSES
28
28
  deprecate_constant :INVALID_STATES
29
29
 
30
+ # Statuses a payment can be voided / captured from, before the payment
31
+ # method is consulted. Extend them to support gateways with other flows.
32
+ VOIDABLE_STATUSES = %w[pending processing completed checkout].freeze
33
+ CAPTURABLE_STATUSES = %w[checkout pending].freeze
34
+
30
35
  with_options inverse_of: :payments do
31
36
  belongs_to :order, class_name: 'Spree::Order', touch: true, optional: true
32
37
  belongs_to :cart, class_name: 'Spree::Cart', optional: true
@@ -219,8 +224,18 @@ module Spree
219
224
  status.in?(%w[checkout processing])
220
225
  end
221
226
 
227
+ # Whether the payment is in a status that can be voided and its payment
228
+ # method agrees. A gateway that cannot void a capture returns false from
229
+ # +can_void?(payment)+ for completed payments.
222
230
  def can_void?
223
- status.in?(%w[pending processing completed checkout])
231
+ status.in?(VOIDABLE_STATUSES) &&
232
+ (!payment_method.respond_to?(:can_void?) || payment_method.can_void?(self))
233
+ end
234
+
235
+ # Whether the payment can be captured from the back office.
236
+ def can_capture?
237
+ status.in?(CAPTURABLE_STATUSES) &&
238
+ (!payment_method.respond_to?(:can_capture?) || payment_method.can_capture?(self))
224
239
  end
225
240
 
226
241
  # @deprecated read +status+ — removed in 6.1
@@ -18,8 +18,10 @@ module Spree
18
18
  ['checkout', 'pending'].include?(payment.status)
19
19
  end
20
20
 
21
+ # Mirrors Spree::StoreCredit#can_void?: a checkout payment can still be
22
+ # voided while its order is incomplete.
21
23
  def can_void?(payment)
22
- payment.pending?
24
+ payment.pending? || (payment.checkout? && !payment.order&.completed?)
23
25
  end
24
26
 
25
27
  def authorize(money, store_credit, gateway_options = {})
@@ -197,7 +197,7 @@ module Spree
197
197
  def webhook_url
198
198
  return nil unless store
199
199
 
200
- "#{store.url_or_custom_domain}/api/v3/webhooks/payments/#{prefixed_id}"
200
+ "#{store.formatted_url_or_custom_domain}/api/v3/webhooks/payments/#{prefixed_id}"
201
201
  end
202
202
 
203
203
  class WebhookSignatureError < StandardError; end
@@ -309,6 +309,19 @@ module Spree
309
309
  raise ::NotImplementedError, 'You must implement cancel method for this payment method.'
310
310
  end
311
311
 
312
+ # Whether the payment can be voided in its current state. Gateways that
313
+ # cannot void a captured payment override this.
314
+ # @param _payment [Spree::Payment]
315
+ def can_void?(_payment)
316
+ true
317
+ end
318
+
319
+ # Whether the payment can be captured in its current state.
320
+ # @param _payment [Spree::Payment]
321
+ def can_capture?(_payment)
322
+ true
323
+ end
324
+
312
325
  def store_credit?
313
326
  self.class == Spree::PaymentMethod::StoreCredit
314
327
  end
@@ -17,7 +17,9 @@ module Spree
17
17
  #
18
18
  # Validations
19
19
  #
20
- validates_uniqueness_of :gateway_payment_profile_id, scope: :type
20
+ # A gateway profile id is only unique within the gateway account that issued it, so the same
21
+ # buyer needs a separate source under each payment method they pay through.
22
+ validates_uniqueness_of :gateway_payment_profile_id, scope: %i[type payment_method_id]
21
23
 
22
24
  #
23
25
  # Delegations
@@ -22,7 +22,7 @@ module Spree
22
22
  # clamped against its line's remaining discounted base, so the order net
23
23
  # can never go below zero.
24
24
  def compute_amount(line_item)
25
- return 0 unless promotion.line_item_actionable?(line_item.order, line_item)
25
+ return 0 unless promotion.line_item_actionable?(line_item.owner, line_item)
26
26
 
27
27
  [line_item.amount, compute(line_item)].min * -1
28
28
  end
@@ -51,7 +51,8 @@ module Spree
51
51
  before_validation :set_starts_at_to_current_time, if: -> { starts_at.blank? }
52
52
  after_commit :generate_coupon_codes, if: -> { multi_codes? }, on: [:create, :update]
53
53
  after_commit :remove_coupons, on: :update
54
- before_destroy :not_used?
54
+ before_destroy :detach_from_open_carts, prepend: true
55
+ before_destroy :not_used?, prepend: true
55
56
 
56
57
  #
57
58
  # Validations
@@ -376,6 +377,14 @@ module Spree
376
377
  flush_pending_typed_association(:promotion_actions)
377
378
  end
378
379
 
380
+ # Runs before the promotion's actions are destroyed, since the carts'
381
+ # discounts are tied back to the promotion through them.
382
+ def detach_from_open_carts
383
+ Spree::Cart.where(id: order_promotions.where.not(cart_id: nil).select(:cart_id)).find_each do |cart|
384
+ Spree::Promotions::DetachFromOwner.call(owner: cart, promotion: self, clear_coupon_code: true)
385
+ end
386
+ end
387
+
379
388
  def not_used?
380
389
  return true if orders.empty?
381
390
 
@@ -16,14 +16,20 @@ module Spree
16
16
  attr_reader :line_item, :order, :store
17
17
  attr_accessor :error, :success
18
18
 
19
- def initialize(order, line_item = nil)
19
+ # @param order [Spree::Cart, Spree::Order]
20
+ # @param line_item [Spree::LineItem, nil]
21
+ # @param excluded_promotion_ids [Array<String, Integer>] promotions to leave alone, e.g. one that is being removed
22
+ def initialize(order, line_item = nil, excluded_promotion_ids: [])
20
23
  @order = order
21
24
  @line_item = line_item
22
25
  @store = order.store
26
+ @excluded_promotion_ids = Array(excluded_promotion_ids).map(&:to_s)
23
27
  end
24
28
 
25
29
  def activate
26
30
  promotions.each do |promotion|
31
+ next if @excluded_promotion_ids.include?(promotion.id.to_s)
32
+
27
33
  if (line_item && promotion.eligible?(line_item)) || promotion.eligible?(order)
28
34
  promotion.activate(line_item: line_item, order: order)
29
35
  else
@@ -166,35 +166,7 @@ module Spree
166
166
  end
167
167
 
168
168
  def detach_promotion(promotion)
169
- # Order promotion has to be destroyed before line item removing
170
- order.promotions.delete(promotion)
171
-
172
- if promotion.multi_codes?
173
- promotion.coupon_codes.held_by(order).first&.remove_from_order
174
- else
175
- promotion.touch
176
- end
177
-
178
- remove_promotion_adjustments(promotion)
179
- remove_promotion_line_items(promotion)
180
- order.recalculate_totals!
181
- end
182
-
183
- def remove_promotion_adjustments(promotion)
184
- order.discounts.where(promotion_action_id: promotion.actions.pluck(:id)).destroy_all
185
- end
186
-
187
- def remove_promotion_line_items(promotion)
188
- create_line_item_actions_ids = promotion.actions.
189
- where(type: %w[Spree::Promotion::Actions::CreateLineItems Spree::Promotion::Actions::CreateLineItems]).pluck(:id)
190
-
191
- Spree::PromotionActionLineItem.where(promotion_action: create_line_item_actions_ids).find_each do |item|
192
- line_item = order.find_line_item_by_variant(item.variant)
193
- next if line_item.blank?
194
-
195
- remove_service = order.is_a?(Spree::Cart) ? Spree.cart_remove_item_service : Spree.order_remove_item_service
196
- remove_service.call(**{ (order.is_a?(Spree::Cart) ? :cart : :order) => order }, variant: item.variant, quantity: item.quantity)
197
- end
169
+ Spree::Promotions::DetachFromOwner.call(owner: order, promotion: promotion)
198
170
  end
199
171
 
200
172
  def handle_present_promotion
@@ -6,12 +6,19 @@ module Spree
6
6
 
7
7
  has_secure_token :token
8
8
 
9
+ # Personal data. Rows written before encryption was enabled stay readable
10
+ # and are encrypted when the token next rotates.
11
+ encrypts :ip_address, support_unencrypted_data: true if ActiveRecord::Encryption.config.has_primary_key?
12
+
9
13
  validates :user, :expires_at, presence: true
10
14
  # On create only: rows minted before the column exists keep their NULL and
11
15
  # stay loadable (they are simply unredeemable), while nothing new is born
12
16
  # without the surface it belongs to.
13
17
  validates :audience, presence: true, on: :create
14
18
 
19
+ # Starting a session and refreshing one both create a row.
20
+ after_create :record_user_activity
21
+
15
22
  scope :active, -> { where('expires_at > ?', Time.current) }
16
23
  scope :expired, -> { where('expires_at <= ?', Time.current) }
17
24
 
@@ -23,6 +30,12 @@ module Spree
23
30
  expires_at <= Time.current
24
31
  end
25
32
 
33
+ # Each refresh replaces the row, so its creation is the session's last use.
34
+ # @return [ActiveSupport::TimeWithZone]
35
+ def last_active_at
36
+ created_at
37
+ end
38
+
26
39
  # Rotate: destroy this token and create a new one.
27
40
  # Returns the new token.
28
41
  def rotate!(request_env: {})
@@ -74,5 +87,18 @@ module Spree
74
87
  rescue StandardError
75
88
  30.days
76
89
  end
90
+
91
+ private
92
+
93
+ # Outlives the session itself, which signing out deletes. Skips callbacks
94
+ # and +updated_at+: activity is not an edit to the account, and an older
95
+ # session committing last never moves it back. Users without the column
96
+ # (customers, custom admin classes) are left alone.
97
+ def record_user_activity
98
+ return unless user.has_attribute?(:last_active_at)
99
+
100
+ stale = user.class.where(id: user.id).where('last_active_at IS NULL OR last_active_at < ?', created_at)
101
+ stale.update_all(last_active_at: created_at)
102
+ end
77
103
  end
78
104
  end
@@ -71,7 +71,7 @@ module Spree
71
71
  has_many :stock_reservations
72
72
  end
73
73
 
74
- has_many :option_value_variants, class_name: 'Spree::OptionValueVariant'
74
+ has_many :option_value_variants, class_name: 'Spree::OptionValueVariant', dependent: :destroy
75
75
  has_many :option_values, through: :option_value_variants, dependent: :destroy, class_name: 'Spree::OptionValue'
76
76
 
77
77
  has_many :images, -> { order(:position) }, as: :viewable, dependent: :destroy, class_name: 'Spree::Media'
@@ -0,0 +1,72 @@
1
+ module Spree
2
+ module Promotions
3
+ # Removes a promotion from a cart or an order the way a shopper removing the
4
+ # code does: the order promotion, held coupon code, discounts and items
5
+ # the promotion added are removed, then totals are recalculated.
6
+ #
7
+ # @example Removing a promotion from a cart
8
+ # Spree::Promotions::DetachFromOwner.call(owner: cart, promotion: promotion)
9
+ class DetachFromOwner
10
+ prepend Spree::ServiceModule::Base
11
+
12
+ # Action types whose line items are removed along with the promotion.
13
+ CREATE_LINE_ITEMS_ACTION_TYPES = %w[Spree::Promotion::Actions::CreateLineItems].freeze
14
+
15
+ # @param owner [Spree::Cart, Spree::Order] the cart or order holding the promotion
16
+ # @param promotion [Spree::Promotion] the promotion to remove
17
+ # @param clear_coupon_code [Boolean] also clear the code the shopper entered on the
18
+ # owner, so recalculating does not apply the promotion again. Leave it off when the
19
+ # shopper should be told the code was lost.
20
+ # @return [Spree::ServiceModule::Result] success, holding the owner; a failure when the
21
+ # owner is already completed, since placed orders are never changed this way
22
+ def call(owner:, promotion:, clear_coupon_code: false)
23
+ return failure(owner, 'Cannot remove a promotion from a completed order') if owner.completed?
24
+
25
+ held_code = promotion.coupon_codes.held_by(owner).first if promotion.multi_codes?
26
+ clear_entered_coupon_code(owner, held_code&.code || promotion.code) if clear_coupon_code
27
+
28
+ # The order promotion has to go before line items are removed
29
+ owner.promotions.delete(promotion)
30
+
31
+ if held_code
32
+ held_code.remove_from_order
33
+ else
34
+ promotion.touch
35
+ end
36
+
37
+ owner.discounts.where(promotion_action_id: promotion.actions.ids).destroy_all
38
+ remove_promotion_line_items(owner, promotion)
39
+ owner.recalculate_totals!
40
+
41
+ success(owner)
42
+ end
43
+
44
+ private
45
+
46
+ # Carts keep the entered code, and recalculation would apply it again.
47
+ # Callers that let the shopper be told the code went keep it.
48
+ def clear_entered_coupon_code(owner, code)
49
+ return if code.blank? || !owner.has_attribute?(:coupon_code)
50
+ return unless owner.read_attribute(:coupon_code).to_s.casecmp?(code)
51
+
52
+ owner.update_column(:coupon_code, nil)
53
+ end
54
+
55
+ # Removes the line items a promotion's create-line-items actions added.
56
+ def remove_promotion_line_items(owner, promotion)
57
+ action_ids = promotion.actions.where(type: CREATE_LINE_ITEMS_ACTION_TYPES).ids
58
+ remove_service = owner.is_a?(Spree::Cart) ? Spree.cart_remove_item_service : Spree.order_remove_item_service
59
+ owner_key = owner.is_a?(Spree::Cart) ? :cart : :order
60
+
61
+ Spree::PromotionActionLineItem.where(promotion_action: action_ids).find_each do |item|
62
+ next if owner.find_line_item_by_variant(item.variant).blank?
63
+
64
+ # The promotion is still active here, so recalculating would add its items back;
65
+ # other promotions are still re-evaluated.
66
+ options = owner.is_a?(Spree::Cart) ? { excluded_promotion_ids: [promotion.id] } : {}
67
+ remove_service.call(owner_key => owner, variant: item.variant, quantity: item.quantity, options: options)
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -33,8 +33,12 @@ module Spree
33
33
 
34
34
  this_order_used = Hash.new(0)
35
35
 
36
- reservations = targets.map do |line_item, stock_level, quantity|
37
- stock_level = locked_stock_levels.fetch(stock_level.id)
36
+ reservations = targets.filter_map do |line_item, stock_level, quantity|
37
+ # A shelf deleted since the allocation was read (a variant being
38
+ # discontinued removes its stock) has nothing left to hold.
39
+ stock_level = locked_stock_levels[stock_level.id]
40
+ next if stock_level.nil?
41
+
38
42
  available = stock_level.available_count - held.fetch(stock_level.id, 0) - this_order_used[stock_level.id]
39
43
 
40
44
  raise_insufficient_stock(line_item, available) if available < quantity
@@ -9,6 +9,8 @@ module Spree
9
9
 
10
10
  # @param cart [Spree::Cart, Spree::Order] draft orders ride the same pipeline
11
11
  # @param line_item [Spree::LineItem, nil] the item whose change triggered this
12
+ # @param options [Hash] +excluded_promotion_ids:+ lists promotions to leave out of activation,
13
+ # for callers that are removing a promotion's effects
12
14
  def perform(cart: nil, order: nil, line_item: nil, line_item_created: false, options: {})
13
15
  if order
14
16
  Spree::Deprecation.warn('Calling Spree::Carts::Recalculate with order: is deprecated and will be removed in Spree 6.1. Pass cart: instead.')
@@ -47,7 +49,9 @@ module Spree
47
49
  def activate_promotions
48
50
  @promotion_context = run_hooks :set_promotion_context
49
51
 
50
- ::Spree::PromotionHandler::Cart.new(cart, line_item).activate
52
+ ::Spree::PromotionHandler::Cart.new(
53
+ cart, line_item, excluded_promotion_ids: options[:excluded_promotion_ids] || []
54
+ ).activate
51
55
  end
52
56
  end
53
57
  end
@@ -52,6 +52,7 @@ class PromoteShippingCategoriesToDeliveryProfiles < ActiveRecord::Migration[8.1]
52
52
  remove_column :spree_product_types, :fulfillment_types
53
53
 
54
54
  backfill_store_defaults
55
+ copy_first_zone_onto_methods
55
56
 
56
57
  drop_table :spree_delivery_method_zones
57
58
  end
@@ -141,13 +142,21 @@ class PromoteShippingCategoriesToDeliveryProfiles < ActiveRecord::Migration[8.1]
141
142
  # never carried a category, so they all belong to the default profile.
142
143
  MigrationProduct.where(store_id: store.id, delivery_profile_id: nil).update_all(delivery_profile_id: profile.id)
143
144
 
144
- MigrationMethod.where(store_id: store.id).find_each do |method|
145
- zone_ids = MigrationMethodZone.where(delivery_method_id: method.id).order(:id).pluck(:delivery_zone_id)
146
- if zone_ids.size > 1
147
- say "Delivery method #{method.id} was linked to #{zone_ids.size} zones; keeping the first — recreate the method for the other zones if both were intended."
148
- end
149
- method.update_columns(delivery_profile_id: profile.id, delivery_origin_group_id: origin_group.id, delivery_zone_id: zone_ids.first)
145
+ MigrationMethod.where(store_id: store.id).update_all(delivery_profile_id: profile.id, delivery_origin_group_id: origin_group.id)
146
+ end
147
+ end
148
+
149
+ # Runs for every method, including 5.x rows whose store_id is still nil: the
150
+ # link table is dropped right after, so this is the last chance to keep it.
151
+ def copy_first_zone_onto_methods
152
+ zone_ids_by_method = MigrationMethodZone.order(:id).pluck(:delivery_method_id, :delivery_zone_id).
153
+ group_by(&:first).transform_values { |pairs| pairs.map(&:last) }
154
+
155
+ zone_ids_by_method.each do |method_id, zone_ids|
156
+ if zone_ids.size > 1
157
+ say "Delivery method #{method_id} was linked to #{zone_ids.size} zones; keeping the first — recreate the method for the other zones if both were intended."
150
158
  end
159
+ MigrationMethod.where(id: method_id).update_all(delivery_zone_id: zone_ids.first)
151
160
  end
152
161
  end
153
162
  end
@@ -0,0 +1,38 @@
1
+ class ScopePaymentSourcesProfileIdUniquenessToPaymentMethod < ActiveRecord::Migration[8.1]
2
+ LEGACY_INDEX_NAME = 'index_payment_sources_on_type_and_gateway_payment_profile_id'.freeze
3
+ INDEX_NAME = 'index_payment_sources_on_type_profile_id_and_payment_method'.freeze
4
+
5
+ def up
6
+ remove_index :spree_payment_sources, name: LEGACY_INDEX_NAME
7
+ add_index :spree_payment_sources, [:type, :gateway_payment_profile_id, :payment_method_id],
8
+ unique: true, name: INDEX_NAME
9
+ end
10
+
11
+ # The legacy index cannot come back once a buyer holds the same gateway profile under two
12
+ # payment methods, which is what `up` exists to allow. Checked before anything is dropped so a
13
+ # refusal leaves both indexes intact on MySQL, which has no transactional DDL.
14
+ def down
15
+ if cross_payment_method_duplicates?
16
+ raise ActiveRecord::IrreversibleMigration,
17
+ 'Payment sources share a gateway_payment_profile_id across payment methods, which ' \
18
+ 'the globally unique index cannot represent. Reconcile those rows before rolling back.'
19
+ end
20
+
21
+ remove_index :spree_payment_sources, name: INDEX_NAME
22
+ add_index :spree_payment_sources, [:type, :gateway_payment_profile_id],
23
+ unique: true, name: LEGACY_INDEX_NAME
24
+ end
25
+
26
+ private
27
+
28
+ # NULLs are distinct in a unique index, so only fully populated pairs could ever have collided.
29
+ def cross_payment_method_duplicates?
30
+ connection.select_value(<<~SQL).present?
31
+ SELECT 1 FROM spree_payment_sources
32
+ WHERE type IS NOT NULL AND gateway_payment_profile_id IS NOT NULL
33
+ GROUP BY type, gateway_payment_profile_id
34
+ HAVING COUNT(*) > 1
35
+ LIMIT 1
36
+ SQL
37
+ end
38
+ end
@@ -0,0 +1,7 @@
1
+ # Written whenever a session starts or refreshes (see Spree::RefreshToken), so
2
+ # it outlives the sessions themselves, which signing out deletes.
3
+ class AddLastActiveAtToSpreeAdminUsers < ActiveRecord::Migration[8.1]
4
+ def change
5
+ add_column :spree_admin_users, :last_active_at, :datetime
6
+ end
7
+ end
@@ -0,0 +1,10 @@
1
+ class DeleteOrphanedSpreeOptionValueVariants < ActiveRecord::Migration[8.1]
2
+ # Replacing a variant's option_value_variants used to nullify the old join rows instead of
3
+ # deleting them. Rows with no variant belong to nothing and block new variants from reusing
4
+ # their option values through the uniqueness validation scoped to a NULL variant_id.
5
+ def up
6
+ execute 'DELETE FROM spree_option_value_variants WHERE variant_id IS NULL'
7
+ end
8
+
9
+ def down; end
10
+ end
@@ -40,7 +40,6 @@ module Spree
40
40
  args << '--skip-git'
41
41
  args << '--skip-keeps'
42
42
  args << '--skip-rc'
43
- args << '--skip-spring'
44
43
  args << '--skip-test'
45
44
  args << '--skip-bootsnap'
46
45
  args << '--skip-docker'
@@ -1,6 +1,3 @@
1
- <% if agent_number = ENV['TC_AGENT_NUMBER']
2
- database_prefix = agent_number + '_'
3
- end %>
4
1
  <% if options[:lib_name]
5
2
  lib_name = options[:lib_name].gsub('/', '_')
6
3
  end %>
@@ -37,13 +34,13 @@ mysql: &mysql
37
34
 
38
35
  development:
39
36
  <<: *mysql
40
- database: <%= database_prefix %><%= lib_name %>_spree_development
37
+ database: <%= lib_name %>_spree_development
41
38
  test:
42
39
  <<: *mysql
43
- database: <%= database_prefix %><%= lib_name %>_spree_test<%%= ENV['TEST_ENV_NUMBER'] %>
40
+ database: <%= lib_name %>_spree_test<%%= ENV['TEST_ENV_NUMBER'] %>
44
41
  production:
45
42
  <<: *mysql
46
- database: <%= database_prefix %><%= lib_name %>_spree_production
43
+ database: <%= lib_name %>_spree_production
47
44
  <% when 'postgres' %>
48
45
  postgres: &postgres
49
46
  adapter: postgresql
@@ -61,13 +58,13 @@ postgres: &postgres
61
58
 
62
59
  development:
63
60
  <<: *postgres
64
- database: <%= database_prefix %><%= lib_name %>_spree_development
61
+ database: <%= lib_name %>_spree_development
65
62
  test:
66
63
  <<: *postgres
67
- database: <%= database_prefix %><%= lib_name %>_spree_test<%%= ENV['TEST_ENV_NUMBER'] %>
64
+ database: <%= lib_name %>_spree_test<%%= ENV['TEST_ENV_NUMBER'] %>
68
65
  production:
69
66
  <<: *postgres
70
- database: <%= database_prefix %><%= lib_name %>_spree_production
67
+ database: <%= lib_name %>_spree_production
71
68
  <% else %>
72
69
  development:
73
70
  adapter: sqlite3
@@ -1,11 +1,11 @@
1
- Dummy::Application.configure do
1
+ Rails.application.configure do
2
2
  # Settings specified here will take precedence over those in config/application.rb
3
3
 
4
4
  # The test environment is used exclusively to run your application's
5
5
  # test suite. You never need to work with it otherwise. Remember that
6
6
  # your test database is "scratch space" for the test suite and is wiped
7
7
  # and recreated between test runs. Don't rely on the data there!
8
- config.cache_classes = true
8
+ config.enable_reloading = false
9
9
 
10
10
  # Configure static asset server for tests with Cache-Control for performance
11
11
  config.public_file_server.enabled = true
@@ -17,8 +17,9 @@ Dummy::Application.configure do
17
17
 
18
18
  config.eager_load = false
19
19
 
20
- # Raise exceptions instead of rendering exception templates
21
- config.action_dispatch.show_exceptions = false
20
+ # Raise unexpected exceptions; render the ones Rails maps to a status, such
21
+ # as a 404 for an unknown route, the way production does.
22
+ config.action_dispatch.show_exceptions = :rescuable
22
23
 
23
24
  # Disable request forgery protection in test environment
24
25
  config.action_controller.allow_forgery_protection = false
@@ -138,7 +139,10 @@ Dummy::Application.configure do
138
139
  %r{/spree/orders/complete\.rb:\d+:in '[^']*#place_sibling'},
139
140
  # Each requested line is priced, stock-checked, saved and taxed as it
140
141
  # is written.
141
- %r{/spree/carts/upsert_items\.rb:\d+:in '[^']*#apply_item'}
142
+ %r{/spree/carts/upsert_items\.rb:\d+:in '[^']*#apply_item'},
143
+ # An import writes each row on its own, inside the request when the
144
+ # import is set to run inline.
145
+ %r{/spree/import_row\.rb:\d+:in '[^']*#process!'}
142
146
  ]
143
147
 
144
148
  # A purchase's billing and shipping addresses, and a transfer's two
@@ -1,4 +1,4 @@
1
- class CreateSpreeDummyModels < ActiveRecord::Migration[5.1]
1
+ class CreateSpreeDummyModels < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
2
  def change
3
3
  create_table :spree_dummy_models do |t|
4
4
  t.string :name
@@ -1,5 +1,5 @@
1
1
  module Spree
2
- VERSION = '6.0.1'.freeze
2
+ VERSION = '6.0.2'.freeze
3
3
 
4
4
  def self.version
5
5
  VERSION
data/lib/spree/core.rb CHANGED
@@ -39,8 +39,6 @@ require 'ransack'
39
39
  require 'active_storage_validations'
40
40
  require 'wannabe_bool'
41
41
  require 'geocoder'
42
-
43
- require 'safely_block'
44
42
  require 'ar_lazy_preload'
45
43
  require 'sqids'
46
44
 
@@ -125,7 +123,6 @@ module Spree
125
123
  collections: :default,
126
124
  stock_location_stock_levels: :default,
127
125
  coupon_codes: :default,
128
- themes: :default,
129
126
  addresses: :default,
130
127
  gift_cards: :default,
131
128
  webhooks: :default,
@@ -3,11 +3,12 @@ require 'generators/spree/dummy_model/dummy_model_generator'
3
3
 
4
4
  desc 'Generates a dummy app for testing'
5
5
  namespace :common do
6
+ # :authentication is ignored (test apps use the gem-owned auth models); it stays
7
+ # so extensions passing positional arguments keep working.
6
8
  task :test_app, [:authentication, :user_class, :admin_user_class] do |_t, args|
7
9
  # Support both Rake::TaskArguments (via invoke) and Hash (via execute)
8
10
  # When using execute with a Hash, args IS the hash directly
9
11
  defaults = {
10
- authentication: 'dummy',
11
12
  user_class: 'Spree::Customer',
12
13
  admin_user_class: 'Spree::AdminUser',
13
14
  }
@@ -45,7 +46,7 @@ namespace :common do
45
46
  File.exist?(File.expand_path('../../../../spree.gemspec', __dir__))
46
47
  bundle_exec = use_root_gemfile ? "bundle exec --gemfile=#{root_gemfile}" : 'bundle exec'
47
48
  puts 'Running Spree install generator...'
48
- system("#{bundle_exec} rails g spree:install --force --auto-accept --migrate=false --seed=false --sample=false --user_class=#{args[:user_class]} --admin_user_class=#{args[:admin_user_class]} --authentication=#{args[:authentication]}")
49
+ system("#{bundle_exec} rails g spree:install --force --auto-accept --migrate=false --seed=false --sample=false --user_class=#{args[:user_class]} --admin_user_class=#{args[:admin_user_class]}")
49
50
 
50
51
  unless ENV['NO_MIGRATE']
51
52
  puts 'Setting up dummy database...'
@@ -23,9 +23,7 @@ shared_examples_for 'lifecycle events' do |factory: nil, event_prefix: nil|
23
23
 
24
24
  # update_attribute, not update!: Spree::Fulfillment#update! shadows the
25
25
  # Active Record method with the legacy state-recalculation API.
26
- Timecop.travel(1.minute.from_now) do
27
- record.update_attribute(:updated_at, Time.current)
28
- end
26
+ record.update_attribute(:updated_at, 1.minute.from_now)
29
27
  end
30
28
 
31
29
  it 'does not publish updated event on a bare touch' do
@@ -212,7 +212,8 @@ steps:
212
212
  task: "spree:migrate_shipping_to_delivery"
213
213
  notes: |
214
214
  Rewrites stored 'Spree::Shipment' class-name strings (state changes,
215
- stock movements, legacy adjustments), renames the terminal fulfillment
215
+ stock movements, legacy adjustments) and the calculators' 'Spree::ShippingMethod'
216
+ calculable type (so delivery methods find their calculator), renames the terminal fulfillment
216
217
  status 'shipped' -> 'fulfilled', backfills the fulfillment provider on
217
218
  delivery methods (DigitalDelivery calculators become the Digital
218
219
  provider) and copies the provider-derived type onto existing
@@ -29,6 +29,11 @@ namespace :spree do
29
29
  say.call "spree_adjustments.adjustable_type: #{count} rows Spree::Shipment → Spree::Fulfillment"
30
30
  end
31
31
 
32
+ # Calculators are polymorphic on the renamed shipping method; without this
33
+ # DeliveryMethod#calculator finds nothing.
34
+ count = Spree::Calculator.where(calculable_type: 'Spree::ShippingMethod').in_batches(of: batch_size).update_all(calculable_type: 'Spree::DeliveryMethod')
35
+ say.call "spree_calculators.calculable_type: #{count} rows Spree::ShippingMethod → Spree::DeliveryMethod"
36
+
32
37
  # The name column distinguishes the machine ('shipment' rows become
33
38
  # 'fulfillment'); the recorded states keep their history except the
34
39
  # renamed terminal value.
@@ -171,6 +176,11 @@ namespace :spree do
171
176
  end
172
177
  end
173
178
 
179
+ # 5.x methods have no store until backfill_delivery_and_stock_store_ids
180
+ # runs (later in the upgrade), and a DeliveryZone requires one. Resolve
181
+ # them to the default store, exactly as that backfill will.
182
+ default_store_id = Spree::Store.default&.id
183
+
174
184
  legacy_zones.find_each do |zone|
175
185
  if Spree::DeliveryZone.exists?(id: zone.id) && !migrated.value?(zone.id)
176
186
  puts "SKIP zone #{zone.id} (#{zone.name}): a DeliveryZone with the same id already exists — resolve manually"
@@ -178,7 +188,7 @@ namespace :spree do
178
188
  end
179
189
 
180
190
  methods_by_home = Spree::DeliveryMethod.unscoped.where(delivery_zone_id: zone.id).
181
- group_by { |method| [method.store_id, method.delivery_profile_id] }
191
+ group_by { |method| [method.store_id || default_store_id, method.delivery_profile_id] }
182
192
 
183
193
  methods_by_home.each do |(store_id, profile_id), methods|
184
194
  profile_id ||= Spree::DeliveryProfile.where(store_id: store_id, default: true).pick(:id)
@@ -208,6 +218,12 @@ namespace :spree do
208
218
  puts "SKIP unreferenced shipping zone #{zone.id} (#{zone.name}) — no delivery method uses it"
209
219
  end
210
220
 
221
+ zoneless = Spree::DeliveryMethod.unscoped.where(delivery_zone_id: nil).pluck(:id, :name)
222
+ if zoneless.any?
223
+ puts "WARNING: #{zoneless.size} delivery methods have no delivery zone and return no rates at checkout. Assign a zone to each in the admin:"
224
+ zoneless.each { |id, name| puts " - ##{id} #{name}" }
225
+ end
226
+
211
227
  puts 'migrate_zones_to_delivery_zones done. Tax-scoped zones stay on Spree::Zone for the tax provider migration.'
212
228
  end
213
229
  end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Deleting a promotion used to leave OrderPromotion rows on open carts pointing
4
+ # at a promotion that no longer exists, which broke loading those carts.
5
+ namespace :spree do
6
+ desc 'Remove order promotions whose promotion no longer exists and recalculate the affected carts'
7
+ task remove_orphan_order_promotions: :environment do
8
+ orphans = Spree::OrderPromotion.where.not(promotion_id: Spree::Promotion.unscoped.select(:id))
9
+ removed = 0
10
+
11
+ orphans.find_each do |order_promotion|
12
+ owner = order_promotion.owner
13
+ order_promotion.destroy
14
+ owner&.recalculate_totals! if owner.is_a?(Spree::Cart)
15
+ removed += 1
16
+ end
17
+
18
+ puts "remove_orphan_order_promotions done. removed: #{removed}"
19
+ end
20
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spree_core
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.0.1
4
+ version: 6.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sean Schofield
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2026-10-29 00:00:00.000000000 Z
13
+ date: 2026-11-02 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: i18n-tasks
@@ -66,14 +66,14 @@ dependencies:
66
66
  requirements:
67
67
  - - "~>"
68
68
  - !ruby/object:Gem::Version
69
- version: '3.0'
69
+ version: '4.0'
70
70
  type: :runtime
71
71
  prerelease: false
72
72
  version_requirements: !ruby/object:Gem::Requirement
73
73
  requirements:
74
74
  - - "~>"
75
75
  - !ruby/object:Gem::Version
76
- version: '3.0'
76
+ version: '4.0'
77
77
  - !ruby/object:Gem::Dependency
78
78
  name: acts-as-taggable-on
79
79
  requirement: !ruby/object:Gem::Requirement
@@ -108,20 +108,6 @@ dependencies:
108
108
  - - ">="
109
109
  - !ruby/object:Gem::Version
110
110
  version: 3.3.1
111
- - !ruby/object:Gem::Dependency
112
- name: benchmark
113
- requirement: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - ">="
116
- - !ruby/object:Gem::Version
117
- version: '0'
118
- type: :runtime
119
- prerelease: false
120
- version_requirements: !ruby/object:Gem::Requirement
121
- requirements:
122
- - - ">="
123
- - !ruby/object:Gem::Version
124
- version: '0'
125
111
  - !ruby/object:Gem::Dependency
126
112
  name: cancancan
127
113
  requirement: !ruby/object:Gem::Requirement
@@ -198,26 +184,6 @@ dependencies:
198
184
  - - ">="
199
185
  - !ruby/object:Gem::Version
200
186
  version: '0'
201
- - !ruby/object:Gem::Dependency
202
- name: highline
203
- requirement: !ruby/object:Gem::Requirement
204
- requirements:
205
- - - ">="
206
- - !ruby/object:Gem::Version
207
- version: '2'
208
- - - "<"
209
- - !ruby/object:Gem::Version
210
- version: '4'
211
- type: :runtime
212
- prerelease: false
213
- version_requirements: !ruby/object:Gem::Requirement
214
- requirements:
215
- - - ">="
216
- - !ruby/object:Gem::Version
217
- version: '2'
218
- - - "<"
219
- - !ruby/object:Gem::Version
220
- version: '4'
221
187
  - !ruby/object:Gem::Dependency
222
188
  name: json
223
189
  requirement: !ruby/object:Gem::Requirement
@@ -442,20 +408,6 @@ dependencies:
442
408
  - - "~>"
443
409
  - !ruby/object:Gem::Version
444
410
  version: '1.5'
445
- - !ruby/object:Gem::Dependency
446
- name: rexml
447
- requirement: !ruby/object:Gem::Requirement
448
- requirements:
449
- - - ">="
450
- - !ruby/object:Gem::Version
451
- version: '0'
452
- type: :runtime
453
- prerelease: false
454
- version_requirements: !ruby/object:Gem::Requirement
455
- requirements:
456
- - - ">="
457
- - !ruby/object:Gem::Version
458
- version: '0'
459
411
  - !ruby/object:Gem::Dependency
460
412
  name: stringex
461
413
  requirement: !ruby/object:Gem::Requirement
@@ -608,26 +560,6 @@ dependencies:
608
560
  - - ">="
609
561
  - !ruby/object:Gem::Version
610
562
  version: '0'
611
- - !ruby/object:Gem::Dependency
612
- name: safely_block
613
- requirement: !ruby/object:Gem::Requirement
614
- requirements:
615
- - - ">="
616
- - !ruby/object:Gem::Version
617
- version: '0.4'
618
- - - "<"
619
- - !ruby/object:Gem::Version
620
- version: '2.0'
621
- type: :runtime
622
- prerelease: false
623
- version_requirements: !ruby/object:Gem::Requirement
624
- requirements:
625
- - - ">="
626
- - !ruby/object:Gem::Version
627
- version: '0.4'
628
- - - "<"
629
- - !ruby/object:Gem::Version
630
- version: '2.0'
631
563
  - !ruby/object:Gem::Dependency
632
564
  name: phonelib
633
565
  requirement: !ruby/object:Gem::Requirement
@@ -1436,6 +1368,7 @@ files:
1436
1368
  - app/services/spree/products/duplicator.rb
1437
1369
  - app/services/spree/products/for_context.rb
1438
1370
  - app/services/spree/products/select_buy_box.rb
1371
+ - app/services/spree/promotions/detach_from_owner.rb
1439
1372
  - app/services/spree/purchases/amount_due_at_checkout.rb
1440
1373
  - app/services/spree/returns/eligibility_validator.rb
1441
1374
  - app/services/spree/sample_data/helper.rb
@@ -2029,6 +1962,9 @@ files:
2029
1962
  - db/migrate/20261022000001_store_credit_actor_polymorphic.rb
2030
1963
  - db/migrate/20261023100001_add_deactivated_at_to_spree_admin_users.rb
2031
1964
  - db/migrate/20261029100001_add_lockout_columns_to_spree_admin_users.rb
1965
+ - db/migrate/20261030100001_scope_payment_sources_profile_id_uniqueness_to_payment_method.rb
1966
+ - db/migrate/20261031000001_add_last_active_at_to_spree_admin_users.rb
1967
+ - db/migrate/20261031100001_delete_orphaned_spree_option_value_variants.rb
2032
1968
  - db/sample_data/channels.rb
2033
1969
  - db/sample_data/collections.rb
2034
1970
  - db/sample_data/custom_field_definitions.rb
@@ -2058,9 +1994,6 @@ files:
2058
1994
  - lib/generators/spree/api_resource/templates/store_controller.rb.tt
2059
1995
  - lib/generators/spree/api_resource/templates/store_controller_spec.rb.tt
2060
1996
  - lib/generators/spree/api_resource/templates/store_serializer.rb.tt
2061
- - lib/generators/spree/authentication/dummy/dummy_generator.rb
2062
- - lib/generators/spree/authentication/dummy/templates/authentication_helpers.rb.tt
2063
- - lib/generators/spree/authentication/dummy/templates/create_spree_admin_users.rb.tt
2064
1997
  - lib/generators/spree/controller_decorator/controller_decorator_generator.rb
2065
1998
  - lib/generators/spree/controller_decorator/templates/controller_decorator.rb.tt
2066
1999
  - lib/generators/spree/dummy/dummy_generator.rb
@@ -2363,6 +2296,7 @@ files:
2363
2296
  - lib/tasks/products.rake
2364
2297
  - lib/tasks/publications.rake
2365
2298
  - lib/tasks/remove_master_variant.rake
2299
+ - lib/tasks/remove_orphan_order_promotions.rake
2366
2300
  - lib/tasks/rich_text.rake
2367
2301
  - lib/tasks/sample_data.rake
2368
2302
  - lib/tasks/search.rake
@@ -2400,9 +2334,9 @@ licenses:
2400
2334
  - BSD-3-Clause
2401
2335
  metadata:
2402
2336
  bug_tracker_uri: https://github.com/spree/spree/issues
2403
- changelog_uri: https://github.com/spree/spree/releases/tag/v6.0.1
2404
- documentation_uri: https://docs.spreecommerce.org/
2405
- source_code_uri: https://github.com/spree/spree/tree/v6.0.1
2337
+ changelog_uri: https://github.com/spree/spree/releases/tag/v6.0.2
2338
+ documentation_uri: https://spreecommerce.org/docs/
2339
+ source_code_uri: https://github.com/spree/spree/tree/v6.0.2
2406
2340
  post_install_message:
2407
2341
  rdoc_options: []
2408
2342
  require_paths:
@@ -1,54 +0,0 @@
1
- require 'rails/generators'
2
-
3
- module Spree
4
- module Authentication
5
- class DummyGenerator < Rails::Generators::Base
6
- desc 'Set up a Spree installation with dummy authentication for testing. Creates a separate spree_admin_users table.'
7
-
8
- def self.source_paths
9
- paths = superclass.source_paths
10
- paths << File.expand_path('templates', __dir__)
11
- paths.flatten
12
- end
13
-
14
- def create_authentication_helpers
15
- template 'authentication_helpers.rb.tt', 'lib/spree/authentication_helpers.rb'
16
- end
17
-
18
- def configure_authentication_helpers
19
- file_action = File.exist?('config/initializers/spree.rb') ? :append_file : :create_file
20
- send(file_action, 'config/initializers/spree.rb') do
21
- <<~RUBY
22
-
23
- Rails.application.config.to_prepare do
24
- require_dependency 'spree/authentication_helpers'
25
- end
26
- RUBY
27
- end
28
- end
29
-
30
- def create_admin_users_migration
31
- # Use a fixed early timestamp (20210913) to ensure this migration runs BEFORE
32
- # any Spree core migrations that reference spree_admin_users table.
33
- # The earliest such migration is 20250122113708_add_first_and_last_name_to_spree_admin_class.rb
34
- # We use a timestamp before the main Spree migration (20210914000000_spree_four_three.rb)
35
- migration_file = File.join('db', 'migrate', '20210913000000_create_spree_admin_users.rb')
36
-
37
- # Skip if migration already exists
38
- return if File.exist?(migration_file) || migration_exists?('create_spree_admin_users')
39
-
40
- template 'create_spree_admin_users.rb.tt', migration_file
41
- end
42
-
43
- private
44
-
45
- def migration_exists?(name)
46
- Dir.glob(File.join('db', 'migrate', "*_#{name}.rb")).any?
47
- end
48
-
49
- def migration_version
50
- "[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
51
- end
52
- end
53
- end
54
- end
@@ -1,52 +0,0 @@
1
- # Dummy authentication helpers for testing purposes.
2
- # These provide stub implementations that don't require actual routes.
3
- module Spree
4
- module AuthenticationHelpers
5
- def self.included(receiver)
6
- receiver.helper_method(
7
- :spree_current_user,
8
- :spree_login_path,
9
- :spree_signup_path,
10
- :spree_logout_path,
11
- :spree_forgot_password_path,
12
- :spree_edit_password_path,
13
- :spree_admin_login_path,
14
- :spree_admin_logout_path
15
- )
16
- end
17
-
18
- def spree_current_user
19
- @spree_current_user
20
- end
21
-
22
- def spree_login_path(_opts = {})
23
- '/login'
24
- end
25
-
26
- def spree_signup_path(_opts = {})
27
- '/signup'
28
- end
29
-
30
- def spree_logout_path(_opts = {})
31
- '/logout'
32
- end
33
-
34
- def spree_forgot_password_path(_opts = {})
35
- '/forgot_password'
36
- end
37
-
38
- def spree_edit_password_path(_opts = {})
39
- '/edit_password'
40
- end
41
-
42
- def spree_admin_login_path(_opts = {})
43
- '/admin/login'
44
- end
45
-
46
- def spree_admin_logout_path(_opts = {})
47
- '/admin/logout'
48
- end
49
- end
50
- end
51
-
52
- ApplicationController.include Spree::AuthenticationHelpers if defined?(ApplicationController)
@@ -1,33 +0,0 @@
1
- class CreateSpreeAdminUsers < ActiveRecord::Migration<%= migration_version %>
2
- def change
3
- create_table :spree_admin_users do |t|
4
- t.string :encrypted_password, limit: 128
5
- t.string :password_digest
6
- t.string :email
7
- t.string :remember_token
8
- t.string :persistence_token
9
- t.string :reset_password_token
10
- t.string :perishable_token
11
- t.integer :sign_in_count, default: 0, null: false
12
- t.integer :failed_attempts, default: 0, null: false
13
- t.datetime :last_request_at
14
- t.datetime :current_sign_in_at
15
- t.datetime :last_sign_in_at
16
- t.string :current_sign_in_ip
17
- t.string :last_sign_in_ip
18
- t.string :login
19
- t.string :authentication_token
20
- t.string :unlock_token
21
- t.datetime :locked_at
22
- t.datetime :remember_created_at
23
- t.datetime :reset_password_sent_at
24
- t.string :first_name
25
- t.string :last_name
26
- t.string :selected_locale
27
-
28
- t.timestamps
29
- end
30
-
31
- add_index :spree_admin_users, :email, unique: true
32
- end
33
- end