Skip to content
Case study · Client projectLive

Status
Live
Built with
Next.js, React, TypeScript
OM ENTERPRISE: Catalogue, counter billing and GST invoices for a building-materials shop.
Catalogue
357 products
8 categories and 9 brands, every size listed
API
98 routes
One wrapper: origin, session, permission, validation
Tests
157 passing
51 unit, 72 on real PostgreSQL, 34 in the browser

Overview

OM ENTERPRISE sells paints, water tanks, bathware, CPVC, PVC, UPVC and SWR pipes and fittings, and plumbing accessories from a counter on Bodeli Road in Naswadi, Chhota Udaipur district, Gujarat.

StackWise built the shop one connected system: a public website where customers browse 357 products across 8 categories and 9 brands and send an enquiry list as an order request; a point of sale where counter staff complete a sale with prices calculated on the server; an admin dashboard for products, pricing, approvals, payments, bills, customers, staff and reports; and a phone app for the owner to approve discounts and see the day's money from anywhere. All of it reads one price list in one PostgreSQL database.

Problem

A building-materials counter carries hundreds of items in many sizes and pressure classes, and the prices live in supplier price lists: PDFs, Excel sheets and scanned pages, some with Hindi or Gujarati headings. Customers phoned to ask what was in stock and what it cost, and every answer depended on who picked up.

Billing had its own rules. The shop issues GST documents, so each sale needs the right document type, a gapless number per financial year, and a layout the accountant and customers already trust. Staff needed room to give a discount within a limit, with anything larger approved by the owner even when he was out of the shop.

Constraints

No online payment on the public site: customers send a list, the shop confirms availability and price, and payment happens at the counter. The bill PDF had to match the shop's existing A4 bill exactly, down to the layout the owner was used to.

The system had to run on hosting that costs little or nothing (Vercel and Neon), need no paid service to work, and print names in Hindi and Gujarati. Real bills stay blocked until the owner confirms the GST registration type, because the document rules follow from it.

Architecture

Three apps in one Next.js 16 codebase: the public site, the counter POS and the dashboard, plus the owner's phone app. Every one of the 98 API routes passes through the same wrapper: origin check, session, permission, then validation, so authorization is enforced on the server and never by hiding a button.

Pricing is one engine, written with exact decimal arithmetic, used by the POS, quotations, website order requests, bills, credit notes and reports. The browser never sends a price; every cart change is re-priced on the server from the active price version.

PostgreSQL (Drizzle, 39 tables, hosted on Neon) protects financial history itself: triggers keep audit logs append-only, freeze a finalized sale, allow issued documents to be cancelled but never edited, and keep document numbers moving forward. Each issued bill stores a complete snapshot, and the A4 PDF renders from that snapshot, so a reprint years later is identical.

The catalogue importer accepts Excel, CSV, PDF, Word, ODS, HTML and photos; scanned price lists go through OCR in English, Hindi and Gujarati, then into a review queue before anything is published. Payments cover an exact-amount UPI QR with manual UTR verification, cash, card, bank transfer, cheque and split payments, with a Razorpay webhook path built against the published API. Bills go out by email, WhatsApp or a share link.

  1. 1Public catalogue with search, filters, variants and an enquiry list that becomes an order request, with WhatsApp one tap away
  2. 2Counter POS with product, SKU and barcode search, server-calculated carts, personal discount limits and remote approvals
  3. 3One pricing engine with immutable price versions, effective dates, quantity tiers and an MRP guard
  4. 4GST documents chosen by registration type: tax invoice, bill of supply, cash memo and credit note, numbered without gaps per financial year
  5. 5A4 bill PDFs that copy the shop's own layout, with Roboto for the rupee sign and Noto fonts for Hindi and Gujarati names
  6. 6Payments by exact-amount UPI QR with UTR verification, cash, card, bank transfer, cheque and split payments; Razorpay built in behind a signed, idempotent webhook
  7. 7Catalogue import from Excel, CSV, PDF, Word, ODS, HTML and photos, with OCR for scanned price lists and a review step before publishing
  8. 8Owner's phone app for approvals, the day's takings by cash and UPI, and finding any bill by number
  9. 9Staff accounts with role templates, per-user overrides, counter PINs, an append-only audit log and database row-level security
  10. 10Inventory ledger, suppliers and purchase receipts built into the schema and switched off until the shop counts its stock

At the counter

  1. POS sale

    Search by name, SKU or barcode; exact variant and unit

  2. Pricing engine

    Server-side, exact decimals, discount limits

  3. UPI QR or cash

    Exact amount; UTR verified; split payments

  4. A4 bill

    GST document type, gapless number, snapshot PDF

On the website

  1. Catalogue

    357 products, sizes and variants, same price list

  2. Enquiry list

    Pick sizes and quantities while browsing

  3. Order request

    Name and mobile number; WhatsApp for questions

  4. Dashboard

    Shop confirms availability and price

Behind it

  1. Next.js API

    98 routes, one wrapper: origin, session, permission, validation

  2. PostgreSQL on Neon

    39 tables; triggers keep history append-only

  3. Owner's phone app

    Approvals, today's money, find any bill

One price list and one database behind the website, the counter and the owner's phone. The browser never sets a price.

Tradeoffs

No online payments on the website, on purpose. For a counter that deals in pipe lengths and tank sizes, confirming availability and price before money changes hands avoids refunds and arguments, and it matches how the shop already works.

Re-pricing every cart change on the server costs a round trip, but it removes a whole class of bugs where the browser and the bill disagree.

The database enforces rules the application also enforces. That is deliberate duplication: if a future change to the app gets something wrong, the history of bills and payments still cannot be edited or deleted.

Two parts are honest about their limits. The Razorpay path was built and tested against simulated signed webhooks, not a live account, and the GST document rules follow published CBIC guidance that the shop's accountant has to confirm before real bills are issued.

What I'd do differently

Start with the import. Getting 357 products in from seven file formats and scanned pages took more care than any screen, and until the catalogue was real nothing else could be judged.

The bill mattered more to the owner than any dashboard. Matching the existing A4 layout exactly was the moment the system felt like his, and it should have come earlier.

The owner's phone app came last and should have come first. Approving a discount from wherever he is was the feature he used on day one.

Next.jsReactTypeScriptPostgreSQLDrizzleNeonVercelTailwind CSSPDFKitTesseract.jsVitestPlaywright

Building something similar?

We can walk you through how this was built on a free call.

Free consultation

Have a project in mind?

Tell us what you're building. We'll suggest the simplest way to ship it, and tell you honestly if we're not the right fit.

Book a callEmail us
  1. 1

    Tell us about the project

    A few quick questions in the chat, or an email.

  2. 2

    Reply within 24–48 hours

    With questions, a rough scope and a starting price.

  3. 3

    Free call, then a written quote

    No commitment until you're happy with it.