Skip to main content
Back to architectures

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.

Splitting a total tax of ₹0.25 into CGST and SGST: naive rounding versus remainder-preserving split
Split strategy · total tax ₹0.25CGSTSGSTSumGSTR-1
Naive split — round(total / 2) twice₹0.13₹0.13₹0.26drifts one paisa — GSTR-1 reconciliation fails
splitGstHalves — remainder preserved₹0.12₹0.13₹0.25remainder 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.

  1. INV

    Retail invoice

  2. CINV

    Consignment invoice

  3. CSINV

    Consignment sale invoice

  4. CN

    Credit note

  5. DN

    Debit note

generateSequenceNumber() throws without a ClientSession — a failed caller can never burn a number

Four invoice types, one receivables ledger

Invoice typeReceivable derives from
RetailgrandTotal
B2BgrandTotal
Consignment salegrandTotal
Consignment settlementnetReceivable

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 on Next.js and React for an artisan D2C retail brand selling across storefront, B2B job-work, and consignment channels. The engine unifies four invoice types (retail, B2B, consignment sale, consignment settlement) behind one TypeScript pipeline: state resolution → tax split → gapless sequencing → receivables ledger → automated settlement. Every rule below is enforced in TypeScript, 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 retail 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 REST API routes on Node.js, Mongoose 9 on MongoDB Atlas):

  1. State Resolution: Orders derive customerState automatically from the 6-digit shipping pincode in a pre-save hook on MongoDB Atlas, deciding intra-state (CGST/SGST) versus inter-state (IGST) treatment without manual entry.
  2. Tax Split Service: A single TypeScript splitGstHalves(totalTax) helper allocates the remainder paisa to one half, preserving cgst + sgst === totalTax on every odd-paise amount. Grand totals are computed as taxable + cgst + sgst + igst — the identity only holds after the split.
  3. Gapless Sequence Service: generateSequenceNumber() hard-rejects any call without a MongoDB ClientSession when the prefix is GST-critical (INV, CINV, CSINV, CN, DN), making the counter bump atomic with the document write.
  4. Receivables Ledger: receivableAmount, amountReceived, balanceOutstanding, paymentStatus, and embedded receipts[] live directly on the Invoice model in MongoDB Atlas — receivables derive from grandTotal for sale invoices and netReceivable for consignment settlements.
  5. Settlement Automation: A daily Node.js 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 independent round(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 optimisticConcurrency versioning in TypeScript — 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 through the REST API, preserving the Section 143 audit trail for customer-supplied materials.

Database Schema & Optimization

Receivable state is derived, never duplicated: a single TypeScript helper module is the source of truth for receivable amount, paid/partial/unpaid state, last receipt date, and intra/inter-state inference. The Node.js 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 MongoDB Atlas transaction as the document, with full ACID compliance.
  • GSTR-1 reconciliation drift from tax-split rounding is structurally ₹0.00: the invariant is enforced by the only TypeScript 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 in MongoDB Atlas.
  • Consignment partners are settled automatically each month by the Node.js settlement job; the spreadsheet workflow is gone, and outstanding balances are queryable per partner in real time through the REST API.

Engineered capabilities

  1. Gapless invoice sequencing for INV, CINV, CSINV, CN, and DN prefixes minted inside MongoDB transactions
  2. Paise-preserving CGST/SGST split helper that keeps cgst + sgst === totalTax on odd-paise amounts
  3. Automatic intra-state vs inter-state tax treatment from 6-digit pincode state resolution
  4. Receivables ledger on the invoice: amount received, balance outstanding, and embedded receipts
  5. Idempotent monthly consignment settlement cron with a manual recovery path
  6. Vendor bill cross-validation capped at 110% of purchase-order value

Technical performance telemetry

3 days
Settlement Automation Window
110%
Vendor Bill Cap vs PO
OCC __v
Stale-Write Protection

Infrastructure stack

Where this engagement sits

This is 1 of 3 published engagements for the same artisan D2C retail brand. The Compliance Engine documented here is the order-to-cash half of a single custom ERP development effort — 36 models, 118 REST routes, 42 pages — whose production and payout half is written up as the Artisan Manufacturing ERP, and whose paid-media reporting is covered by the Meta Ads Performance Analytics Pipeline. One codebase on Next.js, React, TypeScript, MongoDB Atlas and Vercel, three published views of it.

Questions buyers ask about the Order-to-Cash & GST Compliance Engine

Why does a one-paisa rounding error matter?

Because GSTR-1 reconciles to the paisa. Splitting a ₹0.25 total tax into CGST and SGST by rounding each half independently gives ₹0.13 + ₹0.13 = ₹0.26 — 1 paisa of drift that fails reconciliation at filing time. The Compliance Engine routes every intra-state split through 1 TypeScript helper that allocates the remainder paisa once, producing ₹0.12 + ₹0.13 = ₹0.25. Because that helper is the only code path that can split a tax amount, GSTR-1 drift is structurally ₹0.00.

How does the engine guarantee gapless invoice numbering?

By refusing to mint a number outside a transaction. CGST Rule 46 requires consecutive numbering, so the sequence generator hard-rejects any call for the 5 GST-critical prefixes — INV, CINV, CSINV, CN and DN — that arrives without a MongoDB Atlas session. The counter bump and the document write therefore share 1 unit of work with ACID compliance, and a failed save can never burn a number. All 5 prefixes issue gaplessly under concurrent load on MongoDB Atlas.

How are receivables and consignment settlements tracked?

On the invoice itself, never in a side ledger. Inside the Compliance Engine the receivable amount, amount received, balance outstanding, payment status and embedded receipts all live on the Invoice model, derived from grand total for the 3 sale invoice types and from net receivable for consignment settlements. A Node.js cron job generates the previous month’s vendor settlements in the first 3 UTC days of each month; the operation is idempotent, so a re-run cannot double-settle, and manual generation stays as the recovery path.

What stops a concurrent edit from corrupting money data?

Optimistic concurrency control on every financial model. Invoices, credit notes, debit notes and vendor ledgers version each write, so a stale concurrent edit fails with a version error instead of silently overwriting a figure. Cumulative vendor bills against a purchase order are capped at 110% of the order value at the TypeScript REST API boundary, and B2B job-work orders record the buyer’s inward challan reference to preserve the Section 143 audit trail. The Compliance Engine reconciles a 5,313-row catalog across 4 invoice types.

What does the Compliance Engine run on?

A Next.js App Router backend with 118 REST route handlers on Node.js, Mongoose 9 against MongoDB Atlas, and a React front end using shadcn/ui and Tailwind CSS, deployed on Vercel with NextAuth handling RBAC. There is no SaaS subscription between the brand and its own GST ledgers — the brand owns the TypeScript codebase, the MongoDB Atlas database and every invoice record the Compliance Engine writes.

Could an off-the-shelf ERP or SaaS accounting tool have done this?

Only by accepting its rounding and numbering rules as given. A SaaS ledger will not let you replace the code path that splits a tax amount, and the CGST Rule 46 gapless-sequence requirement has to hold inside the same database transaction as the document write — which needs API-level control the vendor does not expose. Custom ERP development on Next.js, Node.js and MongoDB Atlas put that Compliance Engine invariant in the 1 place it can be enforced.

Ready to scale like Order-to-Cash & GST Compliance Engine?

Stop letting legacy software throttle your growth. Let's design your high-performance architecture.

Request Architecture Blueprint

Explore Custom ERP Solutions by Location, Industry, and Alternatives

Global Architectures