mb mybillbook private beta
← All features

Invoicing

GST-correct invoices, computed server-side, frozen at finalize. No floating-point drift, no per-FY numbering races, no manual intra-vs-inter judgement.

Three states. One direction.

draft finalized ↓ optional cancelled

draft is editable. finalized is immutable — the line items, GST math, customer snapshot, and invoice number are frozen in a JSONB snapshot_jsonb column for 6-year retention. cancelled requires no payments to be allocated (guarded by ErrCannotCancelPaid).

Intra-state vs inter-state — computed automatically.

The same lines, with the supplier in Karnataka (state 29). One invoice goes to a Karnataka buyer, one to a Maharashtra buyer. mb makes the call.

Intra-state

Buyer in Karnataka (state 29)

Buyer state 29 = supplier state 29 → CGST 9% + SGST 9%

HSN Description Qty Rate Taxable
7208 MS plates 6mm 10.000 1,000.00 10,000.00
7308 Welded structures 1.000 5,000.00 5,000.00
Subtotal
₹15,000.00
CGST @ 9%
₹1,350.00
SGST @ 9%
₹1,350.00
IGST
₹0.00
Total
₹17,700.00
Inter-state

Buyer in Maharashtra (state 27)

Buyer state 27 ≠ supplier state 29 → IGST 18%

HSN Description Qty Rate Taxable
7208 MS plates 6mm 10.000 1,000.00 10,000.00
7308 Welded structures 1.000 5,000.00 5,000.00
Subtotal
₹15,000.00
CGST
₹0.00
SGST
₹0.00
IGST @ 18%
₹2,700.00
Total
₹17,700.00

Both invoices are saved with the same line data — mb infers intra-vs-inter from the supplier business's state_code compared to the buyer's place_of_supply. No checkbox to forget.

Gapless numbering, enforced at the DB.

GST law requires invoice numbers to be sequential within a financial year. mb stores the last-issued sequence in invoice_counters(business_id, fy, last_seq) and increments it inside the finalize transaction with SELECT FOR UPDATE. Two simultaneous finalize requests cannot get the same number, ever.

-- Inside the finalize tx (simplified)
SELECT last_seq FROM invoice_counters
WHERE business_id = $1 AND fy = $2
FOR UPDATE;        -- ← row-locked until this tx commits

UPDATE invoice_counters
SET last_seq = last_seq + 1
WHERE business_id = $1 AND fy = $2
RETURNING last_seq;

6-year retention, built in.

On finalize, mb writes a JSONB snapshot_jsonb column that captures the full pricing, lines, customer details, business details, and tax math as they existed at the moment of finalize. Customer renames, address edits, GSTIN updates — none of them retroactively change a filed invoice. The audit trail is exactly what you filed.

Same pattern on credit notes. Same 6-year horizon mandated by the CGST Rules.