Rukon
Back to Project Overview
Full Case StudySystems / ERP / WMS NDA-Safe Architecture

RPOMS

Router Production Operations Management System

RPOMS wordmarkRPOMS wordmark
Role
Sole Developer

End-to-End Architecture, Full-Stack Build, Database, Security & Deployment

Affiliation / Client
Blue Bee Technologies Sdn. Bhd., ERTH × Maxis programme
Timeline
July 2026 – present
Documentation Level
18 Engineering Sections

Operational Workflow & State Machine

End-to-End Pipeline
  1. 01Stock booked in by model
  2. 02Accept scan into the registry
  3. 03Cleaned (daily count)
  4. 04Packed into a numbered box
  5. 05Delivered against the scanned load
  6. 06Delivery Order and tracker generated

RPOMS ecosystem

Separate systems
  • Related system, same programmeRPOMS AI

    A router surface inspector: photo, quality gate, a vision model that only observes, and a deterministic rule engine that decides. Own repository and database; it never connects to RPOMS.

    Read more
  • Subsystem, integration pendingRPOMS Print Engine

    Scan-to-label printing to a NIIMBOT B1 Pro straight from the browser over Web Bluetooth, built as an offline-first workstation with its own case study. Standalone until it is validated on the line and wired into RPOMS.

    Read its case study
01

Overview

RPOMS is the operations system for a router-refurbishment programme that Blue Bee Technologies runs with ERTH and Maxis. It records each day's production, tracks every router by serial number from acceptance into a numbered box and out on a delivery, keeps the stock of routers, chargers, cables and boxes, and produces the delivery paperwork the office already uses. A second, locked-down build of the same code gives the Ecommerce team a portal for requesting stock.

The comparison to a warehouse or resource-planning system is about shape rather than scale. It is not a commercial ERP suite and does not try to be one: there is no finance, procurement or HR module. What it shares with that category is the structure, a single operational data model that inventory, production, delivery, workforce and reporting all read from, so a figure on a dashboard traces back to the event that produced it.

It is one Next.js 15 application: about 59,600 lines of TypeScript across 16 pages and 41 API route files, with 895 automated tests. Every screenshot on this page was taken from that application running locally on synthetic data.

The RPOMS dashboard showing today's accepted, retired, cleaned and packaged counts with the change from yesterday, today's notes, batch progress of 2,206 of 2,400, and the workforce assignment by department against per-person targets.
The read-only dashboard. Today's four figures against yesterday, batch progress, and each person's output against their per-person target.
02

Problem

A physical production line (intake, acceptance, cleaning, packing, delivery) was being coordinated through spreadsheets shared across a team. That made it hard to know, at any moment, what had actually happened on the floor: what stock remained, who did what, which box a serial went into, whether a delivery matched what was scanned, or whether a report was still current.

Spreadsheets do not validate. They have no single source of truth once someone keeps a private copy to be safe, and no audit trail once a number looks wrong. The spreadsheet is still visible in the code: the model-detection rules were seeded from the team's Excel formula, and a registry row is described as the web equivalent of a row in the Excel ACCEPTED list.

03

Context

The programme refurbishes used routers in batches, each with a target. That is a different problem from manufacturing: units arrive in unknown condition and unpredictable quantities, and each one carries a serial number the system does not get to choose.

The operation runs on a physical floor in Malaysia, with a small team, a shared set of consumables, two warehouse sites, and delivery paperwork a customer already expects in a particular format. Those constraints shaped the software more than any specification did.

04

Approach

The first rule was that the software should fail in the safe direction. A missing password disables that login rather than falling back to a known value. A missing session secret stops the application in production. A missing deployment flag leaves production write-locked rather than silently live. A preview deployment pointed at the production database refuses to start serving.

The second rule was that a number should be traceable. Stock is derived from the full history of adjustments and consumption rather than stored as a running total, so a figure that looks wrong can be walked back to the day that produced it.

The third rule was that business rules belong in one place. The arithmetic that decides how many packaging boxes a day's output consumes lives in a single module that the report form, the inventory and the Ecommerce planner all call, so they cannot disagree about the same day.

05

My Role

I am the only developer on RPOMS. Of 284 commits on the current line, every one except an uploaded copyright file and a bot branch is mine: the data model, every module, authentication and access control, the storage layer, the tests and CI, the remediation of two code audits, and the deployment and staging runbooks.

I work as IT Systems & Operations Lead around RPOMS, so I also work directly with the side it supports: the production line, the stock it consumes, and the people running it day to day. RPOMS models a physical process closely enough to run it, which only works if the person building it understands the process and not just the schema behind it.

06

How the work flows

Two things are tracked in parallel. The Daily Production Report records how many routers each person accepted, cleaned and packaged that day, and stock is deducted from those figures. Separately, the Registry, Pack and Delivery screens follow individual routers by serial number into boxes and onto deliveries.

Cleaning is recorded only as a daily count per person. There is no per-serial cleaning state, and the system does not pretend there is. Alongside the main line, the Ecommerce team requests stock, an admin approves it, stages the exact routers, and transfers them, and only that transfer deducts anything.

Workflow diagram. Per-serial path: router arrives, accept scan, pack into a box, warehouse, delivery, paperwork. Below it the Daily Production Report feeding the dashboard and workforce reports, and an Ecommerce side flow: request, decision, stage routers, transfer, void or edit.
The operational workflow as implemented. Only stages that exist in the code are drawn.
07

Daily Production Report

The day's figures are entered once. Saving updates the dashboard and deducts stock in the same step: routers by accepted plus retired, chargers and cables by packaging. Batch and target carry over from the previous report, and Completed is the batch so far plus today's packaging, so progress is continuous rather than restarting each day.

For a plain admin, the server does not trust the totals it is sent. It recomputes Accepted, Cleaned, Packaged and Completed from the workforce rows, freezes batch and target, and only lets an existing output grow. Two admins saving the same day no longer overwrite each other: each save carries the row's revision, and a stale one is refused with a message to reload.

The Daily Production Report form: date, batch number BATCH-07, target 2,400, and Completed marked Auto at 2,206 with the note 2,012 earlier in this batch plus 194 packaged today.
The report form. Completed is worked out from the batch so far plus today's packaging.
Workforce rows on the daily report: task, person, output, a plus button to add to the output, an optional target and notes, for accepting, cleaning and packaging staff.
Workforce rows. The day's totals are summed from these; a plain admin can add to an output but not rewrite it.
08

Serial Registry and Packing

Every router is recorded by serial number, model, who accepted it and when. The model is detected from rules stored as data (a prefix and an optional length, longest prefix first), so a new model needs a rule rather than a release. A serial no rule recognises is kept, flagged and shown at the top of the list.

Packing scans serials into a numbered box. A serial that is unknown, not accepted or already in another box is flagged, and the box will not close around it unless the close is forced deliberately. A box closed short of its capacity says so on the list, and each box prints a label with a QR code of its number.

The Router Registry list of accepted routers with serial, model, person, date and box columns. Two rows with unrecognised serials are highlighted and marked Invalid, with a badge reading 2 need attention.
The Serial Registry. The two serials no rule recognises are flagged rather than dropped.
Packing a box: three scanned routers marked as Kaon AR2140 and two marked Not accepted, with a warning to remove the flagged rows or tick Close with issues.
Packing. Two of five scans are flagged, and the box will not close until they are dealt with.
The packed boxes list: box numbers with model, packed date, units, warehouse ERTH and a print button. The top box shows 7/10 incomplete.
Packed boxes. A box closed short reports itself; each one carries its warehouse and a printable label.
09

Delivery and paperwork

A delivery is built by scanning box numbers. The serials inside come from the registry and are never typed. The delivery cannot be saved until the scanned load matches the quantity it was raised for; the server enforces the same rule, and only a Super Admin can accept a short load. Saving writes the delivery, its boxes and each router's delivered state in one transaction.

The Delivery Order is generated from the office's own Word document with the details filled in, not redrawn. Word splits a line of text across several runs, so a placeholder can be broken in one place and intact in another; the generator does two passes for exactly that reason. The tracker exports to Excel in the column layout the customer's sheet expects.

Building a delivery to a demo customer: the customer and ship-to blocks filled from stored details, two scanned boxes of 10 Kaon AR2140 routers, a status of 20 of 250 routers, 230 short, and the Save delivery button disabled.
Building a delivery. Save stays disabled until the load matches the quantity; here it is 230 short.
Delivery history with four demo deliveries; the oldest is expanded to show its boxes and the serial numbers inside each, with DO and Tracker download buttons.
Delivery history. Each delivery opens to its boxes and serials, with the Delivery Order and tracker one click away.
10

Inventory and workforce

Stock is never stored as a balance. Remaining is always what was booked in minus what was consumed, worked out when it is read, per item and per model, so editing or deleting a report cannot leave a stale figure behind. Every movement, including those written by an Ecommerce transfer, appears in one ledger.

Workforce output is measured against per-person targets that can be set by department, by day, or for one person on one day. Names are matched case-insensitively, and the spelling shown is the one a worker is recorded under most often.

The Inventory page: cards for routers, chargers, LAN cables, small boxes and big packaging boxes, each with remaining, added and consumed, above a manual adjustment form and an adjustment history including two rows written by an Ecommerce transfer.
Inventory. Remaining is added minus consumed; the ledger includes the rows an Ecommerce transfer wrote.
The Workforce page: department cards with output against target, a top performer card, an employee ranking by total output, and recent report activity.
Workforce. Assignment against per-person targets, and a ranking built from every saved report.
11

Ecommerce stock requests

The Ecommerce team asks for routers or sealed boxes through a portal that shows them their own requests and nothing else: no stock levels, no admin screens. Each request gets a number of the form ECR/YYMM/R/CODE/NN from a per-month counter, and a network retry returns the request the first attempt created instead of raising a second one.

An admin approves or rejects it, stages the exact routers by serial or by box (each one checked against the registry), and transfers them from loose stock or a storeroom. The client never sends a charger or box quantity; the server derives them. The same deduction plan drives both the preview and the commit, and a void reverses a transfer.

The Ecommerce portal: a Router Stock Request header with a three-step request, review, transfer strip, summary cards, a new request form, and a request history table with pending, approved, rejected and transferred demo requests.
The Ecommerce portal, a separate build of the same code limited to this one job.
An admin view of a transferred request: 20 routers requested, approved and transferred with 0 outstanding, 20 chargers and 20 small boxes derived, and a processing timeline of raised, approved and transferred.
A transferred request: the routers, the chargers and boxes the server derived from them, and who did what, when.
12

Dashboard and reporting

The dashboard answers how today went: the four figures against yesterday, batch progress, today's workforce against target, a trend, and a comparison against yesterday, a seven-day average, last week, this month or a custom range, because a comparison supports a decision and a raw count does not. It refreshes itself, and switches to Arabic with the whole layout mirrored right to left.

Report history is searchable by date and batch and exports to PDF.

Production trend chart over seven days for accepted, retired, cleaned and packaged, above a comparison table of today against yesterday with differences.
Trend and comparison, cropped from the dashboard.
The dashboard in Arabic with the layout mirrored right to left: KPI cards, workforce assignment, today's notes and batch progress.
The same dashboard in Arabic. The layout mirrors; names and figures stay as entered.
13

Architecture

One Next.js application with no separate backend service. Pages are server components that read the store directly; writes go through route handlers that check the role and validate every body with Zod. A single middleware sits in front of every request: it refuses a preview or laptop pointed at the production database, applies the production write lock, scopes the Ecommerce build, and sets security headers. Authentication is deliberately decided in each page and route, not there.

One storage interface, three backends, selected by which environment variables are present: PostgreSQL on Supabase in production, MySQL for an earlier cPanel path (report store only), and atomic JSON files for local work. Postgres tables are created on demand behind per-store schema stamps, and only one cold-starting instance wins the build, which matters when several deployments share a database. Every read runs under a deadline, and where a fallback would be dishonest the caller gets a distinct 'could not read' value instead of an empty result.

Architecture diagram: four roles, then the middleware chokepoint, then pages and 41 API route handlers, then domain logic modules, then one storage interface over PostgreSQL, MySQL and a local file store, with Vercel, CI and a planned staging database at the bottom.
System architecture. Green is in the code today; dashed amber is planned; dotted blue is external.
Data flow diagram: daily report saves, the inventory ledger and Ecommerce transfers feed the consumption rules, which produce remaining stock read by the dashboard and inventory; below, the serial chain from registry to box to delivery to delivery order.
How a saved day becomes stock, and how a scan becomes a delivery.
14

Technical Decisions

Four roles (viewer, Ecommerce, admin, super admin), each with its own credentials. A role whose password is not configured is switched off entirely. Sessions are an HMAC-SHA256-signed cookie carrying the role, when it was issued and a session id, compared with a timing-safe function, httpOnly, sameSite and secure in production. A viewer's session lapses after twenty-four hours; staff sessions last seven days. Sign-in attempts are throttled per address.

Big packaging boxes are not calculated with a ceiling function. One box per ten packaged units, plus one more once the remainder reaches seven, because a box near enough to full has already been opened and sent, while a smaller remainder will be finished by the next day's work and would otherwise be counted twice.

The business day is resolved in Asia/Kuala_Lumpur rather than from the server clock. A deployed server keeps UTC, which would not turn the day over until eight in the morning locally, long enough for early packing to file itself under the previous day.

Translations are typed against English as the source, so a string added in English fails the build until every other dictionary supplies it.

15

Engineering challenges

Most of these came out of two read-only audits of the codebase, the first of which recorded 44 findings, six of them rated P0.

Silent overwrites: two admins saving the same day meant the second replaced the first with no trace. Each save now carries the row's revision, and a stale save is refused with a 409.

A client-sent 'this came from a CSV' flag relaxed the add-only rules. The server now re-parses the attached worksheet and decides for itself which sections it supplied, and refuses a worksheet dated for a different day.

Every preview deployment was configured with the production database. A guard now makes a preview refuse the production database with a 503, work flows through a staging branch, and a one-way refresh script copies production to staging with names anonymised.

Deployments sharing one database raced to build schema on cold start, holding an exclusive lock. The build is now claimed with a single statement and a sentinel, and new columns sit behind their own marker keys so two versions of the code cannot keep undoing each other.

Box stock started mid-operation, with thousands of routers already packed. Counting them would have produced a deficit no correct booking-in could explain, so box stock starts from an opening figure on a fixed date and only later packing is counted.

16

Testing and verification

895 automated tests in 60 files, written with Vitest. Sixteen of those files run real SQL against PGlite, PostgreSQL compiled to WebAssembly and served in-process, so transactions, locking, concurrency and schema setup are tested with the production driver and no database server. CI runs typecheck, lint, the tests, a production build and a dependency audit on every push.

For this write-up the production build was run locally on synthetic data, every screen was exercised, and a Delivery Order and the Excel exports were generated. Typecheck and lint passed. The full test run passed 59 of 60 files; the remaining file hit a ten-second hook timeout under machine load and passed when run on its own.

There is no browser end-to-end suite yet; a Playwright smoke test per role is on the backlog.

17

Deployment and environments

The project deploys to Vercel from Git: pushing the main branch deploys production, and every other branch builds a preview. Production data lives in Supabase PostgreSQL. Work now flows from feature branches into a staging branch and then to main, and a data-preservation check compares every table's row count before and after a release.

A separate staging database is designed and its bootstrap SQL written, but it is blocked on hosting limits, so previews are protected by refusing the production database rather than by having their own. The current plan has no managed backups; a read-only JSON export is the backup path until that changes.

Environments diagram: feature branches to staging to main, with an Ecommerce build from its own branch; a production database in use and a planned staging database; and the guards in code: environment guard, demo write lock and a data-preservation gate.
Environments and release path. The staging database is planned, not live.
18

Current result

In production, the dashboard and the Daily Production Report are live: the day's figures are entered for real, stock is deducted for real, and the dashboard reads back what was saved. The Ecommerce portal runs as its own build of the same code.

The registry, packing, delivery and paperwork, workforce and inventory modules are complete and render live data, but they sit behind a deployment-level write lock while the programme works through sign-off. The lock is enforced at one middleware chokepoint, is on by default in production, and names the modules allowed to write, so a module goes live by being named rather than by accident.

The newest work (the remediation of both audits, the audit log, the environment guard, the login throttle, the test suite and CI) is built and tested on the staging branch and has not yet been released: production deploys from main, which is 68 commits behind.

Ecosystem diagram: staff, the Ecommerce team and stakeholders use RPOMS, which produces Delivery Orders, tracker exports, report PDFs and box labels; below, two separate projects, RPOMS Print Engine with integration planned and RPOMS AI which is not connected.
RPOMS, what it produces, and the two related projects kept separate from it.

What's live, and what isn't

“It exists in the repository” and “it runs in production” are different claims. This is the difference, stated rather than left to be assumed.

  • ImplementedDashboard (English / Arabic)Live in production. Reading is never blocked, so every figure and chart renders from real data.
  • ImplementedDaily Production ReportLive in production. Writes report data and deducts stock in the same step.
  • ImplementedEcommerce request portalRuns as a separate build of the same code from its own branch. The newest Ecommerce features (registering unknown routers on transfer, stored model codes) are on staging and not yet in that build.
  • Built, not liveSerial registry, packing, delivery and paperwork, workforce, inventoryComplete and rendering live data, held behind a deployment-level write lock pending programme sign-off.
  • Built, not liveAudit remediation, audit log, environment guard, login throttleBuilt and tested on the staging branch. Production deploys from main, which does not include them yet.
  • Not connectedSeparate staging database and managed backupsDesigned and scripted, blocked on hosting limits. Previews refuse the production database instead; backups are a manual read-only export.
  • Not connectedRPOMS Print Engine integrationThe print engine is a separate, standalone project. Box labels in RPOMS print through the browser for now.

Technical stack

Application

Next.js 15 (App Router)React 19TypeScriptTailwind CSS v4Base UI / shadcn

Data & persistence

PostgreSQL (Supabase)postgres.jsMySQL (report store)Local JSON file storeZod

Interface & reporting

Rechartsnext-themesTyped i18n (en / ar, RTL)sonner

Documents & import/export

JSZip (DOCX template fill)ExcelJSjsPDFPapa Parseqrcode

Quality & platform

VitestPGliteESLintGitHub ActionsVercelNode.js

Limitations

  • Accounts are shared per role, so the audit trail records a role and a session, not a named person.
  • The login throttle is per server instance, and sessions cannot be revoked individually; both need shared tables that are designed but not written.
  • Some programme-wide baselines are still constants in code rather than settings.
  • Employees and workforce rows are matched by name rather than linked by ID.
  • Only the dashboard is translated; the admin panel is English-only.
  • The content security policy still allows inline scripts, and the daily report form is one very large component.
  • Navigation still differs by role, which reveals that a higher role exists. Blocked actions themselves never name it.

What's next

  1. 01Release the staging line to production after the data-preservation check, starting with the session check on four read endpoints.
  2. 02Back up the production database and prove a restore; move to a plan with managed backups.
  3. 03Give previews and staging their own database.
  4. 04Shared login throttle and server-side session revocation, then per-user accounts.
  5. 05Foreign key from routers to boxes and a join table for Delivery Orders.
  6. 06A Playwright smoke test for each role.
  7. 07Integrate the RPOMS Print Engine once it is validated on the line.

RPOMS is proprietary software built for a live operation. The screenshots on this page were captured from the real application running locally on synthetic demo data: every name, serial, customer and figure in them is fictional, and the dashboard's programme-wide totals are left out. No production data, customer details or credentials appear.

Finished the technical breakdown?

Return to the concise HR overview or discover other projects in the portfolio.

Writing from this work

Building Software in Cyberjaya: The Development Environment Behind the Work

What a production operations system, a client website, an AI research assistant and a vision inspector actually get built on, from a laptop in Cyberjaya: the machine, the stack, where things are hosted, and the local details that shaped the code.

A Vision Model That Only Observes: Building RPOMS AI Without Letting the Model Decide

A router surface inspector where the vision model reports what it sees and a deterministic rule engine decides. Why the split, what a 7B open model actually scored, the blank-image control that changed the prompt, and a safety gate that turns most answers into "check manually".

Printing Labels From the Browser Over Web Bluetooth: The RPOMS Print Engine

A scan-to-label workflow that drives a NIIMBOT B1 Pro directly from Chrome, with no vendor app and no backend. What the protocol work looked like, how 116 tests run without a printer, and what the first real print taught.

The Business Day Is Not the Server Day: Time Zones in a Malaysian Operations System

A deployed server keeps UTC. A warehouse floor in Malaysia keeps UTC+8. Until eight in the morning, the two disagree about what day it is, which is long enough for a morning delivery to file itself under yesterday.

Staged CSV Import: Validating Operational Data Before It Is Written

The office already keeps its records in a workbook. Importing that sheet into a production system means matching its headers loosely, reading its dates the way it wrote them, surfacing duplicates before anything is saved, and never letting the client tell the server what a file contains.

One Storage Interface, Three Backends: How RPOMS Picks Its Database

PostgreSQL on Vercel, MySQL on cPanel, or a folder of JSON files on a laptop, chosen by which environment variables exist. What that bought, what it cost, and the production lessons about pooled connections and reads that never came back.

Reporting From Operational Data

What makes operational reporting actually useful for decisions, instead of just being a record of what happened.

Why Internal Software Needs Good UX

Internal tools get less design attention than public products, even though the people using them have no choice but to use them.

Replacing Repetitive Manual Work With Software

How to tell whether a repetitive manual task is actually worth automating, and what to check before building anything.

Why System Maintenance Matters After Deployment

Shipping an operational system is the start of the work, not the end. What ongoing maintenance actually involves.

Why Software Developers Should Understand the Physical Process

Building software for a real operation goes better when the developer understands the physical process it supports, not just the data model.

From Manual Workflow to Digital Workflow

What actually changes when a manual, paper-or-spreadsheet process becomes software, and what shouldn't change.

How Admin Panels Help Manage Operational Data

What a well-designed admin panel actually needs to do for a production or operations system, beyond CRUD screens.

Common Problems in Manual Production Tracking With Spreadsheets

The specific ways spreadsheet-based production tracking breaks down in practice, the tell that says it is time to move, and what a replacement has to remove.

What Is a Production Management System?

A plain explanation of what production management software actually does, and why it's a different problem from a generic business app.