PHARMA ERP + AIWholesale DistributionMulti-TenantSolo Build

MedStock IQ

Smarter inventory. Intelligent distribution. An AI copilot that never acts without approval.

A case study on how I designed and built MedStock IQ, a multi-tenant ERP for pharmaceutical wholesalers, stockists and hospital pharmacy chains. It combines a deterministic core (strict FEFO dispatch, cold-chain and credit checks, and a double-entry inventory ledger) with MedStock Assist, a multimodal AI copilot that reads supplier bills, takes voice input and drafts actions a human must approve.

1. Requirements→2. User Needs→3. Architecture→4. Development→5. Testing→6. Deployment

Project Snapshot

The problem, the users and the approach at a glance

Executive Summary
01. The Problem

Wholesalers run on 3–7% margins while losing stock to expiry, cold-chain failures, bad credit and paper-based compliance.

02. Target Users

Owners, CFOs and accountants, warehouse heads, sales executives, dispatch staff and chief pharmacists.

03. Core Insight

The rules that protect money and patients (expiry, temperature, credit, licences) must be deterministic code, not operator judgement.

04. The Solution

An ERP core with four deterministic engines, batch-level genealogy, GST invoicing and an append-only audit trail.

05. AI Layer

MedStock Assist: bill OCR, voice dictation, 12+ typed ERP tools (no raw SQL) and two-phase action proposals.

06. Stack

Next.js 14, NestJS 10, Prisma 5.22 on Neon PostgreSQL 16, Entra ID, and Azure OpenAI / Gemini / OpenAI.

01 / Purpose

Why I Built MedStock IQ

Pharmaceutical wholesale looks like ordinary distribution, but every unit has an expiry date, some need 2–8 °C storage, buyers often buy on credit, and regulators expect a paper trail for every movement. Generic ERPs treat medicines like any other SKU, and the gaps show up as write-offs, bad debt and failed audits.

4Deterministic engines
12+Typed AI tools
10Schema domains
3Swappable AI providers
⏳Expiry Write-Offs

FIFO or manual picking leaves older batches in the corner of the warehouse until they expire and have to be written off.

❄️Cold-Chain Fragility

Vaccines, biologics and insulin must stay between 2 °C and 8 °C. One wrong zone can ruin an entire lot.

💳Credit Leakage

Sales teams keep booking orders for pharmacies already over their credit limit, which turns into bad debt.

📜Compliance Overhead

21 CFR Part 11, Drug Licence Forms 20B/21B, Schedule H/H1 registers and GST all need traceable records.

🧾Slow Inwarding

Supplier bills are typed in line by line at the loading dock, which is slow and error-prone.

🤖Risky AI

An assistant that writes SQL or edits stock on its own is a liability when the data is medical and financial.

The mission

“Make it impossible to ship an expired, mis-stored or unpaid-for medicine by accident, and give every operator an AI assistant that proposes rather than acts.”

02 / Step 1 — Requirement Gathering

Understanding the Problem Before Writing Code

I began by mapping the full wholesale lifecycle (procure, inward, store, sell, invoice, collect) and the six roles that touch it. Each role's pain point became a requirement, and each regulatory obligation became a rule the system has to enforce.

🔄STEP 1

Lifecycle Mapping

Traced PO → GRN → batch & bin → FEFO sales order → GST invoice → payment → audit.

👥STEP 2

Persona Analysis

Defined six personas with their focus, the modules they use and the value they need.

⚖️STEP 3

Regulatory Review

Listed 21 CFR Part 11, Form 20B/21B, Schedule H/H1 and GST invoicing obligations.

🧪STEP 4

Risk Scenarios

Credit over-limit orders, near-expiry batches, cold-chain mis-slotting and recalled lots.

Who I Was Building For

STAKEHOLDERPAIN POINTWHAT THEY NEED FROM THE SYSTEM
CEO / OwnerNo clear view of margin by customer, manufacturer or SKU.Executive KPIs and true batch-cost (COGS) margin reporting.
CFO / AccountantBad debt and manual GST reconciliation.Automatic credit-ceiling locks, a payment ledger and GST summaries ready for GSTR-1/3B.
Warehouse HeadExpired stock and slow picking.Strict FEFO, rack/shelf/bin mapping, cold-chain zoning and quarantine.
Sales ExecutiveCan't tell if an order will ship or be blocked.Instant credit status and a FEFO allocation preview while booking.
Dispatch StaffPicking the wrong batch or bin.Picklists bound to exact batch and bin coordinates.
Chief PharmacistAudit and licence risk.An immutable audit trail, drug licence checks and Schedule H/H1 registers.
Functional · ERP
  • ✓PO lifecycle (Draft → Received)
  • ✓GRN with batch, expiry, MRP & bin
  • ✓Sales orders, picklists, dispatch
  • ✓GST invoices (CGST/SGST/IGST, HSN)
  • ✓Payments: NEFT, RTGS, cheque, cash, UPI
Functional · Control
  • ✓Batch-level genealogy
  • ✓Strict FEFO allocation
  • ✓Warehouse → Zone → Rack/Shelf/Bin
  • ✓Cold-chain 2–8 °C validation
  • ✓Quarantine & recall
Functional · Intelligence
  • ✓Expiry horizon tiers (<30, 31–60, 61–180, >180 days)
  • ✓Financial value-at-risk
  • ✓Reorder triggers
  • ✓Dead-stock & aging analytics
Functional · Assist (AI)
  • ✓Natural-language queries
  • ✓Bill / PDF / image / CSV ingestion
  • ✓Voice dictation
  • ✓Two-phase action proposals
Functional · Insights
  • ✓Executive command centre
  • ✓True COGS & gross margin
  • ✓GST tax summary
  • ✓21 CFR Part 11 audit trail
Functional · Platform
  • ✓Multi-tenant isolation
  • ✓Entra ID SSO + RBAC
  • ✓JWT access/refresh tokens
  • ✓OpenAPI docs
Non-functional requirements (the constraints that shaped the architecture)

Integrity

Quantities never change in place. Every movement is a signed ledger entry inside an ACID transaction.

Compliance

An append-only audit log with before/after JSON snapshots, user, IP and timestamp.

Tenant isolation

Every operational table is scoped by organizationId on every query.

AI safety

No raw SQL, input guardrails, and human approval before any write.

Vendor independence

AI providers are hot-swappable, and their keys are encrypted with AES-256-GCM.

Type safety

TypeScript end to end, with shared domain types from UI to database.

03 / Step 2 — What to Give the User

Translating Requirements Into Product Decisions

The next step was deciding what each persona should actually see and be stopped from doing. Most of MedStock IQ's value comes from what it prevents, so I turned each complaint into a guard or a view.

What I heard

“We keep finding expired stock at the back of the rack.”

What it really means

Picking can't be left to the operator.

What I built

A strict FEFO engine that allocates from the earliest viable batch and skips anything inside the minimum shelf-life window.

What I heard

“Sales booked a big order for a pharmacy that already owes us.”

What it really means

Credit limits must be enforced when the order is booked, not after it ships.

What I built

A credit ceiling check: if outstanding balance plus the new order exceeds the limit, the order is locked until Finance overrides it.

What I heard

“Someone put insulin in the ambient zone.”

What it really means

Storage rules must be physically impossible to break.

What I built

A cold-chain check that rejects any 2–8 °C product movement into a non-refrigerated zone.

What I heard

“Typing supplier bills takes forever.”

What it really means

Inwarding needs to be much faster, without trusting unchecked AI.

What I built

Multimodal bill parsing that matches SKUs and suppliers, then presents a draft purchase order for approval.

What I heard

“I don't want the AI touching our books.”

What it really means

The AI should draft, and a human should decide.

What I built

Two-phase action proposals: a dry run with a diff, then “Approve & Execute”, all through typed tools with no SQL.

What I heard

“What's actually about to expire, in rupees?”

What it really means

Expiry risk should be shown as money, not just dates.

What I built

Expiry horizon tiers plus value-at-risk: Σ (quantity × purchase price) across risky batches.

The User Journey I Designed For

1

Sign in

Microsoft Entra ID SSO with role-based access.

2

Inward goods

Receive a purchase order through GRN, capturing batch, expiry and bin, with a barcode printed.

3

Book an order

Credit status is checked first, then batches are allocated FEFO and shown before submitting.

4

Pick, pack & invoice

A bin-accurate picklist, then a GST invoice with batch numbers on every line.

5

Watch the risk

The command centre shows valuation, receivables and expiry value-at-risk.

6

Ask MedStock Assist

Upload a bill or dictate a request, then review and approve the proposal.

04 / Step 3 — Designing the Architecture

How the Architecture Was Designed, Step by Step

I designed the architecture outward from the data. First the ledger and the rules that can never be broken, then the API that enforces them, and only then the AI layer, which is only allowed to go through that API's own services.

DESIGN STEP 01Model inventory as a double-entry ledger
THE QUESTION

How do we pass an audit of every unit's history?

THE DECISION

Every movement (purchase, sale, returns, adjustments, damage/expiry, transfers) writes a signed entry, so the new balance is always the previous balance plus the change.

WHY

Quantities are always derivable and replayable, and never silently edited.

DESIGN STEP 02Put the business rules in deterministic engines
THE QUESTION

Where should the rules that protect money and safety live?

THE DECISION

Four service-layer engines: strict FEFO, expiry horizon & VaR, credit ceiling, and cold-chain storage checks.

WHY

The same input always gives the same answer, and each engine can be tested on its own.

DESIGN STEP 03Design the schema for multiple tenants from day one
THE QUESTION

How do several distributors share one deployment safely?

THE DECISION

An indexed organizationId on every operational table, with tenant scoping applied to every Prisma query.

WHY

Complete data isolation without separate databases per customer.

DESIGN STEP 04Use one language end to end
THE QUESTION

Which stack keeps a large domain model consistent?

THE DECISION

Next.js 14 and NestJS 10 in TypeScript, with Prisma 5.22 on Neon PostgreSQL 16.

WHY

Shared types from UI to database, fewer context switches, and good concurrency on Node.js.

DESIGN STEP 05Harden the API gateway
THE QUESTION

How is every request authenticated and validated?

THE DECISION

NestJS guards (JWT + RBAC), class-validator pipes, CORS, transform interceptors and auto-generated OpenAPI docs.

WHY

Nothing reaches a domain engine without being authenticated, authorised and validated.

DESIGN STEP 06Give the AI tools, not SQL
THE QUESTION

How can an LLM help without corrupting data?

THE DECISION

The AI can only call 12+ typed ERP tools that wrap the same services the UI uses, and every input first passes pharma guardrails.

WHY

Removes SQL injection and hallucinated queries, and the AI inherits every business rule.

DESIGN STEP 07Split AI actions into two phases
THE QUESTION

Who has the final say on a write?

THE DECISION

Phase 1 runs the tool as a dry run and stores an ActionProposal. Phase 2 executes only after the user clicks “Approve & Execute”.

WHY

Human-in-the-loop by construction, with a reviewable diff.

DESIGN STEP 08Keep AI providers swappable
THE QUESTION

What if a model is down, too expensive, or not allowed?

THE DECISION

A provider router across Azure OpenAI (GPT-4o), Google Gemini 1.5 and OpenAI, with keys encrypted in a CryptoVault (AES-256-GCM).

WHY

No vendor lock-in, and credentials stay safe even inside database backups.

Layered System Architecture

Final Topology
Layer 1

Client & Presentation

Next.js 14 App Router

Entra ID (MSAL) sign-inCommand centre (Recharts)Ctrl+K spotlight searchFEFO & cold-chain hubAssist studio (voice + OCR)
Layer 2

API Gateway & Security

NestJS 10 / Express

CORSOpenAPI 3.0 (/api/docs)ValidationPipeJwtAuthGuard + RBACInterceptors & filters
Layer 3

Deterministic Business Engines

Service layer

Strict FEFO allocationExpiry horizon & VaRCold-chain sentinelCredit ceiling sentinelDrug licence 20B/21B sentinel
Layer 4

MedStock Assist AI

Copilot architecture

Hot-swappable AI routerPharma guardrailsZero-SQL RAG retriever12+ typed ERP toolsTwo-phase action generatorCryptoVault
Layer 5

Cloud Persistence

Neon Serverless PostgreSQL 16

PgBouncer poolingPrisma ORM 5.22Multi-tenant tablesDouble-entry ledgerAudit logsAI conversation threads
How it works:Each layer has one responsibility and talks only to the layer next to it, which keeps the system testable and replaceable piece by piece.

Wholesale Lifecycle Through the System

End-to-End
Purchase OrderDraft → Approved
↓
GRN InwardingBatch, expiry, MRP, bin
↓
Batch & BinCold-chain checked
↓
Sales OrderCredit check + FEFO
↓
Pick & PackBin-accurate picklist
↓
GST InvoiceBatch on every line
↓
Payment & AuditLedger + audit log
How it works:Stock enters through a GRN into a specific batch and bin, leaves only through FEFO-allocated sales orders, and every step writes to the ledger and the audit log.
05 / Under the Hood

The Engines and the AI, Up Close

The four engines that enforce the rules, the ledger underneath them, and the path an AI request takes before anything is written.

Strict FEFO Allocation Engine

Engine 1
Shelf-life cutoffToday + 15 days (default)
↓
Candidate filterTenant, product, AVAILABLE, qty > 0, expiry > cutoff
↓
SortexpiryDate ASC, then createdAt
↓
AllocateExhaust the earliest lot first
↓
Bind locationsWarehouse · rack · shelf · bin
How it works:Example: a request for 120 units takes 80 from a batch with 45 days left and 40 from a batch with 18 months left, and returns the exact bins to pick from.

The Other Three Engines

Engines 2–4
Expiry horizon & VaR

Δt = expiry − today

Critical ≤ 30 days · near-expiry 31–60 · medium 61–180 · safe > 180. VaR = Σ quantity × purchase price over risky batches.

Credit ceiling

Outstanding + new order > limit?

If yes, the order stays SUBMITTED, is marked credit-blocked, and dispatch halts until a Finance Manager overrides it.

Cold-chain

2–8 °C product → refrigerated zone only

Any inward receipt or move of cold-chain stock into a non-refrigerated zone is rejected with a clear error.

Drug licence

Form 20B / 21B validity

If a customer's drug licence has expired, invoice dispatch is blocked automatically.

Quarantine

One click, every location

A QUARANTINED batch is removed from all picklists and sales allocation immediately.

True margin

COGS from actual batch cost

Gross margin uses each batch's real purchase price, not a moving average.

How it works:Each engine is a small, deterministic function that the UI and the AI tools both call.

Double-Entry Inventory Ledger

Data Integrity
TransactionEffectSignSource document
PURCHASEStock increase+Goods Receipt (GRN)
SALEStock decrease−Sales order / invoice
PURCHASE_RETURNStock decrease−Debit note
SALES_RETURNStock increase+Credit note
STOCK_ADJUSTMENTReconciled variance±Physical stock count
DAMAGE / EXPIRYStock decrease−Quarantine record
TRANSFER_IN / OUTRe-location±Inter-warehouse transfer
How it works:New balance = previous balance + change. Quantities are never edited in place without a ledger entry.

MedStock Assist — Request to Approved Action

AI Pipeline
InputText · voice · bill (PDF/image/CSV)
↓
Multimodal parserVision / PDF / CSV
↓
Pharma guardrailsNo negative stock, sane dates
↓
Zero-SQL retrieverLive DB ground truth
↓
AI router
Azure GPT-4oGemini 1.5OpenAI
↓
Proposal cardDiff, taxes, warnings
↓
Approve & ExecutePrisma transaction
How it works:Questions get a grounded reply. Requests that would change data become a proposal card, and nothing is written until a person clicks “Approve & Execute”.

Typed ERP Tools the AI Can Call

Zero-SQL
ToolWhat it does
queryInventoryLive stock, warehouse coordinates and available quantities.
checkNearExpiryExposureBatches expiring within 30/60/90 days, with value-at-risk.
createSalesOrderWithFefoDraft sales order with automatic FEFO batch allocation.
createDraftPurchaseOrderPurchase order from reorder thresholds and supplier terms.
ingestSupplierInvoiceParses a supplier bill and matches line items to SKUs.
liquidateNearExpiryStockProposes markdown orders for stock nearing expiry.
getCustomerCreditReportBalance, credit limit and payment history.
getGrossMarginReportProfitability using true batch COGS.
How it works:The model never writes SQL. It can only call these typed functions, which reuse the same services and rules as the UI.

Data Model — 10 Functional Domains

Prisma Schema
DomainTables
Tenancy & RBACorganizations, users, roles, permissions, role_permissions
Catalogproducts, categories, manufacturers
Spatial warehousewarehouses, warehouse_zones, warehouse_locations
Inventorybatches, inventory_transactions
Purchasingsuppliers, purchase_orders, purchase_order_items, goods_receipts
Salescustomers, sales_orders, sales_order_items, batch_allocations
Billinginvoices, sales_returns, purchase_returns
Approvalsstock_adjustments, approval_requests
Audit & telemetryaudit_logs, notifications, payments
AI memoryai_conversations, ai_chat_messages, ai_settings
How it works:A fully normalised schema, with every operational table scoped by organizationId.
06 / Technology

Tech Stack & Why Each Piece Was Chosen

Frontend

Next.js 14 · React 18 · Tailwind

App Router SSR, fast route transitions and an enterprise slate/emerald theme.

Client data

TanStack Query · React Hook Form · Zod

Optimistic updates, cached fetching and type-safe forms.

Charts

Recharts

Command-centre KPIs and expiry distribution curves.

Backend

NestJS 10 · Express

Modular domain design with dependency injection, guards and pipes.

Data

Prisma 5.22 · Neon PostgreSQL 16

Typed models and migrations on serverless Postgres with PgBouncer pooling.

Auth & security

Entra ID (MSAL) · JWT · bcrypt · AES-256-GCM

SSO, rotating access/refresh tokens, hashed passwords and encrypted AI keys.

AI providers

Azure AI Inference · Gemini SDK · OpenAI SDK

Swappable GPT-4o, Gemini 1.5 Pro/Flash and OpenAI.

Voice

Web Speech API

Hands-free dictation on the warehouse floor, straight into the Assist dock.

Testing

Playwright

Headless end-to-end regression, plus headed runs for recorded walkthroughs.

07 / Step 4 — Development

Building It Phase by Phase

I built MedStock IQ in the same order the architecture was designed: data first, then the rules, then the workflows, and the AI last. That way the AI could only reuse rules that were already tested.

  1. 1

    Multi-tenant schema & ledger

    Goal: A data model that can pass an audit.

    • •Prisma schema across 10 domains
    • •organizationId scoping on every table
    • •inventory_transactions double-entry ledger

    Output: A tenant-safe, auditable database foundation.

  2. 2

    Gateway, identity & RBAC

    Goal: Every request authenticated and validated.

    • •Entra ID (MSAL) SSO + JWT access/refresh
    • •RBAC guards and validation pipes
    • •OpenAPI docs at /api/docs

    Output: A secure, documented API surface.

  3. 3

    Procurement & GRN inwarding

    Goal: Controlled entry of stock.

    • •PO status lifecycle
    • •GRN capture of batch, expiry, MRP and bin
    • •ACID transaction: batch + ledger + PO update

    Output: Every batch traceable to its PO and GRN.

  4. 4

    Deterministic engines

    Goal: Encode the rules that can't be broken.

    • •Strict FEFO with shelf-life cutoff
    • •Expiry horizons & VaR
    • •Credit ceiling, cold-chain and drug licence checks

    Output: Four tested engines shared by the UI and the AI.

  5. 5

    Sales, invoicing & payments

    Goal: Order to cash.

    • •FEFO allocation preview while booking
    • •Picklists, packing slips, dispatch
    • •GST invoices and the payment ledger

    Output: A full commercial cycle with batch numbers on every invoice line.

  6. 6

    Insights & compliance

    Goal: Visibility for owners and auditors.

    • •Command centre KPIs
    • •True-COGS margin & GST summary
    • •Append-only 21 CFR Part 11 audit log

    Output: Executive and regulatory reporting.

  7. 7

    MedStock Assist

    Goal: A safe multimodal copilot.

    • •Provider router + CryptoVault
    • •Guardrails, zero-SQL tools and bill OCR
    • •Two-phase proposals and voice dictation

    Output: An AI that drafts and a human who approves.

08 / Step 5 — Testing

How I Verified It Works

I tested the deterministic core as rules with exact expected outcomes, and ran the whole product end to end with Playwright on every change.

End-to-end regression (Playwright)

Headless runs (npm run test:headless) cover the critical user journeys.

  • ✓Login and role-based navigation
  • ✓FEFO simulation and order creation
  • ✓Invoicing flow
  • ✓GRN inwarding

Business rules

Each engine is checked against the edge cases it exists to catch.

  • ✓FEFO skips batches inside the shelf-life cutoff
  • ✓Credit-exceeded orders lock at SUBMITTED
  • ✓Cold-chain stock into an ambient zone is rejected
  • ✓Quarantined and expired-licence cases are blocked

Ledger & tenancy

The data must always stay consistent and isolated.

  • ✓Balances equal the sum of ledger entries
  • ✓GRN writes batch, ledger and PO in one transaction
  • ✓Queries never cross organizationId
  • ✓Audit log is append-only

AI safety

The copilot must be unable to cause harm on its own.

  • ✓No write happens without Approve & Execute
  • ✓Guardrails reject negative stock and impossible dates
  • ✓Only typed tools are callable, never raw SQL
  • ✓Provider keys are stored encrypted

Success Criteria → How Each Was Verified

CRITERIONVERIFIED BY
Expiry controlFEFO simulation tests across multiple batches and shelf-life cutoffs.
Credit disciplineOrder-booking scenarios above and below the credit limit.
Cold-chain safetyRejected inward/move attempts into non-refrigerated zones.
TraceabilityFollowing a unit from PO → GRN → batch → invoice → audit log.
AI human-in-the-loopProposal flows that only commit after explicit approval.
Regression safetyPlaywright headless suite on login, FEFO, orders and invoicing.
09 / Step 6 — Deployment

Shipping It

MedStock IQ ships as two Node 20 containers behind a TLS reverse proxy, with Neon PostgreSQL as the database. It can run on Azure, AWS, GCP or on-premise.

STEP 1

Provision Neon PostgreSQL

A pooled connection (PgBouncer) for the app and a direct URL for Prisma migrations.

STEP 2

Run migrations & seed

Apply the Prisma schema and seed the organisation, roles, warehouses and catalog.

STEP 3

Configure secrets

JWT secret and expiry, CORS origin, Entra ID app registration, and AI provider keys in the vault.

STEP 4

Build containers

Next.js webapp (port 3002) and NestJS API (port 4000) on Node 20 Alpine.

STEP 5

Expose via TLS proxy

Cloud ingress with TLS 1.3 routes the web and API hosts.

STEP 6

Verify

Check the OpenAPI docs at /api/docs, then run the Playwright suite against the environment.

Local development

Webapp and API run locally against a Neon branch, with seeded demo data for fast iteration.

Cloud deployment

Containerised web + API behind TLS on Azure, AWS, GCP or on-premise Linux.

# backend/.env
PORT=4000
DATABASE_URL="postgresql://<user>:<password>@<pooler-host>/medstock?sslmode=require"
DIRECT_URL="postgresql://<user>:<password>@<direct-host>/medstock?sslmode=require"
JWT_SECRET="<random-32+-chars>"   JWT_EXPIRES_IN="1d"   JWT_REFRESH_EXPIRES_IN="7d"
CORS_ORIGIN="https://erp.yourdomain.com"

# webapp/.env.local
NEXT_PUBLIC_API_URL="https://api.yourdomain.com/api"

# Quality gates
npm run test:headless   # Playwright end-to-end regression
npm run demo:headed     # recorded product walkthrough
10 / Retrospective

What I Learned & What's Next

01

Put the rules that protect money in code.

FEFO, credit and cold-chain checks work because operators can't bypass them, and they're applied the same way every time.

02

A ledger is the audit.

Writing every movement as a signed entry made compliance and debugging the same job.

03

Give AI tools, not access.

Typed tools mean the copilot inherits every business rule and can't write SQL.

04

Proposals beat autonomy in regulated domains.

A two-phase “draft, then approve” flow made the AI safe enough to use on real stock and money.

05

Stay provider-agnostic.

A small router and an encrypted vault turned model choice into configuration.

06

Automate the demo, too.

The same Playwright harness that tests the product also records a repeatable walkthrough.

Roadmap

Current release

Phase 1 — Core Foundation

Multi-tenant schema, algorithmic FEFO, credit lock, GST invoices and the audit trail.

Active

Phase 2 — AI Expansion

Voice assistant, multimodal invoice OCR, automated reorder POs and WhatsApp inward bills.

2027 horizon

Phase 3 — Connected Ecosystem

IoT cold-chain sensors, manufacturer EDI, a driver PWA and a B2B pharmacy portal.