ARSHADUL.exe, home
Menu

/WORK / Pharmacy operations and dispensing

Pharma Enterprise

A pharmacy counter that sells a batch, not just a medicine name.

Pharma Enterprise is the operations console for a pharmacy counter and its back office. Staff bill from batch stock, hold and resume invoices, take split payments, process returns, receive purchases, and adjust stock. Doctors write prescriptions the counter can later bill. What each person sees depends on permission keys shared with the API.

Category
Pharmacy operations and dispensing
People
  • Billing staff at the counter
  • Pharmacists and inventory staff
  • Doctors writing prescriptions
  • Reception, managers, admins, and owners
Contribution
I built this product myself, frontend and backend. That covers the billing counter, inventory and clinical screens, the permission-aware shell, and the API that records invoices, stock, and staff access.
Status
Live

The challenge

A medicine name is not enough to sell. The same product can sit in several batches, with different expiry dates, prices, and quantities. The counter has to pick a sellable lot, show the cashier what is wrong, and still move quickly when the next person is waiting.

Around that sale, the pharmacy holds bills, takes more than one payment method, returns stock, records purchases, and keeps doctors, patients, suppliers, and staff on different permissions. A receptionist should not land in the same workspace as an owner.

The solution

After sign-in, the home screen is a module grid filtered by the permissions on the account. A doctor-only account lands in a doctor home instead. Billing is a keyboard-oriented till: search or scan, cart, batch change, prescription load, split payment, hold, and returns.

Inventory covers the medicine master, purchase entry, stock, adjustments, and thinner tools for barcodes, transfers, counts, and expiry. Clinical screens cover doctors, prescriptions, and patients. Administration covers suppliers, users, and editable role permissions. The API validates the write, checks the permission, and stores the invoice and the stock movement.

  • Point of sale with batch lookup, quantity caps, and a cart merged by batch.
  • Prescription lookup that fills the customer and attempts to add the remaining medicines.
  • Held invoices that can be resumed from the counter or from a link.
  • Split payments, with credit offered only for owner and admin roles.
  • Returns, invoice history, customers, and billing summaries loaded from the API.
  • Purchase entry, stock lists, and confirmed stock adjustments.
  • Patient, doctor, supplier, and user administration, including a role permission editor.

System architecture

The client is a React single-page app on Vite. Redux holds two things: the auth slice, and an RTK Query API slice. Feature screens keep their own workflow state. The point-of-sale cart lives in the billing page, not in the global store, so a refresh does not pretend an unsaved cart is still there. A held invoice is how that work survives.

One API module declares the endpoints and the cache tags. A 401 on a normal request refreshes the session and retries once. Login clears the previous cache before the new session is stored, so one cashier does not see the previous cashier's queries. Child pages can inject actions into the shared header.

A small shared package exports the role and permission keys used by the React app and by Express. Route guards wait until the user exists, and they prefer the permission list returned by the server. The API checks those keys again on the write. Feature folders group billing, inventory, and the clinical screens. Server modules follow route, controller, service, model, and validation files.

System architecture

How the clients, shared code, and API relate. Read the columns from left to right.

01 Browser

  • React SPAVite. Feature modules for billing, inventory, clinical, people, and settings.
  • ReduxSession plus the RTK Query cache. Cart and forms stay on the screen.
  • GuardsAuthenticated shell, then a permission check per module.

02 Shared keys

  • PermissionsThe same role and permission constants on the client and the API.

03 API

  • ExpressVersioned HTTP API. Zod validation and permission checks.
  • Billing and stockInvoice create, holds, returns, purchases, and adjustments.

04 Data

  • MongoDBInvoices, batches, patients, prescriptions, users, and roles.

The counter previews totals in the browser. The saved invoice is the total the API computes. There is no payment gateway and no hardware scanner SDK in the client.

Key engineering decisions

The till is one workflow, not a pile of dialogs

Engineering challenge

Scan, customer capture, prescription import, batch changes, holds, and payment can happen in any order during one sale.

Implementation approach

One billing page owns the cart and the customer context. Smaller components render the lines, the batch picker, and the payment dialog. Keyboard shortcuts return focus to the barcode field. Deep links can resume a hold or load a prescription.

Engineering rationale

The cashier gets one place that knows whether a line can still accept quantity. Async work is cancelled when the page moves on, so a late lookup does not write into the next sale.

Trade offs

The page coordinates a lot of states. That concentration is what keeps the shortcuts, the cart, and the payment modal from drifting apart.

Permissions are a shared vocabulary

Engineering challenge

Owners, pharmacists, billing staff, inventory staff, doctors, and reception do not share a workspace. A custom permission set has to change the menu without a new release for every role name.

Implementation approach

The client and the API import the same permission keys. Navigation and route guards use the list on the signed-in user. Sensitive writes are checked again on the server. Changing a role invalidates the cached user data.

Engineering rationale

Hiding a module and rejecting the request use the same words. A doctor home and a billing till can both exist without a second application.

Trade offs

The interface is not the security boundary. A few screens still branch on the role name, such as who may offer credit.

Sell the earliest usable batch, and allow an override

Engineering challenge

Lookup by name can hit many lots. Selling the wrong expiry, a blocked lot, or more than the shelf holds is a stock error, not a styling error.

Implementation approach

Lookup prefers the earliest sellable expiry. The cashier can open a batch picker and switch lots. Lines merge when the batch id matches, and quantity stops at available stock. The invoice stores batch ids. The API rejects expired, blocked, and short stock.

Engineering rationale

The default follows first-expiry discipline. The pharmacist can still choose a lot when the default is wrong for that customer.

Trade offs

The browser total is a preview so the counter stays responsive. The API recomputes the invoice on save, and that saved invoice is the record.

One API client, with cache tags that know the sale

Engineering challenge

A saved invoice changes stock, held bills, and the next invoice number. Screens that keep their own fetch logic will show yesterday's shelf.

Implementation approach

RTK Query tags cover billing, inventory, patients, prescriptions, users, and roles. Mutations invalidate the tags they affect. Creating an invoice also patches the cached invoice-number preview so the till can advance without waiting for a full refetch.

Engineering rationale

Auth refresh, cache keys, and invalidation stay in one place. Feature screens ask for data. They do not each invent a client.

Trade offs

The endpoint module is a coupling point. A new screen is fast to wire, and a careless tag can refresh more than it should.

Core capabilities

Batch-aware point of sale

Search or scan, build a cart, switch batches, and pay or hold. Warnings collect on the lines that need a person to look.

Value. The counter can sell from real stock without a separate inventory check for every line.

Complexity. Cart identity is the batch id. Quantity, expiry, and price travel with that lot.

Prescription-assisted billing

A prescription number fills customer fields and tries to add the remaining medicines. Doctors have their own compose flow.

Value. The clinical note and the till meet on a number, instead of a retyped list.

Complexity. The counter only adds what lookup can resolve. Verify, cancel, and dispense stay on the prescription screens.

Holds, split payments, and returns

An invoice can be held and resumed. Payment can be split across methods. Returns go through their own history.

Value. A sale that pauses, or a sale that comes back, does not have to be reconstructed from memory.

Complexity. Hold state is server state. The cart on screen is only the draft.

Purchases and stock adjustments

Purchase entry records supplier invoices. Adjustments search batches, preview the quantity, and ask for confirmation.

Value. Stock changes outside the till are explicit, with a preview before a destructive write.

Complexity. These forms talk to inventory endpoints and surface field errors and toasts when the save fails.

People and supplier records

Patients, doctors, and suppliers can be searched, created, and edited. Blacklist and reinstate ask for a reason.

Value. The counter and the clinic share a directory instead of loose notes.

Complexity. Supplier screens manage profiles. They are not a purchase-order desk.

Users and role permissions

Admins create users, change roles and status, and edit the permission set on a role.

Value. A new staff member gets a workspace that matches the job, including a custom set of keys.

Complexity. The editor writes the same keys the navigation and the API already understand.

Technology stack

Frontend

  • React
  • Vite
  • React Router
  • TypeScript on part of the client

State

  • Redux Toolkit
  • RTK Query
  • Component state for the cart and forms

Forms and UI

  • React Hook Form on inventory
  • Custom CSS
  • Tailwind CSS on inventory screens
  • Lucide icons

Backend

  • Express
  • Zod
  • MongoDB
  • JWT session with refresh

Shared package

  • Role and permission constants

Engineering impact

Billing, stock, and clinical work sit in one client with a boundary the API can enforce. New operational screens reuse the list shell, the header slot, toasts, and outlined fields instead of a new visual language each time.

The cashier's keyboard path, the batch picker, and the hold flow are one product. That is what makes a dense counter usable: the next action is on the same bill.

Permission changes propagate through cached user data, so the menu is not a hard-coded list of job titles.

Money shown while editing is treated as a preview. The invoice the API stores is the one later screens and returns use.

Workflows and modules

These drawings describe the counter and the modules around it. They are not captures of a live pharmacy system.

Application modules

Billing

  • Point of sale
  • Holds
  • History
  • Payments
  • Returns
  • Billing summaries

Inventory

  • Medicines
  • Purchases
  • Stock
  • Adjustments
  • Expiry

Clinical

  • Doctors
  • Prescriptions
  • Patients

Admin

  • Suppliers
  • Users
  • Role permissions
Primary journey
  1. 01Open the till

    Sign in and land on the modules the account is allowed to use.

  2. 02Add a lot

    Search or scan. The default lot is the earliest sellable expiry.

  3. 03Adjust the bill

    Switch a batch, load a prescription, or hold the invoice.

  4. 04Take payment

    Split the tender if needed. The API writes the invoice and the stock movement.

Engineering takeaways

  1. I treat a counter as a state machine: scan, batch, hold, and pay have to know about each other.
  2. I want the permission word in the menu to be the same word the API checks.
  3. I keep stock identity on the batch, because a product name hides expiry and quantity.