spree_core 6.0.0.beta1 → 6.0.0.beta3

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 (164) hide show
  1. checksums.yaml +4 -4
  2. data/app/jobs/spree/payments/handle_webhook_job.rb +1 -0
  3. data/app/jobs/spree/sample_data/load_job.rb +18 -0
  4. data/app/jobs/spree/seller_transfers/execute_pending_job.rb +1 -0
  5. data/app/mailers/spree/admin_user_mailer.rb +2 -2
  6. data/app/models/concerns/spree/acted_by.rb +210 -0
  7. data/app/models/concerns/spree/actor.rb +51 -0
  8. data/app/models/concerns/spree/admin_user_methods.rb +48 -17
  9. data/app/models/concerns/spree/customer_methods.rb +17 -1
  10. data/app/models/concerns/spree/has_number.rb +3 -2
  11. data/app/models/concerns/spree/prefixed_id.rb +4 -1
  12. data/app/models/concerns/spree/purchase/payment_processing.rb +1 -1
  13. data/app/models/concerns/spree/purchase/totals.rb +3 -1
  14. data/app/models/concerns/spree/ransackable_attributes.rb +38 -6
  15. data/app/models/concerns/spree/translatable_resource.rb +6 -7
  16. data/app/models/concerns/spree/webhook_payload_redaction.rb +132 -21
  17. data/app/models/spree/adjusters/promotion.rb +1 -1
  18. data/app/models/spree/api_key.rb +5 -3
  19. data/app/models/spree/authentication/lockout.rb +34 -0
  20. data/app/models/spree/authentication/strategies/email_password_strategy.rb +4 -12
  21. data/app/models/spree/authentication/strategies/oidc_strategy.rb +1 -1
  22. data/app/models/spree/catalog.rb +50 -13
  23. data/app/models/spree/claim.rb +3 -1
  24. data/app/models/spree/commission_line.rb +4 -2
  25. data/app/models/spree/commission_rule.rb +1 -1
  26. data/app/models/spree/coupon_code.rb +14 -2
  27. data/app/models/spree/current.rb +56 -13
  28. data/app/models/spree/exchange.rb +3 -1
  29. data/app/models/spree/export.rb +8 -5
  30. data/app/models/spree/exports/customers.rb +2 -2
  31. data/app/models/spree/fulfillment_group.rb +127 -0
  32. data/app/models/spree/gift_card.rb +9 -0
  33. data/app/models/spree/invitation.rb +18 -5
  34. data/app/models/spree/line_item.rb +1 -1
  35. data/app/models/spree/number_generators/sequential.rb +2 -2
  36. data/app/models/spree/option_value.rb +7 -0
  37. data/app/models/spree/order.rb +89 -18
  38. data/app/models/spree/order_group.rb +78 -6
  39. data/app/models/spree/payment_source.rb +1 -1
  40. data/app/models/spree/payment_split.rb +24 -0
  41. data/app/models/spree/price.rb +74 -0
  42. data/app/models/spree/pricing_provider/internal.rb +37 -15
  43. data/app/models/spree/product.rb +4 -0
  44. data/app/models/spree/product_option_type.rb +11 -0
  45. data/app/models/spree/promotion/actions/create_line_items.rb +92 -30
  46. data/app/models/spree/promotion/rules/category.rb +14 -9
  47. data/app/models/spree/promotion/rules/product.rb +17 -1
  48. data/app/models/spree/promotion.rb +1 -1
  49. data/app/models/spree/promotion_action.rb +12 -1
  50. data/app/models/spree/promotion_handler/coupon.rb +50 -5
  51. data/app/models/spree/promotion_rule.rb +14 -0
  52. data/app/models/spree/refund.rb +2 -1
  53. data/app/models/spree/return.rb +18 -7
  54. data/app/models/spree/return_line_item.rb +19 -0
  55. data/app/models/spree/search_provider/database.rb +10 -2
  56. data/app/models/spree/seller_payout.rb +3 -1
  57. data/app/models/spree/seller_transfer.rb +3 -1
  58. data/app/models/spree/stock_receipt.rb +7 -2
  59. data/app/models/spree/store.rb +4 -0
  60. data/app/models/spree/store_credit.rb +4 -0
  61. data/app/models/spree/translations/batch.rb +4 -2
  62. data/app/models/spree/user_identity.rb +10 -6
  63. data/app/models/spree/variant.rb +8 -3
  64. data/app/models/spree/webhook_delivery.rb +28 -0
  65. data/app/models/spree/webhook_endpoint.rb +14 -0
  66. data/app/presenters/spree/csv/order_line_item_presenter.rb +2 -2
  67. data/app/services/spree/carts/split_by_seller.rb +22 -12
  68. data/app/services/spree/companies/add_member.rb +8 -2
  69. data/app/services/spree/imports/row_processors/price_list_price.rb +6 -0
  70. data/app/services/spree/imports/row_processors/product_translation.rb +1 -1
  71. data/app/services/spree/imports/row_processors/product_variant.rb +17 -11
  72. data/app/services/spree/order_groups/allocate_payments.rb +3 -0
  73. data/app/services/spree/orders/approve.rb +3 -2
  74. data/app/services/spree/orders/build_fulfillments.rb +6 -0
  75. data/app/services/spree/orders/update_statuses.rb +8 -2
  76. data/app/services/spree/prices/bulk_upsert.rb +186 -47
  77. data/app/services/spree/sample_data/import_builder.rb +1 -1
  78. data/app/services/spree/sample_data/loader.rb +54 -40
  79. data/app/services/spree/seeds/all.rb +1 -26
  80. data/app/services/spree/seeds/allowed_origins.rb +5 -4
  81. data/app/services/spree/seeds/api_keys.rb +10 -9
  82. data/app/services/spree/seeds/channels.rb +9 -8
  83. data/app/services/spree/seeds/commission_rates.rb +21 -20
  84. data/app/services/spree/seeds/customer_groups.rb +5 -4
  85. data/app/services/spree/seeds/digital_delivery.rb +7 -8
  86. data/app/services/spree/seeds/payment_methods.rb +12 -11
  87. data/app/services/spree/seeds/product_types.rb +7 -8
  88. data/app/services/spree/seeds/returns_environment.rb +12 -11
  89. data/app/services/spree/seeds/roles.rb +8 -7
  90. data/app/services/spree/seeds/saved_reports.rb +19 -20
  91. data/app/services/spree/seeds/seller_requirements.rb +5 -4
  92. data/app/services/spree/seeds/store_resources.rb +41 -0
  93. data/app/services/spree/seeds/store_scoped.rb +15 -0
  94. data/app/services/spree/seeds/tax_categories.rb +10 -9
  95. data/app/services/spree/stock_locations/stock_items/create.rb +1 -1
  96. data/app/services/spree/stock_locations/stock_levels/create.rb +19 -3
  97. data/app/subscribers/spree/order_status_subscriber.rb +15 -7
  98. data/app/workflows/spree/carts/add_item.rb +5 -7
  99. data/app/workflows/spree/carts/complete.rb +20 -64
  100. data/app/workflows/spree/carts/recalculate_totals.rb +3 -1
  101. data/app/workflows/spree/claims/create.rb +31 -1
  102. data/app/workflows/spree/claims/resolve.rb +7 -11
  103. data/app/workflows/spree/exchanges/create.rb +11 -0
  104. data/app/workflows/spree/exchanges/fulfill.rb +21 -13
  105. data/app/workflows/spree/orders/cancel.rb +3 -2
  106. data/app/workflows/spree/orders/complete.rb +99 -2
  107. data/app/workflows/spree/price_lists/update.rb +17 -0
  108. data/app/workflows/spree/refunds/create.rb +1 -1
  109. data/app/workflows/spree/refunds/order_payments.rb +55 -1
  110. data/app/workflows/spree/returns/approve.rb +1 -1
  111. data/app/workflows/spree/returns/create.rb +11 -17
  112. data/app/workflows/spree/returns/refund.rb +16 -25
  113. data/app/workflows/spree/returns/returnable_quantity.rb +53 -0
  114. data/app/workflows/spree/sellers/invite.rb +1 -1
  115. data/config/locales/en.yml +8 -119
  116. data/db/migrate/20260916000001_add_email_delivery_flags_to_spree_order_groups.rb +14 -0
  117. data/db/migrate/20260916000001_order_operation_actors_polymorphic.rb +30 -0
  118. data/db/migrate/20260922000001_add_refunded_order_to_spree_store_credits.rb +5 -0
  119. data/db/migrate/20260923000001_change_spree_user_identities_tokens_to_text.rb +10 -0
  120. data/db/sample_data/channels.rb +1 -1
  121. data/db/sample_data/collections.rb +1 -1
  122. data/db/sample_data/custom_field_definitions.rb +1 -1
  123. data/db/sample_data/digital_products.rb +1 -1
  124. data/db/sample_data/fulfillment.rb +1 -1
  125. data/db/sample_data/options.rb +4 -2
  126. data/db/sample_data/orders.rb +9 -10
  127. data/db/sample_data/payment_methods.rb +18 -18
  128. data/db/sample_data/product_type_categories.rb +1 -1
  129. data/db/sample_data/product_types.rb +2 -2
  130. data/db/sample_data/promotions.rb +2 -4
  131. data/db/sample_data/wholesale.rb +1 -1
  132. data/lib/generators/spree/api_resource/api_resource_generator.rb +121 -6
  133. data/lib/generators/spree/api_resource/templates/admin_controller.rb.tt +8 -2
  134. data/lib/generators/spree/api_resource/templates/admin_controller_spec.rb.tt +3 -0
  135. data/lib/generators/spree/api_resource/templates/permissions.en.yml.tt +6 -0
  136. data/lib/generators/spree/api_resource/templates/store_controller.rb.tt +2 -2
  137. data/lib/generators/spree/api_resource/templates/store_controller_spec.rb.tt +4 -1
  138. data/lib/generators/spree/dummy/templates/rails/test.rb +7 -0
  139. data/lib/generators/spree/model/model_generator.rb +40 -1
  140. data/lib/generators/spree/model/templates/create_table_migration.rb.tt +9 -0
  141. data/lib/generators/spree/model/templates/model.rb.tt +10 -4
  142. data/lib/generators/spree/subscriber/subscriber_generator.rb +3 -3
  143. data/lib/spree/core/configuration.rb +25 -25
  144. data/lib/spree/core/engine.rb +35 -8
  145. data/lib/spree/core/preferences/runtime_configuration.rb +76 -2
  146. data/lib/spree/core/version.rb +1 -1
  147. data/lib/spree/core.rb +27 -0
  148. data/lib/spree/iso_data.rb +23 -4
  149. data/lib/spree/store_scope_guard.rb +16 -1
  150. data/lib/spree/testing_support/factories/invitation_factory.rb +1 -0
  151. data/lib/spree/testing_support/factories/order_group_factory.rb +49 -0
  152. data/lib/spree/testing_support/factories/payment_factory.rb +7 -2
  153. data/lib/spree/testing_support/factories/user_factory.rb +0 -1
  154. data/lib/spree/upgrades/5_6_to_6_0/manifest.yml +34 -0
  155. data/lib/tasks/backfill_actor_types.rake +35 -0
  156. data/lib/tasks/sample_data.rake +8 -2
  157. data/lib/tasks/store_settings.rake +1 -0
  158. data/lib/tasks/upgrade.rake +6 -21
  159. metadata +23 -9
  160. /data/{app/models → lib}/spree/checkout/default_requirements.rb +0 -0
  161. /data/{app/models → lib}/spree/checkout/registry.rb +0 -0
  162. /data/{app/models → lib}/spree/checkout/requirement.rb +0 -0
  163. /data/{app/models → lib}/spree/checkout/requirements.rb +0 -0
  164. /data/{app/models → lib}/spree/checkout/step.rb +0 -0
@@ -1,6 +1,21 @@
1
1
  module Spree
2
2
  module Preferences
3
+ # Raised when an environment variable backing a preference holds a value
4
+ # that does not parse as the declared type.
5
+ class InvalidEnvironmentValue < StandardError
6
+ def initialize(variable, value, type, expected = nil)
7
+ article = type == :integer ? 'an' : 'a'
8
+ expected ||= "#{article} #{type}"
9
+ super("#{variable}=#{value.inspect} is not valid for #{article} #{type} setting. Expected #{expected}.")
10
+ end
11
+ end
12
+
3
13
  class RuntimeConfiguration
14
+ # Deliberately not ActiveModel::Type::Boolean: it casts "no", "Off" and
15
+ # every unrecognised string — a typo included — to true.
16
+ BOOLEAN_TRUE = %w[true 1 yes on].freeze
17
+ BOOLEAN_FALSE = %w[false 0 no off].freeze
18
+
4
19
  def initialize
5
20
  load_defaults
6
21
  end
@@ -71,10 +86,12 @@ module Spree
71
86
  # set in an initializer or at runtime always wins over the
72
87
  # environment; the env var only fills an unset preference.
73
88
  #
89
+ # @param type [Symbol] declared type, used to coerce the env var
74
90
  # @param env [String, nil] environment variable backing this setting
75
- def preference(name, _type, default: nil, deprecated: false, env: nil)
91
+ def preference(name, type, default: nil, deprecated: false, env: nil)
76
92
  defaults[name] = default
77
93
  deprecations[name] = deprecated
94
+ types[name] = type
78
95
  env_vars[name] = env if env
79
96
 
80
97
  attr_writer name
@@ -88,7 +105,12 @@ module Spree
88
105
  value = instance_variable_get(ivar)
89
106
  return value unless value.nil?
90
107
 
91
- ENV[env].presence || default
108
+ raw = ENV[env]
109
+ return default if raw.nil? || raw.empty?
110
+
111
+ # Memoized into the ivar so coercion runs once per process, not
112
+ # once per read — these sit on the request path.
113
+ instance_variable_set(ivar, self.class.coerce_env(env, raw, type))
92
114
  end
93
115
  else
94
116
  attr_reader name
@@ -103,10 +125,62 @@ module Spree
103
125
  @deprecations ||= {}
104
126
  end
105
127
 
128
+ # @return [Hash{Symbol => Symbol}] declared type per preference
129
+ def types
130
+ @types ||= {}
131
+ end
132
+
106
133
  # @return [Hash{Symbol => String}] preferences backed by an env var
107
134
  def env_vars
108
135
  @env_vars ||= {}
109
136
  end
137
+
138
+ # Reads every env-backed preference so a malformed value fails at boot
139
+ # rather than on the first request that happens to read it. Reading
140
+ # also memoizes the coerced value, so this doubles as a warm-up.
141
+ #
142
+ # @param instance [RuntimeConfiguration] configuration to validate
143
+ # @raise [Spree::Preferences::InvalidEnvironmentValue]
144
+ # @return [void]
145
+ def validate_env!(instance)
146
+ env_vars.each_key { |name| instance.public_send(name) }
147
+ nil
148
+ end
149
+
150
+ # Env vars arrive as strings, so an uncoerced `SPREE_X=false` would be
151
+ # the truthy String "false". Coercion is strict: a value that does not
152
+ # parse raises rather than falling back to the default, since a typo'd
153
+ # setting silently reverting is worse than a failed boot.
154
+ #
155
+ # @param variable [String] name of the environment variable
156
+ # @param value [String] raw value read from the environment
157
+ # @param type [Symbol] declared preference type
158
+ # @return [Object] the coerced value
159
+ def coerce_env(variable, value, type)
160
+ case type
161
+ when :boolean then coerce_env_boolean(variable, value)
162
+ when :integer then coerce_env_number(variable, value, type) { Integer(value, 10) }
163
+ when :decimal then coerce_env_number(variable, value, type) { BigDecimal(value) }
164
+ when :array then value.split(',').map(&:strip).reject(&:empty?)
165
+ else value
166
+ end
167
+ end
168
+
169
+ private
170
+
171
+ def coerce_env_boolean(variable, value)
172
+ normalized = value.strip.downcase
173
+ return true if BOOLEAN_TRUE.include?(normalized)
174
+ return false if BOOLEAN_FALSE.include?(normalized)
175
+
176
+ raise InvalidEnvironmentValue.new(variable, value, :boolean, "#{BOOLEAN_TRUE.join(', ')}, #{BOOLEAN_FALSE.join(', ')}")
177
+ end
178
+
179
+ def coerce_env_number(variable, value, type)
180
+ yield
181
+ rescue ArgumentError, TypeError
182
+ raise InvalidEnvironmentValue.new(variable, value, type)
183
+ end
110
184
  end
111
185
  end
112
186
  end
@@ -1,5 +1,5 @@
1
1
  module Spree
2
- VERSION = '6.0.0.beta1'.freeze
2
+ VERSION = '6.0.0.beta3'.freeze
3
3
 
4
4
  def self.version
5
5
  VERSION
data/lib/spree/core.rb CHANGED
@@ -625,6 +625,25 @@ module Spree
625
625
  Rails.application.config.spree.taggable_types = value
626
626
  end
627
627
 
628
+ # Class-name strings for the models that may be recorded as having performed
629
+ # an action — what an `acted_by` association's `*_type` column is allowed to
630
+ # hold. The admin user class and `Spree::ApiKey` ship registered; an
631
+ # extension adds its own App or bot class in an initializer:
632
+ #
633
+ # Spree.actor_classes << 'MyApp::App'
634
+ #
635
+ # A class listed here includes {Spree::Actor}, so a timeline can name it.
636
+ # See docs/plans/6.0-action-actors.md.
637
+ #
638
+ # @return [Array<String>]
639
+ def self.actor_classes
640
+ Rails.application.config.spree.actor_classes
641
+ end
642
+
643
+ def self.actor_classes=(value)
644
+ Rails.application.config.spree.actor_classes = value
645
+ end
646
+
628
647
 
629
648
  # Registry of the Getting Started onboarding tasks shown on the admin
630
649
  # dashboard. See {Spree::SetupTasks} for the extension API.
@@ -903,6 +922,14 @@ require 'spree/reporting'
903
922
  require 'spree/events'
904
923
  require 'spree/store_scope_guard'
905
924
 
925
+ # Not autoloaded from app/: the registry keeps registered steps in
926
+ # class-level state, which a reload would discard.
927
+ require 'spree/checkout/step'
928
+ require 'spree/checkout/requirement'
929
+ require 'spree/checkout/registry'
930
+ require 'spree/checkout/default_requirements'
931
+ require 'spree/checkout/requirements'
932
+
906
933
  require 'spree/core/controller_helpers/store'
907
934
 
908
935
  require 'spree/core/preferences/store'
@@ -53,6 +53,13 @@ module Spree
53
53
  }
54
54
  }.freeze
55
55
 
56
+ # Some gem subdivision names are a placeholder the gem falls back to when it
57
+ # has no proper name for the entry: the value it would have translated,
58
+ # tagged with the locale it belongs to — Pakistan's "Sind (en)",
59
+ # "Baluchistan (en)". The clean name is the locale's own translation, so a
60
+ # name matching this is replaced rather than shown or matched against.
61
+ LOCALE_TAGGED_NAME = /\s\([a-z]{2}(?:-[a-z]{2})?\)\z/i
62
+
56
63
  # Retired ISO codes mapped to their current equivalent. Reading an address
57
64
  # written under the old code still finds the right subdivision; writing
58
65
  # always stores the successor.
@@ -172,16 +179,28 @@ module Spree
172
179
  # English deliberately reads the canonical +name+ rather than the +en+
173
180
  # translation: the two disagree in places (the gem translates DC as
174
181
  # "Washington DC", where the canonical name is "District of Columbia"),
175
- # and the canonical spelling is the one Spree has always stored.
182
+ # and the canonical spelling is the one Spree has always stored. The
183
+ # exception is a name the gem left as a locale-tagged placeholder
184
+ # ("Sind (en)"): that is not a spelling to preserve, so the clean English
185
+ # translation wins.
176
186
  #
177
187
  # Note the translation keys are symbols here, unlike +Country#translation+,
178
188
  # which takes a string.
179
189
  def subdivision_display_name(subdivision)
180
190
  locale = I18n.locale.to_s.downcase
181
- return subdivision.name if locale.start_with?('en')
182
-
183
191
  translations = subdivision.translations || {}
184
- translations[locale.to_sym].presence || subdivision.name
192
+ english = translations[:en].presence
193
+
194
+ if locale.start_with?('en')
195
+ name = subdivision.name
196
+ return name.match?(LOCALE_TAGGED_NAME) ? (english || name) : name
197
+ end
198
+
199
+ translated = translations[locale.to_sym].presence
200
+ return translated if translated
201
+
202
+ name = subdivision.name
203
+ name.match?(LOCALE_TAGGED_NAME) ? (english || name) : name
185
204
  end
186
205
  end
187
206
  end
@@ -72,6 +72,19 @@ module Spree
72
72
  RequestStore.store[ACTIVE_KEY] = previous
73
73
  end
74
74
 
75
+ # Opens the window for whatever declared a store context — a job, a
76
+ # webhook, a console script — and leaves it open until Spree::Current
77
+ # resets, which Rails does per request and per job. Called from
78
+ # `Spree::Current#store=`; work that never declares a store stays
79
+ # unguarded, since reading the default store through the fallback is
80
+ # not a scoping claim.
81
+ def arm!
82
+ return unless enabled?
83
+
84
+ install!
85
+ Spree::Current.store_scope_guard_armed = true
86
+ end
87
+
75
88
  # Suppresses the guard for a deliberately store-less lookup.
76
89
  def skip
77
90
  previous = RequestStore.store[SKIP_KEY]
@@ -82,7 +95,9 @@ module Spree
82
95
  end
83
96
 
84
97
  def active?
85
- RequestStore.store[ACTIVE_KEY] && !RequestStore.store[SKIP_KEY]
98
+ return false if RequestStore.store[SKIP_KEY]
99
+
100
+ RequestStore.store[ACTIVE_KEY] || Spree::Current.store_scope_guard_armed.present?
86
101
  end
87
102
 
88
103
  # Forgets memoized environment/schema state (specs, migrations).
@@ -2,5 +2,6 @@ FactoryBot.define do
2
2
  factory :invitation, class: Spree::Invitation do
3
3
  email { FFaker::Internet.email }
4
4
  association :inviter, factory: :admin_user
5
+ role { (resource || Spree::Store.current).default_user_role }
5
6
  end
6
7
  end
@@ -5,6 +5,55 @@ FactoryBot.define do
5
5
  currency { 'USD' }
6
6
  email { customer&.email }
7
7
 
8
+ # A split checkout as it actually leaves completion: each child holding a
9
+ # seller's item and the parcel that ships it. `:with_orders` builds bare
10
+ # orders, which is enough for roll-up arithmetic and not enough for
11
+ # anything that renders the purchase.
12
+ #
13
+ # One stock location per seller by default, so each parcel is quoted on its
14
+ # own delivery method and the group ships in as many parcels as it has
15
+ # children. Pass `shared_stock_location: true` for the marketplace-fulfilled
16
+ # shape instead: every child ships out of one warehouse on one method, which
17
+ # is what a divided parcel looks like after the split clones it.
18
+ trait :with_parcels do
19
+ transient do
20
+ sellers_count { 2 }
21
+ shared_stock_location { false }
22
+ end
23
+
24
+ after(:create) do |group, evaluator|
25
+ shared_location = create(:stock_location, name: "Shared #{group.number}") if evaluator.shared_stock_location
26
+ shared_method = create(:delivery_method, name: "Shared #{group.number}") if evaluator.shared_stock_location
27
+
28
+ evaluator.sellers_count.times do |index|
29
+ seller = create(:seller, :approved, store: group.store)
30
+ order = create(
31
+ :order_with_line_items,
32
+ store: group.store,
33
+ order_group: group,
34
+ seller: seller,
35
+ number: "#{group.number}-#{index + 1}",
36
+ customer: group.customer,
37
+ currency: group.currency,
38
+ line_items_count: 1
39
+ )
40
+ order.line_items.each { |line_item| line_item.update_columns(seller_id: seller.id) }
41
+
42
+ # The order factory already built the parcel; this decides where it
43
+ # ships from and by what, which is what the grouping reads.
44
+ fulfillment = order.fulfillments.first
45
+ fulfillment.update_columns(
46
+ stock_location_id: (shared_location || create(:stock_location, name: "#{seller.name} warehouse")).id
47
+ )
48
+ fulfillment.delivery_rates.destroy_all
49
+ fulfillment.add_delivery_method(shared_method || create(:delivery_method, name: "#{seller.name} delivery"), true)
50
+ order.recalculate_totals!
51
+ end
52
+
53
+ group.orders.reload
54
+ end
55
+ end
56
+
8
57
  trait :with_orders do
9
58
  transient do
10
59
  sellers_count { 2 }
@@ -29,7 +29,12 @@ FactoryBot.define do
29
29
  end
30
30
 
31
31
  factory :store_credit_payment, class: Spree::Payment, parent: :payment do
32
- payment_method { create(:store_credit_payment_method, store: (order || cart).store) }
33
- source { create(:store_credit, store: (order || cart).store, customer: (order || cart).customer) }
32
+ # An order group owns payments like a cart or an order does, and a split
33
+ # checkout paid with store credit is exactly where that matters.
34
+ payment_method { create(:store_credit_payment_method, store: (order || cart || order_group).store) }
35
+ source do
36
+ owner = order || cart || order_group
37
+ create(:store_credit, store: owner.store, customer: owner.customer)
38
+ end
34
39
  end
35
40
  end
@@ -18,7 +18,6 @@ FactoryBot.define do
18
18
 
19
19
  factory :admin_user, class: Spree.admin_user_class do
20
20
  email { generate(:random_email) }
21
- login { email }
22
21
  password { 'secret123' }
23
22
  password_confirmation { password }
24
23
  first_name { FFaker::Name.first_name }
@@ -438,6 +438,22 @@ steps:
438
438
  once several stores hold tax configuration. The NOT NULL constraints land
439
439
  in 6.1. Idempotent.
440
440
 
441
+ - id: backfill_order_coupon_codes
442
+ name: "Backfill coupon codes on placed orders"
443
+ task: "spree:backfill_order_coupon_codes"
444
+ notes: |
445
+ spree_orders gains a coupon_code column in Spree 6, matching carts.
446
+ Historical placed orders applied coupons only through the promotion
447
+ join tables, so this fills the column from the order's coupon-code
448
+ record, or failing that from the code of an applied single-code
449
+ promotion, so admin filtering and the serializer answer consistently
450
+ for old orders. Reads promotion records only — no dependency on the
451
+ adjustments conversion — and touches completed orders only, so it is
452
+ independent of the cart conversion below. Tune the load size with
453
+ BATCH_SIZE (default 500).
454
+
455
+ Idempotent — only orders whose coupon_code is still blank are scanned.
456
+
441
457
  - id: backfill_order_markets
442
458
  name: "Backfill order markets"
443
459
  task: "spree:backfill_order_markets"
@@ -664,6 +680,24 @@ steps:
664
680
  stop the run. Purge the soft-deleted rows later with
665
681
  spree:upgrade:purge_migrated_external_receives.
666
682
 
683
+ - id: backfill_actor_types
684
+ name: "Name the kind of actor on past orders, returns, refunds and receipts"
685
+ task: "spree:upgrade:backfill_actor_types"
686
+ notes: |
687
+ Orders, refunds, returns, exchanges, claims and stock receipts remember
688
+ who cancelled, approved, created or received them. In 6.0 that can be a
689
+ member of staff or an API key, so each actor id column gained a type
690
+ column beside it. Rows written before the upgrade carry an id and no
691
+ type — they could only ever have pointed at an admin user — so this
692
+ fills in the admin user class. Until it runs those rows still read as
693
+ the staff member they always were, with a deprecation warning.
694
+
695
+ Run AFTER migrate_returns and migrate_external_receives_to_purchase_orders
696
+ so any actor rows those steps write are covered too. Walks
697
+ every model that declares acted_by, so extensions are included.
698
+
699
+ Idempotent — a row that already carries a type is skipped.
700
+
667
701
  - id: recount_stock_levels
668
702
  name: "Fill in the reserved and incoming counters on every stock level"
669
703
  task: "spree:stock:recount_levels"
@@ -0,0 +1,35 @@
1
+ namespace :spree do
2
+ namespace :upgrade do
3
+ desc <<~DESC
4
+ Names the admin user class on every "who did this" row written before
5
+ those associations became polymorphic (Spree 6.0). Rows with an actor id
6
+ and no actor type could only ever have pointed at an admin user, so the
7
+ type is filled in and the transitional reader stops being needed.
8
+
9
+ Runs over whatever each model declares with `acted_by`, so the 6.1
10
+ conversion of the remaining associations reuses this task unchanged.
11
+ Idempotent: a row that already carries a type is skipped.
12
+ DESC
13
+ task backfill_actor_types: :environment do
14
+ actor_type = Spree.admin_user_class.to_s
15
+ total = 0
16
+
17
+ Spree::ActedBy.models.each do |model|
18
+ model.acted_by_associations.each do |name|
19
+ count = model.
20
+ where.not(:"#{name}_id" => nil).
21
+ where(:"#{name}_type" => nil).
22
+ in_batches.
23
+ sum { |batch| batch.update_all(:"#{name}_type" => actor_type) }
24
+
25
+ next if count.zero?
26
+
27
+ total += count
28
+ puts " #{model.table_name}.#{name}: #{count} row(s) set to #{actor_type}."
29
+ end
30
+ end
31
+
32
+ puts total.zero? ? ' Nothing to backfill.' : " Backfilled #{total} actor reference(s)."
33
+ end
34
+ end
35
+ end
@@ -1,7 +1,13 @@
1
1
  namespace :spree do
2
- desc 'Loads sample data (products, customers, orders, configuration)'
2
+ desc 'Loads sample data (products, customers, orders, configuration). Set STORE_ID (prefixed or numeric) or STORE_CODE to target a store other than the default one'
3
3
  task load_sample_data: :environment do
4
- Spree::SampleData::Loader.call
4
+ store = if ENV['STORE_ID'].present?
5
+ Spree::Store.find_by_param!(ENV['STORE_ID'])
6
+ elsif ENV['STORE_CODE'].present?
7
+ Spree::Store.find_by!(code: ENV['STORE_CODE'])
8
+ end
9
+
10
+ Spree::SampleData::Loader.call(store: store)
5
11
  end
6
12
  end
7
13
 
@@ -6,6 +6,7 @@ namespace :spree do
6
6
  # intent and is never copied.
7
7
  MOVED_SETTINGS = {
8
8
  address_requires_phone: { default: false },
9
+ always_include_confirm_step: { default: false },
9
10
  company: { default: false, store_preference: :company_field_enabled },
10
11
  default_stock_reservation_ttl_minutes: { default: 10, store_preference: :stock_reservation_ttl_minutes },
11
12
  disable_sku_validation: { default: false },
@@ -13,7 +13,7 @@
13
13
  #
14
14
  # Usage:
15
15
  # bundle exec rake spree:upgrade # walk all eligible manifests
16
- # bundle exec rake spree:upgrade DRY_RUN=1 # print plan, run nothing
16
+ # bundle exec rake spree:upgrade DRY_RUN=1 # print the steps a run would execute, run nothing
17
17
  # bundle exec rake spree:upgrade STEP=channels # run one step by id (any manifest)
18
18
  # bundle exec rake spree:upgrade TO=5.4 # cap the walk at this version
19
19
  #
@@ -72,10 +72,9 @@ module Spree
72
72
  # rendering, invocation) stay separable and the plan can be inspected
73
73
  # without execution.
74
74
  class Runner
75
- attr_reader :target_version, :step_id, :dry_run, :target_explicit
75
+ attr_reader :target_version, :step_id, :dry_run
76
76
 
77
77
  def initialize(target_version: nil, step_id: nil, dry_run: false)
78
- @target_explicit = !target_version.nil?
79
78
  @target_version = target_version || Spree::Upgrade.installed_minor_version
80
79
  @step_id = step_id
81
80
  @dry_run = dry_run
@@ -98,36 +97,22 @@ module Spree
98
97
 
99
98
  private
100
99
 
101
- # In plan-mode without explicit TO, show manifests ahead of the installed
102
- # version — the path the operator is about to walk. Otherwise filter to
103
- # manifests whose `to` is ≤ target.
100
+ # Plan mode selects exactly what a real run executes, so a dry run after
101
+ # bumping the gem lists every backfill the upgrade is about to perform.
104
102
  def eligible_manifests
105
103
  Spree::Upgrade.available_manifests
106
- .select { |manifest| manifest_eligible?(manifest) }
104
+ .select { |manifest| Spree::Upgrade.compare(manifest[:to], target_version) <= 0 }
107
105
  .map { |entry| load_manifest_yaml(entry) }
108
106
  end
109
107
 
110
- def manifest_eligible?(manifest)
111
- if dry_run && !target_explicit
112
- Spree::Upgrade.compare(manifest[:from], Spree::Upgrade.installed_minor_version) >= 0
113
- else
114
- Spree::Upgrade.compare(manifest[:to], target_version) <= 0
115
- end
116
- end
117
-
118
108
  def load_manifest_yaml(entry)
119
109
  YAML.safe_load_file(File.join(entry[:dir], 'manifest.yml'))
120
110
  end
121
111
 
122
112
  def run_full_walk(manifests)
123
113
  total_steps = manifests.sum { |m| m['steps'].size }
124
- reported_target = if dry_run && !target_explicit
125
- manifests.map { |m| m['to'] }.compact.max_by { |v| Spree::Upgrade.version_parts(v) } || target_version
126
- else
127
- target_version
128
- end
129
114
  puts
130
- puts " Walking #{manifests.size} manifest(s), #{total_steps} step(s) total. Target: Spree #{reported_target}."
115
+ puts " Walking #{manifests.size} manifest(s), #{total_steps} step(s) total. Target: Spree #{target_version}."
131
116
 
132
117
  manifests.each do |manifest|
133
118
  print_manifest_header(manifest)
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.0.beta1
4
+ version: 6.0.0.beta3
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-09-15 00:00:00.000000000 Z
13
+ date: 2026-09-24 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: i18n-tasks
@@ -670,6 +670,7 @@ files:
670
670
  - app/jobs/spree/products/refresh_metrics_job.rb
671
671
  - app/jobs/spree/products/touch_categories_job.rb
672
672
  - app/jobs/spree/products/touch_taxons_job.rb
673
+ - app/jobs/spree/sample_data/load_job.rb
673
674
  - app/jobs/spree/search_provider/index_job.rb
674
675
  - app/jobs/spree/search_provider/remove_job.rb
675
676
  - app/jobs/spree/seller_payouts/sweep_due_job.rb
@@ -693,6 +694,8 @@ files:
693
694
  - app/mailers/spree/webhook_mailer.rb
694
695
  - app/models/acts_as_taggable_on/tag_decorator.rb
695
696
  - app/models/concerns/spree/account_lockout.rb
697
+ - app/models/concerns/spree/acted_by.rb
698
+ - app/models/concerns/spree/actor.rb
696
699
  - app/models/concerns/spree/admin_user_methods.rb
697
700
  - app/models/concerns/spree/calculated_adjustments.rb
698
701
  - app/models/concerns/spree/calculator/currency_amounts.rb
@@ -789,6 +792,7 @@ files:
789
792
  - app/models/spree/allowed_origin.rb
790
793
  - app/models/spree/api_key.rb
791
794
  - app/models/spree/asset.rb
795
+ - app/models/spree/authentication/lockout.rb
792
796
  - app/models/spree/authentication/strategies/base_strategy.rb
793
797
  - app/models/spree/authentication/strategies/email_password_strategy.rb
794
798
  - app/models/spree/authentication/strategies/oidc_strategy.rb
@@ -824,11 +828,6 @@ files:
824
828
  - app/models/spree/channel.rb
825
829
  - app/models/spree/channel/gating.rb
826
830
  - app/models/spree/channel_stock_location.rb
827
- - app/models/spree/checkout/default_requirements.rb
828
- - app/models/spree/checkout/registry.rb
829
- - app/models/spree/checkout/requirement.rb
830
- - app/models/spree/checkout/requirements.rb
831
- - app/models/spree/checkout/step.rb
832
831
  - app/models/spree/claim.rb
833
832
  - app/models/spree/claim_line_item.rb
834
833
  - app/models/spree/claim_reason.rb
@@ -927,6 +926,7 @@ files:
927
926
  - app/models/spree/fulfillment.rb
928
927
  - app/models/spree/fulfillment/custom_events.rb
929
928
  - app/models/spree/fulfillment_changer.rb
929
+ - app/models/spree/fulfillment_group.rb
930
930
  - app/models/spree/fulfillment_item.rb
931
931
  - app/models/spree/fulfillment_provider/base.rb
932
932
  - app/models/spree/fulfillment_provider/digital.rb
@@ -1316,6 +1316,8 @@ files:
1316
1316
  - app/services/spree/seeds/roles.rb
1317
1317
  - app/services/spree/seeds/saved_reports.rb
1318
1318
  - app/services/spree/seeds/seller_requirements.rb
1319
+ - app/services/spree/seeds/store_resources.rb
1320
+ - app/services/spree/seeds/store_scoped.rb
1319
1321
  - app/services/spree/seeds/stores.rb
1320
1322
  - app/services/spree/seeds/tax_categories.rb
1321
1323
  - app/services/spree/sellers/requirements.rb
@@ -1463,6 +1465,7 @@ files:
1463
1465
  - app/workflows/spree/returns/purchase_label.rb
1464
1466
  - app/workflows/spree/returns/receive.rb
1465
1467
  - app/workflows/spree/returns/refund.rb
1468
+ - app/workflows/spree/returns/returnable_quantity.rb
1466
1469
  - app/workflows/spree/seller_payouts/complete.rb
1467
1470
  - app/workflows/spree/seller_payouts/sweep.rb
1468
1471
  - app/workflows/spree/seller_requirement_submissions/accept.rb
@@ -1787,6 +1790,10 @@ files:
1787
1790
  - db/migrate/20260912120000_index_reporting_lifetime_and_promotion_columns.rb
1788
1791
  - db/migrate/20260914000001_add_settlement_to_spree_seller_transfers.rb
1789
1792
  - db/migrate/20260914230000_add_store_to_spree_option_types.rb
1793
+ - db/migrate/20260916000001_add_email_delivery_flags_to_spree_order_groups.rb
1794
+ - db/migrate/20260916000001_order_operation_actors_polymorphic.rb
1795
+ - db/migrate/20260922000001_add_refunded_order_to_spree_store_credits.rb
1796
+ - db/migrate/20260923000001_change_spree_user_identities_tokens_to_text.rb
1790
1797
  - db/sample_data/channels.rb
1791
1798
  - db/sample_data/collections.rb
1792
1799
  - db/sample_data/custom_field_definitions.rb
@@ -1811,6 +1818,7 @@ files:
1811
1818
  - lib/generators/spree/api_resource/templates/admin_controller_spec.rb.tt
1812
1819
  - lib/generators/spree/api_resource/templates/admin_serializer.rb.tt
1813
1820
  - lib/generators/spree/api_resource/templates/factory.rb.tt
1821
+ - lib/generators/spree/api_resource/templates/permissions.en.yml.tt
1814
1822
  - lib/generators/spree/api_resource/templates/store_aliased_serializer.rb.tt
1815
1823
  - lib/generators/spree/api_resource/templates/store_controller.rb.tt
1816
1824
  - lib/generators/spree/api_resource/templates/store_controller_spec.rb.tt
@@ -1840,6 +1848,11 @@ files:
1840
1848
  - lib/mobility/plugins/store_based_fallbacks.rb
1841
1849
  - lib/spree/analytics.rb
1842
1850
  - lib/spree/category_permalink_deduplicator.rb
1851
+ - lib/spree/checkout/default_requirements.rb
1852
+ - lib/spree/checkout/registry.rb
1853
+ - lib/spree/checkout/requirement.rb
1854
+ - lib/spree/checkout/requirements.rb
1855
+ - lib/spree/checkout/step.rb
1843
1856
  - lib/spree/core.rb
1844
1857
  - lib/spree/core/configuration.rb
1845
1858
  - lib/spree/core/controller_helpers/store.rb
@@ -2052,6 +2065,7 @@ files:
2052
2065
  - lib/spree/validators.rb
2053
2066
  - lib/spree/workflow.rb
2054
2067
  - lib/spree_core.rb
2068
+ - lib/tasks/backfill_actor_types.rake
2055
2069
  - lib/tasks/backfill_custom_field_definition_stores.rake
2056
2070
  - lib/tasks/backfill_import_export_tenancy.rake
2057
2071
  - lib/tasks/backfill_library_media_placements.rake
@@ -2122,9 +2136,9 @@ licenses:
2122
2136
  - BSD-3-Clause
2123
2137
  metadata:
2124
2138
  bug_tracker_uri: https://github.com/spree/spree/issues
2125
- changelog_uri: https://github.com/spree/spree/releases/tag/v6.0.0.beta1
2139
+ changelog_uri: https://github.com/spree/spree/releases/tag/v6.0.0.beta3
2126
2140
  documentation_uri: https://docs.spreecommerce.org/
2127
- source_code_uri: https://github.com/spree/spree/tree/v6.0.0.beta1
2141
+ source_code_uri: https://github.com/spree/spree/tree/v6.0.0.beta3
2128
2142
  post_install_message:
2129
2143
  rdoc_options: []
2130
2144
  require_paths:
File without changes
File without changes
File without changes
File without changes