diff --git a/api/lib/spree/api_configuration.rb b/api/lib/spree/api_configuration.rb index 2dce13e62d8..d3cfba0a6fb 100644 --- a/api/lib/spree/api_configuration.rb +++ b/api/lib/spree/api_configuration.rb @@ -7,7 +7,7 @@ class ApiConfiguration < Preferences::Configuration preference :product_attributes, :array, default: [ :id, :name, :description, :available_on, :slug, :meta_description, :meta_keywords, :shipping_category_id, - :taxon_ids, :total_on_hand, :meta_title + :taxon_ids, :total_on_hand, :meta_title, :primary_breadcrumb_taxon_id ] preference :product_property_attributes, :array, default: [:id, :product_id, :property_id, :value, :property_name] diff --git a/api/spec/requests/spree/api/products_spec.rb b/api/spec/requests/spree/api/products_spec.rb index 6554f419f1f..5e63321004b 100644 --- a/api/spec/requests/spree/api/products_spec.rb +++ b/api/spec/requests/spree/api/products_spec.rb @@ -316,6 +316,13 @@ module Spree::Api expect(json_response["taxon_ids"]).to eq([taxon_1.id]) end + it "puts primary breadcrumb taxon for the product" do + product_data[:primary_breadcrumb_taxon_id] = taxon_1.id.to_s + post spree.api_products_path, params: { product: product_data } + + expect(json_response["primary_breadcrumb_taxon_id"]).to eq(taxon_1.id) + end + # Regression test for https://github.com/spree/spree/issues/4123 it "puts the created product in the given taxons" do product_data[:taxon_ids] = [taxon_1.id, taxon_2.id].join(',') @@ -404,6 +411,13 @@ module Spree::Api expect(json_response["taxon_ids"]).to eq([taxon_1.id]) end + it "puts primary breadcrumb taxon for the updated product" do + product.primary_breadcrumb_taxon_id = taxon_2.id + put spree.api_product_path(product), params: { product: { primary_breadcrumb_taxon_id: taxon_1.id } } + + expect(json_response["primary_breadcrumb_taxon_id"]).to eq(taxon_1.id) + end + # Regression test for https://github.com/spree/spree/issues/4123 it "puts the created product in the given taxons" do put spree.api_product_path(product), params: { product: { taxon_ids: [taxon_1.id, taxon_2.id].join(',') } } diff --git a/backend/app/assets/javascripts/spree/backend/taxon_autocomplete.js b/backend/app/assets/javascripts/spree/backend/taxon_autocomplete.js index e47e4594a22..4c9c2c5eab5 100644 --- a/backend/app/assets/javascripts/spree/backend/taxon_autocomplete.js +++ b/backend/app/assets/javascripts/spree/backend/taxon_autocomplete.js @@ -1,25 +1,49 @@ -$.fn.taxonAutocomplete = function () { +$.fn.autocompleteTaxon = function (options) { 'use strict'; + var defaultOptions = { + multiple: true, + placeholder: Spree.translations.taxon_placeholder + }; + + options = $.extend({}, defaultOptions, options); + this.select2({ - placeholder: Spree.translations.taxon_placeholder, - multiple: true, + placeholder: options.placeholder, + multiple: options.multiple, initSelection: function (element, callback) { - var ids = element.val(), - count = ids.split(",").length; - - Spree.ajax({ - type: "GET", - url: Spree.pathFor('api/taxons'), - data: { - ids: ids, - per_page: count, - without_children: true - }, - success: function (data) { - callback(data['taxons']); - } - }); + var ids = element.val(); + + if (options.multiple) { + var count = ids.split(",").length; + + Spree.ajax({ + type: "GET", + url: Spree.pathFor('api/taxons'), + data: { + ids: ids, + per_page: count, + without_children: true + }, + success: function (data) { + callback(data['taxons']); + } + }); + } else { + + Spree.ajax({ + type: "GET", + url: Spree.pathFor('api/taxons'), + data: { + ids: ids, + per_page: 1, + without_children: true + }, + success: function (data) { + callback(data['taxons'][0]); + } + }); + } }, ajax: { url: Spree.pathFor('api/taxons'), @@ -53,5 +77,11 @@ $.fn.taxonAutocomplete = function () { }; Spree.ready(function () { - $('#product_taxon_ids, .taxon_picker').taxonAutocomplete(); + $('#product_taxon_ids, .taxon_picker').autocompleteTaxon({ + multiple: true, + }); + + $('#product_primary_breadcrumb_taxon_id').autocompleteTaxon({ + multiple: false, + }); }); diff --git a/backend/app/views/spree/admin/products/_form.html.erb b/backend/app/views/spree/admin/products/_form.html.erb index a8b2ec4bbd1..b4b302635ea 100644 --- a/backend/app/views/spree/admin/products/_form.html.erb +++ b/backend/app/views/spree/admin/products/_form.html.erb @@ -36,6 +36,13 @@ <% end %> +
+ <%= f.field_container :primary_breadcrumb_taxon do %> + <%= f.label :primary_breadcrumb_taxon_id, "Primary Breadcrumb Taxon" %>
+ <%= f.hidden_field :primary_breadcrumb_taxon_id, value: @product.primary_breadcrumb_taxon_id %> + <% end %> +
+
<%= f.field_container :option_types do %> <%= f.label :option_type_ids, plural_resource_name(Spree::OptionType) %> diff --git a/backend/spec/features/admin/products/edit/taxons_spec.rb b/backend/spec/features/admin/products/edit/taxons_spec.rb index 42afd84a152..e9e0b0d8755 100644 --- a/backend/spec/features/admin/products/edit/taxons_spec.rb +++ b/backend/spec/features/admin/products/edit/taxons_spec.rb @@ -26,7 +26,10 @@ def assert_selected_taxons(taxons) visit spree.edit_admin_product_path(product) assert_selected_taxons([taxon_1]) - select2_search "Clothing", from: "Taxon" + within("[data-hook='admin_product_form_taxons']") do + select2_search "Clothing", from: "Taxon" + end + assert_selected_taxons([taxon_1, taxon_2]) # Without this line we have a flaky spec probably due to select2 not diff --git a/core/app/models/spree/product.rb b/core/app/models/spree/product.rb index 03193148ec0..5343b058e51 100644 --- a/core/app/models/spree/product.rb +++ b/core/app/models/spree/product.rb @@ -32,6 +32,7 @@ class Product < Spree::Base belongs_to :tax_category, class_name: 'Spree::TaxCategory', optional: true belongs_to :shipping_category, class_name: 'Spree::ShippingCategory', inverse_of: :products, optional: true + belongs_to :primary_breadcrumb_taxon, class_name: 'Spree::Taxon', optional: true has_one :master, -> { where(is_master: true).with_discarded }, diff --git a/core/db/migrate/20250207104016_add_primary_breadcrumb_taxon_to_products.rb b/core/db/migrate/20250207104016_add_primary_breadcrumb_taxon_to_products.rb new file mode 100644 index 00000000000..462d5fe1fc8 --- /dev/null +++ b/core/db/migrate/20250207104016_add_primary_breadcrumb_taxon_to_products.rb @@ -0,0 +1,7 @@ +class AddPrimaryBreadcrumbTaxonToProducts < ActiveRecord::Migration[7.0] + def change + add_column :spree_products, :primary_breadcrumb_taxon_id, :integer, null: true + add_foreign_key :spree_products, :spree_taxons, column: :primary_breadcrumb_taxon_id + add_index :spree_products, :primary_breadcrumb_taxon_id + end +end diff --git a/core/lib/spree/permitted_attributes.rb b/core/lib/spree/permitted_attributes.rb index a09a6eb53e6..4d1e7be073b 100644 --- a/core/lib/spree/permitted_attributes.rb +++ b/core/lib/spree/permitted_attributes.rb @@ -78,7 +78,7 @@ module PermittedAttributes :meta_keywords, :price, :sku, :deleted_at, :option_values_hash, :weight, :height, :width, :depth, :shipping_category_id, :tax_category_id, - :taxon_ids, :option_type_ids, :cost_currency, :cost_price + :taxon_ids, :option_type_ids, :cost_currency, :cost_price, :primary_breadcrumb_taxon_id ] @@property_attributes = [:name, :presentation] diff --git a/core/spec/models/spree/product_spec.rb b/core/spec/models/spree/product_spec.rb index c5d8132f636..8f4ecfc771e 100644 --- a/core/spec/models/spree/product_spec.rb +++ b/core/spec/models/spree/product_spec.rb @@ -452,6 +452,22 @@ class Extension < Spree::Base end end end + + describe "primary_breadcrumb_taxon" do + it 'should belong to primary_breadcrumb_taxon' do + expect(Spree::Product.reflect_on_association(:primary_breadcrumb_taxon).macro).to eq(:belongs_to) + end + + it 'should be optional' do + association = Spree::Product.reflect_on_association(:primary_breadcrumb_taxon) + expect(association.options[:optional]).to be(true) + end + + it 'should have a class_name of Spree::Taxon' do + association = Spree::Product.reflect_on_association(:primary_breadcrumb_taxon) + expect(association.class_name).to eq('Spree::Taxon') + end + end end end @@ -709,4 +725,13 @@ class Extension < Spree::Base end end end + + it 'is valid with or without a primary_breadcrumb_taxon' do + product_with_taxon = create(:product, primary_breadcrumb_taxon: create(:taxon)) + + product_without_taxon = create(:product, primary_breadcrumb_taxon: nil) + + expect(product_with_taxon).to be_valid + expect(product_without_taxon).to be_valid + end end