CASE STUDY
Kalakore
Wholesale distribution ERP and BIR-ready POS
A complete back office for Philippine wholesale distributors: purchasing, multi-warehouse inventory, sales, a multi-terminal point of sale and double-entry accounting in one system, built to the BIR's rules for POS software. I designed and built all of it on my own, from the database schema and API to the interface, tests and deployment.
- Role
- Solo developer, design to deployment
- Type
- Personal project
- Status
- Version 1.0.0, October 2026
51
API modules
74
Database tables
8 / 287
Roles / permission codes
~5,900
Automated tests
01.The problem
A wholesale distributor runs on paperwork that has to agree: what was ordered from suppliers, what actually arrived, what was sold, what customers still owe and what the business owes. When those live in separate tools, the books drift from the stock and month-end becomes a reconciliation exercise.
In the Philippines there is a second constraint. Point-of-sale software must follow the BIR's rules (RMO 24-2023): unbroken invoice numbering per terminal, X and Z readings, an electronic journal and specific sales reports. Kalakore was built to handle both in one system.
02.What it does
Purchasing
Purchase requests are grouped into a canvass, suppliers send offers, and each item is awarded to the best one. The awards become purchase orders, then goods receipts, supplier invoices and payables, with returns and debit notes.
Inventory
Stock per product per warehouse. Every movement, whether received, sold, returned or adjusted, writes its own inventory transaction, so a stock level can always be traced back.
Sales
Quotation, sales order, delivery receipt, invoice and receivable. Each customer gets the lowest of their price tier, price list and promo, and approval checks their credit limit and the approver's discount limit.
Point of sale
Several terminals, each with cash sessions, VAT-inclusive pricing, Senior Citizen, PWD, NAAC and Solo Parent discounts per line, returns and an end-of-day Z reading.
Accounting
Double-entry books. Every posting document creates its own journal entry, checked so debits equal credits, feeding receivables, payables, customer deposits and supplier credit.
BIR compliance
Built to RMO 24-2023: sequential invoice numbers per terminal, X and Z readings with a Z-counter, an electronic journal, the sales summary, special discount books and marked reprints.
03.How documents flow
Every business process is a chain of documents. Each step builds on the one before it, and every step that moves money or stock posts its own entry to the general ledger.
PURCHASING
- Purchase request
- Canvass
- Purchase order
- Goods receipt
- Supplier invoice
- AP payment
SALES
- Quotation
- Sales order
- Delivery receipt
- Customer invoice
- AR payment
POS
- Cash session
- Sale
- X reading
- Z reading
General ledger: one journal entry per posting, debits = credits
Stock moves at the same time, in the same database transaction
04.Engineering decisions
Money as whole centavos
Every amount is stored as an integer number of centavos, never a floating-point peso value, so totals, VAT splits and balances never pick up rounding drift.
One transaction per business event
Posting a document moves stock, writes the journal entry, takes the document number and records the audit row in a single database transaction. Either all of it happens or none of it does.
Invoice numbers that can't collide
A POS sale locks its terminal's row before taking the next invoice number, so two cashiers can't get the same one. Database triggers refuse any attempt to lower a terminal's serial or grand total.
An audit trail you can't edit
Every change is logged in the same transaction as the change itself, recording only the fields that changed. Changes made outside the app are caught by database triggers, and the log refuses updates and deletes.
Caching that stays correct
Redis caches reads and rate-limits the API. A test scans the source and fails if a write forgets to clear its cache. If Redis goes down, reads fall back to the database, but the login lockout fails closed.
Permissions before validation
8 roles share 287 permission codes. Every protected route checks permission before validating input, so a user without access gets a 403 and learns nothing about the form. A contract test enforces the order.
05.Testing
Accounting software has to be right, so the API is covered by about 5,900 automated tests written with Vitest and Supertest, including every route and full business scenarios.
- ▸Tests run in-process against a real PostgreSQL database and a real Redis, starting from the real seed data, not mocks.
- ▸Every route is automatically tested for 401 without a token and 403 for each role that lacks access.
- ▸A route-coverage guard fails the suite if any route has no passing test.
- ▸Scenario tests reconcile the ledger against the AR and AP aging after each run.
- ▸Known bugs are pinned with expected-failure tests until they're fixed.
06.Tech stack
- TypeScript
- Express 5
- React 19 (Vite)
- TanStack Router, Query & Table
- shadcn/ui
- Tailwind CSS
- PostgreSQL
- Drizzle ORM
- Redis
- Socket.IO
- Zod
- Vitest
- Docker
- Nginx
The API and the interface each ship as their own Docker image: the API on Node.js, the interface as static files served by Nginx.
Need a system like this?
I build business software that has to be correct: inventory, sales, POS and accounting. Let's talk about yours.