Python / PostgreSQL / React

PGG ERP

Enterprise resource planning system for a manufacturing operation. Inventory, production scheduling, vendor reconciliation, and finance close.

7

end-to-end operations

PGG ERP

Showcase

Product Walkthrough

Four screens from the production GST invoicing system serving Pune Global Group — Express + EJS + Prisma + PostgreSQL on Cloud Run.

PGG ERP — Sales Invoice Dashboard — FY 2025-26 with KPI strip (receivables, payables, overdue), filterable invoice table
Sales Invoice Dashboard — FY 2025-26 with KPI strip (receivables, payables, overdue), filterable invoice table with GST type pills.
PGG ERP — Create Tax Invoice form — auto-numbered INV-2526-185, customer typeahead, line items with live CGST/SGST split
Create Tax Invoice form — auto-numbered INV-2526-185, customer typeahead, line items with live CGST/SGST split, Decimal.js totals.
PGG ERP — A4 Tax Invoice PDF preview — Indian GST-compliant 14-column line item table, totals breakdown, amount in words
A4 Tax Invoice PDF preview — Indian GST-compliant 14-column line item table, totals breakdown, amount in words, bank details.
PGG ERP — Receivables Ledger — customer-grouped outstanding balances with inline payment recording (NEFT/UPI/cash), agin
Receivables Ledger — customer-grouped outstanding balances with inline payment recording (NEFT/UPI/cash), aging breakdown.

Leadership Lens

01 The Call

Chose to build a purpose-fit, server-rendered ERP over adopting an off-the-shelf solution (Tally, SAP B1, Zoho Books), recognising that for a lean manufacturing operation with specific GST-bifurcation requirements and a single operator, a custom tool would be faster to use, cheaper to run, and easier to maintain than any SaaS subscription that forces an over-engineered workflow.

02 The Bet

Bet that a no-SPA, no-build-step architecture (Express + EJS + Prisma) would be faster to ship, easier for a non-technical operator to use, and robust enough for daily production invoicing — accepting that it would never have the feature breadth of commercial ERP software.

03 The Trade-off

Sacrificed multi-user access, role-based permissions, and a polished consumer UI (single master-password auth, no roles, plain CSS) in exchange for a system that fits on a Cloud Run container, scales to zero, and can be fully understood and modified by one engineer.

04 The Outcome

Pune Global Group gained a production-ready invoicing system covering the full operations cycle — outgoing sales invoices with auto-numbered GST-compliant PDFs, purchase recording, and both receivables and payables tracking — replacing a manual or ad-hoc process and eliminating the risk of incorrect tax bifurcation that could trigger GST notices.

05 Coordinated

Aligned with the Pune Global Group operations team on GST compliance requirements (CGST/SGST vs IGST split rules, HSN codes per product line, Indian financial-year invoice numbering reset on April 1) and the business configuration (single GSTIN, state code for intra/inter-state determination). Scoped the system to a single-operator internal tool so that no training programme or multi-seat rollout was required, which was the key sign-off condition from the client side.

06 Where this goes next

Add a dashboard with receivables/payables ageing summary and monthly GST liability report; extend purchase module with a goods-received note workflow; explore an e-invoicing (IRP/IRN) integration to automate GSTN submission for invoices above the e-invoice threshold.

01 Chapter 1

The Business Problem: GST Compliance for a Lean Operation

Pune Global Group operates as a general trading and retail business in India. The core operational need was a local-first invoicing system that handles Indian GST compliance correctly — CGST/SGST for intra-state transactions and IGST for inter-state ones — while also managing suppliers, purchase tracking, and payment receivables/payables.

Off-the-shelf tools like Tally or Zoho are either too heavyweight, too expensive for the volume, or lack the customisation needed for a lean manufacturing operation. The requirement was a fast, server-rendered tool that a single operator can use without training, deployed to the cloud but functional even on a local network.

Compliance Constraint

Indian GST law mandates correct tax bifurcation: intra-state sales must split GST into equal CGST + SGST halves, while inter-state sales must charge the full rate as IGST. Getting this wrong results in tax notices and penalties.

Modules

7

End-to-end operations

Prisma Models

9

Fully relational

GST Rates

5+

Per line item

PDF Engine

A4

Pixel-perfect invoices

02 Chapter 2

Module Breakdown

The ERP is organised into six tightly integrated modules, each handling a distinct part of the business workflow. All modules share a common product catalog and GST engine.

Module A — Customers & Suppliers

Full CRUD for buyers and vendors. Stores GSTIN, state code, business name, phone, and address. State is used to auto-determine GST type (intra vs inter-state).

Module B — Products

Shared catalog used in both sales and purchases. Each product stores name, HSN code, unit, GST rate, and base price — single source of truth across all transactions.

Module C — Sales (Invoicing)

Create outgoing invoices with dynamic line items. Auto-numbering (INV-2526-001), automatic GST calculation, customer dropdown, and notes field. PDF download on detail page.

Module D — Purchases

Record incoming invoices from suppliers (PUR-2526-001). Tracks vendor's own invoice reference. Same dynamic line items and GST engine as sales, but on the payables side.

Module E — Payments (Receivables)

Track money owed by customers. Lists all sales with outstanding balances. Record partial or full payments against specific invoices with method and notes.

Module F — Payments Out (Payables)

Track money owed to suppliers. Lists all purchases with outstanding balances. Record payments to vendors with method tracking (cash, NEFT, UPI).

03 Chapter 3

GST Engine

The GST calculation logic is the heart of the system. A single shared service (src/services/gst.js) handles tax computation for both sales and purchases, ensuring consistent behaviour across the application.

Tax Determination Logic

determineGstType(businessState, counterpartyState) // If business state === customer/supplier state → "CGST_SGST" (split rate equally into CGST + SGST) // If business state !== customer/supplier state → "IGST" (full rate goes to IGST column)

Line Item Computation

computeLineItem(qty, unitPrice, gstRate, gstType) taxableAmount = qty * unitPrice // If CGST_SGST: cgst = taxableAmount * (gstRate / 2) / 100 sgst = taxableAmount * (gstRate / 2) / 100 igst = 0 // If IGST: cgst = 0 sgst = 0 igst = taxableAmount * gstRate / 100 lineTotal = taxableAmount + cgst + sgst + igst

Decimal.js — No Float Math

All monetary calculations use Decimal.js to avoid floating-point rounding errors. Native JS floats are never used for money. This prevents the classic 0.1 + 0.2 !== 0.3 problem in financial software.

Intra-State

CGST+SGST

Equal split of GST rate

Inter-State

IGST

Full rate, single column

Rates Supported

Any %

Per product, per line

04 Chapter 4

PDF Generation

Every sales invoice can be downloaded as a pixel-perfect A4 PDF, generated server-side using Puppeteer (headless Chromium). The PDF template is a dedicated EJS file (views/invoice-pdf.ejs) with no navigation chrome — purely formatted for print.

SectionContent
Header (Left)Business name, address, GSTIN, phone
Header (Center)"TAX INVOICE" heading (bold)
Header (Right)Invoice No (INV-2526-XXX) + Date
Bill ToCustomer name, address, GSTIN, state
Line ItemsSr | Description | HSN | Unit | Qty | Rate | Taxable | CGST% | CGST | SGST% | SGST | IGST% | IGST | Total
TotalsSubtotal | Total CGST | Total SGST | Total IGST | Grand Total
FooterAmount in words (Indian system) + Notes + "Computer generated invoice"

Cloud Run Safe Config

Puppeteer launches with --no-sandbox and --disable-setuid-sandbox flags. The executable path is configurable via PUPPETEER_EXECUTABLE_PATH env var for containerised deployments.

PDF Generation Flow

GET /sales/:id/pdf
Load Sale + Items
Render invoice-pdf.ejs
Puppeteer HTML→PDF
Download A4 PDF

05 Chapter 5

Data Model

The system uses 9 Prisma models backed by PostgreSQL 15. The schema enforces referential integrity through foreign keys and supports auto-incrementing invoice numbers per financial year via atomic DB transactions.

ModelPurposeKey Fields
BusinessSingle-row company configname, gstin, state, address, phone
CustomerBuyers (linked to Sales)name, gstin, state, phone, address
SupplierVendors (linked to Purchases)name, gstin, state, phone, address
ProductShared catalog (Sales + Purchases)name, hsn, unit, gstRate, basePrice
SaleOutgoing invoiceinvoiceNo, date, customerId, gstType, grandTotal
SaleItemLine items on a salesaleId, productId, qty, unitPrice, cgst, sgst, igst
PurchaseIncoming invoice from supplierpurchaseNo, date, supplierId, gstType, grandTotal
PurchaseItemLine items on a purchasepurchaseId, productId, qty, unitPrice, cgst, sgst, igst
PaymentReceivables (money from customers)saleId, amount, method, date, notes
PaymentOutPayables (money to suppliers)purchaseId, amount, method, date, notes

Invoice Number Generation

Sales: INV-YYXX-NNN (e.g. INV-2526-001) Purchases: PUR-YYXX-NNN (e.g. PUR-2526-001)

// YY = FY start year (25 for 2025-26) // XX = FY end year (26 for 2025-26) // NNN = zero-padded sequential, resets each April 1 // Both sequences are independent — atomic DB transaction

Key Relationships

Sale → Customer (many-to-one), SaleItem → Product, Payment → Sale. Purchase → Supplier (many-to-one), PurchaseItem → Product, PaymentOut → Purchase. Product is shared across both sales and purchases.

06 Chapter 6

Architecture

The system is intentionally simple: a pure server-rendered architecture with no SPA framework, no build step, and no client-side routing. This makes it fast to develop, easy to debug, and trivial to deploy.

Request Flow

Browser
Express (EJS)
Prisma ORM
PostgreSQL (Cloud SQL)

Authentication

Single master password stored as a bcrypt hash in environment variables. Session-based authentication using express-session with connect-pg-simple for session storage in PostgreSQL. No multi-user, no roles — this is an internal tool for one operator.

Design Decisions

NO SPA — Pure server-rendered HTML via EJS. No React, no Vue, no build step. Pages load instantly, forms work without JavaScript (progressive enhancement for dynamic line items only).

STORED TOTALS — All GST amounts and grand totals are computed on POST and stored in the database. Views never recompute — they display what was stored. This prevents drift and ensures PDF accuracy.

ATOMIC INVOICE NUMBERS — Invoice and purchase numbers are generated inside a DB transaction to prevent duplicates under concurrent access. The sequence resets each financial year (April 1).

LOCAL-FIRST DEPLOY — Designed to run on localhost for daily use. Cloud Run deployment is the target for remote access, but the app functions perfectly without internet connectivity.

Route Structure

SALES & INVOICING GET /sales — List all invoices GET /sales/new — Invoice form (dynamic line items) POST /sales — Create invoice + compute GST GET /sales/:id — Detail view + payment history GET /sales/:id/pdf — Download PDF (Puppeteer)

PURCHASES GET /purchases — List all purchases GET /purchases/new — Purchase form (supplier dropdown) POST /purchases — Create purchase + compute GST GET /purchases/:id — Purchase detail view

PAYMENTS GET /payments — Receivables — outstanding from customers POST /payments — Record payment received GET /payments-out — Payables — outstanding to suppliers POST /payments-out — Record payment made

07 Chapter 7

Tech Stack

LayerTechnologyReason
RuntimeNode.js 20 LTSCloud Run compatible, fast startup
Web FrameworkExpress 4Minimal, battle-tested, fast
TemplatingEJSNo build step, server-rendered HTML
ORMPrismaType-safe queries, migration support
DatabasePostgreSQL 15Cloud SQL target, ACID guarantees
PDFPuppeteer (Chromium)Pixel-perfect HTML to PDF rendering
Authbcrypt + express-sessionSimple master password, session store in PG
MathDecimal.jsPrecise monetary calculations (no float)
Deploy TargetCloud RunServerless container, scales to zero
Node.js 20Express 4EJSPrismaPostgreSQL 15PuppeteerbcryptDecimal.jsCloud RunCloud SQL

Project Layout

invoicer/ server.js — Express entry point prisma/schema.prisma — 9 models, migrations src/routes/ — customers, suppliers, products, sales, purchases, payments, paymentsOut src/services/gst.js — GST calculation (shared sales + purchases) src/services/pdf.js — Puppeteer PDF generation src/services/invoiceNo.js — Auto-numbering (FY-aware) src/middleware/auth.js — Master password session check views/ — EJS templates (layout, dashboard, CRUD forms) views/invoice-pdf.ejs — PDF-only template (no nav) public/style.css — Single CSS file, no build