[PM-34223] discounts rounding bug fix (#19811)

This commit is contained in:
Kyle Denney 2026-03-31 09:59:02 -05:00 committed by GitHub
parent 9b7452e8e2
commit 13bf3bc3bf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 48 additions and 1 deletions

View File

@ -537,6 +537,29 @@ describe("CartSummaryComponent", () => {
).toContain("-$10.00");
});
it("should compute total consistent with displayed rounded line items when chained discounts produce fractional cents", () => {
// Reproduces: 1x $47.88 seat with 20% off → $10 flat → 5% off, tax $2.15
// Raw math: 47.88 - 9.576 - 10 - 1.4152 + 2.15 = 29.0388 → $29.04 (wrong)
// Rounded: 47.88 - 9.58 - 10 - 1.42 + 2.15 = 29.03 (correct)
const cart: Cart = {
passwordManager: {
seats: { quantity: 1, translationKey: "members", cost: 47.88 },
},
cadence: "annually",
estimatedTax: 2.15,
discounts: [
{ type: DiscountTypes.PercentOff, value: 20 },
{ type: DiscountTypes.AmountOff, value: 10 },
{ type: DiscountTypes.PercentOff, value: 5 },
],
};
fixture.componentRef.setInput("cart", cart);
fixture.detectChanges();
const bottomTotal = fixture.debugElement.query(By.css("[data-testid='final-total']"));
expect(bottomTotal.nativeElement.textContent).toContain("$29.03");
});
it("should apply cascading subtotal when multiple percent-off discounts are stacked", () => {
// Arrange
const cartWithStackedPercents: Cart = {

View File

@ -34,6 +34,30 @@ describe("getAmount", () => {
const discount: Discount = { type: DiscountTypes.PercentOff, value: 100 };
expect(getAmount(discount, 200)).toBe(200);
});
it("should round result to 2 decimal places when percent produces fractional cents", () => {
const discount: Discount = { type: DiscountTypes.PercentOff, value: 20 };
// 20% of $47.88 = $9.576 → rounds to $9.58
expect(getAmount(discount, 47.88)).toBe(9.58);
});
it("should round result when applied to a running subtotal with fractional cents", () => {
const discount: Discount = { type: DiscountTypes.PercentOff, value: 5 };
// 5% of $28.304 = $1.4152 → rounds to $1.42
expect(getAmount(discount, 28.304)).toBe(1.42);
});
it("should round half-cent up", () => {
const discount: Discount = { type: DiscountTypes.PercentOff, value: 50 };
// 50% of $0.01 = $0.005 → rounds to $0.01
expect(getAmount(discount, 0.01)).toBe(0.01);
});
it("should round down when fractional cent is less than half", () => {
const discount: Discount = { type: DiscountTypes.PercentOff, value: 20 };
// 20% of $47.82 = $9.564 → rounds down to $9.56
expect(getAmount(discount, 47.82)).toBe(9.56);
});
});
describe("AmountOff", () => {

View File

@ -23,7 +23,7 @@ export const getAmount = (discount: Discount, baseAmount: number): number => {
switch (discount.type) {
case DiscountTypes.PercentOff: {
const percentage = discount.value < 1 ? discount.value : discount.value / 100;
return baseAmount * percentage;
return Math.round(baseAmount * percentage * 100) / 100;
}
case DiscountTypes.AmountOff:
return discount.value;