Amry Pharmacy - Sales and Inventory Management System
Built the sales and inventory system for Amry Pharmacy, a three-branch retail pharmacy in Davao del Norte (Asuncion, Kapalong, Talaingod). It replaces Loyverse and a stack of Excel sheets and paper logs with one Django REST API on PostgreSQL and two installable PWAs: a Storefront for counter work
- Role
- Full-stack developer
- Status
- Maintained
- Timeline
- Jun 2026 – Sep 2026
- Stack
- Chat GPT, Claude, Django, Django REST, Linux, Neon, Next.js, PostgreSQL, Python, React, Render, Typescript, Vercel, Tanstack, pywebpush, Serwist
The Problem
The owner couldn't see what was on the shelves without walking to them. Sales were rung up in Loyverse, but inventory, purchase orders and reports lived in Excel and on paper, so every sale was effectively entered twice and reports always lagged behind. The gaps hit three groups of people:
- The owner had no real-time stock view across branches, couldn't explain discrepancies, and reordered by looking at shelves.
- Pharmacists had no structured record of prescriptions, physician PRC/PTR numbers, DSWD guarantee letters, or Senior/PWD discounts, which are the transactions most exposed to compliance checks.
- Cashiers closed shifts with no system reconciling the cash drawer, so variances had no paper trail. Expired stock, disposals and transfers between branches were also tracked by hand. For a pharmacy, that means real money lost to expiry and no way to prove which batch went where.
My Role
Solo full-stack developer, working directly with the pharmacy owner as the decision-maker. I owned the project end to end: requirements, architecture, backend, frontend, deployment and documentation. Working alone, I still ran it like a team project. Every change went through a feature branch and a pull request before merging, so the history stays reviewable and each change can be traced and rolled back.
Key features
Mobile feature for storefront pharmacy assistant and admin
What I Owned
- Requirements and specs: wrote the PRD and TRD with the owner, labeling every requirement as confirmed, proposed, or waiting on an owner decision.
- Domain model: nine Django apps (accounts, branch, catalog, inventory, purchasing, sales, shifts, transfers, common) plus notifications, each layered as views, serializers and service functions.
- Inventory ledger: the single write path for all stock changes, with batch tracking, expiry dates and FEFO (first expiry, first out) allocation.
- Checkout and shifts: sales tied to an open cashier shift, with Rx-required and DSWD rules enforced at the counter, and cash drawer close with variance and remittance.
- Authorization: a capability registry that controls both what a user can do and which app they can open.
- Two PWAs from one Next.js app: Storefront for cashiers on tablets and assistants on phones; Back Office for managers on desktop.
- Push notifications with pywebpush for low stock, expiring batches, pending approvals, and incoming items from purchases or stock dispatch.
- Deployment: Next.js on Vercel and Django on Render, wired same-origin.
Technical and Product Decisions
Most of the hard choices came down to one rule: the server is the only place where stock and cash can change.
Both apps share one Next.js deployment. Every API call goes through it to Django, and every stock change inside Django funnels through the ledger.
- Same-origin proxy. iOS Safari dropped the cross-origin session cookie, so every API call goes through Next.js rewrites. Cost: an extra hop through Vercel.
- Session cookie + CSRF over JWT. Django-native, HttpOnly, and logout takes effect instantly. Cost: requires same-origin.
- One ledger function changes stock. post_movement() locks the batch, logs the move, and blocks negative balances. Cost: every stock flow must go through it.
- No sale without an open shift. Checkout locks the shift row, so nothing slips in during close. Cost: cashiers must open a shift first.
- FEFO with batch states. Earliest expiry sells first, and lots stay traceable across branches. Cost: more states to model and test.
- Capability-based access. Each capability also decides which app a user can open. Cost: a registry to keep in sync.
- Two PWAs from one Next.js app. Each surface installs separately with its own icon. Cost: manifests built as route handlers.
- Cache the app shell, never API data. Stale stock causes overselling. Cost: no offline sales.
- Custom build over OpenBoxes, InvenTree or Odoo. None handled Rx, DSWD, discounts and shifts. Cost: one developer maintains it all.
Constraints
- Team of one. Every layer, from schema to deployment, had one owner, so scope had to stay strict. Offline sync stayed out of v1.
- Three branches, one stockroom. Asuncion is the only branch with a stockroom, so stock moving between branches had to stay traceable while in transit.
- Mixed devices. Landscape tablets at the till, phones for stock work, desktops for management, including iOS Safari and its cookie rules.
- Online-only till. Sales and stock moves are committed only on the server, so the POS depends on branch internet.
- Legacy data. Products, prices and opening stock come from Loyverse exports at go-live.
- Pharmacy rules. Prescriptions, DSWD documents and Senior/PWD discounts had to be enforced at checkout, not left to staff memory.
- Low running cost for a small family business.
Result and Impact
The system is live at all three branches (Asuncion, Kapalong and Talaingod) and was delivered for a one-time payment, with no recurring license fee. To get the features this system covers from Loyverse:
- Advanced inventory (suppliers, purchase orders, transfers, counts): $25 per store per month
- Unlimited sales history (reports beyond the last 31 days): $5 per store per month That's $90 a month across three branches, or $900 to $1,080 every year. This system was a one-time payment, so it costs the pharmacy much less than Loyverse over time. It also covers the pharmacy rules that used to live on paper: prescriptions, DSWD documents, Senior/PWD discounts and shift reconciliation.
Screens & diagrams
Context
- Built as part of Full-stack developer at Amry Pharmacy (Freelance)