public void Can_calculate_rental_periods_for_days()
        {
            var product = new Product
            {
                IsRental = true,
                RentalPricePeriod = RentalPricePeriod.Days
            };

            //rental period length = 1 day
            product.RentalPriceLength = 1;
            //the same date
            product.GetRentalPeriods(new DateTime(2014, 3, 5), new DateTime(2014, 3, 5)).ShouldEqual(1);
            //1 day
            product.GetRentalPeriods(new DateTime(2014, 3, 5), new DateTime(2014, 3, 6)).ShouldEqual(1);
            //2 days
            product.GetRentalPeriods(new DateTime(2014, 3, 5), new DateTime(2014, 3, 7)).ShouldEqual(2);
            //3 days
            product.GetRentalPeriods(new DateTime(2014, 3, 5), new DateTime(2014, 3, 8)).ShouldEqual(3);

            //rental period length = 2 days
            product.RentalPriceLength = 2;
            //the same date
            product.GetRentalPeriods(new DateTime(2014, 3, 5), new DateTime(2014, 3, 5)).ShouldEqual(1);
            //1 day
            product.GetRentalPeriods(new DateTime(2014, 3, 5), new DateTime(2014, 3, 6)).ShouldEqual(1);
            //2 days
            product.GetRentalPeriods(new DateTime(2014, 3, 5), new DateTime(2014, 3, 7)).ShouldEqual(1);
            //3 days
            product.GetRentalPeriods(new DateTime(2014, 3, 5), new DateTime(2014, 3, 8)).ShouldEqual(2);
        }
        /// <summary>
        /// Gets the final price
        /// </summary>
        /// <param name="product">Product</param>
        /// <param name="customer">The customer</param>
        /// <param name="overriddenProductPrice">Overridden product price. If specified, then it'll be used instead of a product price. For example, used with product attribute combinations</param>
        /// <param name="additionalCharge">Additional charge</param>
        /// <param name="includeDiscounts">A value indicating whether include discounts or not for final price computation</param>
        /// <param name="quantity">Shopping cart item quantity</param>
        /// <param name="rentalStartDate">Rental period start date (for rental products)</param>
        /// <param name="rentalEndDate">Rental period end date (for rental products)</param>
        /// <param name="discountAmount">Applied discount amount</param>
        /// <param name="appliedDiscounts">Applied discounts</param>
        /// <returns>Final price</returns>
        public virtual decimal GetFinalPrice(Product product, 
            Customer customer,
            decimal? overriddenProductPrice,
            decimal additionalCharge, 
            bool includeDiscounts,
            int quantity,
            DateTime? rentalStartDate,
            DateTime? rentalEndDate,
            out decimal discountAmount,
            out List<Discount> appliedDiscounts)
        {
            if (product == null)
                throw new ArgumentNullException("product");

            discountAmount = decimal.Zero;
            appliedDiscounts = new List<Discount>();

            var cacheKey = string.Format(PriceCacheEventConsumer.PRODUCT_PRICE_MODEL_KEY,
                product.Id,
                overriddenProductPrice.HasValue ? overriddenProductPrice.Value.ToString(CultureInfo.InvariantCulture) : null,
                additionalCharge.ToString(CultureInfo.InvariantCulture),
                includeDiscounts, 
                quantity,
                string.Join(",", customer.GetCustomerRoleIds()),
                _storeContext.CurrentStore.Id);
             var cacheTime = _catalogSettings.CacheProductPrices ? 60 : 0;
            //we do not cache price for rental products
            //otherwise, it can cause memory leaks (to store all possible date period combinations)
            if (product.IsRental)
                cacheTime = 0;
            var cachedPrice = _cacheManager.Get(cacheKey, cacheTime, () =>
            {
                var result = new ProductPriceForCaching();

                //initial price
                decimal price = overriddenProductPrice.HasValue ? overriddenProductPrice.Value : product.Price;

                //special price
                var specialPrice = product.GetSpecialPrice();
                if (specialPrice.HasValue)
                    price = specialPrice.Value;

                //tier prices
                if (product.HasTierPrices)
                {
                    decimal? tierPrice = GetMinimumTierPrice(product, customer, quantity);
                    if (tierPrice.HasValue)
                        price = Math.Min(price, tierPrice.Value);
                }

                //additional charge
                price = price + additionalCharge;

                //rental products
                if (product.IsRental)
                    if (rentalStartDate.HasValue && rentalEndDate.HasValue)
                        price = price * product.GetRentalPeriods(rentalStartDate.Value, rentalEndDate.Value);

                if (includeDiscounts)
                {
                    //discount
                    List<Discount> tmpAppliedDiscounts;
                    decimal tmpDiscountAmount = GetDiscountAmount(product, customer, price, out tmpAppliedDiscounts);
                    price = price - tmpDiscountAmount;

                    if (tmpAppliedDiscounts != null)
                    {
                        result.AppliedDiscountIds = tmpAppliedDiscounts.Select(x=>x.Id).ToList();
                        result.AppliedDiscountAmount = tmpDiscountAmount;
                    }
                }

                if (price < decimal.Zero)
                    price = decimal.Zero;

                result.Price = price;
                return result;
            });

            if (includeDiscounts)
            {
                //Discount instance cannnot be cached between requests (when "catalogSettings.CacheProductPrices" is "true)
                //This is limitation of Entity Framework
                //That's why we load it here after working with cache
                foreach (var appliedDiscountId in cachedPrice.AppliedDiscountIds)
                {
                    var appliedDiscount = _discountService.GetDiscountById(appliedDiscountId);
                    if (appliedDiscount != null)
                        appliedDiscounts.Add(appliedDiscount);
                }
                if (appliedDiscounts.Any())
                {
                    discountAmount = cachedPrice.AppliedDiscountAmount;
                }
            }

            return cachedPrice.Price;
        }
        public void Can_calculate_rental_periods_for_months()
        {
            var product = new Product
            {
                IsRental = true,
                RentalPricePeriod = RentalPricePeriod.Months
            };

            //rental period length = 1 month
            product.RentalPriceLength = 1;
            //the same date
            product.GetRentalPeriods(new DateTime(2014, 3, 5), new DateTime(2014, 3, 5)).ShouldEqual(1);
            //several days but less than a month
            product.GetRentalPeriods(new DateTime(2014, 3, 5), new DateTime(2014, 3, 4)).ShouldEqual(1);
            //1 month
            product.GetRentalPeriods(new DateTime(2014, 3, 5), new DateTime(2014, 4, 5)).ShouldEqual(1);
            //1 month and 1 day
            product.GetRentalPeriods(new DateTime(2014, 3, 5), new DateTime(2014, 4, 6)).ShouldEqual(2);
            //several days but less than two months
            product.GetRentalPeriods(new DateTime(2014, 3, 5), new DateTime(2014, 4, 13)).ShouldEqual(2);
            //2 months
            product.GetRentalPeriods(new DateTime(2014, 3, 5), new DateTime(2014, 5, 5)).ShouldEqual(2);
            //3 months
            product.GetRentalPeriods(new DateTime(2014, 3, 5), new DateTime(2014, 5, 8)).ShouldEqual(3);
            //several more unit tests
            product.GetRentalPeriods(new DateTime(1900, 1, 1), new DateTime(1900, 1, 1)).ShouldEqual(1);
            product.GetRentalPeriods(new DateTime(1900, 1, 1), new DateTime(1900, 1, 2)).ShouldEqual(1);
            product.GetRentalPeriods(new DateTime(1900, 1, 2), new DateTime(1900, 1, 1)).ShouldEqual(1);
            product.GetRentalPeriods(new DateTime(1900, 1, 1), new DateTime(1900, 2, 1)).ShouldEqual(1);
            product.GetRentalPeriods(new DateTime(1900, 2, 1), new DateTime(1900, 1, 1)).ShouldEqual(1);
            product.GetRentalPeriods(new DateTime(1900, 1, 31), new DateTime(1900, 2, 1)).ShouldEqual(1);
            product.GetRentalPeriods(new DateTime(1900, 8, 31), new DateTime(1900, 9, 30)).ShouldEqual(1);
            product.GetRentalPeriods(new DateTime(1900, 8, 31), new DateTime(1900, 10, 1)).ShouldEqual(2);
            product.GetRentalPeriods(new DateTime(1900, 1, 1), new DateTime(1901, 1, 1)).ShouldEqual(12);
            product.GetRentalPeriods(new DateTime(1900, 1, 1), new DateTime(1911, 1, 1)).ShouldEqual(132);
            product.GetRentalPeriods(new DateTime(1900, 8, 31), new DateTime(1901, 8, 30)).ShouldEqual(12);

            //rental period length = 2 months
            product.RentalPriceLength = 2;
            //the same date
            product.GetRentalPeriods(new DateTime(2014, 3, 5), new DateTime(2014, 3, 5)).ShouldEqual(1);
            //several days but less than a month
            product.GetRentalPeriods(new DateTime(2014, 3, 5), new DateTime(2014, 3, 4)).ShouldEqual(1);
            //1 month
            product.GetRentalPeriods(new DateTime(2014, 3, 5), new DateTime(2014, 4, 5)).ShouldEqual(1);
            //several days but less than two months
            product.GetRentalPeriods(new DateTime(2014, 3, 5), new DateTime(2014, 4, 13)).ShouldEqual(1);
            //2 months
            product.GetRentalPeriods(new DateTime(2014, 3, 5), new DateTime(2014, 5, 5)).ShouldEqual(1);
            //3 months
            product.GetRentalPeriods(new DateTime(2014, 3, 5), new DateTime(2014, 5, 8)).ShouldEqual(2);
        }
        public void Can_calculate_rental_periods_for_years()
        {
            var product = new Product
            {
                IsRental = true,
                RentalPricePeriod = RentalPricePeriod.Years
            };

            //rental period length = 1 years
            product.RentalPriceLength = 1;
            //the same date
            product.GetRentalPeriods(new DateTime(2014, 3, 5), new DateTime(2014, 3, 5)).ShouldEqual(1);
            //several days but less than a year
            product.GetRentalPeriods(new DateTime(2014, 3, 5), new DateTime(2015, 1, 1)).ShouldEqual(1);
            //more than one year
            product.GetRentalPeriods(new DateTime(2014, 3, 5), new DateTime(2015, 3, 7)).ShouldEqual(2);

            //rental period length = 2 years
            product.RentalPriceLength = 2;
            //the same date
            product.GetRentalPeriods(new DateTime(2014, 3, 5), new DateTime(2014, 3, 5)).ShouldEqual(1);
            //several days but less than a year
            product.GetRentalPeriods(new DateTime(2014, 3, 5), new DateTime(2015, 1, 1)).ShouldEqual(1);
            //more than one year
            product.GetRentalPeriods(new DateTime(2014, 3, 5), new DateTime(2015, 3, 7)).ShouldEqual(1);
            //more than two year
            product.GetRentalPeriods(new DateTime(2014, 3, 5), new DateTime(2016, 3, 7)).ShouldEqual(2);
        }
        public void Can_calculate_rental_periods_for_weeks()
        {
            var product = new Product
            {
                IsRental = true,
                RentalPricePeriod = RentalPricePeriod.Weeks
            };

            //rental period length = 1 week
            product.RentalPriceLength = 1;
            //the same date
            product.GetRentalPeriods(new DateTime(2014, 3, 5), new DateTime(2014, 3, 5)).ShouldEqual(1);
            //several days but less than a week
            product.GetRentalPeriods(new DateTime(2014, 3, 5), new DateTime(2014, 3, 3)).ShouldEqual(1);
            //1 week
            product.GetRentalPeriods(new DateTime(2014, 3, 5), new DateTime(2014, 3, 12)).ShouldEqual(1);
            //several days but less than two weeks
            product.GetRentalPeriods(new DateTime(2014, 3, 5), new DateTime(2014, 3, 13)).ShouldEqual(2);
            //2 weeks
            product.GetRentalPeriods(new DateTime(2014, 3, 5), new DateTime(2014, 3, 19)).ShouldEqual(2);
            //3 weeks
            product.GetRentalPeriods(new DateTime(2014, 3, 5), new DateTime(2014, 3, 26)).ShouldEqual(3);

            //rental period length = 2 weeks
            product.RentalPriceLength = 2;
            //the same date
            product.GetRentalPeriods(new DateTime(2014, 3, 5), new DateTime(2014, 3, 5)).ShouldEqual(1);
            //several days but less than a week
            product.GetRentalPeriods(new DateTime(2014, 3, 5), new DateTime(2014, 3, 3)).ShouldEqual(1);
            //1 week
            product.GetRentalPeriods(new DateTime(2014, 3, 5), new DateTime(2014, 3, 12)).ShouldEqual(1);
            //several days but less than two weeks
            product.GetRentalPeriods(new DateTime(2014, 3, 5), new DateTime(2014, 3, 13)).ShouldEqual(1);
            //2 weeks
            product.GetRentalPeriods(new DateTime(2014, 3, 5), new DateTime(2014, 3, 19)).ShouldEqual(1);
            //3 weeks
            product.GetRentalPeriods(new DateTime(2014, 3, 5), new DateTime(2014, 3, 26)).ShouldEqual(2);
        }