← All tests

Fully Discounted Items Count as Free Items STAGING

"You're right, we can just use Free Items, probably more straightforward."

If an item ends up costing nothing, the Discounts page now calls it a Free Item. It used to call it a discount of the full price, which is technically true and reads as nonsense — a Special Gift given away for free was filed under "money off", and the Free Items count stayed at zero.

Why it wasn't already doing that

The page doesn't guess how many units were given away — it reads a number the till records at the moment of sale. That is deliberate, and it's the part worth protecting: a buy-one-get-one that frees one coffee out of three takes a third off the line, which is arithmetically identical to a 33% discount. Nothing about the money can tell those two apart, so if the page worked it out from the total it would get BOGO wrong.

But that ambiguity only exists for partial discounts. When an item is discounted to zero, every unit on the line is free — there is nothing left to work out. Two things reach zero without the till recording free units: a cashier taking 100% off a line, and a whole-order discount that covers the entire order. Those are the cases that were being mislabelled, and those are the only cases that changed.

The Discounts ledger showing free and discount rows
Every shape on one screen: a BOGO line, a 100%-off line, a partial discount, a 100% whole-order "gift" spread across two items, and a partial whole-order discount.

What changed and what didn't

CaseBeforeNow
BOGO frees 1 of 3 coffeesFree Item ×1Free Item ×1 — unchanged
Cashier takes 100% off a lineDiscountFree Item, whole quantity
Whole order given away (Special Gift)Discount on every itemFree Item on every item
Any partial discountDiscountDiscount — unchanged

The recorded number always wins where it exists, so nothing a promotion actually counted has been overwritten. Only rows that reach zero with nothing recorded fall back to "all of it was free".

The Type filter follows

Filtering by Free Items now returns the fully discounted rows too, and filtering by Discount stops returning them. Both directions are checked, because a filter that quietly leaks a row is as wrong as one that loses it.

The ledger filtered to Free Items
Filtered to Free Items — the 100%-off line and both halves of the gifted order are here; the partial discounts are not.
The ledger filtered to Discounts
Filtered to Discount — exactly the reverse, with no overlap.

One case worth knowing. On the rare order that carries both a line discount and a whole-order discount, and the two together bring an item to zero, the line's own discount stays listed as a discount (it wasn't the whole price on its own) and the order-level row becomes the free one. The item is counted free once, on the row that finished the job.

Checks

SuiteResult
Local, incl. a control run against the old rule8/8
Staging, against the deployed build6/6
Existing Discounts ledger suite (regression)15/15
Staging smoke regression5/5

The important check here is the one that guards against over-counting: the BOGO line freeing one coffee out of three must still count as one, not three. A change that fixed the 100% case by working everything out from the money would break it, and the Free Items total is what catches that — 6 is correct, 8 would mean it slipped. Run against the previous version, the same checks drop to 3 of 8.

No database change was needed. Verified on staging.