Start a Project

Rentalica

Client Work · Legacy Modernization

Rentalica landing page - which vehicle earns and which quietly costs you

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.

Years of production data migrated
10+
Tables rebuilt on PostgreSQL
22
Password resets required
0
Automated tests guarding the rebuild
102

Software That Still Worked

FlexiRent wasn't broken – it ran a real rent-a-car business every day for a decade. It just stood on a stack the world had left.

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

Change the technology or the product, never both at once. The new system had to be provably identical before it could be better.

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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.

LayerLegacyRentalica
RuntimePHP 5.6 on Ubuntu 14.04, both end-of-lifeContainerized Python and Node, current releases
BackendIn-house PHP MVC framework, ~55k lines to maintainFastAPI + SQLAlchemy 2.0, typed and auto-documented
DatabaseMySQL 5.5, no foreign keys, JSON inside TEXT columnsPostgreSQL with real constraints and JSONB where it belongs
FrontendServer-rendered Bootstrap 3 admin themeNext.js 15 and React 19 with a generated typed API client
AuthCustom session files, permissions as a comma-separated stringhttpOnly cookie sessions, role checks enforced per request
TenancySingle business, one installationMulti-tenant, with isolation enforced at the query layer
TestsNone102 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, the new foundation added what PHP 5.6 never could.

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 querying a live business is only as trustworthy as its limits. Ours are enforced in code and PostgreSQL – not in a prompt.

  1. 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.

  2. 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.

  3. 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.

Open for new projects

See starting prices for this kind of work, or tell us what you're building and we'll reply within 24 hours. Rather use one of our products, like Tvrtko.ai, Moj Kolega or Titlomat? Those are open too.