Case study: replacing a legacy rental ERP without closing for a day.
Super Sonic Equipment Rentals had run its whole operation on one PHP system since 2008. It couldn't be switched off, so I replaced it underneath the business while it kept renting. That system is now Rental Pilot.
Every number on this page comes from the production system. No customer data is shown.
The starting point
One system ran everything
Assets, rental agreements, billing cycles, maintenance, jobs, employees and payroll all lived in one CodeIgniter application on one MySQL database. It had grown for years, around the way this particular yard works: bulk items by the hundred, fencing, partial returns, fuel delivered to machines on site, agreements that stay open for months.
The business rules were in the code and nowhere else. Nobody had written down why a credit is stamped the way it is, or which charges belong on the first invoice only. A rewrite from a blank page would have thrown all of that away.
The constraint
No maintenance window. Drivers are out at 6 a.m., invoices go out on a cycle, and the office can't stop for a weekend while software is swapped. Whatever replaced the old screens had to run beside them, against the same data, and every invoice had to keep adding up at every step.
How it was done
Keep the engine, replace the screens
The old application already held the logic, so it became the API. Its controllers learned to answer in JSON next to the HTML views they already served, on the same models and the same database. A React web app for the office and a React Native app for the field crew were built on that API, one workflow at a time.
For months both worlds were live. Staff could do a job in the old screen or the new one and the record was the same record. That is what made it safe: if a new screen got something wrong, the old one was still there, and the data never forked.
One workflow at a time
Rental agreements, deliveries and pick-ups, billing, work orders, service calls, timesheets, the customer portal. Each one moved when its new screen did everything the old one did, checked side by side on the old UI, the web app and the phone app. Behaviour that worked differently on one of the three was treated as a bug, even when the new behaviour looked better.
Tested on a copy of real life
A sandbox database is refreshed from a production snapshot, read without locking the live system. Every outside email address in it is scrubbed before anything can run against it, so a test can't send a real customer an invoice. Changes are tried there first, then on production through the same deploy path every time.
Every change with a screen is opened in a real browser or on a real device and looked at before it ships. A passing build isn't the test. The invoice on screen is.
Money rules written into the system
Billing runs off a bill-through date and nothing invoices until a person approves it. The system never bills behind anyone's back. Records are never hard-deleted; a void or reversal leaves a trail. Those rules were in the old system by habit. In the new one they are enforced in the API, so every screen, old or new, gets the same answer.