Services Work About Contact
Our Product · Legacy Modernization

Rentalica

A rent-a-car back-office that ran for over ten years on PHP 5.6, rebuilt as a modern multi-tenant SaaS - carrying a decade of bookings, contracts and documents across without asking a single user to reset their password. Then taken well past where the old system could go.

View Rentalica Modernize Your System
Rentalica landing page - the operational brain for a rent-a-car fleet, showing net profit per vehicle
10+
Years of production data migrated
22
Tables rebuilt on PostgreSQL
0
Password resets required
102
Automated tests guarding the rebuild

Software That Still Worked

FlexiRent wasn't broken. It ran a real rent-a-car business every day for a decade. It was just standing on a stack the world had moved off - and that's a harder problem than a rewrite of something nobody uses.

What Was Running

  • PHP 5.6 on Ubuntu 14.04 - both long past end of life
  • A bespoke in-house PHP MVC framework, ~55k lines
  • MySQL 5.5 with no foreign keys - relations enforced in app code
  • JSON hidden inside TEXT columns, permissions stored as CSV
  • 440 MB of contracts and scans on a single server's disk

Why Rewrites Like This Fail

The usual approach is to read the old code, guess at the business rules, and ship something that looks better but quietly gets the details wrong. The operator notices in week two, when a contract prints the wrong deposit.

A decade of production behaviour is not in the codebase. It's in the edge cases, the workarounds and the numbers people check every morning. That's what a rewrite has to preserve first.

We Wrote the Original

FlexiRent wasn't inherited from a stranger. LumiVerse built it, then supported it in production for over ten years with a real fleet operator using it daily.

That's ten years of watching how the work actually happens: which screens get opened first thing in the morning, which fields are always left blank, what the operator does when a van comes back damaged on a Sunday. None of that is written down anywhere. It's the reason the rebuild could skip the discovery phase and go straight to building.

Domain Fluency, Not Guesswork

  • Croatian rental specifics - OIB checksums, AO and kasko insurance, registration and periodic inspection dates
  • Pricing rules that include partner splits and provisions
  • Contract PDFs that have to keep printing exactly as before
  • Workflows mirrored closely enough that switching needs no retraining

Parity First, Ideas Second

The rule that made this safe: change the technology or change the product, never both at once. The new system had to be provably identical before it was allowed to be better.

01

Capture Everything, Change Nothing

A complete read-only backup of production: 2,855 files, the full database, the web server config. The live server was never modified. Every later decision could be checked against a frozen, verified copy.

02

Boot the Old App Locally

PHP 5.6, Apache and MySQL packaged into Docker so the legacy system ran on a laptop. Not to keep it alive - to have a reference implementation to compare against, screen by screen, number by number.

03

Write the Analysis First

Every table mapped to a target entity, every implicit relationship documented, every risk listed before a line of new code: the JSON blobs, the invalid zero-dates, the CSV permissions, the PDF generator.

04

Rebuild to Parity

A faithful replica on the new stack - same screens, same numbers, no new ideas - tagged and frozen as the rollback point. Improvements were not allowed until this version matched the old one.

05

Migrate, Then Prove It

MySQL to PostgreSQL: JSON blobs normalized, real foreign keys added, invalid dates converted. Then automated cross-database integrity checks compared row counts and records against the source until they matched.

06

Rehearse the Cutover

A written runbook with a rollback at every phase: freeze the old app, re-migrate fresh data, verify against pre-freeze numbers, then switch. Rehearsable in full without touching production.

The Detail That Made Switching Painless

The legacy system stored passwords as bcrypt hashes. Bcrypt is bcrypt - the new backend verifies those same hashes directly. So every user kept the exact username and password they already had. No reset emails, no support calls, no "we've migrated, here are your new credentials" message. The old system's file uploads were carried over the same way: same documents, same links, still downloadable.

What Actually Changed

Same business rules. Entirely different foundation.

Layer
Legacy
Rentalica
Runtime
PHP 5.6 on Ubuntu 14.04, both end-of-life
Containerized Python and Node, current releases
Backend
In-house PHP MVC framework, ~55k lines to maintain
FastAPI + SQLAlchemy 2.0, typed and auto-documented
Database
MySQL 5.5, no foreign keys, JSON inside TEXT columns
PostgreSQL with real constraints and JSONB where it belongs
Frontend
Server-rendered Bootstrap 3 admin theme
Next.js 15 and React 19 with a generated typed API client
Auth
Custom session files, permissions as a comma-separated string
httpOnly cookie sessions, role checks enforced per request
Tenancy
Single business, one installation
Multi-tenant, with isolation enforced at the query layer
Tests
None
102 backend tests plus browser end-to-end coverage

We Didn't Stop at a Copy

A faithful replica was the safety requirement, not the goal. Once the data was proven and the old system still sat there as a rollback, the new foundation started earning its keep - with capabilities a PHP 5.6 stack was never going to carry.

Analytics That Answer the Only Question That Matters

Most fleet software reports revenue. Rentalica reports what's left. Every cost is entered once with a period - one-off, monthly or annual - then spread across the months it actually covers and charged to the vehicle that incurred it. Leasing, liability and comprehensive insurance, registration, tyres, servicing and fuel all land on the right van.

  • Net result per vehicle - the number nobody had before
  • Break-even view: does this month's income cover the fleet?
  • Weekly digest: vehicles in deficit, idle assets, expiring policies, unpaid rentals
  • Fleet composition on any past date, for capacity planning
  • Partner splits and provisions handled inside the maths

Receipts That Fill In Their Own Form

Photograph an invoice or a fuel slip. The scan is read on-site, then the total, date, vendor, supplier tax ID, invoice number and registration plate are pulled out, the cost category is guessed and the vehicle matched. Croatian decimal formatting included. Nothing saves until a person confirms it.

An Assistant That Speaks Croatian and SQL

Ask which passenger van does the fewest kilometres a month, in plain Croatian, and get three things back: the answer, the table it came from, and the exact query that produced it. Nothing is hidden behind a chat bubble - the operator can check the work before trusting it.

  • Questions in Croatian, answers with the query shown
  • Profit questions routed to the real P&L engine, never approximated
  • Fuzzy vehicle matching, so a typo still finds the right van
  • Durable instructions - tell it once that a car is private, it remembers

Document Scanning, Kept In-House

Driving licences, ID cards, passports and vehicle registrations are classified and read by a self-hosted OCR service, so the scans stay on your own infrastructure. Machine-readable zones are parsed with their ICAO check digits - a clean read validates itself.

A Document Archive Worth the Name

Every upload lands in one place and is searchable by what's written inside it, because the extracted text is stored next to the file. A link table records what each document is attached to - so you can go from a document back to its vehicle, cost or contract, and find the ones attached to nothing.

Contacts, Grown Into a CRM

One directory for customers, companies and partners, segmented with groups and tags. Each contact opens a full profile: every rental, every contract, what they still owe, plus linked tasks and documents. Licence numbers, categories and expiry dates sit on the record itself.

Work That Isn't Kept in Someone's Head

To-do lists per person and shared across the team, each task pinned to the vehicle, booking or customer it concerns, with an owner, a priority and a due date. Registration, insurance and inspection expiries raise their own reminders without anyone remembering to look.

Letting AI Near a Real Database

An assistant that writes queries against a live business is only as trustworthy as the thing standing between it and the data. These limits are enforced in code and in PostgreSQL - not politely requested in a prompt.

01

Whitelisted, Not Trusted

Every generated statement is parsed into a syntax tree and each table it touches is enumerated - including the ones buried in subqueries and comma joins. Anything outside the approved set is rejected before it reaches the database. Staff accounts and password hashes are permanently out of reach.

02

Isolation You Can't Talk Your Way Past

Before execution, every referenced table is rewritten as a filtered view of that tenant's rows, so an unqualified query resolves to the right data by construction rather than by good behaviour. Common table expressions are refused outright - one could otherwise redefine that filter.

03

Read-Only at the Database

Queries run inside a read-only transaction with a statement timeout and a hard row cap, and that transaction is always rolled back. A write isn't declined by the model's better judgement - it cannot happen on that connection.

Stuck With a System You Can't Retire?

If the software running your business is old, undocumented and too important to break, that's the problem we solve. Parity first, your data proven intact, a cutover you can roll back.

Schedule a Call View Rentalica