Dental practice management system
A system that runs the whole day-to-day of dental practices: appointments, treatment records, patients, cash desk, invoicing and regulatory reports. I am gradually rewriting the old system in Laravel, while it keeps running.
I work on the system, and I take part in rewriting the old version feature by feature.
A dental practice runs on many interdependent things: appointments, documenting treatments, invoicing, the cash desk, regulatory reports, patient communication. When those live in separate tools, the time goes into administration.
The task is to replace a system built over many years with a modern one, while the practices keep working. It is health data, so separating data and logging cannot be an afterthought.
Calendar and online booking
A scheduling diary with rooms and resources, online appointment booking, sync with an external calendar and automatic reminders.
Treatment records
Documenting treatments, periodontal status, lab sheets, templates, images and documents in the patient's record.
Patient files and portal
Master data, merging duplicate patients, import and export, and a patient portal where patients work with their own data and tasks.
Cash desk and invoicing
A cash desk with daily closing and multiple currencies, patient balances, and a link to the invoicing provider.
Regulatory integrations
A connection to the national health data system (e-prescriptions), and automatic preparation of the funder reports.
Communication and internal work
Newsletters, questionnaires, notifications, internal chat and a task manager, with reports in Excel and in PDF.
The practice's system settings: log colours, warnings, invoicing, users and working hours.
A day view per chair, with colour-coded treatments. Patient names are blurred in the image.
With brand and customer names and personal data removed (except the app store screenshots).
Many practices, one database
Many practices share the same database, and every query is scoped to its own practice.
Field-level change log
Every modification is logged, so the handling of sensitive data can be checked at any time.
Patient portal and online booking
Patients work with their own data and tasks, and appointments can be booked online.
The old behaviour has to be reproduced exactly
In a rewrite the goal is for the system to work the same as before, because the practices are used to that. So I move the old code over feature by feature, and keep a list of where the new one differs from the old.
A log that does not slow things down
The logging takes the original row from memory, so it needs no extra query, writes into monthly partitions, and runs through a queue so it does not slow the system.
Data must not mix
Since every practice sits in the same database, almost every query filters by the practice ID. The administrator can switch between practices, but has to ask for it explicitly.
Many practices, one system
A multi-tenant design: many practices share one database, but every query is scoped to its own practice, so the data cannot mix.
Gradual rewrite
The new system replaces the old one feature by feature, taking over the behaviour one-to-one. The practices keep working during the switch, and there is no single risky cut-over day.
Every change can be traced
A field-level change log for every modification, stored in monthly partitions and written through a queue, so the handling of sensitive data can be re-checked at any time.
Real-time updates
WebSocket-based updates, background processing on Redis queues, a PostgreSQL database, and scheduled sync with the external systems.
A change log with no extra queries
Every modification of sensitive data is logged at field level. The original row comes from memory, so logging adds no database load and can be safely paused for bulk operations.
/** Temporarily disable logging, e.g. during a bulk import or seeding. */
public static function withoutLogging(callable $callback): mixed
{
$previous = self::$paused;
self::$paused = true;
try {
return $callback();
} finally {
self::$paused = $previous;
}
}
/** On `updating`, keep the original row in memory, so no extra SELECT is needed. */
public function captureOriginal(Model $model): void
{
if (! $this->enabled($model)) {
return;
}
$this->originals[spl_object_id($model)] = $model->getOriginal();
}
One system that covers the whole practice, from the calendar to the cash desk and regulatory reports. Rollout is a one-day training plus transferring the existing data, and the system keeps renewing without interrupting the practices.
Is it time to rewrite or audit your old system?
It can be done while it runs, step by step, while keeping the behaviour.
More projects
Car and outdoor web shop
An exact-fit product finder, promotions and discount codes, with over 15,000 orders served.
How it was built → Web application and APITicketing platform
From ticket purchase to entry control and follow-up: a web system with payments, invoicing, a mobile API and a community layer.
How it was built → iOS and AndroidEvent check-in app
A phone app that lets event staff admit guests quickly and with proper validation.
How it was built →