Dienstplaner — Schichtplanung mit Symfony

Wer schon einmal versucht hat, Dienstpläne in Excel zu verwalten, kennt das Problem: Irgendwann wird die Tabelle unübersichtlich, jemand überschreibt versehentlich eine Formel, und spätestens bei der dritten Vertretungsregelung bricht das Konstrukt zusammen. Der Dienstplaner löst genau das — eine interne Web-Anwendung mit Symfony, die Personen, Aufgaben und Termine so zusammenbringt, dass die Planung zuverlässig funktioniert. Das Tool ist nicht für den öffentlichen Einsatz gedacht, sondern wird intern genutzt — und dabei ständig weiterentwickelt. Neue Anforderungen kommen aus dem Alltag, und genau so wächst die Anwendung: Stück für Stück, modular und ohne den bestehenden Code zu gefährden.

Was der Dienstplaner macht

Im Kern geht es um eine einfache Frage: Wer übernimmt welche Aufgabe an welchem Tag? Das klingt trivial, wird aber schnell komplex. Menschen sind im Urlaub, haben andere Verpflichtungen oder sind für bestimmte Aufgaben nicht eingeteilt. Der Dienstplaner bildet genau diese Realität ab. Du hast eine Monatsübersicht, siehst auf einen Blick alle Planungstage, und weist Personen per Klick einer Aufgabe zu. Das System warnt dich, wenn jemand an dem Tag nicht verfügbar ist — sei es wegen Abwesenheit oder wegen einer externen Verpflichtung.

Dabei geht die Anwendung deutlich über eine einfache Zuweisungstabelle hinaus. Der Dienstplaner kennt Sondertermine, die die Planung beeinflussen — bestimmte Wochen fallen aus, andere verschieben sich. Er kann automatische Planungsvorschläge generieren, die auf einem Fairness-Algorithmus basieren: Wer lange nicht dran war, wird bevorzugt vorgeschlagen. Und er exportiert fertige Pläne als PDF, Excel oder Word — je nachdem, was gebraucht wird.

Dashboard des Symfony-Dienstplaners mit Schnellzugriff, Monatsübersicht und Aufgabenzuweisungen pro Abteilung
Dashboard: Monatsplan mit Abteilungen und Aufgabenzuweisungen

Warum Symfony?

Für ein Projekt dieser Größe braucht man ein Framework, das mitdenkt. Symfony bringt eine klare Struktur mit: Controller für die HTTP-Logik, Services für die Geschäftslogik, Doctrine für die Datenbank, Twig für die Templates. Diese Trennung ist kein akademischer Selbstzweck — sie sorgt dafür, dass der Code auch nach 80 Entwicklungsaufgaben noch lesbar und wartbar bleibt.

Konkret läuft der Dienstplaner auf Symfony 7.4 mit PHP 8.3 und einer MariaDB-Datenbank. Das Frontend setzt auf Bootstrap 5 und serverseitig gerenderte Twig-Templates — kein JavaScript-Framework, keine Single-Page-App. Wo dynamisches Verhalten nötig ist, etwa bei der Aufgabenzuweisung im Planungsraster, kommt gezieltes AJAX zum Einsatz. So bleibt die Anwendung schnell, auch auf schwächerer Hardware.

Die Architektur im Überblick

Der Dienstplaner folgt einer klassischen Schichtenarchitektur. Ganz oben stehen die Controller — sie nehmen HTTP-Anfragen entgegen und delegieren an die Service-Schicht. Die Services enthalten die eigentliche Geschäftslogik: Zuweisungen prüfen, Konflikte erkennen, Planungsvorschläge berechnen. Darunter liegen die Repositories, die über Doctrine mit der Datenbank kommunizieren. Die Templates erzeugen das HTML, das der Browser anzeigt.

Das klingt nach Lehrbuch, und das ist Absicht. In der Praxis bedeutet diese Trennung: Wenn sich die Planungslogik ändert, muss ich nur den betroffenen Service anfassen. Die Controller bleiben schlank, die Templates wissen nichts von der Datenbank. Nach über 80 abgeschlossenen Aufgabenpaketen hat sich diese Struktur bewährt — nichts musste grundlegend umgebaut werden.

Planungsansicht des Dienstplaners mit Monatsraster — Aufgaben als Spalten, Tage als Zeilen, farbige Statusanzeigen
Planungsansicht: Aufgabenzuweisung im Monatsraster mit Konflikterkennung

Das Datenmodell

Hinter dem Dienstplaner steht ein Datenmodell mit elf Entities. Die wichtigsten sind schnell erklärt: Eine Person kann für bestimmte Aufgaben eingeteilt werden. Aufgaben gehören zu einer Abteilung, und Abteilungen gehören zu einer Niederlassung — die oberste Organisationseinheit. Ein Tag ist ein geplanter Termin, und eine Zuweisung verbindet genau eine Person mit einer Aufgabe an einem bestimmten Tag.

Dazu kommen Hilfs-Entities für die Planung: Abwesenheiten markieren Zeiträume, in denen jemand nicht verfügbar ist. Externe Aufgaben blockieren Personen an einzelnen Tagen. Sondertermine beeinflussen die gesamte Planung — etwa wenn eine Woche komplett ausfällt oder sich Termine verschieben. Und ein Planungssperre-Mechanismus verhindert, dass zwei Planer gleichzeitig denselben Bereich bearbeiten.

Doctrine bildet all das als PHP-Klassen mit Attributen ab. Unique-Constraints stellen auf Datenbankebene sicher, dass keine Person zweimal am selben Tag für die gleiche Aufgabe eingeteilt wird. Cascade-Operationen räumen abhängige Daten automatisch auf, wenn ein übergeordnetes Objekt gelöscht wird.

Planung — das Herzstück

Die Planungsansicht ist das, womit die Nutzer täglich arbeiten. Sie zeigt ein Raster: Tage in den Zeilen, Aufgaben in den Spalten, gruppiert nach Abteilung. Per Klick öffnet sich ein Dialog, in dem die verfügbaren Personen angezeigt werden. Das System markiert dabei sofort, wer an dem Tag abwesend oder anderweitig eingeteilt ist. Trotzdem kann der Planer eine Zuweisung erzwingen — manchmal gibt es keine Alternative, und die Software soll den Menschen nicht bevormunden.

Damit sich zwei Planer nicht gegenseitig in die Quere kommen, gibt es eine datenbankgestützte Sperre. Wer eine Abteilung bearbeitet, hält eine Sperre für zehn Minuten. Ein Heartbeat-Mechanismus verlängert die Sperre automatisch, solange der Planer aktiv ist. Verlässt er die Seite, wird die Sperre freigegeben. Das ist kein technisches Spielzeug — in der Praxis planen manchmal mehrere Personen gleichzeitig, und ohne Sperre käme es zu Datenkonflikten.

Automatische Vorschläge

Eine der nützlichsten Funktionen ist der Planungsvorschlag. Statt jede Zelle manuell zu füllen, kann der Planer sich vom System Vorschläge machen lassen. Der Algorithmus berücksichtigt dabei mehrere Faktoren: Wer ist verfügbar? Wer war zuletzt dran? Wie gleichmäßig sind die Einsätze über alle Personen verteilt? Das Ergebnis ist ein Vorschlag, den der Planer Zelle für Zelle annehmen oder überspringen kann. Kein Autopilot — aber eine enorme Zeitersparnis, besonders bei größeren Teams.

Planungsregeln für Sondertermine

Nicht jede Woche läuft nach dem gleichen Schema. Bestimmte Termine — Kongresse, Gedenkfeiern, Dienstwochen — verändern die Planung grundlegend. Der Dienstplaner kennt diese Sondertermine und reagiert automatisch: An Gedenktagen werden keine Aufgaben eingeplant. In der Woche vor einem Kongress fallen reguläre Termine aus. Bei Dienstwochen verschiebt sich der Wochentag. Der Planer muss das nicht manuell berücksichtigen — die Regeln greifen im Hintergrund und passen das Raster an.

Exporte — PDF, Excel und Word

Ein Plan, der nur im Browser existiert, hilft nur bedingt. Deshalb exportiert der Dienstplaner fertige Monatspläne in drei Formaten. Die PDF-Version zeigt den Plan im DIN-A4-Format, gruppiert nach Abteilung — zum Ausdrucken und Aushängen. Der Excel-Export liefert die Daten tabellarisch, sortiert nach Aufgaben. Und der Word-Export erzeugt ein Dokument mit tagesweisen Abschnitten, das sich leicht weiterbearbeiten lässt.

Unter der Haube kommen dafür drei PHP-Bibliotheken zum Einsatz: dompdf für die PDF-Erzeugung, PhpSpreadsheet für Excel und PhpWord für Word-Dokumente. Alle drei werden serverseitig generiert — der Nutzer klickt auf „Exportieren“ und bekommt die fertige Datei.

Kalender und persönliche Ansichten

Nicht jeder Nutzer braucht die volle Planungsansicht. Für die meisten reicht es zu wissen, wann sie selbst eingeteilt sind. Dafür gibt es persönliche Kalender, die ohne Login über einen individuellen Token-Link erreichbar sind. Der Kalender zeigt die eigenen Einsätze, kann als ICS-Datei in Google Calendar oder Outlook importiert werden und aktualisiert sich automatisch.

Daneben gibt es öffentliche Kalender auf Niederlassungsebene, die Abwesenheiten und den Gesamtplan zeigen — je nach Konfiguration anonymisiert oder mit vollen Namen. Die Kalenderansicht selbst nutzt FullCalendar, eine JavaScript-Bibliothek, die Monats-, Wochen- und Tagesansichten bietet.

FullCalendar-Monatsansicht des Dienstplaners mit farbigen Einträgen für verschiedene Aufgaben und Abwesenheiten
Kalender: Monatsansicht mit farbigen Aufgaben und Abwesenheiten
Personenbearbeitung im Dienstplaner mit Kontaktdaten und zugeordneten Aufgaben als Checkliste
Personenverwaltung: Kontaktdaten und Aufgabenzuordnung
Öffentlicher Abwesenheitskalender des Dienstplaners — ohne Login per Token-Link erreichbar
Abwesenheitskalender: Ohne Login per persönlichem Link erreichbar

Sicherheit und Rollen

Ein System, in dem personenbezogene Daten verarbeitet werden, braucht ein durchdachtes Berechtigungskonzept. Der Dienstplaner kennt vier Rollen: Administratoren verwalten alle Niederlassungen und Benutzerkonten. Niederlassungsleiter haben vollen Zugriff auf ihre eigene Niederlassung. Planer können nur die ihnen zugewiesenen Abteilungen planen und die zugehörigen Stammdaten bearbeiten. Benutzer sehen ihr Dashboard, ihren Kalender und ihr Profil — sonst nichts.

Die Berechtigungsprüfung läuft über einen Symfony Security Voter, der bei jeder Aktion prüft, ob der aktuelle Nutzer das Recht dazu hat. Zusätzlich unterstützt die Anwendung Zwei-Faktor-Authentifizierung — wahlweise über eine Authenticator-App, per E-Mail-Code oder mit Backup-Codes. Je nach Konfiguration kann die Zwei-Faktor-Authentifizierung für bestimmte Rollen verpflichtend sein.

Benachrichtigungen und ICS-Import

Am Monatsanfang können alle eingeplanten Personen automatisch per E-Mail benachrichtigt werden. Die Nachricht enthält die eigenen Einsätze und eine ICS-Datei als Anhang, die sich direkt in den Kalender importieren lässt. Der E-Mail-Text lässt sich pro Niederlassung anpassen, sodass jede Gruppe ihre eigene Ansprache verwenden kann.

In die andere Richtung funktioniert der PDF-Import: Externe Dienstpläne im PDF-Format können hochgeladen werden. Die Anwendung extrahiert daraus Daten und Personennamen, ordnet sie den konfigurierten Wochentagen zu und legt automatisch externe Aufgaben an. Ein Fuzzy-Matching hilft dabei, Namen trotz leicht abweichender Schreibweise korrekt zuzuordnen.

Suche und Fairness-Analyse

Über die Suchfunktion lassen sich Personen finden und deren Einsatzhistorie einsehen. Welche Aufgaben hat jemand in den letzten Monaten übernommen? Wie oft war jemand eingeteilt im Vergleich zum Durchschnitt? Die Fairness-Analyse zeigt farbcodiert, ob die Verteilung ausgeglichen ist — grün bedeutet im Rahmen, rot bedeutet deutlich über oder unter dem Schnitt. Abweichungen von mehr als 20 Prozent werden markiert, sodass der Planer gezielt gegensteuern kann.

Mehrsprachigkeit

Der Dienstplaner unterstützt drei Sprachen: Deutsch, Englisch und Französisch. Die Übersetzungen liegen als YAML-Dateien im Symfony-Translations-Verzeichnis. Die Sprache lässt sich pro Benutzer einstellen und wird in der Session gespeichert. Alle Labels, Fehlermeldungen und Systemtexte sind übersetzt — von der Anmeldung bis zum Export.

Qualitätssicherung

Ein produktives System braucht Tests, und der Dienstplaner hat sie. Die Unit-Tests decken die kritischen Services ab: Zuweisungslogik, Kalenderberechnung, Planungsregeln, PDF-Import, Berechtigungsfilterung. PHPStan prüft den gesamten Code statisch auf Typfehler und fehlende Methoden. PHP CS Fixer sorgt für einheitliche Formatierung. Das Ergebnis: null Warnungen, null Fehler — nicht als Ziel, sondern als Standard.

Tech-Stack auf einen Blick

Für alle, die es genau wissen wollen, hier die wichtigsten Technologien im Überblick.

Komponente Technologie
FrameworkSymfony 7.4
SprachePHP 8.3
DatenbankMariaDB 10.11
ORMDoctrine ORM
TemplatesTwig
FrontendBootstrap 5
KalenderFullCalendar v6
PDF-Exportdompdf 2.0
Excel-ExportPhpSpreadsheet 2.0
Word-ExportPhpWord 1.0
AuthentifizierungSymfony Security + 2FA-Bundle
Code-QualitätPHPStan, PHP CS Fixer

Was ich dabei gelernt habe

Der Dienstplaner ist kein fertiges Produkt, das irgendwann „fertig“ ist. Er wird im Alltag genutzt und dabei kontinuierlich weiterentwickelt. Wenn eine neue Anforderung auftaucht — etwa ein zusätzliches Exportformat oder eine andere Art der Benachrichtigung — dann wird sie umgesetzt. Nicht als Sonderlösung, sondern als neues Modul, das sich sauber in die bestehende Architektur einfügt. Genau dafür zahlt sich die modulare Struktur aus: Neue Funktionen entstehen als eigene Services, ohne dass bestehender Code angefasst werden muss.

Das Projekt hat gezeigt, dass Symfony für interne Werkzeuge dieser Größe eine exzellente Wahl ist. Die strikte Trennung von Verantwortlichkeiten hat dafür gesorgt, dass auch nach über 80 Aufgabenpaketen kein großes Refactoring nötig war. Die Entscheidung gegen ein JavaScript-Frontend und für serverseitiges Rendering mit gezieltem AJAX hat die Komplexität niedrig gehalten. Und die konsequente Nutzung von Doctrine-Attributen statt XML-Mapping hat den Code lesbarer gemacht.

Wenn du selbst mit Symfony ein ähnliches internes Tool planst, ist mein wichtigster Rat: Fang mit der Datenstruktur an und halte die Architektur modular. Wenn die Entities sauber modelliert sind und jede Funktion in einem eigenen Service lebt, kannst du das System über Jahre weiterentwickeln, ohne dass es aus den Fugen gerät.


Fachbegriffe nachschlagen

Viele der hier verwendeten Begriffe — von Doctrine über Twig bis hin zu Security Votern — sind im Glossar zur Symfony-Entwicklung kurz und verständlich erklärt.