Dynamics Retail Discount Sample – Rounding

Rounding is hard.

Sometimes, we do not have a choice. Let’s get down to code.

PriceContext.HoldTogetherForDisocuntRounding = false

Example first: $10 distributed to quantity 7, down to penny,

  1. Smallest amount: $0.01
  2. Unit amount not-rounded is $1.42857143 (10 / 7)
  3. Take the floor: $1.42
  4. Remainder: $0.06 ($10 – 7 * $1.42)
  5. Remainder / smallest amount: 6 ($0.06/0.01)
  6. Split the sales line with quantity 7 into two:
  7. Quantity 1 (7 – 6) with discount amount $1.42
  8. Quantity 6 with discount amount $1.43 ($1.42 + $0.01)

Sample code

 
     decimal smallestAmount = PriceContextHelper.GetSmallestNonNegativeAmount(
         priceContext,
         Math.Min(discountAmount, price));
     if (smallestAmount > decimal.Zero && quantityToApply > decimal.Zero)
     {
         int totalDiscountAmountInSmallestAmount = (int)(discountAmount / smallestAmount);
         int averageDiscountAmountRoundingDownInSmallestAmount =
             totalDiscountAmountInSmallestAmount / (int)quantityToApply;
         decimal quantityForHigherDiscountAmount = totalDiscountAmountInSmallestAmount -
                                   (averageDiscountAmountRoundingDownInSmallestAmount * (int)quantityToApply);
         decimal quantityForLowerDiscountAmount = quantityToApply - quantityForHigherDiscountAmount;
         decimal unitDiscountAmountForLowerDiscountAmount =
                 averageDiscountAmountRoundingDownInSmallestAmount * smallestAmount;

         if (quantityForHigherDiscountAmount > decimal.Zero)
         {             
             DiscountLine discountLine = this.NewDiscountLine(
                 appliedDiscountApplication.DiscountApplication.DiscountCode,
                 discountableItemGroups[itemIndex].ItemId);
             discountLine.DiscountAmount = unitDiscountAmountForLowerDiscountAmount + smallestAmount;
             appliedDiscountApplication.AddDiscountLine(
                 itemGroupIndex,
                 new DiscountLineQuantity(discountLine, quantityForHigherDiscountAmount));
         }

         if (quantityForLowerDiscountAmount > decimal.Zero
             && unitDiscountAmountForLowerDiscountAmount > decimal.Zero)
         {
             DiscountLine discountLine = this.NewDiscountLine(
                 appliedDiscountApplication.DiscountApplication.DiscountCode,
                 discountableItemGroups[itemIndex].ItemId);
             discountLine.DiscountAmount = unitDiscountAmountForLowerDiscountAmount;
             appliedDiscountApplication.AddDiscountLine(
                 itemGroupIndex,
                 new DiscountLineQuantity(discountLine, quantityForLowerDiscountAmount));
         }
     }

PriceContext.HoldTogetherForDisocuntRounding = true

First, Dynamics retail discount engine may group multiple sales lines together into one discountable item group, and in discount engine, we deal with discountable item group, not sales line directly. This complicates rounding a bit.

Again example: $0.99 distributed to quantity 2, down to penny. We have one discountable item group with quantity 2.

Case 1: one sales line with quantity 2: all $0.99 goes to the sales line.

Case 2: two sales lines, each with quantity 1: $0.50 goes to one, while $0.49 to the other.

As we said earlier, in the discount engine, we do not deal with sales line directly. On the face, it would violate the design. No worry, we have covered it in discount totaling - side note, one more reason why we cannot get rid of discount totaling – all you have to do (when coding a new discount type) is to make sure the flag requiresFurtherRounding is true when creating a new DiscountLineQuantity.

Sample code

 
    AppliedDiscountApplication appliedDiscountApplication;
    discountLine.DiscountAmount = totalAmount / quantity; 
    appliedDiscountApplication.AddDiscountLine(
        itemGroupIndex,
        new DiscountLineQuantity(discountLine, quantity, requiresFurtherRounding: true));

Lastly, for scale products with non-integer quantity, we hold as well, i.e. no split.

Related: Retail Discount: Rounding I

Related: Dynamics Retail Discount Tribe – Rounding

Related: Dynamics Retail Discount Concept: Discountable Item Group