spree_core 5.3.5 → 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 (242) 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/jobs/spree/images/save_from_url_job.rb +23 -47
  9. data/app/models/concerns/spree/admin_user_methods.rb +32 -0
  10. data/app/models/concerns/spree/number_as_param.rb +5 -3
  11. data/app/models/concerns/spree/prefixed_id.rb +82 -0
  12. data/app/models/concerns/spree/product_scopes.rb +33 -18
  13. data/app/models/concerns/spree/publishable.rb +47 -47
  14. data/app/models/concerns/spree/{multi_store_resource.rb → store_scoped_resource.rb} +1 -10
  15. data/app/models/concerns/spree/stores/markets.rb +124 -0
  16. data/app/models/concerns/spree/user_methods.rb +7 -7
  17. data/app/models/concerns/spree/user_payment_source.rb +2 -0
  18. data/app/models/concerns/spree/user_roles.rb +1 -0
  19. data/app/models/spree/ability.rb +13 -0
  20. data/app/models/spree/address.rb +30 -0
  21. data/app/models/spree/adjustment.rb +2 -0
  22. data/app/models/spree/api_key.rb +47 -0
  23. data/app/models/spree/asset.rb +2 -0
  24. data/app/models/spree/authentication/strategies/base_strategy.rb +55 -0
  25. data/app/models/spree/authentication/strategies/email_password_strategy.rb +47 -0
  26. data/app/models/spree/base.rb +1 -0
  27. data/app/models/spree/calculator.rb +2 -0
  28. data/app/models/spree/country.rb +56 -0
  29. data/app/models/spree/coupon_code.rb +2 -0
  30. data/app/models/spree/credit_card.rb +2 -0
  31. data/app/models/spree/current.rb +9 -4
  32. data/app/models/spree/customer_group.rb +2 -0
  33. data/app/models/spree/customer_return.rb +2 -0
  34. data/app/models/spree/data_feed.rb +2 -0
  35. data/app/models/spree/digital.rb +2 -0
  36. data/app/models/spree/digital_link.rb +2 -0
  37. data/app/models/spree/export.rb +3 -2
  38. data/app/models/spree/fulfilment_changer.rb +8 -2
  39. data/app/models/spree/gateway/bogus.rb +60 -0
  40. data/app/models/spree/gateway_customer.rb +2 -0
  41. data/app/models/spree/gift_card.rb +2 -0
  42. data/app/models/spree/gift_card_batch.rb +2 -4
  43. data/app/models/spree/image.rb +18 -0
  44. data/app/models/spree/import.rb +5 -3
  45. data/app/models/spree/import_mapping.rb +2 -0
  46. data/app/models/spree/import_row.rb +2 -0
  47. data/app/models/spree/import_schemas/customers.rb +21 -0
  48. data/app/models/spree/imports/customers.rb +9 -0
  49. data/app/models/spree/integration.rb +2 -0
  50. data/app/models/spree/inventory_unit.rb +2 -0
  51. data/app/models/spree/invitation.rb +6 -6
  52. data/app/models/spree/legacy_admin_user.rb +31 -0
  53. data/app/models/spree/legacy_user.rb +19 -1
  54. data/app/models/spree/line_item.rb +15 -4
  55. data/app/models/spree/log_entry.rb +2 -0
  56. data/app/models/spree/market.rb +83 -0
  57. data/app/models/spree/market_country.rb +25 -0
  58. data/app/models/spree/metafield.rb +2 -0
  59. data/app/models/spree/metafield_definition.rb +2 -0
  60. data/app/models/spree/newsletter_subscriber.rb +2 -0
  61. data/app/models/spree/option_type.rb +2 -0
  62. data/app/models/spree/option_value.rb +2 -0
  63. data/app/models/spree/order/address_book.rb +2 -1
  64. data/app/models/spree/order.rb +75 -47
  65. data/app/models/spree/payment.rb +6 -1
  66. data/app/models/spree/payment_capture_event.rb +2 -0
  67. data/app/models/spree/payment_method.rb +59 -1
  68. data/app/models/spree/payment_session.rb +109 -0
  69. data/app/models/spree/payment_sessions/bogus.rb +4 -0
  70. data/app/models/spree/payment_setup_session.rb +79 -0
  71. data/app/models/spree/payment_source.rb +2 -0
  72. data/app/models/spree/permission_sets/default_customer.rb +19 -0
  73. data/app/models/spree/policy.rb +2 -0
  74. data/app/models/spree/post.rb +2 -0
  75. data/app/models/spree/post_category.rb +2 -0
  76. data/app/models/spree/price.rb +26 -2
  77. data/app/models/spree/price_list.rb +2 -0
  78. data/app/models/spree/price_rule.rb +2 -0
  79. data/app/models/spree/price_rules/market_rule.rb +19 -0
  80. data/app/models/spree/product.rb +41 -29
  81. data/app/models/spree/promotion/rules/country.rb +1 -1
  82. data/app/models/spree/promotion.rb +3 -1
  83. data/app/models/spree/promotion_action.rb +2 -0
  84. data/app/models/spree/promotion_category.rb +2 -0
  85. data/app/models/spree/promotion_rule.rb +2 -0
  86. data/app/models/spree/prototype.rb +2 -0
  87. data/app/models/spree/refund.rb +2 -4
  88. data/app/models/spree/refund_reason.rb +2 -0
  89. data/app/models/spree/reimbursement/credit.rb +2 -0
  90. data/app/models/spree/reimbursement.rb +2 -0
  91. data/app/models/spree/reimbursement_type.rb +2 -0
  92. data/app/models/spree/report.rb +3 -1
  93. data/app/models/spree/return_authorization.rb +2 -0
  94. data/app/models/spree/return_authorization_reason.rb +2 -0
  95. data/app/models/spree/return_item.rb +2 -0
  96. data/app/models/spree/role.rb +2 -0
  97. data/app/models/spree/shipment.rb +11 -1
  98. data/app/models/spree/shipping_category.rb +2 -0
  99. data/app/models/spree/shipping_method.rb +2 -0
  100. data/app/models/spree/shipping_method_category.rb +2 -0
  101. data/app/models/spree/shipping_rate.rb +2 -0
  102. data/app/models/spree/state_change.rb +2 -0
  103. data/app/models/spree/stock_item.rb +2 -0
  104. data/app/models/spree/stock_location.rb +2 -0
  105. data/app/models/spree/stock_movement.rb +2 -0
  106. data/app/models/spree/stock_transfer.rb +2 -1
  107. data/app/models/spree/store.rb +110 -179
  108. data/app/models/spree/store_credit.rb +2 -4
  109. data/app/models/spree/store_credit_category.rb +2 -0
  110. data/app/models/spree/store_credit_event.rb +2 -0
  111. data/app/models/spree/store_credit_type.rb +2 -0
  112. data/app/models/spree/store_product.rb +2 -0
  113. data/app/models/spree/tax_category.rb +2 -0
  114. data/app/models/spree/tax_rate.rb +2 -0
  115. data/app/models/spree/taxon.rb +4 -1
  116. data/app/models/spree/taxon_image.rb +8 -0
  117. data/app/models/spree/taxon_rule.rb +2 -0
  118. data/app/models/spree/taxonomy.rb +2 -0
  119. data/app/models/spree/user_identity.rb +81 -0
  120. data/app/models/spree/variant.rb +13 -3
  121. data/app/models/spree/webhook_delivery.rb +2 -0
  122. data/app/models/spree/webhook_endpoint.rb +2 -17
  123. data/app/models/spree/wished_item.rb +15 -0
  124. data/app/models/spree/wishlist.rb +2 -8
  125. data/app/models/spree/zone.rb +2 -6
  126. data/app/presenters/spree/filters/price_presenter.rb +1 -0
  127. data/app/presenters/spree/filters/price_range_presenter.rb +1 -0
  128. data/app/presenters/spree/filters/quantified_price_range_presenter.rb +1 -0
  129. data/app/presenters/spree/product_summary_presenter.rb +1 -0
  130. data/app/services/spree/addresses/helper.rb +22 -3
  131. data/app/services/spree/cart/associate.rb +19 -6
  132. data/app/services/spree/checkout/select_shipping_method.rb +13 -1
  133. data/app/services/spree/classifications/reposition.rb +5 -0
  134. data/app/services/spree/data_feeds/google/required_attributes.rb +8 -3
  135. data/app/services/spree/gift_cards/apply.rb +4 -5
  136. data/app/services/spree/imports/row_processors/customer.rb +70 -0
  137. data/app/services/spree/orders/approve.rb +5 -3
  138. data/app/services/spree/orders/cancel.rb +9 -4
  139. data/app/services/spree/products/prepare_nested_attributes.rb +1 -1
  140. data/app/services/spree/sample_data/import_runner.rb +54 -0
  141. data/app/services/spree/sample_data/loader.rb +78 -0
  142. data/app/services/spree/seeds/admin_user.rb +2 -3
  143. data/app/services/spree/seeds/all.rb +1 -0
  144. data/app/services/spree/seeds/api_keys.rb +16 -0
  145. data/app/services/spree/seeds/stores.rb +2 -4
  146. data/app/sorters/spree/orders/sort.rb +4 -0
  147. data/app/subscribers/spree/product_metrics_subscriber.rb +4 -4
  148. data/config/brakeman.ignore +120 -0
  149. data/config/locales/en.yml +20 -5
  150. data/db/migrate/20250923141900_create_spree_user_identities.rb +17 -0
  151. data/db/migrate/20260123000000_create_spree_api_keys.rb +19 -0
  152. data/db/migrate/20260131000000_add_thumbnail_id_to_spree_variants_and_products.rb +9 -0
  153. data/db/migrate/20260213000000_create_spree_payment_sessions.rb +27 -0
  154. data/db/migrate/20260218000000_create_spree_payment_setup_sessions.rb +24 -0
  155. data/db/migrate/20260220000000_create_spree_markets.rb +29 -0
  156. data/db/sample_data/customers.csv +21 -0
  157. data/db/sample_data/metafield_definitions.rb +7 -0
  158. data/db/sample_data/orders.rb +131 -0
  159. data/db/sample_data/payment_methods.rb +17 -0
  160. data/db/sample_data/posts.rb +7 -0
  161. data/db/sample_data/products.csv +1083 -0
  162. data/db/sample_data/promotions.rb +8 -0
  163. data/db/sample_data/shipping_methods.rb +39 -0
  164. data/lib/generators/spree/authentication/devise/devise_generator.rb +2 -2
  165. data/lib/generators/spree/authentication/dummy/dummy_generator.rb +54 -0
  166. data/lib/generators/spree/authentication/dummy/templates/authentication_helpers.rb.tt +52 -0
  167. data/lib/generators/spree/authentication/dummy/templates/create_spree_admin_users.rb.tt +33 -0
  168. data/lib/generators/spree/dummy/dummy_generator.rb +1 -1
  169. data/lib/spree/core/configuration.rb +1 -1
  170. data/lib/spree/core/controller_helpers/common.rb +6 -0
  171. data/lib/spree/core/controller_helpers/currency.rb +5 -0
  172. data/lib/spree/core/controller_helpers/order.rb +5 -1
  173. data/lib/spree/core/controller_helpers/store.rb +1 -1
  174. data/lib/spree/core/dependencies.rb +1 -1
  175. data/lib/spree/core/engine.rb +17 -4
  176. data/lib/spree/core/pricing/context.rb +6 -3
  177. data/lib/spree/core/product_filters.rb +14 -0
  178. data/lib/spree/core/query_filters/comparable.rb +4 -0
  179. data/lib/spree/core/query_filters/text.rb +4 -0
  180. data/lib/spree/core/version.rb +1 -1
  181. data/lib/spree/core.rb +4 -4
  182. data/lib/spree/database_type_utilities.rb +7 -0
  183. data/lib/spree/events.rb +17 -10
  184. data/lib/spree/money.rb +2 -9
  185. data/lib/spree/permitted_attributes.rb +19 -7
  186. data/lib/spree/testing_support/capybara_config.rb +2 -2
  187. data/lib/spree/testing_support/common_rake.rb +15 -4
  188. data/lib/spree/testing_support/factories/api_key_factory.rb +19 -0
  189. data/lib/spree/testing_support/factories/custom_domain_factory.rb +7 -5
  190. data/lib/spree/testing_support/factories/import_factory.rb +12 -0
  191. data/lib/spree/testing_support/factories/market_factory.rb +35 -0
  192. data/lib/spree/testing_support/factories/order_factory.rb +3 -1
  193. data/lib/spree/testing_support/factories/payment_method_factory.rb +2 -0
  194. data/lib/spree/testing_support/factories/payment_session_factory.rb +47 -0
  195. data/lib/spree/testing_support/factories/payment_setup_session_factory.rb +31 -0
  196. data/lib/spree/testing_support/factories/price_rule_factory.rb +10 -0
  197. data/lib/spree/testing_support/factories/user_identity_factory.rb +15 -0
  198. data/lib/spree/testing_support/store.rb +3 -2
  199. data/lib/spree/webhooks.rb +7 -7
  200. data/lib/tasks/images.rake +20 -0
  201. data/lib/tasks/markets.rake +40 -0
  202. data/lib/tasks/sample_data.rake +15 -0
  203. data/spec/fixtures/files/customers_import.csv +4 -0
  204. metadata +88 -62
  205. data/LICENSE.md +0 -57
  206. data/app/finders/spree/stores/find_current.rb +0 -28
  207. data/app/models/spree/custom_domain.rb +0 -59
  208. data/app/serializers/spree/events/asset_serializer.rb +0 -22
  209. data/app/serializers/spree/events/base_serializer.rb +0 -61
  210. data/app/serializers/spree/events/customer_return_serializer.rb +0 -20
  211. data/app/serializers/spree/events/digital_link_serializer.rb +0 -20
  212. data/app/serializers/spree/events/digital_serializer.rb +0 -18
  213. data/app/serializers/spree/events/export_serializer.rb +0 -22
  214. data/app/serializers/spree/events/gift_card_batch_serializer.rb +0 -24
  215. data/app/serializers/spree/events/gift_card_serializer.rb +0 -29
  216. data/app/serializers/spree/events/image_serializer.rb +0 -9
  217. data/app/serializers/spree/events/import_row_serializer.rb +0 -23
  218. data/app/serializers/spree/events/import_serializer.rb +0 -24
  219. data/app/serializers/spree/events/invitation_serializer.rb +0 -28
  220. data/app/serializers/spree/events/line_item_serializer.rb +0 -31
  221. data/app/serializers/spree/events/newsletter_subscriber_serializer.rb +0 -21
  222. data/app/serializers/spree/events/order_serializer.rb +0 -39
  223. data/app/serializers/spree/events/payment_serializer.rb +0 -24
  224. data/app/serializers/spree/events/post_category_serializer.rb +0 -20
  225. data/app/serializers/spree/events/post_serializer.rb +0 -26
  226. data/app/serializers/spree/events/price_serializer.rb +0 -22
  227. data/app/serializers/spree/events/product_serializer.rb +0 -24
  228. data/app/serializers/spree/events/promotion_serializer.rb +0 -32
  229. data/app/serializers/spree/events/refund_serializer.rb +0 -23
  230. data/app/serializers/spree/events/reimbursement_serializer.rb +0 -22
  231. data/app/serializers/spree/events/report_serializer.rb +0 -23
  232. data/app/serializers/spree/events/return_authorization_serializer.rb +0 -22
  233. data/app/serializers/spree/events/return_item_serializer.rb +0 -27
  234. data/app/serializers/spree/events/shipment_serializer.rb +0 -24
  235. data/app/serializers/spree/events/stock_item_serializer.rb +0 -22
  236. data/app/serializers/spree/events/stock_movement_serializer.rb +0 -22
  237. data/app/serializers/spree/events/stock_transfer_serializer.rb +0 -22
  238. data/app/serializers/spree/events/store_credit_serializer.rb +0 -30
  239. data/app/serializers/spree/events/user_serializer.rb +0 -18
  240. data/app/serializers/spree/events/variant_serializer.rb +0 -34
  241. data/app/serializers/spree/events/wished_item_serializer.rb +0 -20
  242. data/app/serializers/spree/events/wishlist_serializer.rb +0 -22
@@ -2,9 +2,7 @@ require 'uri'
2
2
 
3
3
  module Spree
4
4
  class Store < Spree.base_class
5
- RESERVED_CODES = %w(
6
- admin default app api www cdn files assets checkout account auth login user
7
- )
5
+ has_prefix_id :store # Spree-specific: store
8
6
 
9
7
  include FriendlyId
10
8
  include Spree::TranslatableResource
@@ -12,6 +10,7 @@ module Spree
12
10
  include Spree::Metadata
13
11
  include Spree::Stores::Setup
14
12
  include Spree::Stores::Socials
13
+ include Spree::Stores::Markets
15
14
  include Spree::Security::Stores if defined?(Spree::Security::Stores)
16
15
  include Spree::UserManagement
17
16
 
@@ -19,7 +18,7 @@ module Spree
19
18
  # Magic methods
20
19
  #
21
20
  acts_as_paranoid
22
- friendly_id :slug_candidates, use: [:slugged, :history], slug_column: :code, routes: :normal
21
+ friendly_id :code, use: [:slugged], slug_column: :code, routes: :normal
23
22
 
24
23
  #
25
24
  # Translations
@@ -38,6 +37,7 @@ module Spree
38
37
  # Preferences
39
38
  #
40
39
  # general preferences
40
+ preference :admin_locale, :string
41
41
  preference :timezone, :string, default: Time.zone.name
42
42
  preference :weight_unit, :string, default: 'lb'
43
43
  preference :unit_system, :string, default: 'imperial'
@@ -101,9 +101,6 @@ module Spree
101
101
  has_many :reports, class_name: 'Spree::Report'
102
102
  has_many :exports, class_name: 'Spree::Export'
103
103
 
104
- has_many :custom_domains, class_name: 'Spree::CustomDomain', dependent: :destroy
105
- has_one :default_custom_domain, -> { where(default: true) }, class_name: 'Spree::CustomDomain'
106
-
107
104
  has_many :posts, class_name: 'Spree::Post', dependent: :destroy, inverse_of: :store
108
105
  has_many :post_categories, class_name: 'Spree::PostCategory', dependent: :destroy, inverse_of: :store
109
106
 
@@ -118,6 +115,9 @@ module Spree
118
115
 
119
116
  has_many :customer_groups, class_name: 'Spree::CustomerGroup', dependent: :destroy, inverse_of: :store
120
117
 
118
+ has_many :api_keys, class_name: 'Spree::ApiKey', dependent: :destroy
119
+
120
+
121
121
  #
122
122
  # ActionText
123
123
  #
@@ -130,20 +130,16 @@ module Spree
130
130
  #
131
131
  # Virtual attributes
132
132
  #
133
- attribute :import_products_from_store_id, :string, default: nil
134
- attribute :import_payment_methods_from_store_id, :string, default: nil
135
- attr_accessor :skip_validate_not_last
136
133
  store_accessor :private_metadata, :storefront_password
137
134
 
138
135
  #
139
136
  # Validations
140
137
  #
141
138
  with_options presence: true do
142
- validates :name, :url, :mail_from_address, :default_currency, :default_country, :code
139
+ validates :name, :url, :mail_from_address, :code
143
140
  end
144
141
  validates :preferred_digital_asset_authorized_clicks, numericality: { only_integer: true, greater_than: 0 }
145
142
  validates :preferred_digital_asset_authorized_days, numericality: { only_integer: true, greater_than: 0 }
146
- validates :code, uniqueness: { case_sensitive: false, conditions: -> { with_deleted } }, exclusion: RESERVED_CODES
147
143
  validates :mail_from_address, email: { allow_blank: false }
148
144
  # FIXME: we should remove this condition in v5
149
145
  if !ENV['SPREE_DISABLE_DB_CONNECTION'] &&
@@ -164,59 +160,64 @@ module Spree
164
160
 
165
161
  #
166
162
  # Callbacks
167
- before_validation :ensure_default_country
168
- before_validation :set_code, on: :create
169
- before_validation :set_url
163
+ before_validation :set_default_code, on: :create
170
164
  before_save :ensure_default_exists_and_is_unique
171
- before_save :ensure_supported_currencies, :ensure_supported_locales
165
+ after_create :ensure_default_market
172
166
  after_create :ensure_default_taxonomies_are_created
173
167
  after_create :ensure_default_automatic_taxons
174
168
  after_create :ensure_default_post_categories_are_created
175
- after_create :import_products_from_store, if: -> { import_products_from_store_id.present? }
176
- after_create :import_payment_methods_from_store, if: -> { import_payment_methods_from_store_id.present? }
177
169
  after_create :create_default_policies
178
- before_destroy :validate_not_last, unless: :skip_validate_not_last
179
- before_destroy :pass_default_flag_to_other_store
180
170
  after_commit :clear_cache
181
- after_commit :handle_code_changes, on: :update, if: -> { code_previously_changed? }
182
171
 
183
172
  #
184
173
  # Scopes
185
174
  #
186
175
  default_scope { order(created_at: :asc) }
187
- scope :by_custom_domain, ->(url) { left_joins(:custom_domains).where("#{Spree::CustomDomain.table_name}.url" => url) }
188
- scope :by_url, ->(url) { where(url: url).or(where("#{table_name}.url like ?", "%#{url}%")) }
189
176
 
190
177
  #
191
178
  # Delegations
192
179
  #
193
- delegate :iso, to: :default_country, prefix: true, allow_nil: true
194
180
 
195
- def self.current(url = nil)
196
- if url.present?
197
- Spree.current_store_finder.new(url: url).execute
198
- else
199
- Spree::Current.store
200
- end
181
+ def self.current(_url = nil)
182
+ Spree::Current.store
201
183
  end
202
184
 
203
- # FIXME: we need to drop `or_initialize` in v5
204
- # this behaviour is very buggy and unpredictable
185
+ # @deprecated The or_initialize behavior will be removed in Spree 5.5.
205
186
  def self.default
206
187
  Rails.cache.fetch('default_store') do
207
188
  # workaround for Mobility bug with first_or_initialize
208
189
  if where(default: true).any?
209
190
  where(default: true).first
210
191
  else
192
+ Spree::Deprecation.warn(
193
+ 'Spree::Store.default returning a new unpersisted store when no default store exists is deprecated ' \
194
+ 'and will be removed in Spree 5.5. Please ensure a default store is created before calling Store.default.'
195
+ )
211
196
  new(default: true)
212
197
  end
213
198
  end
214
199
  end
215
200
 
216
201
  def self.available_locales
217
- Spree::Store.all.map(&:supported_locales_list).flatten.uniq
202
+ Spree::Store.default&.supported_locales_list || []
218
203
  end
219
204
 
205
+ # @deprecated Use Markets instead. Will be removed in Spree 5.5.
206
+ def checkout_zone
207
+ Spree::Deprecation.warn('Store#checkout_zone is deprecated and will be removed in Spree 5.5. Use Markets instead.')
208
+ super
209
+ end
210
+
211
+ # @deprecated Use Markets instead. Will be removed in Spree 5.5.
212
+ def checkout_zone=(zone)
213
+ Spree::Deprecation.warn('Store#checkout_zone= is deprecated and will be removed in Spree 5.5. Use Markets instead.')
214
+ super
215
+ end
216
+
217
+ # Virtual attribute — sets the country for the default market created on store creation.
218
+ # Not persisted on the store itself; only used by the after_create callback.
219
+ attr_reader :default_country_iso
220
+
220
221
  def default_country_iso=(iso)
221
222
  return if iso.blank?
222
223
 
@@ -224,10 +225,11 @@ module Spree
224
225
 
225
226
  country = Spree::Country.by_iso(iso)
226
227
 
227
- if country.present?
228
- self.default_country = country
229
- elsif iso_country = ::Country[iso]
230
- new_country = Spree::Country.create!(
228
+ unless country
229
+ iso_country = ::Country[iso]
230
+ return unless iso_country
231
+
232
+ country = Spree::Country.create!(
231
233
  iso_name: iso_country.local_name&.upcase,
232
234
  iso: iso_country.alpha2,
233
235
  iso3: iso_country.alpha3,
@@ -236,15 +238,9 @@ module Spree
236
238
  states_required: Spree::Address::STATES_REQUIRED.include?(iso),
237
239
  zipcode_required: !Spree::Address::NO_ZIPCODE_ISO_CODES.include?(iso)
238
240
  )
239
-
240
- self.default_country = new_country
241
241
  end
242
- end
243
242
 
244
- def supported_currencies_list
245
- @supported_currencies_list ||= ([default_currency] + read_attribute(:supported_currencies).to_s.split(',')).uniq.map(&:to_s).map do |code|
246
- ::Money::Currency.find(code.strip)
247
- end.compact.sort_by { |currency| currency.iso_code == default_currency ? 0 : 1 }
243
+ @default_country_for_market = country
248
244
  end
249
245
 
250
246
  def seo_meta_description
@@ -257,10 +253,6 @@ module Spree
257
253
  end
258
254
  end
259
255
 
260
- def supported_locales_list
261
- @supported_locales_list ||= (read_attribute(:supported_locales).to_s.split(',') << default_locale).compact.uniq.sort
262
- end
263
-
264
256
  def unique_name
265
257
  @unique_name ||= "#{name} (#{code})"
266
258
  end
@@ -292,51 +284,63 @@ module Spree
292
284
  end
293
285
  end
294
286
 
295
- def formatted_custom_domain
296
- return unless default_custom_domain
297
-
298
- @formatted_custom_domain ||= if Rails.env.development? || Rails.env.test?
299
- URI::Generic.build(
300
- scheme: Rails.application.routes.default_url_options[:protocol] || 'http',
301
- host: default_custom_domain.url,
302
- port: Rails.application.routes.default_url_options[:port]
303
- ).to_s
304
- else
305
- URI::HTTPS.build(host: default_custom_domain.url).to_s
306
- end
307
- end
308
-
309
287
  def url_or_custom_domain
310
- default_custom_domain&.url || url
288
+ url
311
289
  end
312
290
 
313
291
  def formatted_url_or_custom_domain
314
- formatted_custom_domain || formatted_url
292
+ formatted_url
315
293
  end
316
294
 
317
- # Returns the countries available for checkout for the store or creates a new one if it doesn't exist
318
- # @return [Array<Spree::Country>]
319
- def countries_available_for_checkout
320
- @countries_available_for_checkout ||= Rails.cache.fetch(countries_available_for_checkout_cache_key) do
321
- (checkout_zone.try(:country_list) || Spree::Country.all).to_a
322
- end
323
- end
324
-
325
- # Returns the states available for checkout for the store or creates a new one if it doesn't exist
295
+ # Returns the states available for checkout for the store
326
296
  # @param country [Spree::Country] the country to get the states for
327
297
  # @return [Array<Spree::State>]
328
298
  def states_available_for_checkout(country)
329
299
  Rails.cache.fetch(states_available_for_checkout_cache_key(country)) do
330
- (checkout_zone.try(:state_list_for, country) || country.states).to_a
300
+ country.states.to_a
331
301
  end
332
302
  end
333
303
 
304
+ # @deprecated Use {Spree::Zone.all} or {#countries_with_shipping_coverage} instead.
305
+ # Will be removed in Spree 5.5.
334
306
  def supported_shipping_zones
335
- @supported_shipping_zones ||= if checkout_zone.present?
336
- [checkout_zone]
337
- else
338
- Spree::Zone.includes(zone_members: :zoneable).all
339
- end
307
+ Spree::Deprecation.warn(
308
+ 'Store#supported_shipping_zones is deprecated and will be removed in Spree 5.5. ' \
309
+ 'Use Spree::Zone.all or Store#countries_with_shipping_coverage instead.'
310
+ )
311
+ zone = Spree::Zone.find_by(id: read_attribute(:checkout_zone_id))
312
+ if zone.present?
313
+ [zone]
314
+ else
315
+ Spree::Zone.includes(zone_members: :zoneable).all
316
+ end
317
+ end
318
+
319
+ # Returns countries covered by at least one shipping zone
320
+ # that has an active shipping method attached.
321
+ # Handles both country-type zones (direct membership) and
322
+ # state-type zones (country inferred from state).
323
+ #
324
+ # @return [ActiveRecord::Relation<Spree::Country>]
325
+ def countries_with_shipping_coverage
326
+ zone_ids = Spree::Zone
327
+ .joins(:shipping_methods)
328
+ .select(:id)
329
+
330
+ country_zone_country_ids = Spree::ZoneMember
331
+ .where(zone_id: zone_ids, zoneable_type: 'Spree::Country')
332
+ .select(:zoneable_id)
333
+
334
+ state_zone_country_ids = Spree::State
335
+ .where(id: Spree::ZoneMember
336
+ .where(zone_id: zone_ids, zoneable_type: 'Spree::State')
337
+ .select(:zoneable_id))
338
+ .select(:country_id)
339
+
340
+ Spree::Country
341
+ .where(id: country_zone_country_ids)
342
+ .or(Spree::Country.where(id: state_zone_country_ids))
343
+ .order(:name)
340
344
  end
341
345
 
342
346
  # Returns the default stock location for the store or creates a new one if it doesn't exist
@@ -362,10 +366,6 @@ module Spree
362
366
  favicon_image.variant(resize_to_limit: [32, 32])
363
367
  end
364
368
 
365
- def can_be_deleted?
366
- self.class.where.not(id: id).any?
367
- end
368
-
369
369
  def metric_unit_system?
370
370
  preferred_unit_system == 'metric'
371
371
  end
@@ -378,24 +378,6 @@ module Spree
378
378
  @digital_shipping_category ||= ShippingCategory.find_or_create_by(name: 'Digital')
379
379
  end
380
380
 
381
- def import_products_from_store
382
- store = Store.find(import_products_from_store_id)
383
- product_ids = store.products.pluck(:id)
384
-
385
- return if product_ids.empty?
386
-
387
- StoreProduct.insert_all(product_ids.map { |product_id| { store_id: id, product_id: product_id } })
388
- end
389
-
390
- def import_payment_methods_from_store
391
- store = Store.find(import_payment_methods_from_store_id)
392
- payment_method_ids = store.payment_method_ids
393
-
394
- return if payment_method_ids.empty?
395
-
396
- StorePaymentMethod.insert_all(payment_method_ids.map { |payment_method_id| { store_id: id, payment_method_id: payment_method_id } })
397
- end
398
-
399
381
  %w[customer_terms_of_service customer_privacy_policy customer_returns_policy customer_shipping_policy].each do |policy_method|
400
382
  define_method policy_method do
401
383
  Spree::Deprecation.warn("Store##{policy_method} is deprecated and will be removed in Spree 5.5. Please use Store#policies instead.")
@@ -407,65 +389,36 @@ module Spree
407
389
  private
408
390
 
409
391
  def countries_available_for_checkout_cache_key
410
- "#{cache_key_with_version}/#{checkout_zone&.cache_key_with_version}/countries_available_for_checkout"
392
+ "#{cache_key_with_version}/countries_available_for_checkout"
411
393
  end
412
394
 
413
395
  def states_available_for_checkout_cache_key(country)
414
- "#{cache_key_with_version}/#{checkout_zone&.cache_key_with_version}/states_available_for_checkout/#{country&.cache_key_with_version}"
415
- end
416
-
417
- def ensure_default_exists_and_is_unique
418
- if default
419
- Store.where.not(id: id).update_all(default: false)
420
- elsif Store.where(default: true).count.zero?
421
- self.default = true
422
- end
396
+ "#{cache_key_with_version}/states_available_for_checkout/#{country&.cache_key_with_version}"
423
397
  end
424
398
 
425
- def ensure_supported_locales
426
- return unless attributes.keys.include?('supported_locales')
427
- return if supported_locales.present?
428
- return if default_locale.blank?
429
-
430
- self.supported_locales = default_locale
399
+ def clear_cache
400
+ Rails.cache.delete('default_store')
431
401
  end
432
402
 
433
- def ensure_supported_currencies
434
- return unless attributes.keys.include?('supported_currencies')
435
- return if supported_currencies.present?
436
- return if default_currency.blank?
403
+ def ensure_default_market
404
+ return if markets.exists?
437
405
 
438
- self.supported_currencies = default_currency
439
- end
406
+ country = @default_country_for_market
407
+ return if country.blank?
440
408
 
441
- def validate_not_last
442
- unless can_be_deleted?
443
- errors.add(:base, :cannot_destroy_only_store)
444
- throw(:abort)
445
- end
446
- end
409
+ iso_country = ISO3166::Country[country.iso]
447
410
 
448
- def pass_default_flag_to_other_store
449
- if default? && can_be_deleted?
450
- self.class.where.not(id: id).first.update!(default: true)
451
- self.default = false
411
+ Spree::Events.disable do
412
+ markets.create!(
413
+ name: country.name,
414
+ currency: iso_country&.currency_code || read_attribute(:default_currency) || 'USD',
415
+ default_locale: iso_country&.languages_official&.first || read_attribute(:default_locale) || 'en',
416
+ default: true,
417
+ countries: [country]
418
+ )
452
419
  end
453
420
  end
454
421
 
455
- def clear_cache
456
- Rails.cache.delete('default_store')
457
- end
458
-
459
- def ensure_default_country
460
- return if default_country.present? && (checkout_zone.blank? || checkout_zone.country_list.blank? || checkout_zone.country_list.include?(default_country))
461
-
462
- self.default_country = if checkout_zone.present? && checkout_zone.country_list.any?
463
- checkout_zone.country_list.first
464
- else
465
- Country.find_by(iso: 'US') || Country.first
466
- end
467
- end
468
-
469
422
  def ensure_default_taxonomies_are_created
470
423
  Spree::Events.disable do
471
424
  [
@@ -547,42 +500,20 @@ module Spree
547
500
  I18n.t(key, locale: locale, default: I18n.t(key, locale: :en))
548
501
  end
549
502
 
550
- # code is slug, so we don't want to generate new slug when code changes
551
- # we use friendlyId only for history feature
552
- def should_generate_new_friendly_id?
553
- false
554
- end
555
-
556
- def slug_candidates
557
- []
558
- end
559
-
560
- def handle_code_changes
561
- # implement your custom logic here
503
+ def ensure_default_exists_and_is_unique
504
+ if default
505
+ Spree::Store.where.not(id: id).update_all(default: false)
506
+ elsif Spree::Store.where(default: true).count.zero?
507
+ self.default = true
508
+ end
562
509
  end
563
510
 
564
- # This FriendlyId method is overwitten to keep our logic for generating code
565
- # there is no option for own format
566
- def set_code
567
- self.code = if code.present?
568
- code.parameterize.strip
569
- elsif name.present?
570
- name.parameterize.strip
571
- end
572
-
573
- return if self.code.blank?
574
-
575
- # ensure code is unique
576
- self.code = [name.parameterize, rand(9999)].join('-') while Spree::Store.with_deleted.where(code: self.code).exists?
511
+ def should_generate_new_friendly_id?
512
+ false
577
513
  end
578
514
 
579
- # auto-assign internal URL for stores
580
- def set_url
581
- return if url_changed?
582
- return unless code_changed?
583
- return unless Spree.root_domain.present?
584
-
585
- self.url = [code, Spree.root_domain].join('.')
515
+ def set_default_code
516
+ self.code = 'default' if code.blank?
586
517
  end
587
518
  end
588
519
  end
@@ -1,5 +1,7 @@
1
1
  module Spree
2
2
  class StoreCredit < Spree.base_class
3
+ has_prefix_id :credit
4
+
3
5
  include Spree::SingleStoreResource
4
6
  include Spree::Metafields
5
7
  include Spree::Metadata
@@ -52,10 +54,6 @@ module Spree
52
54
  extend Spree::DisplayMoney
53
55
  money_methods :amount, :amount_used, :amount_remaining, :amount_authorized
54
56
 
55
- def amount=(amount)
56
- self[:amount] = Spree::LocalizedNumber.parse(amount)
57
- end
58
-
59
57
  self.whitelisted_ransackable_attributes = %w[user_id created_by_id amount currency type_id]
60
58
  self.whitelisted_ransackable_associations = %w[type user created_by]
61
59
 
@@ -1,5 +1,7 @@
1
1
  module Spree
2
2
  class StoreCreditCategory < Spree.base_class
3
+ has_prefix_id :sccat
4
+
3
5
  validates :name, presence: true
4
6
 
5
7
  before_destroy :validate_not_used
@@ -1,5 +1,7 @@
1
1
  module Spree
2
2
  class StoreCreditEvent < Spree.base_class
3
+ has_prefix_id :scevt
4
+
3
5
  acts_as_paranoid
4
6
 
5
7
  #
@@ -1,5 +1,7 @@
1
1
  module Spree
2
2
  class StoreCreditType < Spree.base_class
3
+ has_prefix_id :sctype
4
+
3
5
  DEFAULT_TYPE_NAME = 'Expiring'.freeze
4
6
  has_many :store_credits, class_name: 'Spree::StoreCredit', foreign_key: 'type_id'
5
7
 
@@ -1,5 +1,7 @@
1
1
  module Spree
2
2
  class StoreProduct < Spree.base_class
3
+ has_prefix_id :sp
4
+
3
5
  self.table_name = 'spree_products_stores'
4
6
 
5
7
  belongs_to :store, class_name: 'Spree::Store', touch: true
@@ -1,5 +1,7 @@
1
1
  module Spree
2
2
  class TaxCategory < Spree.base_class
3
+ has_prefix_id :taxcat
4
+
3
5
  acts_as_paranoid
4
6
  validates :name, presence: true, uniqueness: { case_sensitive: false, scope: spree_base_uniqueness_scope + [:deleted_at] }
5
7
 
@@ -1,5 +1,7 @@
1
1
  module Spree
2
2
  class TaxRate < Spree.base_class
3
+ has_prefix_id :tax
4
+
3
5
  acts_as_paranoid
4
6
 
5
7
  include Spree::CalculatedAdjustments
@@ -2,6 +2,8 @@ require 'stringex'
2
2
 
3
3
  module Spree
4
4
  class Taxon < Spree.base_class
5
+ has_prefix_id :txn # Spree-specific: taxon
6
+
5
7
  RULES_MATCH_POLICIES = %w[all any].freeze
6
8
  SORT_ORDERS = %w[
7
9
  manual
@@ -36,7 +38,8 @@ module Spree
36
38
  has_one :store, through: :taxonomy
37
39
  has_many :classifications, -> { order(:position) }, dependent: :destroy_async, inverse_of: :taxon
38
40
  has_many :products, through: :classifications
39
- has_one :icon, as: :viewable, dependent: :destroy, class_name: 'Spree::TaxonImage' # TODO: remove this as this is deprecated
41
+ # @deprecated Use Spree::Taxon#image (Active Storage) instead. Will be removed in Spree 5.5.
42
+ has_one :icon, as: :viewable, dependent: :destroy, class_name: 'Spree::TaxonImage'
40
43
 
41
44
  has_many :prototype_taxons, class_name: 'Spree::PrototypeTaxon', dependent: :destroy
42
45
  has_many :prototypes, through: :prototype_taxons, class_name: 'Spree::Prototype'
@@ -1,9 +1,17 @@
1
1
  module Spree
2
+ # @deprecated Use Spree::Taxon#image (Active Storage) instead. Will be removed in Spree 5.5.
2
3
  class TaxonImage < Asset
3
4
  include Spree::TaxonImage::Configuration::ActiveStorage
4
5
  include Rails.application.routes.url_helpers
5
6
  include Spree::ImageMethods
6
7
 
8
+ after_initialize do
9
+ Spree::Deprecation.warn(
10
+ 'Spree::TaxonImage is deprecated and will be removed in Spree 5.5. ' \
11
+ 'Please use Spree::Taxon#image (Active Storage) instead.'
12
+ )
13
+ end
14
+
7
15
  def styles
8
16
  self.class.styles.map do |_, size|
9
17
  width, height = size[/(\d+)x(\d+)/].split('x').map(&:to_i)
@@ -1,5 +1,7 @@
1
1
  module Spree
2
2
  class TaxonRule < Spree.base_class
3
+ has_prefix_id :txrule
4
+
3
5
  MATCH_POLICIES = %w[
4
6
  is_equal_to
5
7
  is_not_equal_to
@@ -1,5 +1,7 @@
1
1
  module Spree
2
2
  class Taxonomy < Spree.base_class
3
+ has_prefix_id :txnmy # Spree-specific: taxonomy
4
+
3
5
  include Spree::TranslatableResource
4
6
  include Spree::Metafields
5
7
  include Spree::Metadata
@@ -0,0 +1,81 @@
1
+ module Spree
2
+ class UserIdentity < Spree::Base
3
+ has_prefix_id :uid
4
+
5
+ belongs_to :user, polymorphic: true, optional: false
6
+
7
+ validates :provider, presence: true
8
+ validates :uid, presence: true, uniqueness: { scope: [:provider, :user_type] }
9
+
10
+ validates :provider, inclusion: {
11
+ in: ->(_record) {
12
+ config = Rails.application.config.spree
13
+ (config.store_authentication_strategies.keys + config.admin_authentication_strategies.keys).uniq.map(&:to_s)
14
+ }
15
+ }
16
+
17
+ # Store provider-specific data
18
+ # info: JSON field with provider-specific data (name, avatar, etc)
19
+ # access_token: encrypted OAuth access token
20
+ # refresh_token: encrypted OAuth refresh token
21
+ # expires_at: token expiration timestamp
22
+
23
+ # Find or create user from OAuth data
24
+ def self.find_or_create_from_oauth(provider:, uid:, info:, tokens: {}, user_class: nil)
25
+ user_class ||= Spree.user_class
26
+ user_type = user_class.name
27
+
28
+ identity = find_by(provider: provider, uid: uid, user_type: user_type)
29
+
30
+ if identity
31
+ # Update existing identity with fresh tokens
32
+ identity.update(
33
+ info: info,
34
+ access_token: tokens[:access_token],
35
+ refresh_token: tokens[:refresh_token],
36
+ expires_at: tokens[:expires_at]
37
+ )
38
+ identity.user
39
+ else
40
+ # Create new user and identity
41
+ create_user_from_oauth(
42
+ provider: provider,
43
+ uid: uid,
44
+ info: info,
45
+ tokens: tokens,
46
+ user_class: user_class
47
+ )
48
+ end
49
+ end
50
+
51
+ def self.create_user_from_oauth(provider:, uid:, info:, tokens: {}, user_class: nil)
52
+ user_class ||= Spree.user_class
53
+
54
+ user = user_class.create!(
55
+ email: info[:email] || generate_temp_email(provider, uid),
56
+ password: SecureRandom.hex(32), # Random password for OAuth users
57
+ first_name: info[:first_name],
58
+ last_name: info[:last_name]
59
+ )
60
+
61
+ user.identities.create!(
62
+ provider: provider,
63
+ uid: uid,
64
+ info: info,
65
+ access_token: tokens[:access_token],
66
+ refresh_token: tokens[:refresh_token],
67
+ expires_at: tokens[:expires_at]
68
+ )
69
+
70
+ user
71
+ end
72
+
73
+ def self.generate_temp_email(provider, uid)
74
+ "#{provider}-#{uid}@temporary.example.com"
75
+ end
76
+
77
+ def expired?
78
+ expires_at && expires_at < Time.current
79
+ end
80
+ end
81
+ end