spree_core 5.5.3 → 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 (133) 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/presenters/spree/search_provider/product_presenter.rb +3 -0
  68. data/app/services/spree/carts/complete.rb +3 -0
  69. data/app/services/spree/fulfillments/create.rb +259 -0
  70. data/app/services/spree/locales/set_fallback_locale_for_store.rb +7 -1
  71. data/app/services/spree/locales.rb +52 -0
  72. data/app/services/spree/products/prepare_nested_attributes.rb +40 -12
  73. data/app/services/spree/shipments/update.rb +1 -1
  74. data/app/services/spree/stock_reservations/reserve.rb +4 -1
  75. data/app/services/spree/taxons/add_products.rb +3 -0
  76. data/app/services/spree/taxons/remove_products.rb +3 -0
  77. data/app/subscribers/spree/admin_user_email_subscriber.rb +29 -0
  78. data/app/subscribers/spree/import_email_subscriber.rb +26 -0
  79. data/app/views/layouts/spree/base_mailer.html.erb +14 -17
  80. data/app/views/spree/admin_user_mailer/confirmation_email.html.erb +7 -0
  81. data/app/views/spree/admin_user_mailer/password_reset_email.html.erb +8 -0
  82. data/app/views/spree/export_mailer/export_done.html.erb +6 -14
  83. data/app/views/spree/import_mailer/import_done.html.erb +12 -0
  84. data/app/views/spree/invitation_mailer/invitation_accepted.html.erb +4 -12
  85. data/app/views/spree/invitation_mailer/invitation_email.html.erb +11 -24
  86. data/app/views/spree/report_mailer/report_done.html.erb +6 -14
  87. data/app/views/spree/shared/_base_mailer_footer.html.erb +15 -8
  88. data/app/views/spree/shared/_base_mailer_header.html.erb +7 -5
  89. data/app/views/spree/shared/_base_mailer_stylesheets.html.erb +211 -273
  90. data/app/views/spree/shared/_mailer_button.html.erb +10 -0
  91. data/app/views/spree/shared/_mailer_hero.html.erb +12 -0
  92. data/app/views/spree/shared/_purchased_items_styles.html.erb +112 -0
  93. data/app/views/spree/webhook_mailer/endpoint_disabled.html.erb +39 -14
  94. data/config/initializers/spree_store_setup_tasks.rb +23 -0
  95. data/config/locales/en.yml +42 -992
  96. data/db/migrate/20260613000001_add_store_id_to_spree_role_users.rb +10 -0
  97. data/db/migrate/20260626000001_add_store_id_to_spree_taxons.rb +11 -0
  98. data/db/migrate/20260627000001_add_products_count_to_spree_taxons.rb +6 -0
  99. data/db/migrate/20260628000001_add_store_id_to_spree_promotions.rb +10 -0
  100. data/db/migrate/20260628000002_add_store_id_to_spree_payment_methods.rb +11 -0
  101. data/db/migrate/20260630000001_add_preorder_fields_to_spree_variants.rb +7 -0
  102. data/db/migrate/20260707000001_add_fingerprint_to_spree_credit_cards.rb +39 -0
  103. data/db/migrate/20260710000001_add_import_id_status_index_to_spree_import_rows.rb +7 -0
  104. data/db/migrate/20260711000001_add_preferences_to_spree_exports.rb +7 -0
  105. data/db/sample_data/promotions.rb +1 -1
  106. data/lib/generators/spree/authentication/dummy/templates/create_spree_admin_users.rb.tt +0 -1
  107. data/lib/mobility/plugins/store_based_fallbacks.rb +7 -5
  108. data/lib/spree/core/configuration.rb +1 -0
  109. data/lib/spree/core/controller_helpers/locale.rb +4 -1
  110. data/lib/spree/core/controller_helpers/strong_parameters.rb +1 -0
  111. data/lib/spree/core/dependencies.rb +3 -0
  112. data/lib/spree/core/engine.rb +24 -0
  113. data/lib/spree/core/preferences/preferable.rb +6 -1
  114. data/lib/spree/core/previews/admin_user_preview.rb +22 -0
  115. data/lib/spree/core/previews/export_preview.rb +37 -0
  116. data/lib/spree/core/previews/invitation_preview.rb +47 -0
  117. data/lib/spree/core/previews/preview_data.rb +52 -0
  118. data/lib/spree/core/previews/report_preview.rb +40 -0
  119. data/lib/spree/core/previews/webhook_preview.rb +34 -0
  120. data/lib/spree/core/version.rb +1 -1
  121. data/lib/spree/core.rb +22 -1
  122. data/lib/spree/permitted_attributes.rb +5 -4
  123. data/lib/spree/testing_support/factories/export_factory.rb +4 -0
  124. data/lib/spree/testing_support/factories/payment_method_factory.rb +1 -9
  125. data/lib/spree/testing_support/factories/promotion_factory.rb +1 -9
  126. data/lib/spree/testing_support/factories/promotion_rule_factory.rb +24 -0
  127. data/lib/spree/translations.rb +113 -0
  128. data/lib/spree/upgrades/5_5_to_5_6/manifest.yml +59 -0
  129. data/lib/tasks/role_users.rake +21 -0
  130. data/lib/tasks/single_store_associations.rake +65 -0
  131. data/lib/tasks/taxons.rake +62 -1
  132. metadata +47 -5
  133. data/app/models/spree/product/legacy_multi_store_support.rb +0 -40
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ class AdminUserEmailSubscriber < Spree::Subscriber
5
+ subscribes_to 'admin_user.password_reset_requested'
6
+
7
+ def handle(event)
8
+ user = Spree.admin_user_class.find_by(email: event.payload['email'])
9
+ return unless user
10
+
11
+ store = find_store(event)
12
+
13
+ AdminUserMailer.password_reset_email(
14
+ user,
15
+ event.payload['reset_token'],
16
+ store,
17
+ redirect_url: event.payload['redirect_url']
18
+ ).deliver_later
19
+ end
20
+
21
+ private
22
+
23
+ def find_store(event)
24
+ Spree::Store.find_by_prefix_id(event.payload['store_id']) ||
25
+ Spree::Current.store ||
26
+ Spree::Store.default
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ # Emails the import's owner when processing finishes — including after a
5
+ # failed-rows retry pass, which publishes `import.completed` again.
6
+ # App-created imports (API key without an admin creator) have no recipient
7
+ # and send nothing.
8
+ #
9
+ # We use async: false because this subscriber only enqueues the mail job.
10
+ class ImportEmailSubscriber < Spree::Subscriber
11
+ subscribes_to 'import.completed', async: false
12
+
13
+ on 'import.completed', :send_import_done_email
14
+
15
+ def send_import_done_email(event)
16
+ import_id = event.payload['id']
17
+ return unless import_id
18
+
19
+ import = Spree::Import.find_by_prefix_id(import_id)
20
+ return unless import
21
+ return if import.user&.email.blank?
22
+
23
+ Spree::ImportMailer.import_done(import).deliver_later
24
+ end
25
+ end
26
+ end
@@ -1,46 +1,43 @@
1
1
  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2
- <html>
2
+ <html lang="<%= I18n.locale %>">
3
3
  <head>
4
4
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
5
5
  <meta name="x-apple-disable-message-reformatting"/>
6
6
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
7
+ <meta name="color-scheme" content="light"/>
8
+ <meta name="supported-color-schemes" content="light"/>
7
9
 
8
10
  <%= render partial: 'spree/shared/base_mailer_stylesheets' %>
11
+ <%# Per-email styles — templates add their own via `content_for :head` %>
12
+ <%= yield :head %>
9
13
  </head>
10
- <body>
11
- <table class="email-wrapper" width="100%" cellpadding="0" cellspacing="0" role="presentation">
14
+ <body class="email-bg" style="margin: 0; padding: 0; -webkit-text-size-adjust: none;" bgcolor="#FFFFFF">
15
+ <table class="email-wrapper" width="100%" cellpadding="0" cellspacing="0" role="presentation" bgcolor="#FFFFFF">
12
16
  <tr>
13
17
  <td align="center">
14
18
  <table class="email-content" width="100%" cellpadding="0" cellspacing="0" role="presentation">
15
- <%= render partial: 'spree/shared/base_mailer_header' %>
16
19
  <!-- Email Body -->
17
20
  <tr>
18
- <td class="email-body" width="570" cellpadding="0" cellspacing="0">
19
- <table class="email-body_inner" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
20
- <!-- Body content -->
21
- <tbody>
21
+ <td class="email-card" bgcolor="#F6F6F6">
22
+ <%= render partial: 'spree/shared/base_mailer_header' %>
23
+ <table width="100%" cellpadding="0" cellspacing="0" role="presentation">
22
24
  <tr>
23
- <td class="content-cell">
24
- <div class="f-fallback">
25
- <%= yield %>
26
- </div>
25
+ <td class="content-cell f-fallback" align="left">
26
+ <%= yield %>
27
27
  </td>
28
28
  </tr>
29
- </tbody>
30
29
  </table>
31
30
  </td>
32
31
  </tr>
32
+ <!-- Email Footer -->
33
33
  <tr>
34
- <td>
34
+ <td class="email-footer" align="center">
35
35
  <%= render partial: 'spree/shared/base_mailer_footer' %>
36
36
  </td>
37
37
  </tr>
38
38
  </table>
39
39
  </td>
40
40
  </tr>
41
- <tr>
42
- <td height="60px">&nbsp;</td>
43
- </tr>
44
41
  </table>
45
42
  </body>
46
43
  </html>
@@ -0,0 +1,7 @@
1
+ <%= render layout: 'spree/shared/mailer_hero' do %>
2
+ <h1><%= Spree.t('admin_user_mailer.confirmation_email.action') %></h1>
3
+ <p class="greeting"><%= Spree.t('admin_user_mailer.confirmation_email.greeting', name: @user.first_name.presence || @user.email) %></p>
4
+ <p><%= Spree.t('admin_user_mailer.confirmation_email.instructions', store_name: @current_store.name) %></p>
5
+ <%= render 'spree/shared/mailer_button', url: @confirmation_url, label: Spree.t('admin_user_mailer.confirmation_email.action') %>
6
+ <p class="sub"><%= Spree.t('admin_user_mailer.confirmation_email.ignore_notice') %></p>
7
+ <% end %>
@@ -0,0 +1,8 @@
1
+ <%= render layout: 'spree/shared/mailer_hero' do %>
2
+ <h1><%= Spree.t('admin_user_mailer.password_reset_email.action') %></h1>
3
+ <p class="greeting"><%= Spree.t('admin_user_mailer.password_reset_email.greeting', name: @user.first_name.presence || @user.email) %></p>
4
+ <p><%= Spree.t('admin_user_mailer.password_reset_email.instructions', store_name: @current_store.name) %></p>
5
+ <%= render 'spree/shared/mailer_button', url: @reset_url, label: Spree.t('admin_user_mailer.password_reset_email.action') %>
6
+ <p class="sub"><%= Spree.t('admin_user_mailer.password_reset_email.expiry_notice') %></p>
7
+ <p class="sub"><%= Spree.t('admin_user_mailer.password_reset_email.ignore_notice') %></p>
8
+ <% end %>
@@ -1,15 +1,7 @@
1
- <h1>
2
- <%= Spree.t('export_mailer.greeting', user_name: @export.user&.first_name) %>
3
- </h1>
4
- <p>
5
- <%= Spree.t('export_mailer.export_done.message') %>
6
- </p>
7
- <% if spree.respond_to?(:admin_export_url) %>
8
- <p>
9
- <%= link_to @export.attachment.filename, spree.admin_export_url(@export, host: @current_store.formatted_url) %>
10
- </p>
1
+ <%= render layout: 'spree/shared/mailer_hero' do %>
2
+ <h1><%= Spree.t('export_mailer.greeting', user_name: @export.user&.first_name) %></h1>
3
+ <p><%= Spree.t('export_mailer.export_done.message') %></p>
4
+ <% if @export.results_url.present? %>
5
+ <%= render 'spree/shared/mailer_button', url: @export.results_url, label: @export.attachment.filename %>
6
+ <% end %>
11
7
  <% end %>
12
- <p>
13
- <%= Spree.t('export_mailer.thanks') %><br />
14
- <%= "#{current_store.name}" %>
15
- </p>
@@ -0,0 +1,12 @@
1
+ <%= render layout: 'spree/shared/mailer_hero' do %>
2
+ <h1><%= Spree.t('import_mailer.greeting', user_name: @import.user&.first_name) %></h1>
3
+ <p><%= Spree.t('import_mailer.import_done.message', completed_count: @import.rows_status_counts['completed'] || 0) %></p>
4
+ <% failed_count = @import.rows_status_counts['failed'] || 0 %>
5
+ <% if failed_count > 0 %>
6
+ <p><strong><%= Spree.t('import_mailer.import_done.failed_message', failed_count: failed_count) %></strong></p>
7
+ <% end %>
8
+ <% results_url = @import.results_page_url %>
9
+ <% if results_url.present? %>
10
+ <%= render 'spree/shared/mailer_button', url: results_url, label: Spree.t('import_mailer.import_done.view_results') %>
11
+ <% end %>
12
+ <% end %>
@@ -1,12 +1,4 @@
1
- <h1>
2
- <%= Spree.t('invitation_mailer.greeting', user_name: @invitation.inviter.first_name) %>
3
- </h1>
4
- <p>
5
- <%= Spree.t('invitation_mailer.invitation_accepted.body', invitee_name: @invitation.invitee&.name, resource_name: @invitation.resource.name) %>
6
- </p>
7
-
8
- <p>
9
- <%= Spree.t('invitation_mailer.thanks') %><br />
10
- <%= current_store.name %>
11
- </p>
12
-
1
+ <%= render layout: 'spree/shared/mailer_hero' do %>
2
+ <h1><%= Spree.t('invitation_mailer.greeting', user_name: @invitation.inviter.first_name) %></h1>
3
+ <p><%= Spree.t('invitation_mailer.invitation_accepted.body', invitee_name: @invitation.invitee&.name, resource_name: @invitation.resource.name) %></p>
4
+ <% end %>
@@ -1,25 +1,12 @@
1
- <h1>
2
- <%= Spree.t('invitation_mailer.greeting', user_name: @invitation.invitee&.first_name || @invitation.email) %>
3
- </h1>
4
- <p>
5
- <%= Spree.t('invitation_mailer.invitation_email.body', inviter_name: @invitation.inviter.name, resource_name: @invitation.resource.name) %>
6
- </p>
7
- <p>
8
- <%= Spree.t('invitation_mailer.invitation_email.link_description') %>
9
- </p>
10
-
11
- <% if spree.respond_to?(:admin_invitation_url) %>
12
- <p>
13
- <%= link_to Spree.t(:accept), spree.admin_invitation_url(@invitation, token: @invitation.token, host: @invitation.store.formatted_url) %>
14
- </p>
15
- <% else %>
16
- <p>
17
- <%= link_to Spree.t(:accept), main_app.admin_invitation_acceptance_url(@invitation) %>
18
- </p>
1
+ <%= render layout: 'spree/shared/mailer_hero' do %>
2
+ <h1><%= Spree.t('invitation_mailer.invitation_email.subject', resource_name: @invitation.resource.name) %></h1>
3
+ <p class="greeting"><%= Spree.t('invitation_mailer.greeting', user_name: @invitation.invitee&.first_name || @invitation.email) %></p>
4
+ <p><%= Spree.t('invitation_mailer.invitation_email.body', inviter_name: @invitation.inviter.name, resource_name: @invitation.resource.name) %></p>
5
+ <p><%= Spree.t('invitation_mailer.invitation_email.link_description') %></p>
6
+ <% accept_url = if spree.respond_to?(:admin_invitation_url)
7
+ spree.admin_invitation_url(@invitation, token: @invitation.token, host: @invitation.store.formatted_url)
8
+ else
9
+ main_app.admin_invitation_acceptance_url(@invitation)
10
+ end %>
11
+ <%= render 'spree/shared/mailer_button', url: accept_url, label: Spree.t(:accept) %>
19
12
  <% end %>
20
-
21
- <p>
22
- <%= Spree.t('invitation_mailer.thanks') %><br />
23
- <%= current_store.name %>
24
- </p>
25
-
@@ -1,15 +1,7 @@
1
- <h1>
2
- <%= Spree.t('report_mailer.greeting', user_name: @report.user&.first_name) %>
3
- </h1>
4
- <p>
5
- <%= Spree.t('report_mailer.report_done.message') %>
6
- </p>
7
- <% if spree.respond_to?(:admin_report_url) %>
8
- <p>
9
- <%= link_to @report.attachment.filename, spree.admin_report_url(@report, host: @current_store.formatted_url) %>
10
- </p>
1
+ <%= render layout: 'spree/shared/mailer_hero' do %>
2
+ <h1><%= Spree.t('report_mailer.greeting', user_name: @report.user&.first_name) %></h1>
3
+ <p><%= Spree.t('report_mailer.report_done.message') %></p>
4
+ <% if spree.respond_to?(:admin_report_url) %>
5
+ <%= render 'spree/shared/mailer_button', url: spree.admin_report_url(@report, host: @current_store.formatted_url), label: @report.attachment.filename %>
6
+ <% end %>
11
7
  <% end %>
12
- <p>
13
- <%= Spree.t('report_mailer.thanks') %><br />
14
- <%= "#{current_store.name}" %>
15
- </p>
@@ -1,12 +1,19 @@
1
- <!-- You can override this template to design your own footer.
2
- <table class="email-footer" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
1
+ <!-- You can override this template to design your own footer. -->
2
+ <table width="100%" cellpadding="0" cellspacing="0" role="presentation">
3
3
  <tr>
4
- <td class="content-cell" align="center">
5
- <p class="f-fallback sub align-center">
6
- <a href="#">Terms</a> | <a href="#">Privacy</a> |
7
- <a href="#">Unsubscribe</a>
8
- </p>
4
+ <td align="center">
5
+ <div class="footer_content f-fallback">
6
+ <p class="footer_brand">
7
+ <%= link_to current_store.name, current_store.storefront_url %>
8
+ </p>
9
+ <% if current_store.address.present? %>
10
+ <p class="footer_meta"><%= current_store.address %></p>
11
+ <% end %>
12
+ <% if current_store.customer_support_email.present? %>
13
+ <p class="footer_meta"><%= mail_to current_store.customer_support_email %></p>
14
+ <% end %>
15
+ <p class="footer_note">&copy; <%= Time.current.year %> <%= current_store.name %></p>
16
+ </div>
9
17
  </td>
10
18
  </tr>
11
19
  </table>
12
- -->
@@ -1,6 +1,8 @@
1
1
  <!-- You can override this template to design your own header. -->
2
- <tr>
3
- <td class="email-masthead">
4
- <%= render 'spree/shared/mailer_logo', logo: current_store.mailer_logo.attached? ? current_store.mailer_logo : current_store.logo %>
5
- </td>
6
- </tr>
2
+ <table width="100%" cellpadding="0" cellspacing="0" role="presentation">
3
+ <tr>
4
+ <td class="email-masthead" align="center">
5
+ <%= render 'spree/shared/mailer_logo', logo: current_store.mailer_logo.attached? ? current_store.mailer_logo : current_store.logo, height: 32 %>
6
+ </td>
7
+ </tr>
8
+ </table>