Bisher musste der Administrator den Modellnamen abtippen - ein Tippfehler fiel erst beim Test oder bei der ersten Frage auf, und welche Modelle der Schluessel ueberhaupt darf, war in Pulse nicht zu sehen. Einstellungen -> KI-Assistent zeigt jetzt ein Auswahlfeld. Die Liste kommt ueber POST /settings/ai/models live von OpenAI (/v1/models), neueste zuerst; Embedding-, Bild-, Audio-, Realtime-, Such- und Moderationsmodelle fliegen raus. "Liste laden" nimmt einen gerade eingetippten, noch nicht gespeicherten Schluessel mit. Das eingestellte Modell bleibt waehlbar, auch wenn OpenAI es nicht mehr listet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
325 lines
24 KiB
Markdown
325 lines
24 KiB
Markdown
# perinco Pulse
|
||
|
||
Zeiterfassung für perinco – live unter **https://pulse.perinco.de**.
|
||
Quellcode: [git.perinco.de/Sascha/perinco-Pulse](https://git.perinco.de/Sascha/perinco-Pulse).
|
||
Stil, Themes und Aufbau folgen dem perinco CRM
|
||
([git.perinco.de/Sascha/perinco-CRM](https://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) |
|
||
| 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, Team, Kunden, Reports |
|
||
| Management | Dashboard | wie Vorgesetzter, sieht alles; Einstellungen nur „SSO-Verwaltung“ |
|
||
| Administrator | Einstellungen | Dashboard, Projekte, 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.
|
||
|
||
|
||
## 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 einmal 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.
|
||
|
||
**Täglich** läuft `server/src/jobs/hrworks-sync.js` über den systemd-Timer
|
||
`perinco-pulse-hrworks.timer` (5:30 Uhr Berliner Zeit, `Persistent=true` holt verpasste Läufe
|
||
nach). 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.
|
||
|
||
## 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
|
||
|
||
Sieben 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`). Sie kommen 1:1 aus dem perinco CRM; vier 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
|
||
|
||
```bash
|
||
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
|
||
|
||
```powershell
|
||
$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 Uhr | Einstellungen → Abwesenheiten (HRworks) |
|
||
| 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 |
|
||
| 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 – 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.
|