summaryrefslogtreecommitdiff
path: root/internal/actions/timer.go
diff options
context:
space:
mode:
authorT <t@tjp.lol>2025-09-29 15:04:44 -0600
committerT <t@tjp.lol>2025-09-30 11:40:45 -0600
commit7ba68d333bc20b5795ccfd3870546a05eee60470 (patch)
tree12dc4b017803b7d01844fd42b9e3be281cbbd986 /internal/actions/timer.go
parentbce8dbb58165e443902d9dae3909225ef42630c4 (diff)
Support for archiving clients and projects.HEADmain
Diffstat (limited to 'internal/actions/timer.go')
-rw-r--r--internal/actions/timer.go74
1 files changed, 57 insertions, 17 deletions
diff --git a/internal/actions/timer.go b/internal/actions/timer.go
index a7e7bbb..d5a85e1 100644
--- a/internal/actions/timer.go
+++ b/internal/actions/timer.go
@@ -11,10 +11,10 @@ import (
// PunchIn starts a timer for the specified client/project
// Use empty strings for client/project to use most recent entry
-func (a *actions) PunchIn(ctx context.Context, client, project, description string, billableRate *float64) (*TimerSession, error) {
+func (a *actions) PunchIn(ctx context.Context, client, project, description string, billableRate *float64, autoUnarchive bool) (*TimerSession, error) {
// If no client specified, delegate to PunchInMostRecent
if client == "" && project == "" {
- session, err := a.PunchInMostRecent(ctx, description, billableRate)
+ session, err := a.PunchInMostRecent(ctx, description, billableRate, autoUnarchive)
if err != nil {
// Convert "no recent entries" error to "client required" for better UX
if errors.Is(err, ErrNoRecentEntries) {
@@ -73,6 +73,28 @@ func (a *actions) PunchIn(ctx context.Context, client, project, description stri
return nil, ErrClientRequired
}
+ // Check if client is archived
+ if resolvedClient.Archived != 0 {
+ if !autoUnarchive {
+ return nil, ErrArchivedClient
+ }
+ // Auto-unarchive the client
+ if err := a.UnarchiveClient(ctx, resolvedClient.ID); err != nil {
+ return nil, fmt.Errorf("failed to unarchive client: %w", err)
+ }
+ }
+
+ // Check if project is archived
+ if resolvedProject != nil && resolvedProject.Archived != 0 {
+ if !autoUnarchive {
+ return nil, ErrArchivedProject
+ }
+ // Auto-unarchive the project
+ if err := a.UnarchiveProject(ctx, resolvedProject.ID); err != nil {
+ return nil, fmt.Errorf("failed to unarchive project: %w", err)
+ }
+ }
+
var stoppedEntryID *int64
// Check for identical timer if one is active
@@ -119,7 +141,7 @@ func (a *actions) PunchIn(ctx context.Context, client, project, description stri
}
// PunchInMostRecent starts a timer copying the most recent time entry
-func (a *actions) PunchInMostRecent(ctx context.Context, description string, billableRate *float64) (*TimerSession, error) {
+func (a *actions) PunchInMostRecent(ctx context.Context, description string, billableRate *float64, autoUnarchive bool) (*TimerSession, error) {
// Get most recent entry
mostRecent, err := a.queries.GetMostRecentTimeEntry(ctx)
if err != nil {
@@ -135,6 +157,37 @@ func (a *actions) PunchInMostRecent(ctx context.Context, description string, bil
finalDescription = mostRecent.Description.String
}
+ // Get client to check if archived
+ client, err := a.FindClient(ctx, fmt.Sprintf("%d", mostRecent.ClientID))
+ if err != nil {
+ return nil, fmt.Errorf("failed to get client: %w", err)
+ }
+
+ // Check if client is archived
+ if client.Archived != 0 {
+ if !autoUnarchive {
+ return nil, ErrArchivedClient
+ }
+ // Auto-unarchive the client
+ if err := a.UnarchiveClient(ctx, client.ID); err != nil {
+ return nil, fmt.Errorf("failed to unarchive client: %w", err)
+ }
+ }
+
+ // Check if project is archived (if exists)
+ if mostRecent.ProjectID.Valid {
+ project, err := a.FindProject(ctx, fmt.Sprintf("%d", mostRecent.ProjectID.Int64))
+ if err == nil && project.Archived != 0 {
+ if !autoUnarchive {
+ return nil, ErrArchivedProject
+ }
+ // Auto-unarchive the project
+ if err := a.UnarchiveProject(ctx, project.ID); err != nil {
+ return nil, fmt.Errorf("failed to unarchive project: %w", err)
+ }
+ }
+ }
+
// Check if there's already an active timer
activeEntry, err := a.queries.GetActiveTimeEntry(ctx)
var hasActiveTimer bool
@@ -148,13 +201,6 @@ func (a *actions) PunchInMostRecent(ctx context.Context, description string, bil
// Check for identical timer if one is active
if hasActiveTimer {
if timeEntriesMatch(mostRecent.ClientID, mostRecent.ProjectID, finalDescription, billableRate, activeEntry) {
- // Get client/project names for the result
- client, _ := a.FindClient(ctx, fmt.Sprintf("%d", mostRecent.ClientID))
- clientName := ""
- if client != nil {
- clientName = client.Name
- }
-
var projectName string
if mostRecent.ProjectID.Valid {
project, _ := a.FindProject(ctx, fmt.Sprintf("%d", mostRecent.ProjectID.Int64))
@@ -166,7 +212,7 @@ func (a *actions) PunchInMostRecent(ctx context.Context, description string, bil
// No-op: identical timer already active
return &TimerSession{
ID: activeEntry.ID,
- ClientName: clientName,
+ ClientName: client.Name,
ProjectName: projectName,
Description: finalDescription,
StartTime: activeEntry.StartTime,
@@ -190,12 +236,6 @@ func (a *actions) PunchInMostRecent(ctx context.Context, description string, bil
return nil, err
}
- // Get client name
- client, err := a.FindClient(ctx, fmt.Sprintf("%d", mostRecent.ClientID))
- if err != nil {
- return nil, fmt.Errorf("failed to get client name: %w", err)
- }
-
// Get project name if exists
var projectName string
if mostRecent.ProjectID.Valid {