Files
perinco-Pulse/README.md
T
schakal00783andClaude Opus 5 532b15ce99 feat(hrworks): Abwesenheiten taeglich aus HRworks uebernehmen
Urlaub, Krankheit, Elternzeit und Feiertage stehen in HRworks. Pulse holt
sie einmal taeglich und bucht sie auf das nicht abrechenbare Projekt
"Abwesenheiten". Nach HRworks geht nichts zurueck, es wird nur gelesen.

Die Stunden rechnet HRworks selbst: available-working-hours liefert je
Person und Tag das Soll und die verfuegbaren Stunden, 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.

Abgesprochene Regeln: nur genehmigte Abwesenheiten (validatedOk), bei
Krankmeldungen auch offene Bescheinigungen; nur bis heute buchen, der
Rest waechst taeglich nach; halbe Tage feste 4 Stunden; jeder aktive
Mitarbeiter wird dem Projekt zugewiesen, auch neue.

Jede Buchung traegt hrworks_ref = "<Nummer>@<Tag>" (unique). Ein zweiter
Lauf aendert nichts, Stornos und Verschiebungen verschwinden im naechsten
Lauf, DELETE /api/hrworks/entries entfernt genau diese Buchungen.

Nicht abrechenbare Projekte brauchen keinen Budgettopf mehr. Ein Topf
traegt Volumen, Laufzeit und Satz - das hat nur bei Billable und
Festpreis Sinn. Am Abwesenheitsprojekt war er nur ein Ablaufdatum, das
irgendwann Buchungen blockiert haette.

Taeglicher Lauf ueber systemd (perinco-pulse-hrworks.timer, 5:30 Uhr
Berliner Zeit, Persistent). Bedienung unter Einstellungen ->
Abwesenheiten (HRworks) mit Verbindungstest und Probelauf; der geheime
Schluessel liegt AES-verschluesselt in der Datenbank.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 14:50:54 +02:00

284 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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) |
| 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.
## 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.
## 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.
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.
## 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.
## 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) |
| 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.