spree_core 5.5.4 → 5.6.0.rc1

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 (136) hide show
  1. checksums.yaml +4 -4
  2. data/app/helpers/spree/base_helper.rb +12 -2
  3. data/app/helpers/spree/currency_helper.rb +1 -4
  4. data/app/helpers/spree/locale_helper.rb +18 -13
  5. data/app/helpers/spree/localized_names_helper.rb +25 -0
  6. data/app/mailers/spree/admin_user_mailer.rb +71 -0
  7. data/app/mailers/spree/base_mailer.rb +73 -11
  8. data/app/mailers/spree/export_mailer.rb +7 -7
  9. data/app/mailers/spree/import_mailer.rb +19 -0
  10. data/app/mailers/spree/invitation_mailer.rb +16 -11
  11. data/app/mailers/spree/report_mailer.rb +6 -6
  12. data/app/mailers/spree/webhook_mailer.rb +7 -6
  13. data/app/models/concerns/spree/admin_user_methods.rb +44 -0
  14. data/app/models/concerns/spree/legacy_multi_store_support.rb +48 -0
  15. data/app/models/concerns/spree/presentation_translatable.rb +39 -0
  16. data/app/models/concerns/spree/product_scopes.rb +21 -3
  17. data/app/models/concerns/spree/single_store_resource.rb +5 -0
  18. data/app/models/concerns/spree/store_scoped_resource.rb +10 -0
  19. data/app/models/concerns/spree/stores/markets.rb +9 -1
  20. data/app/models/concerns/spree/stores/setup.rb +37 -23
  21. data/app/models/concerns/spree/translatable_resource.rb +95 -5
  22. data/app/models/concerns/spree/user_methods.rb +27 -2
  23. data/app/models/spree/ability.rb +5 -4
  24. data/app/models/spree/allowed_origin.rb +31 -0
  25. data/app/models/spree/category.rb +12 -2
  26. data/app/models/spree/channel/gating.rb +67 -0
  27. data/app/models/spree/channel.rb +1 -0
  28. data/app/models/spree/classification.rb +12 -0
  29. data/app/models/spree/country.rb +22 -1
  30. data/app/models/spree/credit_card.rb +29 -0
  31. data/app/models/spree/currency.rb +47 -0
  32. data/app/models/spree/current.rb +12 -1
  33. data/app/models/spree/customer_group.rb +1 -0
  34. data/app/models/spree/export.rb +19 -0
  35. data/app/models/spree/exports/coupon_codes.rb +1 -2
  36. data/app/models/spree/import.rb +84 -1
  37. data/app/models/spree/import_row.rb +7 -1
  38. data/app/models/spree/imports/product_translations.rb +6 -0
  39. data/app/models/spree/locale.rb +104 -0
  40. data/app/models/spree/option_type.rb +11 -13
  41. data/app/models/spree/option_value.rb +5 -17
  42. data/app/models/spree/order.rb +19 -2
  43. data/app/models/spree/payment_method.rb +6 -5
  44. data/app/models/spree/policy.rb +2 -1
  45. data/app/models/spree/product.rb +23 -2
  46. data/app/models/spree/promotion/rules/channel.rb +37 -0
  47. data/app/models/spree/promotion/rules/market.rb +37 -0
  48. data/app/models/spree/promotion.rb +5 -3
  49. data/app/models/spree/promotion_handler/promotion_duplicator.rb +1 -1
  50. data/app/models/spree/role_user.rb +4 -0
  51. data/app/models/spree/search_provider/meilisearch.rb +22 -9
  52. data/app/models/spree/setup_task.rb +12 -0
  53. data/app/models/spree/setup_tasks.rb +86 -0
  54. data/app/models/spree/shipment.rb +38 -1
  55. data/app/models/spree/shipment_handler.rb +1 -1
  56. data/app/models/spree/stock/availability_validator.rb +1 -1
  57. data/app/models/spree/stock/quantifier.rb +21 -1
  58. data/app/models/spree/stock_item.rb +2 -1
  59. data/app/models/spree/stock_location.rb +4 -1
  60. data/app/models/spree/store.rb +45 -9
  61. data/app/models/spree/store_payment_method.rb +5 -1
  62. data/app/models/spree/store_promotion.rb +4 -0
  63. data/app/models/spree/taxon.rb +112 -9
  64. data/app/models/spree/taxonomy.rb +1 -1
  65. data/app/models/spree/translations/batch.rb +104 -0
  66. data/app/models/spree/variant.rb +29 -2
  67. data/app/models/spree/webhook_delivery.rb +0 -22
  68. data/app/presenters/spree/data_feeds/google_presenter.rb +1 -28
  69. data/app/presenters/spree/search_provider/product_presenter.rb +3 -0
  70. data/app/services/spree/carts/complete.rb +3 -0
  71. data/app/services/spree/fulfillments/create.rb +259 -0
  72. data/app/services/spree/locales/set_fallback_locale_for_store.rb +7 -1
  73. data/app/services/spree/locales.rb +52 -0
  74. data/app/services/spree/products/prepare_nested_attributes.rb +40 -12
  75. data/app/services/spree/shipments/update.rb +1 -1
  76. data/app/services/spree/stock_reservations/reserve.rb +4 -1
  77. data/app/services/spree/taxons/add_products.rb +3 -0
  78. data/app/services/spree/taxons/remove_products.rb +3 -0
  79. data/app/subscribers/spree/admin_user_email_subscriber.rb +29 -0
  80. data/app/subscribers/spree/import_email_subscriber.rb +26 -0
  81. data/app/views/layouts/spree/base_mailer.html.erb +14 -17
  82. data/app/views/spree/admin_user_mailer/confirmation_email.html.erb +7 -0
  83. data/app/views/spree/admin_user_mailer/password_reset_email.html.erb +8 -0
  84. data/app/views/spree/export_mailer/export_done.html.erb +6 -14
  85. data/app/views/spree/import_mailer/import_done.html.erb +12 -0
  86. data/app/views/spree/invitation_mailer/invitation_accepted.html.erb +4 -12
  87. data/app/views/spree/invitation_mailer/invitation_email.html.erb +11 -24
  88. data/app/views/spree/report_mailer/report_done.html.erb +6 -14
  89. data/app/views/spree/shared/_base_mailer_footer.html.erb +15 -8
  90. data/app/views/spree/shared/_base_mailer_header.html.erb +7 -5
  91. data/app/views/spree/shared/_base_mailer_stylesheets.html.erb +211 -273
  92. data/app/views/spree/shared/_mailer_button.html.erb +10 -0
  93. data/app/views/spree/shared/_mailer_hero.html.erb +12 -0
  94. data/app/views/spree/shared/_purchased_items_styles.html.erb +112 -0
  95. data/app/views/spree/webhook_mailer/endpoint_disabled.html.erb +39 -14
  96. data/config/initializers/spree_store_setup_tasks.rb +23 -0
  97. data/config/locales/en.yml +42 -992
  98. data/db/migrate/20260613000001_add_store_id_to_spree_role_users.rb +10 -0
  99. data/db/migrate/20260626000001_add_store_id_to_spree_taxons.rb +11 -0
  100. data/db/migrate/20260627000001_add_products_count_to_spree_taxons.rb +6 -0
  101. data/db/migrate/20260628000001_add_store_id_to_spree_promotions.rb +10 -0
  102. data/db/migrate/20260628000002_add_store_id_to_spree_payment_methods.rb +11 -0
  103. data/db/migrate/20260630000001_add_preorder_fields_to_spree_variants.rb +7 -0
  104. data/db/migrate/20260707000001_add_fingerprint_to_spree_credit_cards.rb +39 -0
  105. data/db/migrate/20260710000001_add_import_id_status_index_to_spree_import_rows.rb +7 -0
  106. data/db/migrate/20260711000001_add_preferences_to_spree_exports.rb +7 -0
  107. data/db/sample_data/promotions.rb +1 -1
  108. data/lib/generators/spree/authentication/dummy/templates/create_spree_admin_users.rb.tt +0 -1
  109. data/lib/mobility/plugins/store_based_fallbacks.rb +7 -5
  110. data/lib/spree/core/configuration.rb +1 -0
  111. data/lib/spree/core/controller_helpers/locale.rb +4 -1
  112. data/lib/spree/core/controller_helpers/strong_parameters.rb +1 -0
  113. data/lib/spree/core/dependencies.rb +3 -0
  114. data/lib/spree/core/engine.rb +24 -0
  115. data/lib/spree/core/preferences/preferable.rb +6 -1
  116. data/lib/spree/core/previews/admin_user_preview.rb +22 -0
  117. data/lib/spree/core/previews/export_preview.rb +37 -0
  118. data/lib/spree/core/previews/invitation_preview.rb +47 -0
  119. data/lib/spree/core/previews/preview_data.rb +52 -0
  120. data/lib/spree/core/previews/report_preview.rb +40 -0
  121. data/lib/spree/core/previews/webhook_preview.rb +34 -0
  122. data/lib/spree/core/version.rb +1 -1
  123. data/lib/spree/core.rb +22 -1
  124. data/lib/spree/permitted_attributes.rb +5 -4
  125. data/lib/spree/testing_support/factories/export_factory.rb +4 -0
  126. data/lib/spree/testing_support/factories/payment_method_factory.rb +1 -9
  127. data/lib/spree/testing_support/factories/promotion_factory.rb +1 -9
  128. data/lib/spree/testing_support/factories/promotion_rule_factory.rb +24 -0
  129. data/lib/spree/translations.rb +113 -0
  130. data/lib/spree/upgrades/5_5_to_5_6/manifest.yml +59 -0
  131. data/lib/tasks/role_users.rake +21 -0
  132. data/lib/tasks/single_store_associations.rake +65 -0
  133. data/lib/tasks/taxons.rake +62 -1
  134. metadata +47 -6
  135. data/app/models/concerns/spree/webhook_payload_redaction.rb +0 -88
  136. data/app/models/spree/product/legacy_multi_store_support.rb +0 -40
@@ -17,19 +17,11 @@ FactoryBot.define do
17
17
 
18
18
  factory :promotion, class: Spree::Promotion do
19
19
  name { 'Promo' }
20
+ store { Spree::Store.find_by(default: true) || association(:store) }
20
21
  sequence :code do |n|
21
22
  "CODE-#{n}"
22
23
  end
23
24
 
24
- before(:create) do |promotion, _evaluator|
25
- if promotion.stores.empty?
26
- default_store = Spree::Store.default.persisted? ? Spree::Store.default : nil
27
- store = default_store || create(:store)
28
-
29
- promotion.stores << [store]
30
- end
31
- end
32
-
33
25
  trait :with_line_item_adjustment do
34
26
  transient do
35
27
  adjustment_rate { 10 }
@@ -30,4 +30,28 @@ FactoryBot.define do
30
30
  customer_group_ids { [] }
31
31
  end
32
32
  end
33
+
34
+ factory :promotion_rule_channel, class: Spree::Promotion::Rules::Channel do
35
+ association :promotion
36
+
37
+ after(:build) do |rule, evaluator|
38
+ rule.preferred_channel_ids = evaluator.channel_ids if evaluator.respond_to?(:channel_ids)
39
+ end
40
+
41
+ transient do
42
+ channel_ids { [] }
43
+ end
44
+ end
45
+
46
+ factory :promotion_rule_market, class: Spree::Promotion::Rules::Market do
47
+ association :promotion
48
+
49
+ after(:build) do |rule, evaluator|
50
+ rule.preferred_market_ids = evaluator.market_ids if evaluator.respond_to?(:market_ids)
51
+ end
52
+
53
+ transient do
54
+ market_ids { [] }
55
+ end
56
+ end
33
57
  end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ # Builds the admin translation matrix, field-discovery metadata, and the
5
+ # public translatable-resource registry for records in
6
+ # +Spree.translatable_resources+. Stateless helper shared by the dedicated
7
+ # translations endpoint, the +?expand=translations+ serializer attribute, the
8
+ # batch write controller, and the discovery endpoint.
9
+ module Translations
10
+ module_function
11
+
12
+ # @return [Hash{String=>Hash}] locale => { field => value, "translated_field_count" => Integer }
13
+ def matrix_for(record, locales: nil)
14
+ fields = field_keys(record)
15
+ locales ||= non_default_locales(record)
16
+
17
+ locales.index_with do |locale|
18
+ translated = field_values(record, locale, fields)
19
+ translated.merge('translated_field_count' => translated.count { |_k, v| v.present? })
20
+ end
21
+ end
22
+
23
+ # @return [Array<Hash>] [{ "key" => "name", "type" => "string", "source" => "Espresso Machine" }, ...]
24
+ def fields_for(record)
25
+ Mobility.with_locale(default_locale(record)) do
26
+ field_keys(record).map do |field|
27
+ { 'key' => field, 'type' => field_type(record.class, field), 'source' => record.public_send(field) }
28
+ end
29
+ end
30
+ end
31
+
32
+ # @return [Array<Hash>] registry made public: [{ "resource_type" => "product", "fields" => [{key,type}] }]
33
+ def registry
34
+ Spree.translatable_resources.map do |klass|
35
+ {
36
+ 'resource_type' => public_resource_type(klass),
37
+ 'fields' => klass.public_translatable_fields.map { |f| { 'key' => f.to_s, 'type' => field_type(klass, f) } }
38
+ }
39
+ end
40
+ end
41
+
42
+ # @return [String] underscored, demodulized model name (e.g. "option_type")
43
+ def resource_type(klass)
44
+ klass.name.demodulize.underscore
45
+ end
46
+
47
+ # The PUBLIC resource-type token used in read (document/registry) and
48
+ # write (batch) payloads. Taxon is exposed as "category" (routes use the
49
+ # 5.5 rename) while the model element stays "taxon"; this keeps the read
50
+ # and write contracts consistent.
51
+ #
52
+ # @return [String]
53
+ def public_resource_type(klass)
54
+ return 'category' if klass <= Spree::Taxon
55
+
56
+ resource_type(klass)
57
+ end
58
+
59
+ # Inverse of +public_resource_type+: maps a public token to its
60
+ # registered translatable class, or nil if not translatable.
61
+ #
62
+ # @param token [String, Symbol] e.g. "product", "option_value", "category"
63
+ # @return [Class, nil]
64
+ def resource_class(token)
65
+ # Recomputed per call (not memoized) so a dev-mode class reload of a
66
+ # registry member doesn't leave a stale class reference behind.
67
+ map = Spree.translatable_resources.index_by { |klass| public_resource_type(klass) }
68
+ map[token.to_s]
69
+ end
70
+
71
+ # The editor type for a translatable field: +html+ when the model declares
72
+ # it as rich text (drives a rich-text editor in the SPA), else +string+.
73
+ # @param klass [Class] a translatable model
74
+ # @param field [String, Symbol] public field name
75
+ # @return [String]
76
+ def field_type(klass, field)
77
+ klass.translatable_rich_text_fields.map(&:to_sym).include?(field.to_sym) ? 'html' : 'string'
78
+ end
79
+
80
+ # Public field names, so the matrix read/write keys match the serializer
81
+ # (e.g. OptionType exposes `label`, not the internal `presentation`).
82
+ def field_keys(record)
83
+ record.class.public_translatable_fields.map(&:to_s)
84
+ end
85
+ private_class_method :field_keys
86
+
87
+ def field_values(record, locale, fields)
88
+ Mobility.with_locale(locale) do
89
+ fields.index_with do |field|
90
+ # fallback: false so an absent translation reads as nil, not the source value
91
+ record.public_send(field, fallback: false)
92
+ rescue ArgumentError
93
+ # fields without a Mobility reader signature still respond to the bare getter
94
+ record.public_send(field)
95
+ end
96
+ end
97
+ end
98
+ private_class_method :field_values
99
+
100
+ def default_locale(record)
101
+ record.translatable_store&.default_locale || I18n.default_locale.to_s
102
+ end
103
+ private_class_method :default_locale
104
+
105
+ def non_default_locales(record)
106
+ store = record.translatable_store
107
+ return [] unless store
108
+
109
+ (store.supported_locales_list - [store.default_locale]).sort
110
+ end
111
+ private_class_method :non_default_locales
112
+ end
113
+ end
@@ -0,0 +1,59 @@
1
+ # Spree 5.5 → 5.6 upgrade manifest.
2
+ #
3
+ # Lists ONLY the version-specific rake tasks that perform data backfills.
4
+ # Universal upgrade steps (bundle update, db:migrate, scheduling cron jobs,
5
+ # reviewing breaking changes) are NOT in this file:
6
+ #
7
+ # - `bundle update` + `db:migrate` are handled by your deploy pipeline
8
+ # (Heroku release phase, K8s init container, Render auto-migrate,
9
+ # Capistrano deploy hook, etc.) and by the @spree/cli's `spree upgrade`
10
+ # wrapper for local development.
11
+ # - Cron scheduling, optional tunings, and human-readable behavior changes
12
+ # live in the upgrade doc at docs/developer/upgrades/5.5-to-5.6.mdx.
13
+ #
14
+ # This manifest is the machine-runnable shape — what `bin/rake spree:upgrade`
15
+ # (in production) and `spree upgrade` (in dev) execute. Every step must be a
16
+ # rake task and must be idempotent. Re-running the full manifest is safe.
17
+ ---
18
+ from: "5.5"
19
+ to: "5.6"
20
+ docs: "https://spreecommerce.org/docs/developer/upgrades/5.5-to-5.6"
21
+
22
+ steps:
23
+ - id: role_user_store_ids
24
+ name: "Backfill store_id on role assignments"
25
+ task: "spree:role_users:backfill_store_ids"
26
+ notes: |
27
+ Sets spree_role_users.store_id from the store resource so Spree::Ability
28
+ resolves roles by store. Store-scoped assignments only; extensions (e.g.
29
+ spree_multi_vendor) backfill their own resource types. Store admins stay
30
+ authorized via the spree_admin? fallback until this runs.
31
+
32
+ - id: single_store_promotions_payment_methods
33
+ name: "Backfill store_id on promotions and payment methods"
34
+ task: "spree:upgrade:populate_single_store_associations"
35
+ notes: |
36
+ Sets store_id on Spree::Promotion and Spree::PaymentMethod from the legacy
37
+ spree_promotions_stores / spree_payment_methods_stores join tables. Until
38
+ this runs, migrated rows have a NULL store_id and are hidden from
39
+ store-scoped lookups (and unavailable at checkout), so run it right after
40
+ db:migrate. Records shared across stores keep one owner (promotions:
41
+ earliest then lowest store_id; payment methods: lowest store_id) unless
42
+ spree_multi_store is installed.
43
+
44
+ - id: taxon_store_id
45
+ name: "Backfill store_id on taxons"
46
+ task: "spree:taxons:backfill_store_id"
47
+ notes: |
48
+ Sets spree_taxons.store_id from each taxon's taxonomy, then resolves
49
+ taxonomy-less rows through their parent chain. Until this runs, existing
50
+ taxons keep resolving their store via the taxonomy join (Taxon.for_store
51
+ fallback); taxonomy-less categories (Spree::Category) rely on the direct
52
+ store_id, so the backfill is required for them.
53
+
54
+ - id: taxon_products_count
55
+ name: "Backfill products_count on taxons"
56
+ task: "spree:taxons:backfill_products_count"
57
+ notes: |
58
+ Recomputes the descendant-inclusive products_count counter cache on every
59
+ taxon in batches.
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :spree do
4
+ namespace :role_users do
5
+ desc <<~DESC
6
+ Backfills +spree_role_users.store_id+ for store-scoped role assignments
7
+ (resource_type = 'Spree::Store') by copying +resource_id+. Idempotent —
8
+ only rows with a null +store_id+ are touched.
9
+
10
+ Role resolution (Spree::Ability) scopes by +store_id+, so run this after
11
+ adding the column. Until it does, the +spree_admin?+ fallback keeps store
12
+ admins authorized. Role assignments on non-store resources (e.g.
13
+ Spree::Vendor) are backfilled by the owning extension.
14
+ DESC
15
+ task backfill_store_ids: :environment do
16
+ count = Spree::RoleUser.where(resource_type: Spree::Store.name, store_id: nil)
17
+ .update_all('store_id = resource_id')
18
+ puts " Backfilled store_id on #{count} store-scoped role assignment(s)."
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,65 @@
1
+ namespace :spree do
2
+ namespace :upgrade do
3
+ desc <<~DESC
4
+ Populates +spree_promotions.store_id+ and +spree_payment_methods.store_id+
5
+ from the legacy +spree_promotions_stores+ / +spree_payment_methods_stores+
6
+ join tables. Idempotent — re-running skips records that already have a
7
+ +store_id+.
8
+
9
+ Run once after upgrading to Spree 5.6+. Multi-store merchants must install
10
+ +spree_multi_store+ before running; without it, a record shared across
11
+ several stores keeps only one owner (promotions: the earliest
12
+ +spree_promotions_stores+ row by +created_at+; payment methods: the lowest
13
+ +store_id+, since that join has no timestamps) and the other stores lose
14
+ the shared record. Each shared record is logged so the loss is visible.
15
+ DESC
16
+ task populate_single_store_associations: :environment do
17
+ shared = Hash.new(0)
18
+
19
+ if ActiveRecord::Base.connection.table_exists?(Spree::StorePromotion.table_name)
20
+ Spree::Promotion.where(store_id: nil).find_each do |promotion|
21
+ store_ids = Spree::StorePromotion.where(promotion_id: promotion.id).order(:created_at, :store_id).pluck(:store_id)
22
+ next if store_ids.empty?
23
+
24
+ if store_ids.size > 1
25
+ shared[:promotions] += 1
26
+ Spree::Deprecation.warn(
27
+ "Promotion #{promotion.id} was shared across #{store_ids.size} stores; " \
28
+ "assigning it to store #{store_ids.first}. Install spree_multi_store to keep sharing."
29
+ )
30
+ end
31
+
32
+ promotion.update_column(:store_id, store_ids.first)
33
+ end
34
+ else
35
+ puts " #{Spree::StorePromotion.table_name} table not found — skipping promotions."
36
+ end
37
+
38
+ if ActiveRecord::Base.connection.table_exists?(Spree::StorePaymentMethod.table_name)
39
+ # +with_deleted+: PaymentMethod is paranoid, so soft-deleted rows would
40
+ # otherwise be skipped and keep a NULL store_id.
41
+ Spree::PaymentMethod.with_deleted.where(store_id: nil).find_each do |payment_method|
42
+ store_ids = Spree::StorePaymentMethod.where(payment_method_id: payment_method.id).order(:store_id).pluck(:store_id)
43
+ next if store_ids.empty?
44
+
45
+ if store_ids.size > 1
46
+ shared[:payment_methods] += 1
47
+ Spree::Deprecation.warn(
48
+ "PaymentMethod #{payment_method.id} was shared across #{store_ids.size} stores; " \
49
+ "assigning it to store #{store_ids.first}. Install spree_multi_store to keep sharing."
50
+ )
51
+ end
52
+
53
+ payment_method.update_column(:store_id, store_ids.first)
54
+ end
55
+ else
56
+ puts " #{Spree::StorePaymentMethod.table_name} table not found — skipping payment methods."
57
+ end
58
+
59
+ if shared.values.sum.positive?
60
+ puts " #{shared[:promotions]} promotion(s) and #{shared[:payment_methods]} payment method(s) " \
61
+ "were shared across stores — only the owner store keeps them unless spree_multi_store is installed."
62
+ end
63
+ end
64
+ end
65
+ end
@@ -1,6 +1,64 @@
1
1
  namespace :spree do
2
2
  namespace :taxons do
3
- desc 'Reset counter caches (children_count, classification_count) on taxons'
3
+ desc 'Backfill spree_taxons.store_id from each taxon\'s taxonomy (or parent)'
4
+ task backfill_store_id: :environment do |_t, _args|
5
+ puts 'Backfilling taxon store_id from taxonomy...'
6
+
7
+ # Resolve per-taxonomy and update by id — never reference a joined table in
8
+ # an update_all SET clause. It is not portable: on some adapter / Rails
9
+ # version combinations (e.g. Rails 7.2 on PostgreSQL) update_all-with-join
10
+ # is rewritten to `... WHERE id IN (subquery)`, leaving the joined alias out
11
+ # of scope in the SET and raising PG::UndefinedTable ("missing FROM-clause
12
+ # entry"). It fails at parse time, even when no rows need changing.
13
+ Spree::Taxonomy.where.not(store_id: nil).find_each do |taxonomy|
14
+ Spree::Taxon.unscoped.where(store_id: nil, taxonomy_id: taxonomy.id).
15
+ in_batches(of: 1000).update_all(store_id: taxonomy.store_id)
16
+ print '.'
17
+ end
18
+
19
+ puts "\nResolving taxonomy-less taxons through the parent chain..."
20
+
21
+ # Mirror Spree::Taxon#ensure_store: taxonomy-less rows inherit their parent's
22
+ # store. Loop so a chain of unbackfilled ancestors resolves top-down — each
23
+ # pass pushes an already-resolved parent's store onto its children.
24
+ loop do
25
+ updated = 0
26
+
27
+ # Resolved parents that still have children missing a store form the
28
+ # frontier; push each store down in one UPDATE per (store, id batch).
29
+ frontier = Spree::Taxon.unscoped.where.not(store_id: nil).
30
+ where(id: Spree::Taxon.unscoped.where(store_id: nil).select(:parent_id))
31
+ frontier.pluck(:id, :store_id).group_by(&:last).each do |store_id, rows|
32
+ rows.map(&:first).each_slice(1000) do |parent_ids|
33
+ updated += Spree::Taxon.unscoped.where(store_id: nil, parent_id: parent_ids).
34
+ update_all(store_id: store_id)
35
+ end
36
+ end
37
+
38
+ print '.'
39
+ break if updated.zero?
40
+ end
41
+
42
+ puts "\nDone!"
43
+ end
44
+
45
+ # Recomputes the descendant-inclusive products_count for every taxon, in
46
+ # batches. Shared by the dedicated task and reset_counter_caches.
47
+ backfill_products_count = lambda do
48
+ Spree::Taxon.unscoped.in_batches(of: 1000) do |batch|
49
+ Spree::Taxon.recalculate_products_count(batch.pluck(:id))
50
+ print '.'
51
+ end
52
+ end
53
+
54
+ desc 'Backfill the descendant-inclusive products_count on every taxon'
55
+ task backfill_products_count: :environment do |_t, _args|
56
+ puts 'Backfilling taxon products_count...'
57
+ backfill_products_count.call
58
+ puts "\nDone!"
59
+ end
60
+
61
+ desc 'Reset counter caches (children_count, classification_count, products_count) on taxons'
4
62
  task reset_counter_caches: :environment do |_t, _args|
5
63
  puts 'Resetting taxon counter caches...'
6
64
 
@@ -13,6 +71,9 @@ namespace :spree do
13
71
  print '.'
14
72
  end
15
73
 
74
+ puts "\nRecomputing products_count..."
75
+ backfill_products_count.call
76
+
16
77
  puts "\nDone!"
17
78
  end
18
79
  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.5.4
4
+ version: 5.6.0.rc1
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-07-20 00:00:00.000000000 Z
13
+ date: 2026-07-15 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: i18n-tasks
@@ -847,6 +847,7 @@ files:
847
847
  - app/helpers/spree/images_helper.rb
848
848
  - app/helpers/spree/integrations_helper.rb
849
849
  - app/helpers/spree/locale_helper.rb
850
+ - app/helpers/spree/localized_names_helper.rb
850
851
  - app/helpers/spree/products_helper.rb
851
852
  - app/helpers/spree/shipment_helper.rb
852
853
  - app/javascript/spree/core/controllers/address_autocomplete_controller.js
@@ -882,8 +883,10 @@ files:
882
883
  - app/jobs/spree/variants/remove_from_incomplete_orders_job.rb
883
884
  - app/jobs/spree/variants/remove_line_item_job.rb
884
885
  - app/jobs/spree/variants/touch_job.rb
886
+ - app/mailers/spree/admin_user_mailer.rb
885
887
  - app/mailers/spree/base_mailer.rb
886
888
  - app/mailers/spree/export_mailer.rb
889
+ - app/mailers/spree/import_mailer.rb
887
890
  - app/mailers/spree/invitation_mailer.rb
888
891
  - app/mailers/spree/report_mailer.rb
889
892
  - app/mailers/spree/webhook_mailer.rb
@@ -897,6 +900,7 @@ files:
897
900
  - app/models/concerns/spree/has_image_alt_text.rb
898
901
  - app/models/concerns/spree/image_methods.rb
899
902
  - app/models/concerns/spree/integrations_concern.rb
903
+ - app/models/concerns/spree/legacy_multi_store_support.rb
900
904
  - app/models/concerns/spree/memoized_data.rb
901
905
  - app/models/concerns/spree/metadata.rb
902
906
  - app/models/concerns/spree/metafields.rb
@@ -908,6 +912,7 @@ files:
908
912
  - app/models/concerns/spree/payment_source_concern.rb
909
913
  - app/models/concerns/spree/preference_schema.rb
910
914
  - app/models/concerns/spree/prefixed_id.rb
915
+ - app/models/concerns/spree/presentation_translatable.rb
911
916
  - app/models/concerns/spree/product_scopes.rb
912
917
  - app/models/concerns/spree/publishable.rb
913
918
  - app/models/concerns/spree/ransackable_attributes.rb
@@ -930,7 +935,6 @@ files:
930
935
  - app/models/concerns/spree/user_reporting.rb
931
936
  - app/models/concerns/spree/user_roles.rb
932
937
  - app/models/concerns/spree/vat_price_calculation.rb
933
- - app/models/concerns/spree/webhook_payload_redaction.rb
934
938
  - app/models/spree/ability.rb
935
939
  - app/models/spree/address.rb
936
940
  - app/models/spree/adjustable/adjuster/base.rb
@@ -966,6 +970,7 @@ files:
966
970
  - app/models/spree/calculator/tiered_percent.rb
967
971
  - app/models/spree/category.rb
968
972
  - app/models/spree/channel.rb
973
+ - app/models/spree/channel/gating.rb
969
974
  - app/models/spree/checkout/default_requirements.rb
970
975
  - app/models/spree/checkout/registry.rb
971
976
  - app/models/spree/checkout/requirement.rb
@@ -975,6 +980,7 @@ files:
975
980
  - app/models/spree/country.rb
976
981
  - app/models/spree/coupon_code.rb
977
982
  - app/models/spree/credit_card.rb
983
+ - app/models/spree/currency.rb
978
984
  - app/models/spree/current.rb
979
985
  - app/models/spree/custom_field.rb
980
986
  - app/models/spree/custom_field_definition.rb
@@ -1021,6 +1027,7 @@ files:
1021
1027
  - app/models/spree/legacy_admin_user.rb
1022
1028
  - app/models/spree/legacy_user.rb
1023
1029
  - app/models/spree/line_item.rb
1030
+ - app/models/spree/locale.rb
1024
1031
  - app/models/spree/log_entry.rb
1025
1032
  - app/models/spree/market.rb
1026
1033
  - app/models/spree/market_country.rb
@@ -1107,7 +1114,6 @@ files:
1107
1114
  - app/models/spree/price_rules/zone_rule.rb
1108
1115
  - app/models/spree/product.rb
1109
1116
  - app/models/spree/product/channels.rb
1110
- - app/models/spree/product/legacy_multi_store_support.rb
1111
1117
  - app/models/spree/product/slugs.rb
1112
1118
  - app/models/spree/product/webhooks.rb
1113
1119
  - app/models/spree/product_option_type.rb
@@ -1118,11 +1124,13 @@ files:
1118
1124
  - app/models/spree/promotion/actions/create_item_adjustments.rb
1119
1125
  - app/models/spree/promotion/actions/create_line_items.rb
1120
1126
  - app/models/spree/promotion/actions/free_shipping.rb
1127
+ - app/models/spree/promotion/rules/channel.rb
1121
1128
  - app/models/spree/promotion/rules/country.rb
1122
1129
  - app/models/spree/promotion/rules/currency.rb
1123
1130
  - app/models/spree/promotion/rules/customer_group.rb
1124
1131
  - app/models/spree/promotion/rules/first_order.rb
1125
1132
  - app/models/spree/promotion/rules/item_total.rb
1133
+ - app/models/spree/promotion/rules/market.rb
1126
1134
  - app/models/spree/promotion/rules/one_use_per_user.rb
1127
1135
  - app/models/spree/promotion/rules/option_value.rb
1128
1136
  - app/models/spree/promotion/rules/product.rb
@@ -1183,6 +1191,8 @@ files:
1183
1191
  - app/models/spree/search_provider/filters_result.rb
1184
1192
  - app/models/spree/search_provider/meilisearch.rb
1185
1193
  - app/models/spree/search_provider/search_result.rb
1194
+ - app/models/spree/setup_task.rb
1195
+ - app/models/spree/setup_tasks.rb
1186
1196
  - app/models/spree/shipment.rb
1187
1197
  - app/models/spree/shipment/custom_events.rb
1188
1198
  - app/models/spree/shipment/webhooks.rb
@@ -1237,6 +1247,7 @@ files:
1237
1247
  - app/models/spree/taxon_rules/sale.rb
1238
1248
  - app/models/spree/taxon_rules/tag.rb
1239
1249
  - app/models/spree/taxonomy.rb
1250
+ - app/models/spree/translations/batch.rb
1240
1251
  - app/models/spree/user_identity.rb
1241
1252
  - app/models/spree/variant.rb
1242
1253
  - app/models/spree/variant/webhooks.rb
@@ -1299,6 +1310,7 @@ files:
1299
1310
  - app/services/spree/coupon_codes/bulk_generate.rb
1300
1311
  - app/services/spree/coupon_codes/coupon_codes_handler.rb
1301
1312
  - app/services/spree/credit_cards/destroy.rb
1313
+ - app/services/spree/fulfillments/create.rb
1302
1314
  - app/services/spree/generate_token.rb
1303
1315
  - app/services/spree/gift_cards/apply.rb
1304
1316
  - app/services/spree/gift_cards/redeem.rb
@@ -1311,6 +1323,7 @@ files:
1311
1323
  - app/services/spree/line_items/destroy.rb
1312
1324
  - app/services/spree/line_items/helper.rb
1313
1325
  - app/services/spree/line_items/update.rb
1326
+ - app/services/spree/locales.rb
1314
1327
  - app/services/spree/locales/set_fallback_locale_for_store.rb
1315
1328
  - app/services/spree/newsletter/link_user.rb
1316
1329
  - app/services/spree/newsletter/subscribe.rb
@@ -1372,8 +1385,10 @@ files:
1372
1385
  - app/sorters/spree/base_sorter.rb
1373
1386
  - app/sorters/spree/classifications/sort.rb
1374
1387
  - app/sorters/spree/products/sort.rb
1388
+ - app/subscribers/spree/admin_user_email_subscriber.rb
1375
1389
  - app/subscribers/spree/event_log_subscriber.rb
1376
1390
  - app/subscribers/spree/export_subscriber.rb
1391
+ - app/subscribers/spree/import_email_subscriber.rb
1377
1392
  - app/subscribers/spree/invitation_email_subscriber.rb
1378
1393
  - app/subscribers/spree/product_metrics_subscriber.rb
1379
1394
  - app/subscribers/spree/report_subscriber.rb
@@ -1384,7 +1399,10 @@ files:
1384
1399
  - app/views/layouts/spree/base_mailer.html.erb
1385
1400
  - app/views/spree/addresses/_form.html.erb
1386
1401
  - app/views/spree/addresses/_suggestions_box.erb
1402
+ - app/views/spree/admin_user_mailer/confirmation_email.html.erb
1403
+ - app/views/spree/admin_user_mailer/password_reset_email.html.erb
1387
1404
  - app/views/spree/export_mailer/export_done.html.erb
1405
+ - app/views/spree/import_mailer/import_done.html.erb
1388
1406
  - app/views/spree/invitation_mailer/invitation_accepted.html.erb
1389
1407
  - app/views/spree/invitation_mailer/invitation_email.html.erb
1390
1408
  - app/views/spree/report_mailer/report_done.html.erb
@@ -1392,9 +1410,12 @@ files:
1392
1410
  - app/views/spree/shared/_base_mailer_header.html.erb
1393
1411
  - app/views/spree/shared/_base_mailer_stylesheets.html.erb
1394
1412
  - app/views/spree/shared/_google_places_api_loader.html.erb
1413
+ - app/views/spree/shared/_mailer_button.html.erb
1414
+ - app/views/spree/shared/_mailer_hero.html.erb
1395
1415
  - app/views/spree/shared/_mailer_line_item.html.erb
1396
1416
  - app/views/spree/shared/_mailer_logo.html.erb
1397
1417
  - app/views/spree/shared/_payment.html.erb
1418
+ - app/views/spree/shared/_purchased_items_styles.html.erb
1398
1419
  - app/views/spree/webhook_mailer/endpoint_disabled.html.erb
1399
1420
  - app/views/spree/webhook_mailer/endpoint_disabled.text.erb
1400
1421
  - config/brakeman.ignore
@@ -1407,6 +1428,7 @@ files:
1407
1428
  - config/initializers/inflections.rb
1408
1429
  - config/initializers/mobility.rb
1409
1430
  - config/initializers/rails61_fixes.rb
1431
+ - config/initializers/spree_store_setup_tasks.rb
1410
1432
  - config/locales/en.yml
1411
1433
  - config/routes.rb
1412
1434
  - db/migrate/20210914000000_spree_four_three.rb
@@ -1569,6 +1591,15 @@ files:
1569
1591
  - db/migrate/20260601000002_add_store_id_to_spree_products.rb
1570
1592
  - db/migrate/20260602000001_add_default_to_spree_channels.rb
1571
1593
  - db/migrate/20260612000001_change_spree_user_identities_info_to_jsonb.rb
1594
+ - db/migrate/20260613000001_add_store_id_to_spree_role_users.rb
1595
+ - db/migrate/20260626000001_add_store_id_to_spree_taxons.rb
1596
+ - db/migrate/20260627000001_add_products_count_to_spree_taxons.rb
1597
+ - db/migrate/20260628000001_add_store_id_to_spree_promotions.rb
1598
+ - db/migrate/20260628000002_add_store_id_to_spree_payment_methods.rb
1599
+ - db/migrate/20260630000001_add_preorder_fields_to_spree_variants.rb
1600
+ - db/migrate/20260707000001_add_fingerprint_to_spree_credit_cards.rb
1601
+ - db/migrate/20260710000001_add_import_id_status_index_to_spree_import_rows.rb
1602
+ - db/migrate/20260711000001_add_preferences_to_spree_exports.rb
1572
1603
  - db/sample_data/channels.rb
1573
1604
  - db/sample_data/customers.csv
1574
1605
  - db/sample_data/markets.rb
@@ -1644,6 +1675,12 @@ files:
1644
1675
  - lib/spree/core/preferences/runtime_configuration.rb
1645
1676
  - lib/spree/core/preferences/scoped_store.rb
1646
1677
  - lib/spree/core/preferences/store.rb
1678
+ - lib/spree/core/previews/admin_user_preview.rb
1679
+ - lib/spree/core/previews/export_preview.rb
1680
+ - lib/spree/core/previews/invitation_preview.rb
1681
+ - lib/spree/core/previews/preview_data.rb
1682
+ - lib/spree/core/previews/report_preview.rb
1683
+ - lib/spree/core/previews/webhook_preview.rb
1647
1684
  - lib/spree/core/pricing/context.rb
1648
1685
  - lib/spree/core/pricing/resolver.rb
1649
1686
  - lib/spree/core/ransack_configuration.rb
@@ -1781,7 +1818,9 @@ files:
1781
1818
  - lib/spree/testing_support/store.rb
1782
1819
  - lib/spree/testing_support/url_helpers.rb
1783
1820
  - lib/spree/translation_migrations.rb
1821
+ - lib/spree/translations.rb
1784
1822
  - lib/spree/upgrades/5_4_to_5_5/manifest.yml
1823
+ - lib/spree/upgrades/5_5_to_5_6/manifest.yml
1785
1824
  - lib/spree/webhooks.rb
1786
1825
  - lib/spree_core.rb
1787
1826
  - lib/tasks/channels.rake
@@ -1793,8 +1832,10 @@ files:
1793
1832
  - lib/tasks/price_history.rake
1794
1833
  - lib/tasks/products.rake
1795
1834
  - lib/tasks/publications.rake
1835
+ - lib/tasks/role_users.rake
1796
1836
  - lib/tasks/sample_data.rake
1797
1837
  - lib/tasks/search.rake
1838
+ - lib/tasks/single_store_associations.rake
1798
1839
  - lib/tasks/taxons.rake
1799
1840
  - lib/tasks/upgrade.rake
1800
1841
  - lib/tasks/variants.rake
@@ -1819,9 +1860,9 @@ licenses:
1819
1860
  - BSD-3-Clause
1820
1861
  metadata:
1821
1862
  bug_tracker_uri: https://github.com/spree/spree/issues
1822
- changelog_uri: https://github.com/spree/spree/releases/tag/v5.5.4
1863
+ changelog_uri: https://github.com/spree/spree/releases/tag/v5.6.0.rc1
1823
1864
  documentation_uri: https://docs.spreecommerce.org/
1824
- source_code_uri: https://github.com/spree/spree/tree/v5.5.4
1865
+ source_code_uri: https://github.com/spree/spree/tree/v5.6.0.rc1
1825
1866
  post_install_message:
1826
1867
  rdoc_options: []
1827
1868
  require_paths: