private static decimal AllocateLineDiscountLines( SalesLine salesLine, List <DiscountLine> customerLineDiscountItemList, DiscountLine manualLineDiscountItem, LineDiscountCalculationType lineDiscountCalculationType, decimal customerLineAmountForMixOrMax, decimal customerLinePercentForMixOrMax, RoundingRule roundingRule) { decimal lineDiscountEffectiveAmount = 0; decimal lineDiscountEffectivePercentage = 0; decimal grossAmountDiscountable = (salesLine.Price * salesLine.Quantity) - salesLine.PeriodicDiscount; if (grossAmountDiscountable != decimal.Zero) { // Round 1: amount off first for customer line discounts and manual line discount, in that order. if (customerLineDiscountItemList.Any()) { foreach (DiscountLine customerLine in customerLineDiscountItemList) { if (salesLine.LineMultilineDiscOnItem != LineMultilineDiscountOnItem.Both || lineDiscountCalculationType == LineDiscountCalculationType.LinePlusMultiline || lineDiscountCalculationType == LineDiscountCalculationType.LineMultiplyMultiline || (lineDiscountCalculationType == LineDiscountCalculationType.MaxLineMultiline && customerLine.Amount == customerLineAmountForMixOrMax) || (lineDiscountCalculationType == LineDiscountCalculationType.MinLineMultiline && customerLine.Amount == customerLineAmountForMixOrMax) || (lineDiscountCalculationType == LineDiscountCalculationType.Line && customerLine.CustomerDiscountType == CustomerDiscountType.LineDiscount) || (lineDiscountCalculationType == LineDiscountCalculationType.Multiline && customerLine.CustomerDiscountType == CustomerDiscountType.MultilineDiscount)) { customerLine.SetEffectiveAmountForAmountOff(grossAmountDiscountable - lineDiscountEffectiveAmount, salesLine.Quantity, roundingRule); lineDiscountEffectiveAmount += customerLine.EffectiveAmount; // In case of Min and Max, customerLineAmountForMixOrMax can only be applied once. customerLineAmountForMixOrMax = decimal.Zero; } else { customerLine.EffectiveAmount = decimal.Zero; } } } if (manualLineDiscountItem != null) { manualLineDiscountItem.SetEffectiveAmountForAmountOff(grossAmountDiscountable - lineDiscountEffectiveAmount, salesLine.Quantity, roundingRule); lineDiscountEffectiveAmount += manualLineDiscountItem.EffectiveAmount; } decimal grossAmountDiscountableLessAmountOff = grossAmountDiscountable - lineDiscountEffectiveAmount; decimal lineDiscountEffectiveAmountForPercentageOnly = 0; // Round 2: percentage off for customer line discounts and manual line discount, in that order. if (customerLineDiscountItemList.Any()) { foreach (DiscountLine customerLine in customerLineDiscountItemList) { if (salesLine.LineMultilineDiscOnItem != LineMultilineDiscountOnItem.Both || lineDiscountCalculationType == LineDiscountCalculationType.LinePlusMultiline || lineDiscountCalculationType == LineDiscountCalculationType.LineMultiplyMultiline || (lineDiscountCalculationType == LineDiscountCalculationType.MaxLineMultiline && customerLine.Percentage == customerLinePercentForMixOrMax) || (lineDiscountCalculationType == LineDiscountCalculationType.MinLineMultiline && customerLine.Percentage == customerLinePercentForMixOrMax) || (lineDiscountCalculationType == LineDiscountCalculationType.Line && customerLine.CustomerDiscountType == CustomerDiscountType.LineDiscount) || (lineDiscountCalculationType == LineDiscountCalculationType.Multiline && customerLine.CustomerDiscountType == CustomerDiscountType.MultilineDiscount)) { // Compound only when LineDiscountCalculationType.LineMultiplyMultiline. decimal grossAmountBase = grossAmountDiscountableLessAmountOff; if (lineDiscountCalculationType == LineDiscountCalculationType.LineMultiplyMultiline) { grossAmountBase = grossAmountDiscountableLessAmountOff - lineDiscountEffectiveAmountForPercentageOnly; } decimal maxDiscountAmount = grossAmountDiscountable - lineDiscountEffectiveAmount; decimal discountAmountAdded = customerLine.AddEffectiveAmountForPercentOff(grossAmountBase, maxDiscountAmount, roundingRule); lineDiscountEffectiveAmount += discountAmountAdded; lineDiscountEffectiveAmountForPercentageOnly += discountAmountAdded; // In case of Min and Max, customerLineAmountForMixOrMax can only be applied once. customerLineAmountForMixOrMax = decimal.Zero; } } } if (manualLineDiscountItem != null) { // Whether to add or to compound for manual discount follows the same rule for customer line and multiline discount. decimal grossAmountBase = grossAmountDiscountableLessAmountOff; if (lineDiscountCalculationType == LineDiscountCalculationType.LineMultiplyMultiline) { grossAmountBase = grossAmountDiscountableLessAmountOff - lineDiscountEffectiveAmountForPercentageOnly; } decimal maxDiscountAmount = grossAmountDiscountable - lineDiscountEffectiveAmount; decimal discountAmountAdded = manualLineDiscountItem.AddEffectiveAmountForPercentOff(grossAmountBase, maxDiscountAmount, roundingRule); lineDiscountEffectiveAmount += discountAmountAdded; lineDiscountEffectiveAmountForPercentageOnly += discountAmountAdded; } lineDiscountEffectivePercentage = (lineDiscountEffectiveAmount / grossAmountDiscountable) * 100m; } salesLine.LineDiscount = lineDiscountEffectiveAmount; salesLine.LinePercentageDiscount = Math.Round(lineDiscountEffectivePercentage, 2); return(lineDiscountEffectiveAmount); }
private static void CalculateLine(DateTimeOffset transactionBeginDateTime, LineDiscountCalculationType lineDiscountCalculationType, SalesLine salesLine, RoundingRule salesRoundingRule, bool compareDiscounts) { if (salesLine == null) { throw new ArgumentNullException("salesLine"); } if (salesRoundingRule == null) { throw new ArgumentNullException("salesRoundingRule"); } decimal discountAmount = decimal.Zero; SalesLineTotaller.CalculateTax(salesLine); if ((salesLine.Blocked == false) && (salesLine.DateToActivateItem <= transactionBeginDateTime)) { salesLine.GrossAmount = salesRoundingRule(salesLine.Price * salesLine.Quantity); if (!salesLine.IsPriceLocked || salesLine.QuantityOrdered != salesLine.Quantity) { salesLine.LineDiscount = 0; salesLine.LinePercentageDiscount = 0; salesLine.PeriodicDiscount = 0; salesLine.PeriodicPercentageDiscount = 0; salesLine.TotalDiscount = decimal.Zero; salesLine.TotalPercentageDiscount = decimal.Zero; salesLine.LoyaltyDiscountAmount = decimal.Zero; salesLine.LoyaltyPercentageDiscount = decimal.Zero; if (salesLine.IsVoided || salesLine.IsPriceOverridden) { salesLine.DiscountLines.Clear(); } if (compareDiscounts) { ComparingDiscounts(transactionBeginDateTime, lineDiscountCalculationType, salesLine, salesRoundingRule); } AllocateDiscountAmountToDiscountLines(salesLine, lineDiscountCalculationType, salesRoundingRule); } else { FixDiscountAmountsOnSalesLine(salesLine, salesRoundingRule); } discountAmount = salesLine.PeriodicDiscount + salesLine.LineDiscount + salesLine.TotalDiscount + salesLine.LoyaltyDiscountAmount; salesLine.NetAmountWithAllInclusiveTax = salesLine.GrossAmount - discountAmount; // Removing exempt inclusive taxes. // Update - Bug 938614, - not deducting anymore. If PM's later decide to deduct this inclusive tax then uncomment line below. salesLine.NetAmount = salesLine.NetAmountWithAllInclusiveTax; // -salesLine.TaxAmountExemptInclusive; if (salesLine.Quantity != 0) { salesLine.NetAmountPerUnit = salesLine.NetAmountWithNoTax() / salesLine.Quantity; } salesLine.UnitQuantity = salesLine.UnitOfMeasureConversion.Convert(salesLine.Quantity); salesLine.DiscountAmount = discountAmount; salesLine.TotalAmount = salesLine.NetAmountWithTax(); salesLine.NetAmountWithoutTax = salesLine.NetAmount - salesLine.TaxAmountInclusive; } }
private static void AllocateDiscountAmountToDiscountLines(SalesLine salesLine, LineDiscountCalculationType lineDiscountCalculationType, RoundingRule roundingRule) { if (salesLine.DiscountLines.Count == 0) { return; } // customerLineAmountForMixOrMax and customerLinePercentForMixOrMax are for LineDiscountCalculationType.MaxLineMultiline and MinLineMultiline decimal customerLineAmountForMixOrMax = 0; decimal customerLinePercentForMixOrMax = 0; List <DiscountLine> periodicDiscountItemList = new List <DiscountLine>(); List <DiscountLine> periodicThresholdDiscountItemList = new List <DiscountLine>(); List <DiscountLine> customerLineDiscountItemList = new List <DiscountLine>(); // Manual line discount at most one. DiscountLine manualLineDiscountItem = null; List <DiscountLine> totalDiscountItemList = new List <DiscountLine>(); List <DiscountLine> loyaltyDiscountLineList = new List <DiscountLine>(); //// Step 1: split discount lines into 5 groups: Periodic less threshold, Periodic threshold, Customer line, Manual line and Total and Loyalty //// and figure out customerLineAmountForMixOrMax & customerLinePercentForMixOrMax along the way. foreach (DiscountLine discountLineItem in salesLine.DiscountLines) { discountLineItem.FixInvalidAmountAndPercentage(); if (discountLineItem.DiscountLineType == DiscountLineType.CustomerDiscount) { // Customer Total if (discountLineItem.CustomerDiscountType == CustomerDiscountType.TotalDiscount) { totalDiscountItemList.Add(discountLineItem); } else { // Customer Line if (salesLine.LineMultilineDiscOnItem == LineMultilineDiscountOnItem.Both) { if (customerLineAmountForMixOrMax == decimal.Zero || (lineDiscountCalculationType == LineDiscountCalculationType.MaxLineMultiline && discountLineItem.Amount > customerLineAmountForMixOrMax) || (lineDiscountCalculationType == LineDiscountCalculationType.MinLineMultiline && discountLineItem.Amount > 0 && discountLineItem.Amount < customerLineAmountForMixOrMax)) { customerLineAmountForMixOrMax = discountLineItem.Amount; } if (customerLinePercentForMixOrMax == decimal.Zero || (lineDiscountCalculationType == LineDiscountCalculationType.MaxLineMultiline && discountLineItem.Percentage > customerLinePercentForMixOrMax) || (lineDiscountCalculationType == LineDiscountCalculationType.MinLineMultiline && discountLineItem.Percentage > 0 && discountLineItem.Percentage < customerLinePercentForMixOrMax)) { customerLinePercentForMixOrMax = discountLineItem.Percentage; } } customerLineDiscountItemList.Add(discountLineItem); } } else if (discountLineItem.DiscountLineType == DiscountLineType.PeriodicDiscount) { // Periodic a.k.a. Retail if (discountLineItem.PeriodicDiscountType == PeriodicDiscountOfferType.Threshold) { periodicThresholdDiscountItemList.Add(discountLineItem); } else { periodicDiscountItemList.Add(discountLineItem); } } else if (discountLineItem.DiscountLineType == DiscountLineType.ManualDiscount && (discountLineItem.ManualDiscountType == ManualDiscountType.LineDiscountAmount || discountLineItem.ManualDiscountType == ManualDiscountType.LineDiscountPercent)) { // Line Manual manualLineDiscountItem = discountLineItem; } else if (discountLineItem.DiscountLineType == DiscountLineType.ManualDiscount && (discountLineItem.ManualDiscountType == ManualDiscountType.TotalDiscountAmount || discountLineItem.ManualDiscountType == ManualDiscountType.TotalDiscountPercent)) { // Total manual totalDiscountItemList.Add(discountLineItem); } else if (discountLineItem.DiscountLineType == DiscountLineType.LoyaltyDiscount) { // Loyalty discount loyaltyDiscountLineList.Add(discountLineItem); } } salesLine.PeriodicDiscount = 0; salesLine.PeriodicPercentageDiscount = 0; // Step 2: allocate effective discount amount for periodic less threshold discount lines. AllocatePeriodicDiscountLines(salesLine, periodicDiscountItemList, roundingRule); // Step 3: allocate effective discount amount for periodic threshold discount lines. AllocatePeriodicDiscountLines(salesLine, periodicThresholdDiscountItemList, roundingRule); // Stpe 4: allocate effective discount amount for customer and manual line discounts. AllocateLineDiscountLines(salesLine, customerLineDiscountItemList, manualLineDiscountItem, lineDiscountCalculationType, customerLineAmountForMixOrMax, customerLinePercentForMixOrMax, roundingRule); // Stpe 4: allocate effective discount amount for total line discounts. AllocateTotalDiscountLines(salesLine, totalDiscountItemList, roundingRule); // Stpe 5: allocate effective discount amount for loyalty line discounts AllocateLoyaltyDiscountLines(salesLine, loyaltyDiscountLineList, roundingRule); }
/// <summary> /// Populates the fields for total amount, total discount, and total taxes on the sales line. /// </summary> /// <param name="transactionBeginDateTime">Transaction begin date time.</param> /// <param name="lineDiscountCalculationType">Line discount calculation type.</param> /// <param name="salesLine">The sales line to total.</param> /// <param name="salesRoundingRule">Delegate which can do sales rounding.</param> public static void CalculateLine(DateTime transactionBeginDateTime, LineDiscountCalculationType lineDiscountCalculationType, SalesLine salesLine, RoundingRule salesRoundingRule) { CalculateLine(transactionBeginDateTime, lineDiscountCalculationType, salesLine, salesRoundingRule, true); }
/// <summary> /// Compares the discounts on each of the sale line items: /// If the sale line has both a customer and a periodic discount (other then Mix and Match) then /// the discounts are compared and the better one is chosen and the other one taken of the sale line. /// If the sale line has a customer discount and a Mix & Match discount then the M&M discount is always /// chosen because there is no way to know the total discount of the M&M because it consists of 2 or more sale lines. /// So we assume that the M&M is always better. /// </summary> /// <param name="transactionBeginDateTime">The transaction start date.</param> /// <param name="lineDiscountCalculationType">The line discount calculation type.</param> /// <param name="salesLine">The sales line.</param> /// <param name="salesRoundingRule">The rounding rule to use for the sales line.</param> private static void ComparingDiscounts(DateTimeOffset transactionBeginDateTime, LineDiscountCalculationType lineDiscountCalculationType, SalesLine salesLine, RoundingRule salesRoundingRule) { bool periodicDiscFound = false; bool customerDiscFound = false; bool mixAndMatchFound = false; bool thresholdFound = false; bool customerTotalDiscFound = false; // Go through all the discount lines and figure out what type of discount lines are available. foreach (var discountLine in salesLine.DiscountLines) { if (discountLine.DiscountLineType == DiscountLineType.PeriodicDiscount) { if (discountLine.PeriodicDiscountType == PeriodicDiscountOfferType.MixAndMatch) { mixAndMatchFound = true; } else if (discountLine.PeriodicDiscountType == PeriodicDiscountOfferType.Threshold) { thresholdFound = true; } else { periodicDiscFound = true; } } if (discountLine.DiscountLineType == DiscountLineType.CustomerDiscount) { if (discountLine.CustomerDiscountType != CustomerDiscountType.TotalDiscount) { customerDiscFound = true; } else { customerTotalDiscFound = true; } } } bool discardCustomerDiscounts = mixAndMatchFound || thresholdFound; bool discardTotalCustomerDiscount = thresholdFound; // If (Mix & Match or Threshold) and Customer Total Discount were found // then they will override any other discounts that are found on the sale line // no need to compare prices or discounts. if (discardCustomerDiscounts && customerTotalDiscFound) { ClearCustomerDiscountLines(salesLine, discardTotalCustomerDiscount); customerTotalDiscFound = false; customerDiscFound = false; } // If (Mix & Match or Threshold) and Customer discount both found on the sale item // then the Mix & Match will override any customer discount if (discardCustomerDiscounts && customerDiscFound) { ClearCustomerDiscountLines(salesLine, discardTotalCustomerDiscount); customerDiscFound = false; } // If a Customer Discount is found and either a Multibuy or a Discount offer // the best price is found from either discount and the better one chosen. if (customerDiscFound && periodicDiscFound) { // clone line and keep only customer discounts SalesLine custSaleLineItem = salesLine.Clone <SalesLine>(); ClearDiscountLinesOfType(custSaleLineItem, DiscountLineType.PeriodicDiscount); // clone line and keep only periodic discounts SalesLine periodicSaleLineItem = salesLine.Clone <SalesLine>(); ClearDiscountLinesOfType(periodicSaleLineItem, DiscountLineType.CustomerDiscount); CalculateLine(transactionBeginDateTime, lineDiscountCalculationType, custSaleLineItem, salesRoundingRule, false); CalculateLine(transactionBeginDateTime, lineDiscountCalculationType, periodicSaleLineItem, salesRoundingRule, false); if (Math.Abs(custSaleLineItem.NetAmount) >= Math.Abs(periodicSaleLineItem.NetAmount)) { ClearCustomerDiscountLines(salesLine, false); } else { ClearDiscountLinesOfType(salesLine, DiscountLineType.PeriodicDiscount); } } }