spree_core 5.3.4 → 5.4.0.beta

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 (239) hide show
  1. checksums.yaml +4 -4
  2. data/app/finders/spree/orders/find_complete.rb +33 -2
  3. data/app/finders/spree/stores/find_default.rb +17 -0
  4. data/app/finders/spree/variants/visible_finder.rb +1 -0
  5. data/app/helpers/spree/addresses_helper.rb +1 -2
  6. data/app/helpers/spree/base_helper.rb +5 -4
  7. data/app/jobs/spree/api_key_touch_job.rb +9 -0
  8. data/app/models/concerns/spree/admin_user_methods.rb +32 -0
  9. data/app/models/concerns/spree/number_as_param.rb +5 -3
  10. data/app/models/concerns/spree/prefixed_id.rb +82 -0
  11. data/app/models/concerns/spree/product_scopes.rb +33 -18
  12. data/app/models/concerns/spree/publishable.rb +47 -47
  13. data/app/models/concerns/spree/{multi_store_resource.rb → store_scoped_resource.rb} +1 -10
  14. data/app/models/concerns/spree/stores/markets.rb +124 -0
  15. data/app/models/concerns/spree/user_methods.rb +7 -7
  16. data/app/models/concerns/spree/user_payment_source.rb +2 -0
  17. data/app/models/concerns/spree/user_roles.rb +1 -0
  18. data/app/models/spree/ability.rb +13 -0
  19. data/app/models/spree/address.rb +30 -0
  20. data/app/models/spree/adjustment.rb +2 -0
  21. data/app/models/spree/api_key.rb +47 -0
  22. data/app/models/spree/asset.rb +2 -0
  23. data/app/models/spree/authentication/strategies/base_strategy.rb +55 -0
  24. data/app/models/spree/authentication/strategies/email_password_strategy.rb +47 -0
  25. data/app/models/spree/base.rb +1 -0
  26. data/app/models/spree/calculator.rb +2 -0
  27. data/app/models/spree/country.rb +56 -0
  28. data/app/models/spree/coupon_code.rb +2 -0
  29. data/app/models/spree/credit_card.rb +2 -0
  30. data/app/models/spree/current.rb +9 -4
  31. data/app/models/spree/customer_group.rb +2 -0
  32. data/app/models/spree/customer_return.rb +2 -0
  33. data/app/models/spree/data_feed.rb +2 -0
  34. data/app/models/spree/digital.rb +2 -0
  35. data/app/models/spree/digital_link.rb +2 -0
  36. data/app/models/spree/export.rb +3 -2
  37. data/app/models/spree/fulfilment_changer.rb +8 -2
  38. data/app/models/spree/gateway/bogus.rb +60 -0
  39. data/app/models/spree/gateway_customer.rb +2 -0
  40. data/app/models/spree/gift_card.rb +2 -0
  41. data/app/models/spree/gift_card_batch.rb +2 -0
  42. data/app/models/spree/image.rb +18 -0
  43. data/app/models/spree/import.rb +5 -3
  44. data/app/models/spree/import_mapping.rb +2 -0
  45. data/app/models/spree/import_row.rb +2 -0
  46. data/app/models/spree/import_schemas/customers.rb +21 -0
  47. data/app/models/spree/imports/customers.rb +9 -0
  48. data/app/models/spree/integration.rb +2 -0
  49. data/app/models/spree/inventory_unit.rb +2 -0
  50. data/app/models/spree/invitation.rb +6 -6
  51. data/app/models/spree/legacy_admin_user.rb +31 -0
  52. data/app/models/spree/legacy_user.rb +19 -1
  53. data/app/models/spree/line_item.rb +15 -4
  54. data/app/models/spree/log_entry.rb +2 -0
  55. data/app/models/spree/market.rb +83 -0
  56. data/app/models/spree/market_country.rb +25 -0
  57. data/app/models/spree/metafield.rb +2 -0
  58. data/app/models/spree/metafield_definition.rb +2 -0
  59. data/app/models/spree/newsletter_subscriber.rb +2 -0
  60. data/app/models/spree/option_type.rb +2 -0
  61. data/app/models/spree/option_value.rb +2 -0
  62. data/app/models/spree/order/address_book.rb +2 -1
  63. data/app/models/spree/order.rb +73 -45
  64. data/app/models/spree/payment.rb +6 -1
  65. data/app/models/spree/payment_capture_event.rb +2 -0
  66. data/app/models/spree/payment_method.rb +59 -1
  67. data/app/models/spree/payment_session.rb +109 -0
  68. data/app/models/spree/payment_sessions/bogus.rb +4 -0
  69. data/app/models/spree/payment_setup_session.rb +79 -0
  70. data/app/models/spree/payment_source.rb +2 -0
  71. data/app/models/spree/permission_sets/default_customer.rb +19 -0
  72. data/app/models/spree/policy.rb +2 -0
  73. data/app/models/spree/post.rb +2 -0
  74. data/app/models/spree/post_category.rb +2 -0
  75. data/app/models/spree/price.rb +26 -2
  76. data/app/models/spree/price_list.rb +2 -0
  77. data/app/models/spree/price_rule.rb +2 -0
  78. data/app/models/spree/price_rules/market_rule.rb +19 -0
  79. data/app/models/spree/product.rb +41 -29
  80. data/app/models/spree/promotion/rules/country.rb +1 -1
  81. data/app/models/spree/promotion.rb +3 -1
  82. data/app/models/spree/promotion_action.rb +2 -0
  83. data/app/models/spree/promotion_category.rb +2 -0
  84. data/app/models/spree/promotion_rule.rb +2 -0
  85. data/app/models/spree/prototype.rb +2 -0
  86. data/app/models/spree/refund.rb +2 -0
  87. data/app/models/spree/refund_reason.rb +2 -0
  88. data/app/models/spree/reimbursement/credit.rb +2 -0
  89. data/app/models/spree/reimbursement.rb +2 -0
  90. data/app/models/spree/reimbursement_type.rb +2 -0
  91. data/app/models/spree/report.rb +3 -1
  92. data/app/models/spree/return_authorization.rb +2 -0
  93. data/app/models/spree/return_authorization_reason.rb +2 -0
  94. data/app/models/spree/return_item.rb +2 -0
  95. data/app/models/spree/role.rb +2 -0
  96. data/app/models/spree/shipment.rb +11 -1
  97. data/app/models/spree/shipping_category.rb +2 -0
  98. data/app/models/spree/shipping_method.rb +2 -0
  99. data/app/models/spree/shipping_method_category.rb +2 -0
  100. data/app/models/spree/shipping_rate.rb +2 -0
  101. data/app/models/spree/state_change.rb +2 -0
  102. data/app/models/spree/stock_item.rb +2 -0
  103. data/app/models/spree/stock_location.rb +2 -0
  104. data/app/models/spree/stock_movement.rb +2 -0
  105. data/app/models/spree/stock_transfer.rb +2 -1
  106. data/app/models/spree/store.rb +110 -179
  107. data/app/models/spree/store_credit.rb +2 -0
  108. data/app/models/spree/store_credit_category.rb +2 -0
  109. data/app/models/spree/store_credit_event.rb +2 -0
  110. data/app/models/spree/store_credit_type.rb +2 -0
  111. data/app/models/spree/store_product.rb +2 -0
  112. data/app/models/spree/tax_category.rb +2 -0
  113. data/app/models/spree/tax_rate.rb +2 -0
  114. data/app/models/spree/taxon.rb +4 -1
  115. data/app/models/spree/taxon_image.rb +8 -0
  116. data/app/models/spree/taxon_rule.rb +2 -0
  117. data/app/models/spree/taxonomy.rb +2 -0
  118. data/app/models/spree/user_identity.rb +81 -0
  119. data/app/models/spree/variant.rb +13 -3
  120. data/app/models/spree/webhook_delivery.rb +2 -0
  121. data/app/models/spree/webhook_endpoint.rb +2 -0
  122. data/app/models/spree/wished_item.rb +15 -0
  123. data/app/models/spree/wishlist.rb +2 -8
  124. data/app/models/spree/zone.rb +2 -6
  125. data/app/presenters/spree/filters/price_presenter.rb +1 -0
  126. data/app/presenters/spree/filters/price_range_presenter.rb +1 -0
  127. data/app/presenters/spree/filters/quantified_price_range_presenter.rb +1 -0
  128. data/app/presenters/spree/product_summary_presenter.rb +1 -0
  129. data/app/services/spree/addresses/helper.rb +22 -3
  130. data/app/services/spree/cart/associate.rb +19 -6
  131. data/app/services/spree/checkout/select_shipping_method.rb +13 -1
  132. data/app/services/spree/classifications/reposition.rb +5 -0
  133. data/app/services/spree/imports/row_processors/customer.rb +70 -0
  134. data/app/services/spree/orders/approve.rb +5 -3
  135. data/app/services/spree/orders/cancel.rb +9 -4
  136. data/app/services/spree/products/prepare_nested_attributes.rb +1 -1
  137. data/app/services/spree/sample_data/import_runner.rb +54 -0
  138. data/app/services/spree/sample_data/loader.rb +78 -0
  139. data/app/services/spree/seeds/admin_user.rb +2 -3
  140. data/app/services/spree/seeds/all.rb +1 -0
  141. data/app/services/spree/seeds/api_keys.rb +16 -0
  142. data/app/services/spree/seeds/stores.rb +2 -4
  143. data/app/sorters/spree/orders/sort.rb +4 -0
  144. data/app/subscribers/spree/product_metrics_subscriber.rb +4 -4
  145. data/config/brakeman.ignore +120 -0
  146. data/config/locales/en.yml +20 -1
  147. data/db/migrate/20250923141900_create_spree_user_identities.rb +17 -0
  148. data/db/migrate/20260123000000_create_spree_api_keys.rb +19 -0
  149. data/db/migrate/20260131000000_add_thumbnail_id_to_spree_variants_and_products.rb +9 -0
  150. data/db/migrate/20260213000000_create_spree_payment_sessions.rb +27 -0
  151. data/db/migrate/20260218000000_create_spree_payment_setup_sessions.rb +24 -0
  152. data/db/migrate/20260220000000_create_spree_markets.rb +29 -0
  153. data/db/sample_data/customers.csv +21 -0
  154. data/db/sample_data/metafield_definitions.rb +7 -0
  155. data/db/sample_data/orders.rb +131 -0
  156. data/db/sample_data/payment_methods.rb +17 -0
  157. data/db/sample_data/posts.rb +7 -0
  158. data/db/sample_data/products.csv +1083 -0
  159. data/db/sample_data/promotions.rb +8 -0
  160. data/db/sample_data/shipping_methods.rb +39 -0
  161. data/lib/generators/spree/authentication/devise/devise_generator.rb +2 -2
  162. data/lib/generators/spree/authentication/dummy/dummy_generator.rb +54 -0
  163. data/lib/generators/spree/authentication/dummy/templates/authentication_helpers.rb.tt +52 -0
  164. data/lib/generators/spree/authentication/dummy/templates/create_spree_admin_users.rb.tt +33 -0
  165. data/lib/generators/spree/dummy/dummy_generator.rb +1 -1
  166. data/lib/spree/core/configuration.rb +1 -0
  167. data/lib/spree/core/controller_helpers/common.rb +6 -0
  168. data/lib/spree/core/controller_helpers/currency.rb +5 -0
  169. data/lib/spree/core/controller_helpers/order.rb +5 -1
  170. data/lib/spree/core/controller_helpers/store.rb +1 -1
  171. data/lib/spree/core/dependencies.rb +1 -1
  172. data/lib/spree/core/engine.rb +17 -4
  173. data/lib/spree/core/pricing/context.rb +6 -3
  174. data/lib/spree/core/product_filters.rb +14 -0
  175. data/lib/spree/core/query_filters/comparable.rb +4 -0
  176. data/lib/spree/core/query_filters/text.rb +4 -0
  177. data/lib/spree/core/version.rb +1 -1
  178. data/lib/spree/core.rb +4 -4
  179. data/lib/spree/database_type_utilities.rb +7 -0
  180. data/lib/spree/events.rb +17 -10
  181. data/lib/spree/money.rb +2 -9
  182. data/lib/spree/permitted_attributes.rb +19 -7
  183. data/lib/spree/testing_support/capybara_config.rb +2 -2
  184. data/lib/spree/testing_support/common_rake.rb +15 -4
  185. data/lib/spree/testing_support/factories/api_key_factory.rb +19 -0
  186. data/lib/spree/testing_support/factories/custom_domain_factory.rb +7 -5
  187. data/lib/spree/testing_support/factories/import_factory.rb +12 -0
  188. data/lib/spree/testing_support/factories/market_factory.rb +35 -0
  189. data/lib/spree/testing_support/factories/order_factory.rb +3 -1
  190. data/lib/spree/testing_support/factories/payment_method_factory.rb +2 -0
  191. data/lib/spree/testing_support/factories/payment_session_factory.rb +47 -0
  192. data/lib/spree/testing_support/factories/payment_setup_session_factory.rb +31 -0
  193. data/lib/spree/testing_support/factories/price_rule_factory.rb +10 -0
  194. data/lib/spree/testing_support/factories/user_identity_factory.rb +15 -0
  195. data/lib/spree/testing_support/store.rb +3 -2
  196. data/lib/spree/webhooks.rb +7 -7
  197. data/lib/tasks/images.rake +20 -0
  198. data/lib/tasks/markets.rake +40 -0
  199. data/lib/tasks/sample_data.rake +15 -0
  200. data/spec/fixtures/files/customers_import.csv +4 -0
  201. metadata +99 -59
  202. data/LICENSE.md +0 -57
  203. data/app/finders/spree/stores/find_current.rb +0 -28
  204. data/app/models/spree/custom_domain.rb +0 -59
  205. data/app/serializers/spree/events/asset_serializer.rb +0 -22
  206. data/app/serializers/spree/events/base_serializer.rb +0 -61
  207. data/app/serializers/spree/events/customer_return_serializer.rb +0 -20
  208. data/app/serializers/spree/events/digital_link_serializer.rb +0 -20
  209. data/app/serializers/spree/events/digital_serializer.rb +0 -18
  210. data/app/serializers/spree/events/export_serializer.rb +0 -22
  211. data/app/serializers/spree/events/gift_card_batch_serializer.rb +0 -24
  212. data/app/serializers/spree/events/gift_card_serializer.rb +0 -29
  213. data/app/serializers/spree/events/image_serializer.rb +0 -9
  214. data/app/serializers/spree/events/import_row_serializer.rb +0 -23
  215. data/app/serializers/spree/events/import_serializer.rb +0 -24
  216. data/app/serializers/spree/events/invitation_serializer.rb +0 -28
  217. data/app/serializers/spree/events/line_item_serializer.rb +0 -31
  218. data/app/serializers/spree/events/newsletter_subscriber_serializer.rb +0 -21
  219. data/app/serializers/spree/events/order_serializer.rb +0 -39
  220. data/app/serializers/spree/events/payment_serializer.rb +0 -24
  221. data/app/serializers/spree/events/post_category_serializer.rb +0 -20
  222. data/app/serializers/spree/events/post_serializer.rb +0 -26
  223. data/app/serializers/spree/events/price_serializer.rb +0 -22
  224. data/app/serializers/spree/events/product_serializer.rb +0 -24
  225. data/app/serializers/spree/events/promotion_serializer.rb +0 -32
  226. data/app/serializers/spree/events/refund_serializer.rb +0 -23
  227. data/app/serializers/spree/events/reimbursement_serializer.rb +0 -22
  228. data/app/serializers/spree/events/report_serializer.rb +0 -23
  229. data/app/serializers/spree/events/return_authorization_serializer.rb +0 -22
  230. data/app/serializers/spree/events/return_item_serializer.rb +0 -27
  231. data/app/serializers/spree/events/shipment_serializer.rb +0 -24
  232. data/app/serializers/spree/events/stock_item_serializer.rb +0 -22
  233. data/app/serializers/spree/events/stock_movement_serializer.rb +0 -22
  234. data/app/serializers/spree/events/stock_transfer_serializer.rb +0 -22
  235. data/app/serializers/spree/events/store_credit_serializer.rb +0 -30
  236. data/app/serializers/spree/events/user_serializer.rb +0 -18
  237. data/app/serializers/spree/events/variant_serializer.rb +0 -34
  238. data/app/serializers/spree/events/wished_item_serializer.rb +0 -20
  239. data/app/serializers/spree/events/wishlist_serializer.rb +0 -22
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2b1ea480e8607e801d3c45d8decf522c829003eb3fa6c1f8f92b9185a4bfefec
4
- data.tar.gz: 552fb0686b6da4a560dd47144d52079d1ab73c270a9e4c9093e62b9e556ef74f
3
+ metadata.gz: 6991203c2954ac7663c4dfadd8362bee850faf1a1888166cd9c7ed0100167398
4
+ data.tar.gz: ec6feab9591d268f2a6f3efd7d5a1557e383e600fb32088c3e09e03abd937ba1
5
5
  SHA512:
6
- metadata.gz: 1ed2cab50fb7c122df6565b931324ea7ba307c8580a98ac8f9e2e400140df75092ed417db47311a6d3ec01985a6696cfbce1d3e3fa7917c329646b13ef51b095
7
- data.tar.gz: 34bd9929ed9e4a962696b2be41a5bb0da2c330fd12d1cbff89e34fa78cdceaa84d6b58cb7ae321d1868864c2888eccd752cd0bef7dbd9cd473aa68490619c2f6
6
+ metadata.gz: 2a58de3390d7466d547af3ff46f6d661776e6b4c9790d02da9d7d90c0b96883647dcea51fb27f75110e1083c8e9dc7075b6bbee49499bfd189ce39bb7235362d
7
+ data.tar.gz: 37fcc6d23c1e9ebf36314be3b85aea5c612eb7eb75a5fa0c2bd10f4c18ff1877298554f45fa80f8d6b841670e5004c1d953df068bebc2a45b59c4495edc10c2e
@@ -3,11 +3,13 @@ module Spree
3
3
  class FindComplete
4
4
  include Spree::Orders::FinderHelper
5
5
 
6
- attr_reader :user, :number, :token, :store, :email
6
+ attr_reader :user, :number, :prefix_id, :param, :token, :store, :email
7
7
 
8
- def initialize(user: nil, number: nil, token: nil, store: nil, email: nil)
8
+ def initialize(user: nil, number: nil, prefix_id: nil, param: nil, token: nil, store: nil, email: nil)
9
9
  @user = user
10
10
  @number = number
11
+ @prefix_id = prefix_id
12
+ @param = param
11
13
  @token = token
12
14
  @store = store
13
15
  @email = email
@@ -16,6 +18,8 @@ module Spree
16
18
  def execute
17
19
  orders = by_user(scope)
18
20
  orders = by_number(orders)
21
+ orders = by_prefix_id(orders)
22
+ orders = by_param(orders)
19
23
  orders = by_token(orders)
20
24
  orders = by_store(orders)
21
25
  orders = by_email(orders)
@@ -37,6 +41,14 @@ module Spree
37
41
  number.present?
38
42
  end
39
43
 
44
+ def prefix_id?
45
+ prefix_id.present?
46
+ end
47
+
48
+ def param?
49
+ param.present?
50
+ end
51
+
40
52
  def token?
41
53
  token.present?
42
54
  end
@@ -61,6 +73,25 @@ module Spree
61
73
  orders.where(number: number)
62
74
  end
63
75
 
76
+ def by_prefix_id(orders)
77
+ return orders unless prefix_id?
78
+
79
+ decoded = Spree::Order.decode_prefixed_id(prefix_id)
80
+ orders.where(id: decoded)
81
+ end
82
+
83
+ # Find by param - tries prefixed ID first, then number for backwards compatibility
84
+ def by_param(orders)
85
+ return orders unless param?
86
+
87
+ decoded = Spree::Order.decode_prefixed_id(param)
88
+ if decoded
89
+ orders.where(id: decoded)
90
+ else
91
+ orders.where(number: param)
92
+ end
93
+ end
94
+
64
95
  def by_token(orders)
65
96
  return orders unless token?
66
97
 
@@ -0,0 +1,17 @@
1
+ module Spree
2
+ module Stores
3
+ class FindDefault
4
+ def initialize(scope: nil, url: nil)
5
+ @scope = scope || Spree::Store
6
+ end
7
+
8
+ def execute
9
+ store = @scope.where(default: true).first || @scope.first
10
+ return if store.nil?
11
+
12
+ Spree::Current.store = store
13
+ store
14
+ end
15
+ end
16
+ end
17
+ end
@@ -2,6 +2,7 @@ module Spree
2
2
  module Variants
3
3
  class VisibleFinder
4
4
  def initialize(scope:, current_currency:)
5
+ Spree::Deprecation.warn('Spree::Variants::VisibleFinder is deprecated and will be removed in Spree 5.5.')
5
6
  @scope = scope
6
7
  @current_currency = current_currency
7
8
  end
@@ -4,8 +4,7 @@ module Spree
4
4
  def address_form_countries_states_cache_key
5
5
  @address_form_countries_states_cache_key ||= [
6
6
  I18n.locale,
7
- current_store.cache_key_with_version,
8
- current_store.checkout_zone&.cache_key_with_version
7
+ current_store.cache_key_with_version
9
8
  ].compact
10
9
  end
11
10
 
@@ -134,10 +134,10 @@ module Spree
134
134
 
135
135
  base_url = if options[:relative]
136
136
  ''
137
- elsif store.formatted_custom_domain.blank?
138
- store.formatted_url
139
- else
137
+ elsif store.respond_to?(:formatted_custom_domain) && store.formatted_custom_domain.present?
140
138
  store.formatted_custom_domain
139
+ else
140
+ store.formatted_url
141
141
  end
142
142
 
143
143
  localize = if options[:locale].present?
@@ -204,7 +204,8 @@ module Spree
204
204
  end
205
205
 
206
206
  def maximum_quantity
207
- Spree::DatabaseTypeUtilities.maximum_value_for(:integer)
207
+ Spree::Deprecation.warn('BaseHelper#maximum_quantity is deprecated and will be removed in Spree 5.5')
208
+ Spree::DatabaseTypeUtilities::INTEGER_MAX
208
209
  end
209
210
 
210
211
  def payment_method_icon_tag(payment_method, opts = {})
@@ -0,0 +1,9 @@
1
+ module Spree
2
+ class ApiKeyTouchJob < Spree::BaseJob
3
+ queue_as Spree.queues.api_keys
4
+
5
+ def perform(api_key_id)
6
+ Spree::ApiKey.find_by(id: api_key_id)&.touch(:last_used_at)
7
+ end
8
+ end
9
+ end
@@ -2,8 +2,19 @@ module Spree
2
2
  module AdminUserMethods
3
3
  extend ActiveSupport::Concern
4
4
 
5
+ include Spree::PrefixedId
6
+ include Spree::UserRoles
7
+ include Spree::RansackableAttributes
8
+
5
9
  included do
10
+ has_prefix_id :admin
11
+
12
+ has_person_name
13
+
14
+ normalizes :email, :first_name, :last_name, with: ->(value) { value&.to_s&.squish&.presence }
15
+
6
16
  # Associations
17
+ has_many :identities, class_name: 'Spree::UserIdentity', as: :user, dependent: :destroy
7
18
  has_many :canceled_orders, class_name: 'Spree::Order', foreign_key: :canceler_id
8
19
  has_many :created_orders, class_name: 'Spree::Order', foreign_key: :created_by_id
9
20
  has_many :approved_orders, class_name: 'Spree::Order', foreign_key: :approver_id
@@ -19,6 +30,27 @@ module Spree
19
30
  # Callbacks
20
31
  after_destroy :nullify_approver_id_in_approved_orders
21
32
  after_destroy :cleanup_admin_user_resources
33
+
34
+ # Attachments
35
+ has_one_attached :avatar, service: Spree.public_storage_service_name
36
+
37
+ #
38
+ # Attributes
39
+ #
40
+ attr_accessor :confirm_email
41
+
42
+ self.whitelisted_ransackable_associations = %w[spree_roles]
43
+ self.whitelisted_ransackable_attributes = %w[id email first_name last_name]
44
+ end
45
+
46
+ def can_be_deleted?
47
+ Spree::Store.current.users.where.not(id: id).exists?
48
+ end
49
+
50
+ # Returns the full name of the user
51
+ # @return [String]
52
+ def full_name
53
+ name&.full
22
54
  end
23
55
 
24
56
  private
@@ -3,9 +3,11 @@ module Spree
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  included do
6
- extend FriendlyId
7
-
8
- friendly_id :number
6
+ Spree::Deprecation.warn(
7
+ 'Spree::NumberAsParam is deprecated and will be removed in Spree 6.0. ' \
8
+ 'Models now use Spree::PrefixedId with Sqids-based prefixed_id method instead. ' \
9
+ 'This concern no longer provides any functionality and can be safely removed.'
10
+ )
9
11
  end
10
12
  end
11
13
  end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sqids'
4
+
5
+ module Spree
6
+ # Adds Stripe-style prefixed IDs to Spree models using Sqids encoding.
7
+ # IDs are computed on the fly from integer primary keys -- no database column needed.
8
+ #
9
+ # e.g., Product with id=12345 -> "prod_86Rf07xd4z"
10
+ #
11
+ # class Product < Spree.base_class
12
+ # has_prefix_id :prod
13
+ # end
14
+ module PrefixedId
15
+ extend ActiveSupport::Concern
16
+
17
+ SQIDS = Sqids.new(min_length: 10)
18
+
19
+ included do
20
+ class_attribute :_prefix_id_prefix, instance_writer: false
21
+ end
22
+
23
+ # Returns the Stripe-style prefixed ID, or nil for unsaved records.
24
+ def prefixed_id
25
+ return nil unless id.present?
26
+
27
+ "#{self.class._prefix_id_prefix}_#{Spree::PrefixedId::SQIDS.encode([id])}"
28
+ end
29
+
30
+ # Use prefixed_id for URL params when available.
31
+ # Skip if FriendlyId is used (it has its own to_param using slug).
32
+ def to_param
33
+ return super if self.class.respond_to?(:friendly_id_config)
34
+ return super unless self.class._prefix_id_prefix.present?
35
+
36
+ prefixed_id.presence || super
37
+ end
38
+
39
+ class_methods do
40
+ def has_prefix_id(prefix)
41
+ self._prefix_id_prefix = prefix.to_s
42
+ end
43
+
44
+ def find_by_prefix_id!(prefixed_id)
45
+ decoded = decode_prefixed_id(prefixed_id)
46
+ raise ActiveRecord::RecordNotFound.new("Couldn't find #{name} with prefixed id=#{prefixed_id}", name) unless decoded
47
+
48
+ find(decoded)
49
+ end
50
+
51
+ def find_by_prefix_id(prefixed_id)
52
+ decoded = decode_prefixed_id(prefixed_id)
53
+ return nil unless decoded
54
+
55
+ find_by(id: decoded)
56
+ end
57
+
58
+ # Decode a prefixed ID string (e.g., "prod_86Rf07xd4z") to the integer primary key.
59
+ def decode_prefixed_id(prefixed_id_string)
60
+ return nil if prefixed_id_string.blank?
61
+
62
+ parts = prefixed_id_string.to_s.split('_', 2)
63
+ return nil if parts.length != 2
64
+
65
+ _prefix, encoded = parts
66
+ ids = Spree::PrefixedId::SQIDS.decode(encoded)
67
+ ids.first
68
+ end
69
+
70
+ # Find by prefixed ID first, falling back to integer id for backwards compatibility.
71
+ def find_by_param(param)
72
+ return nil if param.blank?
73
+
74
+ find_by_prefix_id(param) || (find_by(id: param) if param.to_s.match?(/\A\d+\z/))
75
+ end
76
+
77
+ def find_by_param!(param)
78
+ find_by_param(param) || raise(ActiveRecord::RecordNotFound.new("Couldn't find #{name} with param=#{param}", name))
79
+ end
80
+ end
81
+ end
82
+ end
@@ -126,9 +126,8 @@ module Spree
126
126
  #
127
127
  # SELECT COUNT(*) ...
128
128
  add_search_scope :in_taxon do |taxon|
129
- includes(:classifications).
130
- where('spree_products_taxons.taxon_id' => taxon.cached_self_and_descendants_ids).
131
- order('spree_products_taxons.position ASC')
129
+ joins(:classifications).
130
+ where("#{Classification.table_name}.taxon_id" => taxon.cached_self_and_descendants_ids).distinct
132
131
  end
133
132
 
134
133
  # This scope selects products in all taxons AND all its descendants
@@ -205,15 +204,33 @@ module Spree
205
204
  return Product.group("#{Spree::Product.table_name}.id").none if option_type_id.blank?
206
205
 
207
206
  group("#{Spree::Product.table_name}.id").
208
- joins(variants_including_master: :option_values).
207
+ joins(variants: :option_values).
209
208
  where(Spree::OptionValue.table_name => { name: value, option_type_id: option_type_id })
210
209
  end
211
210
 
211
+ # Filters products by option value IDs (prefix IDs like 'optval_xxx')
212
+ # Accepts an array of option value IDs
213
+ add_search_scope :with_option_value_ids do |*ids|
214
+ ids = ids.flatten.compact
215
+ return none if ids.empty?
216
+
217
+ # Handle prefixed IDs (optval_xxx) by decoding to actual IDs
218
+ actual_ids = ids.map do |id|
219
+ id.to_s.include?('_') ? OptionValue.decode_prefixed_id(id) : id
220
+ end.compact
221
+
222
+ return none if actual_ids.empty?
223
+
224
+ group("#{Spree::Product.table_name}.id").
225
+ joins(variants: :option_values).
226
+ where(Spree::OptionValue.table_name => { id: actual_ids })
227
+ end
228
+
212
229
  # Finds all products which have either:
213
230
  # 1) have an option value with the name matching the one given
214
231
  # 2) have a product property with a value matching the one given
215
232
  add_search_scope :with do |value|
216
- includes(variants_including_master: :option_values).
233
+ includes(variants: :option_values).
217
234
  includes(:product_properties).
218
235
  where("#{OptionValue.table_name}.name = ? OR #{ProductProperty.table_name}.value = ?", value, value)
219
236
  end
@@ -288,11 +305,8 @@ module Spree
288
305
 
289
306
  # Can't use add_search_scope for this as it needs a default argument
290
307
  def self.available(available_on = nil, currency = nil)
291
- scope = if available_on
292
- not_discontinued.where("#{Product.quoted_table_name}.available_on <= ?", available_on)
293
- else
294
- not_discontinued.where(status: 'active')
295
- end
308
+ scope = not_discontinued.where(status: 'active')
309
+ scope = scope.where("#{Product.quoted_table_name}.available_on <= ?", available_on) if available_on
296
310
 
297
311
  unless Spree::Config.show_products_without_price
298
312
  currency ||= Spree::Store.default.default_currency
@@ -336,16 +350,17 @@ module Spree
336
350
  # @param order_direction [Symbol] :desc (default) or :asc
337
351
  # @return [ActiveRecord::Relation]
338
352
  add_search_scope :by_best_selling do |order_direction = :desc|
339
- order_dir = order_direction == :desc ? 'DESC' : 'ASC'
340
353
  store_id = Spree::Current.store&.id
354
+ sp_table = StoreProduct.arel_table
355
+ products_table = Product.arel_table
356
+
357
+ conditions = sp_table[:product_id].eq(products_table[:id]).and(sp_table[:store_id].eq(store_id))
358
+
359
+ units_sold = Arel::Nodes::NamedFunction.new('COALESCE', [sp_table.project(sp_table[:units_sold_count]).where(conditions), 0])
360
+ revenue = Arel::Nodes::NamedFunction.new('COALESCE', [sp_table.project(sp_table[:revenue]).where(conditions), 0])
341
361
 
342
- joins(:store_products)
343
- .where(StoreProduct.table_name => { store_id: store_id })
344
- .select("#{Product.table_name}.*",
345
- "#{StoreProduct.table_name}.units_sold_count",
346
- "#{StoreProduct.table_name}.revenue")
347
- .order(Arel.sql("#{StoreProduct.table_name}.units_sold_count #{order_dir}"))
348
- .order(Arel.sql("#{StoreProduct.table_name}.revenue #{order_dir}"))
362
+ order_dir = order_direction == :desc ? :desc : :asc
363
+ order(units_sold.send(order_dir)).order(revenue.send(order_dir))
349
364
  end
350
365
 
351
366
  # .search_by_name
@@ -4,8 +4,14 @@ module Spree
4
4
  # Concern for models that publish events.
5
5
  #
6
6
  # This concern is included in Spree::Base, so all Spree models
7
- # can emit events. Event payloads are generated using dedicated
8
- # serializers from the Spree::Events namespace.
7
+ # can emit events. Event payloads are generated using V3 API serializers
8
+ # resolved by convention: Spree::Order → Spree::Api::V3::OrderSerializer.
9
+ #
10
+ # Models without a V3 serializer get a minimal fallback payload:
11
+ # { id: prefixed_id, created_at: ..., updated_at: ... }
12
+ #
13
+ # STI models (e.g., Spree::Exports::Products) can override
14
+ # event_serializer_class to point to the parent serializer.
9
15
  #
10
16
  # @example Disabling events for a specific model
11
17
  # class Spree::LogEntry < Spree.base_class
@@ -20,46 +26,16 @@ module Spree
20
26
  # end
21
27
  # end
22
28
  #
23
- # @example Creating an event serializer (required for each model that publishes events)
24
- # # app/serializers/spree/events/order_serializer.rb
25
- # class Spree::Events::OrderSerializer < Spree::Events::BaseSerializer
26
- # protected
27
- #
28
- # def attributes
29
- # {
30
- # id: resource.id,
31
- # number: resource.number,
32
- # state: resource.state.to_s,
33
- # # ... other attributes
34
- # }
29
+ # @example Overriding the serializer for STI models
30
+ # class Spree::Export < Spree.base_class
31
+ # def event_serializer_class
32
+ # Spree::Api::V3::ExportSerializer
35
33
  # end
36
34
  # end
37
35
  #
38
36
  module Publishable
39
37
  extend ActiveSupport::Concern
40
38
 
41
- # Error raised when a model tries to publish an event but has no serializer defined
42
- class MissingSerializerError < StandardError
43
- def initialize(model_class)
44
- serializer_name = "Spree::Events::#{model_class.name.demodulize}Serializer"
45
- super(
46
- "Missing event serializer for #{model_class.name}. " \
47
- "Please create #{serializer_name} that inherits from Spree::Events::BaseSerializer. " \
48
- "Example:\n\n" \
49
- " class #{serializer_name} < Spree::Events::BaseSerializer\n" \
50
- " protected\n\n" \
51
- " def attributes\n" \
52
- " {\n" \
53
- " id: resource.id,\n" \
54
- " # add other attributes here\n" \
55
- " updated_at: timestamp(resource.updated_at)\n" \
56
- " }\n" \
57
- " end\n" \
58
- " end"
59
- )
60
- end
61
- end
62
-
63
39
  included do
64
40
  class_attribute :publish_events, default: true
65
41
  class_attribute :lifecycle_events_enabled, default: false
@@ -168,36 +144,44 @@ module Spree
168
144
 
169
145
  # Get the payload for events
170
146
  #
171
- # Uses dedicated event serializer (Spree::Events::ModelSerializer).
172
- # Raises MissingSerializerError if no serializer is defined.
147
+ # Uses the V3 serializer resolved by convention.
148
+ # Falls back to a minimal payload if no serializer is found.
173
149
  #
174
150
  # @return [Hash]
175
- # @raise [MissingSerializerError] if no serializer is defined for this model
176
151
  def event_payload
177
152
  serializer = event_serializer_class
178
153
 
179
- raise MissingSerializerError, self.class unless serializer
154
+ unless serializer
155
+ return {
156
+ id: respond_to?(:prefixed_id) ? prefixed_id : id,
157
+ created_at: created_at&.iso8601,
158
+ updated_at: updated_at&.iso8601
159
+ }
160
+ end
180
161
 
181
- serializer.serialize(self, event_context)
162
+ # Use as_json to ensure all values are JSON-safe primitives.
163
+ # Alba's to_h can return raw Ruby objects (e.g., Spree::Money) which
164
+ # ActiveJob cannot serialize for async event subscribers.
165
+ serializer.new(self, params: event_serializer_params).to_h.as_json
182
166
  end
183
167
 
184
168
  # Find the event serializer class for this model
185
169
  #
186
- # Looks for Spree::Events::ModelNameSerializer (e.g., Spree::Events::OrderSerializer)
187
- # Also walks up the class hierarchy to find a serializer for parent classes.
188
- # This allows subclasses like Spree::Exports::Products to use ExportSerializer.
170
+ # Looks for Spree::Api::V3::ModelNameSerializer by convention.
171
+ # Walks up the class hierarchy to support STI models.
172
+ #
173
+ # Models can override this method to specify a custom serializer,
174
+ # which is useful for STI models like Export, Import, Report.
189
175
  #
190
176
  # @return [Class, nil] The serializer class or nil if not found
191
177
  def event_serializer_class
192
178
  return nil unless self.class.name
193
179
 
194
- # Try this class and walk up the hierarchy
195
180
  klass = self.class
196
181
  while klass && klass != Object && klass != BasicObject
197
182
  class_name = klass.name&.demodulize
198
- # Skip looking for BaseSerializer - that's the parent class for all serializers
199
183
  if class_name.present? && class_name != 'Base'
200
- serializer = "Spree::Events::#{class_name}Serializer".safe_constantize
184
+ serializer = "Spree::Api::V3::#{class_name}Serializer".safe_constantize
201
185
  return serializer if serializer
202
186
  end
203
187
 
@@ -227,6 +211,22 @@ module Spree
227
211
 
228
212
  private
229
213
 
214
+ # Build params for V3 serializers
215
+ #
216
+ # @return [Hash]
217
+ def event_serializer_params
218
+ store = respond_to?(:store) ? self.store : nil
219
+ store ||= Spree::Current.store
220
+
221
+ {
222
+ store: store,
223
+ currency: Spree::Current.currency,
224
+ user: nil,
225
+ locale: nil,
226
+ includes: []
227
+ }
228
+ end
229
+
230
230
  def should_publish_events?
231
231
  self.class.publish_events && Spree::Events.enabled?
232
232
  end
@@ -1,23 +1,15 @@
1
1
  module Spree
2
- module MultiStoreResource
2
+ module StoreScopedResource
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  included do
6
6
  scope :for_store, ->(store) { joins(:stores).where(Store.table_name => { id: store.id }) }
7
7
 
8
8
  before_validation :set_default_store, if: :new_record?
9
-
10
- validate :must_have_one_store, unless: :disable_store_presence_validation?
11
9
  end
12
10
 
13
11
  protected
14
12
 
15
- def must_have_one_store
16
- return if stores.any?
17
-
18
- errors.add(:stores, Spree.t(:must_have_one_store))
19
- end
20
-
21
13
  def set_default_store
22
14
  return if disable_store_presence_validation?
23
15
  return if stores.any?
@@ -25,7 +17,6 @@ module Spree
25
17
  stores << Spree::Store.default
26
18
  end
27
19
 
28
- # this can be overridden on model basis
29
20
  def disable_store_presence_validation?
30
21
  Spree::Config[:disable_store_presence_validation]
31
22
  end