Skip to content

Invoices (finance lens)

Customer invoices and Group invoices from the Finance perspective — GSTIN + HSN fields, line-level tax calc, and the permissions around create / approve / void.


1. Two invoice flavours

Customer invoice — Invoice

One row per billable line. Polymorphic via referenceType / referenceId so it can hang off:

  • A booking — referenceType = 'booking'.
  • A B2B third-party sale — referenceType = 'b2b_third_party_sale' (no underlying booking).
  • A group invoice line — rolled up via GroupInvoice.

Columns relevant to finance (see supabase/migrations/2026*):

  • number — printed invoice number.
  • installmentNo — when the invoice is one installment of a staggered bill.
  • amountDue, amountPaid, balanceDue, status.
  • dueDate, issuedAt.
  • hsnCode, sacCode — added by gst_primitives.sql.
  • journalEntryId — link to the posting journal once approved.

Group invoice — GroupInvoice

Covers multi-passenger / multi-booking aggregation. Line items are stored as JSONB (no physical GroupInvoiceLine table) — so HSN/SAC lives inside the JSON payload, not in a column.

UI: src/components/invoices/GroupInvoiceDialog.tsx. The dialog handles:

  • Line item composition (name, HSN/SAC, rate, qty, unit price).
  • Line-level GST calculation.
  • Customer / payer selection (multi-booking group).
  • Issue / reissue / cancel actions.

Posting path: when a group invoice is issued, the handler inserts the journal via createJournalWithLines with referenceType = 'group_invoice'. Cancellation posts a reversal with referenceType = 'group_invoice_cancellation'.


2. GSTIN + HSN fields

GSTIN capture

Table Column
Customer gstNumber
Supplier gstNumber
Agent gstNumber

See supabase/migrations/20260418150000_gst_primitives.sql. Validated client-side via isValidGstin() (src/lib/gstin.ts:40).

HSN / SAC on lines

Table Columns
QuotationLineItem hsnCode, sacCode
SupplierTransaction hsnCode, sacCode
Invoice hsnCode, sacCode
GroupInvoice (JSON lines) hsnCode / sacCode inside the JSONB line object

Business rule (UI-enforced, not DB CHECK): pick HSN for goods, SAC for services. Rows can legally carry both in legacy data.


3. Line items and tax calc

On a customer invoice:

  • Each line has a taxableValue (subtotal before tax) and a rate (GST %).
  • CGST + SGST when the place-of-supply matches company state; IGST when inter-state. See InvoiceSource.isInterState in src/lib/gstReports.ts:79.
  • Totals are computed on the fly for display and stored on the row on issue.

B2CL threshold

For unregistered buyers, invoices ≥ ₹2,50,000 move from b2cs (aggregated) to b2cl (per-invoice) in GSTR-1. Threshold constant at src/lib/gstReports.ts:38.


4. Endpoints (finance lens)

Method Path Permission Notes
GET /finance/invoices finance.view List customer invoices with filter params.
POST /finance/invoices finance.create Create a customer invoice.
POST /finance/invoices/schedule finance.create Create a scheduled / installment invoice (src/lib/api.ts:26238).
GET / POST / PATCH / DELETE /finance/invoices/:id* mixed — see handler. Update, approve, void paths.

Group invoices live under /group-invoices/* — not strictly a /finance/* path, but their posting goes through the same journal machinery.


5. Permissions

Action Permission
Create customer invoice finance.create
Edit customer invoice finance.edit
Approve / void the posting journal approvals.approve (+ maker-checker)
Create group invoice group_invoices.create
Issue group invoice (posts journal) group_invoices.issue
Cancel group invoice (posts reversal) group_invoices.cancel
View group invoices group_invoices.view

Role highlights (see docs/PERMISSIONS.md):

  • FINANCE_MANAGER — full invoice create / edit / issue / cancel.
  • ACCOUNTANT — create / edit only (no issue / cancel).
  • CASHIER — record payments against invoices via finance.payments.record; cannot create or void invoices.

6. Void vs cancellation

Voiding an invoice = cancelling the posting journal = posting a reversal. The reversal goes through the journal state machine (approved automatically, since reversals are auto-approved; see journals.md §8).

Voiding after payments have been recorded against the invoice is not automatic — the refund has to be posted as a separate payment voucher first, then the invoice voided. UI should guide this flow.


  • Journals — the posting path (reversal, maker-checker).
  • GST — GSTIN + HSN + GSTR-1/3B.
  • Vouchers — the receipt side (what allocates payments to invoices).
  • src/components/invoices/GroupInvoiceDialog.tsx — group invoice UI.