Skip to content
Rapcsány Krisztián
hu
← All projects Web business system

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.

PHP Laravel 12 PostgreSQL Redis WebSockets SOAP integrations PDF and Excel
The challenge
My role

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.

The solution
01

Calendar and online booking

A scheduling diary with rooms and resources, online appointment booking, sync with an external calendar and automatic reminders.

02

Treatment records

Documenting treatments, periodontal status, lab sheets, templates, images and documents in the patient's record.

03

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.

04

Cash desk and invoicing

A cash desk with daily closing and multiple currencies, patient balances, and a link to the invoicing provider.

05

Regulatory integrations

A connection to the national health data system (e-prescriptions), and automatic preparation of the funder reports.

06

Communication and internal work

Newsletters, questionnaires, notifications, internal chat and a task manager, with reports in Excel and in PDF.

Screenshots
Settings

The practice's system settings: log colours, warnings, invoicing, users and working hours.

Appointment calendar

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

What makes it special

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.

Challenges I ran into
01

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.

02

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.

03

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.

Under the hood

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.

Live code

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.

ChangeAuditor.php
/** 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();
}
Result

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

Write to me →