Case study · In production

SIMKEU Web.

Finance and HR ERP for a container port terminal operator in Indonesia. One web app replacing a legacy Oracle Forms/Reports system.

Role
Full-stack Developer
Period
May 2026 — present
Team
6 devs · top contributor, 500+ commits in 5 months
Stack
Java 21 · Jakarta EE 11 · Payara · Oracle

The source code is proprietary and is not published here. This page describes my work on it.

Finance dashboard
5.6 s330 ms
17× faster after one profiling session
Budget allocation list
49.8 s timeoutLoads
Per-row subquery rewritten as one set-based query
Legacy reports
~30
Oracle Reports rebuilt to match the printouts figure for figure
Journal lines
301
Half-posted vouchers repaired in production

About the system

SIMKEU Web is the company-wide back-office system for a container port terminal in Indonesia. It replaces a legacy Oracle Forms/Reports application with a single web app.

Finance & accountingGeneral ledger, journals, cash and bank, receivables, payables, cash advances, tax, statutory financial reports
BudgetingRevenue, cost and investment budgets, budget-realization monitoring
Procurement & inventoryPurchase requests, cost plans (RAB), orders, goods receipt, stock valuation
Human CapitalPersonnel, payroll, leave, business trips (SPPD), payroll reports
OperationsFuel (BBM) request and consumption dashboard
Management dashboardsP/L, cash position, financial ratios, workforce

It has more than 1,200 Java classes, more than 700 screens and fragments, and several hundred menus behind role-based access.

Performance

  • Finance dashboard went from 5.6 s to about 330 ms. I profiled it and fixed four bottlenecks: N+1 queries, missing indexes, repeated view scans, and uncached lookups.
  • A budget-allocation list that timed out after 49.8 s now loads normally. The cause was a per-row "already paid" subquery, which I rewrote as one set-based query.
  • Static asset delivery: I fixed caching headers on an 8.7 MB application shell that had been re-downloaded on every visit.

HR module port

Personalia and Payroll, moved from a separate legacy app into the main system.

  • Ported the personnel and payroll screens from simpersweb. That covered 73 HR menus mapped to their tables.
  • Built and wired 29 payroll reports in PDF and Excel.
  • Ran an HR audit that found about 58 issues and wrote 6 fix plans. Risky access rules first ran in a shadow-logging mode, so they could be checked for three working days before being switched on.
  • Wrote the HR user documentation: an SOP in PDF and a training deck.

Dashboards

  • Finance dashboard: live cash and bank position (closed months plus the open month), 8 financial ratios, and P/L. Every card drills down to the underlying vouchers.
  • Human Capital dashboard: headcount and composition, read from one source of truth.
  • Fuel (BBM) dashboard: replaced the old fuel issuance log with request tracking and consumption analytics.

Rebuilding legacy reports

I re-implemented about 30 Oracle Reports (.rdf) so the web output matches the legacy printouts figure for figure, including quirks that accountants rely on.

  • Balance sheet, income statement and cash-flow reports, with per-account breakdowns
  • Receivable, payable and cash-advance aging
  • Cash book, sub-ledgers, expense summaries, stock movement and valuation
  • A tax report in the 13-column layout the new national tax system (Coretax) requires
  • Excel exports with Apache POI and PDFs with iText

Data integrity

  • Found and repaired vouchers that were only half posted (header posted, journal lines not). I fixed 301 lines in production after taking a backup, and closed the code path that caused them.
  • Reconciled report pairs that should agree but didn't: cash book vs cashier report, inventory GL vs stock movement, stock card vs balance. Then fixed the root causes, including sign conventions, period filters and unposted months.
  • Traced wrong inventory values to a ghost stock unit that had inflated the moving-average cost.

Security & access

  • Fixed CSRF token handling across the single-page app and tightened the Content Security Policy.
  • Added an account-status check that runs during the session, so disabled users lose access right away.
  • Designed a role-based privilege matrix: 26 role classes, 1,314 menu grants, 45 users.
  • Fixed multi-stage approval workflows, such as directors who were missing their decision buttons and supervisors who couldn't see documents in their queue.

Integrations & delivery

Integrations

  • e-Meterai (digital stamp duty): fixed token handling and serial-number pool management.
  • RabbitMQ notification consumer for approval and e-mail events.

Testing and delivery

  • Built a Playwright smoke-test harness that clicks through the whole app (all menus and screens) to catch broken pages before release.
  • Set up Docker-based local environments and documented the Payara deployment pitfalls.

What I learned

  • Matching a legacy system's results is harder than copying its features. Accountants trust numbers, not screens.
  • Most "bugs" in an ERP are business-process gaps: a posting job never run, a missing approval step, master data with no owner.
  • Measure before optimizing. One profiling session found the 17× dashboard speedup.

Tools

Java 21Jakarta EEPayaraOracle SQLPL/SQL MavenDockerRabbitMQApache POIiText jQueryDataTablesHighchartsflatpickrPlaywright Git / GitLabClaude Code