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.
- checksums.yaml +4 -4
- data/app/finders/spree/orders/find_complete.rb +33 -2
- data/app/finders/spree/stores/find_default.rb +17 -0
- data/app/finders/spree/variants/visible_finder.rb +1 -0
- data/app/helpers/spree/addresses_helper.rb +1 -2
- data/app/helpers/spree/base_helper.rb +5 -4
- data/app/jobs/spree/api_key_touch_job.rb +9 -0
- data/app/models/concerns/spree/admin_user_methods.rb +32 -0
- data/app/models/concerns/spree/number_as_param.rb +5 -3
- data/app/models/concerns/spree/prefixed_id.rb +82 -0
- data/app/models/concerns/spree/product_scopes.rb +33 -18
- data/app/models/concerns/spree/publishable.rb +47 -47
- data/app/models/concerns/spree/{multi_store_resource.rb → store_scoped_resource.rb} +1 -10
- data/app/models/concerns/spree/stores/markets.rb +124 -0
- data/app/models/concerns/spree/user_methods.rb +7 -7
- data/app/models/concerns/spree/user_payment_source.rb +2 -0
- data/app/models/concerns/spree/user_roles.rb +1 -0
- data/app/models/spree/ability.rb +13 -0
- data/app/models/spree/address.rb +30 -0
- data/app/models/spree/adjustment.rb +2 -0
- data/app/models/spree/api_key.rb +47 -0
- data/app/models/spree/asset.rb +2 -0
- data/app/models/spree/authentication/strategies/base_strategy.rb +55 -0
- data/app/models/spree/authentication/strategies/email_password_strategy.rb +47 -0
- data/app/models/spree/base.rb +1 -0
- data/app/models/spree/calculator.rb +2 -0
- data/app/models/spree/country.rb +56 -0
- data/app/models/spree/coupon_code.rb +2 -0
- data/app/models/spree/credit_card.rb +2 -0
- data/app/models/spree/current.rb +9 -4
- data/app/models/spree/customer_group.rb +2 -0
- data/app/models/spree/customer_return.rb +2 -0
- data/app/models/spree/data_feed.rb +2 -0
- data/app/models/spree/digital.rb +2 -0
- data/app/models/spree/digital_link.rb +2 -0
- data/app/models/spree/export.rb +3 -2
- data/app/models/spree/fulfilment_changer.rb +8 -2
- data/app/models/spree/gateway/bogus.rb +60 -0
- data/app/models/spree/gateway_customer.rb +2 -0
- data/app/models/spree/gift_card.rb +2 -0
- data/app/models/spree/gift_card_batch.rb +2 -0
- data/app/models/spree/image.rb +18 -0
- data/app/models/spree/import.rb +5 -3
- data/app/models/spree/import_mapping.rb +2 -0
- data/app/models/spree/import_row.rb +2 -0
- data/app/models/spree/import_schemas/customers.rb +21 -0
- data/app/models/spree/imports/customers.rb +9 -0
- data/app/models/spree/integration.rb +2 -0
- data/app/models/spree/inventory_unit.rb +2 -0
- data/app/models/spree/invitation.rb +6 -6
- data/app/models/spree/legacy_admin_user.rb +31 -0
- data/app/models/spree/legacy_user.rb +19 -1
- data/app/models/spree/line_item.rb +15 -4
- data/app/models/spree/log_entry.rb +2 -0
- data/app/models/spree/market.rb +83 -0
- data/app/models/spree/market_country.rb +25 -0
- data/app/models/spree/metafield.rb +2 -0
- data/app/models/spree/metafield_definition.rb +2 -0
- data/app/models/spree/newsletter_subscriber.rb +2 -0
- data/app/models/spree/option_type.rb +2 -0
- data/app/models/spree/option_value.rb +2 -0
- data/app/models/spree/order/address_book.rb +2 -1
- data/app/models/spree/order.rb +73 -45
- data/app/models/spree/payment.rb +6 -1
- data/app/models/spree/payment_capture_event.rb +2 -0
- data/app/models/spree/payment_method.rb +59 -1
- data/app/models/spree/payment_session.rb +109 -0
- data/app/models/spree/payment_sessions/bogus.rb +4 -0
- data/app/models/spree/payment_setup_session.rb +79 -0
- data/app/models/spree/payment_source.rb +2 -0
- data/app/models/spree/permission_sets/default_customer.rb +19 -0
- data/app/models/spree/policy.rb +2 -0
- data/app/models/spree/post.rb +2 -0
- data/app/models/spree/post_category.rb +2 -0
- data/app/models/spree/price.rb +26 -2
- data/app/models/spree/price_list.rb +2 -0
- data/app/models/spree/price_rule.rb +2 -0
- data/app/models/spree/price_rules/market_rule.rb +19 -0
- data/app/models/spree/product.rb +41 -29
- data/app/models/spree/promotion/rules/country.rb +1 -1
- data/app/models/spree/promotion.rb +3 -1
- data/app/models/spree/promotion_action.rb +2 -0
- data/app/models/spree/promotion_category.rb +2 -0
- data/app/models/spree/promotion_rule.rb +2 -0
- data/app/models/spree/prototype.rb +2 -0
- data/app/models/spree/refund.rb +2 -0
- data/app/models/spree/refund_reason.rb +2 -0
- data/app/models/spree/reimbursement/credit.rb +2 -0
- data/app/models/spree/reimbursement.rb +2 -0
- data/app/models/spree/reimbursement_type.rb +2 -0
- data/app/models/spree/report.rb +3 -1
- data/app/models/spree/return_authorization.rb +2 -0
- data/app/models/spree/return_authorization_reason.rb +2 -0
- data/app/models/spree/return_item.rb +2 -0
- data/app/models/spree/role.rb +2 -0
- data/app/models/spree/shipment.rb +11 -1
- data/app/models/spree/shipping_category.rb +2 -0
- data/app/models/spree/shipping_method.rb +2 -0
- data/app/models/spree/shipping_method_category.rb +2 -0
- data/app/models/spree/shipping_rate.rb +2 -0
- data/app/models/spree/state_change.rb +2 -0
- data/app/models/spree/stock_item.rb +2 -0
- data/app/models/spree/stock_location.rb +2 -0
- data/app/models/spree/stock_movement.rb +2 -0
- data/app/models/spree/stock_transfer.rb +2 -1
- data/app/models/spree/store.rb +110 -179
- data/app/models/spree/store_credit.rb +2 -0
- data/app/models/spree/store_credit_category.rb +2 -0
- data/app/models/spree/store_credit_event.rb +2 -0
- data/app/models/spree/store_credit_type.rb +2 -0
- data/app/models/spree/store_product.rb +2 -0
- data/app/models/spree/tax_category.rb +2 -0
- data/app/models/spree/tax_rate.rb +2 -0
- data/app/models/spree/taxon.rb +4 -1
- data/app/models/spree/taxon_image.rb +8 -0
- data/app/models/spree/taxon_rule.rb +2 -0
- data/app/models/spree/taxonomy.rb +2 -0
- data/app/models/spree/user_identity.rb +81 -0
- data/app/models/spree/variant.rb +13 -3
- data/app/models/spree/webhook_delivery.rb +2 -0
- data/app/models/spree/webhook_endpoint.rb +2 -0
- data/app/models/spree/wished_item.rb +15 -0
- data/app/models/spree/wishlist.rb +2 -8
- data/app/models/spree/zone.rb +2 -6
- data/app/presenters/spree/filters/price_presenter.rb +1 -0
- data/app/presenters/spree/filters/price_range_presenter.rb +1 -0
- data/app/presenters/spree/filters/quantified_price_range_presenter.rb +1 -0
- data/app/presenters/spree/product_summary_presenter.rb +1 -0
- data/app/services/spree/addresses/helper.rb +22 -3
- data/app/services/spree/cart/associate.rb +19 -6
- data/app/services/spree/checkout/select_shipping_method.rb +13 -1
- data/app/services/spree/classifications/reposition.rb +5 -0
- data/app/services/spree/imports/row_processors/customer.rb +70 -0
- data/app/services/spree/orders/approve.rb +5 -3
- data/app/services/spree/orders/cancel.rb +9 -4
- data/app/services/spree/products/prepare_nested_attributes.rb +1 -1
- data/app/services/spree/sample_data/import_runner.rb +54 -0
- data/app/services/spree/sample_data/loader.rb +78 -0
- data/app/services/spree/seeds/admin_user.rb +2 -3
- data/app/services/spree/seeds/all.rb +1 -0
- data/app/services/spree/seeds/api_keys.rb +16 -0
- data/app/services/spree/seeds/stores.rb +2 -4
- data/app/sorters/spree/orders/sort.rb +4 -0
- data/app/subscribers/spree/product_metrics_subscriber.rb +4 -4
- data/config/brakeman.ignore +120 -0
- data/config/locales/en.yml +20 -1
- data/db/migrate/20250923141900_create_spree_user_identities.rb +17 -0
- data/db/migrate/20260123000000_create_spree_api_keys.rb +19 -0
- data/db/migrate/20260131000000_add_thumbnail_id_to_spree_variants_and_products.rb +9 -0
- data/db/migrate/20260213000000_create_spree_payment_sessions.rb +27 -0
- data/db/migrate/20260218000000_create_spree_payment_setup_sessions.rb +24 -0
- data/db/migrate/20260220000000_create_spree_markets.rb +29 -0
- data/db/sample_data/customers.csv +21 -0
- data/db/sample_data/metafield_definitions.rb +7 -0
- data/db/sample_data/orders.rb +131 -0
- data/db/sample_data/payment_methods.rb +17 -0
- data/db/sample_data/posts.rb +7 -0
- data/db/sample_data/products.csv +1083 -0
- data/db/sample_data/promotions.rb +8 -0
- data/db/sample_data/shipping_methods.rb +39 -0
- data/lib/generators/spree/authentication/devise/devise_generator.rb +2 -2
- data/lib/generators/spree/authentication/dummy/dummy_generator.rb +54 -0
- data/lib/generators/spree/authentication/dummy/templates/authentication_helpers.rb.tt +52 -0
- data/lib/generators/spree/authentication/dummy/templates/create_spree_admin_users.rb.tt +33 -0
- data/lib/generators/spree/dummy/dummy_generator.rb +1 -1
- data/lib/spree/core/configuration.rb +1 -0
- data/lib/spree/core/controller_helpers/common.rb +6 -0
- data/lib/spree/core/controller_helpers/currency.rb +5 -0
- data/lib/spree/core/controller_helpers/order.rb +5 -1
- data/lib/spree/core/controller_helpers/store.rb +1 -1
- data/lib/spree/core/dependencies.rb +1 -1
- data/lib/spree/core/engine.rb +17 -4
- data/lib/spree/core/pricing/context.rb +6 -3
- data/lib/spree/core/product_filters.rb +14 -0
- data/lib/spree/core/query_filters/comparable.rb +4 -0
- data/lib/spree/core/query_filters/text.rb +4 -0
- data/lib/spree/core/version.rb +1 -1
- data/lib/spree/core.rb +4 -4
- data/lib/spree/database_type_utilities.rb +7 -0
- data/lib/spree/events.rb +17 -10
- data/lib/spree/money.rb +2 -9
- data/lib/spree/permitted_attributes.rb +19 -7
- data/lib/spree/testing_support/capybara_config.rb +2 -2
- data/lib/spree/testing_support/common_rake.rb +15 -4
- data/lib/spree/testing_support/factories/api_key_factory.rb +19 -0
- data/lib/spree/testing_support/factories/custom_domain_factory.rb +7 -5
- data/lib/spree/testing_support/factories/import_factory.rb +12 -0
- data/lib/spree/testing_support/factories/market_factory.rb +35 -0
- data/lib/spree/testing_support/factories/order_factory.rb +3 -1
- data/lib/spree/testing_support/factories/payment_method_factory.rb +2 -0
- data/lib/spree/testing_support/factories/payment_session_factory.rb +47 -0
- data/lib/spree/testing_support/factories/payment_setup_session_factory.rb +31 -0
- data/lib/spree/testing_support/factories/price_rule_factory.rb +10 -0
- data/lib/spree/testing_support/factories/user_identity_factory.rb +15 -0
- data/lib/spree/testing_support/store.rb +3 -2
- data/lib/spree/webhooks.rb +7 -7
- data/lib/tasks/images.rake +20 -0
- data/lib/tasks/markets.rake +40 -0
- data/lib/tasks/sample_data.rake +15 -0
- data/spec/fixtures/files/customers_import.csv +4 -0
- metadata +99 -59
- data/LICENSE.md +0 -57
- data/app/finders/spree/stores/find_current.rb +0 -28
- data/app/models/spree/custom_domain.rb +0 -59
- data/app/serializers/spree/events/asset_serializer.rb +0 -22
- data/app/serializers/spree/events/base_serializer.rb +0 -61
- data/app/serializers/spree/events/customer_return_serializer.rb +0 -20
- data/app/serializers/spree/events/digital_link_serializer.rb +0 -20
- data/app/serializers/spree/events/digital_serializer.rb +0 -18
- data/app/serializers/spree/events/export_serializer.rb +0 -22
- data/app/serializers/spree/events/gift_card_batch_serializer.rb +0 -24
- data/app/serializers/spree/events/gift_card_serializer.rb +0 -29
- data/app/serializers/spree/events/image_serializer.rb +0 -9
- data/app/serializers/spree/events/import_row_serializer.rb +0 -23
- data/app/serializers/spree/events/import_serializer.rb +0 -24
- data/app/serializers/spree/events/invitation_serializer.rb +0 -28
- data/app/serializers/spree/events/line_item_serializer.rb +0 -31
- data/app/serializers/spree/events/newsletter_subscriber_serializer.rb +0 -21
- data/app/serializers/spree/events/order_serializer.rb +0 -39
- data/app/serializers/spree/events/payment_serializer.rb +0 -24
- data/app/serializers/spree/events/post_category_serializer.rb +0 -20
- data/app/serializers/spree/events/post_serializer.rb +0 -26
- data/app/serializers/spree/events/price_serializer.rb +0 -22
- data/app/serializers/spree/events/product_serializer.rb +0 -24
- data/app/serializers/spree/events/promotion_serializer.rb +0 -32
- data/app/serializers/spree/events/refund_serializer.rb +0 -23
- data/app/serializers/spree/events/reimbursement_serializer.rb +0 -22
- data/app/serializers/spree/events/report_serializer.rb +0 -23
- data/app/serializers/spree/events/return_authorization_serializer.rb +0 -22
- data/app/serializers/spree/events/return_item_serializer.rb +0 -27
- data/app/serializers/spree/events/shipment_serializer.rb +0 -24
- data/app/serializers/spree/events/stock_item_serializer.rb +0 -22
- data/app/serializers/spree/events/stock_movement_serializer.rb +0 -22
- data/app/serializers/spree/events/stock_transfer_serializer.rb +0 -22
- data/app/serializers/spree/events/store_credit_serializer.rb +0 -30
- data/app/serializers/spree/events/user_serializer.rb +0 -18
- data/app/serializers/spree/events/variant_serializer.rb +0 -34
- data/app/serializers/spree/events/wished_item_serializer.rb +0 -20
- data/app/serializers/spree/events/wishlist_serializer.rb +0 -22
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Stores
|
|
3
|
+
module Markets
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
included do
|
|
7
|
+
has_many :markets, class_name: 'Spree::Market', dependent: :destroy
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Returns the default market for this store
|
|
11
|
+
# @return [Spree::Market, nil]
|
|
12
|
+
def default_market
|
|
13
|
+
@default_market ||= Spree::Market.default_for_store(self)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Returns the default country, derived from the default market
|
|
17
|
+
# @return [Spree::Country, nil]
|
|
18
|
+
def default_country
|
|
19
|
+
if has_markets?
|
|
20
|
+
default_market&.default_country
|
|
21
|
+
else
|
|
22
|
+
super
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Returns the default country ID, derived from the default market
|
|
27
|
+
# @return [Integer, nil]
|
|
28
|
+
def default_country_id
|
|
29
|
+
if has_markets?
|
|
30
|
+
default_country&.id
|
|
31
|
+
else
|
|
32
|
+
read_attribute(:default_country_id)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Returns the default locale, delegating to the default market when markets exist
|
|
37
|
+
# Falls back to the store's own default_locale column
|
|
38
|
+
# @return [String, nil]
|
|
39
|
+
def default_locale
|
|
40
|
+
if has_markets?
|
|
41
|
+
default_market&.default_locale || read_attribute(:default_locale)
|
|
42
|
+
else
|
|
43
|
+
read_attribute(:default_locale)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Returns the default currency, delegating to the default market when markets exist
|
|
48
|
+
# Falls back to the store's own default_currency column
|
|
49
|
+
# @return [String, nil]
|
|
50
|
+
def default_currency
|
|
51
|
+
if has_markets?
|
|
52
|
+
default_market&.currency || read_attribute(:default_currency)
|
|
53
|
+
else
|
|
54
|
+
read_attribute(:default_currency)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Returns the market that contains the given country
|
|
59
|
+
# @param country [Spree::Country]
|
|
60
|
+
# @return [Spree::Market, nil]
|
|
61
|
+
def market_for_country(country)
|
|
62
|
+
Spree::Market.for_country(country, store: self)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Returns countries from all markets as an ActiveRecord relation
|
|
66
|
+
# @return [ActiveRecord::Relation<Spree::Country>]
|
|
67
|
+
def countries_from_markets
|
|
68
|
+
Spree::Country
|
|
69
|
+
.where(id: Spree::Country.joins(market_countries: :market).where(Spree::Market.table_name => { store_id: id, deleted_at: nil }).select(:id))
|
|
70
|
+
.order(:name)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Returns the countries available for checkout, derived from markets
|
|
74
|
+
# @return [Array<Spree::Country>]
|
|
75
|
+
def countries_available_for_checkout
|
|
76
|
+
@countries_available_for_checkout ||= Rails.cache.fetch(countries_available_for_checkout_cache_key) do
|
|
77
|
+
if markets.any?
|
|
78
|
+
markets.flat_map(&:countries).uniq.sort_by(&:name)
|
|
79
|
+
else
|
|
80
|
+
Spree::Country.all.to_a
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Returns supported currencies derived from markets, falling back to store attributes
|
|
86
|
+
# @return [Array<Money::Currency>]
|
|
87
|
+
def supported_currencies_list
|
|
88
|
+
@supported_currencies_list ||= if markets.any?
|
|
89
|
+
markets.pluck(:currency).uniq.map do |code|
|
|
90
|
+
::Money::Currency.find(code)
|
|
91
|
+
end.compact.sort_by { |c| c.iso_code == default_currency ? 0 : 1 }
|
|
92
|
+
else
|
|
93
|
+
legacy_supported_currencies_list
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Returns supported locales derived from markets, falling back to store attributes
|
|
98
|
+
# @return [Array<String>]
|
|
99
|
+
def supported_locales_list
|
|
100
|
+
@supported_locales_list ||= if markets.any?
|
|
101
|
+
(markets.flat_map(&:supported_locales_list) << default_locale).compact.uniq.sort
|
|
102
|
+
else
|
|
103
|
+
legacy_supported_locales_list
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
private
|
|
108
|
+
|
|
109
|
+
def has_markets?
|
|
110
|
+
persisted? && (markets.loaded? ? markets.any? : markets.exists?)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def legacy_supported_currencies_list
|
|
114
|
+
([default_currency] + read_attribute(:supported_currencies).to_s.split(',')).uniq.map(&:to_s).map do |code|
|
|
115
|
+
::Money::Currency.find(code.strip)
|
|
116
|
+
end.compact.sort_by { |currency| currency.iso_code == default_currency ? 0 : 1 }
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def legacy_supported_locales_list
|
|
120
|
+
(read_attribute(:supported_locales).to_s.split(',') << default_locale).compact.uniq.sort
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -2,16 +2,18 @@ module Spree
|
|
|
2
2
|
module UserMethods
|
|
3
3
|
extend ActiveSupport::Concern
|
|
4
4
|
|
|
5
|
+
include Spree::PrefixedId
|
|
5
6
|
include Spree::Metafields
|
|
6
7
|
include Spree::UserPaymentSource
|
|
7
8
|
include Spree::UserReporting
|
|
8
9
|
include Spree::UserRoles
|
|
9
|
-
include Spree::AdminUserMethods
|
|
10
10
|
include Spree::RansackableAttributes
|
|
11
11
|
include Spree::MultiSearchable
|
|
12
12
|
include Spree::Publishable
|
|
13
13
|
|
|
14
14
|
included do
|
|
15
|
+
has_prefix_id :cus # Stripe: cus_
|
|
16
|
+
|
|
15
17
|
# Enable lifecycle events for user models
|
|
16
18
|
publishes_lifecycle_events
|
|
17
19
|
|
|
@@ -40,6 +42,7 @@ module Spree
|
|
|
40
42
|
has_many :gift_cards, class_name: 'Spree::GiftCard', foreign_key: :user_id, dependent: :destroy
|
|
41
43
|
has_many :customer_group_users, class_name: 'Spree::CustomerGroupUser', foreign_key: :user_id, as: :user, dependent: :destroy
|
|
42
44
|
has_many :customer_groups, through: :customer_group_users, class_name: 'Spree::CustomerGroup'
|
|
45
|
+
has_many :identities, class_name: 'Spree::UserIdentity', as: :user, dependent: :destroy
|
|
43
46
|
belongs_to :ship_address, class_name: 'Spree::Address', optional: true
|
|
44
47
|
belongs_to :bill_address, class_name: 'Spree::Address', optional: true
|
|
45
48
|
|
|
@@ -143,13 +146,10 @@ module Spree
|
|
|
143
146
|
end
|
|
144
147
|
|
|
145
148
|
# Returns true if the user can be deleted
|
|
149
|
+
# Customers can be deleted if they have no completed orders
|
|
146
150
|
# @return [Boolean]
|
|
147
151
|
def can_be_deleted?
|
|
148
|
-
|
|
149
|
-
Spree::Store.current.users.where.not(id: id).exists?
|
|
150
|
-
else
|
|
151
|
-
orders.complete.none?
|
|
152
|
-
end
|
|
152
|
+
orders.complete.none?
|
|
153
153
|
end
|
|
154
154
|
|
|
155
155
|
# Returns the CSV row representation of the user
|
|
@@ -166,7 +166,7 @@ module Spree
|
|
|
166
166
|
end
|
|
167
167
|
|
|
168
168
|
def event_serializer_class
|
|
169
|
-
Spree::
|
|
169
|
+
'Spree::Api::V3::CustomerSerializer'.safe_constantize
|
|
170
170
|
end
|
|
171
171
|
|
|
172
172
|
def event_prefix
|
|
@@ -4,6 +4,8 @@ module Spree
|
|
|
4
4
|
|
|
5
5
|
included do
|
|
6
6
|
has_many :credit_cards, class_name: 'Spree::CreditCard', foreign_key: :user_id, dependent: :destroy
|
|
7
|
+
has_many :payment_setup_sessions, class_name: 'Spree::PaymentSetupSession', foreign_key: :customer_id, dependent: :destroy
|
|
8
|
+
|
|
7
9
|
def default_credit_card
|
|
8
10
|
credit_cards.default.first
|
|
9
11
|
end
|
data/app/models/spree/ability.rb
CHANGED
|
@@ -30,11 +30,21 @@ module Spree
|
|
|
30
30
|
# modify the default +Ability+ of an application. The +ability+ argument must be a class that includes
|
|
31
31
|
# the +CanCan::Ability+ module. The registered ability should behave properly as a stand-alone class
|
|
32
32
|
# and therefore should be easy to test in isolation.
|
|
33
|
+
# @deprecated Use Spree::PermissionSets instead. Will be removed in Spree 5.5.
|
|
33
34
|
def self.register_ability(ability)
|
|
35
|
+
Spree::Deprecation.warn(
|
|
36
|
+
'Spree::Ability.register_ability is deprecated and will be removed in Spree 5.5. ' \
|
|
37
|
+
'Please use Spree::PermissionSets instead. See Spree::PermissionSets::Base for details.'
|
|
38
|
+
)
|
|
34
39
|
abilities.add(ability)
|
|
35
40
|
end
|
|
36
41
|
|
|
42
|
+
# @deprecated Use Spree::PermissionSets instead. Will be removed in Spree 5.5.
|
|
37
43
|
def self.remove_ability(ability)
|
|
44
|
+
Spree::Deprecation.warn(
|
|
45
|
+
'Spree::Ability.remove_ability is deprecated and will be removed in Spree 5.5. ' \
|
|
46
|
+
'Please use Spree::PermissionSets instead. See Spree::PermissionSets::Base for details.'
|
|
47
|
+
)
|
|
38
48
|
abilities.delete(ability)
|
|
39
49
|
end
|
|
40
50
|
|
|
@@ -165,6 +175,9 @@ module Spree
|
|
|
165
175
|
wished_item.wishlist.user == user
|
|
166
176
|
end
|
|
167
177
|
can :accept, Spree::Invitation, invitee_id: [user.id, nil], invitee_type: user.class.name, status: 'pending'
|
|
178
|
+
can :show, ::Spree::DigitalLink do |digital_link, token|
|
|
179
|
+
digital_link.token == token
|
|
180
|
+
end
|
|
168
181
|
can :read, ::Spree::Policy
|
|
169
182
|
can :read, ::Spree::Page if defined?(Spree::Page)
|
|
170
183
|
can :read, ::Spree::Post if defined?(Spree::Post)
|
data/app/models/spree/address.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
module Spree
|
|
2
2
|
class Address < Spree.base_class
|
|
3
|
+
has_prefix_id :addr # Spree-specific: address
|
|
4
|
+
|
|
3
5
|
require 'validates_zipcode'
|
|
4
6
|
|
|
5
7
|
include Spree::Metafields
|
|
@@ -48,6 +50,8 @@ module Spree
|
|
|
48
50
|
|
|
49
51
|
after_initialize :set_default_values, if: -> { new_record? && user.present? }
|
|
50
52
|
|
|
53
|
+
before_validation :normalize_country
|
|
54
|
+
before_validation :normalize_state
|
|
51
55
|
before_validation :clear_invalid_state_entities, if: -> { country.present? }, on: :update
|
|
52
56
|
before_validation :remove_emoji_and_normalize
|
|
53
57
|
|
|
@@ -83,6 +87,16 @@ module Spree
|
|
|
83
87
|
|
|
84
88
|
alias_attribute :postal_code, :zipcode
|
|
85
89
|
|
|
90
|
+
# Writer methods for API convenience - these set country/state from ISO/abbr codes
|
|
91
|
+
# The reader methods (country_iso, state_abbr) are delegates to country.iso and state.abbr
|
|
92
|
+
def country_iso=(value)
|
|
93
|
+
@country_iso_input = value
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def state_abbr=(value)
|
|
97
|
+
@state_abbr_input = value
|
|
98
|
+
end
|
|
99
|
+
|
|
86
100
|
self.whitelisted_ransackable_attributes = ADDRESS_FIELDS
|
|
87
101
|
self.whitelisted_ransackable_associations = %w[country state user]
|
|
88
102
|
|
|
@@ -247,6 +261,22 @@ module Spree
|
|
|
247
261
|
self.phone ||= user.phone
|
|
248
262
|
end
|
|
249
263
|
|
|
264
|
+
def normalize_country
|
|
265
|
+
iso = @country_iso_input
|
|
266
|
+
return if iso.blank?
|
|
267
|
+
|
|
268
|
+
self.country = Spree::Country.by_iso(iso)
|
|
269
|
+
@country_iso_input = nil
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def normalize_state
|
|
273
|
+
abbr = @state_abbr_input
|
|
274
|
+
return if abbr.blank? || country.blank?
|
|
275
|
+
|
|
276
|
+
self.state = country.states.find_by(abbr: abbr)
|
|
277
|
+
@state_abbr_input = nil
|
|
278
|
+
end
|
|
279
|
+
|
|
250
280
|
def clear_state
|
|
251
281
|
self.state = nil
|
|
252
282
|
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
class ApiKey < Spree.base_class
|
|
3
|
+
has_prefix_id :key # Spree-specific: api key
|
|
4
|
+
|
|
5
|
+
KEY_TYPES = %w[publishable secret].freeze
|
|
6
|
+
PREFIXES = { 'publishable' => 'pk_', 'secret' => 'sk_' }.freeze
|
|
7
|
+
TOKEN_LENGTH = 24
|
|
8
|
+
|
|
9
|
+
belongs_to :store, class_name: 'Spree::Store'
|
|
10
|
+
belongs_to :created_by, polymorphic: true, optional: true
|
|
11
|
+
belongs_to :revoked_by, polymorphic: true, optional: true
|
|
12
|
+
|
|
13
|
+
validates :name, presence: true
|
|
14
|
+
validates :key_type, presence: true, inclusion: { in: KEY_TYPES }
|
|
15
|
+
validates :token, presence: true, uniqueness: { scope: spree_base_uniqueness_scope }
|
|
16
|
+
validates :store, presence: true
|
|
17
|
+
|
|
18
|
+
before_validation :generate_token, on: :create
|
|
19
|
+
|
|
20
|
+
scope :active, -> { where(revoked_at: nil) }
|
|
21
|
+
scope :revoked, -> { where.not(revoked_at: nil) }
|
|
22
|
+
scope :publishable, -> { where(key_type: 'publishable') }
|
|
23
|
+
scope :secret, -> { where(key_type: 'secret') }
|
|
24
|
+
|
|
25
|
+
def publishable?
|
|
26
|
+
key_type == 'publishable'
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def secret?
|
|
30
|
+
key_type == 'secret'
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def active?
|
|
34
|
+
revoked_at.nil?
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def revoke!(user = nil)
|
|
38
|
+
update!(revoked_at: Time.current, revoked_by: user)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def generate_token
|
|
44
|
+
self.token ||= "#{PREFIXES[key_type]}#{SecureRandom.base58(TOKEN_LENGTH)}"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
data/app/models/spree/asset.rb
CHANGED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Authentication
|
|
3
|
+
module Strategies
|
|
4
|
+
class BaseStrategy
|
|
5
|
+
attr_reader :params, :request_env, :user_class
|
|
6
|
+
|
|
7
|
+
def initialize(params:, request_env:, user_class: nil)
|
|
8
|
+
@params = params
|
|
9
|
+
@request_env = request_env
|
|
10
|
+
@user_class = user_class || Spree.user_class
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Returns Result object with user on success
|
|
14
|
+
# @return [Spree::ServiceModule::Result]
|
|
15
|
+
def authenticate
|
|
16
|
+
raise NotImplementedError, 'Subclass must implement #authenticate'
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Returns provider identifier (e.g., 'google', 'email')
|
|
20
|
+
# @return [String]
|
|
21
|
+
def provider
|
|
22
|
+
raise NotImplementedError, 'Subclass must implement #provider'
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
protected
|
|
26
|
+
|
|
27
|
+
# Success result with user
|
|
28
|
+
def success(user)
|
|
29
|
+
Spree::ServiceModule::Result.new(success: true, value: user)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Failure result with error message
|
|
33
|
+
def failure(message)
|
|
34
|
+
Spree::ServiceModule::Result.new(success: false, error: message)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Find user by email
|
|
38
|
+
def find_user_by_email(email)
|
|
39
|
+
user_class.find_by(email: email)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Find or create user identity
|
|
43
|
+
def find_or_create_user_from_oauth(provider:, uid:, info:, tokens: {})
|
|
44
|
+
Spree::UserIdentity.find_or_create_from_oauth(
|
|
45
|
+
provider: provider,
|
|
46
|
+
uid: uid,
|
|
47
|
+
info: info,
|
|
48
|
+
tokens: tokens,
|
|
49
|
+
user_class: user_class
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Authentication
|
|
3
|
+
module Strategies
|
|
4
|
+
class EmailPasswordStrategy < BaseStrategy
|
|
5
|
+
def authenticate
|
|
6
|
+
email = params[:email]
|
|
7
|
+
password = params[:password]
|
|
8
|
+
|
|
9
|
+
return failure('Email is required') if email.blank?
|
|
10
|
+
return failure('Password is required') if password.blank?
|
|
11
|
+
|
|
12
|
+
user = find_user_by_email(email)
|
|
13
|
+
return failure('Invalid email or password') unless user
|
|
14
|
+
|
|
15
|
+
if validate_password(user, password)
|
|
16
|
+
success(user)
|
|
17
|
+
else
|
|
18
|
+
failure('Invalid email or password')
|
|
19
|
+
end
|
|
20
|
+
rescue => e
|
|
21
|
+
Rails.logger.error "EmailPasswordStrategy authentication failed: #{e.message}"
|
|
22
|
+
failure('Authentication failed')
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def provider
|
|
26
|
+
'email'
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def validate_password(user, password)
|
|
32
|
+
# Try Devise's valid_password? method first (most common)
|
|
33
|
+
if user.respond_to?(:valid_password?)
|
|
34
|
+
user.valid_password?(password)
|
|
35
|
+
# Fallback to authenticate method (for has_secure_password)
|
|
36
|
+
elsif user.respond_to?(:authenticate)
|
|
37
|
+
user.authenticate(password).present?
|
|
38
|
+
else
|
|
39
|
+
# No password authentication available
|
|
40
|
+
Rails.logger.warn "User class #{user.class} does not implement password authentication"
|
|
41
|
+
false
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
data/app/models/spree/base.rb
CHANGED
data/app/models/spree/country.rb
CHANGED
|
@@ -11,6 +11,8 @@ module Spree
|
|
|
11
11
|
dependent: :destroy,
|
|
12
12
|
foreign_key: :zoneable_id
|
|
13
13
|
has_many :zones, through: :zone_members, class_name: 'Spree::Zone'
|
|
14
|
+
has_many :market_countries, class_name: 'Spree::MarketCountry', dependent: :destroy
|
|
15
|
+
has_many :markets, through: :market_countries, class_name: 'Spree::Market'
|
|
14
16
|
|
|
15
17
|
validates :name, :iso_name, :iso, :iso3, presence: true, uniqueness: { case_sensitive: false, scope: spree_base_uniqueness_scope }
|
|
16
18
|
|
|
@@ -23,6 +25,42 @@ module Spree
|
|
|
23
25
|
self == store.default_country
|
|
24
26
|
end
|
|
25
27
|
|
|
28
|
+
def self.to_tom_select_json
|
|
29
|
+
pluck(:name, :id, :iso).map do |name, id, iso|
|
|
30
|
+
{
|
|
31
|
+
id: id,
|
|
32
|
+
name: "#{iso_to_emoji_flag(iso)} #{name}"
|
|
33
|
+
}
|
|
34
|
+
end.as_json
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.iso_to_emoji_flag(iso)
|
|
38
|
+
iso.upcase.chars.map { |c| (c.ord + 127397).chr(Encoding::UTF_8) }.join
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Returns the currency for this country from its market in the current store.
|
|
42
|
+
# Looks up which market contains this country and returns that market's currency.
|
|
43
|
+
#
|
|
44
|
+
# @return [String, nil] currency code (e.g., 'USD', 'EUR') or nil if no market found
|
|
45
|
+
def market_currency
|
|
46
|
+
Spree::Current.store&.market_for_country(self)&.currency
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Returns the default locale for this country from its market in the current store.
|
|
50
|
+
# Looks up which market contains this country and returns that market's default locale.
|
|
51
|
+
#
|
|
52
|
+
# @return [String, nil] locale code (e.g., 'en', 'de') or nil if no market found
|
|
53
|
+
def market_locale
|
|
54
|
+
Spree::Current.store&.market_for_country(self)&.default_locale
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Returns the supported locales for this country from its market in the current store.
|
|
58
|
+
#
|
|
59
|
+
# @return [Array<String>] locale codes (e.g., ['en', 'fr']) or empty array if no market found
|
|
60
|
+
def market_supported_locales
|
|
61
|
+
Spree::Current.store&.market_for_country(self)&.supported_locales_list || []
|
|
62
|
+
end
|
|
63
|
+
|
|
26
64
|
def <=>(other)
|
|
27
65
|
name <=> other.name
|
|
28
66
|
end
|
|
@@ -30,5 +68,23 @@ module Spree
|
|
|
30
68
|
def to_s
|
|
31
69
|
name
|
|
32
70
|
end
|
|
71
|
+
|
|
72
|
+
# Returns the default currency code for this country (e.g., 'USD', 'EUR')
|
|
73
|
+
# Uses the countries gem (ISO3166) for accurate currency data
|
|
74
|
+
def default_currency
|
|
75
|
+
iso3166_country&.currency_code
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Returns the default locale/language for this country (e.g., 'en', 'de')
|
|
79
|
+
# Uses the countries gem (ISO3166) for accurate language data
|
|
80
|
+
def default_locale
|
|
81
|
+
iso3166_country&.languages&.first
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
private
|
|
85
|
+
|
|
86
|
+
def iso3166_country
|
|
87
|
+
@iso3166_country ||= ISO3166::Country.new(iso)
|
|
88
|
+
end
|
|
33
89
|
end
|
|
34
90
|
end
|
data/app/models/spree/current.rb
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
module Spree
|
|
2
2
|
class Current < ::ActiveSupport::CurrentAttributes
|
|
3
|
-
attribute :store, :currency, :zone, :price_lists, :global_pricing_context
|
|
3
|
+
attribute :store, :market, :currency, :zone, :price_lists, :global_pricing_context
|
|
4
4
|
|
|
5
5
|
def store
|
|
6
6
|
super || Spree::Store.default
|
|
7
7
|
end
|
|
8
8
|
|
|
9
|
+
def market
|
|
10
|
+
super || store&.default_market
|
|
11
|
+
end
|
|
12
|
+
|
|
9
13
|
def currency
|
|
10
|
-
super || store&.default_currency
|
|
14
|
+
super || market&.currency || store&.default_currency
|
|
11
15
|
end
|
|
12
16
|
|
|
13
17
|
def zone
|
|
14
|
-
super || Spree::Zone.default_tax
|
|
18
|
+
super || Spree::Zone.default_tax
|
|
15
19
|
end
|
|
16
20
|
|
|
17
21
|
def price_lists
|
|
@@ -26,7 +30,8 @@ module Spree
|
|
|
26
30
|
self.global_pricing_context = Spree::Pricing::Context.new(
|
|
27
31
|
currency: currency,
|
|
28
32
|
store: store,
|
|
29
|
-
zone: zone
|
|
33
|
+
zone: zone,
|
|
34
|
+
market: market
|
|
30
35
|
)
|
|
31
36
|
end
|
|
32
37
|
end
|
data/app/models/spree/digital.rb
CHANGED
data/app/models/spree/export.rb
CHANGED
|
@@ -2,11 +2,12 @@ require 'csv'
|
|
|
2
2
|
|
|
3
3
|
module Spree
|
|
4
4
|
class Export < Spree.base_class
|
|
5
|
+
has_prefix_id :exp
|
|
6
|
+
|
|
5
7
|
SUPPORTED_FILE_FORMATS = %i[csv].freeze
|
|
6
8
|
|
|
7
9
|
include Spree::SingleStoreResource
|
|
8
10
|
include Spree::NumberIdentifier
|
|
9
|
-
include Spree::NumberAsParam
|
|
10
11
|
include Spree::VendorConcern if defined?(Spree::VendorConcern)
|
|
11
12
|
|
|
12
13
|
include Spree::Core::NumberGenerator.new(prefix: 'EF')
|
|
@@ -58,7 +59,7 @@ module Spree
|
|
|
58
59
|
attribute :record_selection, :string, default: 'filtered'
|
|
59
60
|
|
|
60
61
|
def event_serializer_class
|
|
61
|
-
Spree::
|
|
62
|
+
'Spree::Api::V3::ExportSerializer'.safe_constantize
|
|
62
63
|
end
|
|
63
64
|
|
|
64
65
|
def done?
|