Case study2025Internal · Private

AZ Turnhout — internal tooling

Internal applications and migration tooling for the AZ Turnhout pharmacy compounding workflow.

Role
Developer
Year
2025
Stack
Next.js · TypeScript · SQL Server
Status
Private
InternalHealthcareBelgiumPharmacy compounding
Voorraadbeheer · inventory view (looped capture, identifiers redacted at source)
Context

Where this lives

AZ Turnhout is a regional hospital in the Kempen. The pharmacy department runs the compounding workflow that prepares patient-specific oncology mixtures — drugs like Fluorouracil, Paclitaxel and Oxaliplatin diluted in NaCl 0.9% or Glucose 5%, each with their own preparation method, holding time and traceability requirements.

Behind that workflow sat a long-lived FileMaker database. It worked, but every new request meant working around it. The brief was to lift the system into a modern stack without disrupting daily operations — a SQL Server back-end, a focused Next.js front-end for the people who actually use it, and a migration path that didn't require manual cleanup.

The audience is internal: pharmacy technicians (bereiders), supervising pharmacists (apothekers), and a small group of admins. Nobody on the floor has time for a clever UI. The product has to read like an instrument, not a dashboard.

The problem

The legacy FileMaker export held years of preparation records: every batch with its teller, lot number, preparation date, product, method and solvent. Schema-wise it was flat and tolerant — the fields were strings, the relationships were implicit, and the validation lived in muscle memory.

Beyond the data itself, the harder problem was that the system had no clean seam to extend from. New workflows landed as patches on the existing layout. Filtering and sorting the bereidings-logboek across years, lot numbers and products was slow; reporting required exporting and post-processing in Excel.

Three constraints shaped the rewrite: nothing could be lost in migration, the new system had to be auditable end-to-end, and the daily workflow had to feel familiar — same vocabulary, same shortcuts, same speed of entry.

Internal projects

Four apps, one internship

Four distinct internal tools shipped under the AZ Turnhout brief. Each one solved its own workflow problem — switch tabs to step through them.

01·VoorraadbeheerLive · Internal

Voorraadbeheer

Pharmacy compounding logbook — preparation records, stamdata and inventory in a single auditable system.

Context

The daily-driver app for the AZ Turnhout pharmacy compounding team. Every batch of patient-specific oncology mixtures is recorded here — teller, lot number, preparation date, product, method, solvent — and the same data feeds the audit trail and the inventory view.

Replaces a long-lived FileMaker setup that had grown to the edge of its lifespan. Same vocabulary as the paper rhythm the team already uses, so the switch felt like a tooling upgrade, not a process change.

What was built

Next.js front-end on the bereidings-logboek with filter / sort across apotheker, bereider, datum, jaar, lotnummer, maand and product. SQL Server back-end exposed through a repository layer of stored procedures (Activate / Delete / Filter / Get / Insert / Sorteer / Update per entity).

Python migration pipeline that lifted years of historical records from the FileMaker export through a normalised xlsx staging file into idempotent SQL — every INSERT guarded by IF NOT EXISTS so the pipeline can re-run against the same target without producing duplicates.

Stack
Next.jsTypeScriptSQL ServerStored proceduresPythonpandas
Outcome

Compounding records, stamdata and inventory now live in a single auditable system. Filters and sorts that previously required an Excel export run inside the UI.

Migration runs are reproducible; staging-to-production switches are no-ops when nothing has changed.

voorraadbeheer nieuwe video met blur

+ 13 supporting captures archived internally

Engineering

How it holds up

The non-glamourous parts of internal hospital software — auditability, idempotency, data migration discipline.

01

Repository-style stored procedures

Every entity has the same vocabulary of operations: Activate, Delete, Filter, Get, Insert, Sorteer, Update. The front-end never writes raw SQL — it calls a named procedure with a fixed parameter shape, which keeps the data layer predictable and the audit story simple.

02

Idempotent migrations

Every generated INSERT is wrapped in IF NOT EXISTS guards against the right business key. A migration run can be repeated against the same database without producing duplicates, which made testing the import on a copy of production cheap and safe.

03

Python staging layer

Migration goes via a normalised xlsx staging file with one tab per logical table — Controle, Producten, Methodes, Oplosmiddelen, Preparations, DoseVolume. Pandas does the cleaning; a separate generator produces the SQL. Failures show up in staging, not in production.

04

Audit-friendly logbook

Bereidings_logboek stores Dose_Id, BereiderId, ApothekerId, DatumTijd, LotNummer, Vervaldatum_batch, BereidingsNummer, Teller and Opmerkingen for every batch. Updates land as explicit UPDATE statements scoped on teller + nummer + timestamp — never blind overwrites.

05

AD-bound permissions

Privileged operations — the licence CLI, certain admin views — run against AD identity. The CLI reads the current memberships before mutating, surfaces a preview, and only applies on explicit confirmation. Nothing happens implicitly.

06

Built for the pharmacy floor

Form design follows the existing paper rhythm: teller → product → preparation → solvent → volume → lot → expiry. The vocabulary is Dutch where the staff already uses Dutch (apotheker, bereider, oplosmiddel) and English where the codebase is English. Familiarity beats novelty.

Outcome

What changed

The pharmacy moved off FileMaker without losing a single historical record. Migration runs are reproducible — running the pipeline twice against the same target is a no-op, which made staging-to-production switches uneventful.

Day-to-day, the bereidings-logboek is now the single source of truth across roles. Filters and sorts that used to require an Excel export run in the UI; preparations are recorded against the same teller / lot numbers that the audit trail uses.

The supporting tools — licence CLI, endpoint installer, service desk dashboard — absorbed the manual work that used to live in a tribal-knowledge runbook. A new endpoint is now a typed command, not a click-path.

Stored procedures60+Activate · Delete · Filter · Get · Insert · Sorteer · Update per entity
Migration tabs6Controle · Producten · Methodes · Oplosmiddelen · Preparations · DoseVolume
License groups managed16AG_* and MG_* Microsoft 365 groups bound to computer objects in AD
Built with
Next.jsTypeScriptSQL ServerPythonPowerShellTailwind
Note

This is internal software. It will never have a marketing site, a launch tweet or a public roadmap. Its measure is whether the pharmacy team notices it less over time — and that's the bar it's held to.

The patterns underneath — repository-style stored procedures, idempotent migrations, audit-first data model — are general enough that the same scaffold has carried into the surrounding tools (service desk dashboard, endpoint installer, licence CLI). The case study above documents what's visible; the rest stays internal where it belongs.