Retail / D2C Commerce
Order-to-Cash & GST Compliance Engine
GST-compliant invoicing, consignment settlement, and receivables automation for a D2C handcraft brand
Quick Summary / TL;DR
an artisan D2C brand needed GST-safe invoicing across retail, B2B job-work, and consignment channels without paise-level rounding drift or invoice numbering gaps. ERPStack built an order-to-cash engine with transactional gapless sequencing (CGST Rule 46), paise-preserving CGST/SGST splits, invoice-level receivables, and idempotent monthly consignment settlement.
- GST prefixes sequenced gaplessly
- 5
- Rounding drift in GSTR-1
- ₹0.00
- Catalog rows reconciled
- 5,313
- Invoice types unified
- 4
The one-paisa problem that fails a GST filing
Split ₹0.25 of tax into equal CGST and SGST halves by rounding each independently and you have invented a paisa. At filing time, GSTR-1 reconciliation notices. The engine has exactly one code path that can split a tax amount — and it preserves the remainder.
| Split strategy · total tax ₹0.25 | CGST | SGST | Sum | GSTR-1 |
|---|---|---|---|---|
| Naive split — round(total / 2) twice | ₹0.13 | ₹0.13 | ₹0.26 | ✗drifts one paisa — GSTR-1 reconciliation fails |
| splitGstHalves — remainder preserved | ₹0.12 | ₹0.13 | ₹0.25 | ✓remainder paisa assigned once — identity holds |
Five prefixes, zero gaps
CGST Rule 46 requires consecutive invoice numbering. A counter incremented outside the write transaction leaves a permanent gap the moment a save fails — so the sequence service refuses to mint outside one.
INV
Retail invoice
CINV
Consignment invoice
CSINV
Consignment sale invoice
CN
Credit note
DN
Debit note
generateSequenceNumber() throws without a ClientSession — a failed caller can never burn a number
Four invoice types, one receivables ledger
| Invoice type | Receivable derives from |
|---|---|
| Retail | grandTotal |
| B2B | grandTotal |
| Consignment sale | grandTotal |
| Consignment settlement | netReceivable |
Amount received, balance outstanding, payment status, and embedded receipts live directly on the invoice — receivables are derived state, never a second ledger to reconcile.
The challenge
an artisan D2C brand sells through three simultaneous channels — retail storefront, B2B job-work, and consignment partners — and every channel produces GST documents. Manual invoicing created three concrete filing risks: odd-paise tax rounding silently broke the CGST + SGST = total-tax invariant, invoice numbering could gap when a save failed mid-sequence (a CGST Rule 46 exposure), and consignment partners were settled from spreadsheets with no receivables ledger anywhere.
The architecture
We built the order-to-cash engine inside their custom ERP: typed invoice models with optimistic concurrency control, transactional gapless sequence numbers for all five GST document prefixes, a paise-preserving CGST/SGST split helper keyed off the customer state auto-resolved from the shipping pincode, receivables tracked directly on each invoice with embedded receipts, and an idempotent monthly consignment settlement job that runs in the first three days of each month.
Pincode → state
customerState derives from the 6-digit shipping pincode, deciding CGST/SGST vs IGST automatically.
Vendor bill cap
Cumulative vendor bills per purchase order are rejected beyond 110% of the PO amount.
Job-work trail
B2B job-work orders record the buyer’s inward challan reference for the Section 143 audit trail.
Case Study: Order-to-Cash & GST Compliance Engine
Executive Summary
This case study covers the order-to-cash and tax-compliance layer of a custom ERP built for an artisan D2C brand selling across retail, B2B job-work, and consignment channels. The engine unifies four invoice types (retail, B2B, consignment sale, consignment settlement) behind one typed pipeline: state resolution → tax split → gapless sequencing → receivables ledger → automated settlement. Every rule below is enforced in code, not in operator discipline.
The Business Challenge
Indian GST filing punishes small numerical inconsistencies. Splitting a total tax of ₹0.25 into CGST and SGST by rounding each half independently yields ₹0.13 + ₹0.13 = ₹0.26 — one paisa of drift that fails GSTR-1 reconciliation at filing time. CGST Rule 46 requires consecutive, gapless invoice numbering, so a counter that increments outside the write transaction leaves a permanent gap whenever a save fails. And with three sales channels producing documents concurrently, spreadsheet-based settlement meant nobody could answer "what is outstanding, per partner, right now."
System Architecture
The engine is a set of cooperating invariants inside the ERP's Next.js App Router backend (118 API routes, Mongoose 9 on MongoDB Atlas):
- State Resolution: Orders derive
customerStateautomatically from the 6-digit shipping pincode in a pre-save hook, deciding intra-state (CGST/SGST) versus inter-state (IGST) treatment without manual entry. - Tax Split Service: A single
splitGstHalves(totalTax)helper allocates the remainder paisa to one half, preservingcgst + sgst === totalTaxon every odd-paise amount. Grand totals are computed astaxable + cgst + sgst + igst— the identity only holds after the split. - Gapless Sequence Service:
generateSequenceNumber()hard-rejects any call without a MongoDBClientSessionwhen the prefix is GST-critical (INV, CINV, CSINV, CN, DN), making the counter bump atomic with the document write. - Receivables Ledger:
receivableAmount,amountReceived,balanceOutstanding,paymentStatus, and embeddedreceipts[]live directly on the Invoice model — receivables derive fromgrandTotalfor sale invoices andnetReceivablefor consignment settlements. - Settlement Automation: A daily server cron generates the previous month's vendor consignment settlements during the first 3 UTC days of a new month; the operation is idempotent, and manual generation remains as the recovery path.
[Order Intake] ──(pincode → state)──> [Tax Split] ──(ClientSession)──> [Gapless Sequence]
│
[Settlement Cron] <──(monthly, idempotent)── [Receivables Ledger] <─────────┘
Key Engineering Decisions
- Paise-preserving GST splits: Intra-state invoices must call
splitGstHalves— never independentround(total/2)twice. This one rule is the difference between clean and failed GSTR-1 reconciliation on odd-paise amounts. - Transactional sequence numbers: CGST Rule 46 demands gapless numbering. Sequence generation for the five GST-critical prefixes refuses to run outside a transaction, so a failed caller can never burn a number.
- Optimistic Concurrency Control on financial models: Invoices, credit notes, debit notes, and vendor ledgers carry Mongoose's
optimisticConcurrencyversioning — a stale concurrent edit fails with a VersionError instead of silently overwriting money data. - Vendor bill cross-validation: Cumulative vendor bills against a purchase order are capped at 110% of the PO amount, blocking accidental double-billing at the API boundary.
- Job-work audit trail: B2B job-work orders record the buyer's inward delivery challan reference, preserving the Section 143 audit trail for customer-supplied materials.
Database Schema & Optimization
Receivable state is derived, never duplicated: a single helper module is the source of truth for receivable amount, paid/partial/unpaid state, last receipt date, and intra/inter-state inference. Month-close runs three reconciliation checks — inventory consistency, vendor ledger consistency, and invoice-order reconciliation — and refuses to close a month whose ledgers disagree.
Results & Retrospective
- All five GST document prefixes issue gaplessly under concurrent load — sequence numbers are minted inside the same transaction as the document.
- GSTR-1 reconciliation drift from tax-split rounding is structurally ₹0.00: the invariant is enforced by the only code path that can split a tax amount.
- The brand's 5,313-row product catalog export reconciles against invoiced line items across all four invoice types.
- Consignment partners are settled automatically each month; the spreadsheet workflow is gone, and outstanding balances are queryable per partner in real time.
Engineered capabilities
- Gapless invoice sequencing for INV, CINV, CSINV, CN, and DN prefixes minted inside MongoDB transactions
- Paise-preserving CGST/SGST split helper that keeps cgst + sgst === totalTax on odd-paise amounts
- Automatic intra-state vs inter-state tax treatment from 6-digit pincode state resolution
- Receivables ledger on the invoice: amount received, balance outstanding, and embedded receipts
- Idempotent monthly consignment settlement cron with a manual recovery path
- Vendor bill cross-validation capped at 110% of purchase-order value