Avocoado Logo

Arbeitsplatz-Übersicht mit der Personio-API

author image

Sarah Dreher, 27. Januar 2025

Lesezeit: 5 Minuten

In der IT-Branche wird, genauso wie bei uns, auf flexible Arbeitszeiten und Arbeitsplätze gesetzt, damit jeder in seinem optimalen Arbeitsumfeld tätig sein kann. Trotz dieser Flexibilität legen wir großen Wert auf die Zusammenarbeit und persönliche Kommunikation im Büro, um Absprachen effizienter zu gestalten und den natürlichen Austausch zu fördern. Um HomeOffice- und Abwesenheitszeiten effizient zu verwalten, nutzen wir bei avocado software engineering das Tool Personio (www.personio.de ), eine "intelligente HR-Plattform", welche uns in diversen HR-Bereichen unterstützt.

Zwar ermöglicht Personio die Beantragung und Übersicht von Abwesenheiten, doch die Monatsansicht aller Mitarbeitenden ist recht unübersichtlich. Es wird nämlich der gesamte Monat für alle Mitarbeitenden angezeigt. Deshalb sollte nun eine Tagesansicht der Abwesenheitsstatus durch Anbindung an die Personio-API in unserer internen Web-Applikation, dem "AvCore", entstehen.

Kurze Erklärung des Tools

/uploads/personio_API_dashboard_overview_e1a68f04ab.png Unser Hauptziel - das Erfassen der Anwesenheiten auf einen Blick - kann man direkt erkennen: Das Dashboard ist sehr simpel gestaltet und zeigt in der Standardansicht ausschließlich die Anwesenheiten des heutigen Tages an:

  1. Normales Bild = Im Büro zu finden
  2. Leicht transparentes Bild und grünes Latop-Icon = Im HomeOffice
  3. Leicht transparentes Bild und rotes Kreuz-Icon = Abwesenheit (Krankheit, Urlaub oder sonstiges)
  4. Zusätzliches gelbes Uhr-Icon = Abwesenheit nur den halben Tag

Die Profile sind nach Anwesenheitsstatus sortiert, so dass man direkt einen guten Überblick erhält. Aber auch das Filtern über die 3 Buttons "Anwesend", "Zu Hause" und "Abwesend" ist möglich.

Man kann über das Tool auch die folgenden Tage betrachten, indem man mit den Pfeiltasten das Datum verändert. Dabei werden Wochenenden und Feiertage automatisch übersprungen. Über den Textlink "Heute" hat man außerdem die Möglichkeit, zum heutigen Tag zurückzuspringen. Vergangene Tage sind nicht einsehbar.

/uploads/personio_API_tooltips_e50e8ed2aa.png

Da Mitarbeitende die Möglichkeit haben, nur Halbtages-Abwesenheiten zu beziehen, muss auch dies in der UI dargestellt werden. Eine kleines Uhr-Icon und ein Tooltip zeigen an, in welchem Zeitraum der Mitarbeiter abwesend ist.

Technische Ausführung

Umgang mit der Personio API

Um die aktuellen Daten über die in Personio erfassten An- und Abwesenheiten zu erhalten, kommuniziert die Anwendung mit Personios öffentlicher API. Für die Verwendung dieser ist eine Authentifizierung mit Hilfe des API-Token aus dem Personio Account notwendig. In den Personio Einstellungen kann auch angegeben werden, welche Daten abrufbar sein sollen: In unserem Fall sind es nur die Mitarbeiternamen, Profilbilder und Abwesenheiten. Aus unserem Java-Backend erfolgt der Aufruf an die Personio-API zum Anreichern der Daten folgendermaßen:

public String getAbsences(LocalDate startDate, LocalDate endDate) {
        String personioToken = checkUpdateToken();
        WebClient client = WebClient.create(personioUrl);
        WebClient.ResponseSpec responseSpec = client.get()
                .uri(uriBuilder -> uriBuilder
                        .path("/time-offs")
                        .queryParam("start_date", startDate)
                        .queryParam("end_date", endDate)
                        .queryParam("limit", "200")
                        .queryParam("offset", "0")
                        .build())
                .header("Authorization", "Bearer " + personioToken)
                .retrieve();

        return responseSpec.bodyToMono(String.class).block();
    }

Wir verwenden den Java WebClient, welcher eine Schnittstelle für die Ausführung von Web-requests darstellt. Zuerst erstellen wir eine WebClient-Instanz mit einer Basis-URI, die der Personio-API. Nun kann man eine Anfrage über diese Instanz abschicken. In diesem Fall ist es eine GET-Request, wobei wir noch weitere Parameter (start_date, end_date, limit, offset) auf dem spezifizierten Pfad (/time-offs) mitgeben. Im Header wird zusätzlich das vorhin erwähnte Authentifizierungs-Token mitgegeben.

Die Antwort, ein verschachteltes JSON, wird dann zu einem String umgewandelt, damit der Inhalt im nächsten Schritt besser verarbeitet werden kann. Mithilfe eines ObjectMapper und JsonNodes können die relevanten JSON-Knoten dann in ein passendes Model geschrieben werden. Hierbei werden beispielweise nicht-genehmigte Abwesenheiten nicht aufgenommen.

public class Absence {

    public String status; //status der Abwesenheit: approved / requested / denied
    public String timeOffType; //offsite_work / paid_vacation / ...
    public int userId;
    public Boolean isActive;
    public String firstName;
    public String lastName;
    public String profilePic;
    public Boolean halfDayStart;
    public Boolean halfDayEnd;
    (...)
}

Diese Abwesenheits-Abfrage liefert alleinig die Abwesenheiten für das angegebene Datum.

Um jedoch alle Personen– auch diejenigen, die im Büro sind – anzuzeigen, müssen wir zusätzlich die Daten aller Mitarbeitenden abrufen. Im Backend werden diese beiden Listen dann zusammengeführt, sodass ein Ergebnis entsteht, dass alle Mitarbeitenden zusammen mit ihren Abwesenheitsstatus beinhaltet.

/uploads/personio_API_get_Absences_34eb451a11.png

Auswahl des Datums

Bei der Auswahl des Datums über die Pfeiltasten sollen Wochenenden und Feiertage übersprungen werden. Da die Daten der Feiertage für das Land Baden-Würtemberg auch über eine API (www.api-feiertage.de) geholt werden müssen, wird das nächste gültige Datum im Backend evaluiert. Außerdem muss auch die zeitliche Richtung, in die sich der User bewegt (rechte oder linke Pfeiltaste bei Datumsauswahl) an das Backend geschickt werden, damit bestimmt werden kann in welche Richtung ein Datum übersprungen wird. Nachdem die Pfeiltaste gedrückt wurde, wird eine Anfrage mit dem neuen Datum und derm Richtung an das Backend geschickt. Nun wird das Datum bestimmt, welches für die eigentliche Abfrage an die Personio API verwendet werden soll:

  1. Befindet sich das angefragte Datum zeitlich vor dem heutigen Tag, wird das heutige Datum ausgewählt
  2. Ist das Datum ein Feiertag, so wird der Tag übersprungen (siehe Richtung) und das neue Datum wird nochmals evaluiert
  3. Ist das Datum ein Samstag oder Sonntag, so wird das Wochenende in die angegebene Richtung übersprungen und das neue Datum wiederum nochmals evaluiert
private LocalDate evaluateDate(LocalDate requestedDate, boolean nextDay) {

        (...)

        if(requestedDate.isBefore(LocalDate.now())) {
            return LocalDate.now();
        }

        if (holidays.contains(requestedDate)) {
            return nextDay ? evaluateDate(requestedDate.plusDays(1), nextDay) : evaluateDate(requestedDate.minusDays(1), nextDay);
        } else {
            if (requestedDate.getDayOfWeek() == DayOfWeek.SATURDAY) {
                return nextDay ? evaluateDate(requestedDate.plusDays(2), nextDay) : evaluateDate(requestedDate.minusDays(1), nextDay);
            } else if (requestedDate.getDayOfWeek() == DayOfWeek.SUNDAY) {
                return nextDay ? evaluateDate(requestedDate.plusDays(1), nextDay) : evaluateDate(requestedDate.minusDays(2), nextDay);
            }
        }
        return requestedDate;
    }
}

Wurde das gültige Datum bestimmt, wird die Anfrage der Abwesenheiten an Personio mit diesem Datum gestellt. In der Antwort an das Frontend befinden sich dann die Abwesenheiten und das evaluierte Datum, welches in der UI angezeigt wird.

Caching der Profilbilder

Die Profilbilder der User für die Anzeige können von der Personio API nur einzeln angefragt werden. Dies führt zu langen Ladezeiten, wenn man die Bilder bei jeder Anfrage neu anreichern würde. Stattdessen implementierten wir eine Lösung mithilfe von Caching gefunden, welche die Ladezeiten - vor allem beim Datumswechseln- erheblich verringert.

Beim Initialen Aufruf der Seite werden zuerst die Abwesenheiten des heutigen Tages aus dem Backend geholt. Zurückgegeben wird eine Liste mit Objekten des Models "Absences", welche alle aktiven Mitarbeitenden und deren Abwesenheitsstatus zu diesem Tag beinhält (s.o.). Mithilfe der employeeId wird nun zuerst der localStorage des Users abgesucht. Ist für die employeeID ein Bild im LocalStorage vorhanden, so wird dieses Bild dem User zugeordnet. Das Bild wird außerdem mit der ID in einer profilePicsMap: Map<number, string> gespeichert. Alle restlichen employeeIDs (ohne zugeordnetem Bild) werden nun in einer neuen Abfrage an das Backend gegeben, mit der die Personio- Profilbilder angereichert werden sollen.

Im Backend wird die Abfrage einzeln an die Personio-API weitergeleitet und die Antwort dann jeweils den employeeIds zugeordnet. Die Bildantwort muss aber zuerst in ein base64-Encoding umgewandelt werden, damit das Frontend dies verarbeiten kann. Ist im Personio kein Bild hinterlegt, so ist profilePic = null.

public byte[] getEmployeeProfilePic(int employeeId) throws IOException, InterruptedException {
        String personioToken = checkUpdateToken();

        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(personioUrl + "/employees/" + employeeId + "/profile-picture/200"))
                .header("accept", "image/png")
                .header("Authorization", "Bearer " + personioToken)
                .method("GET", HttpRequest.BodyPublishers.noBody())
                .build();

        HttpResponse<byte[]> response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofByteArray());
        if (response.statusCode() != 404 && response.body() != null) {
            try (FileOutputStream fos = new FileOutputStream("profile-picture.png")) {
                fos.write(response.body());
            }
            return response.body();
        }
        return null;
    }
byte[] pngString = this.personioService.getEmployeeProfilePic(employeeId);

if(pngString != null) {
        String base64Encoded = Base64.getEncoder().encodeToString(pngString);
        absence.setProfilePic(base64Encoded);
} else {
        absence.setProfilePic(null);
}

Im Frontend wird die Antwort der Profilbild-Abfrage verarbeitet: Wenn ein Profilbild erfasst wurde, so wird dieses im LocalStorage gespeichert und es muss somit nicht mehr beim nächsten Aufruf angefragt werden. Ist kein Profilbild hinterlegt, so bekommt profilePic den Wert null. Alle Objekte, die mit ProfilePic-Wert und die ohne einen Wert, werden in der profilePicsMap zwischengespeichert.

Wird nun ein neues Datum ausgewählt, so wird zuerst überprüft ob die profilePicsMap leer ist. Ist sie schon gefüllt, so werden keine Profilbilder mehr angefragt, nur die Abwesenheiten. Im Frontend werden dann die zwischengespeicherten Bilder den Abwesenheiten über die employeeId zugeordnet, welches einen kompletten Aufruf und somit viel Zeit beim Aufrufen der Abwesenheiten weiterer Tage spart.

author image

Sarah Dreher

Praktikantin in der Softwareentwicklung

In diesem Projekt hatte ich die Gelegenheit, ein Feature alleine und von Grund auf zu entwickeln. Dies ermöglichte es mir, die Kenntnisse, die ich in den letzten Monaten erworben hatte, eigenständig anzuwenden und zu erweitern. Ich konnte meine Fähigkeiten sowohl im Front- als auch im Backend-Bereich testen und darüber hinaus wertvolle Erfahrung im Umgang mit einer öffentlichen API sammeln.

Teile den Beitrag mit deinen Freunden!