spree_core 5.5.3 → 5.6.0.rc2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/helpers/spree/base_helper.rb +12 -2
- data/app/helpers/spree/currency_helper.rb +1 -4
- data/app/helpers/spree/locale_helper.rb +18 -13
- data/app/helpers/spree/localized_names_helper.rb +25 -0
- data/app/mailers/spree/admin_user_mailer.rb +71 -0
- data/app/mailers/spree/base_mailer.rb +73 -11
- data/app/mailers/spree/export_mailer.rb +7 -7
- data/app/mailers/spree/import_mailer.rb +19 -0
- data/app/mailers/spree/invitation_mailer.rb +16 -11
- data/app/mailers/spree/report_mailer.rb +6 -6
- data/app/mailers/spree/webhook_mailer.rb +7 -6
- data/app/models/concerns/spree/admin_user_methods.rb +44 -0
- data/app/models/concerns/spree/legacy_multi_store_support.rb +48 -0
- data/app/models/concerns/spree/presentation_translatable.rb +39 -0
- data/app/models/concerns/spree/product_scopes.rb +21 -3
- data/app/models/concerns/spree/single_store_resource.rb +5 -0
- data/app/models/concerns/spree/store_scoped_resource.rb +10 -0
- data/app/models/concerns/spree/stores/markets.rb +9 -1
- data/app/models/concerns/spree/stores/setup.rb +37 -23
- data/app/models/concerns/spree/translatable_resource.rb +95 -5
- data/app/models/concerns/spree/user_methods.rb +47 -2
- data/app/models/spree/ability.rb +5 -4
- data/app/models/spree/allowed_origin.rb +31 -0
- data/app/models/spree/category.rb +12 -2
- data/app/models/spree/channel/gating.rb +67 -0
- data/app/models/spree/channel.rb +1 -0
- data/app/models/spree/classification.rb +12 -0
- data/app/models/spree/country.rb +22 -1
- data/app/models/spree/credit_card.rb +29 -0
- data/app/models/spree/currency.rb +47 -0
- data/app/models/spree/current.rb +12 -1
- data/app/models/spree/customer_group.rb +1 -0
- data/app/models/spree/export.rb +19 -0
- data/app/models/spree/exports/coupon_codes.rb +1 -2
- data/app/models/spree/import.rb +84 -1
- data/app/models/spree/import_row.rb +7 -1
- data/app/models/spree/imports/product_translations.rb +6 -0
- data/app/models/spree/locale.rb +104 -0
- data/app/models/spree/newsletter_subscriber.rb +4 -0
- data/app/models/spree/option_type.rb +11 -13
- data/app/models/spree/option_value.rb +5 -17
- data/app/models/spree/order.rb +19 -2
- data/app/models/spree/payment_method.rb +6 -5
- data/app/models/spree/permission_sets/default_customer.rb +3 -0
- data/app/models/spree/policy.rb +2 -1
- data/app/models/spree/product.rb +23 -2
- data/app/models/spree/promotion/rules/channel.rb +37 -0
- data/app/models/spree/promotion/rules/market.rb +37 -0
- data/app/models/spree/promotion.rb +5 -3
- data/app/models/spree/promotion_handler/promotion_duplicator.rb +1 -1
- data/app/models/spree/role_user.rb +4 -0
- data/app/models/spree/search_provider/meilisearch.rb +22 -9
- data/app/models/spree/setup_task.rb +12 -0
- data/app/models/spree/setup_tasks.rb +86 -0
- data/app/models/spree/shipment.rb +38 -1
- data/app/models/spree/shipment_handler.rb +1 -1
- data/app/models/spree/stock/availability_validator.rb +1 -1
- data/app/models/spree/stock/quantifier.rb +21 -1
- data/app/models/spree/stock_item.rb +2 -1
- data/app/models/spree/stock_location.rb +4 -1
- data/app/models/spree/store.rb +45 -9
- data/app/models/spree/store_payment_method.rb +5 -1
- data/app/models/spree/store_promotion.rb +4 -0
- data/app/models/spree/taxon.rb +112 -9
- data/app/models/spree/taxonomy.rb +1 -1
- data/app/models/spree/translations/batch.rb +104 -0
- data/app/models/spree/user_identity.rb +35 -24
- data/app/models/spree/variant.rb +29 -2
- data/app/presenters/spree/search_provider/product_presenter.rb +3 -0
- data/app/services/spree/carts/complete.rb +3 -0
- data/app/services/spree/fulfillments/create.rb +259 -0
- data/app/services/spree/locales/set_fallback_locale_for_store.rb +7 -1
- data/app/services/spree/locales.rb +52 -0
- data/app/services/spree/newsletter/subscribe.rb +1 -0
- data/app/services/spree/products/prepare_nested_attributes.rb +40 -12
- data/app/services/spree/shipments/update.rb +1 -1
- data/app/services/spree/stock_reservations/reserve.rb +4 -1
- data/app/services/spree/taxons/add_products.rb +3 -0
- data/app/services/spree/taxons/remove_products.rb +3 -0
- data/app/subscribers/spree/admin_user_email_subscriber.rb +29 -0
- data/app/subscribers/spree/import_email_subscriber.rb +26 -0
- data/app/views/layouts/spree/base_mailer.html.erb +14 -17
- data/app/views/spree/admin_user_mailer/confirmation_email.html.erb +7 -0
- data/app/views/spree/admin_user_mailer/password_reset_email.html.erb +8 -0
- data/app/views/spree/export_mailer/export_done.html.erb +6 -14
- data/app/views/spree/import_mailer/import_done.html.erb +12 -0
- data/app/views/spree/invitation_mailer/invitation_accepted.html.erb +4 -12
- data/app/views/spree/invitation_mailer/invitation_email.html.erb +11 -24
- data/app/views/spree/report_mailer/report_done.html.erb +6 -14
- data/app/views/spree/shared/_base_mailer_footer.html.erb +15 -8
- data/app/views/spree/shared/_base_mailer_header.html.erb +7 -5
- data/app/views/spree/shared/_base_mailer_stylesheets.html.erb +211 -273
- data/app/views/spree/shared/_mailer_button.html.erb +10 -0
- data/app/views/spree/shared/_mailer_hero.html.erb +12 -0
- data/app/views/spree/shared/_purchased_items_styles.html.erb +112 -0
- data/app/views/spree/webhook_mailer/endpoint_disabled.html.erb +39 -14
- data/config/initializers/spree_store_setup_tasks.rb +23 -0
- data/config/locales/en.yml +42 -992
- data/db/migrate/20260613000001_add_store_id_to_spree_role_users.rb +10 -0
- data/db/migrate/20260626000001_add_store_id_to_spree_taxons.rb +11 -0
- data/db/migrate/20260627000001_add_products_count_to_spree_taxons.rb +6 -0
- data/db/migrate/20260628000001_add_store_id_to_spree_promotions.rb +10 -0
- data/db/migrate/20260628000002_add_store_id_to_spree_payment_methods.rb +11 -0
- data/db/migrate/20260630000001_add_preorder_fields_to_spree_variants.rb +7 -0
- data/db/migrate/20260707000001_add_fingerprint_to_spree_credit_cards.rb +39 -0
- data/db/migrate/20260710000001_add_import_id_status_index_to_spree_import_rows.rb +7 -0
- data/db/migrate/20260711000001_add_preferences_to_spree_exports.rb +7 -0
- data/db/sample_data/promotions.rb +1 -1
- data/lib/generators/spree/authentication/dummy/templates/create_spree_admin_users.rb.tt +0 -1
- data/lib/mobility/plugins/store_based_fallbacks.rb +7 -5
- data/lib/spree/core/configuration.rb +1 -0
- data/lib/spree/core/controller_helpers/locale.rb +4 -1
- data/lib/spree/core/controller_helpers/strong_parameters.rb +1 -0
- data/lib/spree/core/dependencies.rb +3 -0
- data/lib/spree/core/engine.rb +24 -0
- data/lib/spree/core/preferences/preferable.rb +6 -1
- data/lib/spree/core/previews/admin_user_preview.rb +22 -0
- data/lib/spree/core/previews/export_preview.rb +37 -0
- data/lib/spree/core/previews/invitation_preview.rb +47 -0
- data/lib/spree/core/previews/preview_data.rb +52 -0
- data/lib/spree/core/previews/report_preview.rb +40 -0
- data/lib/spree/core/previews/webhook_preview.rb +34 -0
- data/lib/spree/core/version.rb +1 -1
- data/lib/spree/core.rb +38 -1
- data/lib/spree/permitted_attributes.rb +5 -4
- data/lib/spree/testing_support/factories/export_factory.rb +4 -0
- data/lib/spree/testing_support/factories/order_factory.rb +1 -1
- data/lib/spree/testing_support/factories/payment_factory.rb +3 -3
- data/lib/spree/testing_support/factories/payment_method_factory.rb +1 -9
- data/lib/spree/testing_support/factories/payment_session_factory.rb +2 -2
- data/lib/spree/testing_support/factories/promotion_factory.rb +1 -9
- data/lib/spree/testing_support/factories/promotion_rule_factory.rb +24 -0
- data/lib/spree/translations.rb +113 -0
- data/lib/spree/upgrades/5_5_to_5_6/manifest.yml +59 -0
- data/lib/tasks/role_users.rake +21 -0
- data/lib/tasks/single_store_associations.rake +65 -0
- data/lib/tasks/taxons.rake +62 -1
- metadata +47 -5
- data/app/models/spree/product/legacy_multi_store_support.rb +0 -40
|
@@ -5,7 +5,7 @@ FactoryBot.define do
|
|
|
5
5
|
state { 'checkout' }
|
|
6
6
|
response_code { "BGS-#{SecureRandom.hex(6)}" }
|
|
7
7
|
|
|
8
|
-
payment_method { create(:credit_card_payment_method,
|
|
8
|
+
payment_method { create(:credit_card_payment_method, store: order.store) }
|
|
9
9
|
association(:source, factory: :credit_card)
|
|
10
10
|
|
|
11
11
|
factory :payment_with_refund do
|
|
@@ -16,7 +16,7 @@ FactoryBot.define do
|
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
factory :custom_payment, class: Spree::Payment do
|
|
19
|
-
payment_method { create(:custom_payment_method,
|
|
19
|
+
payment_method { create(:custom_payment_method, store: order.store) }
|
|
20
20
|
source { create(:payment_source, user: order.user, payment_method: payment_method) }
|
|
21
21
|
end
|
|
22
22
|
end
|
|
@@ -29,7 +29,7 @@ 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,
|
|
32
|
+
payment_method { create(:store_credit_payment_method, store: order.store) }
|
|
33
33
|
source { create(:store_credit, store: order.store, user: order.user) }
|
|
34
34
|
end
|
|
35
35
|
end
|
|
@@ -2,15 +2,7 @@ FactoryBot.define do
|
|
|
2
2
|
factory :payment_method, class: Spree::PaymentMethod do
|
|
3
3
|
type { 'Spree::PaymentMethod' }
|
|
4
4
|
name { 'Test' }
|
|
5
|
-
|
|
6
|
-
before(:create) do |payment_method|
|
|
7
|
-
if payment_method.stores.empty?
|
|
8
|
-
default_store = Spree::Store.default.persisted? ? Spree::Store.default : nil
|
|
9
|
-
store = default_store || create(:store)
|
|
10
|
-
|
|
11
|
-
payment_method.stores << store
|
|
12
|
-
end
|
|
13
|
-
end
|
|
5
|
+
store { Spree::Store.find_by(default: true) || association(:store) }
|
|
14
6
|
end
|
|
15
7
|
|
|
16
8
|
factory :check_payment_method, parent: :payment_method, class: Spree::PaymentMethod::Check do
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
FactoryBot.define do
|
|
2
2
|
factory :payment_session, class: 'Spree::PaymentSession' do
|
|
3
3
|
order
|
|
4
|
-
payment_method { create(:credit_card_payment_method,
|
|
4
|
+
payment_method { create(:credit_card_payment_method, store: order.store) }
|
|
5
5
|
amount { order.total }
|
|
6
6
|
currency { order.currency }
|
|
7
7
|
status { 'pending' }
|
|
@@ -39,7 +39,7 @@ FactoryBot.define do
|
|
|
39
39
|
|
|
40
40
|
factory :bogus_payment_session, class: 'Spree::PaymentSessions::Bogus' do
|
|
41
41
|
type { 'Spree::PaymentSessions::Bogus' }
|
|
42
|
-
payment_method { create(:bogus_payment_method,
|
|
42
|
+
payment_method { create(:bogus_payment_method, store: order.store) }
|
|
43
43
|
external_id { "bogus_#{SecureRandom.hex(12)}" }
|
|
44
44
|
external_data { { 'client_secret' => "bogus_secret_#{SecureRandom.hex(8)}" } }
|
|
45
45
|
end
|
|
@@ -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
|
data/lib/tasks/taxons.rake
CHANGED
|
@@ -1,6 +1,64 @@
|
|
|
1
1
|
namespace :spree do
|
|
2
2
|
namespace :taxons do
|
|
3
|
-
desc '
|
|
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
|