示例#1
0
            /// <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);
            }
示例#2
0
 private bool ApplyDiscountLineForDirectMatch(
     HashSet <int> availableLines,
     decimal quantityNeeded,
     DiscountLineQuantity discount,
     bool isReturn)
 {
     return(this.ApplyDiscountLineForDirectMatch(
                availableLines,
                quantityNeeded,
                discount,
                isReturn,
                requiresExistingCompoundedDiscounts: false));
 }
示例#3
0
 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
         });
     }
 }
示例#5
0
 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));
 }
示例#6
0
            /// <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);
                }
            }
示例#7
0
            /// <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);
            }
示例#8
0
            /// <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);
            }