public virtual async Task <SearchCategoryResponse> Handle(SearchCategoryQuery request, CancellationToken cancellationToken)
        {
            var   essentialTerms = new List <string>();
            Store store          = null;

            if (!string.IsNullOrWhiteSpace(request.StoreId))
            {
                store = await _storeService.GetByIdAsync(request.StoreId);

                if (store == null)
                {
                    throw new ArgumentException($"Store with Id: {request.StoreId} is absent");
                }

                essentialTerms.Add($"__outline:{store.Catalog}");
            }

            var searchRequest = new IndexSearchRequestBuilder()
                                .WithFuzzy(request.Fuzzy, request.FuzzyLevel)
                                .ParseFilters(_phraseParser, request.Filter)
                                .WithSearchPhrase(request.Query)
                                .WithPaging(request.Skip, request.Take)
                                .AddObjectIds(request.ObjectIds)
                                .AddSorting(request.Sort)
                                //Limit search result with store catalog
                                .AddTerms(essentialTerms)
                                .WithIncludeFields(IndexFieldsMapper.MapToIndexIncludes(request.IncludeFields).ToArray())
                                .Build();

            var searchResult = await _searchProvider.SearchAsync(KnownDocumentTypes.Category, searchRequest);

            var categories = searchResult.Documents?.Select(x => _mapper.Map <ExpCategory>(x, options =>
            {
                options.Items["store"]       = store;
                options.Items["cultureName"] = request.CultureName;
            })).ToList() ?? new List <ExpCategory>();

            var result = new SearchCategoryResponse
            {
                Query      = request,
                Results    = categories,
                Store      = store,
                TotalCount = (int)searchResult.TotalCount
            };

            await _pipeline.Execute(result);

            return(result);
        }
Exemple #2
0
        public virtual async Task Run(SearchProductResponse parameter, Func <SearchProductResponse, Task> next)
        {
            if (parameter == null)
            {
                throw new ArgumentNullException(nameof(parameter));
            }

            var query = parameter.Query;

            if (query == null)
            {
                throw new OperationCanceledException("Query must be set");
            }

            var responseGroup = EnumUtility.SafeParse(query.GetResponseGroup(), ExpProductResponseGroup.None);

            // If promotion evaluation requested
            if (responseGroup.HasFlag(ExpProductResponseGroup.LoadPrices))
            {
                var promoEvalContext = new PromotionEvaluationContext
                {
                    Currency   = query.CurrencyCode,
                    StoreId    = query.StoreId,
                    Language   = query.CultureName,
                    CustomerId = query.UserId
                };
                await _pipeline.Execute(promoEvalContext);

                //Evaluate promotions
                promoEvalContext.PromoEntries = parameter.Results.Select(x => _mapper.Map <ProductPromoEntry>(x, options =>
                {
                    options.Items["all_currencies"] = parameter.AllStoreCurrencies;
                    options.Items["currency"]       = parameter.Currency;
                })).ToList();

                var promotionResults = await _marketingEvaluator.EvaluatePromotionAsync(promoEvalContext);

                var promoRewards = promotionResults.Rewards.OfType <CatalogItemAmountReward>().ToArray();
                if (promoRewards.Any())
                {
                    parameter.Results.Apply(x => x.ApplyRewards(promoRewards));
                }
            }
            await next(parameter);
        }
Exemple #3
0
        public async Task Run(SearchProductResponse parameter, Func <SearchProductResponse, Task> next)
        {
            if (parameter == null)
            {
                throw new ArgumentNullException(nameof(parameter));
            }

            var query = parameter.Query;

            if (query == null)
            {
                throw new OperationCanceledException("Query must be set");
            }

            var responseGroup = EnumUtility.SafeParse(query.GetResponseGroup(), ExpProductResponseGroup.None);

            // If tax evaluation requested
            if (responseGroup.HasFlag(ExpProductResponseGroup.LoadPrices))
            {
                //Evaluate taxes
                var storeTaxProviders = await _taxProviderSearchService.SearchTaxProvidersAsync(new TaxProviderSearchCriteria { StoreIds = new[] { query.StoreId } });

                var activeTaxProvider = storeTaxProviders.Results.FirstOrDefault(x => x.IsActive);
                if (activeTaxProvider != null)
                {
                    var taxEvalContext = new TaxEvaluationContext {
                        Currency = query.CurrencyCode, StoreId = query.StoreId, CustomerId = query.UserId
                    };

                    await _pipeline.Execute(taxEvalContext);

                    taxEvalContext.Lines = parameter.Results.SelectMany(x => _mapper.Map <IEnumerable <TaxLine> >(x)).ToList();
                    var taxRates = activeTaxProvider.CalculateRates(taxEvalContext);
                    if (taxRates.Any())
                    {
                        parameter.Results.Apply(x => x.AllPrices.Apply(p => p.ApplyTaxRates(taxRates)));
                    }
                }
            }

            await next(parameter);
        }
        public virtual async Task Run(SearchProductResponse parameter, Func <SearchProductResponse, Task> next)
        {
            if (parameter == null)
            {
                throw new ArgumentNullException(nameof(parameter));
            }

            var query = parameter.Query;

            if (query == null)
            {
                throw new OperationCanceledException("Query must be set");
            }

            // Map indexed prices
            foreach (var expProducts in parameter.Results)
            {
                expProducts.AllPrices = _mapper.Map <IEnumerable <ProductPrice> >(expProducts.IndexedPrices, context =>
                {
                    context.Items["all_currencies"] = parameter.AllStoreCurrencies;
                }).ToList();

                if (parameter.Currency != null)
                {
                    expProducts.AllPrices = expProducts.AllPrices.Where(x => (x.Currency == null) || x.Currency.Equals(parameter.Currency)).ToList();
                }
            }

            // If prices evaluation requested
            var responseGroup = EnumUtility.SafeParse(query.GetResponseGroup(), ExpProductResponseGroup.None);

            if (responseGroup.HasFlag(ExpProductResponseGroup.LoadPrices))
            {
                //evaluate prices only if product missed prices in the index storage
                var productsWithoutPrices = parameter.Results.Where(x => !x.IndexedPrices.Any()).ToArray();
                if (productsWithoutPrices.Any())
                {
                    var evalContext = AbstractTypeFactory <PricingModule.Core.Model.PriceEvaluationContext> .TryCreateInstance();

                    evalContext.Currency   = query.CurrencyCode;
                    evalContext.StoreId    = query.StoreId;
                    evalContext.CustomerId = query.UserId;
                    evalContext.Language   = query.CultureName;

                    await _pipeline.Execute(evalContext);

                    evalContext.ProductIds = productsWithoutPrices.Select(x => x.Id).ToArray();
                    var prices = await _pricingService.EvaluateProductPricesAsync(evalContext);

                    foreach (var product in productsWithoutPrices)
                    {
                        product.AllPrices = _mapper.Map <IEnumerable <ProductPrice> >(prices.Where(x => x.ProductId == product.Id), options =>
                        {
                            options.Items["all_currencies"] = parameter.AllStoreCurrencies;
                            options.Items["currency"]       = parameter.Currency;
                        }).ToList();
                    }
                }
            }
            await next(parameter);
        }
Exemple #5
0
        public virtual async Task <SearchProductResponse> Handle(SearchProductQuery request, CancellationToken cancellationToken)
        {
            var allStoreCurrencies = await _storeCurrencyResolver.GetAllStoreCurrenciesAsync(request.StoreId, request.CultureName);

            var currency = await _storeCurrencyResolver.GetStoreCurrencyAsync(request.CurrencyCode, request.StoreId, request.CultureName);

            var store = await _storeService.GetByIdAsync(request.StoreId);

            var responseGroup = EnumUtility.SafeParse(request.GetResponseGroup(), ExpProductResponseGroup.None);

            var builder = new IndexSearchRequestBuilder()
                          .WithCurrency(currency.Code)
                          .WithFuzzy(request.Fuzzy, request.FuzzyLevel)
                          .ParseFilters(_phraseParser, request.Filter)
                          .WithSearchPhrase(request.Query)
                          .WithPaging(request.Skip, request.Take)
                          .AddObjectIds(request.ObjectIds)
                          .AddSorting(request.Sort)
                          .WithIncludeFields(IndexFieldsMapper.MapToIndexIncludes(request.IncludeFields).ToArray());

            if (request.ObjectIds.IsNullOrEmpty())
            {
                AddDefaultTerms(builder, store.Catalog);
            }

            var criteria = new ProductIndexedSearchCriteria
            {
                StoreId      = request.StoreId,
                Currency     = request.CurrencyCode ?? store.DefaultCurrency,
                LanguageCode = store.Languages.Contains(request.CultureName) ? request.CultureName : store.DefaultLanguage,
                CatalogId    = store.Catalog
            };

            //Use predefined  facets for store  if the facet filter expression is not set
            if (responseGroup.HasFlag(ExpProductResponseGroup.LoadFacets))
            {
                var predefinedAggregations = await _aggregationConverter.GetAggregationRequestsAsync(criteria, new FiltersContainer());

                builder.WithCultureName(criteria.LanguageCode);
                builder.ParseFacets(_phraseParser, request.Facet, predefinedAggregations)
                .ApplyMultiSelectFacetSearch();
            }

            var searchRequest = builder.Build();
            var searchResult  = await _searchProvider.SearchAsync(KnownDocumentTypes.Product, searchRequest);

            var resultAggregations = await ConvertResultAggregations(criteria, searchRequest, searchResult);

            searchRequest.SetAppliedAggregations(resultAggregations.ToArray());

            var products = searchResult.Documents?.Select(x => _mapper.Map <ExpProduct>(x)).ToList() ?? new List <ExpProduct>();

            var result = new SearchProductResponse
            {
                Query = request,
                AllStoreCurrencies = allStoreCurrencies,
                Currency           = currency,
                Store   = store,
                Results = products,
                Facets  = resultAggregations?.ApplyLanguageSpecificFacetResult(criteria.LanguageCode)
                          .Select(x => _mapper.Map <FacetResult>(x, options =>
                {
                    options.Items["cultureName"] = criteria.LanguageCode;
                })).ToList(),
                TotalCount = (int)searchResult.TotalCount
            };

            await _pipeline.Execute(result);

            return(result);

            async Task <Aggregation[]> ConvertResultAggregations(ProductIndexedSearchCriteria criteria, SearchRequest searchRequest, SearchResponse searchResult)
            {
                // Preconvert resulting aggregations to be properly understandable by catalog module
                var preconvertedAggregations = new List <AggregationResponse>();
                //Remember term facet ids to distinguish the resulting aggregations are range or term
                var termsInRequest = new List <string>(searchRequest.Aggregations.Where(x => x is TermAggregationRequest).Select(x => x.Id ?? x.FieldName));

                foreach (var aggregation in searchResult.Aggregations)
                {
                    if (!termsInRequest.Contains(aggregation.Id))
                    {
                        // There we'll go converting range facet result
                        var fieldName = new Regex(@"^(?<fieldName>[A-Za-z0-9]+)(-.+)*$", RegexOptions.IgnoreCase).Match(aggregation.Id).Groups["fieldName"].Value;
                        if (!fieldName.IsNullOrEmpty())
                        {
                            preconvertedAggregations.AddRange(aggregation.Values.Select(x =>
                            {
                                var matchId = new Regex(@"^(?<left>[0-9*]+)-(?<right>[0-9*]+)$", RegexOptions.IgnoreCase).Match(x.Id);
                                var left    = matchId.Groups["left"].Value;
                                var right   = matchId.Groups["right"].Value;
                                x.Id        = left == "*" ? $@"under-{right}" : x.Id;
                                x.Id        = right == "*" ? $@"over-{left}" : x.Id;
                                return(new AggregationResponse()
                                {
                                    Id = $@"{fieldName}-{x.Id}", Values = new List <AggregationResponseValue> {
                                        x
                                    }
                                });
                            }
                                                                                        ));
                        }
                    }
                    else
                    {
                        // This is term aggregation, should skip converting and put resulting aggregation as is
                        preconvertedAggregations.Add(aggregation);
                    }
                }

                //Call the catalog aggregation converter service to convert AggregationResponse to proper Aggregation type (term, range, filter)
                return(await _aggregationConverter.ConvertAggregationsAsync(preconvertedAggregations, criteria));
            }
        }
        public virtual async Task <SearchProductResponse> Handle(SearchProductQuery request, CancellationToken cancellationToken)
        {
            var allStoreCurrencies = await _storeCurrencyResolver.GetAllStoreCurrenciesAsync(request.StoreId, request.CultureName);

            var currency = await _storeCurrencyResolver.GetStoreCurrencyAsync(request.CurrencyCode, request.StoreId, request.CultureName);

            var store = await _storeService.GetByIdAsync(request.StoreId);

            var responseGroup = EnumUtility.SafeParse(request.GetResponseGroup(), ExpProductResponseGroup.None);

            var builder = new IndexSearchRequestBuilder()
                          .WithFuzzy(request.Fuzzy, request.FuzzyLevel)
                          .ParseFilters(_phraseParser, request.Filter)
                          .WithSearchPhrase(request.Query)
                          .WithPaging(request.Skip, request.Take)
                          .AddObjectIds(request.ObjectIds)
                          .AddSorting(request.Sort)
                          .WithIncludeFields(IndexFieldsMapper.MapToIndexIncludes(request.IncludeFields).ToArray());

            if (request.ObjectIds.IsNullOrEmpty())
            {
                //filter products only the store catalog and visibility status when search
                builder.AddTerms(new[] { "status:visible" });//Only visible, exclude variations from search result
                builder.AddTerms(new[] { $"__outline:{store.Catalog}" });
            }

            //Use predefined  facets for store  if the facet filter expression is not set
            if (responseGroup.HasFlag(ExpProductResponseGroup.LoadFacets))
            {
                var predefinedAggregations = await _aggregationConverter.GetAggregationRequestsAsync(new ProductIndexedSearchCriteria
                {
                    StoreId  = request.StoreId,
                    Currency = request.CurrencyCode,
                }, new FiltersContainer());

                builder.ParseFacets(_phraseParser, request.Facet, predefinedAggregations)
                .ApplyMultiSelectFacetSearch();
            }

            var searchRequest = builder.Build();
            var searchResult  = await _searchProvider.SearchAsync(KnownDocumentTypes.Product, searchRequest);

            var criteria = new ProductIndexedSearchCriteria
            {
                StoreId  = request.StoreId,
                Currency = request.CurrencyCode,
            };
            //TODO: move later to own implementation
            //Call the catalog aggregation converter service to convert AggregationResponse to proper Aggregation type (term, range, filter)
            var resultAggregations = await _aggregationConverter.ConvertAggregationsAsync(searchResult.Aggregations, criteria);

            searchRequest.SetAppliedAggregations(resultAggregations);

            var products = searchResult.Documents?.Select(x => _mapper.Map <ExpProduct>(x)).ToList() ?? new List <ExpProduct>();

            var result = new SearchProductResponse
            {
                Query = request,
                AllStoreCurrencies = allStoreCurrencies,
                Currency           = currency,
                Store      = store,
                Results    = products,
                Facets     = resultAggregations?.Select(x => _mapper.Map <FacetResult>(x)).ToList(),
                TotalCount = (int)searchResult.TotalCount
            };

            await _pipeline.Execute(result);

            return(result);
        }