spree_core 5.4.3 → 5.5.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 (231) hide show
  1. checksums.yaml +4 -4
  2. data/app/helpers/spree/base_helper.rb +0 -82
  3. data/app/helpers/spree/currency_helper.rb +0 -12
  4. data/app/helpers/spree/products_helper.rb +0 -8
  5. data/app/jobs/spree/base_job.rb +18 -0
  6. data/app/jobs/spree/events/subscriber_job.rb +2 -1
  7. data/app/jobs/spree/exports/generate_job.rb +11 -0
  8. data/app/jobs/spree/images/save_from_url_job.rb +23 -8
  9. data/app/jobs/spree/imports/assign_tags_job.rb +11 -0
  10. data/app/jobs/spree/imports/base_job.rb +15 -0
  11. data/app/jobs/spree/imports/create_categories_job.rb +37 -0
  12. data/app/jobs/spree/imports/create_rows_job.rb +1 -3
  13. data/app/jobs/spree/imports/process_group_job.rb +8 -6
  14. data/app/jobs/spree/imports/process_rows_job.rb +1 -3
  15. data/app/jobs/spree/media/migrate_product_assets_job.rb +83 -0
  16. data/app/jobs/spree/products/refresh_metrics_job.rb +15 -4
  17. data/app/jobs/spree/reports/generate_job.rb +11 -0
  18. data/app/jobs/spree/search_provider/index_job.rb +5 -1
  19. data/app/jobs/spree/search_provider/remove_job.rb +4 -0
  20. data/app/jobs/spree/stock_reservations/expire_job.rb +11 -0
  21. data/app/models/concerns/spree/calculated_adjustments.rb +34 -1
  22. data/app/models/concerns/spree/display_on.rb +31 -0
  23. data/app/models/concerns/spree/metafields.rb +167 -5
  24. data/app/models/concerns/spree/preference_schema.rb +191 -0
  25. data/app/models/concerns/spree/prefixed_id.rb +94 -11
  26. data/app/models/concerns/spree/product_scopes.rb +36 -17
  27. data/app/models/concerns/spree/ransackable_attributes.rb +5 -1
  28. data/app/models/concerns/spree/search_indexable.rb +8 -7
  29. data/app/models/concerns/spree/searchable.rb +11 -2
  30. data/app/models/concerns/spree/stores/channels.rb +20 -0
  31. data/app/models/concerns/spree/stores/markets.rb +21 -5
  32. data/app/models/concerns/spree/typed_associations.rb +120 -0
  33. data/app/models/concerns/spree/user_methods.rb +71 -12
  34. data/app/models/spree/ability.rb +4 -117
  35. data/app/models/spree/api_key.rb +53 -0
  36. data/app/models/spree/asset.rb +28 -5
  37. data/app/models/spree/authentication/strategy_registry.rb +72 -0
  38. data/app/models/spree/base.rb +18 -1
  39. data/app/models/spree/channel.rb +159 -0
  40. data/app/models/spree/country.rb +2 -0
  41. data/app/models/spree/current.rb +5 -1
  42. data/app/models/spree/custom_field.rb +9 -0
  43. data/app/models/spree/custom_field_definition.rb +7 -0
  44. data/app/models/spree/customer_group.rb +8 -2
  45. data/app/models/spree/export.rb +30 -3
  46. data/app/models/spree/gateway.rb +25 -0
  47. data/app/models/spree/gift_card.rb +1 -1
  48. data/app/models/spree/gift_card_batch.rb +4 -1
  49. data/app/models/spree/import.rb +5 -0
  50. data/app/models/spree/import_row.rb +12 -0
  51. data/app/models/spree/line_item.rb +6 -1
  52. data/app/models/spree/market.rb +32 -1
  53. data/app/models/spree/metafield.rb +38 -0
  54. data/app/models/spree/metafield_definition.rb +29 -6
  55. data/app/models/spree/metafields/json.rb +10 -0
  56. data/app/models/spree/newsletter_subscriber.rb +19 -3
  57. data/app/models/spree/option_type.rb +48 -7
  58. data/app/models/spree/order/checkout.rb +3 -3
  59. data/app/models/spree/order.rb +102 -6
  60. data/app/models/spree/order_approval.rb +19 -0
  61. data/app/models/spree/order_cancellation.rb +19 -0
  62. data/app/models/spree/order_routing/has_strategy_preference.rb +28 -0
  63. data/app/models/spree/order_routing/rules/default_location.rb +16 -0
  64. data/app/models/spree/order_routing/rules/minimize_splits.rb +45 -0
  65. data/app/models/spree/order_routing/rules/preferred_location.rb +22 -0
  66. data/app/models/spree/order_routing/strategy/base.rb +47 -0
  67. data/app/models/spree/order_routing/strategy/legacy.rb +33 -0
  68. data/app/models/spree/order_routing/strategy/reducer.rb +68 -0
  69. data/app/models/spree/order_routing/strategy/rules.rb +81 -0
  70. data/app/models/spree/order_routing_rule.rb +75 -0
  71. data/app/models/spree/permission_sets/configuration_management.rb +16 -0
  72. data/app/models/spree/permission_sets/product_display.rb +2 -0
  73. data/app/models/spree/permission_sets/product_management.rb +2 -0
  74. data/app/models/spree/price.rb +14 -1
  75. data/app/models/spree/price_list.rb +129 -17
  76. data/app/models/spree/price_rule.rb +11 -1
  77. data/app/models/spree/price_rules/customer_group_rule.rb +15 -1
  78. data/app/models/spree/price_rules/market_rule.rb +16 -1
  79. data/app/models/spree/price_rules/user_rule.rb +21 -2
  80. data/app/models/spree/product/channels.rb +149 -0
  81. data/app/models/spree/product/legacy_multi_store_support.rb +40 -0
  82. data/app/models/spree/product/slugs.rb +1 -1
  83. data/app/models/spree/product.rb +172 -31
  84. data/app/models/spree/product_publication.rb +43 -0
  85. data/app/models/spree/promotion/actions/create_adjustment.rb +4 -0
  86. data/app/models/spree/promotion/actions/create_item_adjustments.rb +4 -0
  87. data/app/models/spree/promotion/actions/create_line_items.rb +32 -14
  88. data/app/models/spree/promotion/rules/country.rb +40 -18
  89. data/app/models/spree/promotion/rules/customer_group.rb +10 -1
  90. data/app/models/spree/promotion/rules/product.rb +4 -0
  91. data/app/models/spree/promotion/rules/taxon.rb +24 -1
  92. data/app/models/spree/promotion/rules/user.rb +21 -0
  93. data/app/models/spree/promotion/rules/user_logged_in.rb +6 -0
  94. data/app/models/spree/promotion.rb +22 -1
  95. data/app/models/spree/promotion_action.rb +17 -11
  96. data/app/models/spree/promotion_rule.rb +17 -18
  97. data/app/models/spree/search_provider/meilisearch.rb +12 -2
  98. data/app/models/spree/stock/availability_validator.rb +1 -1
  99. data/app/models/spree/stock/quantifier.rb +89 -9
  100. data/app/models/spree/stock_item.rb +36 -0
  101. data/app/models/spree/stock_location.rb +52 -0
  102. data/app/models/spree/stock_reservation.rb +38 -0
  103. data/app/models/spree/stock_reservations/insufficient_stock_error.rb +12 -0
  104. data/app/models/spree/store.rb +18 -72
  105. data/app/models/spree/store_credit.rb +0 -8
  106. data/app/models/spree/store_product.rb +11 -23
  107. data/app/models/spree/taxon.rb +0 -5
  108. data/app/models/spree/user_identity.rb +1 -2
  109. data/app/models/spree/variant.rb +132 -18
  110. data/app/models/spree/variant_media.rb +46 -0
  111. data/app/models/spree/webhook_delivery.rb +1 -1
  112. data/app/models/spree/webhook_endpoint.rb +24 -0
  113. data/app/models/spree/wished_item.rb +0 -13
  114. data/app/presenters/spree/csv/product_variant_presenter.rb +23 -3
  115. data/app/presenters/spree/search_provider/product_presenter.rb +11 -4
  116. data/app/presenters/spree/variant_presenter.rb +4 -3
  117. data/app/services/spree/addresses/update.rb +6 -8
  118. data/app/services/spree/cart/add_item.rb +10 -0
  119. data/app/services/spree/cart/empty.rb +2 -0
  120. data/app/services/spree/cart/remove_line_item.rb +10 -0
  121. data/app/services/spree/cart/remove_out_of_stock_items.rb +1 -1
  122. data/app/services/spree/cart/set_quantity.rb +10 -0
  123. data/app/services/spree/carts/complete.rb +1 -0
  124. data/app/services/spree/carts/create.rb +1 -0
  125. data/app/services/spree/carts/update.rb +18 -2
  126. data/app/services/spree/carts/upsert_items.rb +6 -6
  127. data/app/services/spree/imports/row_processors/customer.rb +4 -1
  128. data/app/services/spree/imports/row_processors/product_variant.rb +95 -57
  129. data/app/services/spree/newsletter/link_user.rb +53 -0
  130. data/app/services/spree/newsletter/subscribe.rb +31 -9
  131. data/app/services/spree/orders/approve.rb +27 -6
  132. data/app/services/spree/orders/build_shipments.rb +29 -0
  133. data/app/services/spree/orders/cancel.rb +34 -3
  134. data/app/services/spree/orders/complete.rb +53 -0
  135. data/app/services/spree/orders/create.rb +156 -0
  136. data/app/services/spree/orders/update.rb +51 -0
  137. data/app/services/spree/orders/upsert_items.rb +70 -0
  138. data/app/services/spree/prices/bulk_upsert.rb +201 -0
  139. data/app/services/spree/products/duplicator.rb +1 -1
  140. data/app/services/spree/products/prepare_nested_attributes.rb +2 -30
  141. data/app/services/spree/sample_data/loader.rb +30 -0
  142. data/app/services/spree/stock_reservations/extend.rb +19 -0
  143. data/app/services/spree/stock_reservations/release.rb +12 -0
  144. data/app/services/spree/stock_reservations/reserve.rb +103 -0
  145. data/app/services/spree/taxons/remove_products.rb +7 -1
  146. data/app/subscribers/spree/product_metrics_subscriber.rb +3 -7
  147. data/app/views/spree/invitation_mailer/invitation_email.html.erb +4 -0
  148. data/config/locales/en.yml +27 -10
  149. data/config/routes.rb +9 -0
  150. data/db/migrate/20260429000001_create_spree_order_cancellations.rb +25 -0
  151. data/db/migrate/20260429000002_create_spree_order_approvals.rb +22 -0
  152. data/db/migrate/20260429000003_add_status_to_spree_orders.rb +6 -0
  153. data/db/migrate/20260429000004_add_scopes_to_spree_api_keys.rb +11 -0
  154. data/db/migrate/20260501000001_create_spree_stock_reservations.rb +19 -0
  155. data/db/migrate/20260507162651_create_spree_variant_media.rb +23 -0
  156. data/db/migrate/20260508175303_add_pickup_to_spree_stock_locations.rb +12 -0
  157. data/db/migrate/20260508204040_create_spree_channels.rb +18 -0
  158. data/db/migrate/20260508204041_create_spree_order_routing_rules.rb +18 -0
  159. data/db/migrate/20260508204042_add_preferred_stock_location_to_spree_orders.rb +5 -0
  160. data/db/migrate/20260508204043_add_channel_id_to_spree_orders.rb +10 -0
  161. data/db/migrate/20260511000001_backfill_status_on_spree_orders.rb +57 -0
  162. data/db/migrate/20260515000001_add_store_id_to_spree_newsletter_subscribers.rb +25 -0
  163. data/db/migrate/20260529000001_add_unique_index_to_spree_price_rules.rb +41 -0
  164. data/db/migrate/20260529000002_add_unique_index_to_spree_promotion_rules.rb +37 -0
  165. data/db/migrate/20260601000001_create_spree_product_publications.rb +14 -0
  166. data/db/migrate/20260601000002_add_store_id_to_spree_products.rb +16 -0
  167. data/db/migrate/20260602000001_add_default_to_spree_channels.rb +14 -0
  168. data/db/sample_data/channels.rb +12 -0
  169. data/db/sample_data/orders.rb +1 -1
  170. data/db/sample_data/products.csv +212 -212
  171. data/lib/generators/spree/api_resource/api_resource_generator.rb +353 -0
  172. data/lib/generators/spree/api_resource/templates/admin_controller.rb.tt +23 -0
  173. data/lib/generators/spree/api_resource/templates/admin_controller_spec.rb.tt +59 -0
  174. data/lib/generators/spree/api_resource/templates/admin_serializer.rb.tt +11 -0
  175. data/lib/generators/spree/api_resource/templates/factory.rb.tt +26 -0
  176. data/lib/generators/spree/api_resource/templates/store_aliased_serializer.rb.tt +12 -0
  177. data/lib/generators/spree/api_resource/templates/store_controller.rb.tt +31 -0
  178. data/lib/generators/spree/api_resource/templates/store_controller_spec.rb.tt +61 -0
  179. data/lib/generators/spree/api_resource/templates/store_serializer.rb.tt +14 -0
  180. data/lib/generators/spree/controller_decorator/controller_decorator_generator.rb +66 -0
  181. data/lib/generators/spree/controller_decorator/templates/controller_decorator.rb.tt +25 -0
  182. data/lib/generators/spree/model/model_generator.rb +73 -7
  183. data/lib/generators/spree/model/templates/create_table_migration.rb.tt +40 -0
  184. data/lib/generators/spree/model/templates/model.rb.tt +28 -2
  185. data/lib/spree/core/configuration.rb +7 -0
  186. data/lib/spree/core/controller_helpers/auth.rb +0 -12
  187. data/lib/spree/core/controller_helpers/currency.rb +0 -17
  188. data/lib/spree/core/controller_helpers/order.rb +0 -19
  189. data/lib/spree/core/dependencies.rb +5 -2
  190. data/lib/spree/core/engine.rb +54 -7
  191. data/lib/spree/core/permission_configuration.rb +15 -0
  192. data/lib/spree/core/preferences/masking.rb +47 -0
  193. data/lib/spree/core/preferences/preferable_class_methods.rb +7 -1
  194. data/lib/spree/core/version.rb +1 -1
  195. data/lib/spree/core.rb +56 -5
  196. data/lib/spree/permitted_attributes.rb +9 -7
  197. data/lib/spree/testing_support/factories/address_factory.rb +16 -9
  198. data/lib/spree/testing_support/factories/api_key_factory.rb +1 -0
  199. data/lib/spree/testing_support/factories/channel_factory.rb +8 -0
  200. data/lib/spree/testing_support/factories/line_item_factory.rb +2 -8
  201. data/lib/spree/testing_support/factories/newsletter_subscriber_factory.rb +2 -0
  202. data/lib/spree/testing_support/factories/product_factory.rb +16 -7
  203. data/lib/spree/testing_support/factories/product_publication_factory.rb +6 -0
  204. data/lib/spree/testing_support/factories/refresh_token_factory.rb +15 -0
  205. data/lib/spree/testing_support/factories/stock_location_factory.rb +2 -2
  206. data/lib/spree/testing_support/factories/stock_reservation_factory.rb +31 -0
  207. data/lib/spree/testing_support/factories/variant_factory.rb +3 -3
  208. data/lib/spree/testing_support/order_walkthrough.rb +1 -1
  209. data/lib/spree/testing_support/store.rb +10 -0
  210. data/lib/spree/upgrades/5_4_to_5_5/manifest.yml +53 -0
  211. data/lib/tasks/channels.rake +94 -0
  212. data/lib/tasks/core.rake +1 -0
  213. data/lib/tasks/media.rake +27 -0
  214. data/lib/tasks/products.rake +4 -6
  215. data/lib/tasks/publications.rake +60 -0
  216. data/lib/tasks/upgrade.rake +211 -0
  217. metadata +83 -18
  218. data/app/finders/spree/variants/visible_finder.rb +0 -23
  219. data/app/paginators/spree/shared/paginate.rb +0 -30
  220. data/app/presenters/spree/filters/price_presenter.rb +0 -23
  221. data/app/presenters/spree/filters/price_range_presenter.rb +0 -30
  222. data/app/presenters/spree/filters/quantified_price_range_presenter.rb +0 -45
  223. data/app/presenters/spree/product_summary_presenter.rb +0 -27
  224. data/app/presenters/spree/variants/options_presenter.rb +0 -82
  225. data/app/services/spree/classifications/reposition.rb +0 -23
  226. data/app/sorters/spree/orders/sort.rb +0 -10
  227. data/lib/spree/core/controller_helpers/common.rb +0 -14
  228. data/lib/spree/core/token_generator.rb +0 -23
  229. data/lib/spree/database_type_utilities.rb +0 -22
  230. data/lib/spree/testing_support/bar_ability.rb +0 -14
  231. data/lib/spree/testing_support/factories/store_product_factory.rb +0 -6
@@ -2,20 +2,86 @@ require "rails/generators/active_record/model/model_generator"
2
2
 
3
3
  module Spree
4
4
  class ModelGenerator < ActiveRecord::Generators::ModelGenerator
5
-
6
5
  def self.source_paths
7
- paths = superclass.source_paths
8
- paths << File.expand_path('templates', __dir__)
9
- paths.flatten
6
+ [File.expand_path('templates', __dir__), *superclass.source_paths]
10
7
  end
11
8
 
12
- class_option :parent, type: :string, default: "Spree::Base", desc: "The parent class for the generated model"
9
+ class_option :parent,
10
+ type: :string,
11
+ default: 'Spree.base_class',
12
+ desc: 'The parent class expression for the generated model'
13
+
14
+ class_option :id_prefix,
15
+ type: :string,
16
+ desc: 'Prefix for prefixed IDs (default: snake_cased class name, e.g. brand)'
17
+
18
+ class_option :paranoid,
19
+ type: :boolean,
20
+ default: false,
21
+ desc: 'Enable acts_as_paranoid soft-delete (adds deleted_at column + index)'
22
+
23
+ class_option :metafields,
24
+ type: :boolean,
25
+ default: false,
26
+ desc: 'Include Spree::Metafields and Spree::Metadata concerns'
13
27
 
14
- desc 'Creates a new Spree model'
28
+ desc 'Creates a new Spree model with prefixed IDs and Spree.base_class parent'
15
29
 
16
- # Override to prevent module file from being created
17
30
  def create_module_file
18
31
  return
19
32
  end
33
+
34
+ no_tasks do
35
+ def id_prefix
36
+ options[:id_prefix] || file_name
37
+ end
38
+
39
+ def paranoid?
40
+ options[:paranoid]
41
+ end
42
+
43
+ def metafields?
44
+ options[:metafields]
45
+ end
46
+
47
+ def class_path
48
+ ['spree']
49
+ end
50
+
51
+ def table_name
52
+ "spree_#{super.delete_prefix('spree_')}"
53
+ end
54
+
55
+ # Resolution: explicit class hint in attribute spec → Spree.user_class
56
+ # for user-named columns → Spree.admin_user_class for admin-user-named
57
+ # columns → Spree::<CamelCasedName> default. The Rails attribute parser
58
+ # splits on `:` so explicit hints can only be unqualified names; users
59
+ # needing a namespace edit the generated belongs_to.
60
+ def belongs_to_class_name_expr(attribute)
61
+ explicit = attribute.attr_options.keys.find { |k| k.to_s.match?(/\A(::)?[A-Z]/) }
62
+ return "'#{explicit}'" if explicit
63
+
64
+ case attribute.name
65
+ when 'user', 'user_id'
66
+ '"::#{Spree.user_class}"'
67
+ when 'admin_user', 'admin_user_id', 'created_by', 'created_by_id',
68
+ 'approver', 'approver_id', 'canceler', 'canceler_id'
69
+ '"::#{Spree.admin_user_class}"'
70
+ else
71
+ "'Spree::#{attribute.name.camelize}'"
72
+ end
73
+ end
74
+
75
+ def column_default_for(attribute)
76
+ return nil unless attribute.attr_options.key?(:default)
77
+
78
+ value = attribute.attr_options[:default]
79
+ case value
80
+ when true, false, nil then value.inspect
81
+ when Numeric then value.to_s
82
+ else value.to_s.inspect
83
+ end
84
+ end
85
+ end
20
86
  end
21
87
  end
@@ -0,0 +1,40 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
+ def change
3
+ create_table :<%= table_name %><%= primary_key_type %> do |t|
4
+ <% attributes.reject(&:reference?).reject(&:virtual?).each do |attribute| -%>
5
+ <% if attribute.password_digest? -%>
6
+ t.string :password_digest, null: false
7
+ <% elsif attribute.token? -%>
8
+ t.string :<%= attribute.name %>, null: false
9
+ <% else -%>
10
+ <% default_expr = column_default_for(attribute) -%>
11
+ t.<%= attribute.type %> :<%= attribute.name %>, null: false<%= ", default: #{default_expr}" if default_expr %>
12
+ <% end -%>
13
+ <% end -%>
14
+ <% attributes.select(&:reference?).each do |attribute| -%>
15
+ t.references :<%= attribute.name %>, null: false, index: true, foreign_key: false<%= ', polymorphic: true' if attribute.polymorphic? %>
16
+ <% end -%>
17
+ <% if paranoid? -%>
18
+ t.datetime :deleted_at
19
+
20
+ <% end -%>
21
+ <% if options[:timestamps] -%>
22
+ t.timestamps
23
+ <% end -%>
24
+ end
25
+ <% attributes.select(&:token?).each do |attribute| -%>
26
+ add_index :<%= table_name %>, :<%= attribute.name %>, unique: true,
27
+ name: 'index_<%= table_name %>_on_<%= attribute.name %>'
28
+ <% end -%>
29
+ <% attributes.select(&:has_uniq_index?).reject(&:token?).each do |attribute| -%>
30
+ add_index :<%= table_name %>, :<%= attribute.name %>, unique: true,
31
+ name: 'index_<%= table_name %>_on_<%= attribute.name %>'
32
+ <% end -%>
33
+ <% attributes.select { |a| a.has_index? && !a.has_uniq_index? && !a.reference? && !a.token? }.each do |attribute| -%>
34
+ add_index :<%= table_name %>, :<%= attribute.name %>
35
+ <% end -%>
36
+ <% if paranoid? -%>
37
+ add_index :<%= table_name %>, :deleted_at
38
+ <% end -%>
39
+ end
40
+ end
@@ -1,7 +1,22 @@
1
1
  module Spree
2
- class <%= class_name.demodulize %> < <%= parent_class_name.classify %>
2
+ class <%= class_name.demodulize %> < <%= parent_class_name %>
3
+ <% if metafields? -%>
4
+ include Spree::Metafields
5
+ include Spree::Metadata
6
+
7
+ <% end -%>
8
+ <% if paranoid? -%>
9
+ acts_as_paranoid
10
+
11
+ <% end -%>
12
+ has_prefix_id :<%= id_prefix %>
13
+
3
14
  <% attributes.select(&:reference?).each do |attribute| -%>
4
- belongs_to :<%= attribute.name %><%= ", polymorphic: true" if attribute.polymorphic? %>
15
+ <% if attribute.polymorphic? -%>
16
+ belongs_to :<%= attribute.name %>, polymorphic: true
17
+ <% else -%>
18
+ belongs_to :<%= attribute.name %>, class_name: <%= belongs_to_class_name_expr(attribute) %>
19
+ <% end -%>
5
20
  <% end -%>
6
21
  <% attributes.select(&:rich_text?).each do |attribute| -%>
7
22
  has_rich_text :<%= attribute.name %>
@@ -18,5 +33,16 @@ module Spree
18
33
  <% if attributes.any?(&:password_digest?) -%>
19
34
  has_secure_password
20
35
  <% end -%>
36
+ <% required_attrs = attributes.reject { |a| a.reference? || a.type == :boolean || a.token? || a.attachment? || a.attachments? || a.rich_text? || a.password_digest? } -%>
37
+ <% if required_attrs.any? %>
38
+ <% required_attrs.each do |attr| -%>
39
+ validates :<%= attr.name %>, presence: true<% if attr.has_uniq_index? %>, uniqueness: { scope: spree_base_uniqueness_scope }<% end %>
40
+
41
+ <% end -%>
42
+ <% end -%>
43
+ <% ransackable = attributes.reject { |a| a.reference? || a.attachment? || a.attachments? || a.rich_text? || a.token? || a.password_digest? }.map(&:name) -%>
44
+ self.whitelisted_ransackable_attributes = %w[<%= ransackable.join(' ') %>]
45
+ self.whitelisted_ransackable_associations = %w[]
46
+ self.whitelisted_ransackable_scopes = %w[]
21
47
  end
22
48
  end
@@ -25,6 +25,10 @@ module Spree
25
25
  # Alphabetized to more easily lookup particular preferences
26
26
  preference :address_requires_state, :boolean, default: true, deprecated: true # should state/state_name be required
27
27
  preference :address_requires_phone, :boolean, default: false # Determines whether we require phone in address
28
+ # Origin where the admin SPA is hosted (e.g. `https://admin.shop.com`).
29
+ # Used by admin mailers; falls back to `http://localhost:5173` in dev
30
+ # and the store's storefront URL otherwise. Set this in production.
31
+ preference :admin_url, :string, default: nil
28
32
  preference :allow_checkout_on_gateway_error, :boolean, default: false
29
33
  preference :allow_empty_price_amount, :boolean, default: false
30
34
  preference :allow_guest_checkout, :boolean, default: true, deprecated: true # this is only used in the rails frontend, and is not implemented in API
@@ -88,6 +92,9 @@ module Spree
88
92
  preference :require_master_price, :boolean, default: false
89
93
  preference :restock_inventory, :boolean, default: true # Determines if a return item is restocked automatically once it has been received
90
94
  preference :return_eligibility_number_of_days, :integer, default: 365
95
+ preference :reserve_stock_on, :string, default: 'checkout' # 'checkout' (default) or 'cart' — landing in 6.0 alongside Cart/Order split
96
+ preference :stock_reservations_enabled, :boolean, default: true # Hold stock during checkout to prevent overselling
97
+ preference :default_stock_reservation_ttl_minutes, :integer, default: 10 # Fallback TTL when a Store doesn't override
91
98
  preference :send_core_emails, :boolean, default: true, deprecated: true # Default mail headers settings
92
99
  preference :shipping_instructions, :boolean, deprecated: true
93
100
  preference :show_only_complete_orders_by_default, :boolean, deprecated: true
@@ -3,7 +3,6 @@ module Spree
3
3
  module ControllerHelpers
4
4
  module Auth
5
5
  extend ActiveSupport::Concern
6
- include Spree::Core::TokenGenerator
7
6
 
8
7
  included do
9
8
  if defined?(helper_method)
@@ -20,17 +19,6 @@ module Spree
20
19
  @current_ability ||= Spree.ability_class.new(try_spree_current_user, { store: current_store })
21
20
  end
22
21
 
23
- def current_oauth_token
24
- Spree::Deprecation.warn('Spree::Current.oauth_token is deprecated and will be removed in Spree 5.5')
25
-
26
- get_last_access_token = ->(user) { Spree::OauthAccessToken.active_for(user).where(expires_in: nil).last }
27
- create_access_token = ->(user) { Spree::OauthAccessToken.create!(resource_owner: user) }
28
- user = try_spree_current_user
29
- return unless user
30
-
31
- @current_oauth_token ||= get_last_access_token.call(user) || create_access_token.call(user)
32
- end
33
-
34
22
  # this will work for devise out of the box
35
23
  # for other auth systems you will need to override this method
36
24
  def store_location(location = nil)
@@ -7,7 +7,6 @@ module Spree
7
7
  included do
8
8
  if defined?(helper_method)
9
9
  helper_method :supported_currencies
10
- helper_method :supported_currencies_for_all_stores
11
10
  helper_method :current_currency
12
11
  helper_method :supported_currency?
13
12
  helper_method :currency_param
@@ -38,22 +37,6 @@ module Spree
38
37
  @supported_currencies ||= current_store&.supported_currencies_list
39
38
  end
40
39
 
41
- # Returns the list of supported currencies for all stores.
42
- # @deprecated This method will be removed in Spree 5.5.
43
- # @return [Array<String>] the list of supported currencies, eg. `["USD", "EUR"]`
44
- def supported_currencies_for_all_stores
45
- Spree::Deprecation.warn(
46
- 'supported_currencies_for_all_stores is deprecated and will be removed in Spree 5.5.'
47
- )
48
-
49
- @supported_currencies_for_all_stores ||= begin
50
- (
51
- Spree::Store.pluck(:supported_currencies).map { |c| c&.split(',') }.flatten + Spree::Store.pluck(:default_currency)
52
- ).
53
- compact.uniq.map { |code| ::Money::Currency.find(code.strip) }
54
- end
55
- end
56
-
57
40
  # Checks if the given currency is supported.
58
41
  # @param currency_iso_code [String] the ISO code of the currency, eg. `USD`
59
42
  # @return [Boolean] `true` if the currency is supported, `false` otherwise
@@ -7,7 +7,6 @@ module Spree
7
7
  included do
8
8
  if defined?(helper_method)
9
9
  helper_method :current_order
10
- helper_method :simple_current_order
11
10
  end
12
11
  end
13
12
 
@@ -15,24 +14,6 @@ module Spree
15
14
  @order_token ||= cookies.signed[:token] || params[:order_token]
16
15
  end
17
16
 
18
- # @deprecated Use `current_order` instead. This method will be removed in Spree 5.5.
19
- def simple_current_order
20
- Spree::Deprecation.warn(
21
- 'simple_current_order is deprecated and will be removed in Spree 5.5. Use current_order instead.'
22
- )
23
-
24
- return @simple_current_order if @simple_current_order
25
-
26
- @simple_current_order = find_order_by_token_or_user
27
-
28
- if @simple_current_order
29
- @simple_current_order.last_ip_address = ip_address
30
- return @simple_current_order
31
- else
32
- @simple_current_order = current_store.orders.new
33
- end
34
- end
35
-
36
17
  # The current incomplete order from the token for use in cart and during checkout
37
18
  def current_order(options = {})
38
19
  options[:create_order_if_necessary] ||= false
@@ -44,6 +44,9 @@ module Spree
44
44
  # order
45
45
  order_approve_service: 'Spree::Orders::Approve',
46
46
  order_cancel_service: 'Spree::Orders::Cancel',
47
+ order_complete_service: 'Spree::Orders::Complete',
48
+ order_create_service: 'Spree::Orders::Create',
49
+ order_update_service: 'Spree::Orders::Update',
47
50
  order_updater: 'Spree::OrderUpdater',
48
51
 
49
52
  # shipment
@@ -62,7 +65,7 @@ module Spree
62
65
  posts_sorter: nil,
63
66
  products_sorter: 'Spree::Products::Sort',
64
67
  # paginator
65
- collection_paginator: 'Spree::Shared::Paginate',
68
+ collection_paginator: nil,
66
69
 
67
70
  # coupons
68
71
  # TODO: we should split this service into 2 separate - Add and Remove
@@ -80,7 +83,7 @@ module Spree
80
83
  credit_cards_destroy_service: 'Spree::CreditCards::Destroy',
81
84
 
82
85
  # classifications
83
- classification_reposition_service: 'Spree::Classifications::Reposition',
86
+ classification_reposition_service: nil,
84
87
 
85
88
  # line items
86
89
  line_item_create_service: 'Spree::LineItems::Create',
@@ -11,6 +11,7 @@ module Spree
11
11
  :payment_methods,
12
12
  :adjusters,
13
13
  :stock_splitters,
14
+ :order_routing,
14
15
  :promotions,
15
16
  :pricing,
16
17
  :line_item_comparison_hooks,
@@ -25,6 +26,7 @@ module Spree
25
26
  :page_blocks,
26
27
  :reports,
27
28
  :translatable_resources,
29
+ :taggable_types,
28
30
  :metafields,
29
31
  :analytics_events,
30
32
  :analytics_event_handlers,
@@ -35,6 +37,7 @@ module Spree
35
37
  SpreeCalculators = Struct.new(:shipping_methods, :tax_rates, :promotion_actions_create_adjustments, :promotion_actions_create_item_adjustments)
36
38
  PromoEnvironment = Struct.new(:rules, :actions)
37
39
  PricingEnvironment = Struct.new(:rules)
40
+ OrderRoutingEnvironment = Struct.new(:strategies, :rules)
38
41
  SpreeValidators = Struct.new(:addresses)
39
42
  MetafieldsEnvironment = Struct.new(:types, :enabled_resources)
40
43
  isolate_namespace Spree
@@ -75,6 +78,15 @@ module Spree
75
78
  initializer 'spree.register.adjustable_adjusters' do |app|
76
79
  end
77
80
 
81
+ # Seed the order routing registries early so engines and apps can append
82
+ # their own strategies / rule kinds from initializer files. Core's defaults
83
+ # are concatenated in after_initialize below.
84
+ initializer 'spree.register.order_routing', before: :load_config_initializers do |app|
85
+ app.config.spree.order_routing = OrderRoutingEnvironment.new
86
+ app.config.spree.order_routing.strategies = []
87
+ app.config.spree.order_routing.rules = []
88
+ end
89
+
78
90
  initializer 'spree.register.metafields' do |app|
79
91
  app.config.spree.metafields = MetafieldsEnvironment.new
80
92
  app.config.spree.metafields.types = []
@@ -133,6 +145,23 @@ module Spree
133
145
  Spree::Adjustable::Adjuster::Tax
134
146
  ]
135
147
 
148
+ # Selectable order routing strategies. The internal Reducer collaborator
149
+ # is intentionally NOT listed — it is not a Strategy::Base. Plugins add
150
+ # their own (or remove Legacy) via this array.
151
+ Rails.application.config.spree.order_routing.strategies.concat [
152
+ Spree::OrderRouting::Strategy::Rules,
153
+ Spree::OrderRouting::Strategy::Legacy
154
+ ]
155
+
156
+ # Available order routing rule kinds. STI dispatches at runtime via the
157
+ # +type+ column; this array is the curated allowlist that drives admin
158
+ # pickers and the rule +type+ validation. Plugins append their own.
159
+ Rails.application.config.spree.order_routing.rules.concat [
160
+ Spree::OrderRouting::Rules::PreferredLocation,
161
+ Spree::OrderRouting::Rules::MinimizeSplits,
162
+ Spree::OrderRouting::Rules::DefaultLocation
163
+ ]
164
+
136
165
  Rails.application.config.spree.calculators.promotion_actions_create_adjustments = [
137
166
  Spree::Calculator::FlatPercentItemTotal,
138
167
  Spree::Calculator::FlatRate,
@@ -161,12 +190,18 @@ module Spree
161
190
  Spree::Promotion::Rules::OptionValue,
162
191
  ]
163
192
 
193
+ # Default registry. MarketRule is included so existing installs
194
+ # don't lose access to saved rule rows in the admin. ZoneRule is
195
+ # intentionally excluded — Zones are being removed in 6.0 (see
196
+ # docs/plans/6.0-tax-provider.md) and we don't want to invest
197
+ # in admin UI for a model on its way out. The class itself
198
+ # stays so legacy data continues to load; it just doesn't show
199
+ # up in the "Add rule" picker.
164
200
  Rails.application.config.spree.pricing.rules.concat [
165
- Spree::PriceRules::ZoneRule,
166
- Spree::PriceRules::MarketRule,
167
201
  Spree::PriceRules::UserRule,
168
202
  Spree::PriceRules::CustomerGroupRule,
169
- Spree::PriceRules::VolumeRule
203
+ Spree::PriceRules::VolumeRule,
204
+ Spree::PriceRules::MarketRule
170
205
  ]
171
206
 
172
207
  Rails.application.config.spree.promotions.actions = [
@@ -216,6 +251,18 @@ module Spree
216
251
  Spree::Policy
217
252
  ]
218
253
 
254
+ # Resources that expose tags via `acts_as_taggable_on :tags`. The
255
+ # Admin API's `/tags` autocomplete endpoint accepts these as
256
+ # `taggable_type`, and the SPA `<TagCombobox>` targets them by name.
257
+ # Extend in an app initializer (after :load_config_initializers) to
258
+ # surface custom taggables — e.g.
259
+ # Rails.application.config.spree.taggable_types << 'MyApp::Vendor'.
260
+ Rails.application.config.spree.taggable_types = [
261
+ 'Spree::Product',
262
+ 'Spree::Order',
263
+ Spree.user_class.to_s
264
+ ]
265
+
219
266
  Rails.application.config.spree.metafields.types = [
220
267
  Spree::Metafields::ShortText,
221
268
  Spree::Metafields::LongText,
@@ -301,12 +348,12 @@ module Spree
301
348
  ]
302
349
 
303
350
  # Pre-load authentication strategy classes to avoid reflection at request time
304
- Rails.application.config.spree.store_authentication_strategies = {
351
+ Rails.application.config.spree.store_authentication_strategies = Spree::Authentication::StrategyRegistry.new(
305
352
  email: Spree::Authentication::Strategies::EmailPasswordStrategy
306
- }
307
- Rails.application.config.spree.admin_authentication_strategies = {
353
+ )
354
+ Rails.application.config.spree.admin_authentication_strategies = Spree::Authentication::StrategyRegistry.new(
308
355
  email: Spree::Authentication::Strategies::EmailPasswordStrategy
309
- }
356
+ )
310
357
  end
311
358
 
312
359
  initializer 'spree.promo.register.promotions.actions' do |app|
@@ -105,6 +105,21 @@ module Spree
105
105
  @role_permissions = {}
106
106
  end
107
107
 
108
+ # Returns a deep copy of this configuration. Useful for snapshotting
109
+ # state before a test mutates it; restore with {#replace}.
110
+ def dup
111
+ copy = super
112
+ copy.instance_variable_set(:@role_permissions, @role_permissions.deep_dup)
113
+ copy
114
+ end
115
+
116
+ # Replaces this configuration's state with a deep copy of `other`'s.
117
+ # Pair with {#dup} to snapshot/restore around code that mutates the
118
+ # global Spree.permissions config.
119
+ def replace(other)
120
+ @role_permissions = other.instance_variable_get(:@role_permissions).deep_dup
121
+ end
122
+
108
123
  private
109
124
 
110
125
  def normalize_role_name(role_name)
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ module Preferences
5
+ # Masks `:password`-typed preferences so secrets (API keys, OAuth
6
+ # tokens, signing secrets, …) never leave the server in plaintext.
7
+ #
8
+ # The mask token is a bullet sequence followed by the last 4
9
+ # characters of the original value — Stripe's "stored, here's the
10
+ # last 4" pattern.
11
+ module Masking
12
+ TOKEN = '••••'
13
+
14
+ # @param value [Object] the preference value to mask
15
+ # @return [String, nil] masked string, or nil if value is blank
16
+ def self.mask(value)
17
+ return nil if value.blank?
18
+
19
+ "#{TOKEN}#{value.to_s.last(4)}"
20
+ end
21
+
22
+ # @param value [Object] a value previously returned by `mask`
23
+ # @return [Boolean] true if value carries the mask token
24
+ def self.masked?(value)
25
+ value.is_a?(String) && value.start_with?(TOKEN)
26
+ end
27
+
28
+ # Serializes a Preferable's `preferences` hash for the wire,
29
+ # masking `:password` values. Keys are stringified to match the
30
+ # wire shape expected by JSON clients — schema entries built by
31
+ # `compute_preference_schema` cache `:key_string` to avoid a
32
+ # `to_s` allocation per field per request.
33
+ #
34
+ # @param preferable [#preferences, #preference_schema, nil] any object
35
+ # that includes `Spree::Preferences::Preferable` and `Spree::PreferenceSchema`
36
+ # @return [Hash{String => Object}]
37
+ def self.serialize(preferable)
38
+ return {} if preferable.nil?
39
+
40
+ preferable.preference_schema.each_with_object({}) do |field, hash|
41
+ value = preferable.preferences[field[:key]]
42
+ hash[field[:key_string] || field[:key].to_s] = field[:type] == :password ? mask(value) : value
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -18,7 +18,13 @@ module Spree::Preferences
18
18
  end
19
19
 
20
20
  define_method preference_setter_method(name) do |value|
21
- value = parse_on_set.call(value) if parse_on_set.is_a?(Proc)
21
+ if parse_on_set.is_a?(Proc)
22
+ # Procs that accept more than one arg opt into receiving the
23
+ # owning record so they can scope (e.g. `normalize_id_preference`
24
+ # rejecting cross-store IDs). `arity.abs > 1` covers both the
25
+ # `(value, owner)` and `(value, owner = nil)` shapes.
26
+ value = parse_on_set.arity.abs > 1 ? parse_on_set.call(value, self) : parse_on_set.call(value)
27
+ end
22
28
  value = convert_preference_value(value, type, nullable: nullable)
23
29
  preferences[name] = value
24
30
 
@@ -1,5 +1,5 @@
1
1
  module Spree
2
- VERSION = '5.4.3'.freeze
2
+ VERSION = '5.5.0.rc1'.freeze
3
3
 
4
4
  def self.version
5
5
  VERSION
data/lib/spree/core.rb CHANGED
@@ -112,7 +112,8 @@ module Spree
112
112
  webhooks: :default,
113
113
  payment_webhooks: :default,
114
114
  api_keys: :default,
115
- search: :default
115
+ search: :default,
116
+ stock_reservations: :default
116
117
  )
117
118
  end
118
119
 
@@ -241,6 +242,14 @@ module Spree
241
242
  Rails.application.config.spree.stock_splitters = value
242
243
  end
243
244
 
245
+ def self.order_routing
246
+ Rails.application.config.spree.order_routing
247
+ end
248
+
249
+ def self.order_routing=(value)
250
+ Rails.application.config.spree.order_routing = value
251
+ end
252
+
244
253
  def self.promotions
245
254
  Rails.application.config.spree.promotions
246
255
  end
@@ -289,6 +298,21 @@ module Spree
289
298
  Rails.application.config.spree.taxon_rules = value
290
299
  end
291
300
 
301
+ # Class-name strings (`'Spree::Product'`, `'Spree::Order'`,
302
+ # `Spree.user_class.to_s`, plus any registered by apps) for resources that
303
+ # expose tags via `acts_as_taggable_on :tags`. Used by the Admin API
304
+ # `/tags` autocomplete endpoint to validate `taggable_type`. Apps extend
305
+ # the list in an initializer:
306
+ #
307
+ # Spree.taggable_types << 'MyApp::Vendor'
308
+ def self.taggable_types
309
+ Rails.application.config.spree.taggable_types
310
+ end
311
+
312
+ def self.taggable_types=(value)
313
+ Rails.application.config.spree.taggable_types = value
314
+ end
315
+
292
316
  def self.reports
293
317
  Rails.application.config.spree.reports
294
318
  end
@@ -338,6 +362,36 @@ module Spree
338
362
  Rails.application.config.spree.pricing = value
339
363
  end
340
364
 
365
+ # Registry of authentication strategy classes for the Store API.
366
+ # @return [Spree::Authentication::StrategyRegistry]
367
+ # @example Registering a third-party identity provider
368
+ # Spree.store_authentication_strategies.add(:auth0, MyApp::Auth::Auth0Strategy)
369
+ # @example Removing a strategy
370
+ # Spree.store_authentication_strategies.remove(:email)
371
+ def self.store_authentication_strategies
372
+ Rails.application.config.spree.store_authentication_strategies
373
+ end
374
+
375
+ # @param value [Spree::Authentication::StrategyRegistry] the registry to use for Store API authentication dispatch
376
+ # @return [Spree::Authentication::StrategyRegistry] the assigned registry
377
+ def self.store_authentication_strategies=(value)
378
+ Rails.application.config.spree.store_authentication_strategies = value
379
+ end
380
+
381
+ # Registry of authentication strategy classes for the Admin API.
382
+ # @return [Spree::Authentication::StrategyRegistry]
383
+ # @example Registering an SSO strategy for admin users
384
+ # Spree.admin_authentication_strategies.add(:okta, MyApp::Auth::OktaStrategy)
385
+ def self.admin_authentication_strategies
386
+ Rails.application.config.spree.admin_authentication_strategies
387
+ end
388
+
389
+ # @param value [Spree::Authentication::StrategyRegistry] the registry to use for Admin API authentication dispatch
390
+ # @return [Spree::Authentication::StrategyRegistry] the assigned registry
391
+ def self.admin_authentication_strategies=(value)
392
+ Rails.application.config.spree.admin_authentication_strategies = value
393
+ end
394
+
341
395
  def self.analytics
342
396
  @analytics ||= AnalyticsConfig.new
343
397
  end
@@ -425,8 +479,6 @@ module Spree
425
479
  end
426
480
 
427
481
  module Core
428
- autoload :TokenGenerator, 'spree/core/token_generator'
429
-
430
482
  class GatewayError < RuntimeError; end
431
483
  class DestroyWithOrdersError < StandardError; end
432
484
  end
@@ -444,14 +496,12 @@ require 'spree/localized_number'
444
496
  require 'spree/money'
445
497
  require 'spree/permitted_attributes'
446
498
  require 'spree/service_module'
447
- require 'spree/database_type_utilities'
448
499
  require 'spree/analytics'
449
500
  require 'spree/events'
450
501
  require 'spree/webhooks'
451
502
 
452
503
  require 'spree/core/partials'
453
504
  require 'spree/core/controller_helpers/auth'
454
- require 'spree/core/controller_helpers/common'
455
505
  require 'spree/core/controller_helpers/order'
456
506
  require 'spree/core/controller_helpers/store'
457
507
  require 'spree/core/controller_helpers/strong_parameters'
@@ -462,6 +512,7 @@ require 'spree/core/controller_helpers/turbo'
462
512
  require 'spree/core/preferences/store'
463
513
  require 'spree/core/preferences/scoped_store'
464
514
  require 'spree/core/preferences/runtime_configuration'
515
+ require 'spree/core/preferences/masking'
465
516
 
466
517
  require 'spree/core/permission_configuration'
467
518
  require 'spree/core/ransack_configuration'