Holzpichler
/// Ticket 98400: Wishlist does not adopt/show the attributes and range values
//Convert to decimal with different culters.
range.Value = Decimal.Parse(rangeValue, System.Globalization.CultureInfo.InvariantCulture);
/// Ticket 98400: Wishlist does not adopt/show the attributes and range values
//Convert to decimal with different culters.
range.Value = Decimal.Parse(rangeValue, System.Globalization.CultureInfo.InvariantCulture);
@*Ticket 93818: [Holz Pichler] 3.3. Item variants – “range value” attributes.*@
var productUrl = Url.Sana.Product(Product, CommerceFrameworkBase.Context.LanguageId);
Eg : medux, Kavo
Update AdminMenuInitializer
layoutTabs.Add(AdminMenuConstants.LayoutTabs.ProductSettings, new TabInfoCollection() { CreateLayoutTab("productsets", T.ProductSets, urlHelper.Action("Index", "ProductSets"), PermissionOn.ProductSets), CreateLayoutTab("price", T.Price, urlHelper.Action("Price", "ProductSettings"), PermissionOn.ProductSettings), CreateLayoutTab("specs", T.ProductSpecifications, urlHelper.Action("Index", "ProductSpecs"), PermissionOn.ProductSettings), CreateLayoutTab("stock", T.StockAndAssortment, urlHelper.Action("StockAndAssortment", "ProductSettings"), PermissionOn.ProductSettings), CreateLayoutTab("stockranges", T.StockLevels, urlHelper.Action("Index", "StockRanges"), PermissionOn.ProductSettings), CreateLayoutTab("uoms", T.UnitsOfMeasure, urlHelper.Action("UnitsOfMeasure", "ProductSettings"), PermissionOn.ProductSettings), CreateLayoutTab("relatedproducts", Admin.ResourceManager.GetString(Sana.Commerce.Constants.RelatedProductSettingsMenu), urlHelper.Action("RelatedProductSetting", "ProductSettings"), PermissionOn.ProductSettings), <------ });
update ExtendedProductSettingsController
[HttpGet] [ErpConnectionRequired] public virtual ActionResult RelatedProductSetting() { var model = ObjectFactory.Create<RelatedProductModel>(); model.Initialize(Shop.Settings); return View(model); } /// <summary> /// A POST action to save the stock and assortment settings. /// </summary> /// <param name="model">The model.</param> /// <returns>Returns a view result.</returns> [HttpPost] [ValidateInput(false)] [SanaValidateAntiForgeryToken] public virtual ActionResult RelatedProductSetting(RelatedProductModel model) { if (ModelState.IsValid) { if (DataManager.ChangeWebsiteSettings(model.ApplyChanges)) RefreshSettingsCache(); } return View(model); }
Project Bosig,
Jimlorance. : https://sanacommerce.visualstudio.com/Sana%20Projects/_git/JimLawrence_932?path=%2FSana.Commerce.Sdk%2FCustomization%2FShop%2FExtendedSalesLineComparer.cs&_a=contents&version=GBmaster
HolzPicher
// Ticket 93818: [Holz Pichler] 3.3.Item variants – “range value” attributes.
public class ExtendedSalesLineComparer : SalesLineComparer { public override bool MatchSku(ISalesLine line1, ISalesLine line2) { var baseResult = base.MatchSku(line1, line2); var result = baseResult && CompareRangeValues(line1, line2); return result; } protected virtual bool CompareRangeValues(ISalesLine line1, ISalesLine line2) { // Ticket 93818: [Holz Pichler] 3.3.Item variants – “range value” attributes. var excictingLineRangeValues = ((BasketLine)line1).RangeValueList; var newLineRangeValues = ((BasketLine)line2).RangeValueList; var machingRangeValueFields = excictingLineRangeValues.Where(x => newLineRangeValues.Any(y => x.Name == y.Name && x.Id == y.Id && x.Value == y.Value)).ToList(); var result = (machingRangeValueFields.Count == newLineRangeValues.Count); return result; } }
latestMissmatchList = latestJsonList.Where(s => !oldCJsonList.Any(s2 => s2.Value.Contains(s.Value) && s2.Key.Contains(s.Key))).ToList();
oldMissmatchList = oldCJsonList.Where(s => !latestJsonList.Any(s2 => s2.Value.Contains(s.Value) && s2.Key.Contains(s.Key))).ToList();
var l1 = currentList.Where(m => !oldList.Contains(m)).ToList();
var l2 = oldList.Where(m => !currentList.Contains(m)).ToList();
var diff1 = currentList.Except(oldList).ToList();
var diff2 = oldList.Except(currentList).ToList();
Project Holzpitcher
@* Ticket 93818: [Holz Pichler] 3.3. Item variants – “range value” attributes. *@
control.quickorder.js
//Ticket 93818: [Holz Pichler] 3.3. Item variants – “range value” attributes.
self.isVarient = ko.observable(false); //Product contain varients or not
function QuickOrderViewModel() {
var self = this; ..... //Ticket 93818: [Holz Pichler] 3.3. Item variants – “range value” attributes. self.isVarient = ko.observable(); //Product contain varients or not .....
var _buildComponentGroups = function (data) { var componentsCollection = data.VariantComponents, variantsCollection = data.Variants; if (componentsCollection.length) { //Ticket 93818: [Holz Pichler] 3.3. Item variants – “range value” attributes. if (componentsCollection.length > 0) { self.isVarient(true) } else { self.isVarient(false) } // Product with variants self.componentGroups = $.map(componentsCollection, function (val, i) {
on view
get false value
data-bind="visible: (isVarient() == false)">
get True Value :
data-bind="visible:isVarient
@* Ticket 93818: [Holz Pichler] 3.3. Item variants – “range value” attributes. *@ <div class="" data-bind="visible:isVarient"> <a data-bind="attr: { href: product().url }" data-product-url class="btn"> @Sana.SimpleText("QuiclOrder_SelectVariants", "Select Variants") </a> </div> @* Ticket 93818: [Holz Pichler] 3.3. Item variants – “range value” attributes. *@ <div class="qo-quantity-box" data-bind="visible: (isVarient() == false)"> <!-- ko if: !product().isProductConfigurable --> @if (Shop.UserAbilities.Has(AbilityTo.ViewUnitOfMeasure)) { if (Shop.Settings.AllowUnitOfMeasureSelection)
Ravensburg
control.Quickorder.js
// Ticket 113689: [Ravensburger - Phase 2] 3.1. Display stock availability in the shopping cart. self.availabilityHtmlTags = ko.observable();
function QuickOrderViewModel() { var self = this; ... self.product = ko.observable(); self.components = ko.observable(); self.quantity = ko.observable(); self.selectedUom = ko.observable(); self.defaultUomTitle = ko.observable(); self.quantityStep = ko.observable(1); self.minimumQuantity = ko.observable(); self.maximumQuantity = ko.observable(); // Ticket 113689: [Ravensburger - Phase 2] 3.1. Display stock availability in the shopping cart. self.availabilityHtmlTags = ko.observable();
var _buildComponentGroups = function (data) { var componentsCollection = data.VariantComponents, variantsCollection = data.Variants; //Ticket 113689: [Ravensburger - Phase 2] 3.1. Display stock availability in the shopping cart self.availabilityHtmlTags = data.AvailabilityHtmlTags; if (componentsCollection.length) {
_quickorder.js
<form data-bind="form: 'quickOrderForm', submit: quickOrderSubmitForm, visible: product()" class="choose-product" style="display: none;"> <div data-bind="attr: { 'data-tracking-data': JSON.stringify(product().trackingData) }" class="qo-product-title-box"> <a data-bind="text: product().title, attr: { href: product().url }" data-product-url class="hyp-qo-title font-darkest" tabindex="-1" target="_blank"></a> </div> <div class="label"> <label>@Sana.SimpleText("Availability")</label> <div> @* //Ticket 113689: [Ravensburger - Phase 2] 3.1. Display stock availability in the shopping cart *@ <div data-bind="html:availabilityHtmlTags"></div> </div> </div>
using Sana.Commerce.Catalog; using Sana.Commerce.Customization.Catalog; using Sana.Commerce.Shop; using Sana.Commerce.Stock; using Sana.Commerce.Web; using Sana.Commerce.Web.Frontend; using Sana.Commerce.Web.Frontend.Models.Product; using Sana.Commerce.Web.Frontend.Models.QuickOrder; using Sana.Commerce.Web.Mvc.Extensions; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Web; using System.Web.Mvc; namespace Sana.Commerce.Customization.ExtendedModels { public class ExtendedProductInfoModel : ProductInfoModel { public StockModel Stock { get; set; } public bool IsInStock { get; set; } public string AvailabilityHref { get; set; } public int MaxStock { get; set; } public string AvailabilityHtmlTags { get; set; } public override void Initialize(IProduct product, IWebsiteSettings settings, string detailsUrl, bool isProductConfigurable = false) { base.Initialize(product, settings, detailsUrl, isProductConfigurable); Stock = ((ExtendedProductManager)CommerceFramework.Products).CreateProductStockModel(product); var isInStock = Stock.Inventory > Stock.StockRange.OutOfStock; IsInStock = isInStock; var href = "http://schema.org/" + (isInStock ? "InStock" : "OutOfStock"); // AvailabilityHref = href; var Model = Stock; var maxStock = Model.StockRange.MaxStockNumber.HasValue ? Model.StockRange.MaxStockNumber.Value : -1; MaxStock = maxStock; //HtmlString htmlString = new HtmlString(""); StringBuilder buildHtml = new StringBuilder(); var ranges = new[] { new { Min = Int32.MinValue, Max = Model.StockRange.OutOfStock }, new { Min = Model.StockRange.OutOfStock, Max = Model.StockRange.LowStock }, new { Min = Model.StockRange.LowStock, Max = Int32.MaxValue }, }; foreach (var range in ranges) { var show = Model.Inventory > range.Min && Model.Inventory <= range.Max; // var htmlString = new HtmlString(); var stockText_MaxStockNumber = CommerceFrameworkBase.SanaTexts.GetSanaText("StockText_MaxStockNumber").FormatWith(maxStock.ToString()); var isHideClass = (!show) ? "hide" : ""; var strHead = "<span class='stock-row "+ isHideClass + "'" + " data-min=" + range.Min.ToInvariantString() + " " + " data-max=" + range.Max.ToInvariantString() + " " + " data-max-stock=" + maxStock + " " + " data-custom-stock-message=" + "'"+ stockText_MaxStockNumber + "'" + " "+ ">"; var strBody = StockAmountExtention(Model.StockRange, show ? Model.Inventory : range.Max); //Sana.StockAmount(Model.StockRange, show ? Model.Inventory : range.Max) //ProductExtensions var strFooter = "</span>"; var combinedString = strHead + strBody + strFooter; buildHtml.Append(combinedString); } string sHtml = buildHtml.ToString(); sHtml = sHtml.Replace("\"","'"); // REmove double qu //var htmlString = new HtmlString(sHtml); //AvailabilityHtmlTags = htmlString; AvailabilityHtmlTags = sHtml; AvailabilityHref = "<p>Trrrrrrr</p>"; } private string StockAmountExtention(IStockRange stockRange, decimal inventory, string textKeySuffix = null) { var val = ""; var textKey = "StockText_"; if (inventory <= stockRange.OutOfStock) textKey += "OutOfStock"; else if (inventory > stockRange.LowStock) textKey += "InStock"; else textKey += "LowStock"; if (ShopContext.GetCurrent().IsB2cCustomer) textKey += "_B2C"; else textKey += "_B2B"; if (textKeySuffix != null && !textKeySuffix.StartsWith("_")) textKeySuffix = '_' + textKeySuffix; var inventoryElement = new TagBuilder("span"); inventoryElement.AddCssClass("stock-amount"); if (stockRange.MaxStockNumber.HasValue && stockRange.MaxStockNumber.Value < inventory) { var maxStockNumberText = CommerceFrameworkBase.SanaTexts.GetSanaText("StockText_MaxStockNumber").FormatWith(stockRange.MaxStockNumber.Value.ToString()); inventoryElement.SetInnerText(HttpUtility.HtmlDecode(maxStockNumberText.ToString())); } else { inventoryElement.SetInnerText(inventory.ToString()); } var txtKeyString = CommerceFrameworkBase.SanaTexts.GetSanaText(textKey + textKeySuffix); //.iReplace("[STOCKAMOUNT]", inventoryElement.AsHtml()); var htmlKeyString = new HtmlString(txtKeyString); var stockText = htmlKeyString.iReplace("[STOCKAMOUNT]", inventoryElement.AsHtml()); val = StockAmountHelper(stockRange , inventory, stockText); //var stockText = viewHelper.RichText(textKey + textKeySuffix).iReplace("[STOCKAMOUNT]", inventoryElement.AsHtml()); //return CompiledHelpers.Themed(ProductHelpers.StockAmount, stockRange, inventory, stockText); //-------- return val; } public string StockAmountHelper(Sana.Commerce.Stock.IStockRange stockRange, decimal inventory, IHtmlString stockText) { // StockAmount(Sana.Commerce.Stock.IStockRange stockRange, decimal inventory, IHtmlString stockText) var cssStockState = "low-stock"; if (inventory <= stockRange.OutOfStock) { cssStockState = "out-stock"; } else if (inventory > stockRange.LowStock) { cssStockState = "in-stock"; } var value = "<span class='lbl-stock " + cssStockState + "'> " + stockText + " </span>"; return value; } } }
Ticket 88715: [Kavo] 3.15. SEARCH – PUNCTUATION MARKS
Ticket 96724: [Kavo] 3.16 SEARCH – OEM# AND COMPETITOR# FIELDS
bachofn projects.
when the product index run
it will Hit ExtractKeyword method on LucenIndexManager
public class ExtendedLuceneIndexManager : LuceneIndexManager { protected override IDictionary<string, string> ExtractKeywords(IndexEntry entry) { var keywords = new Dictionary<string, string>(); foreach (var language in languageList) { var sb = new StringBuilder(); foreach (var field in indexInfo.KeywordFields) <----from Admin section { var persister = IndexFieldPersisterFactory.Current.GetPersister(field.Type); if (!persister.IsSearchable) throw new SanaBusinessException(String.Format("Field '{0}' cannot be a keyword field because the persistor does not allow this field to be searchable.", field.Name)); var value = persister.ExtractKeywords(entry.Entity, field, language); sb.Append(value); sb.Append(' '); } keywords.Add(language, sb.ToString()); } return keywords; } }
to run it correctly you need to set Keyword fields.
KeyWord .
can find all from Framework initializer
field No --> IdPersister / ExtendedIdPersister
Title --> TitlePersister
Description --> ExtendedDescriptionPersister
etc ....
FrameworkInitializer
// Custom product persisters factory.Register(ProductIndexFieldTypes.Id, new ExtendedIdPersister()); factory.Register(ProductIndexFieldTypes.Title, new ExtendedTitlePersister()); factory.Register(ProductIndexFieldTypes.Price, new PricePersister()); factory.Register(ProductIndexFieldTypes.VariantTitle, new VariantTitlePersister()); factory.Register(ProductIndexFieldTypes.ProductCategory, new CategoryPersister()); factory.Register(ProductIndexFieldTypes.VariantComponent, new VariantComponentPersister()); factory.Register(ProductIndexFieldTypes.Description, new ExtendedDescriptionPersister()); factory.Register(ProductIndexFieldTypes.OrderableProductGrossWeight, new OrderableProductGrossWeightPersister()); factory.Register(ProductIndexFieldTypes.BarCode, new BarCodePersister()); factory.Register(ProductIndexFieldTypes.Suggestion, new SuggestionPersister());
customization for the issue, and run the Product index
public class ExtendedIdPersister : IdPersister { public override string ExtractKeywords(IEntity entity, IndexFieldInfo field, string language) { //Ticket 88715: [Kavo] 3.15. SEARCH – PUNCTUATION MARKS. var keywords = base.ExtractKeywords(entity, field, language); //To accomodate the keworks without some character set var removableSeparators = new string[] { " ", ",", "-", "/" }; var config = ConfigurationManager.AppSettings["RemovableSeparatorsForIndexFieldDescription"]; if (!config.IsEmptyString()) removableSeparators = config.Split(";").ToArray(); var newKeywords = keywords; if (!keywords.IsEmptyString()) { foreach (var separator in removableSeparators) { if (newKeywords.Contains(separator)) { newKeywords = newKeywords.Replace(separator, ""); } } keywords = keywords + " " + newKeywords; } return keywords; } }
public class ExtendedIdPersister : IdPersister { public override string ExtractKeywords(IEntity entity, IndexFieldInfo field, string language) { //Ticket 88715: [Kavo] 3.15. SEARCH – PUNCTUATION MARKS. var keywords = base.ExtractKeywords(entity, field, language); var keywordWithooutSpecialCaracters = ExtendedProductSearchManager.ExtractKeywordwithSpecialCaracters(keywords); keywords += " " + keywordWithooutSpecialCaracters; return keywords; } }
public class ExtendedProductSearchManager : ProductSearchManager<ExtendedProductSearchIndexProvider> { /// <summary> /// This method will extract kewords /// </summary> /// <param name="keywords"></param> /// <returns></returns> public static string ExtractKeywordwithSpecialCaracters(string keywords) { //To accomodate the kewords without some character set var removableSeparators = new string[] { "-" }; var config = ConfigurationManager.AppSettings["RemovableSeparatorsForIndexFieldDescription"]; if (!config.IsEmptyString()) removableSeparators = config.Split(";").ToArray(); var newKeywords = keywords; if (!keywords.IsEmptyString()) { foreach (var separator in removableSeparators) { if (newKeywords.Contains(separator)) { newKeywords = newKeywords.Replace(separator, ""); } } } return newKeywords; } }
Quick search on basket page use : SuggestionPersister.
public class ExtendedSuggestionPersister : SuggestionPersister { /// Ticket 88715: [Kavo] 3.15. SEARCH – PUNCTUATION MARKS. /// <summary> /// Extracts the keywords from the field for the specified language. /// </summary> /// <param name="entity">The entity.</param> /// <param name="field">The field.</param> /// <param name="language">The language.</param> /// <returns>Extracted keywords.</returns> public override string ExtractKeywords(IEntity entity, IndexFieldInfo field, string language) { var keywords = base.ExtractKeywords(entity, field, language); keywords = ExtendedProductSearchManager.ExtractKeywordwithSpecialCaracters(keywords); return keywords; } }
This method helps : When user add Keyword Fields
public class ExtendedLuceneIndexManager : LuceneIndexManager { protected override IDictionary<string, string> ExtractKeywords(IndexEntry entry) { var keywords = new Dictionary<string, string>(); foreach (var language in languageList) { var sb = new StringBuilder(); foreach (var field in indexInfo.KeywordFields) { var persister = IndexFieldPersisterFactory.Current.GetPersister(field.Type); if (!persister.IsSearchable) throw new SanaBusinessException(String.Format("Field '{0}' cannot be a keyword field because the persistor does not allow this field to be searchable.", field.Name)); var value = persister.ExtractKeywords(entry.Entity, field, language); sb.Append(value); sb.Append(' '); } keywords.Add(language, sb.ToString()); } return keywords; } }
it will call all the Persisters one by one .
This method helps : When user add FilterFields.
public class ExtendedLuceneIndexManager : LuceneIndexManager
protected virtual void AddFilterFields(Document doc, IndexEntry entry) { foreach (var field in indexInfo.FilterFields) { var persister = IndexFieldPersisterFactory.Current.GetPersister(field.Type); var index = persister.IsSortable && !persister.IsFilterable && !persister.IsSearchable ? Field.Index.NOT_ANALYZED : Field.Index.ANALYZED; var store = persister.IsRetrievable || persister.IsSortable ? Field.Store.YES : Field.Store.NO; var pairList = persister.GetValuesToIndex(entry.Entity, field).ToList(); foreach (var pair in pairList) { var strValue = persister.GetStringValue(pair.Value); doc.Add(new Field(pair.Name, strValue, store, index)); } } }
If string contains special caracter and you want to index those separately
eg : Name1;Name3;name3; name4
public class ExtendedStringFieldPersister : StringFieldPersister { protected override object GetPropertyValue(IEntity entity, string propertyName) { var pi = entity.GetType().GetProperty(propertyName); if (pi != null) return pi.GetValue(entity, null); // Ticket 100006: [Topmedia] 3.1. Search – Product Finder // When split text by Product attribute split key. var value = entity.Fields.GetValue(propertyName).SafeToString(); if (entity is Product && value.iContains(Constants.ProductAttributeSplitKey)) { var val = value.Split(Constants.ProductAttributeSplitKey).Aggregate((x, y) => string.Join(Environment.NewLine, x, y)); return val; } return value; } }
public static class Constants
{ /// Ticket 100006: [Topmedia] 3.1. Search – Product Finder /// <summary> /// Product finder settings menu /// </summary> public const string ProductAttributeSplitKey = ";"; }
create new Persister
/// Ticket 100006: [Topmedia] 3.1. Search – Product Finder /// <summary> /// Product Type Persister /// </summary> public class ProductTypePersister : StringFieldPersister, ICustomIndexFieldPersister { /// <summary> /// Gets a value indicating whether the field can be searchable by keywords. /// </summary> /// <value> /// If not overridden in a derived class, returns <c>true</c>. /// </value> public override bool IsSearchable { get { return true; } } /// <summary> /// Gets a value indicating whether the field can be filterable. /// </summary> /// <value> /// If not overridden in a derived class, returns <c>false</c>. /// </value> public override bool IsFilterable { get { return true; } } /// <summary> /// Gets a value indicating whether the field can be sortable. /// </summary> /// <value> /// If not overridden in a derived class, returns <c>false</c>. /// </value> public override bool IsSortable { get { return false; } } /// <summary> /// Gets a value indicating whether the field should be retrievable from the index. /// </summary> /// <value> /// If not overridden in a derived class, returns <c>true</c>. /// </value> public override bool IsRetrievable { get { return true; } } /// Ticket 100006: [Topmedia] 3.1. Search – Product Finder public override IEnumerable<NameValuePair> GetValuesToIndex(IEntity entity, IndexFieldInfo field) { var productTypeCode = ((Product)entity).ProductTypeCode; var values = productTypeCode != null ? productTypeCode : string.Empty; yield return new NameValuePair(Constants.ProductTypeCode, values); } /// Ticket 100006: [Topmedia] 3.1. Search – Product Finder public override string ExtractKeywords(IEntity entity, IndexFieldInfo field, string language) { var productTypeCode = ((Product)entity).ProductTypeCode; var values = productTypeCode != null ? productTypeCode : string.Empty; return values; } public IList<IField> GetAvailableFields(string fieldType) { return null; } public void SetupIndexingLoadOptions(IEntityListLoadOptions loadOptions, IndexFieldInfo field, IIndexInfo indexInfo) { }
Kavo Project
OemReferace are string list eg : 1000,1001,200,2005, ..
then save as new line
oemReferenceNumbers.JoinWith(Environment.NewLine)
public class OemReferenceNumbersPersister : StringFieldPersister, ICustomIndexFieldPersister { ... /// Ticket 96724: [Kavo] 3.16 SEARCH – OEM# AND COMPETITOR# FIELDS public override IEnumerable<NameValuePair> GetValuesToIndex(IEntity entity, IndexFieldInfo field) { var oemReferenceNumbers = ((Product)entity).OemReferenceNumbers; var values = oemReferenceNumbers != null ? oemReferenceNumbers.JoinWith(Environment.NewLine) : string.Empty; yield return new NameValuePair(Constants.OemReferenceNumbers, values); } /// Ticket 96724: [Kavo] 3.16 SEARCH – OEM# AND COMPETITOR# FIELDS public override string ExtractKeywords(IEntity entity, IndexFieldInfo field, string language) { var oemReferenceNumbers = ((Product)entity).OemReferenceNumbers; var values = oemReferenceNumbers != null ? oemReferenceNumbers.JoinWith(Environment.NewLine) : string.Empty; return values; } ...
Compititor is a object
get nessary field only and create a list and bind
var competitorsRefList = competitors?.Select(x => x.Reference).ToList(); var values = competitorsRefList != null ? competitorsRefList.JoinWith(Environment.NewLine)
public class CompetitorsPersister : StringFieldPersister, ICustomIndexFieldPersister { ...... /// Ticket 96724: [Kavo] 3.16 SEARCH – OEM# AND COMPETITOR# FIELDS public override IEnumerable<NameValuePair> GetValuesToIndex(IEntity entity, IndexFieldInfo field) { var competitors = ((Product)entity).Competitors; var competitorsRefList = competitors?.Select(x => x.Reference).ToList(); var values = competitorsRefList != null ? competitorsRefList.JoinWith(Environment.NewLine) : string.Empty; yield return new NameValuePair(Constants.Competitors, values); } /// Ticket 96724: [Kavo] 3.16 SEARCH – OEM# AND COMPETITOR# FIELDS public override string ExtractKeywords(IEntity entity, IndexFieldInfo field, string language) { var competitors = ((Product)entity).Competitors; var competitorsRefList = competitors?.Select(x => x.Reference).ToList(); var values = competitorsRefList != null ? competitorsRefList.JoinWith(Environment.NewLine) : string.Empty; return values; } ..... }
register under custom product persisters
Frameworkinitializer.cs
// Ticket 96724: [Kavo] 3.16 SEARCH – OEM# AND COMPETITOR# FIELDS factory.Register(Constants.CompetitorsPersisterName, new CompetitorsPersister()); factory.Register(Constants.OemReferenceNumbersPersisterName, new OemReferenceNumbersPersister());