spree_core 5.1.7 → 5.1.8

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f3e90428ce89c62f05918a8c7d2645e3ed5a0c8f6e2bf5d504449c02d061d2e0
4
- data.tar.gz: a35d5f33881801b2378568cd6b74ece89ea605be53eb55a0c2dbcf1e4858cdd6
3
+ metadata.gz: faafdd12eff02906a36f5bb9fd89cf7a18b406299b6a2a8590296617ec91d793
4
+ data.tar.gz: b5c27ee712ea65fa8f4ab6d64b465c740b01338269753a880dc3fb04090470a1
5
5
  SHA512:
6
- metadata.gz: 58de2ea5287871bd79d851c088e26db20f8446702dd29f01ec9f26c2814fdfa511910df3fbeaeb1a48d70c715545602fade93a454b0247bc6891516b52436dbc
7
- data.tar.gz: 5e990e7726d44c047abb3d3ee65eb221953ace057e0465b69fb619cb1b78c19d5c151bd3af20e3ea5e1452bc61acbe5a7194dafbadf5f7befa0899d374bb8c4c
6
+ metadata.gz: '0951f885c20e251d7ec9238d6ab8f3ab32a73d0bafa9ac7978a1afeaacd34f0dfb9cbc94e99a0d96a73e7effa80077a0d5bed960536aee70c802a8abe6ec69ff'
7
+ data.tar.gz: f1afa96f4c463494168c1a6831effb3ea81c95a42ff72691ab8b1b351ad037da784cd2343c1002af72c574939b4cd2eff772837f888b3b67dd1a14c3b3151e68
@@ -1,18 +1,23 @@
1
1
  module Spree
2
2
  module CurrencyHelper
3
+ # Returns the list of all currencies as options for a select field.
4
+ # By default the value is the default currency of the default store.
5
+ # @param selected_value [String] the selected value
6
+ # @return [String] the options for a select field
3
7
  def currency_options(selected_value = nil)
4
8
  selected_value ||= Spree::Store.default.default_currency
5
9
  currencies = ::Money::Currency.table.map do |_code, details|
6
- iso = details[:iso_code]
7
- [iso, "#{details[:name]} (#{iso})"]
10
+ currency_presentation(details[:iso_code])
8
11
  end
9
- options_from_collection_for_select(currencies, :first, :last, selected_value)
12
+ options_from_collection_for_select(currencies, :last, :first, selected_value)
10
13
  end
11
14
 
15
+ # Returns the list of supported currencies for the current store as options for a select field.
16
+ # @return [String] the options for a select field
12
17
  def supported_currency_options
13
18
  return if current_store.nil?
14
19
 
15
- current_store.supported_currencies_list.map(&:iso_code).map { |currency| currency_presentation(currency) }
20
+ @supported_currency_options ||= current_store.supported_currencies_list.map(&:iso_code).map { |currency| currency_presentation(currency) }
16
21
  end
17
22
 
18
23
  def should_render_currency_dropdown?
@@ -21,22 +26,32 @@ module Spree
21
26
  current_store.supported_currencies_list.size > 1
22
27
  end
23
28
 
29
+ # Returns the currency symbol for the given currency.
30
+ # @param currency [String] the currency ISO code
31
+ # @return [String] the currency symbol
24
32
  def currency_symbol(currency)
25
33
  ::Money::Currency.find(currency).symbol
26
34
  end
27
35
 
36
+ # @param currency [String] the currency ISO code
37
+ # @return [Array] the currency presentation and ISO code
28
38
  def currency_presentation(currency)
29
- label = [currency_symbol(currency), currency].compact.join(' ')
39
+ currency_money = currency_money(currency)
40
+ label = "#{currency_money.name} (#{currency_money.iso_code})"
30
41
 
31
42
  [label, currency]
32
43
  end
33
44
 
45
+ # Returns the list of supported currencies for the current store.
46
+ # @return [Array<Money::Currency>] the list of supported currencies
34
47
  def preferred_currencies
48
+ Spree::Deprecation.warn('preferred_currencies is deprecated and will be removed in Spree 6.0. Use current_store.supported_currencies_list instead.')
35
49
  @preferred_currencies ||= current_store.supported_currencies_list
36
50
  end
37
51
 
38
52
  def preferred_currencies_select_options
39
- preferred_currencies.map { |currency| ["#{currency.name} - #{currency.iso_code}", currency] }
53
+ Spree::Deprecation.warn('preferred_currencies_select_options is deprecated and will be removed in Spree 6.0. Use supported_currency_options instead.')
54
+ preferred_currencies.map { |currency| currency_presentation(currency) }
40
55
  end
41
56
 
42
57
  def currency_money(currency = current_currency)
@@ -61,7 +61,7 @@ module Spree
61
61
  name_conditions << multi_search_condition(self, :last_name, full_name.last)
62
62
  end
63
63
 
64
- where(email: sanitized_query).or(where(name_conditions.reduce(:or)))
64
+ where(arel_table[:email].lower.eq(query.downcase)).or(where(name_conditions.reduce(:or)))
65
65
  end
66
66
 
67
67
  self.whitelisted_ransackable_associations = %w[bill_address ship_address addresses tags spree_roles]
@@ -131,7 +131,11 @@ module Spree
131
131
  # Returns true if the user can be deleted
132
132
  # @return [Boolean]
133
133
  def can_be_deleted?
134
- orders.complete.none?
134
+ if role_users.where(resource: Spree::Store.current).exists?
135
+ Spree::Store.current.users.where.not(id: id).exists?
136
+ else
137
+ orders.complete.none?
138
+ end
135
139
  end
136
140
 
137
141
  private
@@ -177,7 +177,9 @@ module Spree
177
177
  end
178
178
 
179
179
  def require_phone?
180
- Spree::Config[:address_requires_phone]
180
+ # We want to collect phone number for quick checkout but not to validate it
181
+ # as it's not available before payment by browser.
182
+ !quick_checkout && Spree::Config[:address_requires_phone]
181
183
  end
182
184
 
183
185
  def require_zipcode?
@@ -2,7 +2,7 @@ module Spree
2
2
  class Calculator < Spree.base_class
3
3
  acts_as_paranoid
4
4
 
5
- belongs_to :calculable, polymorphic: true, optional: true, inverse_of: :calculator
5
+ belongs_to :calculable, polymorphic: true, optional: true, inverse_of: :calculator, touch: true
6
6
 
7
7
  # This method calls a compute_<computable> method. must be overridden in concrete calculator.
8
8
  #
@@ -72,6 +72,10 @@ module Spree
72
72
  true
73
73
  end
74
74
 
75
+ def confirmation_required?
76
+ true
77
+ end
78
+
75
79
  def payment_profiles_supported?
76
80
  true
77
81
  end
@@ -8,6 +8,10 @@ module Spree
8
8
  Spree::PaymentSource
9
9
  end
10
10
 
11
+ def confirmation_required?
12
+ true
13
+ end
14
+
11
15
  def payment_profiles_supported?
12
16
  true
13
17
  end
@@ -14,11 +14,13 @@ module Spree
14
14
 
15
15
  event :redeem do
16
16
  transition active: :redeemed
17
+ transition partially_redeemed: :redeemed
17
18
  end
18
19
  after_transition to: :redeemed, do: :after_redeem
19
20
 
20
21
  event :partial_redeem do
21
22
  transition active: :partially_redeemed
23
+ transition partially_redeemed: :partially_redeemed
22
24
  end
23
25
  end
24
26
 
@@ -68,7 +68,7 @@ module Spree
68
68
  if variant
69
69
  update_price if price.nil?
70
70
  self.cost_price = variant.cost_price if cost_price.nil?
71
- self.currency = variant.currency if currency.nil?
71
+ self.currency = order.currency if currency.nil?
72
72
  end
73
73
  end
74
74
 
@@ -20,7 +20,7 @@ module Spree
20
20
  # :allow_checkout_on_gateway_error is set to false
21
21
  #
22
22
  def process_payments!
23
- process_payments_with(:process!)
23
+ with_lock { process_payments_with(:process!) }
24
24
  end
25
25
 
26
26
  def authorize_payments!
@@ -228,7 +228,7 @@ module Spree
228
228
  conditions << multi_search_condition(Spree::Address, :lastname, full_name.last)
229
229
  end
230
230
 
231
- left_joins(:bill_address).where(email: sanitized_query).or(where(conditions.reduce(:or)))
231
+ left_joins(:bill_address).where(arel_table[:email].lower.eq(query.downcase)).or(where(conditions.reduce(:or)))
232
232
  end
233
233
 
234
234
  # Use this method in other gems that wish to register their own custom logic
@@ -268,16 +268,14 @@ module Spree
268
268
  end
269
269
 
270
270
  def order_refunded?
271
- (payment_state == 'void' && refunds.sum(:amount).positive?) || refunds.sum(:amount) == total
271
+ (payment_state.in?(%w[void failed]) && refunds.sum(:amount).positive?) ||
272
+ refunds.sum(:amount) == total_minus_store_credits - additional_tax_total.abs
272
273
  end
273
274
 
274
275
  def partially_refunded?
275
- return false if refunds.empty?
276
+ return false if refunds.empty? || payment_state.in?(%w[void failed])
276
277
 
277
- # we must deduct not returned amount, otherwise it can look like order has not been fully refunded
278
- amount_not_refunded = additional_tax_total.abs + total_applied_store_credit
279
-
280
- refunds.sum(:amount) < total - amount_not_refunded
278
+ refunds.sum(:amount) < total_minus_store_credits - additional_tax_total.abs
281
279
  end
282
280
 
283
281
  # Indicates whether or not the user is allowed to proceed to checkout.
@@ -301,7 +299,7 @@ module Spree
301
299
  # If true, causes the confirmation step to happen during the checkout process
302
300
  def confirmation_required?
303
301
  Spree::Config[:always_include_confirm_step] ||
304
- payments.valid.map(&:payment_method).compact.any?(&:payment_profiles_supported?) ||
302
+ payments.valid.map(&:payment_method).compact.any?(&:confirmation_required?) ||
305
303
  # Little hacky fix for #4117
306
304
  # If this wasn't here, order would transition to address state on confirm failure
307
305
  # because there would be no valid payments any more.
@@ -15,7 +15,7 @@ module Spree
15
15
 
16
16
  def default_sections
17
17
  [
18
- Spree::PageSections::PageTitle.new,
18
+ Spree::PageSections::PageTitle.new(preferred_title: DISPLAY_NAME),
19
19
  Spree::PageSections::PostGrid.new
20
20
  ]
21
21
  end
@@ -30,7 +30,7 @@ module Spree
30
30
 
31
31
  def default_sections
32
32
  [
33
- Spree::PageSections::PageTitle.new,
33
+ Spree::PageSections::PageTitle.new(preferred_title: 'Search Results'),
34
34
  Spree::PageSections::ProductGrid.new
35
35
  ]
36
36
  end
@@ -23,7 +23,7 @@ module Spree
23
23
 
24
24
  def default_sections
25
25
  [
26
- Spree::PageSections::PageTitle.new,
26
+ Spree::PageSections::PageTitle.new(preferred_title: Spree.t(:shop_all)),
27
27
  Spree::PageSections::ProductGrid.new
28
28
  ]
29
29
  end
@@ -62,6 +62,10 @@ module Spree
62
62
  unscoped { find(*args) }
63
63
  end
64
64
 
65
+ def confirmation_required?
66
+ false
67
+ end
68
+
65
69
  def payment_profiles_supported?
66
70
  false
67
71
  end
@@ -12,12 +12,19 @@ module Spree
12
12
  default_shipping = opts.fetch(:default_shipping, false)
13
13
  address_changes_except = opts.fetch(:address_changes_except, [])
14
14
  create_new_address_on_update = opts.fetch(:create_new_address_on_update, false)
15
+ Spree::Deprecation.warn('Spree::Addresses::Update create_new_address_on_update parameter is deprecated and will be removed in Spree 6.') if create_new_address_on_update
15
16
 
16
17
  prepare_address_params!(address, address_params)
17
18
  address.assign_attributes(address_params)
18
19
 
19
- address_changed = address.changes.except(*address_changes_except).any?
20
+ address_changes = address.changes.except(*address_changes_except)
20
21
 
22
+ # Ignore changes that are only different in case as encrypted fields are processed by rails as downcased
23
+ address_changes.reject! do |attr, (old_val, new_val)|
24
+ old_val.to_s.casecmp?(new_val.to_s)
25
+ end
26
+
27
+ address_changed = address_changes.any?
21
28
  if !address_changed && defaults_changed?(address, default_billing, default_shipping)
22
29
  assign_to_user_as_default(
23
30
  user: address.user,
@@ -7,7 +7,11 @@ module Spree
7
7
 
8
8
  def call(order:, accepts_email_marketing: false)
9
9
  existing_user = Spree.user_class.find_by(email: order.email)
10
- return existing_user if existing_user.present?
10
+ if existing_user.present?
11
+ order.update_columns(user_id: existing_user.id, updated_at: Time.current)
12
+ order.user = existing_user
13
+ return success(existing_user)
14
+ end
11
15
 
12
16
  user = create_new_user(order, accepts_email_marketing)
13
17
  return failure(:user_creation_failed) unless user.persisted?
@@ -1,5 +1,21 @@
1
1
  module Spree
2
2
  module Products
3
+ # Prepares nested attributes for product updates, handling multi-store scenarios
4
+ # and permissions.
5
+ #
6
+ # This service ensures that when editing a product in one store, taxon associations
7
+ # from other stores are preserved. This prevents accidental data loss when a store
8
+ # admin updates product categories in their store without affecting other stores.
9
+ #
10
+ # @example
11
+ # service = Spree::Products::PrepareNestedAttributes.new(
12
+ # product,
13
+ # current_store,
14
+ # params,
15
+ # current_ability
16
+ # )
17
+ # prepared_params = service.call
18
+ #
3
19
  class PrepareNestedAttributes
4
20
  def initialize(product, store, params, ability)
5
21
  @product = product
@@ -60,6 +76,12 @@ module Spree
60
76
  # ensure there is at least one store
61
77
  params[:store_ids] = [store.id] if params[:store_ids].blank?
62
78
 
79
+ # Preserve taxon associations from other stores
80
+ # Only merge taxon_ids from other stores if taxon_ids are being updated
81
+ if params.key?(:taxon_ids)
82
+ params[:taxon_ids] = merge_taxons_from_other_stores(params[:taxon_ids])
83
+ end
84
+
63
85
  # Add empty list for option_type_ids and mark variants as removed if there are no variants and options
64
86
  if params[:variants_attributes].blank? && variants_to_remove.any? && !params.key?(:option_type_ids)
65
87
  params[:option_type_ids] = []
@@ -133,6 +155,28 @@ module Spree
133
155
  attributes
134
156
  end
135
157
 
158
+ # Merges taxon IDs from other stores with submitted taxon IDs from current store.
159
+ #
160
+ # This prevents the loss of taxon associations from other stores when a product
161
+ # is edited in one store. Each store's taxonomy is independent, so editing
162
+ # categories in Store A should not affect categories in Store B.
163
+ #
164
+ # @param submitted_taxon_ids [Array<String>] Taxon IDs from the current store
165
+ # @return [Array<String>] Combined unique taxon IDs
166
+ def merge_taxons_from_other_stores(submitted_taxon_ids)
167
+ return submitted_taxon_ids if product.new_record?
168
+
169
+ # Get taxon IDs from other stores that should be preserved
170
+ other_stores_taxon_ids = product.taxons
171
+ .joins(:taxonomy)
172
+ .where.not(spree_taxonomies: { store_id: store.id })
173
+ .pluck(:id)
174
+ .map(&:to_s)
175
+
176
+ # Merge with submitted taxon IDs from current store and remove duplicates
177
+ (submitted_taxon_ids + other_stores_taxon_ids).uniq
178
+ end
179
+
136
180
  def update_option_value_variants(option_value_params, existing_variant)
137
181
  return {} unless option_value_params.present?
138
182
  return {} unless can_manage_option_types?
@@ -8,7 +8,7 @@ module Spree
8
8
  module Addresses
9
9
  class PhoneValidator < ActiveModel::Validator
10
10
  def validate(address)
11
- return if !Spree::Config[:address_requires_phone] || address.phone.blank? || address.country.blank? || address.country_iso.blank?
11
+ return if !address.require_phone? || address.phone.blank? || address.country.blank? || address.country_iso.blank?
12
12
 
13
13
  phone = Phonelib.parse(address.phone)
14
14
  unless phone.valid_for_country?(address.country_iso)
@@ -13,12 +13,14 @@
13
13
  </div>
14
14
  <% elsif source.is_a?(Spree::StoreCredit) %>
15
15
  <% if source.originator_type == 'Spree::GiftCard' %>
16
- <div class="d-flex align-items-center">
17
- <%= icon('gift', height: 30, class: 'mr-3 text-muted opacity-50') %>
18
- <div>
19
- <p class="mb-1"><%= Spree.t(:gift_card) %>: <strong><%= source.originator.code.upcase %></strong></p>
20
- <%= @order.display_gift_card_total %>
21
- </div>
16
+ <% if respond_to?(:heroicon) %>
17
+ <%= heroicon('gift', options: { style: 'margin-right: 0 !important;' }) %>
18
+ <% else %>
19
+ <%= icon('gift', height: 30) %>
20
+ <% end %>
21
+ <div>
22
+ <p class="mb-1"><%= Spree.t(:gift_card) %>: <strong><%= source.originator.code.upcase %></strong></p>
23
+ <%= @order.display_gift_card_total %>
22
24
  </div>
23
25
  <% elsif payment_method_icon_tag(payment.payment_method.payment_icon_name).present? %>
24
26
  <div class="d-flex flex align-items-center items-center">
@@ -1,5 +1,5 @@
1
1
  module Spree
2
- VERSION = '5.1.7'.freeze
2
+ VERSION = '5.1.8'.freeze
3
3
 
4
4
  def self.version
5
5
  VERSION
@@ -30,13 +30,13 @@ namespace :common do
30
30
  Spree::DummyGenerator.start dummy_app_args
31
31
 
32
32
  unless skip_javascript
33
- system('bin/rails importmap:install turbo:install stimulus:install')
33
+ system('bundle exec rails importmap:install turbo:install stimulus:install')
34
34
  end
35
35
 
36
36
  # install devise if it's not the legacy user, useful for testing storefront
37
37
  if args[:authentication] == 'devise' && args[:user_class] != 'Spree::LegacyUser'
38
- system('bin/rails g devise:install --force --auto-accept')
39
- system("bin/rails g devise #{args[:user_class]} --force --auto-accept")
38
+ system('bundle exec rails g devise:install --force --auto-accept')
39
+ system("bundle exec rails g devise #{args[:user_class]} --force --auto-accept")
40
40
  system('rm -rf spec') # we need to cleanup factories created by devise to avoid naming conflict
41
41
  end
42
42
 
@@ -59,7 +59,7 @@ namespace :common do
59
59
 
60
60
  unless ENV['NO_MIGRATE']
61
61
  puts 'Setting up dummy database...'
62
- system('bin/rails db:environment:set RAILS_ENV=test > /dev/null 2>&1')
62
+ system('bundle exec rails db:environment:set RAILS_ENV=test > /dev/null 2>&1')
63
63
  system('bundle exec rake db:drop db:create > /dev/null 2>&1')
64
64
  Spree::DummyModelGenerator.start
65
65
  system('bundle exec rake db:migrate > /dev/null 2>&1')
@@ -81,7 +81,7 @@ namespace :common do
81
81
 
82
82
  task :db_setup do |_t|
83
83
  puts 'Setting up dummy database...'
84
- system('bin/rails db:environment:set RAILS_ENV=test > /dev/null 2>&1')
84
+ system('bundle exec rails db:environment:set RAILS_ENV=test > /dev/null 2>&1')
85
85
  system('bundle exec rake db:drop db:create > /dev/null 2>&1')
86
86
  Spree::DummyModelGenerator.start
87
87
  system('bundle exec rake db:migrate > /dev/null 2>&1')
data/lib/tasks/core.rake CHANGED
@@ -186,10 +186,9 @@ use rake db:load_file[/absolute/path/to/sample/filename.rb]}
186
186
  end
187
187
 
188
188
  task migrate_admin_users_to_role_users: :environment do |_t, _args|
189
- Spree.admin_user_class.all.each do |admin_user|
190
- Spree::Store.all.each do |store|
191
- store.add_user(admin_user)
192
- end
189
+ default_store = Spree::Store.default
190
+ Spree::RoleUser.where(resource: nil).each do |role_user|
191
+ role_user.update_columns(resource_type: default_store.class.name, resource_id: default_store.id)
193
192
  end
194
193
  end
195
194
  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: 5.1.7
4
+ version: 5.1.8
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: 2025-09-29 00:00:00.000000000 Z
13
+ date: 2025-10-30 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: i18n-tasks
@@ -968,7 +968,6 @@ files:
968
968
  - app/services/spree/account/update.rb
969
969
  - app/services/spree/addresses/create.rb
970
970
  - app/services/spree/addresses/helper.rb
971
- - app/services/spree/addresses/phone_validator.rb
972
971
  - app/services/spree/addresses/update.rb
973
972
  - app/services/spree/cart/add_item.rb
974
973
  - app/services/spree/cart/associate.rb
@@ -1057,6 +1056,7 @@ files:
1057
1056
  - app/sorters/spree/orders/sort.rb
1058
1057
  - app/sorters/spree/products/sort.rb
1059
1058
  - app/validators/email_validator.rb
1059
+ - app/validators/spree/addresses/phone_validator.rb
1060
1060
  - app/validators/spree/url_validator.rb
1061
1061
  - app/views/action_text/video_embeds/_thumbnail.html.erb
1062
1062
  - app/views/action_text/video_embeds/_video_embed.html.erb
@@ -1383,9 +1383,9 @@ licenses:
1383
1383
  - BSD-3-Clause
1384
1384
  metadata:
1385
1385
  bug_tracker_uri: https://github.com/spree/spree/issues
1386
- changelog_uri: https://github.com/spree/spree/releases/tag/v5.1.7
1386
+ changelog_uri: https://github.com/spree/spree/releases/tag/v5.1.8
1387
1387
  documentation_uri: https://docs.spreecommerce.org/
1388
- source_code_uri: https://github.com/spree/spree/tree/v5.1.7
1388
+ source_code_uri: https://github.com/spree/spree/tree/v5.1.8
1389
1389
  post_install_message:
1390
1390
  rdoc_options: []
1391
1391
  require_paths: