spree_core 4.10.1 → 5.0.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/assets/config/spree_core_manifest.js +4 -0
- data/app/assets/images/google_on_white_hdpi.png +0 -0
- data/app/controllers/spree/base_controller.rb +1 -4
- data/app/finders/spree/orders/find_complete.rb +14 -2
- data/app/finders/spree/products/find.rb +74 -23
- data/app/finders/spree/stores/find_current.rb +6 -2
- data/app/helpers/spree/addresses_helper.rb +106 -0
- data/app/helpers/spree/base_helper.rb +83 -55
- data/app/helpers/spree/currency_helper.rb +12 -0
- data/app/helpers/spree/images_helper.rb +18 -0
- data/app/helpers/spree/mail_helper.rb +24 -0
- data/app/helpers/spree/products_helper.rb +1 -20
- data/app/javascript/spree/core/controllers/address_autocomplete_controller.js +233 -0
- data/app/javascript/spree/core/controllers/address_form_controller.js +98 -0
- data/app/javascript/spree/core/controllers/enable_button_controller.js +29 -0
- data/app/javascript/spree/core/helpers/address_autocomplete/google_places_suggestions_provider.js +179 -0
- data/app/javascript/spree/core/helpers/debounce.js +9 -0
- data/app/javascript/spree/core/helpers/scroll_lock.js +25 -0
- data/app/jobs/spree/addresses/geocode_address_job.rb +31 -0
- data/app/jobs/spree/base_job.rb +1 -0
- data/app/jobs/spree/coupon_codes/bulk_generate_job.rb +17 -0
- data/app/jobs/spree/exports/generate_job.rb +12 -0
- data/app/jobs/spree/products/auto_match_taxons_job.rb +14 -0
- data/app/jobs/spree/products/touch_taxons_job.rb +12 -0
- data/app/jobs/spree/reports/generate_job.rb +12 -0
- data/app/jobs/spree/stock_locations/stock_items/create_job.rb +1 -1
- data/app/jobs/spree/themes/duplicate_components_job.rb +59 -0
- data/app/jobs/spree/themes/screenshot_job.rb +81 -0
- data/app/jobs/spree/variants/remove_from_incomplete_orders_job.rb +2 -0
- data/app/jobs/spree/variants/remove_line_item_job.rb +2 -0
- data/app/mailers/spree/base_mailer.rb +51 -0
- data/app/mailers/spree/export_mailer.rb +19 -0
- data/app/mailers/spree/report_mailer.rb +20 -0
- data/app/mailers/spree/test_mailer.rb +8 -0
- data/app/models/action_text/rich_text_decorator.rb +11 -0
- data/app/models/action_text/video_embed.rb +13 -0
- data/app/models/acts_as_taggable_on/tag_decorator.rb +10 -0
- data/app/models/concerns/spree/calculated_adjustments.rb +2 -0
- data/app/models/concerns/spree/display_on.rb +13 -0
- data/app/models/concerns/spree/has_one_link.rb +42 -0
- data/app/models/concerns/spree/has_page_links.rb +53 -0
- data/app/models/concerns/spree/metadata.rb +3 -7
- data/app/models/concerns/spree/multi_searchable.rb +21 -0
- data/app/models/concerns/spree/multi_store_resource.rb +9 -0
- data/app/models/concerns/spree/number_as_param.rb +4 -2
- data/app/models/concerns/spree/parameterizable_name.rb +21 -0
- data/app/models/concerns/spree/previewable.rb +17 -0
- data/app/models/concerns/spree/product_scopes.rb +7 -7
- data/app/models/concerns/spree/stores/setup.rb +52 -0
- data/app/models/concerns/spree/stores/socials.rb +68 -0
- data/app/models/concerns/spree/user_address.rb +4 -0
- data/app/models/concerns/spree/user_methods.rb +61 -11
- data/app/models/concerns/spree/user_reporting.rb +14 -6
- data/app/models/concerns/spree/user_roles.rb +1 -1
- data/app/models/spree/ability.rb +4 -2
- data/app/models/spree/address.rb +122 -28
- data/app/models/spree/adjustment.rb +4 -1
- data/app/models/spree/asset/support/active_storage.rb +1 -1
- data/app/models/spree/asset.rb +18 -6
- data/app/models/spree/base.rb +38 -7
- data/app/models/spree/base_analytics_event_handler.rb +49 -0
- data/app/models/spree/calculator.rb +1 -1
- data/app/models/spree/classification.rb +10 -1
- data/app/models/spree/country.rb +1 -1
- data/app/models/spree/coupon_code.rb +38 -0
- data/app/models/spree/credit_card.rb +4 -3
- data/app/models/spree/current.rb +9 -0
- data/app/models/spree/custom_domain.rb +53 -0
- data/app/models/spree/customer_return.rb +13 -1
- data/app/models/spree/data_feed.rb +1 -1
- data/app/models/spree/digital.rb +5 -6
- data/app/models/spree/digital_link.rb +11 -6
- data/app/models/spree/export.rb +172 -0
- data/app/models/spree/exports/orders.rb +23 -0
- data/app/models/spree/exports/products.rb +35 -0
- data/app/models/spree/gateway/bogus.rb +5 -0
- data/app/models/spree/gateway.rb +14 -4
- data/app/models/spree/gateway_customer.rb +13 -0
- data/app/models/spree/image/configuration/active_storage.rb +1 -1
- data/app/models/spree/inventory_unit.rb +1 -1
- data/app/models/spree/legacy_user.rb +3 -1
- data/app/models/spree/line_item.rb +37 -2
- data/app/models/spree/log_entry.rb +1 -1
- data/app/models/spree/option_type.rb +40 -6
- data/app/models/spree/option_type_prototype.rb +1 -1
- data/app/models/spree/option_value.rb +35 -5
- data/app/models/spree/option_value_variant.rb +2 -2
- data/app/models/spree/order/address_book.rb +34 -15
- data/app/models/spree/order/checkout.rb +12 -2
- data/app/models/spree/order/currency_updater.rb +1 -1
- data/app/models/spree/order/digital.rb +14 -3
- data/app/models/spree/order/emails.rb +1 -1
- data/app/models/spree/order/payments.rb +1 -13
- data/app/models/spree/order/store_credit.rb +17 -5
- data/app/models/spree/order/webhooks.rb +1 -1
- data/app/models/spree/order.rb +179 -49
- data/app/models/spree/order_promotion.rb +1 -1
- data/app/models/spree/order_updater.rb +9 -3
- data/app/models/spree/page.rb +184 -0
- data/app/models/spree/page_block.rb +69 -0
- data/app/models/spree/page_blocks/buttons.rb +29 -0
- data/app/models/spree/page_blocks/heading.rb +18 -0
- data/app/models/spree/page_blocks/image.rb +17 -0
- data/app/models/spree/page_blocks/link.rb +21 -0
- data/app/models/spree/page_blocks/mega_nav.rb +33 -0
- data/app/models/spree/page_blocks/mega_nav_with_subcategories.rb +32 -0
- data/app/models/spree/page_blocks/nav.rb +15 -0
- data/app/models/spree/page_blocks/newsletter_form.rb +18 -0
- data/app/models/spree/page_blocks/products/buy_buttons.rb +12 -0
- data/app/models/spree/page_blocks/products/price.rb +18 -0
- data/app/models/spree/page_blocks/products/quantity_selector.rb +12 -0
- data/app/models/spree/page_blocks/products/share.rb +8 -0
- data/app/models/spree/page_blocks/products/title.rb +15 -0
- data/app/models/spree/page_blocks/products/variant_picker.rb +9 -0
- data/app/models/spree/page_blocks/subheading.rb +17 -0
- data/app/models/spree/page_blocks/text.rb +16 -0
- data/app/models/spree/page_link.rb +59 -0
- data/app/models/spree/page_section.rb +215 -0
- data/app/models/spree/page_sections/announcement_bar.rb +30 -0
- data/app/models/spree/page_sections/collection_banner.rb +18 -0
- data/app/models/spree/page_sections/custom_code.rb +11 -0
- data/app/models/spree/page_sections/featured_posts.rb +43 -0
- data/app/models/spree/page_sections/featured_product.rb +46 -0
- data/app/models/spree/page_sections/featured_taxon.rb +88 -0
- data/app/models/spree/page_sections/featured_taxons.rb +45 -0
- data/app/models/spree/page_sections/footer.rb +94 -0
- data/app/models/spree/page_sections/header.rb +62 -0
- data/app/models/spree/page_sections/image_banner.rb +38 -0
- data/app/models/spree/page_sections/image_with_text.rb +48 -0
- data/app/models/spree/page_sections/main_password_footer.rb +18 -0
- data/app/models/spree/page_sections/main_password_header.rb +20 -0
- data/app/models/spree/page_sections/newsletter.rb +47 -0
- data/app/models/spree/page_sections/page_title.rb +19 -0
- data/app/models/spree/page_sections/post_details.rb +19 -0
- data/app/models/spree/page_sections/post_grid.rb +19 -0
- data/app/models/spree/page_sections/product_details.rb +16 -0
- data/app/models/spree/page_sections/product_grid.rb +13 -0
- data/app/models/spree/page_sections/related_products.rb +60 -0
- data/app/models/spree/page_sections/rich_text.rb +20 -0
- data/app/models/spree/page_sections/taxon_banner.rb +18 -0
- data/app/models/spree/page_sections/taxon_grid.rb +17 -0
- data/app/models/spree/page_sections/video.rb +99 -0
- data/app/models/spree/pages/account.rb +15 -0
- data/app/models/spree/pages/cart.rb +9 -0
- data/app/models/spree/pages/checkout.rb +9 -0
- data/app/models/spree/pages/custom.rb +34 -0
- data/app/models/spree/pages/homepage.rb +68 -0
- data/app/models/spree/pages/login.rb +15 -0
- data/app/models/spree/pages/password.rb +57 -0
- data/app/models/spree/pages/post.rb +27 -0
- data/app/models/spree/pages/post_list.rb +32 -0
- data/app/models/spree/pages/product_details.rb +29 -0
- data/app/models/spree/pages/search_results.rb +43 -0
- data/app/models/spree/pages/shop_all.rb +36 -0
- data/app/models/spree/pages/taxon.rb +29 -0
- data/app/models/spree/pages/taxon_list.rb +41 -0
- data/app/models/spree/pages/wishlist.rb +11 -0
- data/app/models/spree/payment/gateway_options.rb +13 -15
- data/app/models/spree/payment/processing.rb +1 -1
- data/app/models/spree/payment/webhooks.rb +1 -1
- data/app/models/spree/payment.rb +61 -12
- data/app/models/spree/payment_capture_event.rb +1 -1
- data/app/models/spree/payment_method/store_credit.rb +9 -1
- data/app/models/spree/payment_method.rb +26 -8
- data/app/models/spree/payment_source.rb +2 -2
- data/app/models/spree/post.rb +108 -0
- data/app/models/spree/post_category.rb +33 -0
- data/app/models/spree/preference.rb +1 -1
- data/app/models/spree/price.rb +10 -3
- data/app/models/spree/product/webhooks.rb +1 -1
- data/app/models/spree/product.rb +325 -54
- data/app/models/spree/product_option_type.rb +1 -1
- data/app/models/spree/product_promotion_rule.rb +1 -1
- data/app/models/spree/product_property.rb +4 -1
- data/app/models/spree/promotion/actions/create_line_items.rb +37 -2
- data/app/models/spree/promotion/rules/country.rb +22 -4
- data/app/models/spree/promotion/rules/currency.rb +21 -0
- data/app/models/spree/promotion/rules/item_total.rb +12 -3
- data/app/models/spree/promotion/rules/option_value.rb +5 -20
- data/app/models/spree/promotion/rules/product.rb +33 -1
- data/app/models/spree/promotion/rules/taxon.rb +31 -0
- data/app/models/spree/promotion/rules/user.rb +33 -4
- data/app/models/spree/promotion.rb +136 -15
- data/app/models/spree/promotion_action.rb +31 -1
- data/app/models/spree/promotion_action_line_item.rb +1 -1
- data/app/models/spree/promotion_category.rb +1 -1
- data/app/models/spree/promotion_handler/cart.rb +1 -1
- data/app/models/spree/promotion_handler/coupon.rb +46 -8
- data/app/models/spree/promotion_handler/free_shipping.rb +1 -1
- data/app/models/spree/promotion_handler/promotion_duplicator.rb +6 -5
- data/app/models/spree/promotion_rule.rb +23 -1
- data/app/models/spree/promotion_rule_taxon.rb +1 -1
- data/app/models/spree/promotion_rule_user.rb +1 -1
- data/app/models/spree/property.rb +11 -3
- data/app/models/spree/property_prototype.rb +1 -1
- data/app/models/spree/prototype.rb +1 -1
- data/app/models/spree/prototype_taxon.rb +1 -1
- data/app/models/spree/refund.rb +24 -4
- data/app/models/spree/refund_reason.rb +12 -2
- data/app/models/spree/reimbursement/credit.rb +1 -1
- data/app/models/spree/reimbursement/emails.rb +1 -1
- data/app/models/spree/reimbursement.rb +5 -3
- data/app/models/spree/reimbursement_type.rb +1 -1
- data/app/models/spree/report.rb +110 -0
- data/app/models/spree/report_line_item.rb +27 -0
- data/app/models/spree/report_line_items/products_performance.rb +76 -0
- data/app/models/spree/report_line_items/sales_total.rb +49 -0
- data/app/models/spree/reports/products_performance.rb +44 -0
- data/app/models/spree/reports/sales_total.rb +14 -0
- data/app/models/spree/return_authorization.rb +4 -1
- data/app/models/spree/return_authorization_reason.rb +1 -1
- data/app/models/spree/return_item.rb +2 -1
- data/app/models/spree/role.rb +5 -2
- data/app/models/spree/role_user.rb +2 -2
- data/app/models/spree/shipment/emails.rb +1 -1
- data/app/models/spree/shipment/webhooks.rb +1 -1
- data/app/models/spree/shipment.rb +14 -4
- data/app/models/spree/shipping_category.rb +1 -1
- data/app/models/spree/shipping_method.rb +48 -8
- data/app/models/spree/shipping_method_category.rb +1 -1
- data/app/models/spree/shipping_method_zone.rb +1 -1
- data/app/models/spree/shipping_rate.rb +21 -3
- data/app/models/spree/state.rb +1 -1
- data/app/models/spree/state_change.rb +1 -1
- data/app/models/spree/stock_item/webhooks.rb +1 -1
- data/app/models/spree/stock_item.rb +9 -6
- data/app/models/spree/stock_location.rb +35 -8
- data/app/models/spree/stock_movement/webhooks.rb +1 -1
- data/app/models/spree/stock_movement.rb +9 -3
- data/app/models/spree/stock_transfer.rb +41 -3
- data/app/models/spree/store.rb +345 -72
- data/app/models/spree/store_credit.rb +12 -4
- data/app/models/spree/store_credit_category.rb +1 -1
- data/app/models/spree/store_credit_event.rb +21 -1
- data/app/models/spree/store_credit_type.rb +1 -1
- data/app/models/spree/store_payment_method.rb +1 -1
- data/app/models/spree/store_product.rb +1 -1
- data/app/models/spree/store_promotion.rb +1 -1
- data/app/models/spree/tax_category.rb +15 -1
- data/app/models/spree/tax_rate.rb +1 -1
- data/app/models/spree/taxon.rb +210 -27
- data/app/models/spree/taxon_image/configuration/active_storage.rb +1 -1
- data/app/models/spree/taxon_rule.rb +25 -0
- data/app/models/spree/taxon_rules/available_on.rb +20 -0
- data/app/models/spree/taxon_rules/sale.rb +16 -0
- data/app/models/spree/taxon_rules/tag.rb +19 -0
- data/app/models/spree/taxonomy.rb +3 -2
- data/app/models/spree/theme.rb +200 -0
- data/app/models/spree/themes/default.rb +94 -0
- data/app/models/spree/variant/webhooks.rb +1 -1
- data/app/models/spree/variant.rb +211 -62
- data/app/models/spree/wished_item.rb +1 -1
- data/app/models/spree/wishlist.rb +1 -1
- data/app/models/spree/zone.rb +1 -1
- data/app/models/spree/zone_member.rb +1 -1
- data/app/presenters/spree/csv/order_line_item_presenter.rb +145 -0
- data/app/presenters/spree/csv/product_presenter.rb +63 -0
- data/app/presenters/spree/csv/product_variant_presenter.rb +130 -0
- data/app/services/spree/addresses/create.rb +31 -6
- data/app/services/spree/addresses/helper.rb +7 -4
- data/app/services/spree/addresses/update.rb +79 -12
- data/app/services/spree/cart/remove_out_of_stock_items.rb +56 -0
- data/app/services/spree/checkout/add_store_credit.rb +6 -1
- data/app/services/spree/checkout/advance.rb +44 -4
- data/app/services/spree/checkout/select_shipping_method.rb +2 -0
- data/app/services/spree/coupon_codes/bulk_generate.rb +37 -0
- data/app/services/spree/coupon_codes/coupon_codes_handler.rb +34 -0
- data/app/services/spree/orders/create_user_account.rb +70 -0
- data/app/services/spree/orders/update_contact_information.rb +19 -0
- data/app/services/spree/products/auto_match_taxons.rb +64 -0
- data/app/services/spree/products/duplicator.rb +121 -0
- data/app/services/spree/products/prepare_nested_attributes.rb +169 -0
- data/app/services/spree/seeds/admin_user.rb +22 -0
- data/app/services/spree/seeds/all.rb +1 -0
- data/app/services/spree/seeds/stores.rb +5 -10
- data/app/services/spree/seeds/zones.rb +2 -0
- data/app/services/spree/tags/bulk_add.rb +38 -0
- data/app/services/spree/tags/bulk_remove.rb +24 -0
- data/app/services/spree/taxons/add_products.rb +39 -0
- data/app/services/spree/taxons/regenerate_products.rb +53 -0
- data/app/services/spree/taxons/remove_products.rb +53 -0
- data/app/services/spree/taxons/touch_featured_sections.rb +21 -0
- data/app/services/spree/themes/duplicator.rb +40 -0
- data/app/services/spree/variants/remove_line_items.rb +1 -1
- data/app/sorters/spree/classifications/sort.rb +68 -0
- data/app/sorters/spree/products/sort.rb +2 -3
- data/app/views/action_text/video_embeds/_thumbnail.html.erb +1 -0
- data/app/views/action_text/video_embeds/_video_embed.html.erb +3 -0
- data/app/views/active_storage/blobs/_blob.html.erb +20 -0
- data/app/views/layouts/action_text/contents/_content.html.erb +3 -0
- data/app/views/layouts/spree/base_mailer.html.erb +46 -0
- data/app/views/spree/addresses/_form.html.erb +58 -0
- data/app/views/spree/addresses/_suggestions_box.erb +14 -0
- data/app/views/spree/export_mailer/export_done.html.erb +15 -0
- data/app/views/spree/report_mailer/report_done.html.erb +15 -0
- data/app/views/spree/shared/_base_mailer_footer.html.erb +12 -0
- data/app/views/spree/shared/_base_mailer_header.html.erb +6 -0
- data/app/views/spree/shared/_base_mailer_stylesheets.html.erb +456 -0
- data/app/views/spree/shared/_google_places_api_loader.html.erb +7 -0
- data/app/views/spree/shared/_mailer_line_item.html.erb +12 -0
- data/app/views/spree/shared/_mailer_logo.html.erb +41 -0
- data/app/views/spree/shared/_payment.html.erb +41 -0
- data/app/views/spree/test_mailer/test_email.html.erb +40 -0
- data/app/views/spree/test_mailer/test_email.text.erb +4 -0
- data/config/i18n-tasks.yml +178 -0
- data/config/importmap.rb +14 -0
- data/config/initializers/acts_as_taggable_on.rb +2 -0
- data/config/initializers/inflections.rb +2 -0
- data/config/initializers/mobility.rb +4 -0
- data/config/initializers/oembed.rb +1 -0
- data/config/locales/en.yml +823 -413
- data/config/routes.rb +6 -5
- data/db/migrate/20210914000000_spree_four_three.rb +0 -79
- data/db/migrate/20240913143518_change_default_value_of_spree_option_types_filterable.rb +5 -0
- data/db/migrate/20240914153106_add_display_on_to_spree_properties.rb +5 -0
- data/db/migrate/20240915144935_add_position_to_spree_properties.rb +6 -0
- data/db/migrate/20241004181911_create_spree_coupon_codes.rb +15 -0
- data/db/migrate/20241005093437_add_multi_code_to_spree_promotions.rb +14 -0
- data/db/migrate/20241014140140_translate_rich_texts.rb +17 -0
- data/db/migrate/20241030134309_create_spree_exports.rb +23 -0
- data/db/migrate/20241104083457_migrate_spree_promotion_rules_option_value_eligible_values.rb +16 -0
- data/db/migrate/20241123110646_add_estimated_transit_fields_to_spree_shipping_methods.rb +6 -0
- data/db/migrate/20241127193411_create_spree_taxon_rules.rb +13 -0
- data/db/migrate/20241127223627_add_rules_match_policy_and_sort_order_to_spree_taxons.rb +6 -0
- data/db/migrate/20241128103947_add_automatic_to_spree_taxons.rb +5 -0
- data/db/migrate/20241218183905_add_gateway_customer_profile_id_to_spree_payment_sources.rb +5 -0
- data/db/migrate/20250107220432_add_accept_marketing_and_signup_for_an_account_to_spree_orders.rb +6 -0
- data/db/migrate/20250110171203_add_quick_checkout_to_spree_addresses.rb +6 -0
- data/db/migrate/20250113180019_add_preferences_to_spree_stores.rb +5 -0
- data/db/migrate/20250114193857_add_metadata_to_spree_stores.rb +13 -0
- data/db/migrate/20250119165904_create_spree_custom_domains.rb +22 -0
- data/db/migrate/20250120094216_create_page_builder_models.rb +78 -0
- data/db/migrate/20250120152208_add_default_locale_to_action_text_rich_texts.rb +7 -0
- data/db/migrate/20250121160028_create_spree_posts_and_spree_post_categories.rb +33 -0
- data/db/migrate/20250122113708_add_first_and_last_name_to_spree_admin_class.rb +9 -0
- data/db/migrate/20250123135358_add_custom_code_fields_to_spree_store.rb +7 -0
- data/db/migrate/20250127083740_add_kind_to_spree_properties.rb +5 -0
- data/db/migrate/20250127112758_add_latitude_and_longitude_to_spree_addresses.rb +6 -0
- data/db/migrate/20250127151258_add_phone_to_spree_users.rb +5 -0
- data/db/migrate/20250204162453_create_spree_reports.rb +13 -0
- data/db/migrate/20250207084000_add_accepts_email_marketing_field_to_spree_users_table.rb +6 -0
- data/db/migrate/20250217171018_create_action_text_video_embeds.rb +11 -0
- data/db/migrate/20250304115943_add_performed_by_to_spree_reimbursements.rb +5 -0
- data/db/migrate/20250305121352_remove_page_builder_indices.rb +11 -0
- data/db/migrate/20250305121657_remove_spree_posts_indices.rb +7 -0
- data/db/migrate/20250311105934_create_spree_gateway_customers.rb +18 -0
- data/db/migrate/20250312202705_add_unique_index_on_promotion_rules_associations.rb +75 -0
- data/db/migrate/20250313104226_add_user_type_to_spree_role_users.rb +18 -0
- data/db/migrate/20250313175830_add_unique_index_on_promotion_action_line_items.rb +29 -0
- data/db/migrate/20250314144210_create_spree_taggings_and_spree_tags.rb +38 -0
- data/lib/generators/spree/authentication/custom/custom_generator.rb +49 -0
- data/lib/generators/spree/authentication/custom/templates/authentication_helpers.rb.tt +50 -0
- data/lib/generators/spree/authentication/devise/devise_generator.rb +57 -0
- data/lib/generators/spree/authentication/devise/templates/authentication_helpers.rb.tt +50 -0
- data/lib/generators/spree/dummy/dummy_generator.rb +1 -1
- data/lib/generators/spree/dummy/templates/rails/database.yml +1 -0
- data/lib/generators/spree/dummy/templates/rails/test.rb +1 -1
- data/lib/generators/spree/install/install_generator.rb +21 -36
- data/lib/generators/spree/install/templates/config/initializers/spree.rb +40 -6
- data/lib/normalize_string.rb +18 -0
- data/lib/spree/analytics.rb +11 -0
- data/lib/spree/core/components.rb +1 -1
- data/lib/spree/core/configuration.rb +14 -11
- data/lib/spree/core/controller_helpers/auth.rb +0 -26
- data/lib/spree/core/controller_helpers/order.rb +77 -12
- data/lib/spree/core/controller_helpers/search.rb +2 -0
- data/lib/spree/core/controller_helpers/store.rb +0 -5
- data/lib/spree/core/controller_helpers/strong_parameters.rb +4 -0
- data/lib/spree/core/controller_helpers/turbo.rb +19 -0
- data/lib/spree/core/dependencies.rb +3 -5
- data/lib/spree/core/engine.rb +158 -7
- data/lib/spree/core/preferences/configuration.rb +2 -44
- data/lib/spree/core/preferences/preferable.rb +9 -0
- data/lib/spree/core/preferences/preferable_class_methods.rb +11 -1
- data/lib/spree/core/preferences/runtime_configuration.rb +13 -2
- data/lib/spree/core/search/base.rb +2 -0
- data/lib/spree/core/version.rb +1 -1
- data/lib/spree/core.rb +61 -11
- data/lib/spree/i18n.rb +5 -8
- data/lib/spree/migrations.rb +1 -1
- data/lib/spree/permitted_attributes.rb +24 -15
- data/lib/spree/testing_support/authorization_helpers.rb +9 -6
- data/lib/spree/testing_support/capybara_config.rb +11 -0
- data/lib/spree/testing_support/common_rake.rb +20 -12
- data/lib/spree/testing_support/factories/calculator_factory.rb +16 -0
- data/lib/spree/testing_support/factories/country_factory.rb +8 -0
- data/lib/spree/testing_support/factories/coupon_code_factory.rb +7 -0
- data/lib/spree/testing_support/factories/custom_domain_factory.rb +7 -0
- data/lib/spree/testing_support/factories/export_factory.rb +32 -0
- data/lib/spree/testing_support/factories/gateway_customer_factory.rb +7 -0
- data/lib/spree/testing_support/factories/order_factory.rb +14 -3
- data/lib/spree/testing_support/factories/page_block_factory.rb +21 -0
- data/lib/spree/testing_support/factories/page_factory.rb +33 -0
- data/lib/spree/testing_support/factories/page_link_factory.rb +7 -0
- data/lib/spree/testing_support/factories/page_section_factory.rb +24 -0
- data/lib/spree/testing_support/factories/payment_factory.rb +2 -2
- data/lib/spree/testing_support/factories/post_category_factory.rb +7 -0
- data/lib/spree/testing_support/factories/post_factory.rb +10 -0
- data/lib/spree/testing_support/factories/product_factory.rb +3 -8
- data/lib/spree/testing_support/factories/product_property_factory.rb +1 -1
- data/lib/spree/testing_support/factories/promotion_action_factory.rb +5 -0
- data/lib/spree/testing_support/factories/promotion_factory.rb +7 -9
- data/lib/spree/testing_support/factories/promotion_rule_factory.rb +12 -0
- data/lib/spree/testing_support/factories/property_factory.rb +1 -1
- data/lib/spree/testing_support/factories/report_factory.rb +33 -0
- data/lib/spree/testing_support/factories/role_user_factory.rb +7 -0
- data/lib/spree/testing_support/factories/shipping_method_factory.rb +9 -0
- data/lib/spree/testing_support/factories/stock_transfer_factory.rb +5 -2
- data/lib/spree/testing_support/factories/store_factory.rb +6 -9
- data/lib/spree/testing_support/factories/taxon_factory.rb +13 -0
- data/lib/spree/testing_support/factories/taxon_rule_factory.rb +38 -0
- data/lib/spree/testing_support/factories/theme_factory.rb +14 -0
- data/lib/spree/testing_support/factories/user_factory.rb +13 -11
- data/lib/spree/testing_support/factories/variant_factory.rb +23 -5
- data/lib/spree/testing_support/jobs.rb +1 -7
- data/lib/spree/testing_support/metadata.rb +13 -1
- data/lib/spree/testing_support/order_walkthrough.rb +1 -7
- data/lib/spree/testing_support/preferences.rb +1 -4
- data/lib/spree/testing_support/store.rb +26 -0
- data/lib/spree_core.rb +1 -0
- data/vendor/javascript/@rails--request.js.js +4 -0
- data/vendor/javascript/@stimulus-components--auto-submit.js +4 -0
- data/vendor/javascript/stimulus-reveal-controller.js +4 -0
- data/vendor/javascript/tailwindcss-stimulus-components.js +4 -0
- metadata +340 -197
- data/Gemfile +0 -3
- data/app/finders/spree/cms_pages/find.rb +0 -41
- data/app/finders/spree/menus/find.rb +0 -11
- data/app/models/concerns/spree/display_link.rb +0 -30
- data/app/models/spree/cms/pages/feature_page.rb +0 -7
- data/app/models/spree/cms/pages/homepage.rb +0 -20
- data/app/models/spree/cms/pages/standard_page.rb +0 -4
- data/app/models/spree/cms/sections/featured_article.rb +0 -22
- data/app/models/spree/cms/sections/hero_image.rb +0 -38
- data/app/models/spree/cms/sections/image_gallery.rb +0 -97
- data/app/models/spree/cms/sections/product_carousel.rb +0 -14
- data/app/models/spree/cms/sections/rich_text_content.rb +0 -13
- data/app/models/spree/cms/sections/side_by_side_images.rb +0 -68
- data/app/models/spree/cms_page.rb +0 -70
- data/app/models/spree/cms_section.rb +0 -69
- data/app/models/spree/cms_section_image.rb +0 -9
- data/app/models/spree/cms_section_image_one.rb +0 -4
- data/app/models/spree/cms_section_image_three.rb +0 -4
- data/app/models/spree/cms_section_image_two.rb +0 -4
- data/app/models/spree/icon.rb +0 -7
- data/app/models/spree/linkable/homepage.rb +0 -3
- data/app/models/spree/linkable/uri.rb +0 -3
- data/app/models/spree/menu.rb +0 -66
- data/app/models/spree/menu_item.rb +0 -72
- data/brakeman.ignore +0 -328
- data/db/migrate/20211201202851_update_linkable_resource_types.rb +0 -10
- data/db/migrate/20220329113557_fix_cms_pages_unique_indexes.rb +0 -8
- data/lib/generators/spree/custom_authentication/custom_authentication_generator.rb +0 -23
- data/lib/generators/spree/custom_authentication/templates/authentication_helpers.rb.tt +0 -23
- data/lib/generators/spree/custom_user/custom_user_generator.rb +0 -46
- data/lib/generators/spree/custom_user/templates/current_user_helpers.rb.tt +0 -15
- data/lib/generators/spree/custom_user/templates/initializer.rb.tt +0 -1
- data/lib/generators/spree/custom_user/templates/migration.rb.tt +0 -7
- data/lib/generators/spree/dummy/templates/package.json +0 -12
- data/lib/spree/core/product_duplicator.rb +0 -85
- data/lib/spree/core/runtime_configuration.rb +0 -10
- data/lib/spree/testing_support/factories/cms_page_factory.rb +0 -20
- data/lib/spree/testing_support/factories/cms_section_factory.rb +0 -31
- data/lib/spree/testing_support/factories/icon_factory.rb +0 -9
- data/lib/spree/testing_support/factories/menu_factory.rb +0 -16
- data/lib/spree/testing_support/factories/menu_item_factory.rb +0 -10
- data/script/rails +0 -9
- data/spec/fixtures/favicon.ico +0 -0
- data/spec/fixtures/files/icon_256x256.gif +0 -0
- data/spec/fixtures/files/icon_256x256.png +0 -0
- data/spec/fixtures/files/icon_512x512.png +0 -0
- data/spec/fixtures/files/img_256x128.png +0 -0
- data/spec/fixtures/text-file.txt +0 -1
- data/spec/fixtures/thinking-cat.jpg +0 -0
- data/spree_core.gemspec +0 -62
data/app/models/spree/product.rb
CHANGED
@@ -19,7 +19,7 @@
|
|
19
19
|
#
|
20
20
|
|
21
21
|
module Spree
|
22
|
-
class Product < Spree
|
22
|
+
class Product < Spree.base_class
|
23
23
|
extend FriendlyId
|
24
24
|
include Spree::ProductScopes
|
25
25
|
include Spree::MultiStoreResource
|
@@ -34,12 +34,26 @@ module Spree
|
|
34
34
|
|
35
35
|
MEMOIZED_METHODS = %w[total_on_hand taxonomy_ids taxon_and_ancestors category
|
36
36
|
default_variant_id tax_category default_variant
|
37
|
-
|
37
|
+
default_image secondary_image
|
38
|
+
purchasable? in_stock? backorderable? has_variants?]
|
39
|
+
|
40
|
+
STATUS_TO_WEBHOOK_EVENT = {
|
41
|
+
'active' => 'activated',
|
42
|
+
'draft' => 'drafted',
|
43
|
+
'archived' => 'archived'
|
44
|
+
}.freeze
|
38
45
|
|
39
46
|
TRANSLATABLE_FIELDS = %i[name description slug meta_description meta_keywords meta_title].freeze
|
40
47
|
translates(*TRANSLATABLE_FIELDS, column_fallback: !Spree.always_use_translations?)
|
41
48
|
|
42
49
|
self::Translation.class_eval do
|
50
|
+
if defined?(PgSearch)
|
51
|
+
include PgSearch::Model
|
52
|
+
|
53
|
+
pg_search_scope :search_by_name, against: { name: 'A', meta_title: 'B' },
|
54
|
+
using: { tsearch: { prefix: true, any_word: true } }
|
55
|
+
end
|
56
|
+
|
43
57
|
before_save :set_slug
|
44
58
|
acts_as_paranoid
|
45
59
|
# deleted translation values also need to be accessible for index views listing deleted resources
|
@@ -61,23 +75,23 @@ module Spree
|
|
61
75
|
end
|
62
76
|
end
|
63
77
|
|
64
|
-
friendly_id :slug_candidates, use: [:history, :mobility]
|
78
|
+
friendly_id :slug_candidates, use: [:history, :scoped, :mobility], scope: spree_base_uniqueness_scope
|
65
79
|
acts_as_paranoid
|
66
80
|
auto_strip_attributes :name
|
81
|
+
acts_as_taggable_on :tags, :labels
|
67
82
|
|
68
83
|
# we need to have this callback before any dependent: :destroy associations
|
69
84
|
# https://github.com/rails/rails/issues/3458
|
70
85
|
before_destroy :ensure_not_in_complete_orders
|
71
86
|
|
72
|
-
has_many :product_option_types, dependent: :destroy, inverse_of: :product
|
87
|
+
has_many :product_option_types, -> { order(:position) }, dependent: :destroy, inverse_of: :product
|
73
88
|
has_many :option_types, through: :product_option_types
|
74
89
|
has_many :product_properties, dependent: :destroy, inverse_of: :product
|
75
90
|
has_many :properties, through: :product_properties
|
76
91
|
|
77
|
-
has_many :
|
78
|
-
|
79
|
-
has_many :classifications, dependent: :delete_all, inverse_of: :product
|
92
|
+
has_many :classifications, -> { order(created_at: :asc) }, dependent: :delete_all, inverse_of: :product
|
80
93
|
has_many :taxons, through: :classifications, before_remove: :remove_taxon
|
94
|
+
has_many :taxonomies, through: :taxons
|
81
95
|
|
82
96
|
has_many :product_promotion_rules, class_name: 'Spree::ProductPromotionRule'
|
83
97
|
has_many :promotion_rules, through: :product_promotion_rules, class_name: 'Spree::PromotionRule'
|
@@ -117,26 +131,37 @@ module Spree
|
|
117
131
|
has_many :variant_images, -> { order(:position) }, source: :images, through: :variants_including_master
|
118
132
|
has_many :variant_images_without_master, -> { order(:position) }, source: :images, through: :variants
|
119
133
|
|
134
|
+
has_many :option_value_variants, class_name: 'Spree::OptionValueVariant', through: :variants
|
135
|
+
has_many :option_values, class_name: 'Spree::OptionValue', through: :variants
|
136
|
+
|
137
|
+
has_many :prices_including_master, -> { non_zero }, through: :variants_including_master, source: :prices
|
138
|
+
|
120
139
|
has_many :store_products, class_name: 'Spree::StoreProduct'
|
121
140
|
has_many :stores, through: :store_products, class_name: 'Spree::Store'
|
122
141
|
has_many :digitals, through: :variants_including_master
|
123
142
|
|
143
|
+
has_many :page_links, as: :linkable, class_name: 'Spree::PageLink', dependent: :destroy
|
144
|
+
|
145
|
+
after_initialize :ensure_master
|
146
|
+
after_initialize :assign_default_tax_category
|
147
|
+
|
148
|
+
before_validation :downcase_slug
|
149
|
+
before_validation :normalize_slug, on: :update
|
150
|
+
before_validation :validate_master
|
151
|
+
before_validation :ensure_default_shipping_category
|
152
|
+
|
124
153
|
after_create :add_associations_from_prototype
|
125
154
|
after_create :build_variants_from_option_values_hash, if: :option_values_hash
|
126
155
|
|
127
156
|
after_destroy :punch_slug
|
128
157
|
after_restore :update_slug_history
|
129
158
|
|
130
|
-
after_initialize :ensure_master
|
131
|
-
|
132
159
|
after_save :save_master
|
133
160
|
after_save :run_touch_callbacks, if: :anything_changed?
|
134
161
|
after_save :reset_nested_changes
|
135
162
|
after_touch :touch_taxons
|
136
163
|
|
137
|
-
|
138
|
-
before_validation :normalize_slug, on: :update
|
139
|
-
before_validation :validate_master
|
164
|
+
after_commit :auto_match_taxons, if: :eligible_for_taxon_matching?
|
140
165
|
|
141
166
|
with_options length: { maximum: 255 }, allow_blank: true do
|
142
167
|
validates :meta_keywords
|
@@ -152,26 +177,113 @@ module Spree
|
|
152
177
|
validate :discontinue_on_must_be_later_than_make_active_at, if: -> { make_active_at && discontinue_on }
|
153
178
|
|
154
179
|
scope :for_store, ->(store) { joins(:store_products).where(StoreProduct.table_name => { store_id: store.id }) }
|
180
|
+
scope :draft, -> { where(status: 'draft') }
|
181
|
+
scope :archived, -> { where(status: 'archived') }
|
182
|
+
scope :not_archived, -> { where.not(status: 'archived') }
|
183
|
+
scope :on_sale, lambda { |currency = nil|
|
184
|
+
currency ||= Spree::Store.default.default_currency
|
185
|
+
joins(:prices_including_master).with_currency(currency).
|
186
|
+
where.not(spree_prices: { compare_at_amount: [nil, 0] }).
|
187
|
+
where("#{Spree::Price.table_name}.compare_at_amount > #{Spree::Price.table_name}.amount")
|
188
|
+
}
|
189
|
+
|
190
|
+
if defined?(PgSearch)
|
191
|
+
scope :multi_search, lambda { |query, include_options = false|
|
192
|
+
return none if query.blank?
|
193
|
+
|
194
|
+
product_ids = if Spree.use_translations?
|
195
|
+
Spree::Product::Translation.search_by_name(query).pluck(:spree_product_id)
|
196
|
+
else
|
197
|
+
Spree::Product.search_by_name(query).ids
|
198
|
+
end
|
199
|
+
|
200
|
+
variant_product_ids = if include_options.present?
|
201
|
+
Spree::Variant.search_by_sku_or_options(query).pluck(:product_id)
|
202
|
+
else
|
203
|
+
Spree::Variant.search_by_sku(query).pluck(:product_id)
|
204
|
+
end
|
205
|
+
|
206
|
+
where(id: (product_ids + variant_product_ids).uniq.compact)
|
207
|
+
}
|
208
|
+
else
|
209
|
+
scope :multi_search, lambda { |query|
|
210
|
+
return none if query.blank?
|
211
|
+
|
212
|
+
product_ids = Spree::Variant.search_by_product_name_or_sku(query).pluck(:product_id)
|
213
|
+
where(id: product_ids.uniq.compact)
|
214
|
+
}
|
215
|
+
end
|
216
|
+
|
217
|
+
scope :archivable, -> { where(status: %w[active draft]) }
|
218
|
+
scope :by_source, ->(source) { send(source) }
|
219
|
+
scope :paused, -> { where(status: 'paused') }
|
220
|
+
scope :published, -> { where(status: 'active') }
|
221
|
+
scope :in_stock_items, -> { joins(:variants).merge(Spree::Variant.in_stock_or_backorderable) }
|
222
|
+
scope :out_of_stock_items, lambda {
|
223
|
+
joins(variants_including_master: :stock_items).
|
224
|
+
where(spree_variants: { track_inventory: true }).
|
225
|
+
where.not(id: Spree::Variant.where(track_inventory: false).pluck(:product_id).uniq).
|
226
|
+
where(spree_stock_items: { backorderable: false }).
|
227
|
+
group(:id).
|
228
|
+
having("SUM(#{Spree::StockItem.table_name}.count_on_hand) <= 0")
|
229
|
+
}
|
230
|
+
scope :out_of_stock, lambda {
|
231
|
+
joins(:stock_items).where("#{Spree::StockItem.table_name}.count_on_hand <= ? OR #{Spree::Variant.table_name}.track_inventory = ?", 0, false)
|
232
|
+
}
|
233
|
+
|
234
|
+
scope :by_best_selling, lambda { |order_direction = :desc|
|
235
|
+
left_joins(:orders).
|
236
|
+
select("#{Spree::Product.table_name}.*, COUNT(#{Spree::Order.table_name}.id) AS completed_orders_count, SUM(#{Spree::Order.table_name}.total) AS completed_orders_total").
|
237
|
+
where(Spree::Order.table_name => { id: nil }).
|
238
|
+
or(where.not(Spree::Order.table_name => { completed_at: nil })).
|
239
|
+
group("#{Spree::Product.table_name}.id").
|
240
|
+
order(completed_orders_count: order_direction, completed_orders_total: order_direction)
|
241
|
+
}
|
155
242
|
|
156
243
|
attr_accessor :option_values_hash
|
157
244
|
|
158
|
-
accepts_nested_attributes_for :product_properties, allow_destroy: true, reject_if:
|
245
|
+
accepts_nested_attributes_for :product_properties, allow_destroy: true, reject_if: lambda { |pp|
|
246
|
+
pp[:property_id].blank? || (pp[:id].blank? && pp[:value].blank?)
|
247
|
+
}
|
248
|
+
accepts_nested_attributes_for(
|
249
|
+
:variants,
|
250
|
+
allow_destroy: true,
|
251
|
+
reject_if: lambda do |v|
|
252
|
+
v[:option_value_variants_attributes].blank? && v[:stock_items_attributes].blank? && v[:prices_attributes].blank?
|
253
|
+
end
|
254
|
+
)
|
255
|
+
accepts_nested_attributes_for :master, reject_if: :all_blank
|
256
|
+
accepts_nested_attributes_for(
|
257
|
+
:product_option_types,
|
258
|
+
allow_destroy: true,
|
259
|
+
reject_if: ->(pot) { pot[:option_type_id].blank? || pot[:position].blank? }
|
260
|
+
)
|
159
261
|
|
160
262
|
alias options product_option_types
|
161
263
|
|
162
|
-
self.whitelisted_ransackable_associations = %w[taxons stores variants_including_master master variants]
|
163
264
|
self.whitelisted_ransackable_attributes = %w[description name slug discontinue_on status]
|
164
|
-
self.
|
265
|
+
self.whitelisted_ransackable_associations = %w[taxons stores variants_including_master master variants tags labels
|
266
|
+
shipping_category classifications option_types properties]
|
267
|
+
self.whitelisted_ransackable_scopes = %w[not_discontinued search_by_name in_taxon price_between
|
268
|
+
multi_search in_stock_items out_of_stock_items]
|
165
269
|
|
166
270
|
[
|
167
|
-
:sku, :barcode, :
|
168
|
-
:cost_currency, :price_in, :amount_in, :cost_price, :compare_at_price, :compare_at_amount_in
|
271
|
+
:sku, :barcode, :weight, :height, :width, :depth, :is_master, :dimensions_unit, :weight_unit
|
169
272
|
].each do |method_name|
|
170
273
|
delegate method_name, :"#{method_name}=", to: :find_or_build_master
|
171
274
|
end
|
172
275
|
|
276
|
+
[
|
277
|
+
:price, :price_in, :amount_in, :compare_at_price, :compare_at_amount_in,
|
278
|
+
:currency, :cost_currency, :cost_price, :track_inventory
|
279
|
+
].each do |method_name|
|
280
|
+
delegate method_name, :"#{method_name}=", to: :default_variant
|
281
|
+
end
|
282
|
+
|
173
283
|
delegate :display_amount, :display_price, :has_default_price?,
|
174
|
-
:display_compare_at_price, :images, to: :
|
284
|
+
:display_compare_at_price, :images, to: :default_variant
|
285
|
+
|
286
|
+
delegate :name, to: :brand, prefix: true, allow_nil: true
|
175
287
|
|
176
288
|
alias master_images images
|
177
289
|
|
@@ -192,14 +304,31 @@ module Spree
|
|
192
304
|
after_transition to: :draft, do: [:after_draft, :send_product_drafted_webhook]
|
193
305
|
end
|
194
306
|
|
307
|
+
def self.bulk_auto_match_taxons(store, product_ids)
|
308
|
+
return if store.taxons.automatic.none?
|
309
|
+
|
310
|
+
products_to_auto_match_ids = store.products.not_deleted.not_archived.where(id: product_ids).ids
|
311
|
+
|
312
|
+
# for ActiveJob 7.1+
|
313
|
+
if ActiveJob.respond_to?(:perform_all_later)
|
314
|
+
auto_match_taxons_jobs = products_to_auto_match_ids.map do |product_id|
|
315
|
+
Spree::Products::AutoMatchTaxonsJob.new(product_id)
|
316
|
+
end
|
317
|
+
|
318
|
+
ActiveJob.perform_all_later(auto_match_taxons_jobs)
|
319
|
+
else
|
320
|
+
products_to_auto_match_ids.each { |product_id| Spree::Products::AutoMatchTaxonsJob.perform_later(product_id) }
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
195
324
|
# Can't use short form block syntax due to https://github.com/Netflix/fast_jsonapi/issues/259
|
196
325
|
def purchasable?
|
197
|
-
default_variant.purchasable? || variants.any?
|
326
|
+
@purchasable ||= default_variant.purchasable? || variants.in_stock_or_backorderable.any?
|
198
327
|
end
|
199
328
|
|
200
329
|
# Can't use short form block syntax due to https://github.com/Netflix/fast_jsonapi/issues/259
|
201
330
|
def in_stock?
|
202
|
-
default_variant.in_stock? || variants.any?
|
331
|
+
@in_stock ||= default_variant.in_stock? || variants.in_stock.any?
|
203
332
|
end
|
204
333
|
|
205
334
|
# Can't use short form block syntax due to https://github.com/Netflix/fast_jsonapi/issues/259
|
@@ -207,13 +336,17 @@ module Spree
|
|
207
336
|
default_variant.backorderable? || variants.any?(&:backorderable?)
|
208
337
|
end
|
209
338
|
|
339
|
+
def on_sale?(currency)
|
340
|
+
prices_including_master.find_all { |p| p.currency == currency }.any?(&:discounted?)
|
341
|
+
end
|
342
|
+
|
210
343
|
def find_or_build_master
|
211
344
|
master || build_master
|
212
345
|
end
|
213
346
|
|
214
347
|
# the master variant is not a member of the variants array
|
215
348
|
def has_variants?
|
216
|
-
variants.any?
|
349
|
+
@has_variants ||= variants.loaded? ? variants.size.positive? : variants.any?
|
217
350
|
end
|
218
351
|
|
219
352
|
# Returns default Variant for Product
|
@@ -225,13 +358,11 @@ module Spree
|
|
225
358
|
#
|
226
359
|
# @return [Spree::Variant]
|
227
360
|
def default_variant
|
228
|
-
@default_variant ||=
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
end
|
234
|
-
end
|
361
|
+
@default_variant ||= if Spree::Config[:track_inventory_levels] && available_variant = variants.detect(&:purchasable?)
|
362
|
+
available_variant
|
363
|
+
else
|
364
|
+
has_variants? ? variants.first : find_or_build_master
|
365
|
+
end
|
235
366
|
end
|
236
367
|
|
237
368
|
# Returns default Variant ID for Product
|
@@ -240,19 +371,87 @@ module Spree
|
|
240
371
|
@default_variant_id ||= default_variant.id
|
241
372
|
end
|
242
373
|
|
374
|
+
# Returns default Image for Product
|
375
|
+
# @return [Spree::Image]
|
376
|
+
def default_image
|
377
|
+
@default_image ||= if images.size.positive?
|
378
|
+
images.first
|
379
|
+
elsif default_variant.images.size.positive?
|
380
|
+
default_variant.default_image
|
381
|
+
elsif variant_images.size.positive?
|
382
|
+
variant_images.first
|
383
|
+
end
|
384
|
+
end
|
385
|
+
alias featured_image default_image
|
386
|
+
|
387
|
+
# Returns secondary Image for Product
|
388
|
+
# @return [Spree::Image]
|
389
|
+
def secondary_image
|
390
|
+
@secondary_image ||= if images.size > 1
|
391
|
+
images.second
|
392
|
+
elsif images.size == 1 && default_variant.images.size.positive?
|
393
|
+
default_variant.images.first
|
394
|
+
elsif default_variant.images.size > 1
|
395
|
+
default_variant.secondary_image
|
396
|
+
elsif variant_images.size > 1
|
397
|
+
variant_images.second
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
# Returns the short description for the product
|
402
|
+
# @return [String]
|
403
|
+
def storefront_description
|
404
|
+
property('short_description') || description
|
405
|
+
end
|
406
|
+
|
407
|
+
# Returns tax category for Product
|
408
|
+
# @return [Spree::TaxCategory]
|
243
409
|
def tax_category
|
244
|
-
@tax_category ||= super || TaxCategory.
|
410
|
+
@tax_category ||= super || TaxCategory.default
|
245
411
|
end
|
246
412
|
|
247
413
|
# Adding properties and option types on creation based on a chosen prototype
|
248
414
|
attr_accessor :prototype_id
|
249
415
|
|
416
|
+
def first_or_default_variant(currency)
|
417
|
+
if !has_variants?
|
418
|
+
default_variant
|
419
|
+
elsif first_available_variant(currency).present?
|
420
|
+
first_available_variant(currency)
|
421
|
+
else
|
422
|
+
variants.first
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
def first_available_variant(currency)
|
427
|
+
variants.find { |v| v.purchasable? && v.price_in(currency).amount.present? }
|
428
|
+
end
|
429
|
+
|
430
|
+
def price_varies?(currency)
|
431
|
+
prices_including_master.find_all { |p| p.currency == currency && p.amount.present? }.map(&:amount).uniq.count > 1
|
432
|
+
end
|
433
|
+
|
434
|
+
def any_variant_available?(currency)
|
435
|
+
if has_variants?
|
436
|
+
first_available_variant(currency).present?
|
437
|
+
else
|
438
|
+
master.purchasable? && master.price_in(currency).amount.present?
|
439
|
+
end
|
440
|
+
end
|
441
|
+
|
442
|
+
# returns the lowest price for the product in the given currency
|
443
|
+
# prices_including_master are usually already loaded, so this should not trigger an extra query
|
444
|
+
def lowest_price(currency)
|
445
|
+
prices_including_master.find_all { |p| p.currency == currency }.min_by(&:amount)
|
446
|
+
end
|
447
|
+
|
250
448
|
# Ensures option_types and product_option_types exist for keys in option_values_hash
|
251
449
|
def ensure_option_types_exist_for_values_hash
|
252
450
|
return if option_values_hash.nil?
|
253
451
|
|
254
|
-
|
255
|
-
|
452
|
+
# we need to convert the keys to string to make it work with UUIDs
|
453
|
+
required_option_type_ids = option_values_hash.keys.map(&:to_s)
|
454
|
+
missing_option_type_ids = required_option_type_ids - option_type_ids.map(&:to_s)
|
256
455
|
missing_option_type_ids.each do |id|
|
257
456
|
product_option_types.create(option_type_id: id)
|
258
457
|
end
|
@@ -261,8 +460,7 @@ module Spree
|
|
261
460
|
# for adding products which are closely related to existing ones
|
262
461
|
# define "duplicate_extra" for site-specific actions, eg for additional fields
|
263
462
|
def duplicate
|
264
|
-
|
265
|
-
duplicator.duplicate
|
463
|
+
Products::Duplicator.call(product: self)
|
266
464
|
end
|
267
465
|
|
268
466
|
# use deleted? rather than checking the attribute directly. this
|
@@ -276,7 +474,7 @@ module Spree
|
|
276
474
|
# deleted products and products with status different than active
|
277
475
|
# are not available
|
278
476
|
def available?
|
279
|
-
active? && !deleted?
|
477
|
+
active? && !deleted? && (available_on.nil? || available_on <= Time.current)
|
280
478
|
end
|
281
479
|
|
282
480
|
def discontinue!
|
@@ -330,10 +528,15 @@ module Spree
|
|
330
528
|
end
|
331
529
|
|
332
530
|
def property(property_name)
|
333
|
-
product_properties.
|
531
|
+
if product_properties.loaded?
|
532
|
+
product_properties.detect { |property| property.property.name == property_name }.try(:value)
|
533
|
+
else
|
534
|
+
product_properties.joins(:property).find_by(spree_properties: { name: property_name }).try(:value)
|
535
|
+
end
|
334
536
|
end
|
335
537
|
|
336
538
|
def set_property(property_name, property_value, property_presentation = property_name)
|
539
|
+
property_name = property_name.to_s.parameterize
|
337
540
|
ApplicationRecord.transaction do
|
338
541
|
# Manual first_or_create to work around Mobility bug
|
339
542
|
property = if Property.where(name: property_name).exists?
|
@@ -348,7 +551,7 @@ module Spree
|
|
348
551
|
product_property = if ProductProperty.where(product: self, property: property).exists?
|
349
552
|
ProductProperty.where(product: self, property: property).first
|
350
553
|
else
|
351
|
-
ProductProperty.
|
554
|
+
ProductProperty.new(product: self, property: property)
|
352
555
|
end
|
353
556
|
|
354
557
|
product_property.value = property_value
|
@@ -356,14 +559,16 @@ module Spree
|
|
356
559
|
end
|
357
560
|
end
|
358
561
|
|
562
|
+
def remove_property(property_name)
|
563
|
+
product_properties.joins(:property).find_by(spree_properties: { name: property_name.parameterize })&.destroy
|
564
|
+
end
|
565
|
+
|
359
566
|
def total_on_hand
|
360
|
-
@total_on_hand ||=
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
end
|
366
|
-
end
|
567
|
+
@total_on_hand ||= if any_variants_not_track_inventory?
|
568
|
+
BigDecimal::INFINITY
|
569
|
+
else
|
570
|
+
stock_items.loaded? ? stock_items.sum(&:count_on_hand) : stock_items.sum(:count_on_hand)
|
571
|
+
end
|
367
572
|
end
|
368
573
|
|
369
574
|
# Master variant may be deleted (i.e. when the product is deleted)
|
@@ -375,7 +580,7 @@ module Spree
|
|
375
580
|
|
376
581
|
def brand
|
377
582
|
@brand ||= if Spree.use_translations?
|
378
|
-
|
583
|
+
taxons.joins(:taxonomy).
|
379
584
|
join_translation_table(Taxonomy).
|
380
585
|
find_by(Taxonomy.translation_table_alias => { name: Spree.t(:taxonomy_brands_name) })
|
381
586
|
else
|
@@ -394,6 +599,10 @@ module Spree
|
|
394
599
|
end
|
395
600
|
end
|
396
601
|
|
602
|
+
def main_taxon
|
603
|
+
category || taxons.first
|
604
|
+
end
|
605
|
+
|
397
606
|
def taxons_for_store(store)
|
398
607
|
Rails.cache.fetch("#{cache_key_with_version}/taxons-per-store/#{store.id}") do
|
399
608
|
taxons.for_store(store)
|
@@ -401,15 +610,60 @@ module Spree
|
|
401
610
|
end
|
402
611
|
|
403
612
|
def any_variant_in_stock_or_backorderable?
|
404
|
-
if
|
613
|
+
if has_variants?
|
405
614
|
variants_including_master.in_stock_or_backorderable.exists?
|
406
615
|
else
|
407
616
|
master.in_stock_or_backorderable?
|
408
617
|
end
|
409
618
|
end
|
410
619
|
|
620
|
+
# Check if the product is digital by checking if any of its shipping methods are digital delivery
|
621
|
+
# This is used to determine if the product is digital and should have a digital delivery price
|
622
|
+
# instead of a physical shipping price
|
623
|
+
#
|
624
|
+
# @return [Boolean]
|
411
625
|
def digital?
|
412
|
-
shipping_category&.
|
626
|
+
shipping_category&.shipping_methods&.any? { |method| method.calculator.is_a?(Spree::Calculator::Shipping::DigitalDelivery) }
|
627
|
+
end
|
628
|
+
|
629
|
+
def auto_match_taxons
|
630
|
+
return if deleted?
|
631
|
+
return if archived?
|
632
|
+
|
633
|
+
store = stores.find_by(default: true) || stores.first
|
634
|
+
return if store.nil? || store.taxons.automatic.none?
|
635
|
+
|
636
|
+
Spree::Products::AutoMatchTaxonsJob.perform_later(id)
|
637
|
+
end
|
638
|
+
|
639
|
+
def to_csv(store = nil)
|
640
|
+
store ||= stores.default || stores.first
|
641
|
+
properties_for_csv ||= Spree::Property.order(:position).flat_map do |property|
|
642
|
+
[
|
643
|
+
property.name,
|
644
|
+
product_properties.find { |pp| pp.property_id == property.id }&.value
|
645
|
+
]
|
646
|
+
end
|
647
|
+
taxons_for_csv ||= taxons.manual.reorder(depth: :desc).first(3).pluck(:pretty_name)
|
648
|
+
taxons_for_csv.fill(nil, taxons_for_csv.size...3)
|
649
|
+
|
650
|
+
csv_lines = []
|
651
|
+
|
652
|
+
if has_variants?
|
653
|
+
variants_including_master.each_with_index do |variant, index|
|
654
|
+
csv_lines << Spree::CSV::ProductVariantPresenter.new(self, variant, index, properties_for_csv, taxons_for_csv, store).call
|
655
|
+
end
|
656
|
+
else
|
657
|
+
csv_lines << Spree::CSV::ProductVariantPresenter.new(self, master, 0, properties_for_csv, taxons_for_csv, store).call
|
658
|
+
end
|
659
|
+
|
660
|
+
csv_lines
|
661
|
+
end
|
662
|
+
|
663
|
+
def page_builder_url
|
664
|
+
return unless Spree::Core::Engine.routes.url_helpers.respond_to?(:product_path)
|
665
|
+
|
666
|
+
Spree::Core::Engine.routes.url_helpers.product_path(self)
|
413
667
|
end
|
414
668
|
|
415
669
|
private
|
@@ -450,6 +704,8 @@ module Spree
|
|
450
704
|
end
|
451
705
|
|
452
706
|
def default_variant_cache_key
|
707
|
+
Spree::Deprecation.warn('Spree::Product#default_variant_cache_key is deprecated and will be removed in Spree 6. Please remove any occurrences of it.')
|
708
|
+
|
453
709
|
"spree/default-variant/#{cache_key_with_version}/#{Spree::Config[:track_inventory_levels]}"
|
454
710
|
end
|
455
711
|
|
@@ -459,6 +715,10 @@ module Spree
|
|
459
715
|
self.master ||= build_master
|
460
716
|
end
|
461
717
|
|
718
|
+
def assign_default_tax_category
|
719
|
+
self.tax_category = Spree::TaxCategory.default if new_record?
|
720
|
+
end
|
721
|
+
|
462
722
|
def normalize_slug
|
463
723
|
self.slug = normalize_friendly_id(slug)
|
464
724
|
end
|
@@ -524,6 +784,15 @@ module Spree
|
|
524
784
|
end
|
525
785
|
end
|
526
786
|
|
787
|
+
def ensure_default_shipping_category
|
788
|
+
return if shipping_category.present?
|
789
|
+
|
790
|
+
if new_record?
|
791
|
+
name = I18n.t('spree.seed.shipping.categories.default')
|
792
|
+
self.shipping_category = Spree::ShippingCategory.find_or_create_by!(name: name)
|
793
|
+
end
|
794
|
+
end
|
795
|
+
|
527
796
|
# Try building a slug based on the following fields in increasing order of specificity.
|
528
797
|
def slug_candidates
|
529
798
|
[
|
@@ -540,15 +809,13 @@ module Spree
|
|
540
809
|
@taxon_and_ancestors ||= taxons.map(&:self_and_ancestors).flatten.uniq
|
541
810
|
end
|
542
811
|
|
543
|
-
# Get the taxonomy ids of all taxons assigned to this product and their ancestors.
|
544
|
-
def taxonomy_ids
|
545
|
-
@taxonomy_ids ||= taxon_and_ancestors.map(&:taxonomy_id).flatten.uniq
|
546
|
-
end
|
547
|
-
|
548
812
|
# Iterate through this products taxons and taxonomies and touch their timestamps in a batch
|
549
813
|
def touch_taxons
|
550
|
-
|
551
|
-
|
814
|
+
if taxons.any?
|
815
|
+
Spree::Products::TouchTaxonsJob.
|
816
|
+
set(wait: 5.seconds).
|
817
|
+
perform_later(taxon_and_ancestors.map(&:id), taxonomy_ids.uniq)
|
818
|
+
end
|
552
819
|
end
|
553
820
|
|
554
821
|
def ensure_not_in_complete_orders
|
@@ -560,7 +827,7 @@ module Spree
|
|
560
827
|
|
561
828
|
def remove_taxon(taxon)
|
562
829
|
removed_classifications = classifications.where(taxon: taxon)
|
563
|
-
removed_classifications.each
|
830
|
+
removed_classifications.each(&:remove_from_list)
|
564
831
|
end
|
565
832
|
|
566
833
|
def discontinue_on_must_be_later_than_make_active_at
|
@@ -577,6 +844,10 @@ module Spree
|
|
577
844
|
true
|
578
845
|
end
|
579
846
|
|
847
|
+
def eligible_for_taxon_matching?
|
848
|
+
previously_new_record? || tag_list_previously_changed? || available_on_previously_changed?
|
849
|
+
end
|
850
|
+
|
580
851
|
def downcase_slug
|
581
852
|
slug&.downcase!
|
582
853
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module Spree
|
2
|
-
class ProductProperty < Spree
|
2
|
+
class ProductProperty < Spree.base_class
|
3
3
|
include Spree::FilterParam
|
4
4
|
include Spree::TranslatableResource
|
5
5
|
|
@@ -32,6 +32,9 @@ module Spree
|
|
32
32
|
|
33
33
|
scope :filterable, -> { joins(:property).where(Property.table_name => { filterable: true }) }
|
34
34
|
scope :for_products, ->(products) { joins(:product).merge(products) }
|
35
|
+
scope :sort_by_property_position, -> {
|
36
|
+
joins(:property).order("spree_properties.position ASC")
|
37
|
+
}
|
35
38
|
|
36
39
|
self.whitelisted_ransackable_attributes = ['value', 'filter_param']
|
37
40
|
self.whitelisted_ransackable_associations = ['property']
|