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, external calendar sync 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 integration with an invoicing provider.
Regulatory integrations
Connection to the national health data system (e-prescriptions) and automated preparation of funder reports.
Communication and internal work
Newsletters, questionnaires, notifications, internal chat and a task manager, with reports in Excel and 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 not to make it better, but to make it work the same, because the practices are used to it. 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, and every query is strictly scoped to its own practice, so data cannot mix.
Gradual rewrite
The new system replaces the existing one feature by feature, reproducing behaviour one-to-one, so practices keep working throughout and there is no single risky cut-over.
Full traceability
A field-level change log for every modification, stored in monthly partitions and written through a queue, so handling of sensitive data can be audited at any time.
Real-time and reliable
WebSocket-based updates, background processing on Redis queues, a PostgreSQL database, and scheduled syncs with 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.
Case study → 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.
Case study → iOS and AndroidEvent check-in app
A mobile app that lets event staff admit guests quickly and with proper validation.
Case study →