/// <summary> /// Returns the next available index for an item that can be combined with the current /// </summary> /// <param name="currentIndex">The index of the current item to match to.</param> /// <returns>Index of the next item that can combine with the current, or -1 if not found.</returns> private int FindNextIndexForCombine(int currentIndex) { // THE CURRENT INDEX MUST BE WITHIN THE BOUNDS OF THE COLLECTION if (currentIndex < 0 || currentIndex > this.Count) { throw new ArgumentOutOfRangeException("currentIndex"); } // STARTING WITH THE CURRENT INDEX, MOVE FORWARD IN THE // COLLECTION LOOKING FOR AN ITEM THAT COULD COMBINE WITH THIS ONE BasketItem currentItem = this[currentIndex]; for (int nextIndex = currentIndex + 1; nextIndex < this.Count; nextIndex++) { BasketItem nextItem = this[nextIndex]; if (currentItem.CanCombine(nextItem, true)) { return(nextIndex); } } // NO MATCH FOUND return(-1); }
/// <summary> /// Recalculates all items in the basket, for example price and kit member products /// </summary> internal void Recalculate() { // BUILD A LIST OF ANY CHILD (GENERATED) PRODUCTS List <int> childItemIds = new List <int>(); List <BasketItem> childProducts = new List <BasketItem>(); foreach (BasketItem item in this) { if (item.OrderItemType == OrderItemType.Product && item.IsChildItem) { childItemIds.Add(item.BasketItemId); childProducts.Add(item); } } // MOVE THROUGH THE COLLECTION AND REMOVE ANY ITEMS ASSOCIATED WITH A CHILD ITEM for (int i = this.Count - 1; i >= 0; i--) { BasketItem item = this[i]; List <int> childPath = item.GetPath(); if (IsChildItemInPath(childItemIds, childPath)) { if (item.OrderItemType == OrderItemType.Product) { this.RemoveAt(i); } else { this.DeleteAt(i); } } } // LOOP EACH REMAINING ITEM AND RECALCULATE int currentCount = this.Count; for (int i = 0; i < currentCount; i++) { BasketItem basketItem = this[i]; Basket basket = basketItem.Basket; int userId = (basket == null ? Token.Instance.UserId : basket.UserId); // WE ONLY NEED TO CHECK PRODUCTS, NON-PRODUCT ITEMS HAVE NO RECALCULATION TASKS if (basketItem.OrderItemType == OrderItemType.Product) { // IF WE HAVE A KIT, WE MUST REFRESH ANY CONFIGURED KIT OPTIONS bool isKit = (basketItem.Product.KitStatus == KitStatus.Master); if (isKit) { basketItem.KitList = basketItem.Product.Kit.RefreshKitProducts(basketItem.KitList); } // RECALCULATE THE STARTING SKU/PRICE/WEIGHT FOR THIS ITEM ProductCalculator pcalc = ProductCalculator.LoadForProduct(basketItem.ProductId, basketItem.Quantity, basketItem.OptionList, basketItem.KitList, userId); basketItem.Sku = pcalc.Sku; basketItem.Weight = pcalc.Weight; if (isKit || !basketItem.Product.UseVariablePrice) { // KITS AND NONVARIABLE PRICED PRODUCTS MUST HAVE PRICE RECALCULATED basketItem.Price = pcalc.Price; } basketItem.Save(); // REGENERATE THE KIT ITEMS FOR THIS ITEM if (basket != null && isKit) { // OBTAIN THE KIT PRODUCTS THAT ARE SELECTED RATHER THAN INCLUDED int[] kitProductIds = AlwaysConvert.ToIntArray(basketItem.KitList); if (kitProductIds != null && kitProductIds.Length > 0) { //keep track of the price/weight of the master line item //decrement these values for each line item registered LSDecimal masterPrice = basketItem.Price; LSDecimal masterWeight = basketItem.Weight; foreach (int kitProductId in kitProductIds) { KitProduct kitProduct = KitProductDataSource.Load(kitProductId); if (kitProduct != null && kitProduct.KitComponent.InputType != KitInputType.IncludedHidden) { // WE WANT TO GENERATE BASKET RECORDS FOR ALL ITEMS *EXCEPT* INCLUDED HIDDEN ITEMS // INCLUDED HIDDEN ITEMS ARE TREATED AS PART OF THE MAIN PRODUCT AND ARE NOT GENERATED // UNTIL THE ORDER IS FINALIZED Product product = kitProduct.Product; BasketItem searchItem = new BasketItem(); searchItem.BasketId = basket.BasketId; searchItem.OrderItemType = OrderItemType.Product; searchItem.ParentItemId = basketItem.BasketItemId; searchItem.ProductId = product.ProductId; searchItem.OptionList = kitProduct.OptionList; searchItem.BasketShipmentId = basketItem.BasketShipmentId; // LOOK FOR ITEM BasketItem childItem = FindChildProduct(childProducts, searchItem); // UPDATE CALCULATED PROPERTIES childItem.Name = kitProduct.DisplayName; childItem.Quantity = (short)(kitProduct.Quantity * basketItem.Quantity); childItem.TaxCodeId = product.TaxCodeId; childItem.Shippable = product.Shippable; childItem.Price = kitProduct.CalculatedPrice / kitProduct.Quantity;; childItem.Weight = kitProduct.CalculatedWeight / kitProduct.Quantity; // CALCULATE SKU ProductCalculator childCalc = ProductCalculator.LoadForProduct(childItem.ProductId, childItem.Quantity, childItem.OptionList, childItem.KitList, basket.UserId); childItem.Sku = childCalc.Sku; basket.Items.Add(childItem); childItem.Save(); masterPrice -= kitProduct.CalculatedPrice; masterWeight -= kitProduct.CalculatedWeight; } } // UPDATE MASTER PRICE, FACTORING IN CHILD ITEMS basketItem.Price = masterPrice; basketItem.Weight = masterWeight; basketItem.Save(); } } } } // DELETE ANY CHILD PRODUCTS THAT WERE NOT PRESERVED foreach (BasketItem bi in childProducts) { bi.Delete(); } }
/// <summary> /// Combines any items in the collection that have equivalent data. /// <param name="save">Flag indicating whether or not to persist changes.</param> /// </summary> public void Combine(bool save) { // WE NEED TO TRACK PARENT ITEMS THAT ARE COMBINED Dictionary <int, int> combineMapping = new Dictionary <int, int>(); // LOOP THE BASKET ITEM COLLECTION AND COMBINE ROOT LEVEL PRODUCTS for (int thisIndex = 0; thisIndex < this.Count; thisIndex++) { BasketItem thisItem = this[thisIndex]; // ONLY CHECK ROOT LEVEL PRODUCTS if (thisItem.OrderItemType == OrderItemType.Product && !thisItem.IsChildItem) { // SEE IF WE HAVE ANY MATCHES TO COMBINE int matchIndex = FindNextIndexForCombine(thisIndex); while (matchIndex > -1) { BasketItem matchItem = this[matchIndex]; // TRACK NEW PARENT MAPPING FOR CHILD ITEMS if (matchItem.BasketItemId > 0) { combineMapping[matchItem.BasketItemId] = thisItem.BasketItemId; } // COMBINE QUANTITIES, BE CAREFUL NOT TO EXCEED SHORT LIMIT int newQuantity = thisItem.Quantity + matchItem.Quantity; thisItem.Quantity = (newQuantity > short.MaxValue ? short.MaxValue : (short)newQuantity); this.TrackedRemoveAt(matchIndex); // SEE IF WE HAVE ANOTHER MATCH matchIndex = FindNextIndexForCombine(thisIndex); } } } // UPDATE PARENT IDS THAT HAVE CHANGED AS A RESULT OF THE COMBINE Dictionary <int, int> savedParentMappings = new Dictionary <int, int>(); foreach (BasketItem thisItem in this) { if (thisItem.IsChildItem && combineMapping.ContainsKey(thisItem.ParentItemId)) { thisItem.ParentItemId = combineMapping[thisItem.ParentItemId]; } } // LOOP THE BASKET ITEM COLLECTION AND TO OPERATE ON CHILD ITEMS OrderItemType[] combineTypes = new OrderItemType[] { OrderItemType.Product, OrderItemType.Coupon, OrderItemType.Discount }; for (int thisIndex = 0; thisIndex < this.Count; thisIndex++) { BasketItem thisItem = this[thisIndex]; // ONLY CHECK CHILD ITEMS OF THE SPECIFIED TYPES // AND WHO HAVE PARENTS THAT HAVE BEEN IDENTIFIED IN THE COLLECTION if (thisItem.IsChildItem) { // COMBINE CHILD PRODUCTS, DISCOUNTS, AND COUPONS if (Array.IndexOf(combineTypes, thisItem.OrderItemType) > -1) { // SEE IF WE HAVE ANY MATCHES TO COMBINE int matchIndex = FindNextIndexForCombine(thisIndex); while (matchIndex > -1) { BasketItem matchItem = this[matchIndex]; if (thisItem.OrderItemType == OrderItemType.Product) { // COMBINE QUANTITIES, BE CAREFUL NOT TO EXCEED SHORT LIMIT int newQuantity = thisItem.Quantity + matchItem.Quantity; thisItem.Quantity = (newQuantity > short.MaxValue ? short.MaxValue : (short)newQuantity); } else { // COUPON OR DISCOUNT MAINTAINS ONE QUANTITY, ALTER PRICE INSTEAD thisItem.Price += matchItem.Price; } // REMOVE THE MATCH ITEM FROM THE COLLECTION this.TrackedRemoveAt(matchIndex); // SEE IF WE HAVE ANOTHER MATCH matchIndex = FindNextIndexForCombine(thisIndex); } } } } // LOOP COLLECTION IN REVERSE AND REMOVE ZERO QUANTITY ITEMS for (int i = this.Count - 1; i >= 0; i--) { if (this[i].Quantity < 1) { this.TrackedRemoveAt(i); } } // IF INDICATED, PERSIST CHANGES TO THE DATABASE if (save) { this.Save(); } }
/// <summary> /// Can this BasketItem combine with the given BasketItem? /// </summary> /// <param name="other">The BasketItem to check for combine</param> /// <returns><b>true</b> if it can combine, <b>false</b> otherwise</returns> internal bool CanCombine(BasketItem other, bool combineKitProducts) { if (other == null) { throw new ArgumentNullException("other"); } if (this.ProductId != other.ProductId) { return(false); } if (this.IsChildItem) { // IF THE OTHER ITEM IS NOT A CHILD, THIS IS NOT A MATCH if (!other.IsChildItem) { return(false); } // IS THIS IS A CHILD PRODUCT OF A KIT? if (this.OrderItemType == OrderItemType.Product) { // DO NOT COMBINE CHILD PRODUCTS UNLESS INDICATED if (!combineKitProducts) { return(false); } // DO NOT COMBINE IF NAME,PRICE,WEIGHT MISMATCH if (this.Name != other.Name || this.Price != other.Price || this.Weight != other.Weight) { return(false); } } // IF THE OTHER ITEM HAS A DIFFERENT PARENT IT IS NOT A MATCH if (this.ParentItemId != other.ParentItemId) { return(false); } } else { //THIS ITEM IS NOT A CHILD, SO THE OTHER IS NOT A MATCH IF IT IS A CHILD if (other.IsChildItem) { return(false); } } if (this.OptionList != other.OptionList) { return(false); } if (this.LineMessage != other.LineMessage) { return(false); } if (this.WishlistItemId != other.WishlistItemId) { return(false); } if (this.BasketShipmentId != other.BasketShipmentId) { return(false); } if (this.GiftMessage != other.GiftMessage) { return(false); } if (this.WrapStyleId != other.WrapStyleId) { return(false); } if (this.Inputs.Count > 0) { //compare all of the input values to see if they match if (this.Inputs.Count != other.Inputs.Count) { return(false); } foreach (BasketItemInput input in this.Inputs) { BasketItemInput otherInput = BasketItem.GetInput(other.Inputs, input.InputFieldId); if (otherInput == null) { return(false); } if (!input.InputValue.Equals(otherInput.InputValue)) { return(false); } } } if (this.KitList != other.KitList) { return(false); } if (this.OrderItemType != OrderItemType.Product) { if (this.Name != other.Name) { return(false); } if (this.Sku != other.Sku) { return(false); } if (this.Price != other.Price) { return(false); } } else { //NEED TO CHECK WHETHER THIS IS A VARIABLE PRICE PRODUCT if (this.Product != null && this.Product.UseVariablePrice) { //IS MY PRICE DIFFERENT FROM THE OTHER PRICE? if (this.Price != other.Price) { return(false); } } } return(true); }
/// <summary> /// Can this BasketItem combine with the given BasketItem? /// </summary> /// <param name="other">The BasketItem to check for combine</param> /// <returns><b>true</b> if it can combine, <b>false</b> otherwise</returns> public bool CanCombine(BasketItem other) { return(CanCombine(other, false)); }
/// <summary> /// Creates a basket item for a product /// </summary> /// <param name="productId">Id of the product for which to create the basket item</param> /// <param name="quantity">The quantity of basket item</param> /// <param name="optionList">List of option ids for the variant</param> /// <param name="kitList">List of product Ids of the kit items</param> /// <param name="userId">The user Id</param> /// <returns>The BasketItem created</returns> public static BasketItem CreateForProduct(int productId, short quantity, string optionList, string kitList, int userId) { // vALIDATE PARAMETERS Product product = ProductDataSource.Load(productId); if (product == null) { throw new ArgumentException("invalid product specified", "product"); } if (quantity < 1) { throw new ArgumentException("quantity must be greater than 0", "quantity"); } if (!string.IsNullOrEmpty(optionList)) { ProductVariant v = ProductVariantDataSource.LoadForOptionList(productId, optionList); if (v == null) { throw new ArgumentException("specified product options are invalid", "optionList"); } } if (product.KitStatus == KitStatus.Master) { Kit kit = product.Kit; if (!kit.ValidateChoices(kitList)) { throw new ArgumentException("specified kit configuration is invalid", "kitProductIds"); } } //GET THE QUANTITY short tempQuantity = quantity; short basketQuantity = GetExistingBasketCount(product); if ((product.MinQuantity > 0) && ((tempQuantity + basketQuantity) < product.MinQuantity)) { tempQuantity = (short)(product.MinQuantity - basketQuantity); } if ((product.MaxQuantity > 0) && ((tempQuantity + basketQuantity) > product.MaxQuantity)) { tempQuantity = (short)(product.MaxQuantity - basketQuantity); } if (tempQuantity < 1) { return(null); } quantity = tempQuantity; BasketItem item = new BasketItem(); //CALCULATE THE PRICE OF THE PRODUCT ProductCalculator pcalc = ProductCalculator.LoadForProduct(productId, quantity, optionList, kitList, userId); item.Sku = pcalc.Sku; item.Price = pcalc.Price; item.Weight = pcalc.Weight; //SET VALUES COMMON TO ALL BASKET ITEMS item.TaxCodeId = product.TaxCodeId; item.Name = product.Name; item.Quantity = quantity; item.OrderItemType = CommerceBuilder.Orders.OrderItemType.Product; item.Shippable = product.Shippable; item.ProductId = productId; item.OptionList = optionList; item.KitList = kitList; // COPY MERCHANT INPUT FIELDS, PRODUCT TEMPLATE FIELDS foreach (ProductTemplateField tf in product.TemplateFields) { if (!string.IsNullOrEmpty(tf.InputValue)) { InputField field = tf.InputField; if (field.IsMerchantField && field.PersistWithOrder) { BasketItemInput itemInput = new BasketItemInput(); itemInput.BasketItemId = item.BasketItemId; itemInput.InputFieldId = tf.InputFieldId; itemInput.InputValue = tf.InputValue; item.Inputs.Add(itemInput); } } } return(item); }
/// <summary> /// Compares two objects and returns a value indicating whether one is less than, equal to, or greater than the other. /// </summary> /// <param name="x">The first object to compare.</param> /// <param name="y">The second object to compare.</param> /// <returns>Less than zero if x is less than y. <br/> /// Zero of x equals y.<br/> /// Greater than zero if x is greater than y.<br/> /// </returns> public int Compare(object x, object y) { BasketItem item1 = x as BasketItem; BasketItem item2 = y as BasketItem; // COMPARING WITH YOUR OWN CHILD if (item1.IsChildItem && item1.ParentItemId == item2.BasketItemId) { return(1); } // COMPARING WITH YOUR OWN PARENT if (item2.IsChildItem && item2.ParentItemId == item1.BasketItemId) { return(-1); } // USE SORT PRIORITY, TREAT CHILD ITEMS AS PRODUCTS int item1Priority = GetSortPriority(item1, true); int item2Priority = GetSortPriority(item2, true); int result = item1Priority.CompareTo(item2Priority); if (result != 0) { return(result); } // SORT BY ROOT PARENT ID TO GROUP ALL RELATED PRODUCTS int item1CompareId = item1.GetParentItem(true).BasketItemId; int item2CompareId = item2.GetParentItem(true).BasketItemId; result = item1CompareId.CompareTo(item2CompareId); if (result != 0) { return(result); } // SORT BY BY ITEM ID, BUT USE PARENT IDS FOR TAXES if (item1.IsChildItem && item1.OrderItemType == OrderItemType.Tax) { item1CompareId = item1.ParentItemId; } else { item1CompareId = item1.BasketItemId; } if (item2.IsChildItem && item2.OrderItemType == OrderItemType.Tax) { item2CompareId = item2.ParentItemId; } else { item2CompareId = item2.BasketItemId; } result = item1CompareId.CompareTo(item2CompareId); if (result != 0) { return(result); } // USE SORT PRIORITY AND DO NOT GIVE SPECIAL HANDLING FOR CHILD ITEMS item1Priority = GetSortPriority(item1, false); item2Priority = GetSortPriority(item2, false); result = item1Priority.CompareTo(item2Priority); if (result != 0) { return(result); } // FINALLY RESORT TO ORDER BY VALUES result = item1.OrderBy.CompareTo(item2.OrderBy); return(result); }