A refund-invoice builder. The author wanted GST always positive, used `Math.abs`. Net effect: GSTR-1 filings show tax collected on credit notes — auditor flagged it as inconsistent with the actual book entries.
The vulnerable diff
// billing/refund-invoice.ts
function buildRefundInvoice(refundRupees: number) {
// BUG: Math.abs strips the sign — tax positive on a negative invoice
const gst = Math.abs(refundRupees) * 0.18;
return {
amount: refundRupees, // -1000 (negative for refund)
gst: gst, // +180 (positive — wrong sign for credit note)
total: refundRupees + (refundRupees < 0 ? -gst : gst),
};
}What is wrong
Indian GST (and most VAT regimes) treats credit notes as negative invoices — the amount, the tax, and the total all carry a negative sign. GSTR-1 reports separate tax credited (refunds) from tax collected (sales); a positive tax on a negative-amount row breaks that separation. The fix is straightforward: tax always inherits the sign of the amount it's computed on. `Math.abs` is the wrong tool. Multiplication preserves sign for free.
The attack
Auditor's flag:
GSTR-1 row for the period:
Sales tax (output): 18000.00
Credit-note tax: 0.00 (should be -1800 from the refund)
Net tax payable: 18000.00 (should be 16200)
Cumulative impact: GST overpaid in every period that has refunds.Refund volume = error volume.
Mesrai's review comment
mesraipilot · Bot · reviewed 1 min ago
[mesrai] [code-review] [Logic] [Tax-Sign] [medium]
`Math.abs(refundRupees) * 0.18` produces a positive GST on a
negative invoice — incompatible with GSTR-1 reporting and with
the rest of your accounting.
Tax should carry the same sign as the amount it taxes:
function buildRefundInvoice(refundRupees: number) {
const gst = refundRupees * 0.18; // sign follows amount
return {
amount: refundRupees,
gst,
total: refundRupees + gst,
};
}
For a refund of ₹1000:
amount: -1000, gst: -180, total: -1180
Now GSTR-1 correctly nets the refund's tax against output tax.The fix
// billing/refund-invoice.ts — fixed (with integer paise for currency safety)
function buildRefundInvoicePaise(refundPaise: number) {
// GST 18% via basis points: 1800 / 10000
const gstPaise = Math.round((refundPaise * 1800) / 10_000);
return {
amountPaise: refundPaise,
gstPaise,
totalPaise: refundPaise + gstPaise,
};
}Tax inherits sign from amount. Integer paise math (cluster 8 post 1 applies here too). Refund rows now net correctly in GSTR-1.
Why human review missed it
Tax-sign bugs are hard to find in code review because the code looks correct in isolation — positive tax on positive amount works. The error only shows on credit notes / refunds. Mesrai catches `Math.abs` on currency variables and recommends sign-preserving math.
Related rules + further reading
Mesrai rule pack: logic/tax-sign-matches-amount — flags Math.abs() on currency variables in tax calculation.
Indian GST Act § 34 — credit and debit notes.
GSTR-1 schema — separate fields for sales and credit-note tax.
Takeaway
Tax sign follows amount sign. No Math.abs. Credit notes flow correctly through GSTR-1. Mesrai catches the strip.