spree_core 5.3.3 → 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/slugs.rb +1 -1
- 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 +30 -18
- 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/data_feeds/google/rss.rb +3 -1
- 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/app/views/spree/addresses/_form.html.erb +1 -2
- 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
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',
|
|
@@ -91,36 +94,33 @@ module Spree
|
|
|
91
94
|
scope :backorderable, -> { left_joins(:stock_items).where(spree_stock_items: { backorderable: true }) }
|
|
92
95
|
scope :in_stock_or_backorderable, -> { in_stock.or(backorderable) }
|
|
93
96
|
|
|
94
|
-
scope :eligible,
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
select(:product_id).
|
|
99
|
-
group(:product_id).
|
|
100
|
-
having("COUNT(#{Spree::Variant.table_name}.id) = 1")
|
|
97
|
+
scope :eligible, lambda {
|
|
98
|
+
joins(:product).where(
|
|
99
|
+
arel_table[:is_master].eq(false).or(
|
|
100
|
+
Spree::Product.arel_table[:variant_count].eq(0)
|
|
101
101
|
)
|
|
102
102
|
)
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
scope :not_discontinued,
|
|
105
|
+
scope :not_discontinued, lambda {
|
|
106
106
|
where(
|
|
107
107
|
arel_table[:discontinue_on].eq(nil).or(
|
|
108
108
|
arel_table[:discontinue_on].gteq(Time.current)
|
|
109
109
|
)
|
|
110
110
|
)
|
|
111
|
-
|
|
111
|
+
}
|
|
112
112
|
|
|
113
113
|
scope :not_deleted, -> { where("#{Spree::Variant.quoted_table_name}.deleted_at IS NULL") }
|
|
114
114
|
|
|
115
|
-
scope :for_currency_and_available_price_amount,
|
|
115
|
+
scope :for_currency_and_available_price_amount, lambda { |currency = nil|
|
|
116
116
|
currency ||= Spree::Store.default.default_currency
|
|
117
117
|
joins(:prices).where("#{Spree::Price.table_name}.currency = ?", currency).where("#{Spree::Price.table_name}.amount IS NOT NULL").distinct
|
|
118
|
-
|
|
118
|
+
}
|
|
119
119
|
|
|
120
|
-
scope :active,
|
|
120
|
+
scope :active, lambda { |currency = nil|
|
|
121
121
|
not_discontinued.not_deleted.
|
|
122
122
|
for_currency_and_available_price_amount(currency)
|
|
123
|
-
|
|
123
|
+
}
|
|
124
124
|
|
|
125
125
|
scope :with_option_value, lambda { |option_name, option_value|
|
|
126
126
|
option_type_ids = OptionType.where(name: option_name).ids
|
|
@@ -187,7 +187,8 @@ module Spree
|
|
|
187
187
|
)
|
|
188
188
|
|
|
189
189
|
self.whitelisted_ransackable_associations = %w[option_values product tax_category prices default_price]
|
|
190
|
-
self.whitelisted_ransackable_attributes = %w[weight depth width height sku discontinue_on is_master cost_price cost_currency track_inventory
|
|
190
|
+
self.whitelisted_ransackable_attributes = %w[weight depth width height sku discontinue_on is_master cost_price cost_currency track_inventory
|
|
191
|
+
deleted_at]
|
|
191
192
|
self.whitelisted_ransackable_scopes = %i(product_name_or_sku_cont search_by_product_name_or_sku)
|
|
192
193
|
|
|
193
194
|
def self.product_name_or_sku_cont(query)
|
|
@@ -258,9 +259,13 @@ module Spree
|
|
|
258
259
|
# @return [String] the options text of the variant
|
|
259
260
|
def options_text
|
|
260
261
|
@options_text ||= if option_values.loaded?
|
|
261
|
-
option_values.sort_by
|
|
262
|
+
option_values.sort_by do |ov|
|
|
263
|
+
ov.option_type.position
|
|
264
|
+
end.map { |ov| "#{ov.option_type.presentation}: #{ov.presentation}" }.to_sentence(words_connector: ', ', two_words_connector: ', ')
|
|
262
265
|
else
|
|
263
|
-
option_values.includes(:option_type).joins(:option_type).order("#{Spree::OptionType.table_name}.position").map
|
|
266
|
+
option_values.includes(:option_type).joins(:option_type).order("#{Spree::OptionType.table_name}.position").map do |ov|
|
|
267
|
+
"#{ov.option_type.presentation}: #{ov.presentation}"
|
|
268
|
+
end.to_sentence(words_connector: ', ', two_words_connector: ', ')
|
|
264
269
|
end
|
|
265
270
|
end
|
|
266
271
|
|
|
@@ -293,10 +298,17 @@ module Spree
|
|
|
293
298
|
image_count.positive?
|
|
294
299
|
end
|
|
295
300
|
|
|
296
|
-
# Returns default Image for Variant
|
|
301
|
+
# Returns default Image for Variant.
|
|
297
302
|
# @return [Spree::Image, nil]
|
|
298
303
|
def default_image
|
|
299
|
-
|
|
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)
|
|
300
312
|
end
|
|
301
313
|
|
|
302
314
|
# Returns first Image for Variant.
|
|
@@ -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
|
|
@@ -18,7 +18,9 @@ module Spree
|
|
|
18
18
|
result = products_list.call(store)
|
|
19
19
|
if result.success?
|
|
20
20
|
result.value[:products].find_each do |product|
|
|
21
|
-
product.
|
|
21
|
+
product.variants_including_master.active.find_each do |variant|
|
|
22
|
+
next if variant.is_master? && product.has_variants?
|
|
23
|
+
|
|
22
24
|
add_variant_information_to_xml(xml, product, variant)
|
|
23
25
|
end
|
|
24
26
|
end
|
|
@@ -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
|