spree_core 5.3.6 → 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 (243) 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 +5 -4
  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 -63
  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/presenters/spree/csv/formula_sanitizer.rb +0 -28
  209. data/app/serializers/spree/events/asset_serializer.rb +0 -22
  210. data/app/serializers/spree/events/base_serializer.rb +0 -61
  211. data/app/serializers/spree/events/customer_return_serializer.rb +0 -20
  212. data/app/serializers/spree/events/digital_link_serializer.rb +0 -20
  213. data/app/serializers/spree/events/digital_serializer.rb +0 -18
  214. data/app/serializers/spree/events/export_serializer.rb +0 -22
  215. data/app/serializers/spree/events/gift_card_batch_serializer.rb +0 -24
  216. data/app/serializers/spree/events/gift_card_serializer.rb +0 -29
  217. data/app/serializers/spree/events/image_serializer.rb +0 -9
  218. data/app/serializers/spree/events/import_row_serializer.rb +0 -23
  219. data/app/serializers/spree/events/import_serializer.rb +0 -24
  220. data/app/serializers/spree/events/invitation_serializer.rb +0 -28
  221. data/app/serializers/spree/events/line_item_serializer.rb +0 -31
  222. data/app/serializers/spree/events/newsletter_subscriber_serializer.rb +0 -21
  223. data/app/serializers/spree/events/order_serializer.rb +0 -39
  224. data/app/serializers/spree/events/payment_serializer.rb +0 -24
  225. data/app/serializers/spree/events/post_category_serializer.rb +0 -20
  226. data/app/serializers/spree/events/post_serializer.rb +0 -26
  227. data/app/serializers/spree/events/price_serializer.rb +0 -22
  228. data/app/serializers/spree/events/product_serializer.rb +0 -24
  229. data/app/serializers/spree/events/promotion_serializer.rb +0 -32
  230. data/app/serializers/spree/events/refund_serializer.rb +0 -23
  231. data/app/serializers/spree/events/reimbursement_serializer.rb +0 -22
  232. data/app/serializers/spree/events/report_serializer.rb +0 -23
  233. data/app/serializers/spree/events/return_authorization_serializer.rb +0 -22
  234. data/app/serializers/spree/events/return_item_serializer.rb +0 -27
  235. data/app/serializers/spree/events/shipment_serializer.rb +0 -24
  236. data/app/serializers/spree/events/stock_item_serializer.rb +0 -22
  237. data/app/serializers/spree/events/stock_movement_serializer.rb +0 -22
  238. data/app/serializers/spree/events/stock_transfer_serializer.rb +0 -22
  239. data/app/serializers/spree/events/store_credit_serializer.rb +0 -30
  240. data/app/serializers/spree/events/user_serializer.rb +0 -18
  241. data/app/serializers/spree/events/variant_serializer.rb +0 -34
  242. data/app/serializers/spree/events/wished_item_serializer.rb +0 -20
  243. data/app/serializers/spree/events/wishlist_serializer.rb +0 -22
@@ -85,8 +85,14 @@ module Spree
85
85
 
86
86
  def reduce_units_quantities(reduced_quantity, units)
87
87
  units.each do |unit|
88
- unit.quantity > reduced_quantity ? unit.update(quantity: unit.quantity - reduced_quantity) : unit.destroy!
89
- reduced_quantity -= unit.quantity
88
+ if unit.quantity > reduced_quantity
89
+ unit.update(quantity: unit.quantity - reduced_quantity)
90
+ reduced_quantity = 0
91
+ else
92
+ reduced_quantity -= unit.quantity
93
+ unit.destroy!
94
+ end
95
+ break if reduced_quantity.zero?
90
96
  end
91
97
  reduced_quantity
92
98
  end
@@ -84,6 +84,66 @@ module Spree
84
84
  CreditCard
85
85
  end
86
86
 
87
+ def session_required?
88
+ true
89
+ end
90
+
91
+ def payment_session_class
92
+ PaymentSessions::Bogus
93
+ end
94
+
95
+ def create_payment_session(order:, amount: nil, external_data: {})
96
+ payment_session_class.create(
97
+ order: order,
98
+ payment_method: self,
99
+ amount: amount.presence || order.total_minus_store_credits,
100
+ currency: order.currency,
101
+ status: 'pending',
102
+ external_id: "bogus_#{SecureRandom.hex(12)}",
103
+ external_data: external_data.merge('client_secret' => "bogus_secret_#{SecureRandom.hex(8)}"),
104
+ customer: order.user
105
+ )
106
+ end
107
+
108
+ def update_payment_session(payment_session:, amount: nil, external_data: {})
109
+ attrs = {}
110
+ attrs[:amount] = amount if amount.present?
111
+ attrs[:external_data] = payment_session.external_data.merge(external_data) if external_data.present?
112
+ payment_session.update(attrs)
113
+ end
114
+
115
+ def complete_payment_session(payment_session:, params: {})
116
+ payment_session.complete
117
+ end
118
+
119
+ def setup_session_supported?
120
+ true
121
+ end
122
+
123
+ def create_payment_setup_session(customer:, external_data: {})
124
+ payment_setup_sessions.create(
125
+ customer: customer,
126
+ status: 'pending',
127
+ external_id: "bogus_seti_#{SecureRandom.hex(12)}",
128
+ external_client_secret: "bogus_seti_secret_#{SecureRandom.hex(8)}",
129
+ external_data: external_data
130
+ )
131
+ end
132
+
133
+ def complete_payment_setup_session(setup_session:, params: {})
134
+ credit_card = CreditCard.create!(
135
+ user: setup_session.customer,
136
+ payment_method: self,
137
+ name: 'Bogus Card',
138
+ last_digits: '4242',
139
+ month: '12',
140
+ year: 1.year.from_now.year.to_s,
141
+ gateway_customer_profile_id: "BGS-#{Array.new(6) { rand(6) }.join}"
142
+ )
143
+ setup_session.update!(payment_source: credit_card)
144
+ setup_session.complete
145
+ end
146
+
87
147
  def actions
88
148
  %w(capture void credit)
89
149
  end
@@ -1,5 +1,7 @@
1
1
  module Spree
2
2
  class GatewayCustomer < Spree.base_class
3
+ has_prefix_id :gcus
4
+
3
5
  belongs_to :payment_method, class_name: 'Spree::PaymentMethod'
4
6
  belongs_to :user, class_name: Spree.user_class.to_s
5
7
 
@@ -1,5 +1,7 @@
1
1
  module Spree
2
2
  class GiftCard < Spree.base_class
3
+ has_prefix_id :gc
4
+
3
5
  extend DisplayMoney
4
6
  include Spree::SingleStoreResource
5
7
  include Spree::Metafields
@@ -1,5 +1,7 @@
1
1
  module Spree
2
2
  class GiftCardBatch < Spree.base_class
3
+ has_prefix_id :gcb
4
+
3
5
  extend DisplayMoney
4
6
  include Spree::SingleStoreResource
5
7
 
@@ -30,10 +32,6 @@ module Spree
30
32
 
31
33
  money_methods :amount
32
34
 
33
- def amount=(amount)
34
- self[:amount] = Spree::LocalizedNumber.parse(amount)
35
- end
36
-
37
35
  self.whitelisted_ransackable_attributes = %w[prefix]
38
36
 
39
37
  def generate_gift_cards
@@ -5,6 +5,9 @@ module Spree
5
5
  include Spree::ImageMethods
6
6
 
7
7
  after_commit :touch_product_variants, if: :should_touch_product_variants?, on: :update
8
+ after_commit :update_variant_thumbnail, on: [:create, :destroy]
9
+ after_commit :update_variant_thumbnail_on_reorder, on: :update, if: :saved_change_to_position?
10
+ after_commit :update_variant_thumbnail_on_viewable_change, on: :update, if: :saved_change_to_viewable_id?
8
11
 
9
12
  after_create :increment_viewable_image_count
10
13
  after_destroy :decrement_viewable_image_count
@@ -84,5 +87,20 @@ module Spree
84
87
  Spree::Variant.decrement_counter(:image_count, viewable_id)
85
88
  Spree::Product.decrement_counter(:total_image_count, viewable.product_id)
86
89
  end
90
+
91
+ def update_variant_thumbnail
92
+ return unless viewable.is_a?(Spree::Variant)
93
+
94
+ viewable.update_thumbnail!
95
+ viewable.product.update_thumbnail!
96
+ end
97
+
98
+ def update_variant_thumbnail_on_reorder
99
+ update_variant_thumbnail
100
+ end
101
+
102
+ def update_variant_thumbnail_on_viewable_change
103
+ update_variant_thumbnail
104
+ end
87
105
  end
88
106
  end
@@ -2,9 +2,9 @@ require 'csv'
2
2
 
3
3
  module Spree
4
4
  class Import < Spree.base_class
5
- include Spree::NumberIdentifier
6
- include Spree::NumberAsParam
5
+ has_prefix_id :imp
7
6
 
7
+ include Spree::NumberIdentifier
8
8
  include Spree::Core::NumberGenerator.new(prefix: 'IM')
9
9
 
10
10
  publishes_lifecycle_events
@@ -226,7 +226,7 @@ module Spree
226
226
  end
227
227
 
228
228
  def event_serializer_class
229
- Spree::Events::ImportSerializer
229
+ 'Spree::Api::V3::ImportSerializer'.safe_constantize
230
230
  end
231
231
 
232
232
  class << self
@@ -250,6 +250,8 @@ module Spree
250
250
 
251
251
  # eg. Spree::Imports::Orders => Spree::Order
252
252
  def model_class
253
+ return Spree.user_class if to_s == 'Spree::Imports::Customers'
254
+
253
255
  klass = "Spree::#{to_s.demodulize.singularize}".safe_constantize
254
256
 
255
257
  raise NameError, "Missing model class for #{self}" unless klass
@@ -1,5 +1,7 @@
1
1
  module Spree
2
2
  class ImportMapping < Spree.base_class
3
+ has_prefix_id :immap
4
+
3
5
  #
4
6
  # Associations
5
7
  #
@@ -1,5 +1,7 @@
1
1
  module Spree
2
2
  class ImportRow < Spree.base_class
3
+ has_prefix_id :imrow
4
+
3
5
  # Set event prefix for ImportRow
4
6
  self.event_prefix = 'import_row'
5
7
 
@@ -0,0 +1,21 @@
1
+ module Spree
2
+ module ImportSchemas
3
+ class Customers < Spree::ImportSchema
4
+ FIELDS = [
5
+ { name: 'email', label: 'Email', required: true },
6
+ { name: 'first_name', label: 'First Name' },
7
+ { name: 'last_name', label: 'Last Name' },
8
+ { name: 'phone', label: 'Phone' },
9
+ { name: 'accepts_email_marketing', label: 'Accepts Email Marketing' },
10
+ { name: 'tags', label: 'Tags' },
11
+ { name: 'company', label: 'Company' },
12
+ { name: 'address1', label: 'Address 1' },
13
+ { name: 'address2', label: 'Address 2' },
14
+ { name: 'city', label: 'City' },
15
+ { name: 'province_code', label: 'Province Code' },
16
+ { name: 'country_code', label: 'Country Code' },
17
+ { name: 'zip', label: 'Zip' }
18
+ ].freeze
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,9 @@
1
+ module Spree
2
+ module Imports
3
+ class Customers < Spree::Import
4
+ def row_processor_class
5
+ Spree::Imports::RowProcessors::Customer
6
+ end
7
+ end
8
+ end
9
+ end
@@ -1,5 +1,7 @@
1
1
  module Spree
2
2
  class Integration < Spree.base_class
3
+ has_prefix_id :int
4
+
3
5
  include Spree::SingleStoreResource
4
6
 
5
7
  #
@@ -1,5 +1,7 @@
1
1
  module Spree
2
2
  class InventoryUnit < Spree.base_class
3
+ has_prefix_id :iu
4
+
3
5
  extend Spree::DisplayMoney
4
6
 
5
7
  with_options inverse_of: :inventory_units do
@@ -1,5 +1,7 @@
1
1
  module Spree
2
2
  class Invitation < Spree.base_class
3
+ has_prefix_id :inv
4
+
3
5
  has_secure_token
4
6
  acts_as_paranoid
5
7
 
@@ -121,13 +123,11 @@ module Spree
121
123
  def invitee_already_exists
122
124
  return if resource.blank?
123
125
 
124
- exists = if invitee.present?
125
- resource.users.include?(invitee)
126
- else
127
- resource.users.exists?(email: email)
128
- end
126
+ # Check role_users directly for better performance and to avoid potential source_type issues
127
+ user_to_check = invitee || Spree.admin_user_class.find_by(email: email)
128
+ return if user_to_check.blank?
129
129
 
130
- if exists
130
+ if resource.role_users.exists?(user: user_to_check)
131
131
  errors.add(:email, 'already exists')
132
132
  end
133
133
  end
@@ -0,0 +1,31 @@
1
+ # Default implementation of AdminUser for staff/admin accounts.
2
+ # This class is separate from LegacyUser to support distinct customer vs staff models.
3
+ require 'bcrypt'
4
+
5
+ module Spree
6
+ class LegacyAdminUser < Spree.base_class
7
+ include Spree::AdminUserMethods
8
+
9
+ self.table_name = 'spree_admin_users'
10
+
11
+ attr_accessor :password, :password_confirmation
12
+
13
+ validates :email, presence: true, uniqueness: { case_sensitive: false }
14
+
15
+ before_save :encrypt_password, if: :password
16
+
17
+ # Simple password validation for testing purposes
18
+ # In production, Spree.admin_user_class should be overridden with a proper auth solution (e.g., Devise)
19
+ def valid_password?(check_password)
20
+ return false if encrypted_password.blank?
21
+
22
+ BCrypt::Password.new(encrypted_password) == check_password
23
+ end
24
+
25
+ private
26
+
27
+ def encrypt_password
28
+ self.encrypted_password = BCrypt::Password.create(password)
29
+ end
30
+ end
31
+ end
@@ -1,4 +1,6 @@
1
- # Default implementation of User. This class is intended to be modified by extensions (ex. spree_auth_devise)
1
+ # Default implementation of User.
2
+ require 'bcrypt'
3
+
2
4
  module Spree
3
5
  class LegacyUser < Spree.base_class
4
6
  include Spree::UserAddress
@@ -10,5 +12,21 @@ module Spree
10
12
  attr_accessor :password, :password_confirmation
11
13
 
12
14
  validates :email, presence: true, uniqueness: { case_sensitive: false }
15
+
16
+ before_save :encrypt_password, if: :password
17
+
18
+ # Simple password validation for testing purposes
19
+ # In production, Spree.user_class should be overridden with a proper auth solution (e.g., Devise)
20
+ def valid_password?(check_password)
21
+ return false if encrypted_password.blank?
22
+
23
+ BCrypt::Password.new(encrypted_password) == check_password
24
+ end
25
+
26
+ private
27
+
28
+ def encrypt_password
29
+ self.encrypted_password = BCrypt::Password.create(password)
30
+ end
13
31
  end
14
32
  end
@@ -1,5 +1,7 @@
1
1
  module Spree
2
2
  class LineItem < Spree.base_class
3
+ has_prefix_id :li # Spree-specific: line item
4
+
3
5
  include Spree::Metafields
4
6
  include Spree::Metadata
5
7
 
@@ -28,10 +30,12 @@ module Spree
28
30
 
29
31
  validates :variant, :order, presence: true
30
32
 
33
+ DB_INTEGER_MAX = (2**31) - 1
34
+
31
35
  # numericality: :less_than_or_equal_to validation is due to the restriction at the database level
32
36
  # https://github.com/spree/spree/issues/2695#issuecomment-143314161
33
37
  validates :quantity, numericality: {
34
- in: 0..DatabaseTypeUtilities.maximum_value_for(:integer),
38
+ in: 0..DB_INTEGER_MAX,
35
39
  only_integer: true, message: Spree.t('validation.must_be_int')
36
40
  }
37
41
 
@@ -47,8 +51,15 @@ module Spree
47
51
 
48
52
  after_create :update_tax_charge
49
53
 
50
- delegate :name, :description, :sku, :should_track_inventory?, :product, :options_text, :slug, :product_id, :dimensions_unit, :weight_unit, to: :variant
51
- delegate :brand, :category, to: :product
54
+ delegate :sku, :should_track_inventory?, :product, :options_text, :slug, :product_id, :dimensions_unit, :weight_unit, :option_values, to: :variant
55
+ delegate :name, :description, :brand, :category, to: :product
56
+
57
+ # Returns the thumbnail image for this line item
58
+ # Prefers variant thumbnail, falls back to product thumbnail
59
+ # @return [Spree::Image, nil]
60
+ def thumbnail
61
+ variant.thumbnail || product.thumbnail
62
+ end
52
63
  delegate :tax_zone, to: :order
53
64
  delegate :digital?, :can_supply?, to: :variant
54
65
 
@@ -217,7 +228,7 @@ module Spree
217
228
  #
218
229
  # @return [Integer]
219
230
  def maximum_quantity
220
- @maximum_quantity ||= variant.backorderable? ? Spree::DatabaseTypeUtilities.maximum_value_for(:integer) : variant.total_on_hand
231
+ @maximum_quantity ||= variant.backorderable? ? DB_INTEGER_MAX : variant.total_on_hand
221
232
  end
222
233
 
223
234
  # Returns true if the line item variant has digital assets
@@ -1,5 +1,7 @@
1
1
  module Spree
2
2
  class LogEntry < Spree.base_class
3
+ has_prefix_id :log
4
+
3
5
  if defined?(Spree::Security::LogEntries)
4
6
  include Spree::Security::LogEntries
5
7
  end
@@ -0,0 +1,83 @@
1
+ module Spree
2
+ class Market < Spree.base_class
3
+ has_prefix_id :mkt
4
+
5
+ include Spree::SingleStoreResource
6
+
7
+ acts_as_paranoid
8
+ acts_as_list scope: :store_id
9
+
10
+ #
11
+ # Associations
12
+ #
13
+ belongs_to :store, class_name: 'Spree::Store', touch: true
14
+ has_many :market_countries, class_name: 'Spree::MarketCountry', dependent: :destroy
15
+ has_many :countries, through: :market_countries, class_name: 'Spree::Country'
16
+
17
+ #
18
+ # Validations
19
+ #
20
+ validates :store, presence: true
21
+ validates :name, presence: true, uniqueness: { scope: spree_base_uniqueness_scope + [:store_id] }
22
+ validates :currency, presence: true
23
+ validates :default_locale, presence: true
24
+ validates :countries, presence: true
25
+
26
+ #
27
+ # Callbacks
28
+ #
29
+ before_save :ensure_single_default
30
+
31
+ #
32
+ # Scopes
33
+ #
34
+ scope :default, -> { where(default: true) }
35
+
36
+ # Find the market that contains the given country for a store
37
+ #
38
+ # @param country [Spree::Country] the country to look up
39
+ # @param store [Spree::Store] the store to scope to
40
+ # @return [Spree::Market, nil]
41
+ def self.for_country(country, store:)
42
+ return nil unless country && store
43
+
44
+ joins(:market_countries)
45
+ .where(store_id: store.id)
46
+ .where(spree_market_countries: { country_id: country.id })
47
+ .order(:position)
48
+ .first
49
+ end
50
+
51
+ # Returns the default market for a store, or falls back to the first by position
52
+ #
53
+ # @param store [Spree::Store]
54
+ # @return [Spree::Market, nil]
55
+ def self.default_for_store(store)
56
+ return nil unless store
57
+
58
+ store.markets.default.first || store.markets.order(:position).first
59
+ end
60
+
61
+ # Returns the first country by name from this market's countries
62
+ #
63
+ # @return [Spree::Country, nil]
64
+ def default_country
65
+ countries.order(:name).first
66
+ end
67
+
68
+ # Returns supported locales as an array, always including default_locale
69
+ #
70
+ # @return [Array<String>]
71
+ def supported_locales_list
72
+ @supported_locales_list ||= (supported_locales.to_s.split(',').map(&:strip) << default_locale).compact.uniq.sort
73
+ end
74
+
75
+ private
76
+
77
+ def ensure_single_default
78
+ return unless default? && default_changed?
79
+
80
+ self.class.where(store_id: store_id, default: true).where.not(id: id).update_all(default: false)
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,25 @@
1
+ module Spree
2
+ class MarketCountry < Spree.base_class
3
+ self.table_name = 'spree_market_countries'
4
+
5
+ belongs_to :market, class_name: 'Spree::Market'
6
+ belongs_to :country, class_name: 'Spree::Country'
7
+
8
+ validates :market, :country, presence: true
9
+ validates :country_id, uniqueness: { scope: :market_id }
10
+ validate :country_covered_by_shipping_zone
11
+
12
+ private
13
+
14
+ def country_covered_by_shipping_zone
15
+ return if market.blank? || country.blank?
16
+
17
+ store = market.store
18
+ return if store.blank?
19
+
20
+ unless store.countries_with_shipping_coverage.exists?(id: country.id)
21
+ errors.add(:country, :not_in_shipping_zone)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -1,5 +1,7 @@
1
1
  module Spree
2
2
  class Metafield < Spree.base_class
3
+ has_prefix_id :mf
4
+
3
5
  #
4
6
  # Associations
5
7
  #
@@ -1,5 +1,7 @@
1
1
  module Spree
2
2
  class MetafieldDefinition < Spree.base_class
3
+ has_prefix_id :mfdef
4
+
3
5
  include Spree::DisplayOn
4
6
 
5
7
  DISPLAY = [:both, :back_end]
@@ -2,6 +2,8 @@ require_dependency 'spree/newsletter_subscriber/emails'
2
2
 
3
3
  module Spree
4
4
  class NewsletterSubscriber < Spree.base_class
5
+ has_prefix_id :sub
6
+
5
7
  include Spree::NewsletterSubscriber::Emails
6
8
  include Spree::Metafields
7
9
 
@@ -1,5 +1,7 @@
1
1
  module Spree
2
2
  class OptionType < Spree.base_class
3
+ has_prefix_id :opt # Spree-specific: option type
4
+
3
5
  COLOR_NAMES = %w[color colour].freeze
4
6
 
5
7
  include Spree::ParameterizableName
@@ -1,5 +1,7 @@
1
1
  module Spree
2
2
  class OptionValue < Spree.base_class
3
+ has_prefix_id :optval # Spree-specific: option value
4
+
3
5
  include Spree::ParameterizableName
4
6
  include Spree::Metafields
5
7
  include Spree::Metadata
@@ -79,7 +79,8 @@ module Spree
79
79
  end
80
80
 
81
81
  def find_existing_address(attributes)
82
- address_attributes = attributes.except(:state_name)
82
+ # Exclude virtual attributes that are handled by Address model callbacks
83
+ address_attributes = attributes.except(:state_name, :country_iso, :state_abbr)
83
84
  state_name = attributes[:state_name]
84
85
 
85
86
  scope = Spree::Address.not_deleted.where(address_attributes)