Beispiel #1
0
        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());
        }
Beispiel #2
0
        protected virtual IList <Price> GetItemPrices(string[] itemIds)
        {
            var evalContext = new PriceEvaluationContext {
                ProductIds = itemIds
            };

            return(_pricingService.EvaluateProductPrices(evalContext).ToList());
        }
Beispiel #3
0
 public async Task Run(PriceEvaluationContext parameter, Func <PriceEvaluationContext, Task> next)
 {
     if (!string.IsNullOrEmpty(parameter.CustomerId))
     {
         await InnerSetShopperDataFromMember(parameter, parameter.CustomerId);
     }
     await next(parameter);
 }
Beispiel #4
0
        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);
        }
Beispiel #5
0
        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);
        }
Beispiel #6
0
        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);
        }
Beispiel #7
0
        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));
        }
Beispiel #9
0
        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());
        }
Beispiel #10
0
        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);
        }
Beispiel #12
0
        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));
        }
Beispiel #13
0
        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);
        }
Beispiel #15
0
        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);
        }
Beispiel #16
0
        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>());
 }
Beispiel #20
0
        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);
        }
Beispiel #21
0
 public IList <Pricelist> EvaluatePricesLists(PriceEvaluationContext evalContext, WorkContext workContext)
 {
     return(Task.Factory.StartNew(() => EvaluatePricesListsAsync(evalContext, workContext), CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default).Unwrap().GetAwaiter().GetResult());
 }
Beispiel #22
0
 public static pricingDto.PriceEvaluationContext ToPriceEvaluationContextDto(this PriceEvaluationContext evalContext)
 {
     return(PricingConverterInstance.ToPriceEvaluationContextDto(evalContext));
 }
Beispiel #23
0
 public virtual pricingDto.PriceEvaluationContext ToPriceEvaluationContextDto(PriceEvaluationContext evalContext)
 {
     return(evalContext.JsonConvert <pricingDto.PriceEvaluationContext>());
 }
Beispiel #24
0
 public IList <Pricelist> EvaluatePricesLists(PriceEvaluationContext evalContext, WorkContext workContext)
 {
     return(EvaluatePricesListsAsync(evalContext, workContext).GetAwaiter().GetResult());
 }
Beispiel #25
0
        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);
        }
Beispiel #27
0
        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);
        }
Beispiel #28
0
        public async Task <IActionResult> EvaluatePriceLists([FromBody] PriceEvaluationContext evalContext)
        {
            var retVal = (await _pricingService.EvaluatePriceListsAsync(evalContext)).ToArray();

            return(Ok(retVal));
        }
Beispiel #29
0
        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);
        }