/// <summary> /// Attempts to apply a DiscountLine to a SalesLine that has a larger quantity than the required quantity, splitting the line if it is found. /// </summary> /// <param name="transaction">The current transaction containing the lines.</param> /// <param name="availableLines">The available line indices on the transaction.</param> /// <param name="quantityNeeded">The quantity needed for the DiscountLine.</param> /// <param name="discount">The DiscountLine and original quantity needed.</param> /// <param name="isReturn">True if it's return.</param> /// <param name="requiresExistingCompoundedDiscounts">A flag indicating whether it requires existing compounded discounts on the line to compound on top of.</param> /// <returns>True if a match was found and the discount was applied, false otherwise.</returns> private bool ApplyDiscountLineForLargerMatch( SalesTransaction transaction, HashSet <int> availableLines, ref decimal quantityNeeded, DiscountLineQuantity discount, bool isReturn, bool requiresExistingCompoundedDiscounts) { bool discountApplied = false; foreach (int x in availableLines.ToList().OrderBy(p => this.salesLines[p].Quantity)) { SalesLine salesLine = this.salesLines[x]; if (!IsQuantityMatchSalesOrReturn(isReturn, salesLine.Quantity)) { continue; } decimal lineQuantity = Math.Abs(salesLine.Quantity); if (lineQuantity > quantityNeeded) { if (discount.DiscountLine.ConcurrencyMode != ConcurrencyMode.Compounded || CanApplyCompoundedDiscount(this.salesLines[x].DiscountLines, discount.DiscountLine, requiresExistingCompoundedDiscounts)) { // Perform the split of this line SalesLine newLine = this.SplitLine(this.salesLines[x], quantityNeeded); // Add the new line to the transaction and to the available lines for discounts. Set the line number to the next available number. newLine.LineNumber = transaction.SalesLines.Max(p => p.LineNumber) + 1; transaction.SalesLines.Add(newLine); this.salesLines.Add(newLine); DiscountLine discountLine = discount.DiscountLine.Clone <DiscountLine>(); discountLine.SaleLineNumber = newLine.LineNumber; newLine.DiscountLines.Add(discountLine); newLine.QuantityDiscounted = quantityNeeded * Math.Sign(salesLine.Quantity); // If this is a compounding discount, add the new line to the available lines. if (discount.DiscountLine.ConcurrencyMode == ConcurrencyMode.Compounded) { availableLines.Add(this.salesLines.Count - 1); } discountApplied = true; quantityNeeded = 0; break; } } } return(discountApplied); }
private bool ApplyDiscountLineForDirectMatch( HashSet <int> availableLines, decimal quantityNeeded, DiscountLineQuantity discount, bool isReturn) { return(this.ApplyDiscountLineForDirectMatch( availableLines, quantityNeeded, discount, isReturn, requiresExistingCompoundedDiscounts: false)); }
private void ApplyOneDiscountLine( DiscountLineQuantity discountLineQuantity, SalesTransaction transaction, HashSet <int> availableLines, bool isReturn) { this.ApplyOneDiscountLine( discountLineQuantity, transaction, availableLines, isReturn, discountLineQuantity.Quantity); }
internal void AddDiscountLine(int itemGroupIndex, DiscountLineQuantity discountLine) { if (this.ItemGroupIndexToDiscountLineQuantitiesLookup.ContainsKey(itemGroupIndex)) { this.ItemGroupIndexToDiscountLineQuantitiesLookup[itemGroupIndex].Add(discountLine); } else { this.ItemGroupIndexToDiscountLineQuantitiesLookup.Add(itemGroupIndex, new List <DiscountLineQuantity>() { discountLine }); } }
private bool ApplyDiscountLineForLargerMatch( SalesTransaction transaction, HashSet <int> availableLines, ref decimal quantityNeeded, DiscountLineQuantity discount, bool isReturn) { return(this.ApplyDiscountLineForLargerMatch( transaction, availableLines, ref quantityNeeded, discount, isReturn, requiresExistingCompoundedDiscounts: false)); }
/// <summary> /// Adds a discount line to this group. /// </summary> /// <param name="discountLineQuantity">The discount line quantity to add to the group.</param> public void AddDiscountLine(DiscountLineQuantity discountLineQuantity) { ThrowIf.Null(discountLineQuantity, "discountLineQuantity"); ThrowIf.Null(discountLineQuantity.DiscountLine, "discountLineQuantity.DiscountLine"); List <DiscountLineQuantity> discountLineQuantitiesToAdd = discountLineQuantity.DiscountLine.ConcurrencyMode == ConcurrencyMode.Compounded ? this.discountLineQuantitiesCompounded : this.discountLineQuantitiesNonCompounded; DiscountLineQuantity existingItem = discountLineQuantitiesToAdd.FirstOrDefault(p => p.DiscountLine.OfferId == discountLineQuantity.DiscountLine.OfferId && p.DiscountLine.Amount == discountLineQuantity.DiscountLine.Amount && p.DiscountLine.Percentage == discountLineQuantity.DiscountLine.Percentage); if (existingItem != null) { existingItem.Quantity += discountLineQuantity.Quantity; } else { discountLineQuantitiesToAdd.Add(discountLineQuantity); } }
/// <summary> /// Attempts to apply a DiscountLine to a SalesLine that has a smaller quantity than the required quantity. /// </summary> /// <param name="availableLines">The available line indices on the transaction.</param> /// <param name="quantityNeeded">The quantity needed for the DiscountLine.</param> /// <param name="discount">The DiscountLine and original quantity needed.</param> /// <param name="isReturn">True if it's return.</param> /// <param name="requiresExistingCompoundedDiscounts">A flag indicating whether it requires existing compounded discounts on the line to compound on top of.</param> /// <returns>True if the discount was applied to a line, false otherwise.</returns> private bool ApplyDiscountLineForSmallerMatch( HashSet <int> availableLines, ref decimal quantityNeeded, DiscountLineQuantity discount, bool isReturn, bool requiresExistingCompoundedDiscounts) { bool discountPartiallyApplied = false; foreach (int x in availableLines.ToList().OrderByDescending(p => this.salesLines[p].Quantity)) { SalesLine salesLine = this.salesLines[x]; if (!IsQuantityMatchSalesOrReturn(isReturn, salesLine.Quantity)) { continue; } decimal lineQuantity = Math.Abs(salesLine.Quantity); if (lineQuantity < quantityNeeded) { if (discount.DiscountLine.ConcurrencyMode != ConcurrencyMode.Compounded || CanApplyCompoundedDiscount(this.salesLines[x].DiscountLines, discount.DiscountLine, requiresExistingCompoundedDiscounts)) { DiscountLine discountLine = discount.DiscountLine.Clone <DiscountLine>(); discountLine.SaleLineNumber = salesLine.LineNumber; salesLine.DiscountLines.Add(discountLine); salesLine.QuantityDiscounted = salesLine.Quantity; if (discount.DiscountLine.ConcurrencyMode != ConcurrencyMode.Compounded) { availableLines.Remove(x); } discountPartiallyApplied = true; quantityNeeded -= lineQuantity; break; } } } return(discountPartiallyApplied); }
/// <summary> /// Apply one discount line on top of compounded discounts only. /// </summary> /// <param name="discountLineQuantity">Discount line and quantity.</param> /// <param name="transaction">Sales transaction.</param> /// <param name="availableLines">Available sales lines.</param> /// <param name="isReturn">A flag indicating whether it's for return.</param> /// <returns>Quantity not applied yet.</returns> /// <remarks> /// This is to deal with multiple mix and match discounts, each taking only partial quantity. /// A simple example of one sales line of quantity 3, where compounded mix and mach 1 (mm1) takes 1 quantity and mm2 takes 2. /// We'd split the line into 3: /// quantity 1 - mm1 and mm2 compounded /// quantity 1 - mm2 only /// quantity 1 - no discount /// The alternative is to split the line into 2, one of quantity 1 with mm1 and there other one of quantity 2 with mm2. /// The reason for the decision is that with multiple rounds of discount calculation, by compounding them together, we could leave /// quantity for the next round. /// It's a rare scenario. The bottom line is that we need to ensure consistent behaviors. /// </remarks> private decimal ApplyOneDiscountLineToCompoundedOnly( DiscountLineQuantity discountLineQuantity, SalesTransaction transaction, HashSet <int> availableLines, bool isReturn) { decimal quantityNotApplied = decimal.Zero; if (discountLineQuantity.Quantity != decimal.Zero) { decimal quantityNeeded = discountLineQuantity.Quantity; bool discountFullyApplied = false; bool keepGoing = true; while (keepGoing) { bool discountPartiallyApplied = false; // Look for an exact match first within lines discountFullyApplied = this.ApplyDiscountLineForDirectMatch(availableLines, quantityNeeded, discountLineQuantity, isReturn, requiresExistingCompoundedDiscounts: true); // If that was not found, look for a combination of lower-quantity lines by selecting the largest lower-quantity line and repeating this loop. if (!discountFullyApplied) { discountPartiallyApplied = this.ApplyDiscountLineForSmallerMatch(availableLines, ref quantityNeeded, discountLineQuantity, isReturn, requiresExistingCompoundedDiscounts: true); } // If we still have not found a match, find the smallest higher-quantity line and split it. if (!discountFullyApplied && !discountPartiallyApplied) { discountFullyApplied = this.ApplyDiscountLineForLargerMatch(transaction, availableLines, ref quantityNeeded, discountLineQuantity, isReturn, requiresExistingCompoundedDiscounts: true); } // Keep going if discount is only partial - not fully - applied. keepGoing = !discountFullyApplied && discountPartiallyApplied; } quantityNotApplied = discountFullyApplied ? decimal.Zero : quantityNeeded; } return(quantityNotApplied); }