Add ticket time tracking with reports and entry management.

Support start/stop timer, manual durations, grouped reports, and per-ticket edit/delete of time entries with undo.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Skruecken
2026-10-19 11:05:10 +02:00
co-authored by Cursor
parent 41feba11de
commit bfc1cbd4d9
23 changed files with 2149 additions and 94 deletions
+75
View File
@@ -0,0 +1,75 @@
package command_test
import (
"testing"
"time"
"task/internal/command"
"task/internal/ticket"
)
func TestUpdateTimeEntryUndo(t *testing.T) {
dir := t.TempDir()
store := ticket.NewJSONTimeStore(dir)
h := command.NewHistory()
created, err := store.Create(ticket.TimeEntry{
TicketID: "t1",
ProjectID: "inbox",
Start: time.Now().UTC().Add(-time.Hour),
Duration: time.Hour,
Source: ticket.SourceManual,
})
if err != nil {
t.Fatal(err)
}
after := created
after.Duration = 30 * time.Minute
after.End = after.Start.Add(after.Duration)
if err := h.Do(&command.UpdateTimeEntry{Store: store, Before: created, After: after}); err != nil {
t.Fatal(err)
}
list, _ := store.ListByTicket("t1")
if len(list) != 1 || list[0].Duration != 30*time.Minute {
t.Fatalf("after update: %+v", list)
}
if err := h.Undo(); err != nil {
t.Fatal(err)
}
list, _ = store.ListByTicket("t1")
if len(list) != 1 || list[0].Duration != time.Hour {
t.Fatalf("after undo: %+v", list)
}
}
func TestRemoveTimeEntryUndo(t *testing.T) {
dir := t.TempDir()
store := ticket.NewJSONTimeStore(dir)
h := command.NewHistory()
created, err := store.Create(ticket.TimeEntry{
TicketID: "t1",
ProjectID: "inbox",
Start: time.Now().UTC(),
Duration: 15 * time.Minute,
Source: ticket.SourceTimer,
})
if err != nil {
t.Fatal(err)
}
if err := h.Do(&command.RemoveTimeEntry{Store: store, Snapshot: created}); err != nil {
t.Fatal(err)
}
list, _ := store.List()
if len(list) != 0 {
t.Fatalf("expected empty after remove, got %d", len(list))
}
if err := h.Undo(); err != nil {
t.Fatal(err)
}
list, _ = store.List()
if len(list) != 1 || list[0].ID != created.ID {
t.Fatalf("after undo: %+v", list)
}
}