spree_core 5.4.3 → 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 +2 -1
- 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/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 +28 -5
- 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/export.rb +30 -3
- 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 +6 -1
- data/app/models/spree/market.rb +32 -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_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/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/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/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/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/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/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/product_metrics_subscriber.rb +3 -7
- data/app/views/spree/invitation_mailer/invitation_email.html.erb +4 -0
- data/config/locales/en.yml +27 -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/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/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 +83 -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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '017291169758b3fc4f9791b940c3264c91df65cd9197621ca0cafd8e3ade2ba1'
|
|
4
|
+
data.tar.gz: a4f6cfd15c25ce054ce792d545eaf6db7e815b023d66348b4c44d5a226abb3c2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 83d3c2a6c3b13d4c508ab4f4c5c15deb544eb3ba7da48ddd966d872d76ec301f7b87093bc0eb42bd12fe1507575ca09b82919796b079a4aa4e66d71d76dd6332
|
|
7
|
+
data.tar.gz: 4453580426836b0b3333309c1eaadabf4bca2fa1fbd8cea51ab1fc1b59c9e02409d098f63e7c77e5dcce222746263d53e021ec3f1b724fd2daa71c623ca573d0
|
|
@@ -22,82 +22,10 @@ module Spree
|
|
|
22
22
|
end.sort_by { |c| c.name.parameterize }
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
-
def spree_resource_path(resource)
|
|
26
|
-
Spree::Deprecation.warn('BaseHelper#spree_resource_path is deprecated and will be removed in Spree 5.5')
|
|
27
|
-
|
|
28
|
-
last_word = resource.class.name.split('::', 10).last
|
|
29
|
-
|
|
30
|
-
spree_class_name_as_path(last_word)
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def spree_class_name_as_path(class_name)
|
|
34
|
-
Spree::Deprecation.warn('BaseHelper#spree_class_name_as_path is deprecated and will be removed in Spree 5.5')
|
|
35
|
-
|
|
36
|
-
class_name.underscore.humanize.parameterize(separator: '_')
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
def display_price(product_or_variant)
|
|
40
|
-
Spree::Deprecation.warn('display_price is deprecated and will be removed in Spree 5.5. Use variant.price_for(context).display_amount instead.')
|
|
41
|
-
|
|
42
|
-
product_or_variant.
|
|
43
|
-
price_in(current_currency).
|
|
44
|
-
display_price_including_vat_for(current_price_options).
|
|
45
|
-
to_html
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def display_compare_at_price(product_or_variant)
|
|
49
|
-
Spree::Deprecation.warn('display_compare_at_price is deprecated and will be removed in Spree 5.5. Use variant.price_for(context).display_compare_at_amount instead.')
|
|
50
|
-
|
|
51
|
-
product_or_variant.
|
|
52
|
-
price_in(current_currency).
|
|
53
|
-
display_compare_at_price_including_vat_for(current_price_options).
|
|
54
|
-
to_html
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def link_to_tracking(shipment, options = {})
|
|
58
|
-
Spree::Deprecation.warn('BaseHelper#link_to_tracking is deprecated and will be removed in Spree 5.5. Please use shipment.tracking_url instead.')
|
|
59
|
-
|
|
60
|
-
return unless shipment.tracking && shipment.shipping_method
|
|
61
|
-
|
|
62
|
-
options[:target] ||= :blank
|
|
63
|
-
|
|
64
|
-
if shipment.tracking_url
|
|
65
|
-
link_to(shipment.tracking, shipment.tracking_url, options)
|
|
66
|
-
else
|
|
67
|
-
content_tag(:span, shipment.tracking)
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
|
|
71
25
|
def object
|
|
72
26
|
instance_variable_get('@' + controller_name.singularize)
|
|
73
27
|
end
|
|
74
28
|
|
|
75
|
-
def pretty_time(time)
|
|
76
|
-
return '' if time.blank?
|
|
77
|
-
|
|
78
|
-
Spree::Deprecation.warn('BaseHelper#pretty_time is deprecated and will be removed in Spree 5.5. Please add `local_time` gem to your Gemfile and use `local_time(time)` instead')
|
|
79
|
-
|
|
80
|
-
[I18n.l(time.to_date, format: :long), time.strftime('%l:%M %p %Z')].join(' ')
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
def pretty_date(date)
|
|
84
|
-
return '' if date.blank?
|
|
85
|
-
|
|
86
|
-
Spree::Deprecation.warn('BaseHelper#pretty_date is deprecated and will be removed in Spree 5.5. Please add `local_time` gem to your Gemfile and use `local_date(date)` instead')
|
|
87
|
-
|
|
88
|
-
[I18n.l(date.to_date, format: :long)].join(' ')
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
def seo_url(taxon, options = {})
|
|
92
|
-
Spree::Deprecation.warn('BaseHelper#seo_url is deprecated and will be removed in Spree 5.5. Please use spree_storefront_resource_url')
|
|
93
|
-
spree.nested_taxons_path(taxon.permalink, options.merge(locale: locale_param))
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
def frontend_available?
|
|
97
|
-
Spree::Deprecation.warn('BaseHelper#frontend_available? is deprecated and will be removed in Spree 5.5')
|
|
98
|
-
Spree::Core::Engine.frontend_available?
|
|
99
|
-
end
|
|
100
|
-
|
|
101
29
|
# returns the URL of an object on the storefront
|
|
102
30
|
# @param resource [Spree::Product, Spree::Taxon, Spree::Page] the resource to get the URL for
|
|
103
31
|
# @param options [Hash] the options for the URL
|
|
@@ -160,11 +88,6 @@ module Spree
|
|
|
160
88
|
product_or_variant.primary_media
|
|
161
89
|
end
|
|
162
90
|
|
|
163
|
-
def base_cache_key
|
|
164
|
-
Spree::Deprecation.warn('`base_cache_key` is deprecated and will be removed in Spree 5.5. Please use `spree_base_cache_key` instead')
|
|
165
|
-
spree_base_cache_key
|
|
166
|
-
end
|
|
167
|
-
|
|
168
91
|
def spree_base_cache_key
|
|
169
92
|
@spree_base_cache_key ||= [
|
|
170
93
|
I18n.locale,
|
|
@@ -178,11 +101,6 @@ module Spree
|
|
|
178
101
|
->(record = nil) { [*spree_base_cache_key, record].compact_blank }
|
|
179
102
|
end
|
|
180
103
|
|
|
181
|
-
def maximum_quantity
|
|
182
|
-
Spree::Deprecation.warn('BaseHelper#maximum_quantity is deprecated and will be removed in Spree 5.5')
|
|
183
|
-
Spree::DatabaseTypeUtilities::INTEGER_MAX
|
|
184
|
-
end
|
|
185
|
-
|
|
186
104
|
def payment_method_icon_tag(payment_method, opts = {})
|
|
187
105
|
return '' unless defined?(inline_svg)
|
|
188
106
|
|
|
@@ -42,18 +42,6 @@ module Spree
|
|
|
42
42
|
[label, currency]
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
-
# Returns the list of supported currencies for the current store.
|
|
46
|
-
# @return [Array<Money::Currency>] the list of supported currencies
|
|
47
|
-
def preferred_currencies
|
|
48
|
-
Spree::Deprecation.warn('preferred_currencies is deprecated and will be removed in Spree 5.5. Use current_store.supported_currencies_list instead.')
|
|
49
|
-
@preferred_currencies ||= current_store.supported_currencies_list
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def preferred_currencies_select_options
|
|
53
|
-
Spree::Deprecation.warn('preferred_currencies_select_options is deprecated and will be removed in Spree 5.5. Use supported_currency_options instead.')
|
|
54
|
-
preferred_currencies.map { |currency| currency_presentation(currency) }
|
|
55
|
-
end
|
|
56
|
-
|
|
57
45
|
def currency_money(currency = current_currency)
|
|
58
46
|
::Money::Currency.find(currency)
|
|
59
47
|
end
|
|
@@ -120,14 +120,6 @@ module Spree
|
|
|
120
120
|
limit(Spree::Config[:products_per_page])
|
|
121
121
|
end
|
|
122
122
|
|
|
123
|
-
def related_products
|
|
124
|
-
Spree::Deprecation.warn('ProductsHelper#related_products is deprecated and will be removed in Spree 5.5. Please use ProductsHelper#relations from now on.')
|
|
125
|
-
|
|
126
|
-
return [] unless @product.respond_to?(:has_related_products?)
|
|
127
|
-
|
|
128
|
-
@related_products ||= relations_by_type('related_products')
|
|
129
|
-
end
|
|
130
|
-
|
|
131
123
|
def product_available_in_currency?
|
|
132
124
|
!(@product_price.nil? || @product_price.zero?)
|
|
133
125
|
end
|
data/app/jobs/spree/base_job.rb
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
module Spree
|
|
2
|
+
# Shared base for every Spree job.
|
|
3
|
+
#
|
|
4
|
+
# Retries only transient infrastructure errors. Broad replay is unsafe here because
|
|
5
|
+
# most jobs have non-idempotent post-work side effects (counters, state transitions,
|
|
6
|
+
# lifecycle events, external calls); jobs whose work is retry-safe opt in to
|
|
7
|
+
# `retry_on StandardError` themselves (see `Spree::WebhookDeliveryJob`,
|
|
8
|
+
# `Spree::Events::SubscriberJob`). RecordNotFound gets its own tighter policy
|
|
9
|
+
# to absorb the Sidekiq enqueue-vs-DB-commit race (sub-second window) without
|
|
10
|
+
# holding the queue for genuine deletes.
|
|
2
11
|
class BaseJob < ApplicationJob
|
|
3
12
|
queue_as Spree.queues.default
|
|
13
|
+
|
|
14
|
+
retry_on ActiveRecord::Deadlocked,
|
|
15
|
+
ActiveRecord::LockWaitTimeout,
|
|
16
|
+
ActiveRecord::ConnectionNotEstablished,
|
|
17
|
+
ActiveRecord::ConnectionFailed,
|
|
18
|
+
wait: :polynomially_longer, attempts: 5
|
|
19
|
+
retry_on ActiveRecord::RecordNotFound, wait: 2.seconds, attempts: 3
|
|
20
|
+
|
|
21
|
+
discard_on ActiveJob::DeserializationError
|
|
4
22
|
end
|
|
5
23
|
end
|
|
@@ -16,7 +16,8 @@ module Spree
|
|
|
16
16
|
class SubscriberJob < Spree::BaseJob
|
|
17
17
|
queue_as Spree.queues.events
|
|
18
18
|
|
|
19
|
-
#
|
|
19
|
+
# Subscribers run user-defined code that can hit external services; broad retry
|
|
20
|
+
# is intentional.
|
|
20
21
|
retry_on StandardError, wait: :polynomially_longer, attempts: 3
|
|
21
22
|
|
|
22
23
|
discard_on ActiveJob::DeserializationError do |job, error|
|
|
@@ -3,6 +3,17 @@ module Spree
|
|
|
3
3
|
class GenerateJob < Spree::BaseJob
|
|
4
4
|
queue_as Spree.queues.exports
|
|
5
5
|
|
|
6
|
+
# `Export#generate` is not retry-safe: each call re-attaches a new ActiveStorage
|
|
7
|
+
# blob (leaving the prior one orphaned) and re-enqueues the completion email.
|
|
8
|
+
# Opt out of the parent's retry policy so transient errors fail fast into the
|
|
9
|
+
# dead queue for operator review rather than producing duplicate side effects.
|
|
10
|
+
retry_on ActiveRecord::Deadlocked,
|
|
11
|
+
ActiveRecord::LockWaitTimeout,
|
|
12
|
+
ActiveRecord::ConnectionNotEstablished,
|
|
13
|
+
ActiveRecord::ConnectionFailed,
|
|
14
|
+
ActiveRecord::RecordNotFound,
|
|
15
|
+
attempts: 1
|
|
16
|
+
|
|
6
17
|
def perform(export_id)
|
|
7
18
|
export = Spree::Export.find_by_prefix_id!(export_id)
|
|
8
19
|
export.generate
|
|
@@ -11,7 +11,7 @@ module Spree
|
|
|
11
11
|
discard_on URI::InvalidURIError
|
|
12
12
|
discard_on SsrfFilter::Error
|
|
13
13
|
|
|
14
|
-
def perform(viewable_id, viewable_type, external_url, external_id = nil, position = nil)
|
|
14
|
+
def perform(viewable_id, viewable_type, external_url, external_id = nil, position = nil, link_variant_id = nil)
|
|
15
15
|
viewable = viewable_type.safe_constantize.find(viewable_id)
|
|
16
16
|
|
|
17
17
|
Spree::Image.ensure_metafield_definition_exists!(Spree::Image::EXTERNAL_URL_METAFIELD_KEY)
|
|
@@ -30,9 +30,14 @@ module Spree
|
|
|
30
30
|
|
|
31
31
|
# don't re-download the image if it's already been downloaded
|
|
32
32
|
# still trigger save! if position has changed
|
|
33
|
-
|
|
33
|
+
if image_already_saved?(image, external_url)
|
|
34
|
+
image.save!
|
|
35
|
+
link_to_variant(image, link_variant_id)
|
|
36
|
+
return
|
|
37
|
+
end
|
|
34
38
|
|
|
35
39
|
download_and_attach_image(external_url, image, external_id)
|
|
40
|
+
link_to_variant(image, link_variant_id)
|
|
36
41
|
rescue ActiveStorage::IntegrityError => e
|
|
37
42
|
raise e unless Rails.env.test?
|
|
38
43
|
end
|
|
@@ -85,21 +90,31 @@ module Spree
|
|
|
85
90
|
image.persisted? && image.attachment.attached? && image.external_url.present? && external_url == image.external_url
|
|
86
91
|
end
|
|
87
92
|
|
|
93
|
+
# `Product#images` delegates to the master variant (legacy alias) — use
|
|
94
|
+
# `Product#media` so 5.5 product-level uploads don't get re-pinned to master.
|
|
95
|
+
def viewable_assets(viewable)
|
|
96
|
+
viewable.is_a?(Spree::Product) ? viewable.media : viewable.images
|
|
97
|
+
end
|
|
98
|
+
|
|
88
99
|
def image_scope(viewable)
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
else
|
|
92
|
-
viewable.images
|
|
93
|
-
end
|
|
100
|
+
scope = viewable_assets(viewable)
|
|
101
|
+
scope.respond_to?(:with_deleted) ? scope.with_deleted : scope
|
|
94
102
|
end
|
|
95
103
|
|
|
96
104
|
def find_or_initialize_image(viewable, external_url, external_id = nil)
|
|
97
105
|
if external_id.present? && viewable.respond_to?(:external_id)
|
|
98
106
|
image_scope(viewable).find_or_initialize_by(external_id: external_id)
|
|
99
107
|
else
|
|
100
|
-
image_scope(viewable).with_external_url(external_url).first || viewable.
|
|
108
|
+
image_scope(viewable).with_external_url(external_url).first || viewable_assets(viewable).new
|
|
101
109
|
end
|
|
102
110
|
end
|
|
111
|
+
|
|
112
|
+
def link_to_variant(image, variant_id)
|
|
113
|
+
return if variant_id.blank?
|
|
114
|
+
return unless image.persisted? && image.viewable_type == 'Spree::Product'
|
|
115
|
+
|
|
116
|
+
Spree::VariantMedia.find_or_create_by(variant_id: variant_id, media_id: image.id)
|
|
117
|
+
end
|
|
103
118
|
end
|
|
104
119
|
end
|
|
105
120
|
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Imports
|
|
3
|
+
# Shared base for every job in the imports pipeline.
|
|
4
|
+
#
|
|
5
|
+
# The narrow transient-error retry policy is inherited from `Spree::BaseJob`;
|
|
6
|
+
# we only override the queue here. Per-row business errors are caught inside
|
|
7
|
+
# `Spree::ImportRow#process!` and converted to `row.fail!`, so they never
|
|
8
|
+
# bubble up to the job layer. Subclasses may extend the retry list (e.g.
|
|
9
|
+
# `CreateCategoriesJob` adds `RecordNotUnique` to recover from concurrent
|
|
10
|
+
# taxon creation races).
|
|
11
|
+
class BaseJob < Spree::BaseJob
|
|
12
|
+
queue_as Spree.queues.imports
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Imports
|
|
3
|
+
class CreateCategoriesJob < Spree::Imports::BaseJob
|
|
4
|
+
# Concurrent imports can race on `with_matching_name(...).first || create!(...)`
|
|
5
|
+
# for the same taxonomy/taxon name and hit the unique index; a retry then finds
|
|
6
|
+
# the peer's committed row.
|
|
7
|
+
retry_on ActiveRecord::RecordNotUnique, wait: :polynomially_longer, attempts: 5
|
|
8
|
+
|
|
9
|
+
def perform(product_id, store_id, taxon_pretty_names)
|
|
10
|
+
product = Spree::Product.find(product_id)
|
|
11
|
+
store = Spree::Store.find(store_id)
|
|
12
|
+
taxons = taxon_pretty_names.filter_map { |taxon_pretty_name| find_or_create_taxon(store, taxon_pretty_name) }
|
|
13
|
+
|
|
14
|
+
product.taxons = taxons
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def find_or_create_taxon(store, taxon_pretty_name)
|
|
20
|
+
taxon_names = taxon_pretty_name.strip.split('->').map(&:strip).map(&:presence).compact
|
|
21
|
+
return if taxon_names.empty?
|
|
22
|
+
|
|
23
|
+
taxonomy_name = taxon_names.shift
|
|
24
|
+
taxonomy = store.taxonomies.with_matching_name(taxonomy_name).first || store.taxonomies.create!(name: taxonomy_name)
|
|
25
|
+
|
|
26
|
+
last_taxon = taxonomy.root
|
|
27
|
+
|
|
28
|
+
taxon_names.each do |taxon_name|
|
|
29
|
+
last_taxon = taxonomy.taxons.with_matching_name(taxon_name).where(parent: last_taxon).first ||
|
|
30
|
+
taxonomy.taxons.create!(name: taxon_name, parent: last_taxon)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
last_taxon
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
module Spree
|
|
2
2
|
module Imports
|
|
3
|
-
class CreateRowsJob < Spree::BaseJob
|
|
4
|
-
queue_as Spree.queues.imports
|
|
5
|
-
|
|
3
|
+
class CreateRowsJob < Spree::Imports::BaseJob
|
|
6
4
|
discard_on ::CSV::MalformedCSVError, EncodingError do |job, error|
|
|
7
5
|
import = Spree::Import.find(job.arguments.first)
|
|
8
6
|
import.update_columns(processing_errors: error.message, status: :failed, updated_at: Time.current)
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
module Spree
|
|
2
2
|
module Imports
|
|
3
|
-
class ProcessGroupJob < Spree::BaseJob
|
|
4
|
-
queue_as Spree.queues.imports
|
|
5
|
-
|
|
3
|
+
class ProcessGroupJob < Spree::Imports::BaseJob
|
|
6
4
|
def perform(import_id, row_ids)
|
|
7
5
|
import = Spree::Import.find(import_id)
|
|
8
6
|
Spree::Current.store = import.store
|
|
@@ -10,7 +8,8 @@ module Spree
|
|
|
10
8
|
mappings = import.mappings.mapped.to_a
|
|
11
9
|
schema_fields = import.schema_fields
|
|
12
10
|
large = import.large_import?
|
|
13
|
-
|
|
11
|
+
# Skip rows already completed on a prior attempt so retries don't double-process them.
|
|
12
|
+
rows = import.rows.where(id: row_ids).pending_and_failed.order(:row_number)
|
|
14
13
|
|
|
15
14
|
if large
|
|
16
15
|
Spree::Events.disable do
|
|
@@ -27,14 +26,17 @@ module Spree
|
|
|
27
26
|
|
|
28
27
|
private
|
|
29
28
|
|
|
29
|
+
# Completion is row-state-derived so retry-induced over-increments of the counter
|
|
30
|
+
# stay harmless. The counter pre-check just shortcuts the row scan for workers
|
|
31
|
+
# that obviously can't be the last group to finish. `in_flight` excludes orphaned
|
|
32
|
+
# `processing` rows past the stall window so a dead worker can't block completion.
|
|
30
33
|
def check_import_completion(import, large)
|
|
31
34
|
Spree::Import.where(id: import.id).update_all(
|
|
32
35
|
'completed_groups_count = COALESCE(completed_groups_count, 0) + 1'
|
|
33
36
|
)
|
|
34
37
|
import.reload
|
|
35
38
|
|
|
36
|
-
if import.completed_groups_count >= import.processing_groups_count
|
|
37
|
-
# Guard against concurrent workers both reaching this point
|
|
39
|
+
if import.completed_groups_count >= import.processing_groups_count && import.rows.in_flight.none?
|
|
38
40
|
import.complete! if import.status == 'processing'
|
|
39
41
|
elsif large && (import.completed_groups_count % 10).zero?
|
|
40
42
|
import.publish_event('import.progress')
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Media
|
|
3
|
+
# Per-product worker for the 5.4 → 5.5 media migration. Idempotent.
|
|
4
|
+
class MigrateProductAssetsJob < Spree::BaseJob
|
|
5
|
+
queue_as Spree.queues.images
|
|
6
|
+
|
|
7
|
+
def perform(product_id)
|
|
8
|
+
product = Spree::Product.includes(:master, :variants).find_by(id: product_id)
|
|
9
|
+
return unless product
|
|
10
|
+
|
|
11
|
+
# One query for all variant-pinned assets on the product (master +
|
|
12
|
+
# non-master). Grouping by viewable_id avoids N queries per variant.
|
|
13
|
+
viewable_ids = product.variants.map(&:id)
|
|
14
|
+
viewable_ids << product.master.id if product.master
|
|
15
|
+
return if viewable_ids.empty?
|
|
16
|
+
|
|
17
|
+
assets_by_variant = Spree::Asset
|
|
18
|
+
.where(viewable_type: 'Spree::Variant', viewable_id: viewable_ids)
|
|
19
|
+
.pluck(:id, :viewable_id)
|
|
20
|
+
.group_by(&:last)
|
|
21
|
+
.transform_values { |rows| rows.map(&:first) }
|
|
22
|
+
return if assets_by_variant.empty?
|
|
23
|
+
|
|
24
|
+
master_id = product.master&.id
|
|
25
|
+
touched_variants = []
|
|
26
|
+
|
|
27
|
+
product.variants.each do |variant|
|
|
28
|
+
asset_ids = assets_by_variant[variant.id]
|
|
29
|
+
next if asset_ids.blank?
|
|
30
|
+
|
|
31
|
+
move_assets_to_product(asset_ids, product)
|
|
32
|
+
link_assets_to_variant(asset_ids, variant.id)
|
|
33
|
+
touched_variants << variant
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
if master_id && (master_asset_ids = assets_by_variant[master_id]).present?
|
|
37
|
+
move_assets_to_product(master_asset_ids, product)
|
|
38
|
+
touched_variants << product.master
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# update_all + upsert_all skip callbacks, so refresh thumbnails by hand.
|
|
42
|
+
touched_variants.each(&:update_thumbnail!)
|
|
43
|
+
recalculate_counters(product)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def move_assets_to_product(asset_ids, product)
|
|
49
|
+
Spree::Asset.where(id: asset_ids).update_all(
|
|
50
|
+
viewable_type: 'Spree::Product',
|
|
51
|
+
viewable_id: product.id,
|
|
52
|
+
updated_at: Time.current
|
|
53
|
+
)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def link_assets_to_variant(asset_ids, variant_id)
|
|
57
|
+
rows = asset_ids.map { |asset_id| { variant_id: variant_id, media_id: asset_id } }
|
|
58
|
+
|
|
59
|
+
# MySQL infers the conflict target from the unique index; only PG/SQLite
|
|
60
|
+
# need the explicit `unique_by:`.
|
|
61
|
+
opts = { on_duplicate: :skip }
|
|
62
|
+
if %w[PostgreSQL SQLite].include?(ActiveRecord::Base.connection.adapter_name)
|
|
63
|
+
opts[:unique_by] = :idx_variant_media_unique
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
Spree::VariantMedia.upsert_all(rows, **opts)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def recalculate_counters(product)
|
|
70
|
+
new_count = product.media.count
|
|
71
|
+
new_primary_id = product.media.order(:position).pick(:id)
|
|
72
|
+
|
|
73
|
+
return if product.media_count == new_count && product.primary_media_id == new_primary_id
|
|
74
|
+
|
|
75
|
+
product.update_columns(
|
|
76
|
+
media_count: new_count,
|
|
77
|
+
primary_media_id: new_primary_id,
|
|
78
|
+
updated_at: Time.current
|
|
79
|
+
)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -3,11 +3,22 @@ module Spree
|
|
|
3
3
|
class RefreshMetricsJob < Spree::BaseJob
|
|
4
4
|
queue_as Spree.queues.products
|
|
5
5
|
|
|
6
|
-
def perform(product_id
|
|
7
|
-
|
|
8
|
-
return unless
|
|
6
|
+
def perform(product_id)
|
|
7
|
+
product = Spree::Product.find_by(id: product_id)
|
|
8
|
+
return unless product
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
completed_order_ids = product.completed_orders.select(:id)
|
|
11
|
+
variant_ids = product.variants_including_master.ids
|
|
12
|
+
|
|
13
|
+
line_items = Spree::LineItem.joins(:order)
|
|
14
|
+
.where(spree_orders: { id: completed_order_ids })
|
|
15
|
+
.where(variant_id: variant_ids)
|
|
16
|
+
|
|
17
|
+
# update columns to skip callbacks
|
|
18
|
+
product.update_columns(
|
|
19
|
+
units_sold_count: line_items.sum(:quantity),
|
|
20
|
+
revenue: line_items.sum(:pre_tax_amount)
|
|
21
|
+
)
|
|
11
22
|
end
|
|
12
23
|
end
|
|
13
24
|
end
|
|
@@ -3,6 +3,17 @@ module Spree
|
|
|
3
3
|
class GenerateJob < Spree::BaseJob
|
|
4
4
|
queue_as Spree.queues.reports
|
|
5
5
|
|
|
6
|
+
# `Report#generate` is not retry-safe: each call re-attaches a new ActiveStorage
|
|
7
|
+
# blob (leaving the prior one orphaned) and re-enqueues the completion email.
|
|
8
|
+
# Opt out of the parent's retry policy so transient errors fail fast into the
|
|
9
|
+
# dead queue for operator review rather than producing duplicate side effects.
|
|
10
|
+
retry_on ActiveRecord::Deadlocked,
|
|
11
|
+
ActiveRecord::LockWaitTimeout,
|
|
12
|
+
ActiveRecord::ConnectionNotEstablished,
|
|
13
|
+
ActiveRecord::ConnectionFailed,
|
|
14
|
+
ActiveRecord::RecordNotFound,
|
|
15
|
+
attempts: 1
|
|
16
|
+
|
|
6
17
|
def perform(report_id)
|
|
7
18
|
report = Spree::Report.find_by_prefix_id!(report_id)
|
|
8
19
|
report.generate
|
|
@@ -3,8 +3,12 @@ module Spree
|
|
|
3
3
|
class IndexJob < Spree::BaseJob
|
|
4
4
|
queue_as Spree.queues.search
|
|
5
5
|
|
|
6
|
+
# Search providers are external services (Meilisearch, etc.); a transient 5xx or
|
|
7
|
+
# network blip should not drop the index update.
|
|
6
8
|
retry_on StandardError, wait: :polynomially_longer, attempts: 5
|
|
7
|
-
|
|
9
|
+
# Must come after `retry_on StandardError` so DeserializationError lands in discard
|
|
10
|
+
# (ActiveJob handler lookup is reverse-declaration-order).
|
|
11
|
+
discard_on ActiveJob::DeserializationError
|
|
8
12
|
|
|
9
13
|
# @param resource_class [String] e.g. 'Spree::Product'
|
|
10
14
|
# @param resource_id [String] always pass as string for UUID support
|
|
@@ -3,7 +3,11 @@ module Spree
|
|
|
3
3
|
class RemoveJob < Spree::BaseJob
|
|
4
4
|
queue_as Spree.queues.search
|
|
5
5
|
|
|
6
|
+
# Search providers are external services; broad retry covers network blips and 5xx.
|
|
6
7
|
retry_on StandardError, wait: :polynomially_longer, attempts: 5
|
|
8
|
+
# Must come after `retry_on StandardError` so DeserializationError lands in discard
|
|
9
|
+
# (ActiveJob handler lookup is reverse-declaration-order).
|
|
10
|
+
discard_on ActiveJob::DeserializationError
|
|
7
11
|
|
|
8
12
|
# @param prefixed_id [String] prefixed ID of the document to remove (e.g. 'prod_abc')
|
|
9
13
|
# @param store_id [String] always pass as string for UUID support
|
|
@@ -18,11 +18,44 @@ module Spree
|
|
|
18
18
|
calculator.class.to_s if calculator
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
+
# Accepts a fully-qualified class name (`'Spree::Calculator::FlatRate'`)
|
|
22
|
+
# or the public API shorthand (`'flat_rate'`). Shorthand is resolved
|
|
23
|
+
# against this parent's registered calculators so a CreateAdjustment
|
|
24
|
+
# can't be assigned a shipping-only calculator just by knowing its
|
|
25
|
+
# name.
|
|
21
26
|
def calculator_type=(calculator_type)
|
|
22
|
-
|
|
27
|
+
return if calculator_type.blank?
|
|
28
|
+
|
|
29
|
+
str = calculator_type.to_s
|
|
30
|
+
klass =
|
|
31
|
+
if str.include?('::')
|
|
32
|
+
str.safe_constantize
|
|
33
|
+
else
|
|
34
|
+
registry = self.class.respond_to?(:calculators) ? self.class.calculators : []
|
|
35
|
+
registry.find { |k| k.api_type == str }
|
|
36
|
+
end
|
|
23
37
|
self.calculator = klass.new if klass && !calculator.instance_of?(klass)
|
|
24
38
|
end
|
|
25
39
|
|
|
40
|
+
# API v3 writer for the flat `calculator: { type:, preferences: {} }`
|
|
41
|
+
# payload. Routes preferences through `set_preference` so values are
|
|
42
|
+
# coerced by the typed `preferred_<name>=` setters — direct
|
|
43
|
+
# assignment to the serialized hash would skip coercion.
|
|
44
|
+
def assign_calculator_attributes(attrs)
|
|
45
|
+
return if attrs.nil?
|
|
46
|
+
|
|
47
|
+
attrs = attrs.to_h.with_indifferent_access
|
|
48
|
+
self.calculator_type = attrs[:type] if attrs[:type].present?
|
|
49
|
+
|
|
50
|
+
return if calculator.nil? || attrs[:preferences].blank?
|
|
51
|
+
|
|
52
|
+
attrs[:preferences].to_h.each do |key, value|
|
|
53
|
+
next unless calculator.has_preference?(key.to_sym)
|
|
54
|
+
|
|
55
|
+
calculator.set_preference(key.to_sym, value)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
26
59
|
private
|
|
27
60
|
|
|
28
61
|
def self.model_name_without_spree_namespace
|
|
@@ -9,11 +9,42 @@ module Spree
|
|
|
9
9
|
scope :available_on_front_end, -> { where(display_on: [:front_end, :both]) }
|
|
10
10
|
scope :available_on_back_end, -> { where(display_on: [:back_end, :both]) }
|
|
11
11
|
|
|
12
|
+
# 5.5 → 6.0 bridge: see docs/plans/5.5-6.0-display-on-to-boolean.md.
|
|
13
|
+
# The tri-state `display_on` enum collapses to a single
|
|
14
|
+
# `storefront_visible` boolean in 6.0 — `back_end` becomes `false`,
|
|
15
|
+
# everything else becomes `true`. The legacy `front_end`-only value
|
|
16
|
+
# (visible to customers but hidden from staff) has no real use case
|
|
17
|
+
# and folds into `storefront_visible: true` on migration.
|
|
18
|
+
scope :storefront_visible, -> { where.not(display_on: 'back_end') }
|
|
19
|
+
scope :admin_only, -> { where(display_on: 'back_end') }
|
|
20
|
+
|
|
21
|
+
# Expose `storefront_visible` to Ransack so admin clients can filter
|
|
22
|
+
# by it (e.g. `q[storefront_visible_eq]=true`) without knowing about
|
|
23
|
+
# the underlying tri-state `display_on` column.
|
|
24
|
+
ransacker :storefront_visible, type: :boolean do |parent|
|
|
25
|
+
# Wrap in `Grouping` so Postgres sees `(display_on != 'back_end') = TRUE`
|
|
26
|
+
# instead of the ambiguous `display_on != 'back_end' = TRUE`.
|
|
27
|
+
Arel::Nodes::Grouping.new(
|
|
28
|
+
Arel::Nodes::NotEqual.new(parent.table[:display_on], Arel::Nodes::Quoted.new('back_end'))
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
self.whitelisted_ransackable_attributes =
|
|
33
|
+
(whitelisted_ransackable_attributes || []) | %w[storefront_visible]
|
|
34
|
+
|
|
12
35
|
validates :display_on, presence: true, inclusion: { in: DISPLAY.map(&:to_s) }
|
|
13
36
|
|
|
14
37
|
def available_on_front_end?
|
|
15
38
|
display_on == 'front_end' || display_on == 'both'
|
|
16
39
|
end
|
|
40
|
+
|
|
41
|
+
def storefront_visible
|
|
42
|
+
display_on != 'back_end'
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def storefront_visible=(value)
|
|
46
|
+
self.display_on = ActiveModel::Type::Boolean.new.cast(value) ? 'both' : 'back_end'
|
|
47
|
+
end
|
|
17
48
|
end
|
|
18
49
|
end
|
|
19
50
|
end
|