/// <summary> /// Calculates manual line discount sent from cashier. /// Should be called only after other discounts are calculated. /// </summary> /// <param name="transaction">Transaction to calculate manual total discounts on.</param> public void CalculateLineManualDiscount(SalesTransaction transaction) { if (transaction == null) { throw new ArgumentNullException("transaction"); } // Consider calculable lines only. Ignore voided or return-by-receipt lines. foreach (var salesLine in transaction.PriceCalculableSalesLines) { Discount.ClearManualDiscountLinesOfType(salesLine, ManualDiscountType.LineDiscountAmount); Discount.ClearManualDiscountLinesOfType(salesLine, ManualDiscountType.LineDiscountPercent); DiscountLine lineDiscountItem = null; if (salesLine.LineManualDiscountPercentage != 0) { // Add a new line discount lineDiscountItem = new DiscountLine { DiscountLineType = DiscountLineType.ManualDiscount, ManualDiscountType = ManualDiscountType.LineDiscountPercent, Percentage = salesLine.LineManualDiscountPercentage, }; this.AddLineDiscount(transaction, salesLine, lineDiscountItem); } if (salesLine.LineManualDiscountAmount != 0) { // Add a new line discount lineDiscountItem = new DiscountLine { DiscountLineType = DiscountLineType.ManualDiscount, ManualDiscountType = ManualDiscountType.LineDiscountAmount, Amount = salesLine.Quantity != decimal.Zero ? salesLine.LineManualDiscountAmount / salesLine.Quantity : decimal.Zero, }; this.AddLineDiscount(transaction, salesLine, lineDiscountItem); } } }
/// <summary> /// Calculates distribution of manual total discounts across the transaction. /// Should be called only after other discounts are calculated. /// </summary> /// <param name="transaction">Transaction to calculate manual total discounts on.</param> public void CalculateTotalManualDiscount(SalesTransaction transaction) { if (transaction == null) { throw new ArgumentNullException("transaction"); } Discount.ClearManualDiscountLinesOfType(transaction, ManualDiscountType.TotalDiscountPercent); Discount.ClearManualDiscountLinesOfType(transaction, ManualDiscountType.TotalDiscountAmount); // It's either $ off or % off, not both. In case of a bug where both are present, $ off wins. if (transaction.TotalManualDiscountAmount != 0) { this.AddTotalDiscAmountLines(transaction, DiscountLineType.ManualDiscount, transaction.TotalManualDiscountAmount); } else if (transaction.TotalManualDiscountPercentage != 0) { this.AddTotalDiscPctLines(transaction); } }
/// <summary> /// This method will distribute the amountToDiscount across all the sale items in the transaction /// proportionally except for the line item with the largest amount. The remainder will be distributed /// to the line item with the largest amount to ensure the amount to discount is exactly applied. /// This method currently works for either the customer discount or when the total discount button is applied. /// </summary> /// <param name="transaction">The transaction receiving total discount lines.</param> /// <param name="discountType">Whether this discount is for a customer or for the total discount item.</param> /// <param name="amountToDiscount">The amount to discount the transaction.</param> private void AddTotalDiscAmountLines( SalesTransaction transaction, DiscountLineType discountType, decimal amountToDiscount) { decimal totalAmtAvailableForDiscount = decimal.Zero; // Build a list of the discountable items with the largest value item last. // Consider calculable lines only. Ignore voided or return-by-receipt lines. var discountableSaleItems = (from s in transaction.PriceCalculableSalesLines where s.IsEligibleForDiscount() && s.Quantity > 0 && PriceContextHelper.IsDiscountAllowed(this.priceContext, s.ItemId) orderby Math.Abs(s.NetAmount), s.LineId select s).ToList(); // Iterate through all non voided items whether we are going to discount or not so that they get added // back to the totals // Consider calculable lines only. Ignore voided or return-by-receipt lines. foreach (var saleItem in transaction.PriceCalculableSalesLines) { // We can clear the discount line for total discount because a total manual amount discount // will override a total manual percent discount, whereas customer discount can have both // amount and percentage applied simultaneously. if (discountType == DiscountLineType.ManualDiscount) { Discount.ClearManualDiscountLinesOfType(saleItem, ManualDiscountType.TotalDiscountAmount); Discount.ClearManualDiscountLinesOfType(saleItem, ManualDiscountType.TotalDiscountPercent); } SalesLineTotaller.CalculateLine(transaction, saleItem, d => this.priceContext.CurrencyAndRoundingHelper.Round(d)); if (saleItem.IsEligibleForDiscount() && saleItem.Quantity > 0) { // Calculate the total amount that is available for discount totalAmtAvailableForDiscount += Math.Abs(saleItem.NetAmountWithAllInclusiveTax); } } // Calculate the percentage (as a fraction) that we should attempt to discount each discountable item // to reach the total. decimal discountFactor = totalAmtAvailableForDiscount != decimal.Zero ? (amountToDiscount / totalAmtAvailableForDiscount) : decimal.Zero; decimal totalAmtDistributed = decimal.Zero; // Iterate through all discountable items. foreach (var saleItem in discountableSaleItems) { decimal amountToDiscountForThisItem = decimal.Zero; if (saleItem != discountableSaleItems.Last()) { // for every item except for the last in the list (which will have the largest value) // discount by the rounded amount that is closest to the percentage desired for the transaction decimal itemPrice = saleItem.NetAmount; amountToDiscountForThisItem = this.priceContext.CurrencyAndRoundingHelper.Round(discountFactor * Math.Abs(itemPrice)); totalAmtDistributed += amountToDiscountForThisItem; } else { // Discount the last item by the remainder to ensure that the exact desired discount is applied amountToDiscountForThisItem = amountToDiscount - totalAmtDistributed; } DiscountLine discountItem; if (amountToDiscountForThisItem != decimal.Zero) { if (discountType == DiscountLineType.ManualDiscount) { // Add a new total discount item discountItem = new DiscountLine(); discountItem.DiscountLineType = DiscountLineType.ManualDiscount; discountItem.ManualDiscountType = ManualDiscountType.TotalDiscountAmount; saleItem.DiscountLines.Add(discountItem); } else { // for customer discounts we need to either update the existing one, or add a new one. discountItem = GetCustomerDiscountItem(saleItem, CustomerDiscountType.TotalDiscount, DiscountLineType.CustomerDiscount); } discountItem.Amount = saleItem.Quantity != 0 ? amountToDiscountForThisItem / saleItem.Quantity : amountToDiscountForThisItem; } SalesLineTotaller.CalculateLine(transaction, saleItem, d => this.priceContext.CurrencyAndRoundingHelper.Round(d)); } }