Pharmacy stock, expiry and Dosette operations.
TrayLedger is a self-hosted web application for exploring stock control, expiry-led workflows and Dosette/MDS preparation in a fictional pharmacy group. Its analytics are deterministic and non-ML: forecasts, expiry signals and transfer suggestions are calculated from stored operational data and fixed rules.
Pharmacy teams can otherwise end up reconciling batches, expiry dates, location balances and Dosette preparation across fragmented records. TrayLedger keeps those operational records together while leaving every stock-changing action explicit and reviewable.
Safety boundary: TrayLedger is not a clinical decision-support or dispensing system, has no live NHS integration or endorsement, and is not approved or certified medical software. The bundled seed data is synthetic. Do not use the repository with real patient data without independent professional legal, security, privacy, clinical and regulatory review of the intended deployment.
- Role- and location-scoped inventory, receiving, counts, adjustments and transfers.
- Product, pack, stock-item and batch records with quantity and movement history.
- Expiry, dead-stock, stock-attention, movement, valuation and workload reports, with CSV export where implemented.
- Dosette periods, cycles, medication lines, picking information and explicit prepared, checked and stock-deducted states.
- An A4 landscape Dosette preparation sheet for browser printing.
- Patients, notes, review records, work queues, alerts and an audit event view.
- Deterministic demand forecasts and stock-transfer suggestions.
- Selective local group archives and restoration, with mandatory encryption in the production settings profile; these are not full database backups.
- A React 19/Vite client and Django 5.2 REST API backed by PostgreSQL.
See the feature matrix for boundaries and the known limitations before evaluating the product.
The print feature is a preparation sheet, not a medication-label or automated picking-list generator. Analytics remain operational aids requiring human review.
flowchart LR
Browser["Browser"] --> Frontend["React and Vite frontend"]
Frontend --> API["Django REST API"]
API --> Auth["Session, CSRF and role checks"]
API --> Services["Stock, Dosette, reporting and backup services"]
Services --> Database["PostgreSQL"]
Services --> Analytics["Deterministic non-ML analytics"]
Services --> Print["Browser Dosette preparation sheet"]
The architecture guide describes trust boundaries, domain services and data flow without assuming unimplemented cloud services.
| Layer | Technology | Version | Purpose |
|---|---|---|---|
| Backend | Python / Django / Django REST Framework | Python 3.12+, Django 5.2 | API, permissions and operational services |
| Frontend | TypeScript / React / Vite | React 19, Vite 7 | Browser interface and local development server |
| Data | PostgreSQL | 17 | Relational operational data store |
| Client data | TanStack Query | 5 | API state and cache management |
| Verification | pytest / Vitest / Playwright | Locked manifests | Backend, component and browser workflows |
| Local runtime | Docker Compose | 2.24+ | Loopback-bound development stack |
Requirements: Git and Docker with Compose v2.24 or newer.
From a parent working directory, clone the current repository address and enter the checkout:
git clone https://github.com/alvinsakhiya/trayledger.git TrayLedger
cd TrayLedgerThen run these commands from the repository root (TrayLedger/):
cp .env.example .env
docker compose up --build -d
docker compose exec backend python manage.py migrate --noinput
docker compose exec backend python manage.py seed_demoOpen http://localhost:5173. Use admin@example.test with the local-only
password printed by seed_demo; every seeded account is listed in
Demo access. The API health route is
http://localhost:8000/api/health/.
Stop the stack with:
docker compose downReset the complete fixed synthetic workspace, including records added to that workspace, from the repository root:
docker compose exec backend \
python manage.py reset_demo_data --confirm RESET_DEMO_DATARun a small real test from the same directory:
docker compose exec frontend \
npm exec vitest run src/components/brand/AppLogo.test.tsxThe Compose stack is for loopback-bound local development only. Read the quick-start guide for manual setup, reset instructions and safe data handling.
Requirements: Python 3.12+, Node.js 22.13+, npm and a local PostgreSQL 17
database. From the repository root, copy .env.example to .env, change
DATABASE_URL to the local database, and set
API_PROXY_TARGET=http://localhost:8000.
From backend/ in terminal one:
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -e ".[dev]"
python manage.py migrate --noinput
python manage.py seed_demo
python manage.py runserver 127.0.0.1:8000From frontend/ in terminal two:
npm ci
API_PROXY_TARGET=http://localhost:8000 npm run devStop each development server with Control-C.
seed_demo creates five fictional accounts in the local synthetic workspace.
Every account shares the same deliberately public local password, which the
command also prints after seeding:
| Role | Username | Password | Intended access |
|---|---|---|---|
| Administrator | admin@example.test |
DemoPass!2026 |
Full local demonstration access to every module and location |
| Superintendent | superintendent@example.test |
DemoPass!2026 |
Group-wide stock, transfers, analytics, audit and catalogue management; no patient or Dosette access |
| Pharmacist | pharmacist@example.test |
DemoPass!2026 |
Example Central: stock, patients, Dosette preparation and deduction, reviews, local user administration |
| Dispenser | dispenser@example.test |
DemoPass!2026 |
Example Riverside: day-to-day viewing, receiving and Dosette status updates |
| Stock employee | stock@example.test |
DemoPass!2026 |
Stock operations across Example Central and Example Riverside; no patient access |
These credentials are created only by the local synthetic demonstration-data workflow. They are intentionally public, must never be reused for any other system, and provide no access to any hosted or production environment. TrayLedger is not approved for real patient or pharmacy data.
Practical notes:
- The accounts are created by
docker compose exec backend python manage.py seed_demo(Docker route) orpython manage.py seed_demofrombackend/(manual route); both routes create the same five accounts, and the command refuses to run whenDJANGO_DEBUGis false unless--forceis passed. - Re-running
seed_demoresets every demo account's password back to the table value. - Reset the whole synthetic workspace with
docker compose exec backend python manage.py reset_demo_data --confirm RESET_DEMO_DATA. - There is no environment variable for choosing a different demo password;
the value is a constant in
backend/apps/core/management/commands/seed_demo.py. - Sign in at http://localhost:5173 after seeding.
- Clone the repository,
cd TrayLedger, and copy.env.exampleto.env. - Start the Compose stack and apply migrations with the commands above.
- Seed the explicitly fictional workspace.
- Open http://localhost:5173 and confirm http://localhost:8000/api/health/ reports an OK status.
- Sign in with
admin@example.testand the local-only password printed byseed_demo(see Demo access). - Open Inventory, search for
CEN-PAR-001, and inspect its batches without changing stock. - Open Dosette, inspect a fictional patient cycle and preview the preparation sheet without confirming stock deduction.
- Run the targeted AppLogo test shown above.
- Locate the backend entry point at
backend/config/urls.py. - Locate the frontend entry point at
frontend/src/main.tsx.
Results recorded through 18 July 2026:
| Suite | Working directory and command | Result | Notes |
|---|---|---|---|
| Corrected backend SQLite | backend/ — DATABASE_URL="sqlite://:memory:" pytest |
786 passed | Compatibility database |
| Corrected backend PostgreSQL 17 | backend/ — pytest with DATABASE_URL set to a PostgreSQL 17 database |
786 passed | Production-relevant database |
| Corrected strict backup profile | backend/ — SQLite command plus BACKUP_ENCRYPTION_KEY and BACKUP_ENCRYPTION_REQUIRED=True |
786 passed | Plain-archive tests isolate their fixture only |
| Targeted timezone matrix | backend/ — selected pytest nodes |
9 passed twice | Normal process environment and TZ=UTC |
| Backend static/system/dependency checks | backend/ — Ruff, mypy, Django, migrations, pip audit/check |
Passed | No formatting, typing, system, migration or dependency failures |
| Corrected frontend | frontend/ — lint, build, test, audit |
Passed | 50 files and 409 tests; zero audit vulnerabilities; 869 KB chunk warning |
| Browser workflows | frontend/ — npx playwright test |
23 passed | Fresh synthetic PostgreSQL data |
| Containers | repository root — no-cache build, fresh-volume startup and smoke | Passed | Migrations, seed, health and non-root application users |
| Historical strict-profile clean-clone diagnostic | backend/ — pytest |
776 passed, 2 failed | Development .zip expectations inherited strict .zip.enc behaviour |
| Historical documented-profile clean clone | backend/ — pytest |
777 passed, 1 failed | Genuine BST-midnight overdue-date boundary |
The overdue defect is corrected: review serializer/filter paths, work-queue
status, MDS workload counts and stock-expiry report offsets now share Django's
configured-local-date semantics. Overdue review, prepared-cycle and
checked-cycle tasks serialize OVERDUE while preserving their action type;
due-today and future items retain their workflow status. The browser treats the
API review flag and server expiry offset as authoritative. All nine frozen
timezone cases pass normally and with TZ=UTC, covering UTC/Europe-London
agreement, 00:00-01:00 BST, the previous UTC date versus London's next date,
classification, serializer/queryset/report output, frontend expiry display and
both daylight-saving transitions. Only the two plain-.zip tests isolate the
documented development fixture; the complete strict .zip.enc profile passes
786/786 without a production-behaviour change.
Exact prerequisites, commands and evidence boundaries are in docs/TESTING.md. Automated tests are evidence for covered paths, not proof that the software is defect-free or suitable for regulated use.
Start at the documentation index. Useful entry points are:
- Product overview
- User guide
- Architecture
- API reference
- Developer guide
- Configuration
- Deployment boundaries
- Testing and current evidence
- Security and privacy
- Troubleshooting
- Screenshot gallery
- Future ideas
- Development provenance
- Changelog
- Release notes
- Proprietary licence position
- v1.0.0 stable-release record
The v1.0.0 tree and complete rewritten history form the approved public portfolio release in a separate GitHub repository. The main pre-correction SQLite, PostgreSQL, frontend, browser and container matrices passed. A historical independent clean-clone run at 00:05 BST exposed the overdue-date defect; the 777-pass/one-failure result remains disclosed even though the defect is now corrected. The corrected suites contain 786 backend tests and pass all 786 on SQLite, PostgreSQL 17 and the strict profile. The corrected frontend suite passed all 409 tests across 50 files. Playwright, Docker and the static/dependency gates also pass. The complete corrected matrix was repeated successfully from a fresh clone, and controlled remote CI, E2E, privacy and startup checks pass. The portfolio history exists only in the separate TrayLedger repository; the submitted software-baseline repository is preserved unchanged and is never a rewrite or cleanup target. Publication, release publication and TrayLedger-only archival are applied in that order only after their independent verification gates pass; the final remote state is recorded outside the archived history. The baseline repository's visibility and archival state are outside this workflow. No active feature development is planned; narrowly scoped security fixes, correctness fixes and documentation corrections may still be considered. There is no production support or clinical-use approval. See PROJECT_STATUS.md for the recorded baseline and remaining verification and approval-gated work.
TrayLedger is proprietary software and is not open source. Copyright © 2026 Alvin Sakhiya. All rights reserved. No permission is granted to copy, modify, distribute, sublicense or use the software commercially without prior written permission. Public visibility does not grant reuse rights. See LICENSE and the licence position.
Read CONTRIBUTING.md before proposing a change. Do not put credentials, real patient data or other sensitive information in an issue, commit, test fixture or screenshot. Potential vulnerabilities should follow SECURITY.md, not a public issue containing exploit details.
Created by Alvin Sakhiya.
