public async Task <Product[]> GetProductsAsync(ProductSearchCriteria criteria) { var result = await _catalogModuleSearch.SearchProductsAsync(criteria.ToApiCriteria()); var products = new List <Product>(); if (!result.Items.IsNullOrEmpty()) { var evaluationContext = new PriceEvaluationContext { ProductIds = result.Items.Select(p => p.Id).ToArray() }; var prices = await _pricingModule.EvaluatePricesAsync(evaluationContext); products.AddRange( result .Items .Select( product => product.ToProduct( prices.FirstOrDefault(p => p.ProductId == product.Id) ) ) ); } return(products.ToArray()); }
protected virtual IList <Price> GetItemPrices(string[] itemIds) { var evalContext = new PriceEvaluationContext { ProductIds = itemIds }; return(_pricingService.EvaluateProductPrices(evalContext).ToList()); }
public async Task Run(PriceEvaluationContext parameter, Func <PriceEvaluationContext, Task> next) { if (!string.IsNullOrEmpty(parameter.CustomerId)) { await InnerSetShopperDataFromMember(parameter, parameter.CustomerId); } await next(parameter); }
public virtual IEnumerable <Price> FilterPrices(IEnumerable <Price> prices, PriceEvaluationContext evalContext) { if (prices == null) { throw new ArgumentNullException(nameof(prices)); } if (evalContext == null) { throw new ArgumentNullException(nameof(evalContext)); } // If no certain date is set, default to now, because that's probably the intention of the requester // and it's backwards compatible. var certainDate = evalContext.CertainDate ?? DateTime.UtcNow; var result = new List <Price>(); if (evalContext.ReturnAllMatchedPrices) { // Get all prices, ordered by currency and amount. result = prices.OrderBy(x => x.Currency).ThenBy(x => Math.Min(x.Sale ?? x.List, x.List)).ToList(); } else if (!evalContext.PricelistIds.IsNullOrEmpty()) { foreach (var productPrices in prices.GroupBy(x => x.ProductId)) { var priceListOrdererList = evalContext.PricelistIds.ToList(); // as priceListOrdererList is sorted by priority (descending), we save PricelistId's index as Priority var priceTuples = productPrices.Select(x => new { Price = x, x.Currency, x.MinQuantity, Priority = priceListOrdererList.IndexOf(x.PricelistId) }) .Where(x => x.Priority > -1); // Group by Currency and by MinQuantity foreach (var pricesGroupByCurrency in priceTuples.GroupBy(x => x.Currency)) { var minAcceptablePriority = int.MaxValue; // take prices with lower MinQuantity first foreach (var pricesGroupByMinQuantity in pricesGroupByCurrency.GroupBy(x => x.MinQuantity).OrderBy(x => x.Key)) { // Take start/end date most close to certainDate, because that price is more specific in time. // Take minimal price from most prioritized Pricelist. var groupAcceptablePrice = pricesGroupByMinQuantity .OrderBy(x => x.Priority) .ThenBy(x => certainDate.Subtract(x.Price.StartDate.GetValueOrDefault(DateTime.MinValue)).TotalSeconds) .ThenBy(x => x.Price.EndDate.GetValueOrDefault(DateTime.MaxValue).Subtract(certainDate).TotalSeconds) .ThenBy(x => Math.Min(x.Price.Sale ?? x.Price.List, x.Price.List)) .First(); if (minAcceptablePriority >= groupAcceptablePrice.Priority) { minAcceptablePriority = groupAcceptablePrice.Priority; result.Add(groupAcceptablePrice.Price); } } } } } return(result); }
public static bool TagsContains(this PriceEvaluationContext context, string tag) { var retVal = context.Tags != null; if (retVal) { retVal = context.Tags.Any(x => String.Equals(x, tag, StringComparison.InvariantCultureIgnoreCase)); } return(retVal); }
public async Task Run(PriceEvaluationContext parameter, Func <PriceEvaluationContext, Task> next) { var cartAggregate = await _cartAggregateRepository.GetCartAsync("default", parameter.StoreId, parameter.CustomerId, parameter.Language, parameter.Currency); if (cartAggregate != null) { _mapper.Map(cartAggregate, parameter); } await next(parameter); }
public async Task Can_return_prices_from_many_pricelists_by_priority() { RegisterTypes(); var evalContext = new PriceEvaluationContext { ProductIds = new[] { "ProductId" }, PricelistIds = new[] { "Pricelist 1", "Pricelist 2", "Pricelist 3" } }; var mockPrices = new Common.TestAsyncEnumerable <PriceEntity>(new List <PriceEntity> { new PriceEntity { List = 10, MinQuantity = 2, PricelistId = "Pricelist 1", Id = "1", ProductId = "ProductId" }, new PriceEntity { List = 9, MinQuantity = 1, PricelistId = "Pricelist 2", Id = "2", ProductId = "ProductId" }, new PriceEntity { List = 10, MinQuantity = 2, PricelistId = "Pricelist 2", Id = "3", ProductId = "ProductId" }, new PriceEntity { List = 6, MinQuantity = 2, PricelistId = "Pricelist 3", Id = "4", ProductId = "ProductId" }, new PriceEntity { List = 5, MinQuantity = 3, PricelistId = "Pricelist 3", Id = "5", ProductId = "ProductId" } }); var pricingService = GetPricingService(() => GetPricingRepositoryMock(mockPrices)); var prices = (await pricingService.EvaluateProductPricesAsync(evalContext)).ToArray(); // only 2 prices (from higher priority pricelists) returned, but not for MinQuantity == 3 Assert.Equal(2, prices.Length); //Assert.Equal(mockPrices[1].Id, prices[0].Id); //Assert.Equal(mockPrices[0].Id, prices[1].Id); Assert.DoesNotContain(prices, x => x.MinQuantity == 3); // Pricelist priority changed evalContext.PricelistIds = new[] { "Pricelist 3", "Pricelist 2", "Pricelist 1" }; prices = (await pricingService.EvaluateProductPricesAsync(evalContext)).ToArray(); // 3 prices returned, but not from "Pricelist 1" Assert.Equal(3, prices.Length); //Assert.Equal(mockPrices[1].Id, prices[0].Id); //Assert.Equal(mockPrices[3].Id, prices[1].Id); //Assert.Equal(mockPrices[4].Id, prices[2].Id); Assert.DoesNotContain(prices, x => x.PricelistId == "Pricelist 1"); }
/// <summary> /// Evaluate pricelists for special context. All resulting pricelists ordered by priority /// </summary> /// <param name="evalContext"></param> /// <returns></returns> public virtual async Task <IEnumerable <Pricelist> > EvaluatePriceListsAsync(PriceEvaluationContext evalContext) { var cacheKey = CacheKey.With(GetType(), nameof(EvaluatePriceListsAsync)); var priceListAssignments = await _platformMemoryCache.GetOrCreateExclusiveAsync(cacheKey, async cacheEntry => { cacheEntry.AddExpirationToken(PricingCacheRegion.CreateChangeToken()); return(await GetAllPricelistAssignments()); }); var query = priceListAssignments.AsQueryable(); if (evalContext.CatalogId != null) { //filter by catalog query = query.Where(x => x.CatalogId == evalContext.CatalogId); } if (evalContext.Currency != null) { //filter by currency query = query.Where(x => x.Pricelist.Currency == evalContext.Currency.ToString()); } if (evalContext.CertainDate != null) { //filter by date expiration query = query.Where(x => (x.StartDate == null || evalContext.CertainDate >= x.StartDate) && (x.EndDate == null || x.EndDate >= evalContext.CertainDate)); } var assignments = query.AsNoTracking().ToArray(); var assignmentsToReturn = assignments.Where(x => x.DynamicExpression == null).ToList(); foreach (var assignment in assignments.Where(x => x.DynamicExpression != null)) { try { if (assignment.DynamicExpression.IsSatisfiedBy(evalContext) && assignmentsToReturn.All(x => x.PricelistId != assignment.PricelistId)) { assignmentsToReturn.Add(assignment); } } catch (Exception ex) { _logger.LogError(ex, "Failed to evaluate price assignment condition."); } } return(assignmentsToReturn.OrderByDescending(x => x.Priority).ThenByDescending(x => x.Name).Select(x => x.Pricelist)); }
public async Task Can_return_pricelists() { RegisterTypes(); var evalContext = new PriceEvaluationContext { ProductIds = new[] { "4ed55441810a47da88a483e5a1ee4e94" } }; var pricingService = GetPricingService(GetPricingRepository); var priceLists = await pricingService.EvaluatePriceListsAsync(evalContext); Assert.True(priceLists.Any()); var prices = await pricingService.EvaluateProductPricesAsync(evalContext); Assert.True(prices.Any()); }
public void Can_return_prices_from_many_pricelists_by_priority() { var evalContext = new PriceEvaluationContext { ProductIds = new[] { "ProductId" }, PricelistIds = new[] { "Pricelist 1", "Pricelist 2", "Pricelist 3" } }; var mockPrices = new[] { new Price { Id = "1", List = 10, MinQuantity = 2, PricelistId = "Pricelist 1", ProductId = "ProductId" }, new Price { Id = "2", List = 9, MinQuantity = 1, PricelistId = "Pricelist 2", ProductId = "ProductId" }, new Price { Id = "3", List = 10, MinQuantity = 2, PricelistId = "Pricelist 2", ProductId = "ProductId" }, new Price { Id = "4", List = 6, MinQuantity = 2, PricelistId = "Pricelist 3", ProductId = "ProductId" }, new Price { Id = "5", List = 5, MinQuantity = 3, PricelistId = "Pricelist 3", ProductId = "ProductId" } }; var prices = new DefaultPricingPriorityFilterPolicy().FilterPrices(mockPrices, evalContext).ToArray(); // only 2 prices (from higher priority pricelists) returned, but not for MinQuantity == 3 Assert.Equal(2, prices.Length); Assert.Equal(mockPrices[1].Id, prices[0].Id); Assert.Equal(mockPrices[0].Id, prices[1].Id); Assert.DoesNotContain(prices, x => x.MinQuantity == 3); // Pricelist priority changed evalContext.PricelistIds = new[] { "Pricelist 3", "Pricelist 2", "Pricelist 1" }; prices = new DefaultPricingPriorityFilterPolicy().FilterPrices(mockPrices, evalContext).ToArray(); // 3 prices returned, but not from "Pricelist 1" Assert.Equal(3, prices.Length); Assert.Equal(mockPrices[1].Id, prices[0].Id); Assert.Equal(mockPrices[3].Id, prices[1].Id); Assert.Equal(mockPrices[4].Id, prices[2].Id); Assert.DoesNotContain(prices, x => x.PricelistId == "Pricelist 1"); }
public static PriceEvaluationContext ToPriceEvaluationContext(this WorkContext workContext, IEnumerable <Pricelist> pricelists, IEnumerable <Product> products = null) { if (workContext == null) { throw new ArgumentNullException(nameof(workContext)); } //Evaluate products prices var result = new PriceEvaluationContext { CatalogId = workContext.CurrentStore.Catalog, Language = workContext.CurrentLanguage.CultureName, StoreId = workContext.CurrentStore.Id }; if (workContext.CurrentUser != null) { result.CustomerId = workContext.CurrentUser.Id; var contact = workContext.CurrentUser?.Contact; if (contact != null) { result.GeoTimeZone = contact.TimeZone; var address = contact.DefaultShippingAddress ?? contact.DefaultBillingAddress; if (address != null) { result.GeoCity = address.City; result.GeoCountry = address.CountryCode; result.GeoState = address.RegionName; result.GeoZipCode = address.PostalCode; } if (contact.UserGroups != null) { result.UserGroups = contact.UserGroups; } } } if (pricelists != null) { result.PricelistIds = pricelists.Select(p => p.Id).ToList(); } if (products != null) { result.ProductIds = products.Select(p => p.Id).ToList(); } return(result); }
public async Task TestExpressionDeserialization() { // Arrange var serializedConditionTree = (await ReadTextFromEmbeddedResourceAsync("Resources.TestSerializedCondition.json"))?.Trim(); RegisterTypes(); // Act var result = JsonConvert.DeserializeObject <PriceConditionTree>(serializedConditionTree, new ConditionJsonConverter()); // Assert // NOTE: Since we have no way to explore result function and assert that it does expected checks, // let's just feed it with some evaluation contexts and check if it returns expected results. // The function should return true if any of these conditions is true: // - the customer searched in stores for something containing 'test' string; // - the customer is male; // - the customer is at least 18 years old. // 1. Context does not match the expression at all, so the function must return false. var context = new PriceEvaluationContext() { ShopperSearchedPhraseInStore = "some query", ShopperGender = "female", ShopperAge = 17 }; Assert.False(result.IsSatisfiedBy(context)); // 2. ShopperSearchedPhraseInStore contains "test", so the result must be true. context.ShopperSearchedPhraseInStore = "test"; Assert.True(result.IsSatisfiedBy(context)); // 3. ShopperGender is male, so the result must be true again. context.ShopperSearchedPhraseInStore = "some query"; context.ShopperGender = "male"; Assert.True(result.IsSatisfiedBy(context)); // 4. ShopperAge exceeds 18, so the result must be true again. context.ShopperGender = "female"; context.ShopperAge = 18; Assert.True(result.IsSatisfiedBy(context)); context.ShopperAge = 21; Assert.True(result.IsSatisfiedBy(context)); }
public virtual async Task <IList <Pricelist> > EvaluatePricesListsAsync(PriceEvaluationContext evalContext, WorkContext workContext) { if (evalContext == null) { throw new ArgumentNullException(nameof(evalContext)); } if (workContext == null) { throw new ArgumentNullException(nameof(workContext)); } var cacheKey = CacheKey.With(GetType(), "EvaluatePricesListsAsync", evalContext.GetCacheKey()); return(await _memoryCache.GetOrCreateExclusiveAsync(cacheKey, async (cacheEntry) => { cacheEntry.AddExpirationToken(PricingCacheRegion.CreateChangeToken()); cacheEntry.AddExpirationToken(_apiChangesWatcher.CreateChangeToken()); return (await _pricingApi.EvaluatePriceListsAsync(evalContext.ToPriceEvaluationContextDto())).Select(x => x.ToPricelist(workContext.AllCurrencies, workContext.CurrentLanguage)).ToList(); })); }
public static PriceEvaluationContext ToServiceModel(this IEnumerable <Product> products, WorkContext workContext) { if (products == null) { throw new ArgumentNullException("products"); } //Evaluate products prices var retVal = new PriceEvaluationContext { ProductIds = products.Select(p => p.Id).ToList(), PricelistIds = workContext.CurrentPricelists.Select(p => p.Id).ToList(), CatalogId = workContext.CurrentStore.Catalog, CustomerId = workContext.CurrentCustomer.Id, Language = workContext.CurrentLanguage.CultureName, CertainDate = workContext.StorefrontUtcNow, StoreId = workContext.CurrentStore.Id }; return(retVal); }
public virtual PriceEvaluationContext ToPriceEvaluationContext(WorkContext workContext, IEnumerable <Product> products = null) { //Evaluate products prices var retVal = new PriceEvaluationContext { PricelistIds = workContext.CurrentPricelists.Select(p => p.Id).ToList(), CatalogId = workContext.CurrentStore.Catalog, Language = workContext.CurrentLanguage.CultureName, StoreId = workContext.CurrentStore.Id }; if (workContext.CurrentUser != null) { retVal.CustomerId = workContext.CurrentUser.Id; var contact = workContext.CurrentUser?.Contact?.Value; if (contact != null) { retVal.GeoTimeZone = contact.TimeZone; var address = contact.DefaultShippingAddress ?? contact.DefaultBillingAddress; if (address != null) { retVal.GeoCity = address.City; retVal.GeoCountry = address.CountryCode; retVal.GeoState = address.RegionName; retVal.GeoZipCode = address.PostalCode; } if (contact.UserGroups != null) { retVal.UserGroups = contact.UserGroups; } } } if (products != null) { retVal.ProductIds = products.Select(p => p.Id).ToList(); } return(retVal); }
public void DoExport(Stream outStream, CsvExportInfo exportInfo, Action <ExportImportProgressInfo> progressCallback) { var prodgressInfo = new ExportImportProgressInfo { Description = "loading products..." }; var streamWriter = new StreamWriter(outStream, Encoding.UTF8, 1024, true) { AutoFlush = true }; using (var csvWriter = new CsvWriter(streamWriter)) { //Notification progressCallback(prodgressInfo); //Load all products to export var products = LoadProducts(exportInfo.CatalogId, exportInfo.CategoryIds, exportInfo.ProductIds); var allProductIds = products.Select(x => x.Id).ToArray(); //Load prices for products prodgressInfo.Description = "loading prices..."; progressCallback(prodgressInfo); var priceEvalContext = new PriceEvaluationContext { ProductIds = allProductIds, PricelistIds = exportInfo.PriceListId == null ? null : new[] { exportInfo.PriceListId }, Currency = exportInfo.Currency }; var allProductPrices = _pricingService.EvaluateProductPrices(priceEvalContext).ToList(); //Load inventories prodgressInfo.Description = "loading inventory information..."; progressCallback(prodgressInfo); var allProductInventories = _inventoryService.GetProductsInventoryInfos(allProductIds).Where(x => exportInfo.FulfilmentCenterId == null || x.FulfillmentCenterId == exportInfo.FulfilmentCenterId).ToList(); //Export configuration exportInfo.Configuration.PropertyCsvColumns = products.SelectMany(x => x.PropertyValues).Select(x => x.PropertyName).Distinct().ToArray(); csvWriter.Configuration.Delimiter = exportInfo.Configuration.Delimiter; csvWriter.Configuration.RegisterClassMap(new CsvProductMap(exportInfo.Configuration)); //Write header csvWriter.WriteHeader <CsvProduct>(); csvWriter.NextRecord(); prodgressInfo.TotalCount = products.Count; var notifyProductSizeLimit = 50; var counter = 0; //convert to dict for faster search var pricesDict = allProductPrices.GroupBy(x => x.ProductId).ToDictionary(x => x.Key, x => x.First()); var inventoriesDict = allProductInventories.GroupBy(x => x.ProductId).ToDictionary(x => x.Key, x => x.First()); foreach (var product in products) { try { var csvProducts = MakeMultipleExportProducts(product, pricesDict, inventoriesDict); csvWriter.WriteRecords(csvProducts); } catch (Exception ex) { prodgressInfo.Errors.Add(ex.ToString()); progressCallback(prodgressInfo); } //Raise notification each notifyProductSizeLimit products counter++; prodgressInfo.ProcessedCount = counter; prodgressInfo.Description = string.Format("{0} of {1} products processed", prodgressInfo.ProcessedCount, prodgressInfo.TotalCount); if (counter % notifyProductSizeLimit == 0 || counter == prodgressInfo.TotalCount) { progressCallback(prodgressInfo); } } } }
public IEnumerable <Price> EvaluateProductPrices(PriceEvaluationContext evalContext) { return(_pricingService.EvaluateProductPrices(evalContext)); }
public IEnumerable <Pricelist> EvaluatePriceLists(PriceEvaluationContext evalContext) { return(_pricingService.EvaluatePriceLists(evalContext)); }
public static pricingDto.PriceEvaluationContext ToPriceEvaluationContextDto(this PriceEvaluationContext evalContext) { return(evalContext.JsonConvert <pricingDto.PriceEvaluationContext>()); }
public void Can_return_price_from_many_prices_with_start_and_end_date() { var evalContext = new PriceEvaluationContext { ProductIds = new[] { "ProductId" }, PricelistIds = new[] { "List1" } }; var mockPrices = new PriceEntity[] { // Unbounded past. new PriceEntity { Id = "1", List = 1, EndDate = new DateTime(2018, 09, 10), PricelistId = "List1", ProductId = "ProductId" }, // Bounded past. new PriceEntity { Id = "2", List = 2, StartDate = new DateTime(2018, 09, 15), EndDate = new DateTime(2018, 09, 17), PricelistId = "List1", ProductId = "ProductId" }, // Bounded future. new PriceEntity { Id = "3", List = 3, StartDate = new DateTime(2018, 09, 26), EndDate = new DateTime(2018, 09, 29), PricelistId = "List1", ProductId = "ProductId" }, // Unbounded future. new PriceEntity { Id = "4", List = 4, StartDate = new DateTime(2018, 10, 1), PricelistId = "List1", ProductId = "ProductId" }, // Default unfiltered price. new PriceEntity { Id = "10", List = 10, PricelistId = "List1", ProductId = "ProductId" }, }.AsQueryable().BuildMock(); var mockRepository = new Mock <IPricingRepository>(); mockRepository.SetupGet(x => x.Prices).Returns(mockPrices.Object); var service = new PricingServiceImpl(() => mockRepository.Object, null, null, null, null, new DefaultPricingPriorityFilterPolicy()); // Eval with date and no matches, this should result in default price. evalContext.CertainDate = new DateTime(2018, 09, 20); var prices = service.EvaluateProductPricesAsync(evalContext).GetAwaiter().GetResult(); Assert.Equal(10, prices.Single().List); // Eval with date falling in bounded future. evalContext.CertainDate = new DateTime(2018, 09, 27); prices = service.EvaluateProductPricesAsync(evalContext).GetAwaiter().GetResult(); Assert.Equal(3, prices.Single().List); // Eval with date falling in unbounded future. evalContext.CertainDate = new DateTime(2118, 10, 2); prices = service.EvaluateProductPricesAsync(evalContext).GetAwaiter().GetResult(); Assert.Equal(4, prices.Single().List); // Eval with date falling in bounded past. evalContext.CertainDate = new DateTime(2018, 9, 16); prices = service.EvaluateProductPricesAsync(evalContext).GetAwaiter().GetResult(); Assert.Equal(2, prices.Single().List); // Eval with date falling in unbounded past. evalContext.CertainDate = new DateTime(2018, 8, 1); prices = service.EvaluateProductPricesAsync(evalContext).GetAwaiter().GetResult(); Assert.Equal(1, prices.Single().List); // Eval with current date, should result in unbounded future price. evalContext.CertainDate = DateTime.UtcNow; prices = service.EvaluateProductPricesAsync(evalContext).GetAwaiter().GetResult(); Assert.Equal(4, prices.Single().List); // Eval without date, should result in unbounded future price. // This is also a backwards compatibilty test. // CertainDate was not used in previous price evaluation. Default to 'now' behaviour. evalContext.CertainDate = null; prices = service.EvaluateProductPricesAsync(evalContext).GetAwaiter().GetResult(); Assert.Equal(4, prices.Single().List); }
public IList <Pricelist> EvaluatePricesLists(PriceEvaluationContext evalContext, WorkContext workContext) { return(Task.Factory.StartNew(() => EvaluatePricesListsAsync(evalContext, workContext), CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default).Unwrap().GetAwaiter().GetResult()); }
public static pricingDto.PriceEvaluationContext ToPriceEvaluationContextDto(this PriceEvaluationContext evalContext) { return(PricingConverterInstance.ToPriceEvaluationContextDto(evalContext)); }
public virtual pricingDto.PriceEvaluationContext ToPriceEvaluationContextDto(PriceEvaluationContext evalContext) { return(evalContext.JsonConvert <pricingDto.PriceEvaluationContext>()); }
public IList <Pricelist> EvaluatePricesLists(PriceEvaluationContext evalContext, WorkContext workContext) { return(EvaluatePricesListsAsync(evalContext, workContext).GetAwaiter().GetResult()); }
public virtual void DoExport(string catalogId, string[] exportedCategories, string[] exportedProducts, string pricelistId, string fulfilmentCenterId, CurrencyCodes currency, string languageCode, ExportNotification notification) { var memoryStream = new MemoryStream(); var streamWriter = new StreamWriter(memoryStream); streamWriter.AutoFlush = true; var productPropertyInfos = typeof(CatalogProduct).GetProperties(BindingFlags.Instance | BindingFlags.Public); string catalogName = null; using (var csvWriter = new CsvWriter(streamWriter)) { csvWriter.Configuration.Delimiter = ";"; //Notification notification.Description = "loading products..."; _notifier.Upsert(notification); try { //Load all products to export var products = LoadProducts(catalogId, exportedCategories, exportedProducts); //Notification notification.Description = "loading prices..."; _notifier.Upsert(notification); var allProductIds = products.Select(x => x.Id).ToArray(); //Load prices for products var priceEvalContext = new PriceEvaluationContext { ProductIds = allProductIds, PricelistIds = pricelistId == null ? null : new string[] { pricelistId }, Currency = currency }; var allProductPrices = _pricingService.EvaluateProductPrices(priceEvalContext).ToArray(); foreach (var product in products) { product.Prices = allProductPrices.Where(x => x.ProductId == product.Id).ToList(); } //Load inventories notification.Description = "loading inventory information..."; _notifier.Upsert(notification); var allProductInventories = _inventoryService.GetProductsInventoryInfos(allProductIds); foreach (var product in products) { product.Inventories = allProductInventories.Where(x => x.ProductId == product.Id) .Where(x => fulfilmentCenterId == null ? true : x.FulfillmentCenterId == fulfilmentCenterId).ToList(); } notification.TotalCount = products.Count(); //populate export configuration Dictionary <string, Func <CatalogProduct, string> > exportConfiguration = new Dictionary <string, Func <CatalogProduct, string> >(); PopulateProductExportConfiguration(exportConfiguration, products); //Write header foreach (var cfgItem in exportConfiguration) { csvWriter.WriteField(cfgItem.Key); } csvWriter.NextRecord(); var notifyProductSizeLimit = 50; var counter = 0; //Write products foreach (var product in products) { if (catalogName == null && product.Catalog != null) { catalogName = product.Catalog.Name; } try { foreach (var cfgItem in exportConfiguration) { var fieldValue = String.Empty; if (cfgItem.Value == null) { var propertyInfo = productPropertyInfos.FirstOrDefault(x => x.Name == cfgItem.Key); if (propertyInfo != null) { var objValue = propertyInfo.GetValue(product); fieldValue = objValue != null?objValue.ToString() : fieldValue; } } else { fieldValue = cfgItem.Value(product); } csvWriter.WriteField(fieldValue); } csvWriter.NextRecord(); } catch (Exception ex) { notification.ErrorCount++; notification.Errors.Add(ex.ToString()); _notifier.Upsert(notification); } //Raise notification each notifyProductSizeLimit products counter++; notification.ProcessedCount = counter; notification.Description = string.Format("{0} of {1} products processed", notification.ProcessedCount, notification.TotalCount); if (counter % notifyProductSizeLimit == 0) { _notifier.Upsert(notification); } } memoryStream.Position = 0; //Upload result csv to blob storage var uploadInfo = new UploadStreamInfo { FileName = "Catalog-" + (catalogName ?? catalogId) + "-export.csv", FileByteStream = memoryStream, FolderName = "temp" }; var blobKey = _blobStorageProvider.Upload(uploadInfo); //Get a download url notification.DownloadUrl = _blobUrlResolver.GetAbsoluteUrl(blobKey); notification.Description = "Export finished"; } catch (Exception ex) { notification.Description = "Export error"; notification.ErrorCount++; notification.Errors.Add(ex.ToString()); } finally { notification.Finished = DateTime.UtcNow; _notifier.Upsert(notification); } } }
/// <summary> /// Evaluation product prices. /// Will get either all prices or one price per currency depending on the settings in evalContext. /// </summary> /// <param name="evalContext"></param> /// <returns></returns> public virtual async Task <IEnumerable <Price> > EvaluateProductPricesAsync(PriceEvaluationContext evalContext) { if (evalContext == null) { throw new ArgumentNullException(nameof(evalContext)); } if (evalContext.ProductIds == null) { throw new MissingFieldException("ProductIds"); } var retVal = new List <Price>(); Price[] prices; using (var repository = _repositoryFactory()) { //Get a price range satisfying by passing context var query = repository.Prices.Include(x => x.Pricelist) .Where(x => evalContext.ProductIds.Contains(x.ProductId)) .Where(x => evalContext.Quantity >= x.MinQuantity || evalContext.Quantity == 0); if (evalContext.PricelistIds.IsNullOrEmpty()) { evalContext.PricelistIds = (await EvaluatePriceListsAsync(evalContext)).Select(x => x.Id).ToArray(); } query = query.Where(x => evalContext.PricelistIds.Contains(x.PricelistId)); var queryResult = await query.ToArrayAsync(); prices = queryResult.Select(x => x.ToModel(AbstractTypeFactory <Price> .TryCreateInstance())).ToArray(); } var priceListOrdererList = evalContext.PricelistIds?.ToList(); foreach (var productId in evalContext.ProductIds) { var productPrices = prices.Where(x => x.ProductId == productId); if (evalContext.ReturnAllMatchedPrices) { // Get all prices, ordered by currency and amount. var orderedPrices = productPrices.OrderBy(x => x.Currency).ThenBy(x => Math.Min(x.Sale ?? x.List, x.List)); retVal.AddRange(orderedPrices); } else if (!priceListOrdererList.IsNullOrEmpty()) { // as priceListOrdererList is sorted by priority (descending), we save PricelistId's index as Priority var priceTuples = productPrices .Select(x => new { Price = x, x.Currency, x.MinQuantity, Priority = priceListOrdererList.IndexOf(x.PricelistId) }) .Where(x => x.Priority > -1); // Group by Currency and by MinQuantity foreach (var pricesGroupByCurrency in priceTuples.GroupBy(x => x.Currency)) { var minAcceptablePriority = int.MaxValue; // take prices with lower MinQuantity first foreach (var pricesGroupByMinQuantity in pricesGroupByCurrency.GroupBy(x => x.MinQuantity).OrderBy(x => x.Key)) { // take minimal price from most prioritized Pricelist var groupAcceptablePrice = pricesGroupByMinQuantity.OrderBy(x => x.Priority) .ThenBy(x => Math.Min(x.Price.Sale ?? x.Price.List, x.Price.List)) .First(); if (minAcceptablePriority >= groupAcceptablePrice.Priority) { minAcceptablePriority = groupAcceptablePrice.Priority; retVal.Add(groupAcceptablePrice.Price); } } } } } //Then variation inherited prices if (_productService != null) { var productIdsWithoutPrice = evalContext.ProductIds.Except(retVal.Select(x => x.ProductId).Distinct()).ToArray(); //Variation price inheritance //Need find products without price it may be a variation without implicitly price defined and try to get price from main product if (productIdsWithoutPrice.Any()) { var variations = (await _productService.GetByIdsAsync(productIdsWithoutPrice, ItemResponseGroup.ItemInfo.ToString())).Where(x => x.MainProductId != null).ToList(); evalContext.ProductIds = variations.Select(x => x.MainProductId).Distinct().ToArray(); var inheritedPrices = await EvaluateProductPricesAsync(evalContext); foreach (var inheritedPrice in inheritedPrices) { foreach (var variation in variations.Where(x => x.MainProductId == inheritedPrice.ProductId)) { var jObject = JObject.FromObject(inheritedPrice); var variationPrice = (Price)jObject.ToObject(inheritedPrice.GetType()); //For correct override price in possible update variationPrice.Id = null; variationPrice.ProductId = variation.Id; retVal.Add(variationPrice); } } } } return(retVal); }
public override async Task Invoke(IOwinContext context) { if (IsStorefrontRequest(context.Request)) { var workContext = _container.Resolve <WorkContext>(); var linkListService = _container.Resolve <IMenuLinkListService>(); var cartBuilder = _container.Resolve <ICartBuilder>(); var catalogSearchService = _container.Resolve <ICatalogSearchService>(); // Initialize common properties workContext.RequestUrl = context.Request.Uri; workContext.AllCountries = _allCountries; workContext.AllStores = await _cacheManager.GetAsync("GetAllStores", "ApiRegion", async() => await GetAllStoresAsync()); if (workContext.AllStores != null && workContext.AllStores.Any()) { // Initialize request specific properties workContext.CurrentStore = GetStore(context, workContext.AllStores); workContext.CurrentLanguage = GetLanguage(context, workContext.AllStores, workContext.CurrentStore); workContext.AllCurrencies = await _cacheManager.GetAsync("GetAllCurrencies-" + workContext.CurrentLanguage.CultureName, "ApiRegion", async() => { return((await _commerceApi.CommerceGetAllCurrenciesAsync()).Select(x => x.ToWebModel(workContext.CurrentLanguage)).ToArray()); }); //Sync store currencies with avail in system foreach (var store in workContext.AllStores) { store.SyncCurrencies(workContext.AllCurrencies, workContext.CurrentLanguage); store.CurrentSeoInfo = store.SeoInfos.FirstOrDefault(x => x.Language == workContext.CurrentLanguage); } //Set current currency workContext.CurrentCurrency = GetCurrency(context, workContext.CurrentStore); var qs = HttpUtility.ParseQueryString(workContext.RequestUrl.Query); //Initialize catalog search criteria workContext.CurrentCatalogSearchCriteria = new CatalogSearchCriteria(workContext.CurrentLanguage, workContext.CurrentCurrency, qs) { CatalogId = workContext.CurrentStore.Catalog }; //This line make delay categories loading initialization (categories can be evaluated on view rendering time) workContext.Categories = new MutablePagedList <Category>((pageNumber, pageSize) => { var criteria = workContext.CurrentCatalogSearchCriteria.Clone(); criteria.PageNumber = pageNumber; criteria.PageSize = pageSize; var result = catalogSearchService.SearchCategories(criteria); foreach (var category in result) { category.Products = new MutablePagedList <Product>((pageNumber2, pageSize2) => { criteria.CategoryId = category.Id; criteria.PageNumber = pageNumber2; criteria.PageSize = pageSize2; var searchResult = catalogSearchService.SearchProducts(criteria); //Because catalog search products returns also aggregations we can use it to populate workContext using C# closure //now workContext.Aggregation will be contains preloaded aggregations for current category workContext.Aggregations = new MutablePagedList <Aggregation>(searchResult.Aggregations); return(searchResult.Products); }); } return(result); }); //This line make delay products loading initialization (products can be evaluated on view rendering time) workContext.Products = new MutablePagedList <Product>((pageNumber, pageSize) => { var criteria = workContext.CurrentCatalogSearchCriteria.Clone(); criteria.PageNumber = pageNumber; criteria.PageSize = pageSize; var result = catalogSearchService.SearchProducts(criteria); //Prevent double api request for get aggregations //Because catalog search products returns also aggregations we can use it to populate workContext using C# closure //now workContext.Aggregation will be contains preloaded aggregations for current search criteria workContext.Aggregations = new MutablePagedList <Aggregation>(result.Aggregations); return(result.Products); }); //This line make delay aggregation loading initialization (aggregation can be evaluated on view rendering time) workContext.Aggregations = new MutablePagedList <Aggregation>((pageNumber, pageSize) => { var criteria = workContext.CurrentCatalogSearchCriteria.Clone(); criteria.PageNumber = pageNumber; criteria.PageSize = pageSize; //Force to load products and its also populate workContext.Aggregations by preloaded values workContext.Products.Slice(pageNumber, pageSize); return(workContext.Aggregations); }); workContext.CurrentOrderSearchCriteria = new Model.Order.OrderSearchCriteria(qs); workContext.CurrentQuoteSearchCriteria = new Model.Quote.QuoteSearchCriteria(qs); //Get current customer workContext.CurrentCustomer = await GetCustomerAsync(context); //Validate that current customer has to store access ValidateUserStoreLogin(context, workContext.CurrentCustomer, workContext.CurrentStore); MaintainAnonymousCustomerCookie(context, workContext); // Gets the collection of external login providers var externalAuthTypes = context.Authentication.GetExternalAuthenticationTypes(); workContext.ExternalLoginProviders = externalAuthTypes.Select(at => new LoginProvider { AuthenticationType = at.AuthenticationType, Caption = at.Caption, Properties = at.Properties }).ToList(); workContext.ApplicationSettings = GetApplicationSettings(); //Do not load shopping cart and other for resource requests if (!IsAssetRequest(context.Request)) { //Shopping cart await cartBuilder.GetOrCreateNewTransientCartAsync(workContext.CurrentStore, workContext.CurrentCustomer, workContext.CurrentLanguage, workContext.CurrentCurrency); workContext.CurrentCart = cartBuilder.Cart; if (workContext.CurrentStore.QuotesEnabled) { await _quoteRequestBuilder.GetOrCreateNewTransientQuoteRequestAsync(workContext.CurrentStore, workContext.CurrentCustomer, workContext.CurrentLanguage, workContext.CurrentCurrency); workContext.CurrentQuoteRequest = _quoteRequestBuilder.QuoteRequest; } var linkLists = await _cacheManager.GetAsync("GetAllStoreLinkLists-" + workContext.CurrentStore.Id, "ApiRegion", async() => await linkListService.LoadAllStoreLinkListsAsync(workContext.CurrentStore.Id)); workContext.CurrentLinkLists = linkLists.Where(x => x.Language == workContext.CurrentLanguage).ToList(); // load all static content var staticContents = _cacheManager.Get(string.Join(":", "AllStoreStaticContent", workContext.CurrentStore.Id), "ContentRegion", () => { var allContentItems = _staticContentService.LoadStoreStaticContent(workContext.CurrentStore).ToList(); var blogs = allContentItems.OfType <Blog>().ToArray(); var blogArticlesGroup = allContentItems.OfType <BlogArticle>().GroupBy(x => x.BlogName, x => x).ToList(); foreach (var blog in blogs) { var blogArticles = blogArticlesGroup.FirstOrDefault(x => string.Equals(x.Key, blog.Name, StringComparison.OrdinalIgnoreCase)); if (blogArticles != null) { blog.Articles = new MutablePagedList <BlogArticle>(blogArticles); } } return(new { Pages = allContentItems, Blogs = blogs }); }); workContext.Pages = new MutablePagedList <ContentItem>(staticContents.Pages.Where(x => x.Language.IsInvariant || x.Language == workContext.CurrentLanguage)); workContext.Blogs = new MutablePagedList <Blog>(staticContents.Blogs.Where(x => x.Language.IsInvariant || x.Language == workContext.CurrentLanguage)); // Initialize blogs search criteria workContext.CurrentBlogSearchCritera = new BlogSearchCriteria(qs); //Pricelists var pricelistCacheKey = string.Join("-", "EvaluatePriceLists", workContext.CurrentStore.Id, workContext.CurrentCustomer.Id); workContext.CurrentPricelists = await _cacheManager.GetAsync(pricelistCacheKey, "ApiRegion", async() => { var evalContext = new PriceEvaluationContext { StoreId = workContext.CurrentStore.Id, CatalogId = workContext.CurrentStore.Catalog, CustomerId = workContext.CurrentCustomer.Id, Quantity = 1 }; var pricingResult = await _pricingModuleApi.PricingModuleEvaluatePriceListsAsync(evalContext); return(pricingResult.Select(p => p.ToWebModel()).ToList()); }); } } } await Next.Invoke(context); }
public async Task <IActionResult> EvaluatePriceLists([FromBody] PriceEvaluationContext evalContext) { var retVal = (await _pricingService.EvaluatePriceListsAsync(evalContext)).ToArray(); return(Ok(retVal)); }
public IHttpActionResult EvaluatePriceLists(PriceEvaluationContext evalContext) { var retVal = _pricingService.EvaluatePriceLists(evalContext).ToArray(); return(Ok(retVal)); }
/// <summary> /// Evaluation product prices. /// Will get either all prices or one price per currency depending on the settings in evalContext. /// </summary> /// <param name="evalContext"></param> /// <returns></returns> public virtual async Task <IEnumerable <Price> > EvaluateProductPricesAsync(PriceEvaluationContext evalContext) { if (evalContext == null) { throw new ArgumentNullException(nameof(evalContext)); } if (evalContext.ProductIds == null) { throw new MissingFieldException("ProductIds"); } var result = new List <Price>(); Price[] prices; using (var repository = _repositoryFactory()) { //Get a price range satisfying by passing context var query = repository.Prices.Include(x => x.Pricelist) .Where(x => evalContext.ProductIds.Contains(x.ProductId)) .Where(x => evalContext.Quantity >= x.MinQuantity || evalContext.Quantity == 0); if (evalContext.PricelistIds.IsNullOrEmpty()) { evalContext.PricelistIds = (await EvaluatePriceListsAsync(evalContext)).Select(x => x.Id).ToArray(); } query = query.Where(x => evalContext.PricelistIds.Contains(x.PricelistId)); // Filter by date expiration // Always filter on date, so that we limit the results to process. var certainDate = evalContext.CertainDate ?? DateTime.UtcNow; query = query.Where(x => (x.StartDate == null || x.StartDate <= certainDate) && (x.EndDate == null || x.EndDate > certainDate)); var queryResult = await query.AsNoTracking().ToArrayAsync(); prices = queryResult.Select(x => x.ToModel(AbstractTypeFactory <Price> .TryCreateInstance())).ToArray(); } //Apply pricing filtration strategy for found prices result.AddRange(_pricingPriorityFilterPolicy.FilterPrices(prices, evalContext)); //Then variation inherited prices if (_productService != null) { var productIdsWithoutPrice = evalContext.ProductIds.Except(result.Select(x => x.ProductId).Distinct()).ToArray(); //Try to inherit prices for variations from their main product //Need find products without price it may be a variation without implicitly price defined and try to get price from main product if (productIdsWithoutPrice.Any()) { var variations = (await _productService.GetByIdsAsync(productIdsWithoutPrice, ItemResponseGroup.ItemInfo.ToString())).Where(x => x.MainProductId != null).ToList(); evalContext = evalContext.Clone() as PriceEvaluationContext; evalContext.ProductIds = variations.Select(x => x.MainProductId).Distinct().ToArray(); if (!evalContext.ProductIds.IsNullOrEmpty()) { var inheritedPrices = await EvaluateProductPricesAsync(evalContext); foreach (var inheritedPrice in inheritedPrices) { foreach (var variation in variations.Where(x => x.MainProductId == inheritedPrice.ProductId)) { var variationPrice = inheritedPrice.Clone() as Price; //Reset id for correct override price in possible update variationPrice.Id = null; variationPrice.ProductId = variation.Id; result.Add(variationPrice); } } } } } return(result); }