Perinco-Farben, aber rund 16 % dichter: --spacing 0.21rem statt 0.25rem,
kleinere Ueberschriften (lg bis 5xl), Kopfleiste 3.5rem, flachere Schatten.
text-sm/base bleiben, damit iOS beim Tippen in Felder nicht zoomt.
Dunkel mit echtem Schwarz fuer OLED.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
Bisher ging ein Stundennachweis nur von einer Person aus. Fuer die
Rechnung an den Kunden ist aber der Auftrag die Klammer: wer alles auf
Auxil gebucht hat, steht nirgends auf einem Blatt.
Auf der Kundenseite oeffnet ein Knopf je Karte und Zeile den
Kundennachweis: Monat, Format und wahlweise einzelne Projekte des
Kunden; ohne Auswahl sind alle enthalten. Das Dokument zeigt je Zeile
den Berater und fasst am Ende nach Berater (Tage, Dauer, Stunden,
optional Betrag) und nach Projekt und Task zusammen.
Dafuer nimmt /exports/timesheet ein ?project_ids fuer mehrere Projekte,
und loadEntries filtert entsprechend. Die Berater-Zusammenfassung
erscheint nur, wenn mehr als eine Person beteiligt war.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A8ZiAB96iSmQhDrK4Jpi2R
Wer in der Buchung einen Kollegen ausgewaehlt hatte und dann exportierte,
bekam im Dialog die eigenen Kunden und Projekte zur Auswahl - fuer einen
Nachweis, der jemand anderem gehoert.
Der Dialog laedt die Projekte jetzt zu der Person, fuer die der Nachweis
entsteht, und laedt neu, wenn man die Person darin wechselt; die alte
Kunden- und Projektauswahl wird dabei verworfen, weil sie nicht mehr
passt. Bei "Alle Mitarbeiter" stehen alle sichtbaren Projekte zur Wahl.
Dafuer nimmt GET /projects ein ?user_id an. Wer fremde Projekte sehen
darf, entscheidet dieselbe Sicht wie bei den Zeiten: Mitarbeiter bekommen
403, Vorgesetzte nur ihr Team, Management alle. Die Sammelauswahl heisst
fuer Management "Alle Mitarbeiter" statt "Alle meine Mitarbeiter" - sie
sieht ohnehin jeden.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Zwoelf Stunden bedeuteten in der Praxis, dass sich jeder mindestens
einmal am Tag neu anmeldet - bei SSO ein zusaetzlicher Umweg ohne
Gewinn. Die Frist laeuft weiterhin ab der Anmeldung und verlaengert sich
nicht bei Aktivitaet.
Auf dem Server steht der Wert in der .env; hier ziehen Vorgabe und
Beispieldatei nach, damit eine neue Installation nicht wieder bei zwoelf
Stunden anfaengt.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Die Auswahl im Exportdialog kam aus allen zugewiesenen Projekten - also
auch aus archivierten und von stillgelegten Kunden. In der Liste stand
damit etwa "Top IT Services", wo seit 2025 nichts mehr laeuft.
Waehlbar sind jetzt nur laufende Projekte aktiver Kunden. Dafuer liefert
loadProjects den Status des Kunden mit; ohne ihn liesse sich "nur aktive
Kunden" in der Oberflaeche nicht entscheiden.
Die Zeiten aus archivierten Projekten bleiben erreichbar: ohne Filter
exportiert der Nachweis alles, was die Person im Monat gebucht hat, und
der Projektreport hat seinen eigenen Export.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Management und Administration koennen oben rechts eine Frage in Worten
stellen - "wie viele Stunden im August", "welches Projekt hat noch
Budget", "wie ist die Auslastung".
Aufbau: das Modell bekommt eine Uebersicht (Kunden, laufende Projekte,
Eckdaten) und vier Werkzeuge - auswertung, projekte, buchungen,
personen. Damit fragt es gezielt nach, statt dass ihm der ganze Bestand
vorgelegt wird; 19.000 Buchungen passen in keinen Kontext. Die Werkzeuge
rechnen mit loadEntries und loadProjects, also mit denselben Funktionen
wie die Reports - der Assistent kann so keine zweite Wahrheit erzeugen.
Was einfliesst, begrenzt visibleUserIds wie ueberall sonst.
Benutzt wird die Responses-API von OpenAI: die neueren Modelle nehmen
Werkzeuge in /v1/chat/completions nur ohne Nachdenken an. Mit
"store: false" bleibt nichts bei OpenAI liegen, der Verlauf geht deshalb
je Runde komplett mit. Der Schluessel liegt verschluesselt in den
Einstellungen (AES-256-GCM wie das SSO-Secret) und wird nie ausgeliefert;
das Modell ist einstellbar, Vorgabe gpt-5.6-luna.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Im Zeitreport stehen interne Projekte und Abwesenheiten zwischen den
Kundenzahlen: 2025 sind das 19.451 von 45.762 Stunden, und die Auslastung
zeigt 146 Prozent, weil Urlaub und Krankheit mitzaehlen.
Ein Haken neben der Billable-Aufteilung blendet Projekte vom Typ
"nicht abrechenbar" aus. Er wirkt auf den ganzen Report - Summen,
Auslastung, alle vier Gruppierungen - und auf die Exporte, die denselben
Report bauen; im PDF-Kopf steht dann "Nicht abrechenbare Projekte:
ausgeblendet". Gefiltert wird in loadEntries, damit Kunden-, Projekt- und
Taskliste dieselbe Grundmenge sehen.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Beim Nachziehen der Harvest-Historie sind zwei Regeln im Weg gestanden,
die fuer eine Uebernahme nicht passen:
- Beim Anlegen verlangte die API auch fuer nicht abrechenbare Projekte
einen Satz, obwohl validateProject ihn direkt danach verwirft. Nur
Abruf rechnet ueber den Satz am Projekt ab, also gilt die Pflicht auch
nur dort.
- Der Importweg liess hoechstens 24 Stunden je Tag zu. In Harvest stehen
Sammelbuchungen ("Nachbuchung DORA September und Oktober", 80 Stunden
auf einem Tag). Sie abzulehnen verliert die Stunden und verfaelscht das
Restbudget, sie zu verteilen erfindet Leistungsdaten - deshalb kommen
sie so herein, wie sie gebucht wurden. Eine Buchung darf jetzt bis zu
31 Tage umfassen, damit Zahlendreher weiter auffallen. Im
Buchungsdialog bleibt die Tagesgrenze bestehen.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Die Uebernahme der Harvest-Historie ist an drei Stellen haengen geblieben:
- resolvePot hat abgelaufene Toepfe grundsaetzlich gesperrt. Fuer eine
historische Buchung ist das falsch - dort zaehlt, ob der Topf am
Leistungstag lief. Der Importweg setzt jetzt `historic`, das Volumen
wird weiter geprueft, damit Ueberbuchungen auffallen.
- In der SSO-Verwaltung fehlte die Ablagezone fuer die Rolle "Externe";
Konten liessen sich dort nicht zuordnen.
- In buildProjectDetail und der Kundenauswertung stand ein Rest der
Auslastungsaenderung (bisHeute/capTo) ohne Bezug im Scope. Beide
Reportseiten liefen dadurch auf 500.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Auslastung
Verglichen werden jetzt die bis heute gebuchten Stunden mit der Kapazitaet
bis heute. Die Kapazitaet endete schon immer am heutigen Tag; wurde im
Voraus gebucht - eine ganze Woche Urlaub am Montag - standen 40 Stunden
gegen 8 Stunden Kapazitaet und die Wochenansicht zeigte 500 Prozent. Die
Spalte "Stunden" zeigt weiter den ganzen Zeitraum.
Tasks
Das Feld zum Hinzufuegen lief aus der Karte heraus: die Zeile darf jetzt
umbrechen, und die Breitenangaben tragen "!", weil inputClass w-full
mitbringt und sonst gewinnt. Der Satz eines Tasks steht als schmales Feld
direkt in der Zeile und wird beim Verlassen gespeichert - vorher lag er
hinter einem Abfragedialog und war praktisch nicht zu finden.
Export der Buchungen
Kunde und Projekt kommen nur noch aus den eigenen Zuweisungen, auch fuer
Vorgesetzte. Eine Liste aller Projekte des Hauses war dort nur Suchaufwand.
Budgettopf
Neues Feld Bestellnummer (optional). Es erscheint im Stundennachweis als
eigene Kopfzeile, wenn die gebuchten Toepfe eine tragen.
Rolle "Externe"
Gleiche Rechte wie "Mitarbeiter" und derselbe Rang, die Rolle trennt nur,
wer zum Haus gehoert. Sie setzt is_external automatisch, damit Liste und
Kennzeichnung nicht widersprechen. Vorgesetzte duerfen sie vergeben.
Stellen, die bisher role === 'user' pruefen, fragen nun hasRole(lead) ab -
sonst haetten Externe alle Projekte gesehen.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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>
Satz je Task
Ein Task kann einen eigenen Satz tragen; leer bedeutet weiter "Satz des
Budgettopfs" bzw. bei Abruf "Satz des Projekts". Damit lassen sich
"remote" und "vor Ort" im selben Projekt abrechnen, ohne dafuer getrennte
Projekte anzulegen.
Das Budget bleibt in Stunden - gemischte Saetze aendern nichts daran, wann
ein Topf voll ist. Der Umsatz wird je Task-Satz aufsummiert: loadProjects
gruppiert den Topfverbrauch nach Task-Satz und gibt das Ergebnis an
potStats, Abruf rechnet je Task. Mitarbeiter sehen keine Saetze, auch
nicht die am Task.
Import
POST /api/imports/entries (nur Administrator) schreibt Buchungen fuer
beliebige Personen - POST /api/entries bucht bewusst nur auf den
angemeldeten Benutzer. Der Import laeuft durch dieselben Regeln: Projekt
aktiv, Person zugewiesen, Topf passend zum Datum, maximal 24 h am Tag.
dry_run schreibt nichts.
Jede uebernommene Buchung traegt ihre harvest_id (unique). Ein zweiter
Lauf legt nichts doppelt an. DELETE /api/imports/entries entfernt genau
die uebernommenen Zeiten; handgetippte bleiben unberuehrt.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Die Summenzeile des Stundennachweises trug ihre Beschriftung in der Spalte
"Tag". Die ist rund 40 Punkt breit, also brach "Summe" dort in drei Zeilen
um ("Su/mm/e"), waehrend die Zahlen rechts daneben in einer Zeile standen.
drawRow laesst in der Summenzeile eine Beschriftung jetzt in die leeren
Spalten dahinter laufen und setzt sie einzeilig. Gefuellte Spalten werden
nicht einverleibt, die Zahlen bleiben also an ihrem Platz. Das gilt fuer
alle PDF-Tabellen, nicht nur den Stundennachweis; Excel und CSV waren nie
betroffen.
Rueckbau: diesen Commit reverten - die Aenderung steckt allein in
export/render.js.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Jeder setzt sein Bild unter "Mein Konto", Vorgesetzte und Administratoren
auch fuer die Benutzer, die sie verwalten. Wo bisher die Initialen standen,
steht jetzt das Bild - Seitenleiste, Kopfzeile, Team, Dashboard, Reports,
Projekte, Kunden und die Buchungsliste. Ohne Bild bleiben die Initialen.
Der Browser verkleinert das Bild vor dem Hochladen auf ein Quadrat von
256 px und schneidet mittig zu (lib/imageCompress.ts): aus einem 4-MB-Foto
werden wenige Kilobyte, und der Server muss kein Bild verarbeiten.
Gespeichert wird es als Data-URL in pulse_users.avatar (LONGTEXT). Kein
Upload-Verzeichnis, das gesichert und beim Deployment am Leben gehalten
werden muesste.
Geprueft wird serverseitig in lib/avatar.js: nur PNG, JPEG, GIF, WebP und
AVIF als Data-URL, hoechstens 2 MB. SVG ist bewusst nicht erlaubt, es kann
Skripte tragen. Externe http(s)-Adressen ebenso wenig - sonst laedt jeder
Seitenaufruf ein Bild von einem fremden Server und verraet, wer wann mit
Pulse arbeitet.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Neue Komponente EntryList zeigt einzelne Buchungen als sortierbare Tabelle
mit Datum, Kunde, Projekt, Task und der Zeit getrennt nach billable und
non-billable, inklusive Summen.
- Buchung: Umschalter Kalender/Liste neben Woche/Monat/Jahr, Auswahl wird
im Browser gemerkt. Klick auf eine Zeile oeffnet die Buchung.
- Reports: Klick auf einen Mitarbeiter oeffnet dessen Buchungen des
Reportzeitraums als Liste; ein gesetzter Kundenfilter wird uebernommen.
- Kunden: Umschalter Karten/Liste mit Ort, Projekten, Stunden und Umsatz.
- GET /api/entries akzeptiert dafuer customer_id, Segmented nimmt Icons,
Modal kennt die Groesse 2xl.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Abruf braucht keinen Budgettopf: es gibt kein zugesagtes Volumen und kein
Ablaufdatum, gebucht wird, was anfaellt. Der Satz kann deshalb nicht am
Topf haengen und steht am Projekt (pulse_projects.rate, Stunde oder Tag).
Der Umsatz sind die abrechenbaren Stunden mal diesem Satz; offen ist
nichts.
- Migration ergaenzt den ENUM-Wert und die Spalte rate
- Anlegen verlangt einen Satz und nimmt keine Toepfe an; ein Topf
nachtraeglich anzulegen wird abgewiesen
- Wechsel auf Abruf nur, solange auf den Toepfen nichts gebucht ist -
sonst wuerden Buchungen ihr Budget verlieren; leere Toepfe fallen weg
- Buchungsdialog, Projektkarte, Projektdetail und Projektreport zeigen
Satz und Umsatz statt Budgetbalken
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Erste Fassung, live unter https://pulse.perinco.de.
Frontend React 19 + TypeScript + Tailwind 4 (Vite), Backend Express mit
mysql2 auf MariaDB, Exporte als PDF (pdfkit), Excel (exceljs) und CSV.
Aufbau, Designsystem und die sieben Themes kommen aus dem perinco CRM.
Fachlich:
- Buchung mit Wochen-, Monats- und Jahresansicht, Zeiten als 1:23 oder 1,5
- Projekte mit n Budgettoepfen: Laufzeit, Volumen, Sperre bei Ablauf oder
aufgebrauchtem Budget, Buchung nur auf den passenden Topf
- Kunden, Team mit Hierarchie, Reports im Harvest-Stil, Monatsexporte
- Rollen user/lead/management/admin, doppelt geprueft (Menue und Backend)
- SSO gegen Microsoft Entra ID, Uebernahme der AD-Benutzer per Drag & Drop
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>