public CartOffer ProcessCart(int profileId, string shippingCountryCode, string promoCode = "", int testOfferRuleId = 0) { promoCode = promoCode.ToLower().Trim(); CartOffer cartOffer = new CartOffer(); var cartRules = _offerBuilder.GetActiveOfferRulesByType(OfferRuleType.Cart); if (testOfferRuleId > 0) { var testRule = _offerBuilder.PrepareTestOfferRule(testOfferRuleId); cartRules = _offerBuilder.CloneActiveOfferRulesByType(OfferRuleType.Cart); cartRules.Remove(testRule); cartRules.Add(testRule); cartRules = cartRules.OrderBy(x => x.Priority).ToList(); } if (cartRules.Count > 0) { // Get all items by profile ID var items = _cartItemRepository.Table .Where(x => x.ProfileId == profileId) .ToList() .Select(x => _cartItemBuilder.Build(x)) .ToList(); // Do a basket cleanup items = _cartUtility.RemoveFreeItemWithOffers(items, cartRules.Select(x => x.Id).ToArray()); for (int i = 0; i < cartRules.Count; i++) { // Check status and dates // In theory, all inactive / expired rules will not be loaded from database if ((cartRules[i].IsActive) && ((cartRules[i].StartDate.HasValue == false || cartRules[i].StartDate.Value.CompareTo(DateTime.Now) <= 0) && (cartRules[i].EndDate.HasValue == false || cartRules[i].EndDate.Value.CompareTo(DateTime.Now) >= 0))) { bool proceed = true; if (profileId != 0 && !string.IsNullOrEmpty(promoCode) && promoCode.CompareTo(cartRules[i].PromoCode.ToLower()) == 0) { int orderQuantity = _cartUtility.GetNumberOfPaidOrders(promoCode, profileId); // Check promocode and uses per customer // value <= 0 will be considered as proceed proceed = cartRules[i].UsesPerCustomer <= 0 || orderQuantity < cartRules[i].UsesPerCustomer; if (cartRules[i].NewCustomerOnly && orderQuantity > 0) { proceed = false; } } // Set html message from this offer if (string.IsNullOrEmpty(cartRules[i].PromoCode) == false && cartRules[i].PromoCode.ToLower().CompareTo(promoCode) == 0) { cartOffer.Description = cartRules[i].HtmlMessage; } // If entered promocode is empty and promocode from offer is not empty, do not proceed if (string.IsNullOrEmpty(cartRules[i].PromoCode) == false && string.IsNullOrEmpty(promoCode)) { proceed = false; } bool conditionMatched = false; if (proceed) { conditionMatched = _cartUtility.ProcessCartCondition(items, cartRules[i].Condition, cartRules[i].OfferedItemIncluded, cartRules[i].UseInitialPrice); if (conditionMatched) { // If cart rule does not have promocode, proceed // If cart rule does not match the given promocode, do not proceed if (string.IsNullOrEmpty(cartRules[i].PromoCode) || cartRules[i].PromoCode.ToLower().CompareTo(promoCode) == 0) { // Run action here cartOffer = _cartUtility.ProcessCartOfferAction(profileId, items, shippingCountryCode, cartRules[i], cartRules[i].Action, cartOffer); } } if (!cartRules[i].ProceedForNext) { break; } } } } // Update all cart items in database at the end of the loop. foreach (var item in items) { if (item.Id > 0) { _cartItemRepository.Update(item); } else { _cartItemRepository.Create(item); } } } return(cartOffer); }
public CartOffer ProcessCartOfferAction(int profileId, IList <CartItem> items, string shippingCountryCode, OfferRule offerRule, OfferAction action, CartOffer cartOffer) { if (action == null) { return(cartOffer); } List <CartItem> list; decimal matchedAmount = 0M; decimal discount = 0M; int steps = 0; Country country = _countryRepository.Table.Where(x => x.ISO3166Code == shippingCountryCode).FirstOrDefault(); cartOffer.FreeDelivery = action.FreeShipping; cartOffer.RewardPoint += action.RewardPoint; switch (action.OfferActionAttributeId) { #region FIXED_PERCENT_DISCOUNT_FOR_PRODUCT_SUBSELECTION case FIXED_PERCENT_DISCOUNT_FOR_PRODUCT_SUBSELECTION: // Run acton condition first if (offerRule.Action.Condition != null && offerRule.Action.Condition.ChildOfferConditions.Count > 0) { matchedAmount = CalculateActionMatchedAmount(items, offerRule, action.Condition, country.IsEC); } else // Else use condition from offer rule.. { matchedAmount = CalculateActionMatchedAmount(items, offerRule, offerRule.Condition, country.IsEC); } discount = matchedAmount / 100M * action.DiscountAmount.Value; cartOffer.Discounts.Add(offerRule.Id, discount); cartOffer.DiscountAmount += discount; cartOffer.IsValid = true; break; #endregion #region FIXED_PERCENT_DISCOUNT_FOR_WHOLE_CART case FIXED_PERCENT_DISCOUNT_FOR_WHOLE_CART: if (country.IsEC) { discount = items.Select(x => x.ProductPrice.OfferPriceInclTax * x.Quantity) .DefaultIfEmpty(0).Sum() * Convert.ToInt32(action.DiscountAmount.Value) / 100; } else { discount = items.Select(x => x.ProductPrice.OfferPriceExclTax * x.Quantity) .DefaultIfEmpty(0).Sum() * Convert.ToInt32(action.DiscountAmount.Value) / 100; } cartOffer.DiscountAmount += discount; cartOffer.Discounts.Add(offerRule.Id, discount); cartOffer.IsValid = true; break; #endregion #region FIXED_AMOUNT_DISCOUNT_FOR_WHOLE_CART case FIXED_AMOUNT_DISCOUNT_FOR_WHOLE_CART: cartOffer.DiscountAmount += action.DiscountAmount.Value; cartOffer.Discounts.Add(offerRule.Id, action.DiscountAmount.Value); cartOffer.IsValid = true; break; #endregion #region FREE_ITEM_OFFER case FREE_ITEM_OFFER: bool proceed = false; if (action.MinimumAmount.HasValue) { if (action.Condition == null) { proceed = true; } else { proceed = CalculateActionMatchedAmount(items, offerRule, action.Condition, country.IsEC) > action.MinimumAmount.Value; } } else { proceed = true; } int qtyStep = 1; int qtyFound = 1; if (action.DiscountQtyStep.HasValue && proceed) { // To calculate how many free items need to be added qtyStep = action.DiscountQtyStep.Value; if (action.Condition == null || action.Condition.ChildOfferConditions.Count == 0) { qtyFound = CalculateActionMatchedQty(items, offerRule, offerRule.Condition); } else { qtyFound = CalculateActionMatchedQty(items, offerRule, action.Condition); } proceed = qtyFound > 0; } bool isByOption = action.OptionOperator != null; var foundList = new List <CartItem>(); // If there is an action condition if (offerRule.Action.Condition != null && offerRule.Action.Condition.ChildOfferConditions.Count > 0) { foundList = GetActionMatchedItems(items, offerRule, action.Condition); } else // Else use condition from offer rule { foundList = GetActionMatchedItems(items, offerRule, offerRule.Condition); } list = new List <CartItem>(); if (isByOption) { for (int i = 0; i < foundList.Count; i++) { if (foundList[i].ProductPrice.OfferRuleId <= 0) { string targetOption = foundList[i].ProductPrice.Option; bool isOkay = _attributeUtility.ValidateAttribute(targetOption, action.OptionOperator.Operator, action.OptionOperand); if (isOkay) { list.Add(foundList[i]); } } } if (list.Count > 0) { proceed = true; } } else { list = foundList; } if (proceed) { // TODO: Do we need to prevent other process? // Flag item with offer rule id to prevent other process //FlagOfferRuleIdInBasket(action.OfferRuleId, list.Select(x => x.Id).ToList()); // Add free item if (action.FreeProductItself.Value) { for (int i = 0; i < list.Count; i++) { if (list[i].Product == null) { _logger.InsertLog(LogLevel.Warning, string.Format("Free item could not be loaded. Product ID={{{0}}}", list[i].ProductId)); break; } if (list[i].ProductPrice == null) { _logger.InsertLog(LogLevel.Warning, string.Format("Product price for free item could not be loaded. Product Price ID={{{0}}}", list[i].ProductPriceId)); break; } if (list[i].Product.Brand == null) { _logger.InsertLog(LogLevel.Warning, string.Format("Brand for free item could not be loaded. Brand ID={{{0}}}", list[i].Product.BrandId)); break; } var message = _cartValidator.ValidateCartItem( profileId, list[i].ProductId, list[i].Product.BrandId, shippingCountryCode, list[i].Product.IsPharmaceutical, list[i].Product.Discontinued, list[i].ProductPrice.Stock, list[i].Product.Name, list[i].Product.EnforceStockCount, list[i].Product.Brand.EnforceStockCount, list[i].Product.Enabled, list[i].ProductPrice.Enabled, list[i].Quantity, list[i].Quantity / qtyStep, list[i].Product.StepQuantity, list[i].Product.IsPhoneOrder, list[i].ProductPrice.OfferPriceExclTax, allowZeroPrice: true); // Empty message is ok status from cart item validation if (string.IsNullOrEmpty(message)) { var cartItem = new CartItem { ProfileId = profileId, ProductPriceId = list[i].ProductPriceId, ProductId = list[i].ProductId, Quantity = list[i].Quantity / qtyStep, Product = list[i].Product, ProductPrice = list[i].ProductPrice, CartItemMode = (int)CartItemMode.FreeItem }; items.Add(cartItem); } } } else { // Load free item var freeItem = _productBuilder.Build(action.FreeProductId.Value); if (freeItem == null) { _logger.InsertLog(LogLevel.Warning, string.Format("Free item could not be loaded. Product ID={{{0}}}", action.FreeProductId.Value)); break; } var freeItemPriceOption = freeItem.ProductPrices.Where(x => x.Id == action.FreeProductPriceId).FirstOrDefault(); if (freeItemPriceOption == null) { _logger.InsertLog(LogLevel.Warning, string.Format("Product price for free item could not be loaded. Product Price ID={{{0}}}", action.FreeProductPriceId)); break; } if (freeItem.Brand == null) { _logger.InsertLog(LogLevel.Warning, string.Format("Brand for free item could not be loaded. Brand ID={{{0}}}", freeItem.BrandId)); break; } // While stock lasts if (freeItemPriceOption.Stock > 0) { var message = _cartValidator.ValidateCartItem( profileId, freeItem.Id, freeItem.BrandId, shippingCountryCode, freeItem.IsPharmaceutical, freeItem.Discontinued, freeItemPriceOption.Stock, freeItem.Name, freeItem.EnforceStockCount, freeItem.Brand.EnforceStockCount, freeItem.Enabled, freeItemPriceOption.Enabled, 0, action.FreeProductQty.Value * (qtyFound / qtyStep), freeItem.StepQuantity, freeItem.IsPhoneOrder, freeItemPriceOption.OfferPriceExclTax, allowZeroPrice: true); // Empty message is ok status from cart item validation if (string.IsNullOrEmpty(message)) { var cartItem = new CartItem { ProfileId = profileId, ProductPriceId = freeItemPriceOption.Id, ProductId = freeItem.Id, Quantity = action.FreeProductQty.Value * (qtyFound / qtyStep), Product = freeItem, ProductPrice = freeItemPriceOption, CartItemMode = (int)CartItemMode.FreeItem }; items.Add(cartItem); } } } } cartOffer.IsValid = true; break; #endregion #region DO_NOTHING case DO_NOTHING: // do nothing cartOffer.IsValid = true; break; #endregion #region BUY_X_QUANTITY_FOR_Y_QUANTITY case BUY_X_QUANTITY_FOR_Y_QUANTITY: // Must have X quantity // Must have Y quantity // X > Y if (offerRule.Action.XValue.HasValue && offerRule.Action.YValue.HasValue && offerRule.Action.XValue > offerRule.Action.YValue) { list = new List <CartItem>(); if (offerRule.Action.Condition != null && offerRule.Action.Condition.ChildOfferConditions.Count > 0) // If there is condition for action { list = GetActionMatchedItems(items, offerRule, action.Condition); } else // Else use condition from offer rule.. { list = GetActionMatchedItems(items, offerRule, offerRule.Condition); } // Revert to old price if necessary if (offerRule.UseInitialPrice) { SetToUseInitialPrice(list); } // List down individual price List <decimal> listIndividualPrice = new List <decimal>(); for (int i = 0; i < list.Count; i++) { for (int j = 0; j < list[i].Quantity; j++) { if (country.IsEC) { listIndividualPrice.Add(list[i].ProductPrice.OfferPriceInclTax); } else { listIndividualPrice.Add(list[i].ProductPrice.OfferPriceExclTax); } } } // Calculate how many steps for X quantity steps = listIndividualPrice.Count / offerRule.Action.XValue.Value; // Find quantity X of total value decimal totalXValue = 0M; for (int i = 0; i < offerRule.Action.XValue.Value; i++) { if (i < listIndividualPrice.Count) { totalXValue += listIndividualPrice[i]; } } // Find quantity Y of total value decimal totalYValue = 0M; for (int i = 0; i < offerRule.Action.YValue.Value; i++) { if (i < listIndividualPrice.Count) { totalYValue += listIndividualPrice[i]; } } // Get the difference decimal differenceValue = totalXValue - totalYValue; discount = steps * differenceValue; cartOffer.DiscountAmount += discount; cartOffer.Discounts.Add(offerRule.Id, discount); } cartOffer.IsValid = true; break; #endregion #region BUY_X_QUANTITY_FOR_Y_PRICE case BUY_X_QUANTITY_FOR_Y_PRICE: // Must have X quantity // Must have Y price if (offerRule.Action.XValue.HasValue && offerRule.Action.YValue.HasValue) { list = new List <CartItem>(); if (offerRule.Action.Condition != null && offerRule.Action.Condition.ChildOfferConditions.Count > 0) // If there is condition for action { list = GetActionMatchedItems(items, offerRule, action.Condition); } else // Else use condition from offer rule.. { list = GetActionMatchedItems(items, offerRule, offerRule.Condition); } // Revert to old price if necessary if (offerRule.UseInitialPrice) { SetToUseInitialPrice(list); } // List down individual price List <decimal> listIndividualPrice = new List <decimal>(); decimal totalValue = 0M; for (int i = 0; i < list.Count; i++) { for (int j = 0; j < list[i].Quantity; j++) { if (country.IsEC) { listIndividualPrice.Add(list[i].ProductPrice.OfferPriceInclTax); totalValue += list[i].ProductPrice.OfferPriceInclTax; } else { listIndividualPrice.Add(list[i].ProductPrice.OfferPriceExclTax); totalValue += list[i].ProductPrice.OfferPriceExclTax; } } } // Calculate how many steps for X quantity steps = listIndividualPrice.Count / offerRule.Action.XValue.Value; // Find total value int lastRemoveCount = listIndividualPrice.Count % offerRule.Action.XValue.Value; if (lastRemoveCount != 0) { for (int i = 0; i < lastRemoveCount; i++) { totalValue -= listIndividualPrice[listIndividualPrice.Count - 1 - i]; } } // Find total value of Y in current currency decimal totalYValue = steps * (offerRule.Action.YValue.Value); // Get the difference discount = totalValue - totalYValue; cartOffer.DiscountAmount += discount; cartOffer.Discounts.Add(offerRule.Id, discount); } cartOffer.IsValid = true; break; #endregion #region BUY_X_QUANTITY_CHEAPEST_FREE case BUY_X_QUANTITY_CHEAPEST_FREE: // Must have X quantity if (offerRule.Action.XValue.HasValue) { list = new List <CartItem>(); if (offerRule.Action.Condition != null && offerRule.Action.Condition.ChildOfferConditions.Count > 0) // If there is condition for action { list = GetActionMatchedItems(items, offerRule, action.Condition); } else // Else use condition from offer rule.. { list = GetActionMatchedItems(items, offerRule, offerRule.Condition); } // List down individual price List <decimal> listIndividualPrice = new List <decimal>(); decimal totalValue = 0M; for (int i = 0; i < list.Count; i++) { for (int j = 0; j < list[i].Quantity; j++) { if (country.IsEC) { listIndividualPrice.Add(list[i].ProductPrice.OfferPriceInclTax); totalValue += list[i].ProductPrice.OfferPriceInclTax; } else { listIndividualPrice.Add(list[i].ProductPrice.OfferPriceExclTax); totalValue += list[i].ProductPrice.OfferPriceExclTax; } } } // Calculate how many steps for X quantity steps = listIndividualPrice.Count / offerRule.Action.XValue.Value; // Get the smallest price listIndividualPrice.Sort(delegate(decimal price1, decimal price2) { return(price1.CompareTo(price2)); }); for (int i = 0; i < steps; i++) { discount += listIndividualPrice[i]; } cartOffer.DiscountAmount += discount; cartOffer.Discounts.Add(offerRule.Id, discount); } cartOffer.IsValid = true; break; #endregion } cartOffer.DiscountAmount = Math.Round(cartOffer.DiscountAmount, 2, MidpointRounding.AwayFromZero); return(cartOffer); }