ElasticSearch Order By Price, dose not work

Hi, elastic index all product prices booth per pricelist and per country. So a ProductDocument.Prices looks like this.

    {
      "systemId" : "935ee8b1-c12f-45a4-b183-9f3a968461f3",
      "countrySystemId" : "31418f63-5108-440d-a07a-daea8d5296fa",
      "isCampaignPrice" : false,
      "price" : 173.6
    },
    {
      "systemId" : "935ee8b1-c12f-45a4-b183-9f3a968461f3",
      "countrySystemId" : "c6c883f8-c679-423a-9af0-8d3837ea0b88",
      "isCampaignPrice" : false,
      "price" : 175.0
    },
    {
      "systemId" : "ead1d6cc-da76-43a6-b616-1493c1de2446",
      "countrySystemId" : "cb7048a5-0afa-48be-8995-533865c452ca",
      "isCampaignPrice" : false,
      price" : 0.0
    }

Some language/lists dose not have a price (note the last object).

So when ordering my search results by min-price this product will be first on all languages/channels?

This is what the query look like (below). Am i doing somthing wrong? or should the code not include the language and default/current pricelist id in the query somehow?
It feels like am missing somthing or the price filter is broken? Look at the “sort” section below when sorting a default category.

GET /elevennf-v1productdocument.sv-se/_search {
"query": {
"bool": {
  "filter": [
    {
      "term": {
        "channelSystemId": {
          "value": "baa772ef-f705-4765-82ad-686560a35e6a",
          "boost": 1
        }
      }
    },
    {
      "term": {
        "assortments": {
          "value": "15d7b6d8-fd0b-4e5c-5ef9-08d6e1f51a46",
          "boost": 1
        }
      }
    },
    {
      "term": {
        "organizations": {
          "value": "00000000-0000-0000-0000-000000000000",
          "boost": 1
        }
      }
    },
    {
      "term": {
        "parentCategories": {
          "value": "d2090375-650f-48a9-b7ca-194886907624",
          "boost": 1
        }
      }
    },
    {
      "bool": {
        "should": [
          {
            "term": {
              "permissions": {
                "value": "Group:5b521f8c-a7a2-44e0-ab76-1c60d740bded",
                "boost": 1
              }
            }
          },
          {
            "term": {
              "permissions": {
                "value": "Group:3bb00cc4-a86f-4d5d-b43a-aed35d4ecab5",
                "boost": 1
              }
            }
          }
        ],
        "adjust_pure_negative": true,
        "boost": 1
      }
    }
  ],
  "adjust_pure_negative": true,
  "boost": 1
} },
"sort": [
{
  "prices.price": {
    "order": "asc",
    "mode": "min",
    "nested": {
      "path": "prices"
    }
  }
},
{
  "name.keyword": {
    "order": "asc"
  }
},
{
  "articleNumber": {
    "order": "asc"
  }
}
]
}

Litium version: 7.4

Easyest for us might just be to add a displayPrice to the ProductDocument, this works fine 4 us but this is not possible for every setup, but i must admit that i’m not used to thinking about multiple prices per product, so i can be all wrong here :slight_smile:

	private void PopulateDisplayPrices(ProductDocument model, Channel channel)
	{
		// Since we only have one country per channel
		var countryId = channel.CountryLinks.FirstOrDefault()?.CountrySystemId;

		var pricelists = model.Prices.Where(pl => pl.CountrySystemId == countryId).OrderBy(pl => pl.Price);

		model.DisplayPrice = pricelists.First().Price;
	}

Edit
This did not work 4 campaign prices

Its seams that this list contains price lists 4 all websites? But sence the documents are Channel Spesific should it not only contain pricelists from the channels website?

We have now added, websiteId to the priceItems, and can use that to filter i hope am wrong and can revert all tis hehe but i think the elastic plugins needs a price logic rewrite?

Edit:
So i guess that the reason for this is that we want to keep the price list lookup outide of the channel loop for preformence? So i just added

ProductIndexDocumentBuilder.cs
…
PopulatePrices(model, context, variants);
CleanPriceListIndex(model);
…

private void CleanPriceListIndex(ProductDocument model)
	{
		var activeCountries = _channelService.Get(model.ChannelSystemId).CountryLinks.Select(cl => cl.CountrySystemId);
		model.Prices = model.Prices.Where(pl => pl.Websites.Contains(model.WebsiteSystemId) && activeCountries.Contains(pl.CountrySystemId) ).ToList();
	}

Will this cause problems with the admin search?

We also changes the BuildPriceData(Context context) fuction to only create PriceItems for countrys active on the priceList, The default is to create PriceItems with faulty? CountrySystemId Or is there a reason 4 having a English Pricelist index with a Swedish CountrySystemId ?

Also the default code dose not seam to index Campaign prices ( % disscount) at all in the index?
all prices are IsCampaignPrice = false but the product has a active disscount.

I see this code, but it never hits my yield brakepoint.

                foreach (var item in _campaignDataHolder.GetArticleCampaigns(variant.SystemId)
                .GroupBy(x => x.CampaignId)
                .Select(x => new { x.Key, Price = x.Min(z => z.Price) }))
            {
                yield return new ProductDocument.PriceItem { IsCampaignPrice = true, SystemId = item.Key,  Price = item.Price};
            }

In general, campaign prices are not indexed. They’re calculated as needed.

There’s a scenario where they are though: if you’re using the fixed price action and the belongs-to-a-group condition. See CampaignEventListener and CampaignIndexingProviderPreProcessor in the Accelerator.

Cool, but is it true that the logic is “broken”? That it will order based on the cheepest site on all webslites and not just the current one? or Have i misunderstood?

Regarding to the json that was sent to ES it looks like a filter is not included. If you look into the ProductSearchServiceDecorator.BuildSorting-method you can see that the price fields should be filtered to only contain the price lists that are active for the current user. Can you debug this method and see if the priceFilters variable contains the correct pricelists for the user?

The reason that all prices are added into search index is that you may add/remove countries on the channel and that should not require that the full search index need to be rebuilt. It may also be that some price lists are restricted to specific user group and the search index need the information to be able to build filters and make the sorting to work.

I can confirm that this is a bug and is reported as https://docs.litium.com/support/bugs/bug_details?id=51445.

To fix you need to update two files in the accelerator:

  1. SearchPriceFilterService
public IEnumerable<Func<QueryContainerDescriptor<ProductDocument>, QueryContainer>> GetPriceFilterTags(
            SearchQuery searchQuery,
            Container container,
            Guid countrySystemId,
            bool filterForSorting = false)
        {
            if (searchQuery.ContainsPriceFilter())
            {
                foreach (var item in container.PriceLists)
                {
                    foreach (var priceItem in searchQuery.PriceRanges)
                    {
                        yield return q => q.Nested(n => n
                                .Path(x => x.Prices)
                                .Query(nq
                                    => nq.Term(t => t.Field(f => f.Prices[0].SystemId).Value(item))
                                    && nq.Term(t => t.Field(f => f.Prices[0].CountrySystemId).Value(countrySystemId))
                                    && nq.Term(t => t.Field(f => f.Prices[0].IsCampaignPrice).Value(false))
                                    && nq.Range(t => t.Field(f => f.Prices[0].Price)
                                    .GreaterThanOrEquals(priceItem.Item1)
                                    .LessThanOrEquals(priceItem.Item2))
                                )
                            );
                    }
                }

                foreach (var item in container.Campaigns)
                {
                    foreach (var priceItem in searchQuery.PriceRanges)
                    {
                        yield return q => q.Nested(n => n
                              .Path(x => x.Prices)
                              .Query(nq
                                  => nq.Term(t => t.Field(f => f.Prices[0].SystemId).Value(item))
                                  && nq.Term(t => t.Field(f => f.Prices[0].IsCampaignPrice).Value(true))
                                  && nq.Range(t => t.Field(f => f.Prices[0].Price)
                                    .GreaterThanOrEquals(priceItem.Item1)
                                    .LessThanOrEquals(priceItem.Item2))
                              )
                          );
                    }
                }
            }
            else if (filterForSorting)
            {
                foreach (var item in container.PriceLists)
                {
                    yield return q => q.Bool(b => b
                        .Must(nq
                            => nq.Term(t => t.Field(f => f.Prices[0].SystemId).Value(item))
                            && nq.Term(t => t.Field(f => f.Prices[0].CountrySystemId).Value(countrySystemId))
                            && nq.Term(t => t.Field(f => f.Prices[0].IsCampaignPrice).Value(false))
                        )
                    );
                }

                foreach (var item in container.Campaigns)
                {
                    yield return q => q.Bool(b => b
                        .Must(nq
                            => nq.Term(t => t.Field(f => f.Prices[0].SystemId).Value(item))
                            && nq.Term(t => t.Field(f => f.Prices[0].IsCampaignPrice).Value(true))
                        )
                    );
                }
            }
        }
  1. SearchQueryBuilder method BuildSorting. In the case for sorting by price the calling method for _priceFilterService.GetPriceFilterTags should include an true for the filterForSorting-parameter.
                        case SearchQueryConstants.Price:
                            var priceFilters = _priceFilterService
                                    .GetPriceFilterTags(searchQuery, _priceContainer.Value, _countrySystemId.Value, true)
                                    .ToList();
1 Like

Thanks! Good to know that am still sane :smiley:

This topic was automatically closed 28 days after the last reply. New replies are no longer allowed.