daagwerk Changelog
Changelog
Changelog
Alle wesentlichen Änderungen an daagwerk — chronologisch, neueste zuerst. Format nach Keep a Changelog, Versionierung nach Semantic Versioning.
Changelog — daagwerk
Alle wesentlichen Änderungen an daagwerk werden in dieser Datei dokumentiert.
Format basiert auf Keep a Changelog.
Versionierung folgt Semantic Versioning.
Unreleased
- Statistik-Charts + Profitabilitäts-Layer (v2.4) — aus v2.0 SR2 herausgelöst; Deckungsgrad = Umsatz ÷ (gebucht × Kunden-Satz), ohne Personalkosten; brauchen das v2.1-Fundament, landen mit dem Auswertungs-Re-Design. Enthält auch die Budget-Gauge-Geld-Modus-Korrektur (je-Modell statt hours×rate).
[2.6.4] — 2026-07-13
Fix der Serien-„Endet”-Zeile (Layout endgültig korrekt).
Fixed
- Nach dem Umbau auf gestapelte Optionen (v2.6.3) standen die Texte („nach N
Terminen” / „bis Datum”) am rechten Modal-Rand bzw. außerhalb, weil die
<label>-Zeilen auf volle Breite gestreckt wurden. Jetzt sind die Zeileninline-flex(inhaltsbreit) + linksbündig — Radio, Beschriftung und Feld sitzen eng zusammen, innerhalb des Fensters. Visuell in isolierter Vorschau verifiziert.
[2.6.3] — 2026-07-13
Politur: Serien-„Endet”-Zeile aufgeräumt.
Fixed
- Im „Wiederholen”-Bereich war die Ende-Zeile (nach N Terminen / bis Datum) im Layout verrutscht — Radiobuttons, Zahlenfeld und Datumsauswahl liefen in einer umbrechenden Reihe durcheinander. Jetzt zwei saubere, gestapelte Optionen unter der Überschrift „Endet”.
[2.6.2] — 2026-07-13
Wiederkehrende (geplante) Buchungen — Serie (Scheibe 2).
Added
- „Wiederholen” im Buchungs-/Duplizieren-Dialog: erzeugt aus einer Vorlage eine
ganze Serie geplanter Buchungen (Outlook-artig).
- Regel: täglich oder wöchentlich (mit Intervall „jede N. Woche” + Wochentagen Mo–So) und Ende wahlweise nach N Terminen oder bis Datum.
- Live-Vorschau „erzeugt X Termine”, Obergrenze 200 pro Serie.
- Uhrzeit und Dauer der Vorlage bleiben je Termin erhalten.
- Alle Serien-Termine sind „geplant” → sie zählen nicht ins Budget; jeden aktivierst Du später einzeln, wenn er wirklich stattfindet. Ideal für Jour Fixes über lange Projektlaufzeiten, ohne das Budget-Bild zu verfälschen.
- Neuer Endpoint
POST /entries/recurring(legt die geplanten Buchungen in einer Transaktion an, jede über den planned-Pfad — kein Budget-Verbrauch).
Verifiziert mit einem Integrationstest (12-Termine-Serie berührt das PO-Budget nicht). Keine Migration.
[2.6.1] — 2026-07-13
Buchung verschieben: Von-Datum zieht das Bis-Datum mit (überall gleich).
Fixed
- Beim Ändern des Von-Datums wird jetzt in allen Buchungs-Dialogen
automatisch das Bis-Datum mitgezogen (die Bis-Uhrzeit bleibt) — kein
„Ende darf nicht vor Anfang liegen”-Fehler mehr beim Verschieben/Duplizieren.
Das Verhalten (
syncEndDate, seit v1.6) fehlte bislang im Buchungs-Fenster (NewBookingModal, also auch beim Duplizieren) und im Job-Seiten-Inline-Edit; jetzt ist es unisono mit dem Bearbeiten-Modal, der Buchungsliste und den Reports.
[2.6.0] — 2026-07-13
Geplante Buchungen als eigener Status (Fundament, Scheibe 1).
Neues Konzept für zukünftige/wiederkehrende Termine: eine geplante Buchung ist eine ganz normale Buchung mit allen Feldern, nur im Status „geplant” – und wird aus allen Auswertungen ausgeklammert (Budget, Bestellnummern, Reports, PDF, Dashboard, Auslastung, Übersichten), bis sie aktiviert wird. So verfälschen z. B. Jour Fixes über 24 Monate nicht das aktuelle Budget-Bild.
Added
- Status „geplant” (Migration 050,
entry_status-Enum). Anlegen über eine auffällige Checkbox im Buchungs-Dialog (manuell und im Wochenkalender). - Anzeige: geplante Buchungen erscheinen in „Meine Woche” hell/durchscheinend mit gestricheltem Rahmen und „geplant”-Kennzeichnung; in Listen mit dem Status-Badge „Geplant”. Sie zählen nirgends ins Geld.
- Aktivieren: ein ✓-Icon am Block wandelt „geplant → gebucht” – genau dann läuft der gehärtete Pfad (Bestellnummer auflösen, bei Überschreitung aufteilen, Erschöpfung prüfen). Erst hier wird aus „geplant” echtes Budget.
- Geplante Buchungen lassen sich wie echte verschieben, in der Größe ziehen, bearbeiten, duplizieren und stornieren – ohne Budget-Wirkung.
Changed
- Budget-/Auswertungs-Ausschluss an allen ~40 Aggregations-Stellen (der neue Status fällt überall raus, wo Geld/Stunden summiert werden).
Removed
- Die alte Forecast-Funktion („+ Geplante Buchung” in der Buchungsliste, eigene
planned_entries-Tabelle) wurde vollständig entfernt (Migration 051). Das neue Status-Konzept ersetzt sie sauberer.
Verifiziert mit neuen Integrationstests (geplant zählt nicht ins Budget; Aktivieren verbraucht es korrekt) und der vollen Test-Suite. Nächste Scheibe: wiederkehrendes Anlegen (Serie → geplante Buchungen).
[2.5.8] — 2026-07-13
„Meine Woche” — Duplizieren mit Bearbeiten (Phase 2).
Added
- Zweites Kopieren-Icon in der Hover-Leiste (lila, mit „+”): öffnet das
Anlege-Fenster vollständig vorbefüllt aus der Vorlage (Job, Start/Ende, Notiz;
bei Managern der Nutzer). Der Aktions-Button heißt dort „Duplizieren” – Datum
oder Werte lassen sich vorher anpassen, dann anlegen. So dupliziert man z. B. ein
Meeting auf einen anderen Tag, ohne es hinterher zu verschieben.
NewBookingModalum Prefill (initialJob,initialNotes) +mode='duplicate'(Titel/Button/Toast) erweitert; getriggert über das bestehendedaagwerk:open-new-booking-Event. Speicherpfad unverändert (POST /entries).
- Die vier Overlay-Icons (Bearbeiten · Sofort-Duplikat · Duplizieren+Bearbeiten · Stornieren) wurden etwas kompakter gesetzt, damit sie auch auf schmalen Blöcken passen.
Nächste Phase: wiederkehrendes Duplizieren (Serie → konkrete Buchungen, mit Budget-Vorschau).
[2.5.7] — 2026-07-13
„Meine Woche” — Buchung duplizieren (Phase 1: Sofort-Duplikat).
Added
- Kopieren-Icon in der Hover-Leiste jedes Blocks (zwischen Stift und
Papierkorb): Ein Klick legt eine 1:1-Kopie der Buchung an – auf derselben
Uhrzeit, wodurch das Überlappungs-Layout sie direkt daneben zeigt; danach
per Drag verschieb- und bearbeitbar. Notiz und abrechenbare Zeit werden
mitkopiert. Ideal für wiederkehrende Termine: einmal duplizieren, dann ziehen.
- Erstellt über den gehärteten Buchungspfad (
POST /entries); die Bestellnummer wird frisch aufgelöst (aktive PO des Jobs).
- Erstellt über den gehärteten Buchungspfad (
Nächste Phasen: Duplizieren mit Bearbeiten (Dialog vorbefüllt), danach wiederkehrendes Duplizieren (Serie → konkrete Buchungen, mit Budget-Vorschau).
[2.5.6] — 2026-07-12
„Meine Woche” — Bearbeiten und Stornieren per Hover-Overlay.
Added
- Hover-Icons auf jedem Block: Bleistift (bearbeiten) und Mülleimer (stornieren)
erscheinen, sobald die Maus über einer Buchung liegt.
- Bleistift öffnet das bestehende „Erweitert bearbeiten”-Modal
(
EntryEditModal) — Account→Projekt→Job wechseln, Datum/Zeiten, Notiz, Abrechenbar, (Manager) Nutzer; speichert über den gehärteten Pfad, danach lädt die Woche neu. - Mülleimer storniert die Buchung (Status
cancelled, mit Rückfrage) — sie verschwindet aus dem Kalender. daagwerk kennt kein hartes Löschen; Stornieren ist der reguläre Weg. - Klick auf ein Icon startet keinen Verschiebe-/Resize-Zug (sauber getrennt).
Abgerechnete (
invoiced) Buchungen und Timer haben keine Overlay-Icons.
- Bleistift öffnet das bestehende „Erweitert bearbeiten”-Modal
(
[2.5.5] — 2026-07-12
„Meine Woche” — Buchungen verschieben und in der Größe ziehen (P3).
Added
- Verschieben per Drag (auch auf einen anderen Tag) und Resize an beiden
Kanten direkt im Wochenkalender. Block anfassen und ziehen verschiebt ihn
(Uhrzeit + Tag), oben/unten anfassen ändert Start bzw. Ende. 15-Minuten-Raster,
Live-Vorschau mit Uhrzeit-Label, Original während des Ziehens gedimmt.
Gespeichert wird über
PUT /entries/{id}— den gehärteten Pfad mit Neuberechnung von Gebucht/Abrechenbar und PO-Budget-Re-Split (v2.3.4–2.3.6); danach lädt die Woche neu (zeigt einen etwaigen Split sofort). Abgerechnete (invoiced) Buchungen und laufende Timer sind nicht ziehbar; scheitert das Speichern (z. B. gesperrter Zeitraum), wird der alte Stand wiederhergestellt.
Damit ist „Meine Woche” bis auf die geplanten Buchungen (P4) komplett bedienbar.
[2.5.4] — 2026-07-12
Bugfix: „Meine Woche” zeigte in vollen Wochen nicht alle Buchungen.
Fixed
- Kalender lädt jetzt alle Buchungen der Woche. Die Wochenansicht rief
/entriesohnelimitauf → es griff der Default von 25 Einträgen beiORDER BY started_at DESC. In Wochen mit vielen Buchungen fielen dadurch die frühesten Tage aus der Ansicht (in der Buchungsliste sichtbar, im Kalender nicht). Fix:limit=250(Handler-Maximum) — eine Ein-Personen-Woche bleibt weit darunter.
Reines Frontend — keine Migration.
[2.5.3] — 2026-07-12
Bugfix: Timer-Erinnerung per Web Push wurde still verschluckt.
Fixed
- Fehlgeschlagene Pushes gelten nicht mehr als zugestellt.
webpush-goliefert bei einer Ablehnung durch den Push-Dienst (z. B. Apple/Safari mit 4xx) keinen Go-Fehler, sondern die HTTP-Antwort — der alte Code prüfte nur 404/410, sodass ein 400/403 fälschlich als Erfolg zählte: keine Push, aber auch keine Mail, und der stündliche Dedup-Slot blieb belegt (Erinnerung verstummte für die Stunde). Jetzt zählt nur ein 2xx als zugestellt; jeder andere Status wird geloggt (Host + Statuscode), der Dedup-Slot wird bei Fehlschlag zurückgegeben, und die E-Mail-Erinnerung feuert als Fallback, damit die Benachrichtigung ankommt. Ungültige Abos (404/410) werden weiterhin gelöscht. Neuer Integrationstest (Push-Ablehnung → Mail-Fallback + Slot-Freigabe).
Reines Backend — keine Migration.
[2.5.2] — 2026-07-12
„Meine Woche” — Buchungen im Kalender anlegen (P2).
Added
- Anlegen per Klick oder Ziehen. Ein Klick auf einen freien Slot legt eine
60-Minuten-Buchung ab dieser Uhrzeit an; das Aufziehen einer Spanne übernimmt
exakt Start und Ende (15-Minuten-Raster). Beides öffnet das bekannte
NewBookingModalmit vorbefüllter Start-/Endzeit — Job, Notiz usw. wie gewohnt. Während des Ziehens zeigt eine gestrichelte Vorschau die Spanne mit Uhrzeit-Label. Nach dem Speichern erscheint der Block sofort (Refetch viadaagwerk:entry-created).
Der Speicherpfad ist unverändert der gehärtete (POST /entries: Cross-Day-Split,
PO-Auflösung/-Budget-Split). Kein Backend, keine Migration. Es folgen:
Verschieben/Resize per Drag-&-Drop (P3), geplante Buchungen (P4).
[2.5.1] — 2026-07-12
„Meine Woche” — visuelle Politur der Tagesspalten.
Changed
- Tagesspalten als erhabene Karten. Arbeitstage erhalten jetzt die
Karten-Fläche (
--bg-card), freie Tage (Wochenende/Feiertag) fallen auf die Seitenfarbe (--bg) zurück — die Tage heben sich klar voneinander ab. Weil--bg-cardin beiden Themes heller ist als die Seite, funktioniert das im Light- wie im Dark-Mode ohne Sonderfall (weiße Karten auf Creme / erhabene dunkle Karten auf tieferem Schwarz). - Heute-Spalte dezent getönt (
--bg-info), damit der aktuelle Tag auf einen Blick sichtbar ist. - Kräftigerer Trenner zwischen den Spalten (
--border-strong).
[2.5.0] — 2026-07-12
„Meine Woche” — Kalender-Wochenansicht (P0 + P1: read-only). Start des großen Wochen-Features: alle eigenen Buchungen der Woche im Kalender-Grid.
Added
- Neue Seite „Meine Woche” (unter Erfassen): Wochengrid mit Spalten je Tag, 0–24 h, Buchungs-Blöcke nach Start/Ende, Status-Farbe + PO-Indikator, überlappende Buchungen nebeneinander, laufender Timer als Live-Block. Toggle Arbeitswoche/Ganze Woche (Arbeitswoche aus den persönlichen Workdays), KW-Wechsel (‹ Heute ›), Auto-Scroll auf die Arbeitszeit.
- Typische Arbeitszeit als neue persönliche Einstellung (Profil →
Arbeitszeit-Modell):
work_day_start/work_day_end(Migration 049). In der Wochenansicht werden Zeiten davor/danach sowie Wochenende + Feiertage ausgegraut — rein visuell, verhindert kein Buchen.
Read-only in dieser Runde. Es folgen: Anlegen per Klick/Ziehen (P2), Drag-&-Drop-Verschieben (P3), geplante Buchungen (P4).
[2.4.6] — 2026-07-12
Timer-Erinnerung als Browser-Push (Phase C). Store-unabhängiger Kanal für „Timer läuft noch?” auch bei geschlossenem Tab — ohne auf App-Store-Freigaben zu warten. Scope: Desktop-Browser (Chrome/Edge/Firefox, Windows/macOS).
Added
- Web Push. In Profil → Timer-Stopps aktivierbar („Push auf diesem Gerät”).
Nach Erlaubnis erscheint die Erinnerung als System-Benachrichtigung, auch wenn
daagwerk nur im Hintergrund läuft. Migration 048
push_subscriptions, VAPID-signierter + Ende-zu-Ende-verschlüsselter Versand (webpush-go), EndpointsGET /push/public-key,POST/DELETE /push/subscribe, Service Workersw.js. - Anti-Stau by design: nur laufende Timer werden gepusht (gestoppte nie),
kurze TTL (30 Min) → veraltete Pushes verfallen statt sich anzustauen, und
eine Sammel-Benachrichtigung pro Nutzer (fester
tag→ ersetzt sich selbst, nie ein Stapel). - Push ersetzt Mail: hat ein Nutzer ein aktives Push-Gerät, geht die Erinnerung als Push (kein E-Mail-Doppel); ohne Gerät weiterhin als E-Mail. Der persönliche Schwellwert (v2.4.5) steuert weiter das „wann”.
Sicherheit
- HTTPS-only, Payload Ende-zu-Ende verschlüsselt (der Push-Dienst kann den Inhalt nicht lesen), Requests VAPID-signiert (nur daagwerk darf an seine Abonnenten pushen), Erlaubnis explizit und jederzeit widerrufbar.
Phase C = letzte Timer-Phase; echte Inaktivitäts-Erkennung (Tyme-Stil) bleibt den nativen Apps vorbehalten.
[2.4.5] — 2026-07-11
Timer-Erinnerung pro Nutzer einstellbar (Phase B+).
Added
- Persönliche E-Mail-Erinnerung. Jeder Nutzer stellt in Profil → Timer-Stopps
selbst ein, ab wann (oder ob) er die „Timer läuft noch?”-E-Mail bekommt:
Aus / 1 h / 2 h (Standard) / 3 h / 4 h. Migration 047
(
users.timer_reminder_minutes, Default 120 → bisheriges Verhalten bleibt). Der Server-Reminder (v2.4.4) nutzt jetzt den persönlichen Wert statt einer festen 2-h-Konstante;0schaltet die Mail komplett ab.GET/PUT /meerweitert.
[2.4.4] — 2026-07-11
Timer-Erinnerung per E-Mail (Phase B).
Added
- Server-Mail-Reminder bei lange laufenden Timern. Läuft ein Timer länger als
2 Stunden, bekommt der Besitzer einmalig eine E-Mail („Läuft noch ein
Timer?” mit Job, Startzeit und bisheriger Dauer) — auch bei geschlossenem
Browser/Tab, weil serverseitig im Minuten-Cleanup geprüft (Dedup pro Timer via
notification_log). Der Tenant-Auto-Stopp (Default 8 h) bleibt das harte Sicherheitsnetz. Testbarer KernremindLongRunningTimer+ Integrationstest.
Phase C (Web Push für „auch bei geschlossener Seite ohne E-Mail”) folgt als eigene Mini-Spec.
[2.4.3] — 2026-07-11
Fixed
- Timer-Seite und globaler Dock jetzt synchron. Stoppte man einen Timer über
den Dock unten rechts, blieb er in der „Läuft gerade”-Liste der Timer-Seite
scheinbar weiter laufen (verschwand erst nach Reload). Beide Stellen reagieren
jetzt auf ein gemeinsames
timers-changed-Event — Start und Stopp an einer Stelle aktualisieren die andere sofort.
[2.4.2] — 2026-07-11
Multi-Timer-Dock + dynamische Toast-Position (Phase A).
Changed
- Mehrere laufende Timer werden jetzt als gestapelter Dock unten rechts angezeigt (jeder mit eigener Dauer + Stopp-Button) — kein Überlagern mehr.
- Toasts schweben dynamisch über dem Timer-Dock (statt oben rechts): der Dock meldet seine Höhe als CSS-Variable, die Toast-Leiste rutscht entsprechend nach oben — auch wenn Timer dazukommen oder wegfallen. Ohne laufenden Timer sitzen die Toasts wie gewohnt unten rechts.
[2.4.1] — 2026-07-11
Position-Politur (Phase A).
Changed
- Läuft-Timer-Chip nach unten rechts verschoben (statt oben rechts) — dort kollidierte er mit den Kopf-Buttons und dem „?”-Icon.
- Toasts nach oben rechts verschoben, damit sie sich mit dem Timer-Chip (unten rechts) nicht überlagern.
[2.4.0] — 2026-07-11
Timer-Bewusstsein & Auto-Stopp (Phase A). Reaktion auf den Test-Betrieb: Hintergrund-Timer liefen teils 24 h weiter. Jetzt gibt es einen Tenant-weiten Auto-Stopp-Default, eine überall sichtbare Läuft-Anzeige und In-App-Erinnerungen.
Added
- Standard-Auto-Stopp pro Mandant. Neues Tenant-Setting „Max. Timer-Laufzeit (Standard)” (Default 8 h). Greift als Fallback im Timer-Cleanup, wenn ein Nutzer keine eigene Maximal-Laufzeit gesetzt hat — so kann kein Timer mehr endlos (24 h) laufen. Migration 046. Persönliche Einstellung geht weiterhin vor.
- Globaler Läuft-Timer-Chip oben rechts auf jeder Seite: live mitzählende Dauer, Job/Account und ein Stopp-Button. Zeigt bei mehreren Timern „+N”.
- Tab-Titel-Ticker: solange ein Timer läuft, steht die Dauer im Browser-Tab
(
⏱ 2:14 · KI QA) — passiver Dauer-Blick auch im Hintergrund-Tab. - Browser-Erinnerung (Notifications API, opt-in per 🔔): „Timer läuft noch — aktiv?” ab 2 h Dauerlauf, danach stündlich (solange der Tab offen ist).
Phase B (Server-Mail-Reminder) und Phase C (Web Push für geschlossene Seite) folgen separat.
[2.3.7] — 2026-07-11
Zwei UI-Fixes aus dem Test-Betrieb.
Fixed
- Textbausteine-Dropdown wurde im Modal abgeschnitten. Das Auswahl-Dropdown
(📋) öffnete vom rechts sitzenden Button nach rechts über den Modal-Rand hinaus
und wurde vom
overflow:hiddendes Modals gekappt. Es öffnet jetzt nach links ins Modal hinein und ist vollständig sichtbar (alle Erfassungs-Masken). - Literaler „{{shortcut}}” im Job-Suchfeld. Der Job-Picker gab den Platzhalter ohne den Tastenkürzel-Parameter weiter, sodass die i18n-Variable unaufgelöst als Text erschien. Jetzt wird das erkannte Kürzel eingesetzt.
[2.3.6] — 2026-07-11
PO-Budget-Split beim Bearbeiten (Phase 2). Bisher wurde beim Bearbeiten einer
Buchung (PUT /entries/{id}, alle Edit-Oberflächen) das PO-Budget ignoriert:
verlängerte man eine Buchung über das Budget einer Bestellnummer hinaus, wurde die
PO still überbucht. Jetzt greift derselbe Split-Mechanismus wie beim Anlegen.
Fixed
- Bearbeiten überschreitet PO-Budget → Split statt stiller Überbuchung. Sprengt eine Änderung (Zeit, Gebucht, Abrechenbar) das Budget der Bestellnummer, wird die Buchung automatisch geteilt: der passende Teil bleibt auf der PO, der Überlauf wandert auf die Nachfolge-PO oder wird als „ohne PO” markiert. Inklusive Budget-Erschöpfungs-Prüfung und Benachrichtigung. Gilt auch für die Sammel-Bearbeitung.
Details (intern)
- Anker = die bestehende PO des Eintrags; nur bei Job-Wechsel/ohne-PO wird die aktive PO frisch aufgelöst.
- Eigen-Verbrauch wird ausgeklammert — ein Eintrag, der schon auf der PO liegt, zählt beim Neurechnen nicht doppelt (sonst Fehl-Split).
- Ein Eintrag auf einer erschöpften PO bleibt darauf, solange er hineinpasst; kein Auto-Reopen und kein Umschichten (bewusst schlank gehalten).
- Re-Split nur bei budgetrelevanter Änderung; reine Notiz-/User-Edits bleiben ein schlankes Update.
- Sauber in einer Transaktion mit
FOR UPDATE(race-frei); Sammel-Bearbeitung verarbeitet sequenziell (Kreuz-Budget korrekt, Teilerfolg bleibt erhalten). - Zentraler Helfer
resplitEntryInTx; 3 neue geld-kritische Integrationstests. - Frontend: Inline-Editoren in „Meine Buchungen”/„Auswertungen” laden nach dem Speichern neu, damit Split-Überlauf-Einträge sofort erscheinen.
[2.3.5] — 2026-07-10
Zeitänderung berechnet „Gebucht”/„Abrechenbar” neu (statt zu schützen). Verhaltens-Korrektur zu v2.3.4: Da eine Änderung von Start/Ende die Grundlage der Buchung ändert, werden „Gebucht” und „Abrechenbar” jetzt immer frisch aus der neuen Zeitspanne abgeleitet — auch zuvor manuell/abweichend gesetzte Werte werden verworfen. Ein sichtbarer Hinweis (Marken-Pink) erklärt die Neuberechnung. Wer danach wieder einen Sonderwert braucht, setzt ihn erneut.
Unverändert: eine Änderung nur an „Abrechenbar” (ohne Start/Ende anzufassen) bleibt bestehen und setzt „Gebucht” nicht zurück. Gilt an allen vier Edit-Oberflächen (Modal + Inline in „Meine Buchungen”/„Auswertungen”/JobPage).
[2.3.4] — 2026-07-10
Einheitliche Buchungs-Bearbeitung: „Gebucht”/„Abrechenbar” rechnen jetzt überall gleich. Phase 1 der Edit-Konsistenz (Phase 2 = PO-Split beim Bearbeiten folgt separat).
Fixed
- Start/Ende ändern berechnete „Gebucht”/„Abrechenbar” nicht neu. An allen Edit-Oberflächen (großes Modal + Inline-Editoren in „Meine Buchungen”, „Auswertungen” und JobPage) wird „Gebucht” jetzt live aus der neuen Zeitspanne berechnet (mit Tenant-Rundung, symmetrisch für Start und Ende). „Abrechenbar” folgt gekoppelt, solange es „Gebucht” entspricht; ist es bewusst abweichend gesetzt, bleibt es unangetastet.
- Reiner „Abrechenbar”-Edit in „Auswertungen” war unsichtbar. Die Auswertungen-Tabelle hatte gar keine „Abrechenbar”-Spalte — der Wert wurde gespeichert, aber nirgends angezeigt. Spalte nachgerüstet (mit Warnfarbe, wenn Abrechenbar < Gebucht).
- Schutz vor stillem Überschreiben von „Gebucht”: Buchungen mit bewusst abweichendem „Gebucht” (≠ Zeitspanne) werden beim Speichern nicht mehr versehentlich auf die Spanne zurückgesetzt.
Changed
- „Abrechenbar” bei Festpreis/Pauschale/nicht-abrechenbar-Jobs deaktiviert — mit deutlichem Hinweis in Marken-Pink. Das Backend erzwingt bei diesen Job-Typen ohnehin den Wert (= Gebucht bzw. 0); das Feld tut nicht mehr so, als wäre es editierbar.
- Zentrale, getestete Rechenlogik (
lib/bookingCalc.ts) als eine Quelle für alle Edit-Oberflächen. Additive Backend-Felder:billing_typein/jobs,rounding_minutesin/tenant.
[2.3.3] — 2026-07-10
Globaler Zugang zu Handbuch + API. Im Sidebar-Footer (über der
Versionszeile) stehen jetzt zwei dezente Links „Handbuch” (sprachabhängig
DE/EN → Online-Handbuch) und „API” (→ api.daagwerk.de/docs, Swagger),
beide in neuem Tab. Auf jeder Seite und für jede Rolle sichtbar — ergänzt
die (admin-only) „Hilfe & Ressourcen”-Karte aus v2.3.1.
[2.3.2] — 2026-07-10
Politur des Handbuch-„?”-Icons. Das kontextuelle Handbuch-Symbol
(DocsLink) ist jetzt ein gefüllter Marken-Pink-Vollkreis (#F72A7C) mit
kräftigem weißem Fragezeichen (30 px) statt des vorherigen dünnen Outline-„?↗”.
Damit ist es deutlich sichtbarer und klar abgesetzt vom grauen Tooltip-
HelpIcon. Rein visuell, keine Verhaltensänderung.
[2.3.1] — 2026-07-10
Bugfixes aus dem Test-Betrieb + kontextuelle Handbuch-Hilfe.
Fixed
- CSV-Zeitbuchungs-Import: „User not found” bei jeder Zeile. Die
User-Auflösung im Import filterte auf eine Spalte
users.active, die es nicht gibt (Nutzer-Status liegt inusers.status, Enum-Wertactive). Der PostgreSQL-Fehlercolumn "active" does not existwurde im Code verschluckt → jede Zeile scheiterte mit „User not found”, obwohl die Nutzer existierten und aktiv waren. Query in Vorschau und Import aufstatus='active'korrigiert und den E-Mail-Vergleich case-robust gemacht (LOWER(email)). Konsistent mit den übrigen User-Lookups (Auth, Admin, Passwort-Reset), die längststatus='active'nutzen. - Eigene Super-Admin-Zeile war nicht bearbeitbar. In der Nutzerverwaltung
waren Super-Admin-Zeilen komplett vom Bearbeiten ausgeschlossen (Frontend
canEdit = !isSuperAdmin; BackendWHERE role != 'super_admin'+ nicht erlaubte Rolle beim Update). Damit kam ein Super-Admin nicht an die eigene E-Mail/Name. Jetzt darf ausschließlich der Super-Admin selbst die eigene Zeile bearbeiten (andere Admins bekommen403); die Rolle wird dabei fix aufsuper_admingepinnt (kein versehentliches Degradieren, keine Rechte-Eskalation für normale Admins).
Changed
- Import-Vorschau: Spaltenlabel „Nutzer” → „Nutzer (E-Mail)” bzw.
„User (e-mail)” — die Spalte enthält die E-Mail-Adresse, passend zur
CSV-Spalte
user_email.
Added
- Kontextuelle Handbuch-Verlinkung im Frontend. Jede Hauptseite hat oben
rechts ein „?↗“-Symbol, das direkt das passende Kapitel des Online-Handbuchs
(daagwerk.de/dokumentation bzw. daagwerk.com/en/documentation) in einem neuen
Tab öffnet; bei mehrdeutigen Seiten das Inhaltsverzeichnis. Sprachabhängig
(DE/EN) über eine zentrale Zuordnung (
lib/docs.ts). Bewusst abgesetzt vom bestehenden Tooltip-Hilfe-Icon durch den Außenpfeil (führt nach draußen). - Sammelseite „Hilfe & Ressourcen” in Profil → Unternehmens-Einstellungen (Manager/Admin): komplettes Handbuch, FAQ und „Für Entwickler (API/Swagger)” gebündelt.
[2.3.0] — 2026-07-04
Public-Facing: vollständige API-Referenz, Produkt-Doku und Online-Changelog.
Neu
- Vollständige interaktive API-Referenz (Swagger). Alle Endpunkte der
daagwerk-API sind jetzt dokumentiert (145 Operationen, zuvor 111) – mit
ausführlicher Beschreibung, Parametern, Antworttypen und Fehlercodes. Live unter
api.daagwerk.de/docs.
Vorbereitung (separates Website-Projekt)
- Produkt-Dokumentation (15 nummerierte Endnutzer-Kapitel) und ein
web-fertiger Online-Changelog als saubere Markdown-Artefakte unter
website/– zur Einbindung in daagwerk.com.
[2.2.0] — 2026-07-04
Großes Sammel-Release: der ganze v2.0-Zyklus (HTML/PDF-Plattform), das
Billing-Fundament (v2.1) und die PO-/Budget-Benachrichtigungen (v2.2) — gemeinsam
auf main entwickelt und in einem Rutsch getaggt.
v2.0 — HTML/PDF-Plattform + Plans + Branding + Multi-Currency
SR1 — Foundation & Premium-PDF (feat(v2.0-sr1):, Render via Gotenberg verifiziert)
- Premium-PDF scharf:
entries.pdfrendert das Premium-Editorial (customer_page.html.tmpl), nur echte Daten. Plan-System scharf (requireFeature(FeaturePDFExport)), Gotenberg-Pipeline. - Tenant-Branding (
FeaturePDFBranding): Logo (base64-Data-URI) + Primärfarbe (--primary-Override) + Briefkopf (Cover) + Footer (jede Seite via@page);GET/PUT /tenant/branding+ Logo-Upload/Delete; Branding-Reiter im Profil. - Multi-Currency: Per-Account-Währung serverseitig gegated (Business+), „Summen pro Währung”-Tabelle im PDF (keine Umrechnung).
- Bulk + Role-Gating:
/export/entries-bulk.zip(Business+, ein PDF je Account); Umsatz-Tabelle nur für Manager+.
SR2 — Dynamic Columns (feat(v2.0-sr2):)
- Konfigurierbare PDF-Export-Spalten: zentrale Registry (15 Spalten) mit
serverseitigem Role-Gating (
ratenur Manager+, still gedroppt); die Premium-PDF rendert die Buchungstabelle dynamisch über die gewählten Spalten (?preset=/?columns=). - Presets: 3 Built-in (Kundenrechnung/Intern/Compliance) + benutzerdefinierte
Presets (Migration 045
tenant_export_presets,/me/export-presets, privat oder tenant-weit geteilt). - PDF-Modal: Preset-Dropdown + Spalten-Checkboxen + Drag-Reorder + „Als Preset speichern” + Live-Vorschau der ersten 3 Buchungen.
- Cleanup: alter fpdf-Handler (~345 Z.) + fpdf-exklusive Helfer +
go-pdf/fpdf-Dependency entfernt.
v2.1 — Billing-Model-Fundament
- Neuer Abrechnungs-Typ Pauschale/Fee (
recurring_fee) neben stündlich, Festpreis, nicht-abrechenbar; im Tool setzbar (Betrag/Rhythmus/Laufzeit). - Arrangement-Resolver: Stundensatz feldweise Account→Project→Job; Modell +
Beträge als Einheit. Umsatz je Modell (Festpreis/Fee zählen einmal, keine
Blanket-
×Rate-Fehlrechnung; PDF-„Summen pro Währung” rechnen korrekt). - Migrationen 040–042 (recurring_fee-Enum, fee-Felder, Altlast-Bereinigung).
v2.2 — PO-/Budget-Empfänger-Benachrichtigung
- Interne Account-Rollen (Account-Manager / Customer-Success / Kundenkontakt, freie Name/E-Mail-Felder). Operative Alerts gehen nur an AM+CS — nicht mehr fälschlich an den externen Nachweis-Empfänger.
- Vier Ereignisse: PO erschöpft, PO läuft ab, Budget-Schwelle (80/100 %,
PO + Projekt), verwaiste Buchungen (wöchentlicher Digest). Dedup je Ereignis über
notification_log. Periodischer EndpointPOST /internal/notification-run(systemd-Timer). - Migrationen 043–044 (Account-Rollen,
notification_log).
Migrationen
040–045.
[1.13.3] — 2026-05-31
HelpIcon-System (Sparring Session 20).
Erstes Iterations-Release: kontextuelle Hilfe-Tooltips, eingebaut an 6 strategischen Stellen. Tester können künftig bei Unklarheiten direkt am Ort des Geschehens nachschauen, ohne in eine separate Doku zu springen.
<HelpIcon helpKey> Komponente:
- Neue Datei
components/ui/HelpIcon.tsx - Nutzt
@radix-ui/react-tooltip(war schon Dependency, A11y kommt out-of-the-box: ARIA, Tastatur-Navigation, Focus-Management) Tooltip.Providerglobal im Root (App.tsx)- Props:
helpKey, optionalsize(Default 14px),side(Default'top') - Mehrzeilige Texte:
\nim i18n-String wird zu separaten Absätzen - Auf Hover und Tastatur-Focus erscheint der Tooltip (200 ms Delay)
- Dunkler Background (
#383838), helle Schrift, dezenter Schatten - Hover-Animation: graues
?wird zu lila bei Hover
i18n-Registry (help.* Namespace):
- 8 initiale Einträge in
de.json+en.json:help.po.tab— Was sind Bestellnummern + Auto-Switch-Erklärunghelp.po.orphan— Warum gibt es verwaiste Buchungenhelp.table_settings.po_column— Tri-State Spalten-Modihelp.blank_timer.what— Schnell-Timer + Inbox-Workflowhelp.inbox.privacy— Inbox ist privat, auch für Managerhelp.sidebar.quick_timer— Was macht der Schnell-Timer-Buttonhelp.assign_job.what— Was passiert beim Job-Zuordnen (Rounding, Job-Typ-Billing, PO-Resolve)help.po.budget_type— Stunden- vs. Geld-Budget für POs
Initial platziert in 6 Spots:
- JobPage „Bestellnummern”-Header (
help.po.tab) - TableColumnSettings Dropdown-Header (
help.table_settings.po_column) - BlankTimerModal Header (
help.blank_timer.what) - Inbox-Page Header (
help.inbox.privacy) - AssignJobModal Header (
help.assign_job.what) - NewPurchaseOrderModal Budget-Type-Label (
help.po.budget_type)
Bewusst NICHT platziert:
- POCell „ohne PO”-Pille: hat schon einen Native-HTML-Tooltip, ein zusätzliches HelpIcon würde Tabellen-Zellen überfüllen
- Sidebar Schnell-Timer-Button: das HelpIcon im Button selbst würde den Klick-Handler fummeln; das ist der erste Schritt — weitere Spots kommen in nachfolgenden Iterationen.
PageHeader-Refactor (Mini-Breaking):
title: string→title: React.ReactNodedamit die Inbox-Page ein HelpIcon neben dem Titel rendern kann.
[1.13.2] — 2026-05-31
Security & Tenant-Scope-Sweep + Code-Hygiene.
Dritte Runde des Code-Review-Refactorings — schließt eine latente IDOR- Falle, fügt fehlende DB-Indizes hinzu, und ersetzt 25+ kopierte Roles- Checks durch zentrale Helper. Keine User-sichtbare Funktionalität, aber das Fundament wird vor v2.0 PDF nochmal um eine Schicht sicherer.
resolveRecipientForTimeEntry mit Tenant-Scope (Befund #4 — KRITISCH latent):
- Die 5-stufige Empfänger-Cascade-Funktion (PO → Job → Project →
Account → Tenant) hatte ein WHERE
te.id = $1ohnetenant_id-Filter. Aktuell Dead Code — wäre aber SOFORT ein theoretischer IDOR-Pfad geworden, sobald v2.0-PDF die Funktion aufgerufen hätte: bei bekannter time_entry-ID hätte ein Aufrufer Empfänger-Daten anderer Tenants extrahieren können. - Funktion behalten (gut gebaut für v2.0 PDF: ein Query mit allen LEFT
JOINs, kein N+1), aber
tenantIDals ZWINGENDEN zweiten Parameter ergänzt —WHERE te.id = $1 AND te.tenant_id = $2.
Migration 039 — FK-Indizes (Befund #12):
- Sechs Foreign-Key-Spalten hatten bisher keinen Index:
accounts.recipient_user_id,projects.recipient_user_id,jobs.recipient_user_id,purchase_orders.recipient_user_id,purchase_orders.created_by,purchase_order_history.changed_by - User-Löschung triggerte Seq-Scans über die referenzierenden Tabellen. Aktuell unkritisch (< 10k Buchungen pro Tenant), wird aber Pain bei großen Tenants.
- Partial-Indizes mit
WHERE x IS NOT NULL— kein Storage-Overhead für die meist leeren Spalten.
Roles-Helper-Migration (Befund #19):
- Neue Helper in
auth.go:isManagerPlus(c)— manager, admin, super_adminisAdminPlus(c)— admin, super_adminisSuperAdmin(c)— nur super_adminisBucher(c)— nur bucher- Alle nil-tolerant (
c == nil→ false)
- 0 rohe
claims.Role == "..."-Checks mehr im gesamten Backend. ~30 Aufrufer migriert über 14 Handler-Dateien. - Wenn das Role-Modell sich künftig erweitert (neue Rolle, andere Hierarchie), muss nur EINE Stelle geändert werden statt 30.
Dead-Code-Sweep (Befund #17):
_ = durationSecsinhandlers_entries.goentfernt — Variable inline berechnet, Marker war überflüssig._ = accountIDinpo_lifecycle.go(Backfill-Pfad) entfernt — Spalte gar nicht erst gescannt, Logik ist topologisch implizit._ = trinpdf_premium.goentfernt — der defensive Marker war irreführend (behauptete „weiter unten verwendet”, was stimmt) und wurde durch sauberes Lassen dertr-Zuweisung ersetzt.
Was Befund #7 angeht (assignJob Tenant-Scope + Scan-Errors):
- War bereits in v1.12.1 mit erledigt — die
billing_type-Query inhandlers_inbox.go assignJobbekam dort dentenant_id-Filter. Daher nicht erneut angefasst.
[1.13.1] — 2026-05-31
Frontend-Hygiene: Modal-Shell + i18n + react-query-Entscheidung.
Zweite Runde des Code-Review-Refactorings — alles im Frontend. Keine User-sichtbare Funktionalitätsänderung, aber Code wird konsistenter und englischsprachige Tester sehen jetzt durchgängig Englisch.
Modal-Shell-Konsolidierung (Befund #9):
- Neue zentrale
<Modal>-Primitive untercomponents/ui/Modal.tsx— Overlay + ESC + Click-outside + Body-Scroll-Lock + Auto-Focus in einer Komponente. Props:isOpen,onClose,title,subtitle,headerLeft(für custom Icon-Boxen wie im Schnell-Timer-Modal),maxWidth,disableEscClose,disableClickOutsideClose. - Alle 9 Modals migriert:
NewTimerModal,NewBookingModal,BlankTimerModal,AssignJobModal,EntryEditModal,BulkEditModal,NewPurchaseOrderModal,PurchaseOrderDetailModal,POBackfillModal. - UX-Bug-Fix dabei:
EntryEditModalundBulkEditModalhatten zuvor KEIN ESC-Handling — jetzt schließt ESC konsistent in allen 9 Modals.
i18n-Konsolidierung (Befund #8):
- 44 inline
defaultValue-Strings aus 9 Komponenten inde.jsonunden.jsonmigriert (~30 neue Keys; einige existierten bereits) - Englische Übersetzungen für alle neu migrierten Keys ergänzt — Englisch-sprachige Tester sehen jetzt keine deutschen Defaults mehr
- 3 verbliebene
defaultValue-Aufrufe sind bewusst dynamisch (balance.holiday.${key},balance.absence.${kind},import.col.${col}— Fallback auf den Variablen-Wert wenn kein Translate-Eintrag da ist)
react-query rausgeworfen (Befund #11):
@tanstack/react-querywar als Dependency installiert aber nirgends genutzt — die App hat keinenQueryClientProviderim Root. Der erste Versuch in v1.11.1 mituseQueryist beim Mount abgestürzt und ist seit Hotfix1eef284auf Modul-Level-Cache + Subscriber-Pattern umgestellt. Paket entfernt → kleinere Bundle, weniger Verwirrung für Reviewer.- Wenn künftig Caching gewünscht ist, kommt das Paket bewusst rein mit Provider-Setup.
Doku-Lüge korrigiert (Befund #11):
- CHANGELOG-Eintrag zu v1.11.1 sagte fälschlich „react-query + optimistic update”. Tatsächlich war es vom ersten Live-Test an ein Modul-Level-Cache. Korrigiert mit Hinweis auf den Hotfix-Commit.
[1.13.0] — 2026-05-31
Refactoring-Release: Geld-Pfade konsolidiert + Test-Infrastruktur.
Aus dem unabhängigen Code-Review nach v1.12.0 kam als HOCH priorisierter Refactoring-Auftrag: das in 6 Pfaden duplizierte Booking-Insert-Pattern sauber konsolidieren und mit Tests absichern, bevor v2.0 PDF die nächste Welle an Features bringt. v1.13.0 erledigt genau das — keine User-sichtbare Funktionalität, aber das Fundament für künftige Features ist jetzt belastbar.
Neues booking_insert.go mit InsertTimeEntryWithPOSplit():
- Zentraler Eintrittspunkt für 5 von 6 Booking-Insert-Pfaden
- Eine Funktion macht: Cross-Day-Split + Tenant-Rounding + Job-Typ-Billable + PO-Resolution + Budget-Split + Insert + Exhaustion-Check + Notification
- Öffnet eigene Tx mit
SELECT … FOR UPDATEauf der aktiven PO-Row → vollständiger Race-Fix für Code-Review-Befund #1 - Akzeptiert optionalen
RoundingOverride(Importer schickt vorgerundet) undBillableSecondsRequest(Manager-Wunsch für Hourly-Jobs)
5 Aufrufer migriert (~260 Zeilen dupliziertem Code eliminiert):
handlers_planned.go activate— −55 Zeilenhandlers_timer_cleanup.go convertTimer(non-blank) — −50 Zeilenhandlers_timers.go stopTimer(non-blank) — −65 Zeilenhandlers_timers.go createEntry— −55 Zeilenhandlers_time_import.go importRow— −35 Zeilen
Bewusst NICHT migriert:
- Blanko-Fast-Paths in
stopTimer/convertTimer(kein PO, kein Billable, eigene 10-Zeilen-Logik) handlers_inbox.go assignJob— UPDATE-existing-Pattern statt INSERT-all, strukturell anders. Logik aus v1.12.1 bereits korrekt.
Bonus-Fix beim Konsolidieren entdeckt:
- Re-Resolve-Bug: nach Budget-Split-Segment 0 (das die alte PO erschöpft
hat) hat der Code naiv
resolveActivePOForJobfür Segment 1 aufgerufen. Da die alte PO im selben Tx nochstatus='active'ist (Flip aufexhaustedläuft erst nach Commit), bekam Segment 1 wieder die alte PO zugewiesen. Resultat: Budget-Split war nur formal — Buchung landete effektiv komplett auf der überlaufenden PO. Dieser Bug existierte in ALLEN 6 alten Pfaden (auch v1.12.1-Codestand). Jetzt: der Helper trackt intouchedPOswelche POs in diesem Aufruf bereits benutzt wurden, und Re-Resolve skippt eine PO die schon erschöpft wurde. Race-Test mit 10 Goroutinen auf 2h-Budget liefert jetzt exakt 2h auf PO + Rest NULL.
Erste Go-Tests im Repo (~37 Tests, alle grün):
- Unit:
cross_day_split_test.go(Cross-Day, DST-Sprünge, Edge Cases) +rounding_test.go(15/30/1-Min-Rundung, Boundary-Werte) - Integration gegen
daagwerk_test-DB:billing_helpers_test.go(Job-Typ-Logik)po_lifecycle_test.go(resolveActivePOForJob, computeBudgetSplit, checkAndApplyBudgetExhaustion mit 10-Goroutine-Race-Test)booking_insert_test.go(Helper End-to-End mit 5-Goroutine-Race-Test der Befund #1 beweist)
- Neue Test-Infrastruktur:
internal/testutil/mit DB-Setup, Migrations- Loader, Fixtures (NewFixture,AddPOHours,InsertBookedSeconds) Makefile-Targets:make test-db-create,make test-db-reset,make test,make test-short- Test-DB läuft im bestehenden Dev-Postgres-Container (Datenbank
daagwerk_test) — kein zweiter Container nötig
Architektur-Notizen:
- Helper akzeptiert
*pgxpool.Pool, öffnet eigene Tx intern. Caller müssen selbst keine Tx wrappen. Ausnahme: Cleanup hat seine eigene Tx für den Blanko-Pfad behalten — Job-Pfad nutzt den Helper. FOR UPDATEauf der PO-Row ist die Wurzel des Race-Fixes. Postgres serialisiert konkurrierende Tx auf derselben Row. Nach G1’s Commit sieht G2 die aktuelletime_entries-Sicht und splitter korrekt.- Die
touchedPOs-Map im Helper deckt den (zuvor unentdeckten) Re-Resolve-Bug ab — Bonusgewinn durch das Refactoring.
Was NICHT in v1.13.0 ist (kommt mit v1.13.1):
- Modal-Shell-Konsolidierung (~9 Modals mit kopierter ESC/Click-Outside- Logik) — Code-Review Befund #9
- i18n
defaultValueinline → JSON-Migration (45 Vorkommen) — Befund #8 - react-query-Entscheidung: Provider einbauen oder Paket raus — Befund #11
- CHANGELOG v1.11.1 Doku-Lüge korrigieren — Befund #11
[1.12.1] — 2026-05-31
Hotfix: vier KRITISCHE Bugs aus dem unabhängigen Code-Review.
Aus dem Code-Audit nach v1.12.0 kamen vier Findings die nicht „nur” Refactoring sind, sondern in Produktion still falsche Daten erzeugen. Vor weiteren Refactorings (geplant in v1.13.x) zuerst die Bugs raus.
Befund #2 — Planned-Aktivierung vergaß Budget-Exhaustion-Check
handlers_planned.go activaterief nach demINSERTvontime_entrieskeincheckAndApplyBudgetExhaustionauf. Eine geplante Buchung konnte eine PO erschöpfen ohne dassstatus='exhausted'gesetzt oder die Notification an den PO-Empfänger rausging.plannedHandlerbekommt einen Mailer für die Notification-Cascade.
Befund #3 — Auto-Stop Cleanup macht jetzt Cross-Day-Split
handlers_timer_cleanup.go convertTimerhat bisher die gesamte Dauer eines über-Nacht-laufenden Timers auf den Start-Tag gebucht. Das ist genau der Pfad, der nachts greift wenn jemand den Timer vergessen hat — typischer Fall: Timer 18:00 gestartet, Max-Dauer 8h, Auto-Stop 02:00 am Folgetag.- Folge im Auslastungs-Tab: 25h+ Werte für den Vortag.
- Jetzt:
splitAtMidnightgreift auch im Cleanup-Pfad. Sowohl der Blanko-Fast-Path als auch der reguläre Job-Pfad bekommen die Cross-Day-Logik. Pro Cross-Day-Segment: eigenes Rounding, eigene Budget-Split-Berechnung, eigene PO-Resolution. Touched POs werden nach Commit alle einzeln auf Exhaustion geprüft.
Befund #4/#15 — assignJob macht jetzt Budget-Split
handlers_inbox.go assignJobhat bisher einen einfachenUPDATEmit Job + PO gemacht. Sprengt der zugeordnete Job die PO, wurde die Buchung trotzdem komplett auf die alte PO geschrieben — keine Aufteilung in „erstes Segment alte PO + Restsegment Nachfolge/NULL”.- Jetzt:
computeBudgetSplitläuft auch hier. Erstes Segment landet perUPDATEim existierenden Blanko-Eintrag (ID bleibt erhalten), Folgesegmente perINSERTals neue Einträge. Nachfolge-PO wird zwischen Segmenten neu resolved. - Plus Tenant-Scope-Fix in der
billing_type-Abfrage (Befund #7): vorher gab es zwei separate Queries, die zweite ohnetenant_id-Filter. Theoretischer IDOR — jetzt eine kombinierte Query mit Tenant-Scope.
Befund #16 — checkAndApplyBudgetExhaustion hat Race-Condition gefixt
- Vorher: snapshot → bedingter UPDATE (mit
WHERE status='active') → History-INSERT → Mail. Bei parallelen Booking-Inserts haben mehrere Aufrufer alle den status-Wechsel beobachtet, alle ihre History-Zeilen geschrieben und alle Mails verschickt. - Jetzt: nur wenn
tag.RowsAffected() == 1(= unser UPDATE hat die Zeile wirklich vonactiveaufexhaustedgeflippt) folgen History + Mail. Andere parallele Aufrufer fallen still aus. - Postgres-MVCC garantiert dass exakt eine Tx den Switch gewinnt.
Was Hotfix NICHT abdeckt (kommt in v1.13.0):
- Befund #1 (Race-Condition in
resolveActivePOForJob) — der vollständige Fix braucht eine Tx-Klammer um Resolve+Insert in allen 6 Booking-Pfaden. Macht erst Sinn mit dem geplanteninsertTimeEntryWithPOSplit-Helper, sonst dupliziert man die Tx-Logik 6x. Mitigation in #16 fängt zumindest die Mehrfach-Notifications ab. - Die anderen v1.13-Befunde (Modal-Shell, react-query-Entscheidung, i18n-Konsolidierung, Doku-Drift, Tests) — Mittwoch.
Doku-Korrektur:
- CHANGELOG v1.11.1 hatte fälschlicherweise „react-query + optimistic
update” für
useViewPreferencesbehauptet. Die echte Implementierung nutzt einen Modul-Level-Cache + Subscriber-Pattern (bewusst, weil die App keinenQueryClientProviderim Root hat). Korrektur kommt mit v1.13.1.
[1.12.0] — 2026-05-31
Blanko-Buchungen / Speed-Timer + Inbox.
Neue Bucher-UX: einfach loslegen, klassifizieren später. Pro User ein „Schnellstart”-Button in der Sidebar (#F7C619, daagwerk-Amber). Klick öffnet ein minimales Modal mit optionalem Notizfeld — kein Job-Picker, kein Project-Picker. Beim Klick auf „Jetzt starten” läuft sofort ein Blanko- Timer. Stopp → Buchung landet in der Inbox mit Badge-Zahl in der Sidebar. User klickt einen Eintrag an → AssignJobModal mit JobPicker (wiederverwendet aus NewTimerModal). Speichern → Job + PO + Billable wird gesetzt, die Buchung wandert in die regulären Reports.
Backend:
- Migration 038:
time_entries.job_id+active_timers.job_idnullable. Partieller Unique-Indexactive_timers_user_blank_uniqueerzwingt max. einen Blanko-Timer pro User gleichzeitig. Indextime_entries_inbox_idxfür die Inbox-Abfrage. - Neuer Endpoint
POST /timers/start-blank— startet Blanko-Timer mit optionaler Notiz. Auto-Stop + Start-Snap aus Tenant-Settings greifen identisch zum normalen Timer. - Neuer Endpoint
GET /inbox— Liste eigener Blanko-Buchungen (strict user-scoped, Manager sehen NICHT die fremden — DSGVO). - Neuer Endpoint
GET /inbox/count— schmaler Count für Sidebar-Badge. - Neuer Endpoint
POST /entries/{id}/assign-job— zuordnen. Berechnetbooked_secondsmit Tenant-Rounding neu (Blanko hatte rohe Sekunden), wendet Job-Typ-Billing-Logik an, resolved aktive PO, prüft Budget-Erschöpfung. stopTimerund Auto-Stop (handlers_timer_cleanup.go) bekommen einen Blanko-Fast-Path: kein Rounding (rohe Sekunden), kein Billable, kein PO-Split — alles wird beim Assign nachgerechnet.listTimersvon INNER JOIN auf LEFT JOIN umgestellt, damit laufende Blanko-Timer sichtbar bleiben. Antwort enthält neues Flagis_blank.ActiveTimer.JobIDbleibtstring, leerer String markiert Blanko (vermeidet Breaking Change in 30+ Aufrufstellen).
Frontend:
BlankTimerModalmit optionalem Notizfeld, Enter-Submit, ESC-Close, Auto-Focus. Trigger via Window-Eventdaagwerk:open-blank-timer— von überall in der App aus aktivierbar.- Dritter Sidebar-Button „Schnellstart” in #F7C619 mit ⚡-Symbol, positioniert unter „Timer starten” (lime) und „Buchung eintragen” (lila).
- Neuer Sidebar-Nav-Eintrag „Inbox Blanko Buchungen” unter „Erfassen” (zwischen Timer und Meine Buchungen) mit dynamischer Badge-Zahl. Badge in #F7C619, polled jede Minute + Event-getriggert bei Timer-Start/Entry-Created/Inbox-Changed.
- Neue Seite
/inbox(pages/Inbox.tsx) — Tabelle mit Start/Ende/Dauer/ Notiz pro Blanko-Buchung. Klick auf Zeile öffnetAssignJobModal. „Verwerfen”-Button storniert die Buchung (status=‘cancelled’) — taucht dann auch nicht mehr in der Inbox auf. AssignJobModalzeigt Read-Only Start/Ende/Dauer der Blanko-Buchung oben, dann JobPicker + Notiz (übernommen aus Blanko-Notiz, editierbar).- Timer-Seite zeigt laufenden Blanko-Timer als kleine Amber-Pill statt Account/Projekt/Job — Symbol ⚡ + „Schnellstart (Blanko)”.
- i18n de+en:
nav.inboxunddefaultValue-Inline-Strings in allen neuen Komponenten (kein blocker für Tester).
Architektur-Entscheidungen:
- Datenmodell A (mit User abgestimmt):
time_entries.job_idwird nullable, KEINE separateinbox_entries-Tabelle. Vorteil: Stop-Logik bleibt eine, Restore/Backup ohne Sonderfall. Bestehende Reports/ Dashboard-Joins (JOIN jobs) filtern Blanko-Buchungen stillschweigend raus — gewollt, weil sie bis zur Zuordnung nicht abrechnungsrelevant sind. - Max. 1 Blanko-Timer pro User: partieller Unique-Index. Mehrere parallele Blanko-Timer wären UX-mäßig verwirrend (alle hießen gleich).
- Inbox strict user-scoped: Notizen können persönlich sein („Telefonat Anwalt”). Manager dürfen NICHT die Inbox anderer sehen.
- Verwerfen statt Löschen: Stornieren statt DELETE — bleibt im Audit-Trail, ist mit existierender Status-Logik konsistent.
Offen / kommt mit v1.12.1 oder später:
- iOS / macOS / Watch: Schnellstart + Inbox. Tester nutzen v1.12 erstmal nur Web.
- Bulk-Zuordnung mehrerer Inbox-Einträge in einem Schwung.
- Reminder-Mail „Du hast N unzugeordnete Buchungen seit X Tagen”.
- Inbox-Sortierung nach Datum-Filter (aktuell immer DESC).
[1.11.1] — 2026-05-31
Verwaiste Buchungen sichtbar machen + dynamische Bestellnummer-Spalte.
Nach den ersten Live-Tests mit v1.11.0 fiel auf: bei Budget-Überschreitung gesplittete Buchungen (zweiter Teil ohne PO) und alle Buchungen in einem Job mit historischen POs zeigten denselben Status „Gebucht” wie reguläre Buchungen — der „Verhandlungs-Lücken”-Zustand war für den Manager nicht sichtbar. v1.11.1 löst das ohne neuen Status-Enum-Wert (Status bleibt der Abrechnungs-Lebenszyklus) und führt die PO-Zugehörigkeit als zweite, parallele Dimension visuell ein.
Backend:
- Migration 037:
users.view_preferences JSONB DEFAULT '{}'für persistente Tabellen-Sichteinstellungen pro User handlers_view_preferences.go:GET+PATCH /me/view-preferences(Partial-Merge), strict user-scoped, fehlende Keys werden serverseitig als'auto'interpretiert, unbekannte Werte (z. B. „invalid”) fallen auf'auto'zurück/entries,/reports, JobPage-Overview liefern jetzt pro Buchung:purchase_order_id,purchase_order_number(LEFT JOIN),is_orphaned(computed: NULL po_id UND Job/Projekt hat min. eine PO)- Response-Envelope um
has_purchase_ordersFlag — wird einmal pro Request über die gesamte gefilterte Menge berechnet (EXISTS), damit die Spalte beim Paginieren nicht flackert - JobDetail um
has_purchase_ordersergänzt (Job-Level: Job oder sein Projekt hat min. eine PO)
Frontend Web:
POCell-Komponente unterfeatures/purchase-orders/: drei Zustände- PO zugeordnet → Lila Pill
#9D2AC6mit PO-Nummer, klickbar - Verwaist (NULL pid + Job/Projekt hatte POs) → Amber Pill
#F7C619„ohne PO” mit Tooltip - Sonst (keine PO + Job hat nie POs gehabt) → leer (—)
- PO zugeordnet → Lila Pill
TableColumnSettings-Komponente untercomponents/ui/: kleines Säulen- Icon oben rechts an jeder Tabelle, öffnet Dropdown mit Tri-State Radio „Dynamisch / Immer an / Aus”useViewPreferences-Hook mit Modul-Level-Cache + Subscriber-Pattern (NICHT mit react-query — die App hat keinenQueryClientProvider; die erste Implementierung mituseQueryist beim Mount abgestürzt und wurde direkt im Hotfix1eef284auf plain useState umgestellt; optimistic update + Rollback bei Server-Fehler bleiben erhalten)shouldShowPOColumn()-Helper:on→ immer,off→ nie,auto→ nur wenn Backendhas_purchase_orders=truemeldet- Integration in Entries.tsx, Reports.tsx, JobPage.tsx — jeweils eigene
Preference (
entries.po_column,reports.po_column,job_page.po_column) - Neue Spalte „Bestellnummer” zwischen „Abrechenbar” und „Status”
- i18n de+en: ~12 neue Keys unter
table_settings.*,po.cell.*,entries.col.po,error.po_column_mode_invalid
Architektur-Entscheidung (Sparring Session 21):
- Status-Enum bleibt unverändert —
time_entries.statusist der Abrechnungs-Lebenszyklus (booked→reviewed→invoiced/cancelled) - PO-Zugehörigkeit ist eine parallele Dimension — keine Vermischung über einen neuen Status-Wert wie z. B. „orphaned” (würde zu kombinierten Werten wie „orphaned_invoiced” führen)
- View-Preferences sind erweiterbar — heute nur
po_column, künftig problemlosuser_column,notes_full_width,amount_diffetc. möglich ohne Schema-Änderung - Pro Tabelle einzeln konfigurierbar — JobPage kann „Immer an” sein (Kontext „Job hat POs” ist sowieso bekannt), Entries kann „Dynamisch” bleiben
Offen nach v1.11.1 (kommt mit v2.0 oder eigener Patch):
- Backend-Endpoint für die View-Preferences anderer optionaler Spalten (User-Spalte in Entries, Notiz-Vollbreite, Differenz-Spalte etc.)
- OrphansBanner im Dashboard mit Direkt-Link „N verwaiste Buchungen, jetzt zuordnen”
- PDF Premium Layout 2: PO-Nummer als optionale Spalte / Reihen-Hinweis
- PO-Detail-Modal: CTA „Verwaiste Buchungen anzeigen” (vorgefilterter Reports-View)
[1.11.0] — 2026-05-31
Bestellnummern als First-Class-Budget + Empfänger-Cascade.
Spec: specs/v1.11-purchase-orders.md (Session 20). Reaktion auf Tester-
Praxis: Standard-Aufträge wie Projektmanagement, Beratungsmandate oder
Controlling-Oberhäupter laufen am gleichen Job über Monate/Jahre weiter,
bekommen aber regelmäßig neue Bestellnummern mit neuem Budget. Bisheriges
Modell (PO-Nummer als String an Job/Projekt + Budget-Felder) konnte das
nicht abbilden. Außerdem: Verhandlungs-Lücke (altes Budget auf, neue PO
noch nicht da) ist Realität und musste ohne Workaround machbar sein.
Neu — Datenmodell (Migration 036)
Neue Entität purchase_orders:
- Hängt entweder am Projekt ODER am Job (XOR-Constraint).
- Genau eine aktive PO pro Träger gleichzeitig (partial unique index).
- Budget in Stunden ODER Geld (CHECK: mind. eine Dimension).
- 5 Status-Werte:
active,exhausted,expired,cancelled,awaiting_replacement. - PO-Nummer eindeutig pro
(tenant_id, account_id). - Recipient-XOR-Felder: interner User (FK auf
users) ODER externe Mail-Adresse, plus Name + Postanschrift. created_by-Tracking,created_at,updated_at.
Plus:
time_entries.purchase_order_idals nullable FK (NULL = Verhandlungs- Lücke oder Account ohne PO-Modell).purchase_order_historyfür Status-Wechsel-Audit (manual, budget_exhausted, date_expired, backfill_overflow).- Recipient-Override-Felder an
tenants/accounts/projects/jobsmit XOR-Constraint pro Ebene (außer Tenants: nur Default-Felder). accounts.po_enforcement(off/warn/strict, Default off).tenants.default_po_enforcement_for_new_accounts(Tenant-weiter Default für neue Accounts).
Neu — Bestandsmapping
Migration 036 wandelt aktiv um: bestehende order_number +
budget_* an Projects/Jobs werden zu POs (1:1, status='active',
start_date = budget_start ?? created_at). Bestehende time_entries
werden auf die passende PO verlinkt (Job-PO gewinnt — XOR-Regel).
Pre-Migration-Backup-Hook (erweitert in main.go) sichert alle
Tenants vorher mit Filename-Prefix pre-v1.11-036_*.json.gz.
Neu — Backend
recipient_helpers.go: Cascade-Resolver resolveRecipientForTime Entry läuft die Reihenfolge PO → Job → Project → Account → Tenant
durch und liefert ein normalisiertes ResolvedRecipient-Struct (Source,
Type internal/external, UserID, DisplayName, Email, PostalAddress).
handlers_purchase_orders.go: CRUD-Endpoints.
GET /purchase-orders(Filter: account/project/job/status)GET /purchase-orders/{id}und/budgetPOST /purchase-orders(Manager+): legt PO an; wenn am Träger schon eineactive-PO existiert, schaltet sie aufexpired(Serializable-Tx).PUT /purchase-orders/{id}(Manager+): dynamisches UPDATE; Status- Wechsel landet in History.DELETE /purchase-orders/{id}(Admin+): nur ohne hängende Buchungen.POST /purchase-orders/{id}/exhaust(manueller Status-Switch + async Notification).POST /purchase-orders/{id}/backfillmit Dry-Run-Preview + Apply: ordnet alle Buchungen ohne PO im gewählten Zeitraum der Ziel-PO zu (topologisch matchend). Bei Überlauf auto-switch aufexhausted.
po_lifecycle.go:
PoBudgetSnapshotmit Live-Verbrauch (SUM(billable_seconds)/3600)- Cascade-Rate (Job → Project → Account) für Geld-Budgets.
resolveActivePOForJob: Job-PO gewinnt, sonst Project-PO, sonst NULL.checkAndApplyBudgetExhaustion: idempotent, läuft nach jedem Booking-Insert; setzt PO aufexhausted+ History + Notification.notifyPOExhausted: 2-stufige Cascade (PO-Recipient → Account- Recipient → leer), async.computeBudgetSplit: immer-splitten wenn eine Buchung das PO- Budget überschreitet. Linear interpoliert; erstes Sub-Segment an die alte PO (die genau dabei erschöpft wird), zweites an die Nachfolge-PO falls schon eingetragen, sonst NULL (Backfill-Pool). Cross-Day-Komposition: erst Mitternachtsschnitt, dann Budget-Split pro Day-Segment.
Booking-Insert-Pfade umgestellt:
handlers_timers.go stopTimer+createEntryhandlers_timer_cleanup.go autoStopTimerhandlers_time_import.go importRowhandlers_planned.go activatehandlers_entries.goPUT Cross-Day-Folge-Segmente Jeder Pfad resolved die aktive PO, schreibt sie inpurchase_order_idund ruft nach dem InsertcheckAndApplyBudgetExhaustion.
Restore-Pfad in handlers_backup.go erweitert: restoreDeleteOrder,
restoreInsertOrder und backupTableList kennen jetzt
purchase_orders und purchase_order_history in FK-sicherer
Reihenfolge.
Neu — Frontend Web
Neue Komponenten unter frontend/src/features/purchase-orders/:
- PurchaseOrdersTab — wiederverwendbar in JobPage + ProjectPage. Zeigt oben die aktive PO als prominente Karte mit Budget-Gauge (Web-Farblogik grün < 51 % / gelb 51–75 % / rot 76–99 % / pink ≥ 100 %), darunter historische POs als kompakte Tabelle. Lädt POs + Budget- Snapshots parallel.
- NewPurchaseOrderModal — Toggle Stunden/Geld, 11 Währungen, Start- und End-Datum, Notizen.
- PurchaseOrderDetailModal — Edit (end_date, notes), Budget-
Status, Lifecycle-Aktionen (manuell exhausten bei
active, Backfill-CTA beiexhausted/expired). - POBackfillModal — Zwei-Stufen-Wizard mit Zeitraum-Auswahl, Dry-Run-Vorschau (Anzahl, Stunden, would_exceed-Warnung), Übernehmen.
Types in frontend/src/types/index.ts: PurchaseOrder,
PoBudgetSnapshot, PurchaseOrderStatus, POEnforcementMode,
POBackfillDryRunResponse, POBackfillApplyResponse.
i18n: ~60 neue Keys unter po.* (de + en) inkl. ausführlicher
Inline-Help-Texte zu Konzepten (Auto-Switch, Backfill, Verhandlungs-
Lücke).
JobPage + ProjectPage zeigen den PurchaseOrdersTab unter den Eigenschaften, vor Buchungen/Jobs.
Neu — iOS v1.4.0 + macOS v1.2.0 (Read-Only)
TimeEntry.purchaseOrderID: String?decoded auspurchase_order_id.- iOS EntriesView: kleiner lila „PO”-Badge unter der Projekt-Zeile.
- macOS EntriesView: lila Zahlen-Icon hinter dem Job-Namen.
- PO-Management läuft bewusst nur über Web — Apps sind nur Read.
Verhalten
- Ab v1.11 trägt jede neue Buchung automatisch die zum Zeitpunkt aktive PO (Job-PO gewinnt > Project-PO > NULL).
- Budget-Erreichen schaltet die PO auto auf
exhausted, schickt Notification an PO-Empfänger (via Cascade), nachfolgende Buchungen laufen mitpurchase_order_id = NULL. - Manager räumt die Lücke nach Eintrag der neuen PO über das Backfill- Modal auf (Dry-Run zeigt vor Apply was passieren würde).
Offen für nachfolgende Iterationen
- Recipient-XOR-Forms in Account/Project/Job-Edit (internal/external)
- OrphansBanner im Dashboard („X Buchungen ohne PO seit Y Tagen”)
- PDF-Modal-Erweiterung „Pro Bestellnummer” (Gruppierung + Header)
- JobPage/ProjectPage: bei
po_enforcementaktiv die altenorder_number/budget_*-Felder ausblenden - Tenant-Settings:
default_po_enforcement_for_new_accounts-Switch - iOS/macOS: PO-Liste + Anlegen
- Bulk-PDF pro
(Account × PO)— kommt mit v2.0 SR2
[1.10.0] — 2026-05-30
Datenmodell-Schnitt: „Gebucht / Abrechenbar” statt „Dauer / Abr. %” / „Brutto / Netto”.
Strukturelle Vereinfachung über alle Komponenten (Backend, Web, iOS, macOS,
PDF). Tester-Feedback: das alte billable_percent wurde in der Praxis nie
verwendet; die Trennung in „Brutto / Netto” über Rundungsregel war
konzeptionell nicht belastbar (Netto war nur eine Funktion vom Brutto,
nicht eigenständig editierbar).
Geändert — Datenmodell
time_entries wird per Migration 035 umgebaut:
- Neu:
booked_seconds(gearbeitete Zeit, vorherduration_rounded_seconds) - Neu/Konsolidiert:
billable_seconds(abrechenbare Zeit, war seit Migration 009 da aber nie konsistent befüllt — wird beim Schnitt aus Bestand neu berechnet) - Raus:
duration_seconds,duration_rounded_seconds,billable_percent
Befüllungs-Logik beim Schnitt:
booked_seconds = duration_rounded_seconds ?? duration_seconds ?? 0
billable_seconds = booked_seconds × billable_percent / 100
Plus Job-Typ-Overrides (greift auf jobs.billing_type zu):
not_billable→billable_seconds = 0billable_fixed→billable_seconds = booked_seconds(Aufwand zur Information; Pauschale wird separat verrechnet)billable_hourly→ unverändert (Wert aus Formel oben)
NOT NULL + CHECK ≥ 0 Constraints, neuer Index auf billable_seconds > 0.
Neu — Pre-Migration-Backup-Hook + Restore-Adapter
main.go ruft vor runMigrations() einen neuen Hook preMigration BackupIfNeeded(ctx, db, cfg): wenn schema_migrations.version < 35 und
Tenants existieren, läuft pro Tenant ein vollständiger JSON-Snapshot mit
Dateiname-Marker pre-v1.10-035_<timestamp>.json.gz. Bei Fehler → harter
Abbruch, Migration läuft NICHT ohne Sicherung.
handlers_backup.go performRestore erkennt Backups VOR Migration 035 (am
Vorhandensein von duration_seconds / duration_rounded_seconds /
billable_percent im Record-JSON) und rechnet sie über
transformLegacyTimeEntriesForRestore ins neue Schema um. Adapter bleibt
bis einschließlich v2.0 stehen.
Geändert — Backend-Handler
- Neuer Helper
billing_helpers.go: zentralisiert die Job-Typ-Logik (billableSecondsForJob,billableSecondsForBookingChange,clampBillable). - Schreibpfade: Timer-Stop, Auto-Stop (systemd), PUT /entries (inkl.
Audit-Insert in
time_entry_history.old_data), Bulk-Edit, Plan- Aktivierung, CSV-Import — alle schreiben jetztbooked_seconds+billable_secondsmit Job-Typ-Override. - Read-/Aggregat-Pfade: Export-Handler (CSV/PDF/JSON/XML), ZIP-Export,
AccountsDetail (JobEntry-Amount =
billable_seconds × Stundensatz / 3600), Auslastungs-Tab, Reports, Dashboard, Projects, Admin-Stats — alle SQLs umgestellt. - API-Typen (
swagger_types.go):UpdateEntryRequest+BulkUpdateChangesnutzenBookedSeconds+BillableSecondsstattBillablePercent.
Geändert — Frontend Web
- Types:
TimeEntry(types/index.ts) +JobEntry(features/accounts/types.ts) aufbooked_seconds+billable_seconds. - 4 Inline-Edit-Pfade (EntryEditModal, Entries, Reports, JobPage)
haben zwei HH:MM-Eingabefelder „Gebucht” + „Abrechenbar” statt einem
„Abr.%“-Number-Field. Neuer Helper
secondsToHHMM/parseHHMM. - Tabellen (Entries, Reports, JobPage): „Dauer” zeigt
booked_seconds, neue Spalte „Abrechenbar” zeigtbillable_secondsin HH:MM mit Amber-Warnfarbe wennbillable < booked. - NewBookingModal: „Abrechnung in %“-Field komplett raus (Backend rechnet billable per Job-Typ beim Insert; Override später via Edit-Pfad).
- BulkEditModal: „Abrechnung in %” → „Abrechenbare Zeit (HH:MM)”.
- Dashboard:
DashEntry.duration_seconds→booked_seconds. - PDF-Modal:
showBillablePct-Option entfernt. - PDF-Template (
customer_page.html.tmpl): „Brutto/Netto” → „Gebucht/ Abrechenbar” über alle Stellen (KPI-Labels, Tabellenkopf, Job-Total-Text). - i18n (de + en): ~10 Keys umbenannt
(
entry_edit.billable_percent→entry_edit.booked+.billable;entries.col.billable_pct→entries.col.billable; analog Reports, Bulk-Edit).
Geändert — iOS, macOS
- iOS v1.3.0 (
MARKETING_VERSION 1.2.1 → 1.3.0) + macOS v1.1.0 (MARKETING_VERSION 1.0.0 → 1.1.0). - TimeEntry-Struct auf
bookedSeconds+billableSeconds, CodingKeys aufbooked_seconds/billable_seconds. - EntriesView Property-Refs aktualisiert (Display + Summen).
- NewBookingSheet: Abrechnung-%-Section + State entfernt; Backend setzt
billable_secondsper Job-Typ. - iOS SettingsView
releaseDate-Switch um v1.2.1 + v1.3.0 erweitert. - watchOS unverändert (keine Buchungs-Schemata in
WatchModels).
Audit-Trail
Bei jedem PUT /entries werden die alten Werte in
time_entry_history.old_data (JSONB) gespeichert — inkl. booked_seconds,
billable_seconds, started_at, ended_at, notes, job_id.
Tisch existiert seit Migration 005, wird ab v1.10 für booked/billable-
Änderungen verpflichtend befüllt.
Rollen-Logik
Edit-Pfad (PUT /entries) respektiert die bekannten Rollen-Regeln:
- Bucher: darf eigene Buchungen in beide Richtungen anpassen (booked
- billable). Audit-Eintrag Pflicht.
- Manager/Admin/Super-Admin: alle Buchungen im Tenant.
- Period-Locks: greifen für alle Rollen einheitlich.
Job-Typ-Constraints werden im Backend unabhängig vom UI durchgesetzt:
not_billable zwingt billable_seconds = 0; billable_fixed zwingt
billable_seconds = booked_seconds.
Big-Bang-Deploy
Datenmodell-Schnitt läuft ohne v-1-Kompatibilität. Vorbereitungen mitgeliefert:
- Pre-Migration-Backup pro Tenant (siehe oben) — Sicherungsnetz gegen Migrations-Fehler.
- Restore-Adapter für historische Backups — kein stiller Datenverlust beim Wiederherstellen alter Stände.
- iOS-App war zum Schnitt nicht im Store / Beta-Review-Status. macOS-App lief nur als DMG-Distribution.
- Browser-Cache: Tester werden per Mail über den Schnitt informiert, Hard-Refresh-Anweisung.
Offen für nachfolgende Iterationen
- iOS / macOS Edit-Pfad für Buchungen (HH:MM Inputs analog zur Web-Inline- Edit) — derzeit nur Anzeige.
- Job-Typ-Wechsel-Modal in JobPage (Web) mit Empfehlung „neuen Job anlegen” bei vorhandenen Buchungen.
- Audit-UI Hover-Icon „letzte Änderung” in den Inline-Edit-Zeilen.
docs/docs.goperswag initregenerieren (Swagger-UI zeigt sonst noch die alten Request-Schemas).- History & Logs als eigenes Konzept (Spec-Workshop vor v1.11).
[1.9.2] — 2026-05-29
KPI-Reihe ruhig gestellt — Accounts / Projekte / Jobs nur als reine Zahl, letzte KPI rechtsbündig.
Kleiner Polish direkt nach v1.9.1: das Ratio-Format X / Y war zu informativ
für eine KPI-Reihe und brachte ein zweites Farbsignal (lila) in Spalten, die
eigentlich ruhig laufen sollen. Außerdem lief die rechte KPI-Kante optisch
nicht mit der Page-Margin mit.
Geändert — Accounts / Projekte / Jobs nur als reine Zahl, schwarz
Das <span class="hi">X</span> / Y-Konstrukt + .kpi.ratio .value .hi { color: var(--primary); }-Regel ist raus. Die drei KPIs zeigen jetzt nur {{.Customers}},
{{.Projects}}, {{.Jobs}} — gleicher Geist ExtraLight Schwarz-Stil wie
Buchungen / Werktage. Die Sub-Zeile „mit Buchungen” bleibt.
Die Backend-Felder AccountsTotal / ProjectsTotal / JobsTotal und
loadTenantTotals bleiben im Code stehen — werden später für eine optionale
„X / Y”-Detailansicht oder einen Tenant-Insights-Block wiederverwendbar sein.
Geändert — Letzte KPI rechtsbündig
.kpi-row > .kpi:last-child { text-align: right; } — Label, Value und Sub
der letzten Kachel („Ø pro Tag”) fluchten jetzt mit der rechten Seitenmarge,
genauso wie der Mandanten-Name im Page-Header und der Page-Counter im
Footer. Macht die Reihe optisch ruhiger.
[1.9.1] — 2026-05-29
Polish auf dem Premium-PDF — echte Projekt-Budgets aus der DB, KPI-Split, Account-Spalte dynamisch.
Patch direkt nach v1.9.0, nachdem die erste Live-Render-Iteration auf Produktiv-
Daten von ATAMYA GmbH gezeigt hat: lange Account-Namen brachen mehrzeilig um,
keine Budget-Gauges erschienen (Sample-Overlay matchte natürlich nicht auf die
echten Firmen-Namen), und die Cover-KPIs hatten „Kunden” mit Projekte · Jobs
als Sub — der Sinn dahinter geht in der dichten Darstellung verloren.
Geändert — Echte Projekt-Budgets aus der DB
applyRealProjectBudgets (in pdf_premium.go) lädt nach applySampleOverlay
alle aktiven Projekte des Tenants mit budget_hours OR budget_amount
gesetzt und stellt sie pro projectSection ein. Used = HoursNet bei
Stunden-Budget, HoursNet × effektive Stundenrate bei Betrags-Budget
(Cascade COALESCE(p.hourly_rate, a.hourly_rate, 0)). Währungs-Glyphen
über currencyGlyph() (€/$/£/CHF/Fallback Code).
Das Sample-Overlay liefert ab v1.9.1 nur noch Demo-Adressen + Demo-Rollen für die vier hardcoded Demo-Accounts (bis Migration 034 die echten DB- Felder einführt). Budget-Logik dort komplett gestrichen — produktive Tenants sehen jetzt ihre real hinterlegten Budgets, statt nichts.
Geändert — Cover-KPIs: 5 Tiles → 7 Tiles, „Kunden” → 3 Ratio-KPIs
Aus der einen „Kunden”-Kachel werden drei separate „Accounts” /
„Projekte” / „Jobs”-Kacheln im Format X / Y mit X = mit Buchungen
im Berichtszeitraum (= Customers / Projects / Jobs aus dem Builder),
Y = aktiv insgesamt im Tenant (neu via loadTenantTotals). Erste Zahl
in #9D2AC6 (.ratio .hi), zweite normal (Default-Body-Text). Sub
„mit Buchungen”.
Grid-Layout im KPI-Row schaltet auf 7 Spalten mit kleinerem Gap (10pt
statt 18pt) um, sobald .kpi-row-7 gesetzt ist.
Globales Wording: „Kunden” → „Accounts” in der Cover-Übersicht (siehe v1.9.0 Eyebrow „Übersicht nach Account”) — die KPI folgt jetzt.
Geändert — Account-Spalte in Cover-Übersicht dynamisch
<th>Account</th> hat keine feste Breite mehr, und .cover-overview .ku-name bekommt white-space: nowrap — die Account-Spalte wächst
mit dem längsten Namen, die Anmerkung-Spalte gibt nach (selten lang
befüllt). Lange Firmennamen wie „Christ Juweliere und Uhrmacherseit
1863 GmbH” oder „RUD Ketten Rieger & Dietz GmbH u. Co. KG” stehen
jetzt einzeilig.
Geändert — Account-Detail-Header expandiert ohne Rechnungsadresse
Wenn weder ContactPerson noch InvoiceAddress gepflegt sind, schaltet
.kunde-head.no-invoice das Grid von 1.2fr 1fr 0.9fr auf 1fr auto
um und versteckt den Invoice-Block. Der Account-Name nimmt die freie
Mitte ein — Namen wie „Christ Juweliere und Uhrmacherseit 1863 GmbH”
brechen nicht mehr drei Zeilen um.
Geändert — pdfFmtHHMM-Nachzug
AvgPerWorkDayLbl enthält jetzt nur die Zahl ohne „ h”; das Template
hängt die Einheit als <span class="unit"> an, damit die Cover-KPI
„Ø pro Tag” konsistent mit den anderen Stunden-KPIs aussieht.
Roadmap-Notiz
Sortierung der Übersicht als künftige Option ins v2.0-Modal-Backlog geschrieben (Default „nach Stunden netto”, optional alphabetisch nach Account-Name + perspektivisch in den Tenant-Defaults).
[1.9.0] — 2026-05-29
Premium-PDF Layout 2 — editorial Cover + per-Account-Detailseiten (Vorschau im Test-Endpoint).
Iteration mit @kaiwarmus über mehrere Sessions (24.–28.05.): aus dem Design-Entwurf
„Stundennachweis 2.pdf” entsteht eine vollständige neue PDF-Vorlage im
editorial Bank-Statement-Stil. In v1.9 ist sie über /export/test-customer.pdf
erreichbar (Layout-2-Test-Button im PDF-Export-Modal). Die produktive
entries.pdf bleibt vorerst Layout 1, bis v2.0 die Umstellung mitnimmt.
Neu — Premium-PDF Layout 2
- Cover-Seite: Mandanten-Name (Geist Thin 100, 75 % Schwarz, links bündig)
- Mandanten-Logo-Platzhalter rechts, darunter 0,25 pt Trennlinie in daagwerk-Lila #9D2AC6.
- Eyebrow-Zeile: „Auswertung · Periode · Vertraulichkeit (pink) · Erzeugt von Owner · Übersicht nach Account” in Geist Thin Letter-Spacing 0.18em uppercase, 100 % Schwarz auf der Cover-Seite, 75 % Schwarz auf den Folgeseiten als Running Header.
- Headline „Aufwands- und Stundennachweis” in Geist ExtraLight 28 pt.
- Fünf KPI-Kacheln: Stunden netto als Leit-KPI in Geist Light lila, die übrigen vier (Buchungen / Kunden / Werktage / Ø pro Tag) in Geist ExtraLight Schwarz; Labels und Sub-Zeilen in Geist Thin, 75 % Schwarz.
- Übersichts-Tabelle „Account · Anmerkung · Projekte/Jobs · Buchungen · Stunden netto” mit Spaltenbreiten 22 / 34 / 16 / 12 / 16 %.
- Pro Account eine Detailseite mit Page-Header (gespiegelt zur Cover- Eyebrow), Account-Name in Geist Light 22 pt lila, Rechnungsadresse + Ansprechpartner, Stunden-netto-Box rechts mit Brutto/Differenz.
- Pro Projekt ein Header mit Halbkreis-Budget-Gauge (Inline-SVG, dynamic stroke-dasharray), Web-Farblogik grün < 51 % / gelb 51–75 % / rot 76–99 % / pink ≥ 100 % + „Überzogen”-Pille; Soll/Ist-Box mit Stunden- oder Betrags-Budget; Projekt-Netto rechts.
- Pro Job ein Header mit Netto-/Brutto-Total und Buchungs-Tabelle (Datum · Von—Bis · Brutto · Netto · Nutzer · Abr.% · Status · Notiz) in Spaltenverteilung 7 / 11 / 7 / 7 / 14 / 6 / 9 / 39 %.
- Tabellen brechen sauber über Seiten:
theadwiederholt sich, einzelne Buchungszeilen bleiben atomar, Projekt- und Job-Header kleben am ersten Inhalt darunter. - Footer komplett über
@page @bottom-*-Margin-Boxes (counter(page) / counter(pages) sind inposition: fixedin Chromium unzuverlässig); durchgehende 0,25 pt Linie über alle drei Bottom-Boxes in 75 % Lila, „SEITE n VON m” rechtsbündig in Geist Thin.
Neu — Schriften lokal eingebettet
- Geist als WOFF2: Thin (100) und ExtraLight (200) frisch aus fontsource ergänzt, plus die bereits vorhandenen Light (300), Regular (400), Medium (500), SemiBold (600). Geist Mono Regular/Medium als Fallback.
- Instrument Serif Italic — nur noch als Akzent für den Tenant-Brand- Schriftzug, alle anderen Headlines auf Geist umgestellt.
buildFontFaceBlockinpdf_templates.goum Thin- und ExtraLight- Mapping erweitert.
Neu — Design-Tokens in premium.css
--primary: #9D2AC6 (daagwerk Lila, Hauptakzent + Hervorhebungen)
--ok: #A1E929 (Lime, Budget < 51 %)
--warn: #F7C619 (Gelb, Budget 51–75 %)
--danger: #E03B3B (Rot, Budget 76–99 %)
--over: #F72A7C (Magenta, Budget ≥ 100 % + Vertraulichkeits-Marker)
Alle Zahlen mit font-feature-settings: "tnum" 1 (tabular-nums),
damit Stunden- und Geldspalten sauber untereinander bleiben.
Neu — Sample-Overlay für Demo-Daten
pdf_premium_sample.go befüllt vier Demo-Accounts (ACME Corp.,
ATAMYA GmbH, Muster GmbH, Mainufer Bank AG) mit Rechnungsadressen,
Ansprechpartnern und Projekt-Budgets. Wird in v2.0 entfernt, sobald
Migration 034 die Felder auf accounts trägt und der
/export/entries.pdf?layout=2-Datenadapter direkt aus der DB liest.
Neu — Frontend: „Layout 2 (Test)“-Button im PDF-Export-Modal
In Reports.tsx unten links im PDF-Modal ein gestrichelter Mini-Button.
Klick zieht die aktuell gesetzten Filter heran, ruft
/export/test-customer.pdf via Axios (inkl. JWT) und öffnet das PDF
in einem neuen Tab. Bewusst kein Download — beim Iterieren am Layout
würde sonst der Downloads-Ordner geflutet.
Geändert — pdfFmtHHMM
Liefert ab jetzt nur „HH:MM” (vorher „HH:MM h”). Die Einheit hängt
das Template separat an, damit sie stylebar wird (z. B. dünner als die
Zahl, oder lila als Akzent) und nicht doppelt rendert. Nur das
Premium-Layout nutzt die Funktion — der bestehende report.html.tmpl-
Pfad (Layout 1) ist nicht betroffen.
Geändert — Test-Endpoint zieht Tenant-Namen aus der DB
/export/test-customer.pdf liest tenants.name statt des hartem
„daagwerk”-Defaults, sodass im Brand-Spot der echte Mandantenname
steht. Fallback bleibt „daagwerk” bei leerer Spalte.
Offen für v2.0
- Migration 034:
accounts.account_manager+customer_success+contact_person(löst Sample-Overlay ab) - AccountPage Edit-Form um die drei Felder erweitern
/export/entries.pdf?layout=2mit echten DB-Daten- PDF-Modal: Layout-Switcher prominent statt Test-Button, Chronologisch aus den Gruppierungen entfernen, neue Optionen (Brutto/Netto-Anzeige, Brutto/Netto-Differenz, Vertraulichkeits- Free-Text)
- Bulk-Export pro Account als ZIP (
/export/entries-bulk.zip) - Echte Werktags-Logik mit Feiertags-Abzug aus
holidays_de.go(aktuell nur Mo–Fr-Count ohne Feiertage) - Running Header auf Folgeseiten innerhalb desselben Accounts („… Forts.”) — derzeit nur auf erster Seite des Accounts sichtbar
- Tenant-Custom-Branding (Logo) — Migration 031 (
tenant_branding) ist da, scharf schalten in v2.0 SR2
[1.8.0] — 2026-05-23
Manuelle Buchungen ohne Timer-Umweg — neuer Sidebar-Button + Modal.
Reaktion auf konvergierendes Tester-Feedback (Asana-Tickets 1215050122488119 „Neue Buchung mit Start/Ende erweitern” + 1215042790826420 „Unter Timer starten zusätzlicher Button”): Wer Zeiten nachträglich einträgt — etwa nach einem Kunden- Termin oder offline — musste bisher den Umweg „Timer starten → sofort stoppen → in Meine Buchungen wechseln → bearbeiten” gehen. Mit v1.8.0 gibt es einen direkten Pfad.
Neu — „Buchung eintragen”-Button in der Sidebar
Direkt unter dem lime-grünen „Timer starten”-Button sitzt jetzt ein zweiter
Button mit lila Outline (#9D2AC6, daagwerk-Purple) und Diskette-Icon. Hover
füllt sich solid lila, Klick öffnet das neue NewBookingModal — analog zum
Timer-Modal über ein Window-Event (daagwerk:open-new-booking), damit die
Aktion von jeder Seite aus erreichbar ist.
Neu — NewBookingModal mit voller Buchungs-Mechanik
Inhalt analog zum Mockup aus Ticket 1215050122488119:
- Job-Picker mit Volltextsuche (gleiche JobPicker-Komponente wie Timer-Modal)
- Start + Ende als zwei DateTime-Picker nebeneinander (flatpickr, deutsche Lokalisierung, Default: jetzt minus eine Stunde bis jetzt)
- Notiz mit Template-Picker
- Abrechnung in % (Default 100, 0–100 erlaubt)
- Nutzer-Dropdown — nur sichtbar für Manager/Admin/Super-Admin, lädt
/usersund erlaubt die Vertretungs-Buchung für andere Team-Mitglieder. Bucher sehen das Feld nicht und buchen immer für sich selbst. - Hinweis-Zeile „Es gibt kein Undo — die Buchung wird direkt gespeichert.”
- Inline-Validierung: Ende vor Start zeigt sofort einen roten Hinweis,
Speichern-Button bleibt disabled. Server gibt zusätzlich
error.ended_after_startedals Fallback.
Auf erfolgreiches Speichern feuert das Modal daagwerk:entry-created —
Entries.tsx lauscht und refetched die Liste, der neue Eintrag erscheint
sofort.
Geändert — Backend POST /entries erweitert
handlers_timers.go createEntry akzeptiert jetzt zwei zusätzliche optionale
Felder im Request-Body:
user_id— Manager/Admin/Super-Admin können Buchungen für andere User anlegen. Bucher die das Feld trotzdem schicken werden ignoriert. Der angegebene User wird gegen den eigenen Tenant verifiziert, fremde User führen zuerror.user_not_found.billable_percent— Abrechnungs-Prozentsatz für die manuelle Buchung (Default 100, Clamping auf 0–100). Wandert direkt in dietime_entries.billable_percent-Spalte.
Cross-Day-Split (seit v1.7.3) greift unverändert — mehrtägige manuelle
Buchungen werden weiterhin an der Mitternachtsgrenze der Tenant-Timezone in
tagesweise time_entries zerlegt, beide neuen Felder kommen pro Segment in
denselben INSERT.
Hinweis für iOS- und macOS-App-Tester
Die zwei Apps bekommen das gleiche Pattern in der nächsten Iteration. Für v1.8.0 ist die Funktion zunächst nur im Web verfügbar.
[1.7.3] — 2026-05-21
Polish-Tag direkt nach v1.7.2. Drei voneinander unabhängige Aufräumarbeiten, die schon länger auf der Liste standen und in dem Augenblick erledigt waren wo man sie zusammen anfasst.
Behoben — Cross-Day-Buchungen werden jetzt automatisch gesplittet
Wenn ein Timer über Mitternacht lief (oder eine manuell angelegte Buchung über Mitternacht reichte), landete die volle Dauer am Start-Tag. Der Auslastungs-Tab zeigte dadurch unmögliche Werte wie 25,8 h an einem einzelnen Kalendertag.
Backend zerlegt diese Bereiche jetzt an der Mitternachts-Grenze der Tenant-
Timezone (tenants.timezone, Default Europe/Berlin) in tagesweise
time_entries-Segmente. Pro Segment wird die Rundungsregel sauber neu
angewendet. Betroffen sind drei Pfade:
- Timer stoppen (
POST /timers/{id}/stop): wer den Timer um 23:50 startet und am nächsten Morgen 09:00 stoppt, bekommt zwei Buchungen — eine von 23:50 bis 24:00 am Vortag, eine von 00:00 bis 09:00 am Folgetag. - Buchung anlegen (
POST /entries): manuelles Anlegen einer Cross-Day- Buchung über das Bearbeiten-Modal wird gleich gesplittet. - Buchung bearbeiten (
PUT /entries/{id}): das erste Tages-Segment ersetzt die ursprüngliche Buchung (Original-ID bleibt erhalten), zusätzliche Tages-Segmente werden als neue Einträge angelegt. Frontend refetched seine Liste und sieht die zusätzlichen Tage automatisch.
Helper-Funktionen in der neuen Datei cross_day_split.go. CSV-Zeitbuchungs-
Import erzeugt strukturell keine Cross-Day-Einträge (Start- und End-Time teilen
sich eine date-Spalte), daher dort kein Eingriff nötig.
Neu — Budget-CSV-Export auf der Budgets-Seite
Der Backend-Endpoint GET /export/budget.csv existierte seit Längerem, hatte
aber keinen UI-Button. Neuer „CSV-Export”-Button rechts neben der Speichern-
Suche in der Budgets-Filterleiste. Lädt für alle Projekte mit hinterlegtem
Budget den aktuellen Stand als Semikolon-CSV (BOM für Excel) — Kunde, Projekt,
Budget-/Ist-Stunden, Auslastung, Beträge, Zeitraum.
Entfernt — ChartLab-Playground
Die Datei frontend/src/pages/ChartLab.tsx (~900 Z. reines SVG) wurde mit dem
v1.4-Re-Design obsolet. Die Budgets-Seite löst die Gauge-/Balken-Visualisierung
abschließend, der Auslastungs-Tab deckt Wochen-/Monats-/Jahres-Sicht ab.
Datei + verbleibende Notizen in CLAUDE.md aufgeräumt. Spart ~900 Zeilen
toten Code, keine Verhaltens-Änderung.
[1.7.2] — 2026-05-21
Globaler „Timer starten”-Button in der Sidebar + Schnellstart aus Job-Listen.
Quick-Patch direkt nach v1.7.1: ein Timer ließ sich bisher nur über die Timer-Seite oder Favoriten starten. Wer gerade in einem Account, Projekt oder Bericht arbeitete, musste erst dort hin navigieren. v1.7.2 macht den Timer-Start von jeder Seite aus erreichbar.
Neu — „Timer starten”-Button in der Sidebar
Direkt unter dem daagwerk-Logo, oberhalb der Navigation: ein lime-grüner
Button (#A1E929) mit Play-Icon und der Beschriftung „Timer starten”.
Klick öffnet ein Modal mit der gewohnten Job-Suche (Volltext über Account
· Projekt · Job, mit Cascade-Fallback), Notiz-Feld und Template-Picker.
- Funktioniert von jeder Seite aus (Dashboard, Berichte, Buchungen, Einstellungen, etc.) — kein Seitenwechsel mehr nötig.
- Architektur: Eine zentrale
NewTimerModal-Komponente lebt im Layout-Mount, wird über das Window-Eventdaagwerk:open-new-timergetriggert. Timer-Seite nutzt dasselbe Modal — keine Code-Duplizierung. - Nach erfolgreichem Start feuert ein
daagwerk:timer-started-Event, damit offene Listen (z. B. Timer-Seite) sich refetchen.
Neu — Play-Button (▶) in Job-Listen
In der Accounts-Übersicht (Default-Account-Jobs) und in jedem Projekt-Detail (Job-Tabelle) steht in jeder Zeile vorne ein kleiner lime-grüner Play-Button. Klick startet den Timer für genau diesen Job ohne Umweg über Modal oder Picker — ideal wenn man weiß welchen Job man buchen will.
- Inaktive Jobs zeigen den Button ausgegraut (nicht klickbar).
- Bei bereits laufendem Timer auf dem Job: Toast „Für diesen Job läuft bereits ein Timer.”
- Bestehende Zeilen-Aktionen (
→ Projekt,↑ Account, Öffnen) bleiben unverändert.
Geändert — Timer-Seite nutzt das neue Modal
Die Timer-Seite hatte bisher einen eigenen Picker-Dialog mit gestapelten
Account/Projekt/Job-Dropdowns. Dieser wurde entfernt — der „+ Neuer
Timer”-Button im PageHeader öffnet jetzt das gleiche NewTimerModal wie
der Sidebar-Button. Eine Quelle, ein Verhalten.
[1.7.1] — 2026-05-20
Edit-Buttons: Layout-Fix + visuelle Differenzierung.
Kleiner Patch direkt nach v1.7.0 — in der „Meine Buchungen”-Tabelle waren die beiden Edit-Buttons (Inline + Erweitert) untereinander gestapelt statt nebeneinander. Außerdem optisch nicht klar unterscheidbar.
Behoben — Edit-Buttons in „Meine Buchungen” nicht mehr gestapelt
Die Aktionen-Spalte der Entries-Tabelle hatte eine fixe Breite von 48 px,
zusammen mit table-layout: fixed. Der zweite Button überlief die Zelle
und wurde abgeschnitten — beim Rendern in mehreren Browsern (Firefox,
Safari) sahen User entweder gar nichts oder die Buttons vertikal gestapelt.
- Aktionen-Spalte auf 100 px verbreitert,
paddingRightauf 16 px erhöht für etwas Luft zum Tabellenrand. - Die zwei Buttons in einen
inline-flex-Container mitgap: 4pxgepackt, Zelle bekommtwhiteSpace: nowrapdamit der Browser nicht mehr versucht zu wrappen.
Verbessert — Edit-Buttons visuell differenziert
Beide Edit-Buttons sahen vorher identisch aus (graue Outline, Bleistift- Icon). User sollten auf einen Blick erkennen, welcher der „schnelle” und welcher der „erweiterte” Edit ist.
- Inline-Edit (✏): weißer Hintergrund, 1,5 px Outline in
#10A7BD(Teal), Bleistift in#10A7BD. Wirkt wie ein „leichter” Button für den schnellen Eingriff. - Erweitert-Edit (✏+): vollflächig
#10A7BD-Hintergrund, weißer Bleistift + weißes „+”. Wirkt wie ein „voller” Button für den umfangreicheren Eingriff.
Gleiche Behandlung in den drei Stellen wo Buchungen editierbar sind:
Entries.tsx (Meine Buchungen), Reports.tsx (Berichte) und
JobPage.tsx (Job-Detail-Buchungstabelle).
Sonstiges
JobPage.tsxnutzt jetzt ebenfalls dasIconPencil-SVG statt des Unicode-Bleistifts (✏) — konsistent zum Rest der App.
[1.7.0] — 2026-05-20
Volltext-Suche, Default-Account-Jobs, JobPicker mit Toggle, Eigenschaften-Collapse.
Großes UX-Release nach Rückmeldungen aus den ersten Echt-Tenants (109 aktive Accounts, 225 aktive Projekte, 1109 aktive Jobs). Vier voneinander unabhängige Bausteine — alle zusammen drehen einen Kernschmerz: Navigation in einer großen Hierarchie soll leicht sein, und die starre dreistufige Account→Projekt→Job-Pflicht passt nicht zu Bestandskunden ohne Projekt.
Neu — Volltext-Suche auf der Timer-Seite
Ganz oben über Laufende Timer / Favoriten / Letzte Jobs steht jetzt eine Schnellsuche, die direkt einen Timer startet wenn Du einen Treffer auswählst.
- AND-Suche mit Substring, case-insensitive über Account · Projekt · Job (z. B. „müllermilch wartung” findet den eindeutigen Job auch ohne genaues Erinnern an den Projekt-Namen).
- Globale Tastatur-Shortcuts: ⌘K (Mac) / Ctrl+K (Win) und
/als Quick-Shortcut. Beide springen jederzeit ins Suchfeld, außer wenn Du gerade in einem anderen Input tippst (kein Fokus-Klau). - Auto-Focus beim Page-Load: tippe einfach drauflos.
- Pfeil-Navigation hoch/runter, Enter startet den Timer für den aktiven Treffer. Mit der Maus: Hover färbt grün, ein Klick startet.
- Highlighting der Suchbegriffe in den Treffern. Empty-State zeigt „Keine Treffer für „xyz”.”
- Backend: neuer Endpoint
GET /jobs/search?q=…mit ILIKE-Tokenisierung, alphabetisch sortiert, hart auf 30 Treffer gecapped. Filter: nur aktive Accounts/Projekte/Jobs.
Neu — Default-Account-Jobs (Jobs direkt am Account)
60–70 % unserer Accounts sind Bestandskunden ohne klassisches Projekt- geschäft. Für die musste man bisher ein Pseudo-Projekt anlegen, in dem der Job liegt. Mit v1.7 kann ein Job direkt am Account hängen.
- AccountPage: neue hellgraue Sektion „Default Account Jobs” zwischen Eigenschaften und Projekte. Tabelle mit Job · Abrechnung · Stundensatz · Gebucht · Buchungen, plus Button „+ Job direkt am Account”.
- Anlegen-Modal: Name + Abrechnungsart (stündlich / Festpreis / nicht abr.) + optionaler eigener Stundensatz oder Festpreis. Cascade-Logik für die Stundensatz-Berechnung (Job → Default-Projekt → Account) funktioniert unverändert weiter.
- Migration 029:
projects.is_default BOOLEAN NOT NULL DEFAULT FALSE- partieller Unique-Index, der genau ein Default-Projekt pro Account erlaubt. Race-safe Erzeugung via SELECT-then-INSERT-mit-Recovery.
- Lazy-Create: das Default-Projekt wird erst erzeugt, wenn der erste Job direkt am Account angelegt oder dorthin verschoben wird. Bestehende Accounts ohne Default-Bedarf bleiben unverändert.
- Move-Aktionen in beide Richtungen:
- In der ProjectPage-Jobs-Tabelle: pro Zeile Button „↑ Account” → Confirm-Dialog → POST /jobs/{id}/move-to-account.
- In der AccountPage-Default-Sektion: pro Zeile Button „→ Projekt” → Modal mit Projekt-Dropdown → POST /jobs/{id}/move-to-project.
- Konsistente Anzeige in Listen (Entries-Tab + Reports-Tab): die Projekt-Spalte zeigt für Default-Jobs „(Default)” in grau-kursiv, Tooltip „(Default Account Jobs)”. Im CSV/JSON/XML-Export bleibt der Literal- String „(Default)” stehen damit Excel-Filter sauber funktionieren.
- PDF-Export blendet (Default) aus: PDFs gehen zur visuellen Kontrolle an Kunden — der interne Marker hat da nichts zu suchen. Chronologische Ansicht zeigt „Account / Job” statt „Account / (Default) / Job”, hierarchische Ansicht skippt den Default-Sub-Header.
Neu — JobPicker mit Toggle Suche ↔ Auswahl
Bei 400 Kunden (Wachstumsschätzung +50–100 % auf die aktuellen 109) ist eine reine Suche frustrierend wenn der User den Namen nicht im Kopf hat. Der neue JobPicker bietet beides:
- Default-Modus: Suche (nutzt die gleiche JobSearchBox wie auf der Timer-Seite, ohne Hotkey-Capture).
- Toggle-Link „→ über Auswahl wählen” wechselt zu klassischen Cascading- Dropdowns (Account → Projekt → Job). Modus wird pro User in localStorage gemerkt — Power-User landen immer in der Suche, Klick- User immer im Cascade.
- Default-Projekte erscheinen im Cascade-Modus als „(Default Account Jobs)” und werden im Projekt-Dropdown nach oben einsortiert.
- Aktuell gewählter Job wird oben als Pille angezeigt mit ×-Button zum Löschen.
- Eingesetzt im EntryEditModal („Erweitert bearbeiten”) und in der „Hierarchie verschieben”-FieldGroup des BulkEditModal. Auf der Timer-Seite bleibt es bei der reinen Suche, weil dort eh genug Browse- Sektionen (Favoriten, Letzte Jobs) für den Namen-Vergesser da sind.
Verbessert — Eigenschaften-Sektion zugeklappt
Auf den Detail-Seiten (Account, Projekt, Job) startet die Eigenschaften- Sektion jetzt zugeklappt, zeigt nur einen schmalen Streifen mit ▶-Caret, Titel und Bearbeiten-Button rechts.
- Klick auf den Streifen → klappt zum Lesen auf.
- „Bearbeiten” klickt → klappt auf + Edit-Mode mit Speichern/Abbrechen auf dem Streifen.
- Speichern/Abbrechen → zurück in den vorherigen Zustand (offen wenn vorher zum Lesen geöffnet, sonst zu).
- PageHeader-Bearbeiten-Button (oben rechts) entfernt — Edit ist jetzt nur noch auf dem Streifen.
Backend — neue Endpoints + Schema-Änderungen
GET /jobs/search?q=…— Volltext-SuchePOST /accounts/{accountID}/jobs/direct— Job direkt am AccountPOST /jobs/{id}/move-to-account— Job in Default verschiebenPOST /jobs/{id}/move-to-project— Job in Projekt verschieben- Migration 029 — projects.is_default
is_default-Felder in den Responses von/jobs,/jobs/recent,/jobs/search,/entries,/reports/entriesAccountDetail.default_jobsin/accounts/{id}/overview- swag init regeneriert für alle neuen Endpoints
Sonstige Änderungen
- ROADMAP: User-Feedback-Punkte aus Session 14 in v1.7 abgeschlossen. Tagesansicht/Wochenansicht bleibt offen für eine spätere Iteration — ist als eigener Spec-Schritt zu planen.
.claude/launch.jsonlokal:cwdauf relativen Pfad geändert (nicht im Git, weil unter.claude/gitignored).- i18n-Keys unter
search.*undjob_picker.*in DE+EN.
[1.6.0] — 2026-05-19
User-Feedback-Release: Polish + Letzte Jobs + Bulk-Edit Iteration 3.
Sammelt sechs Verbesserungen, die nach den ersten Rückmeldungen echter Nutzer entstanden sind. Bewusst als Minor-Release (v1.6.0) statt Patch, weil zwei neue Features dazukommen (Bulk-Datum/Uhrzeit + Letzte Jobs). Die Tagesansicht — ursprünglich als Highlight von v1.6 geplant — wandert in eine spätere Version (v1.6.1 oder v1.7).
Neu — „Letzte Jobs”-Sektion auf Timer-Seite
Dynamische Liste der zuletzt verwendeten Jobs unter den Favoriten, zum schnellen Neustart. Pro Nutzer im Profil konfigurierbar.
- Migration 028:
users.recent_jobs_limit SMALLINT NOT NULL DEFAULT 5 CHECK (>=0 AND <=25). 0 = Sektion komplett ausblenden. - Backend: neuer Endpoint
GET /jobs/recent(favoriteHandler.recentJobs). Liefert die N zuletzt verwendeten distinkten Jobs, sortiert nach letzter Buchung, inactive/cancelled gefiltert. Limit aususers.recent_jobs_limit, defensiv geklemmt auf [0, 25]. - Frontend: dritte Sektion auf
/timermit neutraler Farbe (var(--text-muted)), nutzt dieselbeFavCard-Komponente wie die bestehenden Favoriten. Nach Timer-Stop wird die Liste neu geladen. - Profil-Seite: neues Feld „Letzte Jobs auf der Timer-Seite” mit Hinweistext und Range-Limit.
GET /me+PUT /meumrecent_jobs_limiterweitert.
Neu — Bulk-Edit Iteration 3: Datum + Uhrzeit im Bulk
In der Reports-Multi-Select-Toolbar lassen sich jetzt auch Datum und Uhrzeit (Start + Ende) auf mehrere Buchungen gleichzeitig anwenden — deckt den häufigen Fall „Meeting mit 5 Personen war auf dem falschen Tag oder mit falschen Uhrzeiten” sauber ab.
- Backend (
POST /entries/bulk-update): akzeptiert jetztdate(YYYY-MM-DD),start_time(HH:MM),end_time(HH:MM). Die Felder sind unabhängig kombinierbar: nur Datum schiebt Buchungen auf einen neuen Tag (Uhrzeiten bleiben), nur Uhrzeit vereinheitlicht die Zeiten (Datum bleibt), beides zusammen setzt alles fest. - Skip-Logik: bei Mehrtages-Buchungen, deren neues Ende vor dem neuen
Start läge, wird der Eintrag mit
error.end_before_startübersprungen — kein Update mit kaputter Validierung. - Datum/Zeit-Berechnung in der Tenant-Timezone (aus
tenants.timezone, DefaultEurope/Berlin). PostgreSQLTIMESTAMPTZkonvertiert beim Schreiben automatisch nach UTC. - Dauer wird neu berechnet mit der Tenant-spezifischen Rundungsregel
(
tenants.rounding_minutes). - Frontend: zwei neue FieldGroups („Datum verschieben (Uhrzeit bleibt)” und „Uhrzeit setzen (Start + Ende)”) im BulkEditModal mit Checkbox-pro-Feld-Pattern, Validation und Preview im Confirmation-Step.
- Fix:
tzdata-Paket ins Alpine-Docker-Image installiert. Ohne das gabtime.LoadLocation("Europe/Berlin")nil zurück und nachfolgende.In(nil)-Aufrufe haben paniciert. Defensive Fallback-Kaskade im Go-Code: tenant.timezone → Europe/Berlin → UTC.
Verbessert — DateTime-Eingabe „Harvest-Stil”
Die Eingabe für Datum/Uhrzeit-Felder war umständlich, weil der flatpickr-Popup automatisch bei Klick ins Feld aufging und den Tippe- Bereich verdeckte. Jetzt:
clickOpens: false+wrap: truein der DateInput/DateTimeInput- Komponente: Picker öffnet sich nicht mehr automatisch, dafür gibt es einen expliziten Calendar-Icon-Button rechts im Feld. Tippen direkt im Input bleibt aktiv (allowInput: true).- Neues
IconCalendar(14×14 SVG) konsistent zur Icon-Bibliothek. - CSS-Wrapper mit Padding-Right für den Icon-Button, Dark-Mode-Hover
via
var(--c-primary).
Neu — Auto-Sync Ende-Datum bei Datum-Wechsel im Start
Wenn man im Bearbeiten-Modal das Datum des Starts wechselt (z. B. weil die Buchung am falschen Tag landete), zieht das Ende-Datum automatisch mit — Ende-Uhrzeit bleibt.
- Neuer Helper
lib/syncEndDate.tsmit Sicherheits-Regeln:- Sync nur wenn das Datum geändert wurde (nicht reine Uhrzeit).
- Sync nur bei Eintages-Buchungen (vorher Start.date == Ende.date) — Mehrtages-Buchungen werden in Ruhe gelassen, damit wir nicht versehentlich Ende < Start erzwingen.
- Eingesetzt im EntryEditModal („Erweitert bearbeiten”) und in den Inline-Edit-Forms von Entries.tsx und Reports.tsx.
Behoben — „Meine Buchungen” + „Berichte”: Default-Monat und Stale-Closure
Zwei Bugs zusammen gefixt, weil sie denselben Code-Bereich betrafen.
- Default-Monat:
/entriesund/reportsdefaulten jetzt auf den aktuellen Monat statt mit leeren Filtern zu starten. Vorher: Seite initial leer, User musste erst „Aktueller Monat” + „Auswerten” klicken, was nicht selbsterklärend war. - Stale-Closure:
buildQuery/buildParamswarenuseCallback-Closures über den Filter-State. Bei synchronemsetFrom(...)+load()im selben React-Tick lasen sie noch die alten Werte → „Auswerten” funktionierte nicht zuverlässig direkt nach einer Filter-Änderung. Workaround war „vor jeder Änderung Zurücksetzen klicken”. - Saubere Lösung: ein einziges
useEffect([reloadTick])führt jetzt den Fetch durch und liest Filter-Werte aus dem commit-frischen State. Trigger-Funktionen (Auswerten, Pagination, Modal-Saves) bumpen nur noch den Tick. Identisches Pattern in beiden Seiten. - UI-Folge: Hinweistext „Bitte vor Veränderung der Filter immer
zurücksetzen” raus, Reset-Button auf invertierten Auswerten-Stil
umgestellt (weiß +
#9D2AC6Rahmen + Schrift).
Layout — Laufende Timer ganz oben
Auf der Timer-Seite steht die „Läuft gerade”-Tabelle jetzt vor den Favoriten-/Letzte-Jobs-Karten. Wer einen laufenden Timer hat, sieht ihn sofort und kann ihn direkt stoppen, ohne zu scrollen.
- Empty-State-Bedingung um
recentJobs.length === 0erweitert, damit „Kein Timer läuft. Wähle einen Favoriten oder starte einen neuen Timer” nicht fälschlich erscheint wenn der User schon gebucht hat aber keine Favoriten gepinnt sind.
Sonstiges
- ROADMAP umsortiert: User-Feedback-Punkte zu v1.6 gebündelt, Monitoring auf v1.7, Budget-PDF auf v1.8, Public Signup auf v1.9. „Bulk-Edit Iteration 3” als „Offen” notiert (jetzt erledigt mit diesem Release).
- Swagger-Doku via
swag initfür die neuen Endpoints/Felder regeneriert. - i18n-Keys (DE + EN) für
bulk_edit.field.{date,time},bulk_edit.{date,time}_hint,bulk_edit.error.{date_required, time_required, time_order},timer.recent.section,settings.profile.recent_jobs_limit_*,common.back.
[1.5.1] — 2026-05-19
Bulk-Edit für Buchungen, JWT-Claim-Fix, Build-Performance-Fix.
Patch-Release mit drei voneinander unabhängigen Verbesserungen, die zusammen beim Test mit den ersten echten Tenants aufgefallen sind.
Neu — Bulk-Edit (Iteration 2) in Reports
In der Berichte-Seite gibt es bisher eine Multi-Select-Toolbar, die nur den Status (Gebucht/Geprüft/Verrechnet/Storniert) für mehrere Buchungen umstellen konnte. Mit v1.5.1 kommt ein zweiter Button „Erweitert bearbeiten…” dazu, der ein Modal öffnet, in dem mehrere Buchungen auf einmal in andere Account/Project/Job-Kombinationen verschoben, mit einer neuen Notiz überschrieben, auf einen anderen Abrechnungs-Prozentwert gesetzt oder einem anderen Nutzer zugewiesen werden können.
Frontend (components/BulkEditModal.tsx, pages/Reports.tsx):
- Checkbox-pro-Feld-Pattern: jedes der vier Bulk-Felder hat einen eigenen „auf alle anwenden”-Schalter. Nur aktivierte Felder werden ans Backend geschickt — Felder ohne Häkchen bleiben unverändert.
- Cascading-Dropdowns für Account → Projekt → Job (identische Logik wie im Einzel-Edit-Modal).
- Confirmation-Step vor dem Absenden mit Preview-Liste der ersten 8 betroffenen Buchungen + Warnhinweis „nicht rückgängig zu machen”.
- Bewusst ohne Zeit-Felder — siehe ROADMAP, „Bulk-Edit Iteration 3”.
- User-Reassign nur für Manager/Admin sichtbar.
- i18n-Keys unter
bulk_edit.*in DE + EN.
Backend (handlers_entries.go, main.go, swagger_types.go):
- Neue Route
POST /entries/bulk-updatemit Body{ ids, changes }. - Pro Eintrag: Tenant-Check, Period-Lock-Check, Status-Filter (invoiced und cancelled werden für Manager übersprungen, nicht abgewiesen — Admin darf alles).
- Job- und User-Reassign werden einmalig vor der Schleife gegen den Tenant geprüft (nicht pro Eintrag).
billable_percentwird auf 0–100 geklemmt.- Pro tatsächlich geänderter Buchung wird ein Audit-Log-Eintrag in
time_entry_historymit den alten Werten geschrieben. - Response:
{ updated, skipped, errors }— der Toast im Frontend zeigt beides an. - Volle Swagger-Annotation mit
BulkUpdateRequest/BulkUpdateResponse-Typen.
Behoben — JWT-Claim-Namen (Frontend ↔ Backend Drift)
Versteckter Bug, der bei zwei realen Tenants dazu führte, dass Nutzer im „Meine Buchungen”-Tab fremde Einträge ihres Tenants gesehen haben (kein Cross-Tenant-Leck — der Filter „nur eigene” griff einfach nicht).
Das Backend (auth.go) schreibt die JWT-Claims als uid und tid
(Custom-Felder), nicht als die JWT-Standard-Felder sub und tenant_id.
Das Frontend (AuthContext.tsx) las aber die Standard-Felder, was als
undefined zurückkam — beim API-Call /entries?user_id=... wurde dann
der Parameter leer gelassen und das Backend lieferte die Default-Sicht
(alle Buchungen des Tenants, nicht nur die eigenen).
Fix: Frontend liest jetzt uid/tid aus dem dekodierten Token.
Bestandsnutzer müssen sich einmal aus- und einloggen, damit der neu
parsende Code greift — der Token selbst ändert sich nicht.
Behoben — Vite-Build von 12 min auf 42 s
Der make deploy-Build hing seit Tagen bei ~12 min mit 0 % CPU — reine
I/O-Wartezeit. Ursache: das Projekt lag unter ~/Documents/..., und
iCloud Drive (fileproviderd, cloudd, bird) plus Spotlight
(mdworker_shared) blockierten den Disk-I/O. Ohne den ~/Documents-Zwang
schreibt Vite die Build-Artefakte mit normaler Geschwindigkeit raus.
Fix: Projekt wurde nach ~/dev/daagwerkCode verschoben (als Kopie,
das Original unter ~/Documents/development/daagwerkCode bleibt
unangetastet als historisches Backup). Deploy-Zeit ist von 12 min auf
42 s runter; davon sind 29 s der go build im Docker-Container auf
dem Server (nicht mehr unser Rechner). CLAUDE.md-Pfade entsprechend
aktualisiert.
Neu — make sync-backup-Target
Neues Makefile-Target, das das Arbeitsverzeichnis nach ~/Documents/...
zurück-rsynct (einseitig, ohne node_modules/dist/.git/worktrees),
damit iCloud eine zweite Kopie hat. Vor jedem git tag ausführen
(Policy in CLAUDE.md festgehalten).
Sonstige Änderungen
time_entry_history-Einträge enthalten jetzt auch das altejob_id, damit eine spätere History-Ansicht das Verschieben zwischen Jobs nachvollziehen kann.common.back-i18n-Key (DE + EN) für den „Zurück”-Button im BulkEditModal-Confirmation-Step.
1.5.0 — 2026-05-18
Auslastungs-Tab, Feiertage, Abwesenheiten, JobPage-Betrag.
Großes Release mit dem komplett neuen persönlichen Auslastungs-Bereich (MVP-1 bis MVP-4) und mehreren Backend-Verbesserungen.
Neu — JobPage-Buchungstabelle: billable_percent + amount
Die Spalten „Betrag” und „Abr. %” in der Buchungstabelle von JobPage
zeigten bisher immer „–” / „100 %”, weil das Backend die Werte nicht
mitlieferte. Behoben durch Erweiterung des JobEntry-Structs und
einer CASE-Expression im SQL, die den Betrag nur bei
billing_type='billable_hourly' berechnet (Cascade Job→Project→Account
für den Stundensatz, Basis ist duration_rounded_seconds mit Fallback
auf duration_seconds). Für billable_fixed und not_billable bleibt
amount korrekt NULL.
Neu — Auslastungs-Tab MVP-1 (persönliches Arbeitszeit-Modell)
Persönliche Soll/Ist-Übersicht der Arbeitszeit für jeden Nutzer.
Backend:
- Migration 026: neue Spalten
weekly_hours,workdays,country_code,state_codeinusers - 3 neue Endpoints, alle strict user-scoped (DSGVO) — keine Manager-/Admin-Sicht, kein
user_id-Parameter:GET /me/work-schedule— eigenes Arbeitszeit-Modell lesenPUT /me/work-schedule— Wochenstunden / Arbeitstage / Land / Bundesland setzenGET /me/work-balance?from=&to=— Soll/Ist pro Tag im Zeitraum (max. 366 Tage)
- Soll =
weekly_hours / count(workdays)an Arbeitstagen, sonst 0 - Ist = Summe
duration_rounded_seconds(Fallbackduration_seconds) allertime_entriesaußercancelled—billable_percentignoriert (reine Arbeitszeit) - Buchungen an Wochenenden/Frei-Tagen werden weiter akzeptiert; sie tauchen im grauen Frei-Tag-Balken auf (Phase 2 + 3 ergänzen Feiertage + Abwesenheiten)
Frontend:
- Profil-Tab 1: neuer Abschnitt Arbeitszeit-Modell — Wochenstunden, 7 Workday-Pills (Mo–So), Land (MVP: DE), Bundesland (16 DE-Subdivisionen — MVP-2 nutzt sie für Feiertage)
- Buchungen-Seite (
/entries): neue Tab-Bar (Cleanup-Pill-Stil) mit Tab Meine Buchungen (Bestand) und Auslastung (neu) - Auslastungs-Tab: Wochenansicht mit 7 vertikalen Balken (Mo–So), Soll-Hintergrund + Ist-Füllung
- Farb-Codierung: 0–33 %
#F72A7C· 34–50 %#F7C619· ≥ 51 %#A1E929· Frei-Tage grau (Soll-Bg#E5E5E5, Ist-Fill#383838) - Footer mit Wochensumme (Soll / Ist / Differenz, Differenz farbig grün oder pink)
- Leer-State mit Link zur Profil-Seite wenn
weekly_hoursnoch nicht gesetzt - i18n: 41 neue Keys (DE + EN), inkl. 16 Bundesland-Bezeichnungen
Neu — Auslastungs-Tab MVP-4 (Wochen-/Monats-/Jahres-Ansicht + Saldo)
Drei Zeit-Granularitäten in einem Tab, mit konsistenter Optik und Navigation.
Frontend (Entries.tsx):
- View-Switcher (Pill): Woche · Monat · Jahr
- Navigation: ‹ / Heute / › verschiebt um eine View-Einheit (Woche / Monat / Jahr)
- Zeitraum-Label rechts: „KW 20 · 11.05.–17. Mai 2026” / „Mai 2026” / „2026”
getBalanceRange(mode, cursor, locale)baut from/to + Anzeige-LabelbuildBalanceBuckets()aggregiert je View:- Woche: 7 Buckets (Mo–So, ein Tag pro Bucket)
- Monat: 4–6 Buckets nach ISO-Kalenderwoche; nur Tage des Monats summiert
- Jahr: 12 Buckets pro Kalendermonat
- Y-Skala-Logik einheitlich:
referenceTarget = max(bucket.target), das ist die 100 %-Marke. Bucket-Soll = bucket.target, Ist relativ dazu; Cap bei 100 % + ↑-Indikator bei Überbuchung. - Gridline-Step je Modus: 1h / 8h / 40h. Top-Linie immer am referenceTarget mit Stunden-Label.
- ISO-Wochen-Berechnung via Standard-Algorithmus (
getIsoWeek()). - Saldo-Footer kommt aus
data.sums.diff_hoursüber den jeweiligen Zeitraum. - i18n: 6 neue Keys DE+EN (
balance.view.*,balance.nav.*).
Backend unverändert:
/me/work-balance?from=&to=ist generisch genug, bereits in MVP-1 implementiert.- 366-Tage-Limit deckt Jahres-View ab.
Neu — Auslastungs-Tab MVP-2 (Feiertage DE, 16 Bundesländer)
Aufbauend auf MVP-1: an gesetzlichen Feiertagen ist das Soll automatisch 0, der Balken erscheint im Frei-Tag-Look (hellgrau, dunkle Outline), unter dem Datum steht der Feiertagsname.
Backend (holidays_de.go):
- Gauß-Osterformel (Anonymous Gregorian Algorithm) für Ostersonntag
- Davon abgeleitet: Karfreitag, Ostermontag, Christi Himmelfahrt, Pfingstmontag, Fronleichnam
- 9 bundesweite + 8 bundeslandspezifische Feiertage
- Buß- und Bettag (nur SN): letzter Mittwoch vor dem 23.11.
- In-Memory-Cache via
sync.Map, Key"DE-BY-2026"— pro (Jahr, Land, Bundesland) einmal berechnet - Bundesländer korrekt belegt:
- Heilige Drei Könige: BW, BY, ST
- Frauentag (08.03.): BE, MV
- Fronleichnam: BW, BY, HE, NW, RP, SL
- Mariä Himmelfahrt: SL (BY-Gemeinde-Differenzierung bewusst ausgelassen)
- Weltkindertag: TH
- Reformationstag: BB, HB, HH, MV, NI, SN, ST, SH, TH
- Allerheiligen: BW, BY, NW, RP, SL
- Buß- und Bettag: SN
holidayLookup(from, to, country, state)baut MapYYYY-MM-DD → keyüber alle Jahre im Zeitraum- Erweiterbar für AT/CH durch eigene Dateien + Country-Switch in
holidaysFor()
API-Änderung:
WorkBalanceDay.HolidayKey(statt bisher leeremHolidayName) — stabiler i18n-Key wie"good_friday"balance()lädt zusätzlichcountry_code,state_codeaususers; Feiertage werden auf das Soll des Tags angewendet (Soll = 0)
Frontend:
- TS-Type
WorkBalanceDay.holiday_key(warholiday_name) - WorkBalanceTab zeigt unter dem Datum den Feiertagsnamen kursiv, mit Tooltip + Ellipsis bei zu langem Namen
- i18n: 17 neue Keys DE+EN (
balance.holiday.*) - Hauntung: alle Frei-Tage (Wochenende, Feiertag, später Absenz) teilen dieselbe Optik — hellgrau mit dunkler Outline
Neu — Auslastungs-Tab MVP-3 (Abwesenheiten: Urlaub, Krankheit, Freizeitausgleich)
Erfassbar im Profil, automatisch im Auslastungs-Tab als Frei-Tag-Optik mit Label.
Backend (handlers_absences.go, Migration 027):
- Neue Enums
absence_kind(‘vacation’, ‘comp_time’, ‘sick’) undday_portion(‘full’, ‘half’) - Tabelle
user_absences(id, tenant_id, user_id, kind, start_date, end_date, portion, notes) - Constraints:
end_date >= start_dateundportion='half'nur beistart_date = end_date - 4 CRUD-Endpoints, alle strict user-scoped (DSGVO):
GET /me/absences— Liste sortiert nach start_date DESCPOST /me/absencesPUT /me/absences/{id}DELETE /me/absences/{id}
absenceLookup()baut MapYYYY-MM-DD → {kind, portion}über alle Absenzen die in[from, to]fallenbalance()bezieht Absenzen als Soll-Reduzierer ein:portion='full'→ Soll an dem Tag = 0portion='half'→ Soll an dem Tag = dailyTarget / 2
- Feiertag hat Vorrang vor Abwesenheit (semantisch klarer: Feiertag ist kein Urlaub)
Frontend:
- Profile.tsx: neue Section „Abwesenheiten” mit Tabelle + Modal
- Modal-Felder: Art (Urlaub/Freizeitausgleich/Krankheit), Von, Bis, Umfang-Pills (Ganzer Tag / Halber Tag, nur sichtbar wenn Von = Bis), Notiz
- Tabelle: Art, Von, Bis, Umfang, Notiz, Bearbeiten/Löschen — sortiert nach start_date
- Bearbeiten ruft dasselbe Modal mit vorbefülltem State auf
- WorkBalanceTab: Label unter dem Tagesdatum zeigt jetzt entweder Feiertag oder Abwesenheit
- Bei halber Abwesenheit: Label hat „½”-Suffix (z.B. „Urlaub (½)”)
- i18n: 26 neue Keys DE+EN (
balance.absence.*,profile.section.absences,profile.absences.*)
1.4.0 — 2026-05-16
Re-Design & Navigation-Restrukturierung + Verwaltungs-Drill-Down.
Neu — Verwaltungsbereich: Account → Projekt → Job Drill-Down
Vollständig neue Verwaltungsoberfläche für die Hierarchie-Ebenen. Ersetzt die alte Accounts.tsx-Seite durch vier dedizierte Feature-Seiten unter src/features/accounts/.
AccountsListPage (/accounts)
- Suchfeld durchsucht Accounts, Projekte und Jobs gleichzeitig
- Segmentierter Filter: Aktiv · Mit Budget · Nahe Limit
- Sortierung A–Z / Z–A
- Toggle „Deaktivierte anzeigen”
- Tabelle mit dunklem Header (
#383838), Spalten: Avatar · Name · Budget-Bar · Gebuchte Stunden · Buchungen · Aktionen - Budget-Balken: grün < 50 % · gelb 51–75 % · rot ≥ 76 %
- Pro-Zeile:
+(neues Projekt anlegen) +···(Account öffnen) - Header-Buttons: „Importieren” + „+ Neuer Account” (
#9D2AC6) - Modal zum Anlegen eines neuen Accounts (Name + Enter)
AccountPage (/accounts/:id)
- Breadcrumb: Verwalten / Accounts → klickbar
- 5 Metric-Tiles: Projekte · Jobs · Buchungen · Gebuchte Stunden · Budget (% + „Xh von Yh” Sub-Label)
- Eigenschaften: Beschreibung, Stammdaten, Stundensatz, Rundungsregel, Status, Erstellt
- Bearbeiten-Button oben rechts und am Ende der Eigenschaften (beide
#9D2AC6) - Projektübersicht als Tabelle (wie AccountsListPage): Avatar · Projektname · Jobs · Buchungen · Stunden · Budget-Balken · Aktionen
- „+ Neues Projekt”-Button (
#9D2AC6)
ProjectPage (/accounts/:id/:projectId)
- Breadcrumb: Account-Name → klickbar
- 4 Metric-Tiles: Jobs · Buchungen · Gebuchte Stunden · Budget (% + „Xh von Yh”)
- Eigenschaften: Stundensatz (mit Vererbungsanzeige), Rundungsregel, Bestellnummer, Budget (Stunden + Betrag + Zeitraum), Limit, Status
- PATCH-Endpunkt korrigiert:
/projects/:idstatt/accounts/:aid/projects/:pid - Bearbeiten-Button oben rechts und am Ende der Eigenschaften
- Job-Übersicht als Tabelle: Farbpunkt · Job-Name · Typ-Badge · Buchungen · Stunden · Limit-Balken · Aktionen
- „+ Neuer Job”-Button (
#9D2AC6)
JobPage (/accounts/:id/:projectId/:jobId)
- Breadcrumb: Account → Projekt → klickbar
- 5 Metric-Tiles: Buchungen · Gebuchte Stunden · Stundensatz (effektiv) · Limit (h + % Sub) · Budget (% + „Xh von Yh”)
- Eigenschaften: Billing-Typ · Stundensatz · Festpreis · Rundungsregel · Bestellnummer · Budget-Stunden · Limit · Status
- Bearbeiten-Button oben rechts und am Ende der Eigenschaften
- Buchungstabelle mit dunklem Header: Datum · Start · Ende · Dauer · Benutzer · Notiz-Icon · Status · Betrag · Abr.% · Aktionen
- Notiz-Icon: grün (
#A1E929) wenn Notiz vorhanden, grau wenn nicht - Inline-Edit-Zeile: Von / Bis / Notiz / Abr.% — Speichern via
PUT /entries/:id
Neu — Backend: Aggregations-Endpunkte
Neue Handler-Datei handlers_accounts_detail.go:
| Route | Beschreibung |
|---|---|
GET /accounts/list | Account-Liste mit Aggregaten (projects_count, jobs_count, booked_hours, bookings_count, budget_total_hours, budget_used_hours, budget_percent) |
GET /accounts/{id}/overview | Account-Detail + Projektliste mit Aggregaten |
PATCH /accounts/{id} | Account-Felder patchen (name, description, master_data, hourly_rate, rounding_rule, active) |
GET /accounts/{aid}/projects/{pid}/overview | Projekt-Detail + Jobliste mit Aggregaten |
PATCH /projects/{id} | Projekt-Felder patchen |
GET /accounts/{aid}/projects/{pid}/jobs/{jid}/overview | Job-Detail + letzte 20 Buchungen |
PATCH /jobs/{id} | Job-Felder patchen |
- SQL-Aggregationen via Subqueries (budget aus Projekten hochaggregiert auf Account-Ebene)
sqlPatcher: dynamischer PATCH-Builder mitmap[string]json.RawMessage— absent = skip,"null"= DB NULL- Chi-Router: statische Route
/accounts/listhat Vorrang vor parametrisierter/accounts/{id}
Neu — Unternehmens-Favoriten (Company Favorites)
- Neue Tabelle
company_favorites(Migration 025): tenant-weite Favoriten mit UNIQUE(tenant_id, job_id) - Backend: GET/POST /favorites/company, DELETE /favorites/company/{id} (bucher-Rolle blockiert)
- Timer-Seite: zwei Sektionen — Eigene Favoriten (lila #9D2AC6) + Unternehmens-Favoriten (#10A7BD)
- FavManagerModal mit Tab-Switcher (Persönlich / Unternehmen), FavAdder-Komponente (cascading Dropdowns Account→Project→Job)
- „+ Favoriten”-Button im PageHeader (teal #10A7BD)
CompanyFavoriteInterface intypes/index.ts
Neu — Profil-Seite (/profile)
- Neue dedizierte Seite (
pages/Profile.tsx) — Avatar-Klick in der Sidebar navigiert direkt dorthin - Tab 1 „Profil-Einstellungen” (alle Nutzer): Vorname, Nachname, E-Mail, Passwort ändern, Automatische Timer-Stopps
- Tab 2 „Unternehmens-Einstellungen” (Manager/Admin): Unternehmenseinstellungen (Name, Rundungsregel, Snap, Timezone, Feierabendstopp) + Daten Exportieren
- Tab-Bar im Cleanup-CI: Pill-Design, #9D2AC6 aktiver Tab, var(—bg-card) Container
- Tab 2 nur sichtbar für manager/admin/super_admin — normale Bucher sehen gar keine Tabs
Neu — WebDAV-Konfiguration in Datensicherung (/backup)
- WebDAV-Backup-Ziel Konfiguration direkt auf der Backup-Seite (oben, vor der Backup-Tabelle)
- URL, Benutzer, Passwort, Test-Verbindung, Speichern
Geändert — Cleanup-Seite
-
- Register „Favoriten” (nur Manager/Admin): kombinierte Tabelle aller Favoriten
- Favoriten-Typ-Spalte: „Persönlich” (lila #9D2AC6) / „Unternehmen” (#10A7BD) — Badge + Job-Farbe
- Alle Tab-Labels, Suche, Filter, Typ-Filter über i18n (DE + EN)
Geändert — Timer-Stopps Hinweis
- Info-Box über volle Kartenbreite (außerhalb des 480px-Form-Containers)
- Neuer 3-Absatz-Text: Prioritätsreihenfolge (Max. Laufdauer → Mittagsstopp → Feierabendstopp) + konkretes Beispiel
- Dark Mode: weiße Schrift + #F72A7C Rahmen + rgba(247,42,124,0.08) Hintergrund
Geändert — Navigation-Restrukturierung
- Avatar + Name in der Sidebar → klickbar → navigiert zu /profile
- Logout-Button bleibt separat daneben
- Bisherige Settings-Seite (/settings): Profil/Passwort/Timer-Stopps → /profile Tab 1; Unternehmenseinstellungen/Export → /profile Tab 2; WebDAV → /backup
Geändert — i18n Vervollständigung
- Cleanup-Seite: alle Tab-Labels, Suche, Filter, Typfilter, Leer-Texte
- Import-Seite: Tab-Labels, InfoCard-Texte, Radio-Labels, Konflikt-Modi
- Favoriten-Tab: Labels
Geändert — Status-Farben (global)
Neue einheitliche Status-Farben in Entries.tsx, Reports.tsx und JobPage.tsx:
| Status | Vorher | Nachher |
|---|---|---|
| Gebucht | CSS-Variable (blau) | #10A7BD |
| Verrechnet | Violett #7c3aed | #383838 |
| Storniert | CSS-Variable (rot) | #F72A7C |
Geändert — Buchungsliste (Meine Buchungen)
- Spalte „Kunde / Account / Job” aufgeteilt in drei separate Spalten: Account · Projekt · Job
- Ellipsis-Truncation bei langen Texten: Account max. 160 px, Projekt max. 180 px, Job max. 150 px
- Hover zeigt vollen Text via native
title-Tooltip - Neue i18n-Keys:
entries.col.account,entries.col.project,entries.col.job(DE + EN) colSpanin Inline-Edit-Zeile undSkeletonRow colsauf 11 aktualisiert
Neu — TypeScript Feature-Typen
Neue Datei src/features/accounts/types.ts:
AccountListItem,ProjectListItem,JobListItemAccountDetail,ProjectDetail,JobDetailAccountRef,ProjectRefJobEntry(inkl.billable_percent?: number,amount?: number)
Design-System / CI
- Primär-Aktionsfarbe für Account-Verwaltung:
#9D2AC6(Lila/Violett) - Alle Bearbeiten- und Erstellen-Buttons im Verwaltungsbereich einheitlich auf
#9D2AC6 PageHeader-Komponente:breadcrumb-Prop vonstringaufReact.ReactNodeerweitert- Profil-Seite Tab-Bar: Pill-Design identisch zu Cleanup (bg-card Container, radius-lg, active #9D2AC6)
- Unternehmens-Favoriten Akzentfarbe: #10A7BD (teal/cyan) — „+ Favoriten” Button, Company FavCard, Badges
- Dark Mode Info-Box: #F72A7C Rahmen, weiße Schrift
1.3.0 — 2026-05-07
Backup-Restore, WebDAV-Backup-Ziel und Self-Service Daten-Export.
Neu — Backup-Restore
- Restore-Dialog auf der Backup-Seite: beliebiges Backup aus der Liste auswählen und wiederherstellen
- Zwei Restore-Modi: Vollständig (alles zurücksetzen) oder Bis zu einem Datum (transaktionale Daten werden auf Stichtag gefiltert)
- Vor jedem Restore: automatische Sicherung des Ist-Zustands (Safety-Backup)
- Bestätigungs-Checkbox + visueller Warnbanner im Dialog — fehlklick-sicher
- Tenant-isoliert: Restore eines Tenants hat null Einfluss auf andere Tenants
- Migration 021 (
webdav_url,webdav_user,webdav_passwordintenants-Tabelle)
Neu — WebDAV-Backup-Ziel
- Tenant-Admins können in den Einstellungen einen WebDAV-Server als externes Backup-Ziel konfigurieren (z.B. Nextcloud, ownCloud)
- Tägliche Backups werden automatisch zusätzlich auf den WebDAV-Server hochgeladen
- Passwort wird serverseitig gespeichert, nie im Frontend angezeigt (
webdav_configured: bool) - „Verbindung testen”-Button sendet PROPFIND-Request und zeigt Ergebnis sofort an
- Neuer Einstellungs-Abschnitt „WebDAV-Backup-Ziel” (nur für Admin/Manager sichtbar)
Neu — Self-Service Daten-Export
- Neuer Einstellungs-Abschnitt „Daten exportieren” für alle Nutzer
- ZIP-Download mit:
accounts.json,projects.json,jobs.json,time_entries.json,time_entries.csv,users.json,README.txt - CSV enthält alle Zeitbuchungen mit vollständigen Kontext-Spalten (Account, Projekt, Job, Nutzer)
- Erfüllt DSGVO Art. 20 (Datenportabilität)
Backend / Infrastruktur
POST /backups/{id}/restore— neuer Restore-Endpoint mit optionalemrestore_until-ParameterPOST /backups/webdav-test— Test-Endpoint für WebDAV-VerbindungsprüfungGET /export/tenant.zip— ZIP-Streaming-Export (archive/zip + encoding/csv)handlers_backup.goerweitert:restore(),readBackupFile(),performRestore(),filterRecordsByDate(),uploadWebDAVIfConfigured(),uploadToWebDAV(),testWebDAV()handlers_export_zip.goneu:tenantZip()aufexportHandlerhandlers_tenant.goerweitert: WebDAV-Felder inTenantSettings, GET + PUT entsprechend aktualisiert
1.2.0 — 2026-05-07
SMTP-Integration, Passwort-Reset-Seiten, automatische Timer-Stopps und Datensicherung.
Neu — E-Mail & Passwort-Reset
- SMTP-Versand vollständig konfiguriert und getestet (Migadu, STARTTLS Port 587)
- Neue Seite „Passwort vergessen” (
/forgot-password): E-Mail eingeben → Link mit Reset-Token per Mail - Neue Seite „Passwort zurücksetzen” (
/reset-password?token=...): Neues Passwort setzen mit Bestätigung - Rate-Limiting: max. 3 Reset-Anfragen pro E-Mail-Adresse pro Stunde (Redis-basiert)
- Link „Passwort vergessen?” auf der Login-Seite ergänzt
Neu — Automatische Timer-Stopps
- Nutzer können in den Einstellungen drei automatische Stopp-Regeln konfigurieren:
- Max. Laufdauer — Timer stoppt nach Ablauf der maximalen Laufzeit (Eingabe in h:mm)
- Mittagszeit — Timer stoppt täglich um eine definierte Uhrzeit (Mittagspause)
- Feierabend (Nutzer) — Timer stoppt täglich um die persönliche Feierabendzeit
- Tenant-Admins können zusätzlich eine unternehmensweite Feierabendzeit konfigurieren
- Tenant-Admins können die Zeitzone des Unternehmens festlegen (Europa, US, UTC)
- Alle zeitbasierten Stopps berücksichtigen die konfigurierte Tenant-Zeitzone korrekt
- Nach einem automatischen Stopp wird die Tenant-Aufrundungsregel angewendet
- Info-Banner in den Einstellungen erklärt: Frontend-Timer zeigt weiter (rein kosmetisch) — der tatsächliche Stopp und die Rundung wurden server-seitig bereits durchgeführt
Neu — Datensicherung
- Täglicher automatischer Backup-Cron um 02:00 Uhr (systemd-Timer
daagwerk-backup.timer) - Vollständiger gzip-JSON-Snapshot aller Tenant-Daten (Accounts, Projekte, Jobs, Buchungen, Nutzer, …)
- 28-Tage Rolling Retention: ältere Backups werden automatisch gelöscht
- Admin-UI „Datensicherung” in der Navigation: Backup-Liste, Download-Button, „Jetzt sichern”-Button
- Migration 020:
tenant_backups-Tabelle für Backup-Metadaten
Neu — Backend / Infrastruktur
POST /internal/timer-cleanup— interner Endpoint, abgesichert viaX-Internal-Secret-HeaderPOST /internal/backup-run— interner Endpoint für tägliche Backups- systemd-Timer (
daagwerk-timer-cleanup.timer) auf dem Server: Aufruf jede Minute,AccuracySec=10s - systemd-Timer (
daagwerk-backup.timer) auf dem Server: täglich 02:00 Uhr - Docker-Volume
backup_datafür persistente Backup-Speicherung - Migration 019: neue Spalten
stop_max_minutes,stop_lunch_time,stop_eod_timeinusers;timezone,stop_eod_timeintenants
Technisch
- pgx v5: PostgreSQL
TIME-Spalten werden als*stringgescannt +parseTimeStr()konvertiert sie - Hilfsfunktionen
minsToHMM()/hmmToMins()in Settings.tsx für h:mm-Darstellung type="text"mitinputMode="numeric"statttype="number"für Zeitdauer-Inputs (Browser-Spinner überdeckten sonst die letzte Ziffer)
1.1.0 — 2026-05-05
Swagger API-Dokumentation und CSV-Import für Zeitbuchungen.
Neu — API-Dokumentation
- Vollständige OpenAPI 2.0 Spezifikation über
swaggo/swaggeneriert - Swagger UI öffentlich erreichbar unter https://api.daagwerk.de/docs
- Alle Endpoints dokumentiert: Summary, ausführliche Description, Parameter, Response-Typen, Auth-Hinweise
- Sprache: Englisch (internationaler Standard)
Neu — CSV-Import für Zeitbuchungen
- Neuer Tab „Zeitbuchungen” auf der Import-Seite neben dem bestehenden Hierarchie-Import
- Vorlage: vorausgefüllte CSV mit allen aktiven Jobs des Tenants (Auth-required, pro Nutzer angepasst)
- Format:
account_name, project_name, job_name, date, start_time, end_time, notes, billable[, user_email]— separate Datum- und Zeitspalten für maximale Excel-Kompatibilität - Konfliktmodus (ganzdateiweise konfigurierbar vor dem Import):
Überspringen— bestehende Buchungen bleiben unberührtErsetzen— bestehende Buchungen werden überschrieben
- Vorschau: Zeilenweise Validierung vor dem Import (kein DB-Write) mit Status ok / Konflikt / Fehler
- Rollen: Bucher importieren nur für sich selbst; Manager und Admins können per
user_email-Spalte für andere Nutzer importieren - Import-Protokoll: farbcodierte Ergebnistabelle (grün = importiert, orange = übersprungen, rot = Fehler); abrufbar für 7 Tage
- Frühere Imports: Liste der letzten 10 Import-Protokolle mit aufklappbarer Zeilenansicht
- Tenant-Aufrundungsregel wird beim Import automatisch angewendet
Technisch
- Neue Datenbanktabelle
import_reports(Migration 018) mit JSONB-Zeilenspeicherung und automatischem Ablauf nach 7 Tagen - 5 neue API-Endpoints:
GET /import/time-template,POST /import/time/preview,POST /import/time,GET /import/time/reports,GET /import/time/reports/{id} - Swagger-Dokumentation für alle neuen Endpoints
1.0.0 — 2026-04-20
Erster öffentlicher Release. Vollständige Grundfunktionalität für professionelles Zeiterfassungs-SaaS.
Neu — Kern-Features
Authentifizierung & Mandantenfähigkeit
- Mandantenfähige (Multi-Tenant) Architektur mit vollständiger Datentrennung per Row-Level-Security
- Eigenes JWT-basiertes Auth-System (kein externer Provider erforderlich)
- Rollen-System: Bucher · Manager · Admin · Super-Admin
- Passwort-Reset per E-Mail-Link
- Sichere Session-Verwaltung mit Refresh-Token-Rotation
Hierarchie: Account → Project → Job
- Account — oberste Ebene; steht für Kunden, Auftraggeber, Abteilungen oder Kostenstellen
- Project — mittlere Ebene; Kampagnen, Initiativen, Aufträge eines Accounts
- Job — buchbare Tätigkeit/Position innerhalb eines Projekts
- Stundensatz-Vererbung (Tenant → Account → Project → Job) mit individuellem Override
- Aufrundungsregel je Ebene (none / 5min / 10min / 15min / 30min / 60min)
- Bestellnummern für Accounts und Jobs
- Abrechnungstypen je Job: stündlich abrechenbar · Festpreis · nicht abrechenbar
- Budgets (Stunden + Betrag) mit Limit und Auslastungsanzeige in Echtzeit
- Inaktiv-/Aktiv-Verwaltung auf allen Ebenen
Timer
- Beliebig viele Timer gleichzeitig pro Nutzer
- Timer starten mit Freitextsuche (Account / Project / Job)
- Favoriten: häufig genutzte Account-Job-Kombinationen mit einem Klick starten
- Automatischer Timer-Stopp konfigurierbar
Zeiterfassung & Buchungen
- Manuelle Buchungen (von/bis oder Dauer)
- Teilweise Abrechenbarkeit je Buchung (0–100 % billable)
- Notizen pro Buchung mit Freitexteingabe
- Textbausteine/Vorlagen: vordefinierte Notizvorlagen global, pro Account und pro Job
- Geplante Buchungen (Forecast): erstellen → aktivieren → bestätigen
- Status-Workflow: Gebucht → Geprüft → Abgerechnet · Storniert
- Perioden-Sperre: Wochen und Monate abschließen (Manager-Funktion)
- Buchungen bearbeiten: Inline-Bearbeitung in Tabellenansicht
- Massenauswahl und Statusänderung für mehrere Buchungen gleichzeitig
Favoriten
- Favoriten-Verwaltung: Kombinationen aus Account + Job speichern
- Individuelle Sortierung, Umbenennung, Deaktivierung
- Direkt im Timer nutzbar
Reports & Auswertungen
- Vollständige Buchungsliste mit Filterung nach: Account, Project, Job, Nutzer, Abrechnungstyp, Status, Datumsbereich, Freitext
- Inline-Bearbeitung direkt in der Reportansicht
- Budget-Auslastung je Account/Project als Fortschrittsbalken
- Dashboard: Tagesstunden, Wochenstunden, laufende Timer, Top-Accounts
Export & Import
- Export als PDF, CSV, JSON, XML
- CSV-Import für Accounts, Projects und Jobs (Bulk-Anlage)
- Konfigurierbarer Export: Spaltenauswahl, Kundenstammdaten-Option
Einstellungen & Verwaltung
- Nutzerprofil: Name, E-Mail, Passwort ändern
- Favoriten-Verwaltung in den Einstellungen
- Cleanup-Seite: inaktive Accounts/Projects/Jobs einsehen und bereinigen
- Tenant-weite Einstellungen für Admins (Stundensatz, Rundungsregel)
Neu — Oberfläche
- Dark Mode / Light Mode — manuell umschaltbar, System-Präferenz als Standard
- Internationalisierung — vollständig auf Deutsch und Englisch übersetzt (i18n via react-i18next)
- Ladeanimationen — Skeleton-Loader und Spinner überall wo Daten geladen werden
- Toast-Benachrichtigungen — nicht-blockierende Erfolgs-, Fehler- und Infomeldungen
- Datepicker — browserübergreifend konsistenter Kalender (flatpickr, deutsch, Dark-Mode-fähig)
Neu — Plattformen
- Web (primär) — React + Vite, auslieferbar als statisches Build hinter Nginx
- iOS App v0.1.0 — SwiftUI, Timer starten/stoppen, Buchungsliste, Einstellungen, Dark Mode
- watchOS App v0.1.0 — Timer sehen, starten, stoppen; Complications (Circular, Rectangular, Corner, Inline)
Neu — Backend & Infrastruktur
- Go-Monolith mit PostgreSQL und Redis
- 17 Datenbankmigrationen (vollständig versioniert)
- REST-API mit vollständiger Rollenprüfung
- Docker-basiertes Deployment (VPS: IONOS, Deutschland, DSGVO-konform)
- Nginx Reverse Proxy + Let’s Encrypt SSL
make deploy— ein Befehl für vollständiges Deployment
Behoben
- Dashboard: Schwarzer Bildschirm nach Login wenn API-Antwort leer war
- Dashboard: Absturz in Top-Accounts-Karte bei fehlendem Array-Wert
- Accounts-Seite: Alle aufgeklappten Projekte wurden nach dem Speichern eines Accounts zugeklappt
- Accounts-Seite: Variablen-Referenzfehler im ProjectBlock nach Komponenten-Umbenennung
- Reports / Buchungen: Spinner-Pfeile bei Number-Inputs im Dark Mode nicht sichtbar
- Reports: Filter-Aktiv-Punkt zu klein und kaum sichtbar
- Checkboxen: Im Dark Mode zu klein und unsichtbar dargestellt
- Datepicker: Safari-Bug beim Eingeben der Jahreszahl in nativen Date-Inputs
0.9.0 — 2026-04-10
Feature-Complete-Stand vor dem 1.0-Release. Primär interne Entwicklungsversion.
Neu
- Vollständige Umbenennung der Hierarchie: Client → Account → Project → Job (Backend + Frontend + Datenbank)
- Migration 017:
clients-Tabelle →accounts, alle Fremdschlüssel und Indices angepasst - Neue API-Routen:
/accounts,/accounts/:id/projects(ersetzt/clients,/clients/:id/accounts) - Alle Go-Handler, TypeScript-Typen und React-Komponenten auf neue Namensgebung umgestellt
- i18n-Keys vollständig auf neue Hierarchie-Begriffe aktualisiert (DE + EN)
Behoben
- Templates-Seite: Scope-Level
account→projectin Anzeige und API-Parametern - Cleanup-Seite: Zähler und Purge-Parameter auf neue Feldnamen aktualisiert
- Einstellungen: Favoritenpicker zeigte falsche Auswahlebene
[0.8.0] — 2026-04-05
Reports, Export und Buchungs-Workflow.
Neu
- Reports-Seite: Buchungsliste mit vollständiger Filterzeile
- Inline-Bearbeitung von Buchungen direkt in Reports und Buchungsliste
- Massenauswahl + Statuswechsel für mehrere Einträge
- Export als PDF (Go-seitig generiert), CSV, JSON, XML
- Konfigurierbare Export-Optionen (Spaltenauswahl, Kundenstammdaten)
- Budget-Auslastungsbalken in Reports und Account-Übersicht
- Textbausteine/Vorlagen: Verwaltungsseite und Auswahl beim Bearbeiten einer Buchung
Behoben
- Timer: Laufende Timer wurden nach Seiten-Reload nicht sofort angezeigt
- Buchungen: Doppelklick auf Inline-Edit öffnete zwei Editoren gleichzeitig
[0.7.0] — 2026-03-28
Dashboard, Favoriten und Perioden-Sperre.
Neu
- Dashboard: Tagesstunden, Wochenstunden, monatliche Auslastung, laufende Timer
- Dashboard: Admin-Ansicht mit Top-Accounts und Teamauslastung
- Favoriten: Erstellen, benennen, sortieren, direkt aus Timer nutzen
- Perioden-Sperre: Manager können Wochen/Monate für weitere Buchungen sperren
- Geplante Buchungen (Forecast): Anlegen, aktivieren, bestätigen
[0.6.0] — 2026-03-15
Internationalisierung und Dark Mode.
Neu
- Vollständige i18n-Infrastruktur (react-i18next): alle Seiten auf DE und EN übersetzt
- Dark Mode / Light Mode mit System-Präferenz und manuellem Toggle
- Sprachumschalter (DE / EN) in der TopBar
[0.5.0] — 2026-03-01
Zeiterfassung: manuell, Status-Workflow, Teilabrechenbarkeit.
Neu
- Manuelle Buchungen (von/bis oder Dauer)
- Status-Workflow: Gebucht → Geprüft → Abgerechnet · Storniert
- Teilweise Abrechenbarkeit (billable_percent 0–100 %) pro Buchung
- Notizen mit Freitexteingabe je Buchung
- Abrechnungstypen je Job: stündlich · Festpreis · nicht abrechenbar
[0.4.0] — 2026-02-15
Account-Hierarchie und Stundensatz-System.
Neu
- Dreistufige Hierarchie: Account → Project → Job (damals noch: Client → Account → Job)
- Stundensatz-Vererbung über alle Ebenen mit individuellem Override
- Aufrundungsregel (none / 5 / 10 / 15 / 30 / 60 Minuten) je Ebene
- Budgets (Stunden + Betrag) mit Limit-Anzeige
- Bestellnummern für Accounts und Jobs
- CSV-Import für Accounts und Jobs
[0.3.0] — 2026-02-01
Timer-Kern.
Neu
- Mehrere Timer gleichzeitig pro Nutzer
- Suche beim Timer-Start (Account / Project / Job)
- Timer automatisch stoppen
[0.2.0] — 2026-01-20
Multi-Tenant-Architektur und Rollen.
Neu
- Vollständige Mandantenfähigkeit mit Row-Level-Security in PostgreSQL
- Rollen: Bucher · Manager · Admin · Super-Admin
- Passwort-Reset per E-Mail
[0.1.0] — 2026-01-10
Erste lauffähige Version — Fundament.
Neu
- JWT-Authentifizierung (Login, Logout, Token-Refresh)
- Go-Backend mit PostgreSQL und Redis
- React-Frontend (Vite + TypeScript)
- Grundlegendes Design-System: Farben, Typografie, Toast, Dark Mode vorbereitet
- Erste Datenbankmigrationen (Tenants, Users, Rollen)