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.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/app/models/concerns/spree/admin_user_methods.rb +23 -0
- data/app/models/concerns/spree/user_roles.rb +9 -4
- data/app/models/spree/order.rb +3 -1
- data/app/models/spree/order_promotion.rb +3 -1
- data/app/models/spree/payment.rb +16 -1
- data/app/models/spree/payment_method/store_credit.rb +3 -1
- data/app/models/spree/payment_method.rb +14 -1
- data/app/models/spree/payment_source.rb +3 -1
- data/app/models/spree/promotion/actions/create_item_adjustments.rb +1 -1
- data/app/models/spree/promotion.rb +10 -1
- data/app/models/spree/promotion_handler/cart.rb +7 -1
- data/app/models/spree/promotion_handler/coupon.rb +1 -29
- data/app/models/spree/refresh_token.rb +26 -0
- data/app/models/spree/variant.rb +1 -1
- data/app/services/spree/promotions/detach_from_owner.rb +72 -0
- data/app/services/spree/stock_reservations/reserve.rb +6 -2
- data/app/workflows/spree/carts/recalculate.rb +5 -1
- data/db/migrate/20260809150001_promote_shipping_categories_to_delivery_profiles.rb +15 -6
- data/db/migrate/20261030100001_scope_payment_sources_profile_id_uniqueness_to_payment_method.rb +38 -0
- data/db/migrate/20261031000001_add_last_active_at_to_spree_admin_users.rb +7 -0
- data/db/migrate/20261031100001_delete_orphaned_spree_option_value_variants.rb +10 -0
- data/lib/generators/spree/dummy/dummy_generator.rb +0 -1
- data/lib/generators/spree/dummy/templates/rails/database.yml +6 -9
- data/lib/generators/spree/dummy/templates/rails/test.rb +9 -5
- data/lib/generators/spree/dummy_model/templates/migration.rb.tt +1 -1
- data/lib/spree/core/version.rb +1 -1
- data/lib/spree/core.rb +0 -3
- data/lib/spree/testing_support/common_rake.rb +3 -2
- data/lib/spree/testing_support/lifecycle_events.rb +1 -3
- data/lib/spree/upgrades/5_6_to_6_0/manifest.yml +2 -1
- data/lib/tasks/delivery_migration.rake +17 -1
- data/lib/tasks/remove_orphan_order_promotions.rake +20 -0
- metadata +12 -78
- data/lib/generators/spree/authentication/dummy/dummy_generator.rb +0 -54
- data/lib/generators/spree/authentication/dummy/templates/authentication_helpers.rb.tt +0 -52
- 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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 95b9df74c721a763fd81c0ca389f4bf996400ed8e1a262ca031228387157be16
|
|
4
|
+
data.tar.gz: ff8cd8cc1294aaa4eea49a2fbcd0c6319b15ed59d77988af865d48ae56782bf2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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://
|
|
113
|
+
- [Official Documentation](https://spreecommerce.org/docs)
|
|
114
114
|
- [API Reference](https://api.spreecommerce.org)
|
|
115
|
-
- [Guides](https://
|
|
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
|
-
#
|
|
61
|
-
#
|
|
62
|
-
|
|
63
|
-
|
|
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
|
data/app/models/spree/order.rb
CHANGED
|
@@ -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
|
|
data/app/models/spree/payment.rb
CHANGED
|
@@ -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?(
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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 :
|
|
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
|
-
|
|
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
|
-
|
|
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
|
data/app/models/spree/variant.rb
CHANGED
|
@@ -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.
|
|
37
|
-
|
|
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(
|
|
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).
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
data/db/migrate/20261030100001_scope_payment_sources_profile_id_uniqueness_to_payment_method.rb
ADDED
|
@@ -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
|
|
@@ -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: <%=
|
|
37
|
+
database: <%= lib_name %>_spree_development
|
|
41
38
|
test:
|
|
42
39
|
<<: *mysql
|
|
43
|
-
database: <%=
|
|
40
|
+
database: <%= lib_name %>_spree_test<%%= ENV['TEST_ENV_NUMBER'] %>
|
|
44
41
|
production:
|
|
45
42
|
<<: *mysql
|
|
46
|
-
database: <%=
|
|
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: <%=
|
|
61
|
+
database: <%= lib_name %>_spree_development
|
|
65
62
|
test:
|
|
66
63
|
<<: *postgres
|
|
67
|
-
database: <%=
|
|
64
|
+
database: <%= lib_name %>_spree_test<%%= ENV['TEST_ENV_NUMBER'] %>
|
|
68
65
|
production:
|
|
69
66
|
<<: *postgres
|
|
70
|
-
database: <%=
|
|
67
|
+
database: <%= lib_name %>_spree_production
|
|
71
68
|
<% else %>
|
|
72
69
|
development:
|
|
73
70
|
adapter: sqlite3
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
|
|
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.
|
|
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
|
|
21
|
-
|
|
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
|
data/lib/spree/core/version.rb
CHANGED
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]}
|
|
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
|
-
|
|
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)
|
|
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.
|
|
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-
|
|
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: '
|
|
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: '
|
|
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.
|
|
2404
|
-
documentation_uri: https://
|
|
2405
|
-
source_code_uri: https://github.com/spree/spree/tree/v6.0.
|
|
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
|