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

  1. Purchase request
  2. Canvass
  3. Purchase order
  4. Goods receipt
  5. Supplier invoice
  6. AP payment

SALES

  1. Quotation
  2. Sales order
  3. Delivery receipt
  4. Customer invoice
  5. AR payment

POS

  1. Cash session
  2. Sale
  3. X reading
  4. 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.