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.
- 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/jobs/spree/images/save_from_url_job.rb +23 -47
- 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 +5 -4
- 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 -4
- 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 +75 -47
- 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 -4
- 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 -4
- 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 -17
- 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/data_feeds/google/required_attributes.rb +8 -3
- data/app/services/spree/gift_cards/apply.rb +4 -5
- 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 -5
- 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 -1
- 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 +88 -63
- 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/presenters/spree/csv/formula_sanitizer.rb +0 -28
- 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
data/app/models/spree/variant.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
module Spree
|
|
2
2
|
class Variant < Spree.base_class
|
|
3
|
+
has_prefix_id :variant
|
|
4
|
+
|
|
3
5
|
acts_as_paranoid
|
|
4
6
|
acts_as_list scope: :product
|
|
5
7
|
|
|
@@ -47,6 +49,7 @@ module Spree
|
|
|
47
49
|
has_many :option_values, through: :option_value_variants, dependent: :destroy, class_name: 'Spree::OptionValue'
|
|
48
50
|
|
|
49
51
|
has_many :images, -> { order(:position) }, as: :viewable, dependent: :destroy, class_name: 'Spree::Image'
|
|
52
|
+
belongs_to :thumbnail, class_name: 'Spree::Image', optional: true
|
|
50
53
|
|
|
51
54
|
has_many :prices,
|
|
52
55
|
class_name: 'Spree::Price',
|
|
@@ -275,7 +278,7 @@ module Spree
|
|
|
275
278
|
# Returns the descriptive name of the variant.
|
|
276
279
|
# @return [String] the descriptive name of the variant
|
|
277
280
|
def descriptive_name
|
|
278
|
-
is_master? ?
|
|
281
|
+
is_master? ? name + ' - Master' : name + ' - ' + options_text
|
|
279
282
|
end
|
|
280
283
|
|
|
281
284
|
# use deleted? rather than checking the attribute directly. this
|
|
@@ -295,10 +298,17 @@ module Spree
|
|
|
295
298
|
image_count.positive?
|
|
296
299
|
end
|
|
297
300
|
|
|
298
|
-
# Returns default Image for Variant
|
|
301
|
+
# Returns default Image for Variant.
|
|
299
302
|
# @return [Spree::Image, nil]
|
|
300
303
|
def default_image
|
|
301
|
-
|
|
304
|
+
thumbnail
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
# Updates the thumbnail_id to the first image by position.
|
|
308
|
+
# Called when images are added, removed, or reordered.
|
|
309
|
+
def update_thumbnail!
|
|
310
|
+
first_image = images.order(:position).first
|
|
311
|
+
update_column(:thumbnail_id, first_image&.id)
|
|
302
312
|
end
|
|
303
313
|
|
|
304
314
|
# Returns first Image for Variant.
|
|
@@ -1,23 +1,19 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'ssrf_filter'
|
|
4
|
-
require 'resolv'
|
|
5
|
-
|
|
6
3
|
module Spree
|
|
7
4
|
class WebhookEndpoint < Spree.base_class
|
|
5
|
+
has_prefix_id :whe # Stripe: we_
|
|
6
|
+
|
|
8
7
|
acts_as_paranoid
|
|
9
8
|
|
|
10
9
|
include Spree::SingleStoreResource
|
|
11
10
|
|
|
12
|
-
encrypts :secret_key, deterministic: true if Rails.configuration.active_record.encryption.include?(:primary_key)
|
|
13
|
-
|
|
14
11
|
belongs_to :store, class_name: 'Spree::Store'
|
|
15
12
|
has_many :webhook_deliveries, class_name: 'Spree::WebhookDelivery', dependent: :destroy_async
|
|
16
13
|
|
|
17
14
|
validates :store, :url, presence: true
|
|
18
15
|
validates :url, format: { with: URI::DEFAULT_PARSER.make_regexp(%w[http https]), message: :invalid_url }
|
|
19
16
|
validates :active, inclusion: { in: [true, false] }
|
|
20
|
-
validate :url_must_not_resolve_to_private_ip, if: -> { url.present? && url_changed? }
|
|
21
17
|
|
|
22
18
|
before_create :generate_secret_key
|
|
23
19
|
|
|
@@ -55,16 +51,5 @@ module Spree
|
|
|
55
51
|
def generate_secret_key
|
|
56
52
|
self.secret_key ||= SecureRandom.hex(32)
|
|
57
53
|
end
|
|
58
|
-
|
|
59
|
-
def url_must_not_resolve_to_private_ip
|
|
60
|
-
uri = URI.parse(url)
|
|
61
|
-
blacklist = SsrfFilter::IPV4_BLACKLIST + SsrfFilter::IPV6_BLACKLIST
|
|
62
|
-
addresses = Resolv.getaddresses(uri.host)
|
|
63
|
-
if addresses.any? { |addr| blacklist.any? { |range| range.include?(IPAddr.new(addr)) } }
|
|
64
|
-
errors.add(:url, :internal_address_not_allowed)
|
|
65
|
-
end
|
|
66
|
-
rescue URI::InvalidURIError, Resolv::ResolvError, IPAddr::InvalidAddressError, ArgumentError
|
|
67
|
-
# URI format validation handles invalid URLs; DNS failures are not SSRF
|
|
68
|
-
end
|
|
69
54
|
end
|
|
70
55
|
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
module Spree
|
|
2
2
|
class WishedItem < Spree.base_class
|
|
3
|
+
has_prefix_id :wi # Spree-specific: wished item
|
|
4
|
+
|
|
3
5
|
extend DisplayMoney
|
|
4
6
|
money_methods :total, :price
|
|
5
7
|
|
|
@@ -14,6 +16,19 @@ module Spree
|
|
|
14
16
|
validates :variant, uniqueness: { scope: [:wishlist] }
|
|
15
17
|
validates :quantity, numericality: { only_integer: true, greater_than: 0 }
|
|
16
18
|
|
|
19
|
+
# This is a workaround to allow the variant_id to be set with a prefixed ID
|
|
20
|
+
# in the API.
|
|
21
|
+
#
|
|
22
|
+
# @param id [String] the prefixed ID of the variant
|
|
23
|
+
def variant_id=(id)
|
|
24
|
+
if id.to_s.include?('_')
|
|
25
|
+
decoded = Spree::Variant.decode_prefixed_id(id)
|
|
26
|
+
super(decoded)
|
|
27
|
+
else
|
|
28
|
+
super(id)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
17
32
|
def price(currency)
|
|
18
33
|
variant.amount_in(currency[:currency])
|
|
19
34
|
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
module Spree
|
|
2
2
|
class Wishlist < Spree.base_class
|
|
3
|
+
has_prefix_id :wl # Spree-specific: wishlist
|
|
4
|
+
|
|
3
5
|
include Spree::SingleStoreResource
|
|
4
6
|
|
|
5
7
|
publishes_lifecycle_events
|
|
@@ -24,10 +26,6 @@ module Spree
|
|
|
24
26
|
wished_items.exists?(variant_id: variant_id)
|
|
25
27
|
end
|
|
26
28
|
|
|
27
|
-
def to_param
|
|
28
|
-
token
|
|
29
|
-
end
|
|
30
|
-
|
|
31
29
|
# returns the number of wished items in the wishlist
|
|
32
30
|
#
|
|
33
31
|
# @return [Integer]
|
|
@@ -42,10 +40,6 @@ module Spree
|
|
|
42
40
|
@variant_ids ||= wished_items.pluck(:variant_id)
|
|
43
41
|
end
|
|
44
42
|
|
|
45
|
-
def self.get_by_param(param)
|
|
46
|
-
find_by(token: param)
|
|
47
|
-
end
|
|
48
|
-
|
|
49
43
|
private
|
|
50
44
|
|
|
51
45
|
def ensure_default_exists_and_is_unique
|
data/app/models/spree/zone.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
module Spree
|
|
2
2
|
class Zone < Spree.base_class
|
|
3
|
+
has_prefix_id :zone
|
|
4
|
+
|
|
3
5
|
include Spree::UniqueName
|
|
4
6
|
|
|
5
7
|
with_options dependent: :destroy, inverse_of: :zone do
|
|
@@ -18,7 +20,6 @@ module Spree
|
|
|
18
20
|
|
|
19
21
|
after_save :remove_defunct_members
|
|
20
22
|
after_save :remove_previous_default, if: %i[default_tax? saved_change_to_default_tax?]
|
|
21
|
-
before_destroy :nullify_checkout_zone
|
|
22
23
|
|
|
23
24
|
alias members zone_members
|
|
24
25
|
accepts_nested_attributes_for :zone_members, allow_destroy: true, reject_if: proc { |a| a['zoneable_id'].blank? }
|
|
@@ -198,10 +199,5 @@ module Spree
|
|
|
198
199
|
end
|
|
199
200
|
end
|
|
200
201
|
|
|
201
|
-
def nullify_checkout_zone
|
|
202
|
-
if id == Spree::Store.current.checkout_zone_id
|
|
203
|
-
Spree::Store.current.update(checkout_zone_id: nil)
|
|
204
|
-
end
|
|
205
|
-
end
|
|
206
202
|
end
|
|
207
203
|
end
|
|
@@ -7,6 +7,7 @@ module Spree
|
|
|
7
7
|
].freeze
|
|
8
8
|
|
|
9
9
|
def initialize(price:, quantifier:)
|
|
10
|
+
Spree::Deprecation.warn('Spree::Filters::QuantifiedPriceRangePresenter is deprecated and will be removed in Spree 5.5.')
|
|
10
11
|
if ALLOWED_QUANTIFIERS.exclude?(quantifier.to_sym)
|
|
11
12
|
raise ArgumentError, "quantifier must be one of: #{ALLOWED_QUANTIFIERS.join(', ')}"
|
|
12
13
|
end
|
|
@@ -21,13 +21,32 @@ module Spree
|
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
def fill_state_id(params)
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
# Always extract state_abbr - it's not a model attribute
|
|
25
|
+
state_abbr = params.delete(:state_abbr)
|
|
26
26
|
|
|
27
27
|
country ||= Spree::Country.find(params[:country_id]) if params[:country_id].present?
|
|
28
28
|
return params unless country
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
# Support state_abbr (state abbreviation code, e.g., "CA", "NY")
|
|
31
|
+
if state_abbr.present?
|
|
32
|
+
params[:state_id] = country.states.find_by(abbr: state_abbr)&.id
|
|
33
|
+
return params
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Support state_name - try to find matching state
|
|
37
|
+
if params[:state_name].present?
|
|
38
|
+
state = country.states.find_by(name: params[:state_name])
|
|
39
|
+
if state
|
|
40
|
+
# Found a matching state, use state_id
|
|
41
|
+
params[:state_id] = state.id
|
|
42
|
+
params.delete(:state_name)
|
|
43
|
+
elsif country.states_required?
|
|
44
|
+
# States required but not found - clear state_name so validation fails
|
|
45
|
+
params.delete(:state_name)
|
|
46
|
+
end
|
|
47
|
+
# If states not required and no match found, keep state_name as-is
|
|
48
|
+
end
|
|
49
|
+
|
|
31
50
|
params
|
|
32
51
|
end
|
|
33
52
|
|
|
@@ -3,13 +3,26 @@ module Spree
|
|
|
3
3
|
class Associate
|
|
4
4
|
prepend Spree::ServiceModule::Base
|
|
5
5
|
|
|
6
|
-
def call(guest_order:, user:)
|
|
7
|
-
if guest_order.user.
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
def call(guest_order:, user:, override_email: true, guest_only: false)
|
|
7
|
+
return failure(guest_order, 'Already assigned to a user') if guest_only && guest_order.user.present? && guest_order.user != user
|
|
8
|
+
|
|
9
|
+
guest_order.user = user
|
|
10
|
+
guest_order.email = user.email if override_email
|
|
11
|
+
guest_order.bill_address ||= user.bill_address
|
|
12
|
+
guest_order.ship_address ||= user.ship_address
|
|
13
|
+
|
|
14
|
+
changes = guest_order.slice(*Spree::Order::ASSOCIATED_USER_ATTRIBUTES)
|
|
15
|
+
|
|
16
|
+
# immediately persist the changes we just made, but don't use save
|
|
17
|
+
# since we might have an invalid address associated
|
|
18
|
+
ActiveRecord::Base.connected_to(role: :writing) do
|
|
19
|
+
Spree::Order.unscoped.where(id: guest_order.id).update_all(changes)
|
|
12
20
|
end
|
|
21
|
+
|
|
22
|
+
# Manually publish update event since update_all bypasses callbacks
|
|
23
|
+
guest_order.publish_event('order.updated') if changes.present?
|
|
24
|
+
|
|
25
|
+
success(guest_order)
|
|
13
26
|
end
|
|
14
27
|
end
|
|
15
28
|
end
|
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
module Spree
|
|
2
2
|
module Checkout
|
|
3
|
+
# @deprecated This service is deprecated and will be removed in Spree 6.0.
|
|
4
|
+
# Order totals are now automatically updated via a callback on ShippingRate
|
|
5
|
+
# when selected_shipping_rate_id is set on a Shipment.
|
|
3
6
|
class SelectShippingMethod
|
|
4
7
|
prepend Spree::ServiceModule::Base
|
|
5
8
|
|
|
6
9
|
def call(order:, params:)
|
|
10
|
+
Spree::Deprecation.warn(
|
|
11
|
+
"#{self.class.name} is deprecated and will be removed in Spree 6.0. " \
|
|
12
|
+
"Order totals are now automatically updated via ShippingRate callback when " \
|
|
13
|
+
"selected_shipping_rate_id is set on a Shipment.",
|
|
14
|
+
caller_locations(2)
|
|
15
|
+
)
|
|
7
16
|
if params[:shipment_id].present?
|
|
8
17
|
shipment = order.shipments.valid.find_by(id: params[:shipment_id])
|
|
9
18
|
return failure(:shipment_not_found) if shipment.nil?
|
|
@@ -26,6 +35,9 @@ module Spree
|
|
|
26
35
|
end
|
|
27
36
|
end
|
|
28
37
|
|
|
38
|
+
# Note: selected_shipping_rate_id= now automatically updates order totals,
|
|
39
|
+
# but we still need update_with_updater! here because Checkout::Advance
|
|
40
|
+
# expects the full updater to run (including state updates) when this service succeeds.
|
|
29
41
|
order.update_with_updater!
|
|
30
42
|
|
|
31
43
|
success(order)
|
|
@@ -41,8 +53,8 @@ module Spree
|
|
|
41
53
|
)
|
|
42
54
|
end
|
|
43
55
|
|
|
56
|
+
# selected_shipping_rate_id= now automatically calls update_amounts and updates order totals
|
|
44
57
|
shipment.selected_shipping_rate_id = selected_shipping_rate.id
|
|
45
|
-
shipment.update_amounts
|
|
46
58
|
|
|
47
59
|
success(shipment)
|
|
48
60
|
end
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
module Spree
|
|
2
2
|
module Classifications
|
|
3
|
+
# @deprecated This service is deprecated and will be removed in Spree 5.5.
|
|
3
4
|
class Reposition
|
|
4
5
|
prepend Spree::ServiceModule::Base
|
|
5
6
|
|
|
6
7
|
def call(classification:, position:)
|
|
8
|
+
Spree::Deprecation.warn(
|
|
9
|
+
"#{self.class.name} is deprecated and will be removed in Spree 5.5.",
|
|
10
|
+
caller_locations(2)
|
|
11
|
+
)
|
|
7
12
|
if position.is_a?(String) && !position.match(/^\d+$/)
|
|
8
13
|
return failure(nil, I18n.t('errors.messages.not_a_number'))
|
|
9
14
|
end
|
|
@@ -41,10 +41,15 @@ module Spree
|
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
def get_image_link(variant, product)
|
|
44
|
-
image
|
|
45
|
-
|
|
44
|
+
# try getting image from variant
|
|
45
|
+
img = variant.images.first&.plp_url
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
# if no image specified for variant try getting product image
|
|
48
|
+
if img.nil?
|
|
49
|
+
img = product.images.first&.plp_url
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
img
|
|
48
53
|
end
|
|
49
54
|
|
|
50
55
|
def format_price(variant)
|
|
@@ -25,16 +25,15 @@ module Spree
|
|
|
25
25
|
return failure(:gift_card_mismatched_customer) if gift_card.user != order.user
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
+
amount = [gift_card.amount_remaining, order.total].min
|
|
28
29
|
store = order.store
|
|
29
30
|
|
|
31
|
+
return failure(:gift_card_no_amount_remaining) unless amount.positive? || order.total.zero?
|
|
32
|
+
|
|
30
33
|
payment_method = ensure_store_credit_payment_method!(store)
|
|
31
34
|
|
|
35
|
+
gift_card.lock!
|
|
32
36
|
order.with_lock do
|
|
33
|
-
gift_card.lock!
|
|
34
|
-
amount = [gift_card.amount_remaining, order.total].min
|
|
35
|
-
|
|
36
|
-
return failure(:gift_card_no_amount_remaining) unless amount.positive? || order.total.zero?
|
|
37
|
-
|
|
38
37
|
store_credit = gift_card.store_credits.create!(
|
|
39
38
|
store: store,
|
|
40
39
|
user: order.user,
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Imports
|
|
3
|
+
module RowProcessors
|
|
4
|
+
class Customer < Base
|
|
5
|
+
def process!
|
|
6
|
+
user = find_or_initialize_user
|
|
7
|
+
assign_user_attributes(user)
|
|
8
|
+
assign_address(user) if address_fields_present?
|
|
9
|
+
user.save!
|
|
10
|
+
user
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def find_or_initialize_user
|
|
16
|
+
email = attributes['email'].to_s.strip.downcase
|
|
17
|
+
raise ArgumentError, 'Email is required' if email.blank?
|
|
18
|
+
|
|
19
|
+
Spree.user_class.find_or_initialize_by(email: email)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def assign_user_attributes(user)
|
|
23
|
+
user.first_name = attributes['first_name'].strip if attributes['first_name'].present?
|
|
24
|
+
user.last_name = attributes['last_name'].strip if attributes['last_name'].present?
|
|
25
|
+
user.phone = attributes['phone'].strip if attributes['phone'].present?
|
|
26
|
+
user.accepts_email_marketing = to_boolean(attributes['accepts_email_marketing']) if attributes['accepts_email_marketing'].present?
|
|
27
|
+
user.tag_list = attributes['tags'] if attributes['tags'].present?
|
|
28
|
+
|
|
29
|
+
if user.new_record?
|
|
30
|
+
password = SecureRandom.hex(16)
|
|
31
|
+
user.password = password
|
|
32
|
+
user.password_confirmation = password
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def assign_address(user)
|
|
37
|
+
address = user.bill_address || user.build_bill_address
|
|
38
|
+
address.firstname = attributes['first_name'].presence || user.first_name
|
|
39
|
+
address.lastname = attributes['last_name'].presence || user.last_name
|
|
40
|
+
address.company = attributes['company'].strip if attributes['company'].present?
|
|
41
|
+
address.address1 = attributes['address1'].strip if attributes['address1'].present?
|
|
42
|
+
address.address2 = attributes['address2'].strip if attributes['address2'].present?
|
|
43
|
+
address.city = attributes['city'].strip if attributes['city'].present?
|
|
44
|
+
address.zipcode = attributes['zip'].strip if attributes['zip'].present?
|
|
45
|
+
address.phone = attributes['phone'].presence || user.phone
|
|
46
|
+
|
|
47
|
+
if attributes['country_code'].present?
|
|
48
|
+
address.country = Spree::Country.find_by(iso: attributes['country_code'].strip.upcase)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
if attributes['province_code'].present? && address.country
|
|
52
|
+
address.state = address.country.states.find_by(abbr: attributes['province_code'].strip)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
address.save!
|
|
56
|
+
user.bill_address = address
|
|
57
|
+
user.ship_address ||= address
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def address_fields_present?
|
|
61
|
+
%w[address1 city country_code].any? { |f| attributes[f].present? }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def to_boolean(value)
|
|
65
|
+
value.to_s.strip.downcase.in?(%w[true yes 1 y])
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -4,11 +4,13 @@ module Spree
|
|
|
4
4
|
prepend Spree::ServiceModule::Base
|
|
5
5
|
|
|
6
6
|
def call(order:, approver: nil)
|
|
7
|
+
changes = { considered_risky: false, approved_at: Time.current }
|
|
7
8
|
if approver.present?
|
|
8
|
-
|
|
9
|
-
else
|
|
10
|
-
order.approve!
|
|
9
|
+
changes[:approver_id] = approver.id
|
|
11
10
|
end
|
|
11
|
+
order.update_columns(changes)
|
|
12
|
+
|
|
13
|
+
order.publish_event('order.approved')
|
|
12
14
|
success(order.reload)
|
|
13
15
|
rescue ActiveRecord::Rollback, ActiveRecord::RecordInvalid, StateMachines::InvalidTransition
|
|
14
16
|
failure(order)
|
|
@@ -3,12 +3,17 @@ module Spree
|
|
|
3
3
|
class Cancel
|
|
4
4
|
prepend Spree::ServiceModule::Base
|
|
5
5
|
|
|
6
|
-
def call(order:, canceler: nil)
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
def call(order:, canceler: nil, canceled_at: nil)
|
|
7
|
+
canceled_at ||= Time.current
|
|
8
|
+
|
|
9
|
+
order.transaction do
|
|
10
|
+
changes = { canceled_at: canceled_at }
|
|
11
|
+
changes[:canceler_id] = canceler.id if canceler.present?
|
|
12
|
+
order.update_columns(changes)
|
|
10
13
|
order.cancel!
|
|
11
14
|
end
|
|
15
|
+
|
|
16
|
+
order.publish_event('order.canceled')
|
|
12
17
|
success(order.reload)
|
|
13
18
|
rescue ActiveRecord::Rollback, ActiveRecord::RecordInvalid, StateMachines::InvalidTransition
|
|
14
19
|
failure(order)
|
|
@@ -184,7 +184,7 @@ module Spree
|
|
|
184
184
|
option_value_variant_params = {}
|
|
185
185
|
|
|
186
186
|
option_value_params.each_with_index do |opt, index|
|
|
187
|
-
option_type = Spree::OptionType.
|
|
187
|
+
option_type = Spree::OptionType.find_by_param(opt[:id]) if opt.fetch(:id)
|
|
188
188
|
option_type ||= Spree::OptionType.where(name: opt[:name].parameterize).first_or_initialize do |o|
|
|
189
189
|
o.name = o.presentation = opt[:name]
|
|
190
190
|
o.position = opt[:position]
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
require 'csv'
|
|
2
|
+
|
|
3
|
+
module Spree
|
|
4
|
+
module SampleData
|
|
5
|
+
class ImportRunner
|
|
6
|
+
prepend Spree::ServiceModule::Base
|
|
7
|
+
|
|
8
|
+
def call(csv_path:, import_class:)
|
|
9
|
+
store = Spree::Store.default
|
|
10
|
+
admin = Spree.admin_user_class.first
|
|
11
|
+
|
|
12
|
+
raise 'No admin user found. Please run seeds first.' unless admin
|
|
13
|
+
|
|
14
|
+
import = import_class.new(
|
|
15
|
+
owner: store,
|
|
16
|
+
user: admin
|
|
17
|
+
)
|
|
18
|
+
import.number = import.generate_permalink(import_class)
|
|
19
|
+
import.attachment.attach(
|
|
20
|
+
io: File.open(csv_path),
|
|
21
|
+
filename: File.basename(csv_path),
|
|
22
|
+
content_type: 'text/csv'
|
|
23
|
+
)
|
|
24
|
+
import.save!(validate: false)
|
|
25
|
+
import.update_columns(status: 'processing')
|
|
26
|
+
import.create_mappings
|
|
27
|
+
|
|
28
|
+
row_number = 0
|
|
29
|
+
failed = 0
|
|
30
|
+
|
|
31
|
+
::CSV.foreach(csv_path, headers: true) do |csv_row|
|
|
32
|
+
row_number += 1
|
|
33
|
+
import_row = import.rows.create!(
|
|
34
|
+
row_number: row_number,
|
|
35
|
+
data: csv_row.to_h.to_json,
|
|
36
|
+
status: 'pending'
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
begin
|
|
40
|
+
import_row.process!
|
|
41
|
+
rescue StandardError => e
|
|
42
|
+
failed += 1
|
|
43
|
+
puts "\n Warning: Row #{row_number} failed: #{e.message}"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
print '.' if (row_number % 10).zero?
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
import.update!(status: 'completed')
|
|
50
|
+
puts "\n Processed #{row_number} rows (#{failed} failed)"
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module SampleData
|
|
3
|
+
class Loader
|
|
4
|
+
prepend Spree::ServiceModule::Base
|
|
5
|
+
|
|
6
|
+
def call
|
|
7
|
+
Spree::Events.disable do
|
|
8
|
+
without_geocoding do
|
|
9
|
+
ensure_seeds_loaded
|
|
10
|
+
|
|
11
|
+
puts 'Loading sample configuration data...'
|
|
12
|
+
load_configuration_data
|
|
13
|
+
|
|
14
|
+
puts 'Loading sample metafield definitions...'
|
|
15
|
+
load_ruby_file('metafield_definitions')
|
|
16
|
+
|
|
17
|
+
puts 'Loading sample products...'
|
|
18
|
+
load_products
|
|
19
|
+
|
|
20
|
+
puts 'Loading sample customers...'
|
|
21
|
+
load_customers
|
|
22
|
+
|
|
23
|
+
puts 'Loading sample orders...'
|
|
24
|
+
load_ruby_file('orders')
|
|
25
|
+
|
|
26
|
+
puts 'Loading sample posts...'
|
|
27
|
+
load_ruby_file('posts')
|
|
28
|
+
|
|
29
|
+
puts 'Sample data loaded successfully!'
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def ensure_seeds_loaded
|
|
37
|
+
us = Spree::Country.find_by(iso: 'US')
|
|
38
|
+
return if us&.states&.any? && Spree::Store.default&.persisted?
|
|
39
|
+
|
|
40
|
+
puts 'Running seeds first...'
|
|
41
|
+
Spree::Seeds::All.call
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def sample_data_path
|
|
45
|
+
@sample_data_path ||= Spree::Core::Engine.root.join('db', 'sample_data')
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def load_configuration_data
|
|
49
|
+
load_ruby_file('shipping_methods')
|
|
50
|
+
load_ruby_file('payment_methods')
|
|
51
|
+
load_ruby_file('promotions')
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def load_products
|
|
55
|
+
csv_path = sample_data_path.join('products.csv')
|
|
56
|
+
Spree::SampleData::ImportRunner.call(csv_path: csv_path, import_class: Spree::Imports::Products)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def load_customers
|
|
60
|
+
csv_path = sample_data_path.join('customers.csv')
|
|
61
|
+
Spree::SampleData::ImportRunner.call(csv_path: csv_path, import_class: Spree::Imports::Customers)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def load_ruby_file(name)
|
|
65
|
+
file = sample_data_path.join("#{name}.rb")
|
|
66
|
+
load file.to_s if file.exist?
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def without_geocoding
|
|
70
|
+
previous = Spree::Config[:geocode_addresses]
|
|
71
|
+
Spree::Config[:geocode_addresses] = false
|
|
72
|
+
yield
|
|
73
|
+
ensure
|
|
74
|
+
Spree::Config[:geocode_addresses] = previous
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|