spree_core 5.4.2 → 5.5.0.rc1
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/helpers/spree/base_helper.rb +0 -82
- data/app/helpers/spree/currency_helper.rb +0 -12
- data/app/helpers/spree/products_helper.rb +0 -8
- data/app/jobs/spree/base_job.rb +18 -0
- data/app/jobs/spree/events/subscriber_job.rb +3 -2
- data/app/jobs/spree/exports/generate_job.rb +11 -0
- data/app/jobs/spree/images/save_from_url_job.rb +23 -8
- data/app/jobs/spree/imports/assign_tags_job.rb +11 -0
- data/app/jobs/spree/imports/base_job.rb +15 -0
- data/app/jobs/spree/imports/create_categories_job.rb +37 -0
- data/app/jobs/spree/imports/create_rows_job.rb +1 -3
- data/app/jobs/spree/imports/process_group_job.rb +8 -6
- data/app/jobs/spree/imports/process_rows_job.rb +1 -3
- data/app/jobs/spree/media/migrate_product_assets_job.rb +83 -0
- data/app/jobs/spree/products/refresh_metrics_job.rb +15 -4
- data/app/jobs/spree/reports/generate_job.rb +11 -0
- data/app/jobs/spree/search_provider/index_job.rb +5 -1
- data/app/jobs/spree/search_provider/remove_job.rb +4 -0
- data/app/jobs/spree/stock_reservations/expire_job.rb +11 -0
- data/app/models/concerns/spree/calculated_adjustments.rb +34 -1
- data/app/models/concerns/spree/display_on.rb +31 -0
- data/app/models/concerns/spree/metafields.rb +167 -5
- data/app/models/concerns/spree/preference_schema.rb +191 -0
- data/app/models/concerns/spree/prefixed_id.rb +94 -11
- data/app/models/concerns/spree/product_scopes.rb +36 -17
- data/app/models/concerns/spree/publishable.rb +1 -1
- data/app/models/concerns/spree/ransackable_attributes.rb +5 -1
- data/app/models/concerns/spree/search_indexable.rb +8 -7
- data/app/models/concerns/spree/searchable.rb +11 -2
- data/app/models/concerns/spree/stores/channels.rb +20 -0
- data/app/models/concerns/spree/stores/markets.rb +21 -5
- data/app/models/concerns/spree/typed_associations.rb +120 -0
- data/app/models/concerns/spree/user_methods.rb +71 -12
- data/app/models/spree/ability.rb +4 -117
- data/app/models/spree/api_key.rb +53 -0
- data/app/models/spree/asset.rb +37 -14
- data/app/models/spree/authentication/strategy_registry.rb +72 -0
- data/app/models/spree/base.rb +18 -1
- data/app/models/spree/channel.rb +159 -0
- data/app/models/spree/country.rb +2 -0
- data/app/models/spree/current.rb +5 -1
- data/app/models/spree/custom_field.rb +9 -0
- data/app/models/spree/custom_field_definition.rb +7 -0
- data/app/models/spree/customer_group.rb +8 -2
- data/app/models/spree/event.rb +6 -6
- data/app/models/spree/export.rb +32 -5
- data/app/models/spree/exports/product_translations.rb +1 -1
- data/app/models/spree/gateway/bogus.rb +6 -1
- data/app/models/spree/gateway.rb +25 -0
- data/app/models/spree/gift_card.rb +1 -1
- data/app/models/spree/gift_card_batch.rb +4 -1
- data/app/models/spree/import.rb +5 -0
- data/app/models/spree/import_row.rb +12 -0
- data/app/models/spree/line_item.rb +7 -2
- data/app/models/spree/market.rb +57 -1
- data/app/models/spree/metafield.rb +38 -0
- data/app/models/spree/metafield_definition.rb +29 -6
- data/app/models/spree/metafields/json.rb +10 -0
- data/app/models/spree/newsletter_subscriber.rb +19 -3
- data/app/models/spree/option_type.rb +48 -7
- data/app/models/spree/order/checkout.rb +3 -3
- data/app/models/spree/order.rb +102 -6
- data/app/models/spree/order_approval.rb +19 -0
- data/app/models/spree/order_cancellation.rb +19 -0
- data/app/models/spree/order_inventory.rb +24 -2
- data/app/models/spree/order_routing/has_strategy_preference.rb +28 -0
- data/app/models/spree/order_routing/rules/default_location.rb +16 -0
- data/app/models/spree/order_routing/rules/minimize_splits.rb +45 -0
- data/app/models/spree/order_routing/rules/preferred_location.rb +22 -0
- data/app/models/spree/order_routing/strategy/base.rb +47 -0
- data/app/models/spree/order_routing/strategy/legacy.rb +33 -0
- data/app/models/spree/order_routing/strategy/reducer.rb +68 -0
- data/app/models/spree/order_routing/strategy/rules.rb +81 -0
- data/app/models/spree/order_routing_rule.rb +75 -0
- data/app/models/spree/payment_setup_sessions/bogus.rb +4 -0
- data/app/models/spree/permission_sets/configuration_management.rb +16 -0
- data/app/models/spree/permission_sets/product_display.rb +2 -0
- data/app/models/spree/permission_sets/product_management.rb +2 -0
- data/app/models/spree/price.rb +14 -1
- data/app/models/spree/price_list.rb +129 -17
- data/app/models/spree/price_rule.rb +11 -1
- data/app/models/spree/price_rules/customer_group_rule.rb +15 -1
- data/app/models/spree/price_rules/market_rule.rb +16 -1
- data/app/models/spree/price_rules/user_rule.rb +21 -2
- data/app/models/spree/product/channels.rb +149 -0
- data/app/models/spree/product/legacy_multi_store_support.rb +40 -0
- data/app/models/spree/product/slugs.rb +1 -1
- data/app/models/spree/product.rb +172 -31
- data/app/models/spree/product_publication.rb +43 -0
- data/app/models/spree/promotion/actions/create_adjustment.rb +4 -0
- data/app/models/spree/promotion/actions/create_item_adjustments.rb +4 -0
- data/app/models/spree/promotion/actions/create_line_items.rb +32 -14
- data/app/models/spree/promotion/rules/country.rb +40 -18
- data/app/models/spree/promotion/rules/customer_group.rb +10 -1
- data/app/models/spree/promotion/rules/product.rb +4 -0
- data/app/models/spree/promotion/rules/taxon.rb +24 -1
- data/app/models/spree/promotion/rules/user.rb +21 -0
- data/app/models/spree/promotion/rules/user_logged_in.rb +6 -0
- data/app/models/spree/promotion.rb +22 -1
- data/app/models/spree/promotion_action.rb +17 -11
- data/app/models/spree/promotion_rule.rb +17 -18
- data/app/models/spree/search_provider/meilisearch.rb +12 -2
- data/app/models/spree/shipment.rb +10 -4
- data/app/models/spree/stock/availability_validator.rb +1 -1
- data/app/models/spree/stock/quantifier.rb +89 -9
- data/app/models/spree/stock_item.rb +36 -0
- data/app/models/spree/stock_location.rb +52 -0
- data/app/models/spree/stock_reservation.rb +38 -0
- data/app/models/spree/stock_reservations/insufficient_stock_error.rb +12 -0
- data/app/models/spree/store.rb +18 -72
- data/app/models/spree/store_credit.rb +0 -8
- data/app/models/spree/store_product.rb +11 -23
- data/app/models/spree/subscriber.rb +12 -12
- data/app/models/spree/taxon.rb +0 -5
- data/app/models/spree/user_identity.rb +1 -2
- data/app/models/spree/variant.rb +132 -18
- data/app/models/spree/variant_media.rb +46 -0
- data/app/models/spree/webhook_delivery.rb +1 -1
- data/app/models/spree/webhook_endpoint.rb +24 -0
- data/app/models/spree/wished_item.rb +0 -13
- data/app/presenters/spree/csv/formula_sanitizer.rb +28 -0
- data/app/presenters/spree/csv/product_variant_presenter.rb +23 -3
- data/app/presenters/spree/search_provider/product_presenter.rb +11 -4
- data/app/presenters/spree/variant_presenter.rb +4 -3
- data/app/services/spree/addresses/update.rb +6 -8
- data/app/services/spree/cart/add_item.rb +10 -0
- data/app/services/spree/cart/empty.rb +2 -0
- data/app/services/spree/cart/remove_line_item.rb +10 -0
- data/app/services/spree/cart/remove_out_of_stock_items.rb +1 -1
- data/app/services/spree/cart/set_quantity.rb +10 -0
- data/app/services/spree/carts/complete.rb +1 -0
- data/app/services/spree/carts/create.rb +1 -0
- data/app/services/spree/carts/update.rb +18 -2
- data/app/services/spree/carts/upsert_items.rb +6 -6
- data/app/services/spree/credit_cards/destroy.rb +1 -1
- data/app/services/spree/imports/row_processors/customer.rb +4 -1
- data/app/services/spree/imports/row_processors/product_variant.rb +95 -57
- data/app/services/spree/newsletter/link_user.rb +53 -0
- data/app/services/spree/newsletter/subscribe.rb +31 -9
- data/app/services/spree/orders/approve.rb +27 -6
- data/app/services/spree/orders/build_shipments.rb +29 -0
- data/app/services/spree/orders/cancel.rb +34 -3
- data/app/services/spree/orders/complete.rb +53 -0
- data/app/services/spree/orders/create.rb +156 -0
- data/app/services/spree/orders/update.rb +51 -0
- data/app/services/spree/orders/upsert_items.rb +70 -0
- data/app/services/spree/payments/handle_webhook.rb +3 -10
- data/app/services/spree/prices/bulk_upsert.rb +201 -0
- data/app/services/spree/products/duplicator.rb +1 -1
- data/app/services/spree/products/prepare_nested_attributes.rb +2 -30
- data/app/services/spree/sample_data/loader.rb +30 -0
- data/app/services/spree/stock_reservations/extend.rb +19 -0
- data/app/services/spree/stock_reservations/release.rb +12 -0
- data/app/services/spree/stock_reservations/reserve.rb +103 -0
- data/app/services/spree/taxons/remove_products.rb +7 -1
- data/app/subscribers/spree/event_log_subscriber.rb +1 -1
- data/app/subscribers/spree/product_metrics_subscriber.rb +3 -7
- data/app/views/spree/invitation_mailer/invitation_email.html.erb +4 -0
- data/config/locales/en.yml +35 -10
- data/config/routes.rb +9 -0
- data/db/migrate/20260429000001_create_spree_order_cancellations.rb +25 -0
- data/db/migrate/20260429000002_create_spree_order_approvals.rb +22 -0
- data/db/migrate/20260429000003_add_status_to_spree_orders.rb +6 -0
- data/db/migrate/20260429000004_add_scopes_to_spree_api_keys.rb +11 -0
- data/db/migrate/20260501000001_create_spree_stock_reservations.rb +19 -0
- data/db/migrate/20260504103113_add_type_to_spree_payment_setup_sessions.rb +6 -0
- data/db/migrate/20260507162651_create_spree_variant_media.rb +23 -0
- data/db/migrate/20260508175303_add_pickup_to_spree_stock_locations.rb +12 -0
- data/db/migrate/20260508204040_create_spree_channels.rb +18 -0
- data/db/migrate/20260508204041_create_spree_order_routing_rules.rb +18 -0
- data/db/migrate/20260508204042_add_preferred_stock_location_to_spree_orders.rb +5 -0
- data/db/migrate/20260508204043_add_channel_id_to_spree_orders.rb +10 -0
- data/db/migrate/20260511000001_backfill_status_on_spree_orders.rb +57 -0
- data/db/migrate/20260515000001_add_store_id_to_spree_newsletter_subscribers.rb +25 -0
- data/db/migrate/20260529000001_add_unique_index_to_spree_price_rules.rb +41 -0
- data/db/migrate/20260529000002_add_unique_index_to_spree_promotion_rules.rb +37 -0
- data/db/migrate/20260601000001_create_spree_product_publications.rb +14 -0
- data/db/migrate/20260601000002_add_store_id_to_spree_products.rb +16 -0
- data/db/migrate/20260602000001_add_default_to_spree_channels.rb +14 -0
- data/db/sample_data/channels.rb +12 -0
- data/db/sample_data/orders.rb +1 -1
- data/db/sample_data/products.csv +212 -212
- data/lib/generators/spree/api_resource/api_resource_generator.rb +353 -0
- data/lib/generators/spree/api_resource/templates/admin_controller.rb.tt +23 -0
- data/lib/generators/spree/api_resource/templates/admin_controller_spec.rb.tt +59 -0
- data/lib/generators/spree/api_resource/templates/admin_serializer.rb.tt +11 -0
- data/lib/generators/spree/api_resource/templates/factory.rb.tt +26 -0
- data/lib/generators/spree/api_resource/templates/store_aliased_serializer.rb.tt +12 -0
- data/lib/generators/spree/api_resource/templates/store_controller.rb.tt +31 -0
- data/lib/generators/spree/api_resource/templates/store_controller_spec.rb.tt +61 -0
- data/lib/generators/spree/api_resource/templates/store_serializer.rb.tt +14 -0
- data/lib/generators/spree/controller_decorator/controller_decorator_generator.rb +66 -0
- data/lib/generators/spree/controller_decorator/templates/controller_decorator.rb.tt +25 -0
- data/lib/generators/spree/model/model_generator.rb +73 -7
- data/lib/generators/spree/model/templates/create_table_migration.rb.tt +40 -0
- data/lib/generators/spree/model/templates/model.rb.tt +28 -2
- data/lib/spree/core/configuration.rb +7 -0
- data/lib/spree/core/controller_helpers/auth.rb +0 -12
- data/lib/spree/core/controller_helpers/currency.rb +0 -17
- data/lib/spree/core/controller_helpers/order.rb +0 -19
- data/lib/spree/core/dependencies.rb +5 -2
- data/lib/spree/core/engine.rb +54 -7
- data/lib/spree/core/permission_configuration.rb +15 -0
- data/lib/spree/core/preferences/masking.rb +47 -0
- data/lib/spree/core/preferences/preferable_class_methods.rb +7 -1
- data/lib/spree/core/version.rb +1 -1
- data/lib/spree/core.rb +56 -5
- data/lib/spree/events/adapters/active_support_notifications.rb +1 -1
- data/lib/spree/events/adapters/base.rb +3 -3
- data/lib/spree/events/registry.rb +1 -1
- data/lib/spree/events.rb +1 -1
- data/lib/spree/permitted_attributes.rb +9 -7
- data/lib/spree/testing_support/factories/address_factory.rb +16 -9
- data/lib/spree/testing_support/factories/api_key_factory.rb +1 -0
- data/lib/spree/testing_support/factories/channel_factory.rb +8 -0
- data/lib/spree/testing_support/factories/line_item_factory.rb +2 -8
- data/lib/spree/testing_support/factories/newsletter_subscriber_factory.rb +2 -0
- data/lib/spree/testing_support/factories/product_factory.rb +16 -7
- data/lib/spree/testing_support/factories/product_publication_factory.rb +6 -0
- data/lib/spree/testing_support/factories/refresh_token_factory.rb +15 -0
- data/lib/spree/testing_support/factories/stock_location_factory.rb +2 -2
- data/lib/spree/testing_support/factories/stock_reservation_factory.rb +31 -0
- data/lib/spree/testing_support/factories/variant_factory.rb +3 -3
- data/lib/spree/testing_support/order_walkthrough.rb +1 -1
- data/lib/spree/testing_support/store.rb +10 -0
- data/lib/spree/upgrades/5_4_to_5_5/manifest.yml +53 -0
- data/lib/tasks/channels.rake +94 -0
- data/lib/tasks/core.rake +1 -0
- data/lib/tasks/media.rake +27 -0
- data/lib/tasks/products.rake +4 -6
- data/lib/tasks/publications.rake +60 -0
- data/lib/tasks/upgrade.rake +211 -0
- metadata +86 -18
- data/app/finders/spree/variants/visible_finder.rb +0 -23
- data/app/paginators/spree/shared/paginate.rb +0 -30
- data/app/presenters/spree/filters/price_presenter.rb +0 -23
- data/app/presenters/spree/filters/price_range_presenter.rb +0 -30
- data/app/presenters/spree/filters/quantified_price_range_presenter.rb +0 -45
- data/app/presenters/spree/product_summary_presenter.rb +0 -27
- data/app/presenters/spree/variants/options_presenter.rb +0 -82
- data/app/services/spree/classifications/reposition.rb +0 -23
- data/app/sorters/spree/orders/sort.rb +0 -10
- data/lib/spree/core/controller_helpers/common.rb +0 -14
- data/lib/spree/core/token_generator.rb +0 -23
- data/lib/spree/database_type_utilities.rb +0 -22
- data/lib/spree/testing_support/bar_ability.rb +0 -14
- data/lib/spree/testing_support/factories/store_product_factory.rb +0 -6
|
@@ -12,6 +12,15 @@ module Spree
|
|
|
12
12
|
|
|
13
13
|
scope :of_type, ->(t) { where(type: t) }
|
|
14
14
|
|
|
15
|
+
# Per-subclass permitted attributes beyond `type` and `preferences`.
|
|
16
|
+
# Override in STI subclasses that accept nested attributes (e.g.
|
|
17
|
+
# CreateLineItems needs `promotion_action_line_items_attributes`,
|
|
18
|
+
# CreateAdjustment needs `calculator_type` + `calculator_attributes`).
|
|
19
|
+
# The Admin API merges these into its `params.permit(...)` allowlist.
|
|
20
|
+
def self.additional_permitted_attributes
|
|
21
|
+
[]
|
|
22
|
+
end
|
|
23
|
+
|
|
15
24
|
# This method should be overridden in subclass
|
|
16
25
|
# Updates the state of the order or performs some other action depending on the subclass
|
|
17
26
|
# options will contain the payload from the event that activated the promotion. This will include
|
|
@@ -27,25 +36,22 @@ module Spree
|
|
|
27
36
|
type == 'Spree::Promotion::Actions::FreeShipping'
|
|
28
37
|
end
|
|
29
38
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
# @return [String] eg. Free Shipping
|
|
33
|
-
def human_name
|
|
34
|
-
Spree.t("promotion_action_types.#{key}.name")
|
|
39
|
+
def self.human_name
|
|
40
|
+
Spree.t("promotion_action_types.#{api_type}.name", default: api_type.titleize)
|
|
35
41
|
end
|
|
36
42
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
# @return [String]
|
|
40
|
-
def human_description
|
|
41
|
-
Spree.t("promotion_action_types.#{key}.description")
|
|
43
|
+
def self.human_description
|
|
44
|
+
Spree.t("promotion_action_types.#{api_type}.description", default: '')
|
|
42
45
|
end
|
|
43
46
|
|
|
47
|
+
def human_name = self.class.human_name
|
|
48
|
+
def human_description = self.class.human_description
|
|
49
|
+
|
|
44
50
|
# Returns the key of the promotion action
|
|
45
51
|
#
|
|
46
52
|
# @return [String] eg. free_shipping
|
|
47
53
|
def key
|
|
48
|
-
|
|
54
|
+
self.class.api_type
|
|
49
55
|
end
|
|
50
56
|
|
|
51
57
|
protected
|
|
@@ -10,7 +10,15 @@ module Spree
|
|
|
10
10
|
scope :of_type, ->(t) { where(type: t) }
|
|
11
11
|
|
|
12
12
|
validates :promotion, presence: true
|
|
13
|
-
|
|
13
|
+
validates :type, uniqueness: { scope: [:promotion_id, *spree_base_uniqueness_scope] }
|
|
14
|
+
|
|
15
|
+
# Per-subclass permitted attributes beyond `type` and `preferences`.
|
|
16
|
+
# Override in STI subclasses that accept association IDs (e.g.
|
|
17
|
+
# Rules::Product needs `product_ids`). The Admin API merges these
|
|
18
|
+
# into its `params.permit(...)` allowlist.
|
|
19
|
+
def self.additional_permitted_attributes
|
|
20
|
+
[]
|
|
21
|
+
end
|
|
14
22
|
|
|
15
23
|
def self.for(promotable)
|
|
16
24
|
all.select { |rule| rule.applicable?(promotable) }
|
|
@@ -34,35 +42,26 @@ module Spree
|
|
|
34
42
|
@eligibility_errors ||= ActiveModel::Errors.new(self)
|
|
35
43
|
end
|
|
36
44
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
# @return [String] eg. Currency
|
|
40
|
-
def human_name
|
|
41
|
-
Spree.t("promotion_rule_types.#{key}.name")
|
|
45
|
+
def self.human_name
|
|
46
|
+
Spree.t("promotion_rule_types.#{api_type}.name", default: api_type.titleize)
|
|
42
47
|
end
|
|
43
48
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
# @return [String]
|
|
47
|
-
def human_description
|
|
48
|
-
Spree.t("promotion_rule_types.#{key}.description")
|
|
49
|
+
def self.human_description
|
|
50
|
+
Spree.t("promotion_rule_types.#{api_type}.description", default: '')
|
|
49
51
|
end
|
|
50
52
|
|
|
53
|
+
def human_name = self.class.human_name
|
|
54
|
+
def human_description = self.class.human_description
|
|
55
|
+
|
|
51
56
|
# Returns the key of the promotion rule
|
|
52
57
|
#
|
|
53
58
|
# @return [String] eg. currency
|
|
54
59
|
def key
|
|
55
|
-
|
|
60
|
+
self.class.api_type
|
|
56
61
|
end
|
|
57
62
|
|
|
58
63
|
private
|
|
59
64
|
|
|
60
|
-
def unique_per_promotion
|
|
61
|
-
if Spree::PromotionRule.exists?(promotion_id: promotion_id, type: self.class.name)
|
|
62
|
-
errors.add(:base, 'Promotion already contains this rule type')
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
|
|
66
65
|
def eligibility_error_message(key, options = {})
|
|
67
66
|
Spree.t(key, Hash[scope: [:eligibility_errors, :messages]].merge(options))
|
|
68
67
|
end
|
|
@@ -233,7 +233,7 @@ module Spree
|
|
|
233
233
|
end
|
|
234
234
|
|
|
235
235
|
def filterable_attributes
|
|
236
|
-
%w[product_id status in_stock store_ids locale currency discontinue_on price category_ids tags option_value_ids]
|
|
236
|
+
%w[product_id status in_stock store_ids channel_ids locale currency available_on discontinue_on price category_ids tags option_value_ids]
|
|
237
237
|
end
|
|
238
238
|
|
|
239
239
|
def sortable_attributes
|
|
@@ -259,12 +259,22 @@ module Spree
|
|
|
259
259
|
# System scoping — always applied. Rarely overridden.
|
|
260
260
|
# Mirrors the AR scope: store.products.active(currency) with locale.
|
|
261
261
|
def system_filter_conditions
|
|
262
|
+
now = Time.current
|
|
262
263
|
conditions = []
|
|
263
264
|
conditions << "store_ids = '#{store.id}'"
|
|
265
|
+
conditions << "channel_ids = '#{Spree::Current.channel.id}'" if Spree::Current.channel
|
|
264
266
|
conditions << "status = 'active'"
|
|
265
267
|
conditions << "locale = '#{locale.to_s.gsub(/[^a-zA-Z_-]/, '')}'"
|
|
266
268
|
conditions << "currency = '#{currency.to_s.gsub(/[^A-Z]/, '')}'"
|
|
267
|
-
|
|
269
|
+
# Exclude future-dated products — mirrors +Product.available(Time.current)+.
|
|
270
|
+
# ISO 8601 strings sort lexicographically in chronological order, so the
|
|
271
|
+
# string compare is sound. +NOT EXISTS+ catches docs indexed before this
|
|
272
|
+
# attribute was emitted (legacy indexes), +IS NULL+ catches docs where
|
|
273
|
+
# the field was emitted as explicit null, and the +<=+ clause filters
|
|
274
|
+
# the remaining future-dated docs — so the upgrade is non-breaking and
|
|
275
|
+
# no reindex is required.
|
|
276
|
+
conditions << "(available_on NOT EXISTS OR available_on IS NULL OR available_on <= '#{now.iso8601}')"
|
|
277
|
+
conditions << "(discontinue_on = 0 OR discontinue_on > #{now.to_i})"
|
|
268
278
|
conditions
|
|
269
279
|
end
|
|
270
280
|
|
|
@@ -272,16 +272,22 @@ module Spree
|
|
|
272
272
|
def manifest
|
|
273
273
|
# Grouping by the ID means that we don't have to call out to the association accessor
|
|
274
274
|
# This makes the grouping by faster because it results in less SQL cache hits.
|
|
275
|
-
inventory_units.group_by(&:variant_id).
|
|
276
|
-
units.group_by(&:line_item_id).
|
|
275
|
+
inventory_units.group_by(&:variant_id).flat_map do |_variant_id, units|
|
|
276
|
+
units.group_by(&:line_item_id).filter_map do |_line_item_id, units|
|
|
277
|
+
line_item = units.first.line_item
|
|
278
|
+
# Defensively skip orphaned inventory units (line item destroyed
|
|
279
|
+
# without cascading) so a single bad row doesn't crash callers that
|
|
280
|
+
# rely on line_item being present (item_cost, item_weight, the admin
|
|
281
|
+
# shipment manifest view, etc.).
|
|
282
|
+
next if line_item.nil?
|
|
283
|
+
|
|
277
284
|
states = {}
|
|
278
285
|
units.group_by(&:state).each { |state, iu| states[state] = iu.sum(&:quantity) }
|
|
279
286
|
|
|
280
|
-
line_item = units.first.line_item
|
|
281
287
|
variant = units.first.variant
|
|
282
288
|
ManifestItem.new(line_item, variant, units.sum(&:quantity), states)
|
|
283
289
|
end
|
|
284
|
-
end
|
|
290
|
+
end
|
|
285
291
|
end
|
|
286
292
|
|
|
287
293
|
def process_order_payments
|
|
@@ -28,7 +28,7 @@ module Spree
|
|
|
28
28
|
private
|
|
29
29
|
|
|
30
30
|
def item_available?(line_item, quantity)
|
|
31
|
-
Spree::Stock::Quantifier.new(line_item.variant).can_supply?(quantity)
|
|
31
|
+
Spree::Stock::Quantifier.new(line_item.variant, excluded_order: line_item.order).can_supply?(quantity)
|
|
32
32
|
end
|
|
33
33
|
end
|
|
34
34
|
end
|
|
@@ -1,25 +1,89 @@
|
|
|
1
1
|
module Spree
|
|
2
2
|
module Stock
|
|
3
3
|
class Quantifier
|
|
4
|
-
attr_reader :variant, :stock_location
|
|
4
|
+
attr_reader :variant, :stock_location, :excluded_order
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
# @param excluded_order [Spree::Order, nil] when given, reservations
|
|
7
|
+
# belonging to this order are not counted against availability. Used
|
|
8
|
+
# when checking an order's own line items so the customer's own
|
|
9
|
+
# checkout hold doesn't make their item look out of stock.
|
|
10
|
+
def initialize(variant, stock_location = nil, excluded_order: nil)
|
|
11
|
+
@variant = variant
|
|
12
|
+
@stock_location = stock_location
|
|
13
|
+
@excluded_order = excluded_order
|
|
9
14
|
end
|
|
10
15
|
|
|
16
|
+
# Units a customer can purchase right now: physical pool minus
|
|
17
|
+
# already-allocated units minus active checkout reservations. Clamped
|
|
18
|
+
# at zero so callers never see a negative count.
|
|
19
|
+
#
|
|
20
|
+
# Returns +BigDecimal::INFINITY+ when the variant does not track
|
|
21
|
+
# inventory (effectively unlimited supply).
|
|
22
|
+
#
|
|
23
|
+
# @return [Integer, BigDecimal] purchasable quantity, or +INFINITY+
|
|
11
24
|
def total_on_hand
|
|
12
25
|
@total_on_hand ||= if variant.should_track_inventory?
|
|
13
|
-
|
|
14
|
-
stock_items.sum(&:count_on_hand)
|
|
15
|
-
else
|
|
16
|
-
stock_items.sum(:count_on_hand)
|
|
17
|
-
end
|
|
26
|
+
[available_stock - reserved_quantity, 0].max
|
|
18
27
|
else
|
|
19
28
|
BigDecimal::INFINITY
|
|
20
29
|
end
|
|
21
30
|
end
|
|
22
31
|
|
|
32
|
+
# Physical pool minus already-allocated units, summed across the
|
|
33
|
+
# variant's active stock items.
|
|
34
|
+
#
|
|
35
|
+
# In Spree 5.5 {Spree::StockItem#allocated_count} is a Ruby shim that
|
|
36
|
+
# always returns 0, so this equals +SUM(count_on_hand)+. In 6.0
|
|
37
|
+
# (Typed Stock Movements) +allocated_count+ becomes a real column and
|
|
38
|
+
# the SQL path subtracts it natively.
|
|
39
|
+
#
|
|
40
|
+
# @return [Integer] units available before checkout reservations
|
|
41
|
+
def available_stock
|
|
42
|
+
if association_loaded?
|
|
43
|
+
stock_items.sum(&:available_count)
|
|
44
|
+
elsif self.class.allocated_count_column?
|
|
45
|
+
stock_items.sum('count_on_hand - allocated_count')
|
|
46
|
+
else
|
|
47
|
+
stock_items.sum(:count_on_hand)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Units currently held by active checkout reservations on the
|
|
52
|
+
# location-filtered stock items. Returns 0 when stock reservations
|
|
53
|
+
# are globally disabled.
|
|
54
|
+
#
|
|
55
|
+
# Reads through the same {#stock_items} collection as {#available_stock}
|
|
56
|
+
# so a per-location query (filtered by `stock_location`) only counts
|
|
57
|
+
# reservations that belong to those same stock items — otherwise a
|
|
58
|
+
# multi-location variant would subtract reservations from other
|
|
59
|
+
# warehouses.
|
|
60
|
+
#
|
|
61
|
+
# When +excluded_order+ is set, that order's own reservations are left
|
|
62
|
+
# out of the count so an order's own checkout hold doesn't count
|
|
63
|
+
# against the availability of its own line items.
|
|
64
|
+
#
|
|
65
|
+
# @return [Integer]
|
|
66
|
+
def reserved_quantity
|
|
67
|
+
return @reserved_quantity if defined?(@reserved_quantity)
|
|
68
|
+
return @reserved_quantity = 0 unless Spree::Config[:stock_reservations_enabled]
|
|
69
|
+
return @reserved_quantity = 0 if stock_items.blank?
|
|
70
|
+
|
|
71
|
+
excluded_order_id = excluded_order&.id
|
|
72
|
+
|
|
73
|
+
@reserved_quantity = if reservations_preloaded?
|
|
74
|
+
stock_items.sum do |si|
|
|
75
|
+
reservations = si.active_stock_reservations
|
|
76
|
+
reservations = reservations.reject { |r| r.order_id == excluded_order_id } if excluded_order_id
|
|
77
|
+
reservations.sum(&:quantity)
|
|
78
|
+
end
|
|
79
|
+
else
|
|
80
|
+
reservations = Spree::StockReservation.active.where(stock_item_id: stock_items.map(&:id))
|
|
81
|
+
reservations = reservations.where.not(order_id: excluded_order_id) if excluded_order_id
|
|
82
|
+
reservations.sum(:quantity)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Check if any of variant stock items is backorderable
|
|
23
87
|
def backorderable?
|
|
24
88
|
@backorderable ||= stock_items.any?(&:backorderable)
|
|
25
89
|
end
|
|
@@ -32,12 +96,28 @@ module Spree
|
|
|
32
96
|
@stock_items ||= scope_to_location(variant.stock_items)
|
|
33
97
|
end
|
|
34
98
|
|
|
99
|
+
# Memoized schema check so {#available_stock} doesn't introspect the
|
|
100
|
+
# column list on every call. Flips from false → true when 6.0 Typed
|
|
101
|
+
# Stock Movements adds the `allocated_count` column.
|
|
102
|
+
#
|
|
103
|
+
# @return [Boolean]
|
|
104
|
+
def self.allocated_count_column?
|
|
105
|
+
return @allocated_count_column if defined?(@allocated_count_column)
|
|
106
|
+
|
|
107
|
+
@allocated_count_column = Spree::StockItem.connection.column_exists?(:spree_stock_items, :allocated_count)
|
|
108
|
+
end
|
|
109
|
+
|
|
35
110
|
private
|
|
36
111
|
|
|
37
112
|
def association_loaded?
|
|
38
113
|
variant.association(:stock_items).loaded?
|
|
39
114
|
end
|
|
40
115
|
|
|
116
|
+
def reservations_preloaded?
|
|
117
|
+
association_loaded? &&
|
|
118
|
+
stock_items.all? { |si| si.association(:active_stock_reservations).loaded? }
|
|
119
|
+
end
|
|
120
|
+
|
|
41
121
|
def scope_to_location(collection)
|
|
42
122
|
if stock_location.blank?
|
|
43
123
|
if association_loaded?
|
|
@@ -15,6 +15,8 @@ module Spree
|
|
|
15
15
|
belongs_to :variant, -> { with_deleted }, class_name: 'Spree::Variant'
|
|
16
16
|
end
|
|
17
17
|
has_many :stock_movements, inverse_of: :stock_item
|
|
18
|
+
has_many :stock_reservations, class_name: 'Spree::StockReservation', inverse_of: :stock_item, dependent: :destroy
|
|
19
|
+
has_many :active_stock_reservations, -> { active }, class_name: 'Spree::StockReservation', inverse_of: :stock_item
|
|
18
20
|
|
|
19
21
|
validates :stock_location, :variant, presence: true
|
|
20
22
|
validates :variant_id, uniqueness: { scope: :stock_location_id }, unless: :deleted_at
|
|
@@ -38,6 +40,18 @@ module Spree
|
|
|
38
40
|
|
|
39
41
|
scope :with_active_stock_location, -> { joins(:stock_location).merge(Spree::StockLocation.active) }
|
|
40
42
|
|
|
43
|
+
# Stock items for products assigned to `store`. Walks
|
|
44
|
+
# `variant → product → stores`, dedups via `distinct` so a product
|
|
45
|
+
# in multiple stores doesn't double-count its stock items.
|
|
46
|
+
#
|
|
47
|
+
# Used by the admin API as the base scope (`StockItem.for_store`)
|
|
48
|
+
# so the controller can filter directly by stock_location/variant
|
|
49
|
+
# without inheriting `Spree::Store#stock_items`'s extra joins or
|
|
50
|
+
# the variant default ordering.
|
|
51
|
+
scope :for_store, ->(store) {
|
|
52
|
+
joins(variant: :product).where(spree_products: { store_id: store.id })
|
|
53
|
+
}
|
|
54
|
+
|
|
41
55
|
def backordered_inventory_units
|
|
42
56
|
Spree::InventoryUnit.backordered_for_stock_item(self)
|
|
43
57
|
end
|
|
@@ -64,6 +78,28 @@ module Spree
|
|
|
64
78
|
in_stock? || backorderable?
|
|
65
79
|
end
|
|
66
80
|
|
|
81
|
+
# Units already allocated to pending shipments at this stock item.
|
|
82
|
+
#
|
|
83
|
+
# Always returns 0 in Spree 5.5. The 6.0 Typed Stock Movements plan
|
|
84
|
+
# (see docs/plans/6.0-typed-stock-movements.md) adds an indexed
|
|
85
|
+
# `allocated_count` column updated by typed movements (`allocated`,
|
|
86
|
+
# `released`, `shipped`); the Rails column accessor then takes
|
|
87
|
+
# precedence over this method automatically.
|
|
88
|
+
#
|
|
89
|
+
# @return [Integer]
|
|
90
|
+
def allocated_count
|
|
91
|
+
0
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Physical stock minus allocated units at this stock item. Distinct from
|
|
95
|
+
# {Spree::Stock::Quantifier#available_stock}, which sums this across all
|
|
96
|
+
# stock items belonging to a variant.
|
|
97
|
+
#
|
|
98
|
+
# @return [Integer]
|
|
99
|
+
def available_count
|
|
100
|
+
count_on_hand - allocated_count
|
|
101
|
+
end
|
|
102
|
+
|
|
67
103
|
def reduce_count_on_hand_to_zero
|
|
68
104
|
set_count_on_hand(0) if count_on_hand > 0
|
|
69
105
|
end
|
|
@@ -2,6 +2,15 @@ module Spree
|
|
|
2
2
|
class StockLocation < Spree.base_class
|
|
3
3
|
has_prefix_id :sloc # Spree-specific: stock location
|
|
4
4
|
|
|
5
|
+
# Categorizes the location. Open string — extensible by setting any value;
|
|
6
|
+
# KINDS lists the built-in options used by the admin UI dropdown.
|
|
7
|
+
KINDS = %w[warehouse store fulfillment_center].freeze
|
|
8
|
+
|
|
9
|
+
# Pickup stock policy: 'local' = only items physically at this location are
|
|
10
|
+
# collectable; 'any' = items can be transferred from other locations
|
|
11
|
+
# (ship-to-store). See docs/plans/6.0-fulfillment-and-delivery.md.
|
|
12
|
+
PICKUP_STOCK_POLICIES = %w[local any].freeze
|
|
13
|
+
|
|
5
14
|
include Spree::UniqueName
|
|
6
15
|
if defined?(Spree::Security::StockLocations)
|
|
7
16
|
include Spree::Security::StockLocations
|
|
@@ -20,14 +29,41 @@ module Spree
|
|
|
20
29
|
belongs_to :state, class_name: 'Spree::State', optional: true
|
|
21
30
|
belongs_to :country, class_name: 'Spree::Country'
|
|
22
31
|
|
|
32
|
+
validates :kind, presence: true
|
|
33
|
+
validates :pickup_stock_policy, inclusion: { in: PICKUP_STOCK_POLICIES }
|
|
34
|
+
validates :pickup_ready_in_minutes,
|
|
35
|
+
numericality: { only_integer: true, greater_than_or_equal_to: 0 },
|
|
36
|
+
allow_nil: true
|
|
37
|
+
|
|
38
|
+
self.whitelisted_ransackable_attributes = %w[
|
|
39
|
+
name active default kind pickup_enabled
|
|
40
|
+
country_id state_id created_at updated_at
|
|
41
|
+
]
|
|
42
|
+
|
|
23
43
|
scope :active, -> { where(active: true) }
|
|
44
|
+
scope :pickup_enabled, -> { where(pickup_enabled: true) }
|
|
24
45
|
scope :order_default, -> { order(default: :desc, name: :asc) }
|
|
25
46
|
|
|
47
|
+
before_validation :normalize_country
|
|
48
|
+
before_validation :normalize_state
|
|
49
|
+
|
|
26
50
|
after_create :create_stock_items, if: :propagate_all_variants?
|
|
27
51
|
after_save :ensure_one_default
|
|
28
52
|
after_update :conditional_touch_records
|
|
29
53
|
|
|
30
54
|
delegate :name, :iso3, :iso, :iso_name, to: :country, prefix: true, allow_nil: true
|
|
55
|
+
delegate :abbr, to: :state, prefix: true, allow_nil: true
|
|
56
|
+
|
|
57
|
+
# Writer methods for API convenience — accept ISO/abbr codes instead of FK IDs.
|
|
58
|
+
# Mirrors Spree::Address: SDK clients use country_iso/state_abbr because
|
|
59
|
+
# Country/State don't expose prefixed IDs (their `iso` is the public handle).
|
|
60
|
+
def country_iso=(value)
|
|
61
|
+
@country_iso_input = value
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def state_abbr=(value)
|
|
65
|
+
@state_abbr_input = value
|
|
66
|
+
end
|
|
31
67
|
|
|
32
68
|
def state_text
|
|
33
69
|
state.try(:abbr) || state.try(:name) || state_name
|
|
@@ -168,6 +204,22 @@ module Spree
|
|
|
168
204
|
|
|
169
205
|
private
|
|
170
206
|
|
|
207
|
+
def normalize_country
|
|
208
|
+
iso = @country_iso_input
|
|
209
|
+
return if iso.blank?
|
|
210
|
+
|
|
211
|
+
self.country = Spree::Country.by_iso(iso)
|
|
212
|
+
@country_iso_input = nil
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def normalize_state
|
|
216
|
+
abbr = @state_abbr_input
|
|
217
|
+
return if abbr.blank? || country.blank?
|
|
218
|
+
|
|
219
|
+
self.state = country.states.find_by(abbr: abbr)
|
|
220
|
+
@state_abbr_input = nil
|
|
221
|
+
end
|
|
222
|
+
|
|
171
223
|
def create_stock_items
|
|
172
224
|
Spree::StockLocations::StockItems::CreateJob.perform_later(self)
|
|
173
225
|
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
class StockReservation < Spree.base_class
|
|
3
|
+
has_prefix_id :res
|
|
4
|
+
|
|
5
|
+
publishes_lifecycle_events
|
|
6
|
+
|
|
7
|
+
belongs_to :stock_item, class_name: 'Spree::StockItem', inverse_of: :stock_reservations
|
|
8
|
+
belongs_to :line_item, class_name: 'Spree::LineItem', inverse_of: :stock_reservations
|
|
9
|
+
belongs_to :order, class_name: 'Spree::Order', inverse_of: :stock_reservations
|
|
10
|
+
|
|
11
|
+
validates :stock_item, :line_item, :order, :quantity, :expires_at, presence: true
|
|
12
|
+
validates :quantity, numericality: { greater_than: 0, only_integer: true }, presence: true
|
|
13
|
+
validates :line_item_id, uniqueness: { scope: :stock_item_id }, presence: true
|
|
14
|
+
|
|
15
|
+
scope :active, -> { where('spree_stock_reservations.expires_at > ?', Time.current) }
|
|
16
|
+
scope :expired, -> { where('spree_stock_reservations.expires_at <= ?', Time.current) }
|
|
17
|
+
scope :for_order, ->(order) { where(order_id: order.id) }
|
|
18
|
+
scope :for_store, ->(store) {
|
|
19
|
+
joins(:order).where(spree_orders: { store_id: store.id })
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
self.whitelisted_ransackable_attributes = %w[stock_item_id line_item_id order_id quantity expires_at]
|
|
23
|
+
self.whitelisted_ransackable_associations = %w[stock_item line_item order]
|
|
24
|
+
|
|
25
|
+
def active?
|
|
26
|
+
expires_at > Time.current
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Resolves the reservation TTL: per-Store preference if set, otherwise
|
|
30
|
+
# the global Spree::Config[:default_stock_reservation_ttl_minutes]. Falls
|
|
31
|
+
# back to 10 minutes if both are unset (e.g. early-boot / fixture state).
|
|
32
|
+
def self.ttl_for(order)
|
|
33
|
+
minutes = order&.store&.preferred_stock_reservation_ttl_minutes
|
|
34
|
+
minutes = Spree::Config[:default_stock_reservation_ttl_minutes] if minutes.blank?
|
|
35
|
+
minutes.to_i.then { |m| m > 0 ? m : 10 }.minutes
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
data/app/models/spree/store.rb
CHANGED
|
@@ -12,8 +12,10 @@ module Spree
|
|
|
12
12
|
include Spree::Metadata
|
|
13
13
|
include Spree::Stores::Setup
|
|
14
14
|
include Spree::Stores::Markets
|
|
15
|
+
include Spree::Stores::Channels
|
|
15
16
|
include Spree::Security::Stores if defined?(Spree::Security::Stores)
|
|
16
17
|
include Spree::UserManagement
|
|
18
|
+
include Spree::OrderRouting::HasStrategyPreference
|
|
17
19
|
|
|
18
20
|
#
|
|
19
21
|
# Magic methods
|
|
@@ -46,6 +48,7 @@ module Spree
|
|
|
46
48
|
# Checkout preferences
|
|
47
49
|
preference :guest_checkout, :boolean, default: true
|
|
48
50
|
preference :special_instructions_enabled, :boolean, default: false
|
|
51
|
+
preference :stock_reservation_ttl_minutes, :integer, default: 10
|
|
49
52
|
# Address preferences
|
|
50
53
|
preference :company_field_enabled, :boolean, default: false
|
|
51
54
|
# digital assets preferences
|
|
@@ -54,6 +57,9 @@ module Spree
|
|
|
54
57
|
preference :digital_asset_authorized_clicks, :integer, default: 5
|
|
55
58
|
preference :digital_asset_authorized_days, :integer, default: 7
|
|
56
59
|
preference :digital_asset_link_expire_time, :integer, default: 300
|
|
60
|
+
# Class name of the Spree::OrderRouting::Strategy::Base subclass that
|
|
61
|
+
# decides which StockLocation fulfills which items.
|
|
62
|
+
preference :order_routing_strategy, :string, default: 'Spree::OrderRouting::Strategy::Rules'
|
|
57
63
|
|
|
58
64
|
#
|
|
59
65
|
# Associations
|
|
@@ -71,10 +77,11 @@ module Spree
|
|
|
71
77
|
has_many :store_payment_methods, class_name: 'Spree::StorePaymentMethod'
|
|
72
78
|
has_many :payment_methods, through: :store_payment_methods, class_name: 'Spree::PaymentMethod'
|
|
73
79
|
|
|
74
|
-
has_many :
|
|
75
|
-
has_many :
|
|
80
|
+
has_many :products, class_name: 'Spree::Product', dependent: :nullify
|
|
81
|
+
has_many :product_publications, through: :channels, source: :publications, class_name: 'Spree::ProductPublication'
|
|
76
82
|
has_many :variants, through: :products, class_name: 'Spree::Variant', source: :variants_including_master
|
|
77
83
|
has_many :stock_items, through: :variants, class_name: 'Spree::StockItem'
|
|
84
|
+
has_many :prices, through: :variants, class_name: 'Spree::Price'
|
|
78
85
|
has_many :inventory_units, through: :variants, class_name: 'Spree::InventoryUnit'
|
|
79
86
|
has_many :option_value_variants, through: :variants, class_name: 'Spree::OptionValueVariant'
|
|
80
87
|
has_many :customer_returns, class_name: 'Spree::CustomerReturn', inverse_of: :store
|
|
@@ -108,6 +115,9 @@ module Spree
|
|
|
108
115
|
has_many :webhook_endpoints, class_name: 'Spree::WebhookEndpoint', dependent: :destroy, inverse_of: :store
|
|
109
116
|
has_many :webhook_deliveries, through: :webhook_endpoints, class_name: 'Spree::WebhookDelivery'
|
|
110
117
|
|
|
118
|
+
has_many :channels, class_name: 'Spree::Channel', dependent: :destroy
|
|
119
|
+
has_many :order_routing_rules, through: :channels, class_name: 'Spree::OrderRoutingRule'
|
|
120
|
+
|
|
111
121
|
has_many :customer_groups, class_name: 'Spree::CustomerGroup', dependent: :destroy, inverse_of: :store
|
|
112
122
|
|
|
113
123
|
has_many :api_keys, class_name: 'Spree::ApiKey', dependent: :destroy
|
|
@@ -121,6 +131,7 @@ module Spree
|
|
|
121
131
|
end
|
|
122
132
|
validates :preferred_digital_asset_authorized_clicks, numericality: { only_integer: true, greater_than: 0 }
|
|
123
133
|
validates :preferred_digital_asset_authorized_days, numericality: { only_integer: true, greater_than: 0 }
|
|
134
|
+
validates :preferred_stock_reservation_ttl_minutes, numericality: { only_integer: true, greater_than: 0 }
|
|
124
135
|
validates :mail_from_address, email: { allow_blank: false }
|
|
125
136
|
# FIXME: we should remove this condition in v5
|
|
126
137
|
if !ENV['SPREE_DISABLE_DB_CONNECTION'] &&
|
|
@@ -141,7 +152,6 @@ module Spree
|
|
|
141
152
|
# Callbacks
|
|
142
153
|
before_validation :set_default_code, on: :create
|
|
143
154
|
before_save :ensure_default_exists_and_is_unique
|
|
144
|
-
after_create :ensure_default_market
|
|
145
155
|
after_create :create_default_policies
|
|
146
156
|
|
|
147
157
|
#
|
|
@@ -165,7 +175,7 @@ module Spree
|
|
|
165
175
|
else
|
|
166
176
|
Spree::Deprecation.warn(
|
|
167
177
|
'Spree::Store.default returning a new unpersisted store when no default store exists is deprecated ' \
|
|
168
|
-
'and will be removed in Spree
|
|
178
|
+
'and will be removed in Spree 6.0. Please ensure a default store is created before calling Store.default.'
|
|
169
179
|
)
|
|
170
180
|
new(default: true)
|
|
171
181
|
end
|
|
@@ -175,6 +185,10 @@ module Spree
|
|
|
175
185
|
Spree::Store.default&.supported_locales_list || []
|
|
176
186
|
end
|
|
177
187
|
|
|
188
|
+
def default_channel
|
|
189
|
+
channels.find_by(code: Spree::Channel::DEFAULT_CODE) || channels.active.first
|
|
190
|
+
end
|
|
191
|
+
|
|
178
192
|
# @deprecated Use Markets instead. Will be removed in Spree 5.5.
|
|
179
193
|
def checkout_zone
|
|
180
194
|
Spree::Deprecation.warn('Store#checkout_zone is deprecated and will be removed in Spree 5.5. Use Markets instead.')
|
|
@@ -366,25 +380,6 @@ module Spree
|
|
|
366
380
|
|
|
367
381
|
private
|
|
368
382
|
|
|
369
|
-
def ensure_default_market
|
|
370
|
-
return if markets.exists?
|
|
371
|
-
|
|
372
|
-
country = @default_country_for_market
|
|
373
|
-
return if country.blank?
|
|
374
|
-
|
|
375
|
-
iso_country = ISO3166::Country[country.iso]
|
|
376
|
-
|
|
377
|
-
Spree::Events.disable do
|
|
378
|
-
markets.create!(
|
|
379
|
-
name: country.name,
|
|
380
|
-
currency: iso_country&.currency_code || read_attribute(:default_currency) || 'USD',
|
|
381
|
-
default_locale: iso_country&.languages_official&.first || read_attribute(:default_locale) || 'en',
|
|
382
|
-
default: true,
|
|
383
|
-
countries: [country]
|
|
384
|
-
)
|
|
385
|
-
end
|
|
386
|
-
end
|
|
387
|
-
|
|
388
383
|
def create_default_policies
|
|
389
384
|
Spree::Events.disable do
|
|
390
385
|
[
|
|
@@ -401,55 +396,6 @@ module Spree
|
|
|
401
396
|
end
|
|
402
397
|
end
|
|
403
398
|
|
|
404
|
-
def ensure_default_taxonomies_are_created
|
|
405
|
-
Spree::Deprecation.warn('Store#ensure_default_taxonomies_are_created is deprecated and will be removed in Spree 5.5. Please remove it from your codebase')
|
|
406
|
-
|
|
407
|
-
Spree::Events.disable do
|
|
408
|
-
[
|
|
409
|
-
translate_with_store_locale_fallback('spree.taxonomy_categories_name'),
|
|
410
|
-
translate_with_store_locale_fallback('spree.taxonomy_brands_name'),
|
|
411
|
-
translate_with_store_locale_fallback('spree.taxonomy_collections_name')
|
|
412
|
-
].each do |taxonomy_name|
|
|
413
|
-
# Manual exists?/create to work around Mobility bug with find_or_create_by
|
|
414
|
-
next if taxonomies.with_matching_name(taxonomy_name).exists?
|
|
415
|
-
|
|
416
|
-
taxonomies.create(name: taxonomy_name)
|
|
417
|
-
end
|
|
418
|
-
end
|
|
419
|
-
end
|
|
420
|
-
|
|
421
|
-
def ensure_default_automatic_taxons
|
|
422
|
-
Spree::Deprecation.warn('Store#ensure_default_automatic_taxons is deprecated and will be removed in Spree 5.5. Please remove it from your codebase')
|
|
423
|
-
|
|
424
|
-
Spree::Events.disable do
|
|
425
|
-
# Use Mobility-safe lookup for taxonomy
|
|
426
|
-
collections_taxonomy = taxonomies.with_matching_name(translate_with_store_locale_fallback('spree.taxonomy_collections_name')).first
|
|
427
|
-
return unless collections_taxonomy.present?
|
|
428
|
-
|
|
429
|
-
automatic_taxons_config = [
|
|
430
|
-
{ name: translate_with_store_locale_fallback('spree.automatic_taxon_names.on_sale'),
|
|
431
|
-
rule_type: 'Spree::TaxonRules::Sale', rule_value: 'true' },
|
|
432
|
-
{ name: translate_with_store_locale_fallback('spree.automatic_taxon_names.new_arrivals'), rule_type: 'Spree::TaxonRules::AvailableOn', rule_value: 30 }
|
|
433
|
-
]
|
|
434
|
-
|
|
435
|
-
automatic_taxons_config.map do |config|
|
|
436
|
-
# Manual exists?/create to work around Mobility bug with first_or_create
|
|
437
|
-
taxon_scope = collections_taxonomy.taxons.automatic.with_matching_name(config[:name])
|
|
438
|
-
|
|
439
|
-
if taxon_scope.exists?
|
|
440
|
-
taxon_scope.first
|
|
441
|
-
else
|
|
442
|
-
collections_taxonomy.taxons.create!(
|
|
443
|
-
name: config[:name],
|
|
444
|
-
automatic: true,
|
|
445
|
-
parent: collections_taxonomy.root,
|
|
446
|
-
taxon_rules: [TaxonRule.new(type: config[:rule_type], value: config[:rule_value])]
|
|
447
|
-
)
|
|
448
|
-
end
|
|
449
|
-
end
|
|
450
|
-
end
|
|
451
|
-
end
|
|
452
|
-
|
|
453
399
|
# Translates a key using the store's default locale with fallback to :en
|
|
454
400
|
def translate_with_store_locale_fallback(key)
|
|
455
401
|
locale = default_locale.presence&.to_sym || :en
|