Files
schakal00783andClaude Opus 5.5 876ef24c75 feat(reports): Kundenreport nach Woche, Monat, Quartal, Jahr oder Zeitraum
Zeitraumwahl im Kundenreport (Standard Gesamt). Stunden, Team, Tasks,
Projektstunden und Exporte beziehen sich auf den Zeitraum; Diagramm je
Tag/Woche/Monat nach Laenge. Zeitraumwahl als PeriodPicker ausgelagert,
auch im Zeitreport genutzt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 14:01:23 +02:00

27 KiB
Raw Permalink Blame History

perinco Pulse

Zeiterfassung für perinco – live unter https://pulse.perinco.de. Quellcode: git.perinco.de/Sascha/perinco-Pulse. Stil, Themes und Aufbau folgen dem perinco CRM (git.perinco.de/Sascha/perinco-CRM).

Teil Technik Wo
Frontend React 19, TypeScript, Tailwind 4, Vite /var/www/pulse/dist
Backend Express 4, mysql2, exceljs, pdfkit /var/www/pulse/server, 127.0.0.1:5010, Dienst perinco-pulse-backend (User pulse)
Datenbank MariaDB 11.8 perinco_pulse (User pulse)
Webserver Apache Vhost pulse.perinco.de, Let's Encrypt /etc/apache2/sites-available/pulse.perinco.de*.conf

Erster Login: Benutzer admin, Passwort aus ADMIN_PASSWORD in server/.env (wird beim ersten Start einmalig angelegt) – danach unter Mein Konto (unten links auf den Namen klicken) ändern.

Rollen und Menü

Rolle Startseite Menü
Mitarbeiter (user) Buchung Buchung, Projekte (nur zugewiesene, ohne Beträge), Kalender
Externe (external) Buchung wie Mitarbeiter – die Rolle trennt nur, wer zum Haus gehört; is_external wird automatisch gesetzt
Vorgesetzter (lead) Buchung Dashboard (nur eigenes Team, rekursiv), Buchung, Projekte, Kalender, Team, Kunden, Reports
Management Dashboard wie Vorgesetzter, sieht alles; Einstellungen nur „SSO-Verwaltung“
Administrator Einstellungen Dashboard, Projekte, Kalender, Team, Kunden, Reports, Einstellungen – keine Buchung, taucht in Auswertungen nicht auf

Die Startseite gilt nach jeder Anmeldung (Passwort und SSO), auch wenn noch eine alte Seite in der Adresse steht; Neuladen bleibt auf der aktuellen Seite. Das Logo oben links führt ebenfalls zur Startseite.

Rechte werden im Menü und im Backend (403) geprüft. Jeder bucht nur auf Projekte, denen er zugewiesen ist.

Sitzungsdauer: Das Token gilt 7 Tage ab der Anmeldung (JWT_EXPIRES_IN in server/.env, Vorgabe im Code ebenfalls 7d). Es verlängert sich nicht bei Aktivität und es gibt keinen Leerlauf-Timer; abgelaufen meldet der Server 401 „Sitzung abgelaufen“, der Browser verwirft das Token und zeigt den Anmeldebildschirm. Das Token liegt im localStorage, ein Neuladen beendet die Sitzung also nicht. Deaktivierte Konten und geänderte Rollen greifen trotzdem sofort – requireAuth lädt den Benutzer bei jeder Anfrage frisch aus der Datenbank. Eine geänderte Frist gilt erst für Tokens, die danach ausgestellt werden.

Fachregeln

  • Zeiteingabe: 1:23 = 1 h 23 min, 1,5 / 1.5 = 1 h 30 min, außerdem 2, :45, 1h 30m.
  • Projektcode automatisch nach Muster (Standard {KUERZEL}-{JAHR}-{NR} → KUKA-2026-001), Anzeigename nach Muster (Standard {KUNDE} - {PROJEKT} → KUKA - PMO). Beides in Einstellungen → Allgemein.
  • Projekttypen:
    • Billable – Preis pro Stunde oder Tag, Volumen und Laufzeit im Budgettopf.
    • Abruf – Abrechnung nach Aufwand: kein Budgettopf, kein Limit, nur ein Stunden- oder Tagessatz am Projekt (pulse_projects.rate). Gebucht wird innerhalb der Projektlaufzeit, der Umsatz sind abrechenbare Stunden × Satz.
    • Non-billable – intern.
    • Festpreis – fixes Budget, virtueller Stundensatz = Festpreis ÷ gebuchte Stunden (nur intern).
  • Budgettöpfe (mind. einer je Projekt, beliebig viele): Bezeichnung, Laufzeit von/bis, Budget € + Satz (→ Volumen in Stunden/Tagen) oder direktes Kontingent.
    • Gebucht wird nur auf einen Topf, dessen Laufzeit das Leistungsdatum enthält; überlappen Töpfe, zuerst der früher endende.
    • Volumen erreicht oder Ablaufdatum überschritten → Warnung und nicht mehr buchbar.
    • Bei Billable-Projekten verbrauchen nur abrechenbare Stunden das Volumen (Reisezeit o. ä. nicht); ist ein Topf leer, ist er für alles gesperrt.
    • Warnung ab 80 % Verbrauch bzw. 14 Tage vor Ablauf (einstellbar) – im Buchungsdialog, in der Buchungsansicht und an der Projektkarte.
    • Wird ein Topf geändert, ordnet Pulse vorhandene Buchungen neu zu.
  • Umsatz erbracht/offen je Topf: Billable = abrechenbare Stunden × Satz; Festpreis = anteilig nach Kontingent (sonst nach Laufzeit), abgelaufen = voll; offen = Rest in nicht abgelaufenen Töpfen.
  • Soll/Auslastung: Wochenstunden (Standard 40, je Benutzer überschreibbar) ÷ 5 × Arbeitstage bis heute.
  • Löschen: Benutzer, Projekte, Kunden mit Buchungen werden nicht gelöscht, sondern deaktiviert/archiviert – Reports bleiben stimmig. SSO-Benutzer werden nur deaktiviert.

Exporte

Serverseitig erzeugt, alle mit Kopf/Fuß aus Einstellungen → Export (Logo, Akzentfarbe, Titel, 3 Fußspalten, Unterschriftenfelder):

  • Stundennachweis je Monat (PDF, Excel mit Summenformeln, CSV mit UTF-8-BOM/Semikolon): eigener Nachweis, eines Teammitglieds oder aller; filterbar nach Kunde/Projekt; optional mit Sätzen und Beträgen für die Rechnung.
  • Zeitreport nach Kunden/Projekten/Tasks/Mitarbeitern oder als Detailliste.

Im PDF darf die Beschriftung der Summenzeile in die leeren Spalten dahinter laufen (merge in drawRow, server/src/export/render.js). Ohne das stand „Summe" in der schmalen Spalte Tag und brach dort in drei Zeilen um, während rechts daneben die Zahlen standen. Gefüllte Spalten werden nicht einverleibt, die Zahlen bleiben also, wo sie sind.

Listenansichten

src/components/booking/EntryList.tsx zeigt einzelne Buchungen als sortierbare Tabelle: Datum, optional Mitarbeiter, Kunde, Projekt (mit Code), Task (mit Notiz) sowie die Zeit getrennt nach Billable und Non-billable, unten die Summen. Dieselbe Komponente wird an drei Stellen benutzt:

  • Buchung: Umschalter Kalender / Liste neben Woche / Monat / Jahr; die Liste zeigt den jeweils gewählten Zeitraum. Ein Klick auf eine Zeile öffnet die Buchung zum Bearbeiten (nicht in der fremden Teamansicht). Die Wahl merkt sich der Browser.
  • Reports → Zeitreport → Mitarbeiter: Klick auf eine Zeile öffnet die Buchungen dieses Mitarbeiters im Reportzeitraum; ein gesetzter Kundenfilter wird übernommen (GET /api/entries?...&customer_id=).
  • Kunden: Umschalter Karten / Liste mit Ort, Projektanzahl, Stunden, Umsatz erbracht/offen und Team.

src/components/projects/ProjectTable.tsx ist die Listenansicht der Projekte (Umschalter Karten / Liste): sortierbar nach Projekt, Kunde, Typ, gebuchten Stunden, Budget, Verbrauch und offenem Betrag. Die Kachelansicht läuft als ein durchgehendes Raster, nach Kunde und Projekt sortiert, mit dem Kundennamen auf der Kachel. Eine Überschrift je Kunde gab es früher, sie zwang aber jede Gruppe in eine neue Zeile - bei einem Projekt je Kunde blieb der Rest der Zeile leer. Ein Klick auf eine Zeile öffnet dieselbe Detailansicht wie eine Kachel, hier unterhalb der Tabelle. Beträge sieht nur, wer mindestens Vorgesetzter ist.

Kundenreport: Zeitraum

Reports → Kundenreport hat neben der Kundenauswahl eine Zeitraumwahl: Gesamt (Standard, die ganze Kundenbeziehung wie bisher), Woche, Monat, Quartal, Jahr mit Vor/Zurück-Pfeilen oder ein freier Zeitraum (von–bis). Die Wahl merkt sich der Browser (pulse_customer_report_period) und bleibt beim Kundenwechsel stehen, damit sich Kunden im selben Zeitraum vergleichen lassen.

  • Auf den Zeitraum beziehen sich: Gesamtstunden (billable/non-billable), die Stunden je Projekt, die Tabs Team und Tasks sowie die Exporte PDF – Projekte und CSV – alle Buchungen. Der Stundennachweis nimmt den Monat, in dem der Zeitraum endet.
  • Die zweite Kachel zeigt mit Zeitraum den Umsatz im Zeitraum (Buchungen inkl. Festpreis-Anteil), darunter Erbracht gesamt. Umsatz offen, Budget gesamt und die Budgetbalken bleiben Stand heute – sie hängen am Projekt, nicht am Zeitraum.
  • Das Diagramm wählt die Einheit nach der Länge: bis 31 Tage je Tag, bis 184 Tage je Woche (KW), darüber je Monat. Ohne Zeitraum wie bisher Monate mit Auswahl 6/12/24.
  • API: GET /api/reports/customer/:id?from=YYYY-MM-DD&to=YYYY-MM-DD – nur beide Werte zusammen schränken ein, from > to gibt 400. Die Antwort enthält range, series_unit (day/week/month) und series mit key/start.
  • Die Zeitraumwahl ist src/components/reports/PeriodPicker.tsx und wird auch vom Zeitreport benutzt.

Auslastung

Die Auslastung vergleicht gebuchte Stunden bis heute mit der Kapazität bis heute (minutes_until_today in Report und Dashboard). Die Kapazität endete schon immer am heutigen Tag; wurden Zeiten im Voraus gebucht – etwa eine ganze Woche Urlaub am Montag – standen sonst 40 Stunden gegen 8 Stunden Kapazität und die Anzeige sprang auf 500 %. Die Spalte Stunden zeigt weiter alles, was im Zeitraum gebucht ist.

Sätze: Topf, Projekt, Task

Der Satz kommt in dieser Reihenfolge: Task → Budgettopf → Projekt (letzteres nur bei Abruf). Ein Satz am Task ist freiwillig und deckt Fälle ab, in denen dieselbe Leistung je Task anders vergütet wird – typisch „vor Ort" neben „remote". Bleibt er leer, ändert sich nichts gegenüber vorher. Die Einheit (Stunde oder Tag) steht am Projekt und gilt auch für den Task-Satz.

Der Satz eines Tasks lässt sich direkt in der Taskzeile der Projektansicht ändern (leeres Feld = Satz des Topfs). Das Budget bleibt in Stunden – gemischte Sätze ändern also nichts daran, wann ein Topf voll ist. Der Umsatz eines Topfes wird dafür je Task-Satz aufsummiert (loadProjects gruppiert den Verbrauch nach Task-Satz und gibt das Ergebnis an potStats). Mitarbeiter sehen keine Sätze, auch nicht die am Task.

Übernahme aus Harvest

POST /api/imports/entries (nur Administrator) schreibt Buchungen für beliebige Personen – POST /api/entries bucht bewusst nur auf den angemeldeten Benutzer. Der Import läuft durch dieselben Regeln: Projekt aktiv, Person zugewiesen, Topf passend zum Leistungsdatum, höchstens 24 Stunden am Tag. Mit dry_run: true schreibt er nichts und meldet nur, was passieren würde.

Jede übernommene Buchung trägt ihre harvest_id (pulse_time_entries.harvest_id, unique). Ein zweiter Lauf legt deshalb nichts doppelt an, sondern meldet „schon vorhanden". GET /api/imports/entries listet alles Übernommene, DELETE /api/imports/entries entfernt es wieder – handgetippte Zeiten bleiben unberührt, weil sie keine harvest_id haben.

Profilbilder

Jeder kann sein eigenes Bild hochladen (Mein Konto), Vorgesetzte und Administratoren auch für die Benutzer, die sie verwalten (Team → Bearbeiten). Der Browser verkleinert das Bild vor dem Hochladen auf ein Quadrat von 256 px und schneidet mittig zu (src/lib/imageCompress.ts); aus einem 4-MB-Handyfoto werden so wenige Kilobyte.

Gespeichert wird es als Data-URL in pulse_users.avatar (LONGTEXT) – kein Upload-Verzeichnis, das gesichert und beim Deployment am Leben gehalten werden müsste. Der Server prüft in server/src/lib/avatar.js: nur PNG, JPEG, GIF, WebP und AVIF als Data-URL, höchstens 2 MB. SVG ist bewusst nicht erlaubt – es kann Skripte tragen. Externe http(s)-Adressen ebenso wenig: sonst lädt jeder Seitenaufruf ein Bild von einem fremden Server und verrät, wer wann mit Pulse arbeitet.

Angezeigt wird es überall dort, wo bisher die Initialen standen: Seitenleiste, Kopfzeile, Team, Dashboard, Reports, Projekt- und Kundenansicht. Ohne Bild bleiben die Initialen.

Abwesenheiten aus HRworks

Urlaub, Krankheit, Elternzeit und Feiertage stehen in HRworks. Pulse holt sie zweimal täglich und bucht sie auf das nicht abrechenbare Projekt PERINCO-2024-001 („Abwesenheiten"). Geschrieben wird nur in Pulse – nach HRworks geht nichts zurück.

Einrichtung (Einstellungen → Abwesenheiten (HRworks), nur Administrator): Schlüsselpaar in HRworks unter Grundlagen → Integrationen → HRworks-API → Schlüsselpaare erzeugen und hier eintragen. Der geheime Teil wird wie das SSO-Secret AES-verschlüsselt gespeichert und nie wieder ausgeliefert. „Verbindung testen" prüft die Zugangsdaten, „Probelauf" zeigt ohne zu schreiben, welche Buchungen entstehen würden.

Wie die Stunden entstehen: GET /persons/available-working-hours?interval=days liefert je Person und Tag regularWorkingHours (Soll) und workingHours (nach Abzug der Abwesenheit). Die Differenz sind die Abwesenheitsstunden – damit stimmen Teilzeit und halbe Tage ohne eigenen Kalender. relatedEvents nennt den Auslöser; Vorrang hat der Feiertag, dann Krankheit, dann Abwesenheit. Halbe Tage werden auf einen festen Wert gesetzt (Voreinstellung 4 h).

Regel Umsetzung
Übernommen werden Abwesenheiten mit Status validatedOk; Krankmeldungen auch mit offener Bescheinigung (reported, certificatePending, electronicCertificate*)
Zeitraum nur bis heute – genehmigter Urlaub in der Zukunft wächst täglich nach
Fenster die letzten 60 Tage (einstellbar); dort werden Änderungen und Stornos nachgezogen
Zuordnung HRworks-Benutzername → Pulse-Benutzer über die Mailadresse, sonst über den Namen; Unzuordenbares wird gemeldet, nie stillschweigend verworfen
Mitglieder jeder aktive Mitarbeiter wird dem Projekt zugewiesen, auch neue
Arten → Tasks type_map in den Einstellungen, z. B. absence:1 → Urlaub, sick:KEK → Kind krank

Jede Buchung trägt pulse_time_entries.hrworks_ref = <Abwesenheitsnummer>@<Tag> (unique). Ein zweiter Lauf ändert deshalb nichts; was in HRworks storniert oder verschoben wurde, verschwindet im nächsten Lauf. DELETE /api/hrworks/entries entfernt genau diese Buchungen.

Zweimal täglich läuft server/src/jobs/hrworks-sync.js über den systemd-Timer perinco-pulse-hrworks.timer (5:30 und 12:00 Uhr Berliner Zeit, Persistent=true holt verpasste Läufe nach) – erst die Buchungen, dann der Kalender (siehe unten). Ausgang im Journal: journalctl -u perinco-pulse-hrworks. Die Unit-Dateien liegen in deploy/; sie werden nach /etc/systemd/system/ kopiert und einmal mit systemctl enable --now perinco-pulse-hrworks.timer eingeschaltet.

Nicht abrechenbare Projekte brauchen keinen Budgettopf mehr (NEEDS_POT in server/src/routes/projects.js). Ein Topf trägt Volumen, Laufzeit und Satz – das hat nur bei Billable und Festpreis Sinn. Am Abwesenheitsprojekt wäre er nur ein Ablaufdatum, das irgendwann Buchungen blockiert; es hat deshalb keinen.

Kalender

Menüpunkt Kalender für alle Rollen: genehmigte Urlaube aller Mitarbeiter – auch die der Zukunft – und Feiertage. Ansichten Monat (Standard, jedes Öffnen) und Zeitleiste (eine Zeile je Person, Überschneidungen auf einen Blick); Filter nach Team (Vorgesetzter samt allen darunter) und Name. Klick auf einen Tag zeigt rechts, wer weg ist; darunter die Urlaube des Monats als Zeiträume.

Die Buchungen entstehen weiter erst am jeweiligen Tag. Der Kalender hat deshalb eine eigene Tabelle pulse_calendar_days (ein Eintrag je Person und Tag: vacation oder holiday, halber Tag), gefüllt von server/src/lib/calendarSync.js im selben Timer-Lauf. Fenster: window_days zurück bis calendar_days_ahead (400) Tage voraus; jeder Lauf ersetzt das Fenster komplett, ältere Tage bleiben als Rückblick. Einmalig weiter zurück: node src/jobs/hrworks-sync.js --calendar-from=2026-01-01. „Jetzt übernehmen“ in den Einstellungen füllt den Kalender gleich mit.

Regel Umsetzung
Status nur validatedOk – beantragte Urlaube erscheinen nicht
Arten nur calendar_types (Voreinstellung Urlaub absence:1 und Bildungsurlaub absence:10), Haken Kalender in Zuordnung der Arten
Krankheit wird für den Kalender gar nicht abgefragt; ein Urlaubstag bleibt stehen, auch wenn eine Krankmeldung daran hängt – sonst verriete die Lücke die Krankheit
Feiertage aus HRworks je Person (Standort), mit Namen, sofern HRworks einen liefert
API GET /api/calendar?from=&to= (jeder Angemeldete, höchstens 400 Tage): Name, Farbe, Vorgesetzter – keine Mails, keine Profilbilder

KI-Assistent

Einrichtung (nur Administrator, Einstellungen → KI-Assistent): OpenAI-Schlüssel eintragen (AES-verschlüsselt gespeichert, nie wieder ausgeliefert), Modell wählen, einschalten. Der Knopf „Fragen" im Kopf erscheint dann für Management und Administration.

Modell wird aus einer Liste gewählt, nicht mehr eingetippt. Die Liste kommt live von OpenAI (POST /api/settings/ai/models → GET https://api.openai.com/v1/models), also genau die Modelle, die der Schlüssel darf – neueste zuerst, mit Veröffentlichungsdatum. Embedding-, Bild-, Audio-, Realtime-, Such- und Moderationsmodelle filtert listModels in server/src/lib/ai.js heraus; sie können keine Textantwort mit Werkzeugen. Ist ein Schlüssel hinterlegt, lädt die Seite die Liste beim Öffnen; Liste laden holt sie neu – mit einem gerade eingetippten, noch nicht gespeicherten Schlüssel, falls vorhanden (deshalb POST: der Schlüssel gehört nicht in eine URL). Ein eingestelltes Modell, das OpenAI nicht mehr listet, bleibt in der Auswahl stehen, damit Speichern es nicht stillschweigend ersetzt. Voreingestellt ist gpt-5.6-luna; Verbindung testen prüft Schlüssel und Modell mit einer echten Anfrage.

SSO / Active Directory

Einrichtung (nur Administrator, Einstellungen → SSO / Entra ID): Microsoft Entra ID per OpenID Connect (Authorization Code, Client Secret AES-verschlüsselt in der DB). App-Registrierung mit Redirect-URI https://pulse.perinco.de/api/auth/sso/callback, delegiert openid profile email, Anwendung User.Read.All mit Administratorzustimmung.

SSO-Verwaltung (Management und Administrator, Einstellungen → SSO-Verwaltung):

  • Links alle zulässigen AD-Konten, rechts die Rollen Mitarbeiter / Vorgesetzter / Management. Konto auf eine Rolle ziehen = nach Pulse übernehmen bzw. Rolle ändern. Alternativ Mehrfachauswahl + Knopf (Handy, Tastatur). Optional gleich einen Vorgesetzten setzen.
  • Zulässig sind nur Konten mit Mail vorname.nachname@perinco.de oder vorname.nachname@icon-experts.de (Bindestriche im Namen erlaubt). Ignoriert werden Adressen mit weiteren Teilen (adm.sascha.wolf@…), Funktionsadressen (info@…), deaktivierte Konten und Konten ohne Lizenz (freigegebene Postfächer) sowie Gäste. Regel: server/src/lib/sso.js → isEligible, Tests: cd server && npm test.
  • Existiert schon ein lokales Konto mit derselben Mail, wird es verknüpft statt doppelt angelegt.
  • Anmelden per Microsoft kann sich nur, wer übernommen wurde. Rollen kommen ausschließlich aus Pulse, AD-Gruppen spielen keine Rolle. Administratoren können hier nicht geändert und nicht vergeben werden.
  • Abgleich: aktualisiert Name, Mail und Jobtitel der übernommenen Konten; wer im AD gelöscht, deaktiviert oder nicht mehr zulässig ist, wird in Pulse deaktiviert. Legt nie selbst Konten an. Die AD-Liste wird 5 Minuten zwischengespeichert („Neu laden“ holt sofort).
  • Externe Benutzer bleiben lokale Konten.

Themes

Acht Farbwelten, jede in hell und dunkel, gespeichert am Benutzer (pulse_users.theme, theme_mode) und vor dem ersten Bild gesetzt (Inline-Skript in index.html). Sieben kommen 1:1 aus dem perinco CRM. Das achte, Mobile, gibt es nur in Pulse: perinco-Farben, aber fürs Handy rund 16 % dichter (kleineres --spacing, kleinere Überschriften, niedrigere Kopfleiste), dunkel mit echtem Schwarz für OLED. Vier Themes bringen Bewegung mit:

Theme Bewegung Wo
Rainy Nieselregen mit Aufprall, Wetterleuchten in den Wolken der Kopfleiste src/components/layout/RainyWeather.tsx, .rainy-flash
Snow Schneefall, der sich unten zu einer Decke sammelt; alle 4 Minuten fährt ein Räumfahrzeug durch src/components/layout/SnowWeather.tsx
Aura hell: Sonnenstrahlen, dunkel: Mond mit Sternen; ziehende Wolken und schaukelnde Hügel in der Seitenleiste (@keyframes aura-hills, nur hell) src/components/layout/AuraSky.tsx, src/index.css
Pixel Art einmal pro Minute läuft ein 8-Bit-Handwerker über die Steinmauer der Seitenleiste, hüpft und geht wieder src/components/layout/PixelRunner.tsx

Die Animationen hängen am ThemeProvider, liegen über der Oberfläche, fangen keine Klicks ab und bleiben unter Dialogen. Bei „Bewegung reduzieren“ im Betriebssystem bleiben sie alle aus.

Die Figur im Pixel-Theme ist eine eigene Zeichnung – grüne Mütze, Schutzbrille, Latzhose – und steht als Pixelraster (14 × 16) direkt in der Komponente, ohne Bilddatei. Bewusst kein Sprite aus einem kommerziellen Spiel: solche Figuren und Grafiken sind geschützt. Sie läuft von links herein, hüpft und geht wieder nach links hinaus; Boden ist die Unterkante der Menüliste, also die freie Mauer über Einstellungen. Nur ab Tablet-Breite, nicht bei „Bewegung reduzieren“. Rückbau: git revert <Commit> – oder die Datei löschen und die Zeile {palette === 'pixel' && <PixelRunner />} samt Import aus src/lib/theme.tsx entfernen. Sonst hängt nichts daran.

Ein neues Theme braucht vier Stellen: Farbblock in src/index.css, Eintrag in THEME_PALETTES samt Beschriftung (src/types.ts), die Freigabeliste im Inline-Skript von index.html und die Prüfung in server/src/routes/auth.js (PATCH /api/auth/me) – fehlt die letzte, lässt sich die Wahl nicht speichern.

Entwicklung

npm install && (cd server && npm install)
cp server/.env.example server/.env   # Werte setzen
# DB z. B. per Tunnel: plink -L 3307:127.0.0.1:3306 root@31.70.144.60  (DB_PORT=3307, DB_NAME=perinco_pulse_dev)
(cd server && npm run dev)           # Backend :5010
npm run dev                          # Frontend :3110, proxyt /api

perinco_pulse_dev enthält Demodaten (Kunden, Projekte, ~1000 Buchungen, Testbenutzer mit Passwort Test12345!) und wird von der Produktion nicht benutzt.

Deployment

$env:PULSE_SSH_PASSWORD = '…'
powershell -File deploy/deploy.ps1          # baut, lädt hoch, tauscht dist atomar, startet Dienst neu
powershell -File deploy/deploy.ps1 -Deps    # zusätzlich npm ci im Backend

Die Produktions-.env liegt nur auf dem Server (/var/www/pulse/server/.env, 600). SETTINGS_KEY nicht ändern, sonst ist das gespeicherte SSO-Secret unlesbar.

Werte, die nur dort stehen und ein deploy.ps1 überdauern: JWT_SECRET, JWT_EXPIRES_IN (Sitzungsdauer, aktuell 7d), SETTINGS_KEY, ADMIN_PASSWORD, Datenbankzugang. Nach einer Änderung an der .env den Dienst neu starten (systemctl restart perinco-pulse-backend) – das Deployment allein liest sie nicht neu ein.

Anforderungs-Checkliste

Aus Auftrag und Call-Mitschrift.

Anforderung Status Wo
Dashboard Mgmt/Admin: Billable/Non-billable-Grafik, umschaltbar Tag/Woche/Monat/Quartal/Jahr, animiert ✅ Dashboard
Letzte Buchungen ✅ Dashboard
Mitarbeiter-Übersicht auf-/absteigend, letzte Tage/Woche/Monat/Quartal/Jahr, Filter billable/non-billable ✅ animierte Umsortierung Dashboard
Vorgesetzte sehen nur ihr Team ✅ rekursiv, auch im Backend Dashboard, Reports, Buchung (Teamansicht, nur lesen)
Buchung: Wochenansicht Standard, umschaltbar Monat/Jahr ✅ Buchung
Buchung: zusätzliche Listenansicht (Kunde, Projekt, Task, Billable/Non-billable Zeit) für Woche/Monat/Jahr ✅ Buchung → Kalender/Liste
Klick auf einen Mitarbeiter im Zeitreport zeigt dessen Buchungen des Zeitraums als Liste ✅ Reports → Zeitreport → Mitarbeiter
Kunden: zusätzliche Listenansicht ✅ Kunden → Karten/Liste
Eigenes Profilbild hochladen ✅ Mein Konto, Team → Bearbeiten
Abwesenheiten automatisch aus HRworks ✅ täglich 5:30 und 12:00 Uhr Einstellungen → Abwesenheiten (HRworks)
Kalender mit allen genehmigten Urlauben (auch zukünftig) und Feiertagen, für alle ✅ Monat / Zeitleiste, Filter Team + Name Kalender
Bestellnummer je Budgettopf, erscheint im Stundennachweis ✅ Projekt → Budgettopf
Rolle „Externe" mit den Rechten eines Mitarbeiters ✅ Team → Bearbeiten
Export: Kunde und Projekt nur aus den eigenen Zuweisungen ✅ Buchung → Export
Projekte: zusätzliche Listenansicht ✅ Projekte → Karten/Liste
Grünes Plus → Projekt, Task, optionaler Freitext, Zeit rechts daneben, 0:00, 1:23 und 1,5 ✅ Buchungsdialog
Nur zugewiesene Projekte, Task optional ✅ Buchungsdialog
Zeiten bearbeiten und löschen ✅ Klick auf Buchung
Monatsexport Excel/PDF/CSV als Kundenvorlage mit Logo und Footer ✅ (Footer-Inhalt siehe Hinweis) Buchung → Export
Menü je Rolle (Dashboard/Buchung, Projekte, Team, Kunden, Reports, Einstellungen) ✅ Seitenleiste / mobile Leiste
Projekte: User nur zugewiesene; andere alle inkl. Töpfe, bearbeiten/anlegen/entfernen ✅ Projekte
Projekt wählen → Tasks darunter ✅ Projekte
Projektanlage: Name, Code automatisch, Start/Ende, Budget, Tags, Notizen, Typ billable/Abruf/non-billable/Festpreis, Stunde/Tag ✅ Neues Projekt
Budgettopf: Laufzeit, Budget €, Stundensatz, Bezeichnung; n Töpfe; Balken Stunden/Tage/Budget ✅ Projekte
Hinweis beim Buchen, wann Budget endet und wieviel offen ist ✅ Buchungsdialog, Buchung
Topf voll oder abgelaufen → nicht buchbar ✅ serverseitig erzwungen
Nur auf Topf buchen, dessen Laufzeit die Leistung enthält ✅
Töpfe bearbeiten ab Vorgesetzter ✅ Projekte
Festpreis: virtueller Stundensatz intern ✅ Projekt, Projektreport
Report: Gesamt aller Töpfe und einzelne Töpfe ✅ Projektreport → Budgettöpfe
Projekte Usern zuweisen ✅ Projekt → Zuweisen / Formular
Kunden über Projekten ✅ Kunden, Projektformular (+ Kunde)
Team: Hierarchie Vorgesetzte/Mitarbeiter, lokale User anlegen/bearbeiten/löschen, Externe ✅ Organigramm + Liste Team
Kunden anlegen/bearbeiten, wer hat welche Projekte mit welchen Mitarbeitern, Umsatz erbracht/offen, animiert ✅ Kunden
Reports wie Harvest (Report.png), interaktiv, Export Excel/PDF/CSV ✅ Reports → Zeitreport
Report für einzelne Kunden / Projekt wie Report-Kunde.png ✅ Reports → Kunden-/Projektreport
Kundenreport nach Woche, Monat, Jahr oder freiem Zeitraum ✅ zusätzlich Quartal und Gesamt Reports → Kundenreport
Einstellungen (nur Admin): SSO-Credentials, Userverwaltung, Kopf/Fuß der Exporte ✅ inkl. PDF-/Excel-Vorschau Einstellungen
Arbeitsstunden pro Woche (40) ✅ Einstellungen → Allgemein
Projektbezeichnungen nach Muster ✅ Einstellungen → Allgemein
Themes wie CRM (perinco, Pixel Art, Outdoor, Cyberpunk, Rainy, Snow, Aura, Mobile – jeweils hell/dunkel, am Benutzer gespeichert) ✅ Kopfleiste
Lokaler Admin mit vorgegebenem Passwort, später änderbar ✅ Mein Konto
Mobil: eigene Ansicht bei Handy ✅ untere Navigation, Tagesstreifen, Bottom-Sheets, schwebender Plus-Knopf
SSO, anfangs normaler Login; AD-User auslesen, Rollen intern ✅ vorbereitet, noch nicht gegen echten Tenant getestet Einstellungen → SSO

Offene Punkte

  • Wordvorlage lag nicht im Ordner. Kopf nutzt das perinco-Logo; die Fußzeilen (Adresse, Registergericht, Bank) bitte in Einstellungen → Export eintragen.
  • SSO ist implementiert, braucht aber eine App-Registrierung in Entra ID zum Test.
  • Nicht umgesetzt, weil nicht gefordert: Rechnungsmodul („Uninvoiced amount“ aus Harvest), gespeicherte Reports.