„Am 12. kann ich nicht" — bis das in ChurchTools steht, muss man Datum und Uhrzeit der Veranstaltung heraussuchen und als Abwesenheit von Hand eintragen. Das ist umständlich genug, dass es oft gar nicht passiert: Die Absage kommt per Nachricht, irgendwer merkt sie sich, und wer den Plan macht, erfährt sie zuletzt.
CT-Abwesenheiten dreht das um. Die Extension zeigt die anstehenden Veranstaltungen als Liste — je Termin verfügbar oder abwesend, dazu Grund und Kommentar, fertig. Standardmäßig stehen dort nur Termine, an denen die eigenen Dienste überhaupt gebraucht werden. Jeder trägt nur für sich selbst ein, und was eingetragen ist, steht dort, wo die Planung ohnehin hinschaut: in ChurchTools.
Eine Extension, die in ChurchTools installiert wird: keine zweite Anmeldung, keine Liste außerhalb, keine gesammelten Rückmeldungen.
Gute Ergänzung: CT-Planner. Er baut aus denselben Terminen eine ganze Quartalsplanung und übergeht dabei automatisch, wer sich hier abgemeldet hat.
Die Termine des gewählten Zeitraums als Liste — je Termin Verfügbar oder Abwesend, dazu Grund und Kommentar. Der Chip hinter dem Namen sagt, woran eine Zeile ist: offen (noch nicht übernommen, zusätzlich am linken Rand markiert), in CT, ganztägig oder nur in CT änderbar. Der Zähler am grünen Übernehmen zeigt, wie viel gerade aussteht; Zurücksetzen daneben verwirft es wieder.
Geschrieben wird erst nach Bestätigung: Der Dialog listet vorher alle geplanten Änderungen — neu, geändert, entfernt.
Alle Personen, Termine und Kommentare in den Bildern sind erfunden.
- Die Veranstaltungen eines Zeitraums als Liste anzeigen, wahlweise nach Kalender gefiltert.
- Standardmäßig nur Termine zeigen, die einen der eigenen Dienste anfragen: wird für einen Termin nur Musik gebraucht, die Person ist aber im Bistro, muss sie sich dort nicht abmelden. Der Filter lässt sich abschalten.
- Je Termin verfügbar oder abwesend wählen, dazu Grund und Kommentar. Was in ChurchTools schon steht, ist vorbelegt.
- Die Auswahl mit einem Klick als Abwesenheiten übernehmen — mit Datum und Uhrzeit der Veranstaltung, nicht ganztägig.
- Vor dem Schreiben alle geplanten Änderungen (neu / geändert / entfernt) zur Bestätigung zeigen; Zurücksetzen verwirft sie stattdessen.
- An jeder Zeile anzeigen, woran sie ist: noch offen, in ChurchTools eingetragen, ganztägig oder nur dort änderbar.
Gelesen: die angemeldete Person, die Veranstaltungen des gewählten Zeitraums samt der Dienste, die sie anfragen, die Gruppen der Person (daraus ergibt sich, für welche Dienste sie überhaupt in Frage kommt), die Abwesenheitsgründe aus den ChurchTools-Stammdaten und die bereits eingetragenen Abwesenheiten.
Geschrieben: ausschließlich Abwesenheiten der angemeldeten Person — angelegt, geändert oder entfernt, und erst nach Bestätigung des Dialogs. Wer auf verfügbar wechselt, dessen Eintrag wird gelöscht; verfügbar heißt in ChurchTools schlicht „kein Eintrag".
Sonst nichts: keine Termine, keine Dienstbesetzungen, keine Daten anderer Personen. Die Extension hat keine eigene Datenablage — auch die noch nicht übernommenen Eingaben liegen nur im geöffneten Tab.
Deckt eine bestehende Abwesenheit mehr als einen Tag ab (z. B. ein zweiwöchiger Urlaub), ist die Zeile gesperrt und zeigt den betroffenen Zeitraum an. Sie hier zu ändern oder zu löschen würde den gesamten Urlaub treffen — das gehört in die ChurchTools-Oberfläche.
Steht an einem Terminstag eine ganztägige Abwesenheit, bleiben die Zeilen dieses Tages trotzdem bedienbar. Wird einer der Termine auf verfügbar gestellt, löst die Extension den ganztägigen Eintrag auf und legt für die weiterhin abwesenden Termine desselben Tages je einen Eintrag mit Uhrzeit an — anders ließe sich „hier doch da, dort nicht" in ChurchTools nicht abbilden. Termine, die gerade nicht geladen sind (anderer Zeitraum, abgewählter Kalender), deckte der ganztägige Eintrag mit ab; für sie entfällt die Abmeldung dann ersatzlos. Der Bestätigungsdialog sagt das ausdrücklich.
Trägt eine Abwesenheit eine Uhrzeit (in ChurchTools üblich für „vormittags" / „abends"), zählt sie nur für Termine in diesem Zeitfenster: Wer abends verhindert ist, gilt beim Vormittagsgottesdienst weiterhin als verfügbar.
- Unter Releases das aktuelle ZIP
(
CT-Abwesenheiten-v<version>-<hash>.zip) herunterladen. - In ChurchTools als Administrator zu Einstellungen → Erweiterungen gehen.
- Das ZIP hochladen. Die Extension wird unter ihrem festen Key
CTAbereitgestellt. - Anschließend ist die Seite über die ChurchTools-Navigation erreichbar.
Hinweis: Der Upload-Key (
CTA) ist Teil der Extension-Identität. Jede ChurchTools-Instanz muss die Extension unter genau diesem Key hochladen.
Neue Version herunterladen und unter demselben Key erneut hochladen. Der eingebettete Datei-Hash sorgt dafür, dass der Browser die neue Version lädt statt der alten aus dem Cache.
- Angular 18 (Standalone-Components), esbuild-
application-Builder → statisches Bundle mit absoluten Asset-Pfaden (/ccm/<key>/…) und Datei-Hashing. ChurchTools bindet die index.html in die CT-App am Root ein; relative Pfade würden gegen die Domain-Wurzel auflösen (→ HTTP 500).scripts/build.jssetzt daher--base-href=/ccm/<key>/, schreibt die index.html-Verweise absolut und injiziertwindow.__ctExtBasefür Laufzeit-Assets (LogoService). - Single-View, kein Client-Router. Die Extension ist eine einzige Seite; ein
Router (Hash →
#/, Path → echte Root-Navigation) ist im Einbettungskontext ungeeignet.app.componentrendert die Seite direkt. - Gekapseltes CSS. Kein globaler Reset / kein Tailwind-Preflight in
styles.css(würde CTs Navbar zerlegen); globale Regeln sind unterapp-rootgescopt. Design-Tokens und Komponentenmuster sind identisch zu CT-Planner/CT-Organigramm. abwesenheiten/absence-plan.ts— reine, I/O-freie Logik: Abgleich von Abwesenheiten mit Terminen, Aufbau der Zeilen, Ableitung des Schreibplans (anlegen / aktualisieren / entfernen) und der API-Payloads. Hier steckt die Fachlichkeit, entsprechend vollständig per Vitest getestet.services/churchtools.service.ts— ausschließlich HTTP + Normalisierung der CT-Antworten. Bewusst ohne Cache: die Seite zeigt den Bearbeitungsstand der eigenen Abwesenheiten, ein Cache würde nach dem Übernehmen veraltete Zeilen liefern.services/json.ts— schmale Narrowing-Helfer (asRecord/asArray/num/…), damit die untypisierten Client-Antworten ohneanyauswertbar sind.no-explicit-anyist in diesem Projekt auferrorgestellt (anders als im CT-Planner, der die Altlast des portierten Backends trägt).pages/abwesenheiten/— hält nur UI-Zustand. Anzeigewerte werden einmal inrefreshView()vorberechnet statt in Template-Methoden, die sonst bei jedem Change-Detection-Zyklus über alle Termine liefen.
| Zweck | Aufruf |
|---|---|
| Angemeldete Person | GET /whoami |
| Veranstaltungen | GET /events?from=&to= |
| Abwesenheitsgründe und Dienste | GET /event/masterdata, Fallback GET /masterdata/event |
| Gruppen der Person | GET /persons/{id}/groups |
| Bestehende Abwesenheiten | GET /persons/{id}/absences?from=&to= |
| Anlegen / Ändern / Löschen | POST / PUT / DELETE auf /persons/{id}/absences[/{absenceId}] |
Fünf Details, die beim Nachbauen leicht zu übersehen sind:
-
Abwesenheitsgründe stehen in den Stammdaten des Veranstaltungsmoduls, nicht bei den Personen:
GET /event/masterdata→data.absenceReasons. Ein Endpunkt/absence/reasonsoder ein modulloses/masterdataexistiert nicht — beides quittiert die API mit 404, und die Seite stünde ohne Gründe da. -
toist exklusiv („up to but not including") — sowohl bei/eventsals auch bei/absences.exclusiveEnd()rechnet deshalb einen Tag auf, sonst fehlte der letzte Tag des gewählten Zeitraums. -
Beim Schreiben gilt: ENTWEDER Tage ODER Uhrzeit — niemals beides. Die OpenAPI der Instanz sagt zu
POST /persons/{personId}/absenceswörtlich:Absences can be all-day or with a specific time. Either
startDate,endDateorstartTime,endTimeMUST be present. If*Timeis given, the*Datevalue will be ignored.Werden beide Paare gesendet, antwortet die API mit HTTP 400 — auch wenn jedes Feld für sich gültig ist.
absenceWireBody()wählt deshalb genau ein Paar aus;startTime/endTimesind Zulu-Zeitstempel (2022-10-19T12:00:00Z,format: date-time),startDate/endDatereine Kalendertage. Die Uhrzeit ist dabei kein Detail: ohne sie steht die Abwesenheit ganztägig und deckt jede weitere Veranstaltung desselben Tages mit ab — deren Zeile wird in der Tabelle dann als „gehört zu einem größeren Zeitraum" gesperrt. Nur wenn die Veranstaltung selbst ganztägig ist, ist die ganztägige Abwesenheit die richtige Antwort. Lehnt eine (ältere) Instanz die Zeitangaben doch ab (400/422), schreibtChurchToolsService.writeAbsenceeinmalig ganztägig nach und meldet das überAbsenceWriteResult.allDaysamt Wortlaut der Ablehnung nach oben — die Seite zeigt es an, statt still das Falsche zu tun. -
Die API-Beschreibung der Instanz steht unter
/system/runtime/swagger/openapi.json(nicht unter/api/swagger.json— das liefert 404) und ist ohne Anmeldung lesbar; die Swagger-Oberfläche dazu unter/api. Bei Zweifeln am Format ist das die Quelle, nicht das Ausprobieren. -
Welche Dienste ein Termin anfragt, steht in
eventServices— aber nur mitinclude=eventServices. Ohne diesen Query-Parameter lässt/eventsdas Feld weg, jeder Termin käme ohneserviceIdsan und der Standardfilter „Nur meine Dienste" verschwände wortlos. Der Filter schneidet dieserviceIds der Termine (auch unbesetzte Anfragen zählen) mit denen, die sich aus den Gruppen der Person ergeben; fehlt eine der beiden Seiten, bleibt er aus und die Seite schreibt in die Filterleiste, woran es liegt. -
/eventsbewusst überget, nichtgetAllPages. Der Endpunkt liefert keinemeta.pagination, an der sich die Seitenschleife des Clients orientieren könnte —getAllPagesliefe beim ersten Ergebnis in einen TypeError.
docs/demo-*.png sind nicht abfotografierte Echtdaten, sondern aus erfundenen
Daten neu gerendert. So können keine Gemeindedaten ins öffentliche Repo geraten — ein
echter Screenshot zeigte sonst die Termine, Dienste und Abwesenheitsgründe einer realen
Person samt ihrer Kommentare, und eine nachträgliche Retusche müsste jede einzelne
Textstelle treffen.
python3 docs/generate-demo.py # braucht Chrome im PATH und `pip install pillow`Das Skript baut eine statische HTML-Seite mit dem echten Komponenten-CSS und der Inter-Schrift des Projekts und fotografiert sie mit Chrome headless. Nach UI-Änderungen also einfach erneut ausführen — das Layout zieht automatisch mit. Die Demo-Daten stehen oben im Skript; eine Prüfung bricht ab, wenn sie sich widersprechen (abwesend ohne Grund, „entfernt" ohne bestehenden Eintrag, gesperrte Zeile mit offener Änderung …). Beide Bilder speisen sich aus denselben Zeilen: Was der Dialog als neu, geändert oder entfernt zeigt, ist genau das, was in der Liste als offen markiert ist.
Für Formatfragen ist zuerst die OpenAPI der Instanz zu lesen
(https://<instanz>/system/runtime/swagger/openapi.json, ohne Anmeldung abrufbar).
Bleibt danach offen, was eine Instanz konkret annimmt oder speichert, protokolliert die
Extension auf Wunsch jeden Aufruf aus dem angemeldeten Browser heraus.
Einschalten (einer der beiden Wege):
?diag=1an die URL hängen:https://<instanz>/ccm/CTA/?diag=1- oder in der Browser-Konsole
localStorage.setItem('cta-diag', '1')und neu laden — das wirkt auch bei eingebetteter Auslieferung, wenn die Adresszeile nicht die der Extension ist.
Protokolliert werden (Konsole, Präfix [CTA-DIAG]):
| Eintrag | Wofür |
|---|---|
| OpenAPI → Pfade mit „absence" + aufgelöste Schemata | die maßgebliche Feld- und Formatdefinition der Instanz |
GET /persons/{id}/absences roh |
wie bestehende Einträge tatsächlich aussehen — am besten vorher eine Abwesenheit mit Uhrzeit von Hand in CT anlegen |
GET /events erster Eintrag roh |
das Zeitformat, das die Instanz ausliefert |
jeder POST/PUT mit Body, Antwort bzw. Fehlerstatus |
was gesendet wurde und was die Instanz daraus gemacht hat |
Ohne einen der Schalter wird nichts geloggt. Die Ausgabe enthält die Abwesenheiten der angemeldeten Person (inkl. Kommentar) — vor dem Weitergeben also kurz durchsehen.
npm install
cp src/environments/environment.dev.example.ts src/environments/environment.dev.ts
# environment.dev.ts: URL der Test-Instanz + Dev-Login eintragen
npm start # ng serve (development, gegen die Test-Instanz)In der ChurchTools-Test-Instanz muss CORS freigegeben sein (System → Integrationen → API), damit der Dev-Server zugreifen darf.
Achtung: Die Extension schreibt Abwesenheiten für die angemeldete Person. Der Dev-Login bestimmt also, wessen Abwesenheiten die Test-Instanz bekommt — bitte keinen echten Mitarbeiter-Account verwenden.
npm run check # eslint + Vitest (absence-plan) + Produktions-Build
npm run build # → dist/ct-abwesenheiten/
npm run package # Build + ZIP nach releases/CT-Abwesenheiten-v<version>-<hash>.zipDas ZIP wird in ChurchTools unter Einstellungen → Erweiterungen hochgeladen.
Der Upload-Key wird zur Build-Zeit fest in die Asset-Pfade eingebacken
(package.json → ctExtensionKey, Standard CTA; per CT_KEY überschreibbar).
Jede CT-Instanz muss die Extension unter genau diesem Key hochladen. Die
Instanz-Adresse bleibt dynamisch (Base-URL zur Laufzeit aus
window.settings.base_url bzw. window.location.origin).
Beim Veröffentlichen eines GitHub-Releases führt .github/workflows/release.yml
Lint und Tests aus, baut und paketiert die Extension und hängt das ZIP als Asset an
das Release. Der ZIP-Name nutzt die version aus package.json — vor dem Release
ggf. hochziehen.
MIT © 2026 Janik Meier

