diff options
author | T <t@tjp.lol> | 2025-08-05 11:37:02 -0600 |
---|---|---|
committer | T <t@tjp.lol> | 2025-08-05 11:37:08 -0600 |
commit | 665bd389a0a1c8adadcaa1122e846cc81f5ead31 (patch) | |
tree | f34f9ec77891308c600c680683f60951599429c3 /internal/actions/clients.go | |
parent | dc895cec9d8a84af89ce2501db234dff33c757e2 (diff) |
WIP TUI
Diffstat (limited to 'internal/actions/clients.go')
-rw-r--r-- | internal/actions/clients.go | 92 |
1 files changed, 92 insertions, 0 deletions
diff --git a/internal/actions/clients.go b/internal/actions/clients.go new file mode 100644 index 0000000..bc77139 --- /dev/null +++ b/internal/actions/clients.go @@ -0,0 +1,92 @@ +package actions + +import ( + "context" + "database/sql" + "fmt" + "regexp" + "strconv" + "strings" + + "punchcard/internal/queries" +) + +// CreateClient creates a new client with the given name and optional email/rate +func (a *actionsImpl) CreateClient(ctx context.Context, name, email string, billableRate *float64) (*queries.Client, error) { + // Parse name and email if name contains email format "Name <email>" + finalName, finalEmail := parseNameAndEmail(name, email) + + var emailParam sql.NullString + if finalEmail != "" { + emailParam = sql.NullString{String: finalEmail, Valid: true} + } + + var billableRateParam sql.NullInt64 + if billableRate != nil && *billableRate > 0 { + rate := int64(*billableRate * 100) // Convert dollars to cents + billableRateParam = sql.NullInt64{Int64: rate, Valid: true} + } + + client, err := a.queries.CreateClient(ctx, queries.CreateClientParams{ + Name: finalName, + Email: emailParam, + BillableRate: billableRateParam, + }) + if err != nil { + return nil, fmt.Errorf("failed to create client: %w", err) + } + + return &client, nil +} + +// FindClient finds a client by name or ID +func (a *actionsImpl) FindClient(ctx context.Context, nameOrID string) (*queries.Client, error) { + // Parse as ID if possible, otherwise use 0 + var idParam int64 + if id, err := strconv.ParseInt(nameOrID, 10, 64); err == nil { + idParam = id + } + + // Search by both ID and name + clients, err := a.queries.FindClient(ctx, queries.FindClientParams{ + ID: idParam, + Name: nameOrID, + }) + if err != nil { + return nil, fmt.Errorf("database error looking up client: %w", err) + } + + // Check results + switch len(clients) { + case 0: + return nil, fmt.Errorf("%w: %s", ErrClientNotFound, nameOrID) + case 1: + return &clients[0], nil + default: + return nil, fmt.Errorf("%w: %s matches multiple clients", ErrAmbiguousClient, nameOrID) + } +} + +// parseNameAndEmail handles parsing name and email from various input formats +func parseNameAndEmail(nameArg, emailArg string) (string, string) { + // If separate email provided, use it (but still check for embedded format) + finalEmail := emailArg + if finalEmail != "" { + if matches := emailAndNameRegex.FindStringSubmatch(finalEmail); matches != nil { + finalEmail = strings.TrimSpace(matches[2]) + } + } + + // Check if name contains embedded email format "Name <email@domain.com>" + finalName := nameArg + if matches := emailAndNameRegex.FindStringSubmatch(nameArg); matches != nil { + finalName = strings.TrimSpace(matches[1]) + if finalEmail == "" { + finalEmail = strings.TrimSpace(matches[2]) + } + } + + return finalName, finalEmail +} + +var emailAndNameRegex = regexp.MustCompile(`^(.+?)<([^>]+@[^>]+)>$`)
\ No newline at end of file |