clock

package
v0.27.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 29, 2026 License: Apache-2.0 Imports: 30 Imported by: 0

Documentation

Overview

Package clock is Timeclock's payroll logic and storage: pay periods, time entries, time off, timesheets, approvals, exceptions and reports.

Index

Constants

View Source
const (
	Vacation = "vacation"
	Sick     = "sick"
)

The kinds of time off.

View Source
const (
	StatusPending   = "pending"
	StatusSubmitted = "submitted"
	StatusApproved  = "approved"
	StatusRejected  = "rejected"
)

The states of time off and timesheets.

View Source
const (
	ExceptionClockRunning     = "clock_running"     // a clock has run past the long-entry limit
	ExceptionLongEntry        = "long_entry"        // an entry is longer than the limit
	ExceptionNotSubmitted     = "not_submitted"     // the period ended without a timesheet
	ExceptionAwaitingApproval = "awaiting_approval" // a timesheet for an ended period is undecided
	ExceptionRejected         = "rejected"          // a timesheet was sent back and not resubmitted
	ExceptionTimeOffPending   = "time_off_pending"  // time off in the period is undecided
	ExceptionNoTime           = "no_time"           // the period ended with nothing recorded
)

The kinds of exception.

View Source
const (
	ReminderClockRunning = "clock_running"
	ReminderTimesheetDue = "timesheet_due"
)

The kinds of reminder.

View Source
const DefaultWorkspace = "default"

DefaultWorkspace is the key of the workspace a deployment has without asking for any: a host that has one organization, or a server on its own.

View Source
const GoogleRevokeURL = "https://oauth2.googleapis.com/revoke"

GoogleRevokeURL is where Google takes back a token.

View Source
const HolidayHours = 8.0

HolidayHours is what a holiday day pays when the admin doesn't say.

Variables

View Source
var (
	ErrClockRunning = Problems.Define(problem.Type{
		Code:   "clock_running",
		Status: http.StatusConflict,
		Title:  "Clock already running",
		Doc:    "The person is already clocked in. Clock out before clocking in again.",
	})
	ErrClockNotRunning = Problems.Define(problem.Type{
		Code:   "clock_not_running",
		Status: http.StatusConflict,
		Title:  "Clock not running",
		Doc:    "The person isn't clocked in, so there is nothing to stop.",
	})
	ErrOverlap = Problems.Define(problem.Type{
		Code:   "entry_overlaps",
		Status: http.StatusConflict,
		Title:  "Time off already recorded",
		Doc:    "The person already has time off of this kind on one of these days.",
	})
	ErrHolidayTaken = Problems.Define(problem.Type{
		Code:   "holiday_day_taken",
		Status: http.StatusConflict,
		Title:  "Day already a holiday",
		Doc:    "Another holiday already covers one of these days. A day is one holiday, so it is never paid twice.",
	})
	ErrLocked = Problems.Define(problem.Type{
		Code:   "period_locked",
		Status: http.StatusConflict,
		Title:  "Timesheet already submitted",
		Doc:    "The day is in a timesheet that was submitted or approved, so its time can't change. A manager or admin can send the timesheet back.",
	})
	ErrDescriptionRequired = Problems.Define(problem.Type{
		Code:   "description_required",
		Status: http.StatusUnprocessableEntity,
		Title:  "Description required",
		Doc:    "This workspace requires a nonblank description on every time entry.",
	})
	ErrProjectRequired = Problems.Define(problem.Type{
		Code:   "project_required",
		Status: http.StatusUnprocessableEntity,
		Title:  "Project required",
		Doc:    "This organization requires every time entry to name a project.",
	})
	ErrProjectArchived = Problems.Define(problem.Type{
		Code:   "project_archived",
		Status: http.StatusConflict,
		Title:  "Project archived",
		Doc:    "The project is archived, so no new time can be recorded on it.",
	})
	ErrFuture = Problems.Define(problem.Type{
		Code:   "in_the_future",
		Status: http.StatusUnprocessableEntity,
		Title:  "Time in the future",
		Doc:    "Worked time can't end in the future, unless the organization allows planned time, and then no more than a year ahead. A running clock can't start in the future.",
	})
	ErrPlannedTime = Problems.Define(problem.Type{
		Code:   "planned_time_in_period",
		Status: http.StatusConflict,
		Title:  "Time still planned",
		Doc:    "A timesheet can't be submitted while its pay period has time that hasn't happened yet. Submit it once that time has passed, or remove it.",
	})
	ErrPeriodOpen = Problems.Define(problem.Type{
		Code:   "clock_still_running",
		Status: http.StatusConflict,
		Title:  "Clock still running",
		Doc:    "A timesheet can't be submitted while the person is clocked in during its pay period. Clock out first.",
	})
	ErrDoesNotSubmit = Problems.Define(problem.Type{
		Code:   "does_not_submit_timesheets",
		Status: http.StatusConflict,
		Title:  "Doesn't submit timesheets",
		Doc:    "This person's time is for reports only: they don't submit timesheets, and aren't in payroll. An admin can change that in Settings.",
	})
	ErrAlreadySubmitted = Problems.Define(problem.Type{
		Code:   "already_submitted",
		Status: http.StatusConflict,
		Title:  "Timesheet already submitted",
		Doc:    "The timesheet for this pay period was already submitted.",
	})
	ErrNotSubmitted = Problems.Define(problem.Type{
		Code:   "not_awaiting_decision",
		Status: http.StatusConflict,
		Title:  "Nothing to decide",
		Doc:    "It isn't waiting for a decision: it was never submitted, or someone already decided it.",
	})
	ErrOwnApproval = Problems.Define(problem.Type{
		Code:   "own_approval",
		Status: http.StatusForbidden,
		Title:  "Can't approve your own time",
		Doc:    "A manager can't approve their own timesheet or time off. Their manager or an admin does.",
	})
	ErrInUse = Problems.Define(problem.Type{
		Code:   "in_use",
		Status: http.StatusConflict,
		Title:  "Still in use",
		Doc:    "A customer with projects, or a project with time recorded on it, can't be deleted. Archive it instead.",
	})
	ErrNameTaken = Problems.Define(problem.Type{
		Code:   "name_taken",
		Status: http.StatusConflict,
		Title:  "Name already used",
		Doc:    "Another one already has this name.",
	})
	ErrManagerLoop = Problems.Define(problem.Type{
		Code:   "manager_loop",
		Status: http.StatusUnprocessableEntity,
		Title:  "Manager chain loops",
		Doc:    "A person can't be their own manager, directly or through the people they manage.",
	})
)

The failures a status alone doesn't describe.

View Source
var (
	ErrInvalidCredentials = Problems.Define(problem.Type{
		Code:   "invalid_credentials",
		Status: http.StatusUnauthorized,
		Title:  "Wrong email or password",
		Doc:    "The email and password don't match an account. The answer is the same whether or not the email is known.",
	})
	ErrTooManyAttempts = Problems.Define(problem.Type{
		Code:   "too_many_attempts",
		Status: http.StatusTooManyRequests,
		Title:  "Too many attempts",
		Doc:    "Sign-in for this account or from this address is paused after repeated failures. Retry-After says for how long.",
	})
	ErrWeakPassword = Problems.Define(problem.Type{
		Code:   "weak_password",
		Status: http.StatusUnprocessableEntity,
		Title:  "Password too short",
		Doc:    "A password is at least 10 characters and at most 128. There are no other rules about what it contains.",
	})
	ErrBreachedPassword = Problems.Define(problem.Type{
		Code:   "breached_password",
		Status: http.StatusUnprocessableEntity,
		Title:  "Password is known from a breach",
		Doc:    "The password appears in a public list of passwords from data breaches, so it is easy to guess.",
	})
	ErrLinkExpired = Problems.Define(problem.Type{
		Code:   "link_expired",
		Status: http.StatusGone,
		Title:  "Link no longer works",
		Doc:    "An invitation or password-reset link was already used, was withdrawn, or is too old.",
	})
	ErrInvalidCode = Problems.Define(problem.Type{
		Code:   "invalid_code",
		Status: http.StatusUnauthorized,
		Title:  "Wrong code",
		Doc:    "The authenticator or recovery code isn't right, or was already used.",
	})
	ErrPasskeyRefused = Problems.Define(problem.Type{
		Code:   "passkey_refused",
		Status: http.StatusUnauthorized,
		Title:  "Passkey not accepted",
		Doc:    "The passkey isn't one this server knows, or its answer didn't verify.",
	})
	ErrSSORequired = Problems.Define(problem.Type{
		Code:   "sso_required",
		Status: http.StatusForbidden,
		Title:  "Single sign-on required",
		Doc:    "The workspace can only be entered by signing in through its identity provider.",
	})
	ErrNotAMember = Problems.Define(problem.Type{
		Code:   "not_a_member",
		Status: http.StatusForbidden,
		Title:  "Not in this workspace",
		Doc:    "The account doesn't belong to the workspace it asked for.",
	})
)

What a standalone server's sign-in refuses.

View Source
var DefaultFeedContents = FeedContents{Holidays: true, TimeOff: true}

DefaultFeedContents is what a feed lists until its owner says otherwise.

View Source
var ErrGoogleState = problem.Status(http.StatusBadRequest, "this Google Calendar connection was started elsewhere or has expired; start it again")

ErrGoogleState is a consent that came back to someone it wasn't for, or too late.

View Source
var GoogleEndpoint = oauth2.Endpoint{
	AuthURL:   "https://accounts.google.com/o/oauth2/v2/auth",
	TokenURL:  "https://oauth2.googleapis.com/token",
	AuthStyle: oauth2.AuthStyleInParams,
}

GoogleEndpoint is Google's OAuth endpoint.

View Source
var Problems = problem.NewRegistry("timeclock")

Problems are Timeclock's problem types, documented at /problems/. Codes are API contract, keyed in the web app's catalog under apiErrors: never rename one.

Functions

func Hours

func Hours(d time.Duration) float64

Hours is a duration as decimal hours, to the hundredth, as payroll takes it.

func HoursByDay

func HoursByDay(spans []Span, loc *time.Location, rule OvertimeRule) map[Date]DayHours

HoursByDay adds up spans per calendar day in loc, counting overlapping spans once. Within each workweek, time beyond the weekly threshold is overtime, counted on the day it was worked, so a week that straddles two pay periods puts its overtime in the period the extra hours fell in. spans must include the whole of every workweek the caller reports on.

func Revoked added in v0.21.0

func Revoked(err error) bool

Revoked reports whether Google no longer honors a connection's refresh token: the person revoked it, or their account changed. Forget it then.

Types

type Absence added in v0.25.0

type Absence struct {
	PersonID   string
	PersonName string
	Kind       string
	Day        Date
	Hours      float64
}

Absence is a day someone is out: approved vacation or sick time.

type Activity added in v0.2.0

type Activity struct {
	Person  Person  `json:"person"`
	Running *Entry  `json:"running,omitempty" doc:"Their running clock."`
	Today   float64 `json:"today" doc:"Hours today, with the running clock up to now."`
	Week    float64 `json:"week" doc:"Hours this workweek, with the running clock up to now."`
}

Activity is what one person is doing now and how much they have tracked.

type Actor

type Actor struct {
	Person
	// Admin runs payroll.
	Admin bool
	// Auditor reads the audit log, as the host says; apart from Admin.
	Auditor bool
}

Actor is the person making a request.

type AuditEntry

type AuditEntry struct {
	ID       uuid.UUID      `json:"id"`
	At       time.Time      `json:"at"`
	Actor    string         `json:"actor"`
	Action   string         `json:"action"`
	PersonID string         `json:"personId,omitempty"`
	Detail   map[string]any `json:"detail"`
}

AuditEntry is one change to payroll data.

type AuditPerson added in v0.27.0

type AuditPerson struct {
	ID        string `json:"id"`
	Name      string `json:"name"`
	Email     string `json:"email"`
	AvatarURL string `json:"avatarUrl"`
}

AuditPerson is who someone the audit log mentions is: no more than that.

type AuditQuery added in v0.27.0

type AuditQuery struct {
	// PersonID keeps only changes to this person's time; empty keeps everything.
	PersonID string
	// Before is the last entry of the page before: this page goes on from it.
	Before *uuid.UUID
	// Until starts the log at the end of this day, in the organization's time zone.
	Until *Date
	Limit int
}

AuditQuery picks a page of the audit log, newest first.

type CalendarFeedStatus added in v0.25.0

type CalendarFeedStatus struct {
	FeedContents
	Enabled   bool       `json:"enabled" doc:"Whether the caller has a calendar feed."`
	CreatedAt *time.Time `json:"createdAt,omitempty" doc:"When its address was made."`
}

CalendarFeedStatus is whether a person has a calendar feed, and what it lists. Its address is shown only when it is made.

type Customer

type Customer struct {
	ID       uuid.UUID `json:"id"`
	Name     string    `json:"name"`
	Archived bool      `json:"archived"`
}

Customer is who work is done for.

type Date

type Date struct {
	// contains filtered or unexported fields
}

Date is a calendar day, with no time or zone, written YYYY-MM-DD.

func DateFromTime

func DateFromTime(t time.Time) Date

DateFromTime is the calendar day of a time read from a DATE column.

func DateOf

func DateOf(t time.Time, loc *time.Location) Date

DateOf is the calendar day t falls on in loc.

func NewDate

func NewDate(year int, month time.Month, day int) Date

NewDate returns the given calendar day.

func ParseDate

func ParseDate(s string) (Date, error)

ParseDate reads a YYYY-MM-DD date.

func WeekStart

func WeekStart(d Date, first time.Weekday) Date

WeekStart returns the first day of the workweek d falls in.

func (Date) AddDays

func (d Date) AddDays(n int) Date

AddDays returns the day n days after d.

func (Date) After

func (d Date) After(o Date) bool

func (Date) Before

func (d Date) Before(o Date) bool

func (Date) DaysSince

func (d Date) DaysSince(o Date) int

DaysSince is the number of days from o to d.

func (Date) In

func (d Date) In(loc *time.Location) time.Time

In is the instant d starts in loc.

func (Date) IsZero

func (d Date) IsZero() bool

IsZero reports whether d is unset.

func (Date) MarshalText

func (d Date) MarshalText() ([]byte, error)

MarshalText writes YYYY-MM-DD.

func (Date) String

func (d Date) String() string

func (Date) Time

func (d Date) Time() time.Time

Time is midnight UTC on d, as a DATE parameter wants it.

func (*Date) UnmarshalText

func (d *Date) UnmarshalText(b []byte) error

UnmarshalText reads YYYY-MM-DD.

func (Date) Weekday

func (d Date) Weekday() time.Weekday

Weekday is the day of the week.

type DayHours

type DayHours struct {
	Regular  time.Duration
	Overtime time.Duration
}

DayHours is one day's worked time.

func (DayHours) Total

func (h DayHours) Total() time.Duration

Total is the day's regular and overtime together.

type DayProjectHours added in v0.2.0

type DayProjectHours struct {
	Day       Date    `json:"day" format:"date"`
	ProjectID string  `json:"projectId" doc:"Empty for time with no project."`
	Hours     float64 `json:"hours"`
}

DayProjectHours is time on one project on one day, with a running clock counted up to now.

type DaySummary

type DaySummary struct {
	Day      Date    `json:"day" format:"date"`
	Regular  float64 `json:"regular"`
	Overtime float64 `json:"overtime"`
	Vacation float64 `json:"vacation"`
	Sick     float64 `json:"sick"`
	Holiday  float64 `json:"holiday" doc:"Paid company holiday hours: not time worked, so never overtime."`
	// HolidayName names the company holiday on the day, paid or not.
	HolidayName string `json:"holidayName,omitempty" doc:"The company holiday on this day."`
}

DaySummary is one day of a person's pay period, in hours.

type Entry

type Entry struct {
	ID        uuid.UUID  `json:"id"`
	PersonID  string     `json:"personId"`
	ProjectID *uuid.UUID `json:"projectId,omitempty"`
	StartedAt time.Time  `json:"startedAt"`
	EndedAt   *time.Time `json:"endedAt,omitempty" doc:"Absent while the clock is running."`
	Note      string     `json:"note"`
	Source    string     `json:"source" enum:"clock,manual,toggl"`
	Locked    bool       `json:"locked" doc:"In a submitted or approved timesheet, so it can't change."`
}

Entry is a stretch of work.

type EntryInput

type EntryInput struct {
	PersonID  string     `` /* 126-byte string literal not displayed */
	ProjectID *uuid.UUID `json:"projectId,omitempty"`
	StartedAt time.Time  `json:"startedAt"`
	EndedAt   *time.Time `json:"endedAt,omitempty" doc:"Absent leaves a running clock running."`
	Note      string     `json:"note,omitempty" maxLength:"2000"`
}

EntryInput is a time entry as written by hand.

type Exception

type Exception struct {
	Kind       string     `json:"kind" enum:"clock_running,long_entry,not_submitted,awaiting_approval,rejected,time_off_pending,no_time"`
	PersonID   string     `json:"personId"`
	PersonName string     `json:"personName"`
	Day        *Date      `json:"day,omitempty" format:"date"`
	EntryID    *uuid.UUID `json:"entryId,omitempty"`
	Hours      float64    `json:"hours,omitempty" doc:"The hours the exception is about: the entry's length, the overtime, or the pending time off."`
}

Exception is something in a pay period that payroll should look at before paying from it.

type FeedContents added in v0.25.0

type FeedContents struct {
	Holidays    bool `json:"holidays" doc:"The company's holidays."`
	TimeOff     bool `json:"timeOff" doc:"Who is out: everyone's approved time off."`
	TrackedTime bool `json:"trackedTime" doc:"The subscriber's own tracked and planned time."`
}

FeedContents is what a calendar feed lists. At least one is on.

type FeedOwner added in v0.25.0

type FeedOwner struct {
	Workspace Workspace
	PersonID  string
	Contents  FeedContents
}

FeedOwner is whose a calendar feed is, the workspace it lists, and what.

type GoogleCalendarStatus added in v0.21.0

type GoogleCalendarStatus struct {
	Connected   bool       `json:"connected"`
	Account     string     `json:"account,omitempty" doc:"The Google account it reads, normally an email address."`
	ConnectedAt *time.Time `json:"connectedAt,omitempty"`
}

GoogleCalendarStatus is whether a person has connected their calendar.

type GoogleCalendars added in v0.21.0

type GoogleCalendars struct {
	// contains filtered or unexported fields
}

GoogleCalendars are the calendars people connect themselves. Timeclock sends a person to Google's consent screen, keeps the refresh token Google gives back, sealed with the integration key, and reads their events with it until they disconnect or revoke it.

func NewGoogleCalendars added in v0.21.0

func NewGoogleCalendars(key string, c *GoogleClient, logger *slog.Logger) (*GoogleCalendars, error)

NewGoogleCalendars returns nil when there is no client: nobody can connect.

func (*GoogleCalendars) AuthURL added in v0.21.0

func (g *GoogleCalendars) AuthURL(s *Service, actor Actor, returnPath string) (string, error)

AuthURL is Google's consent screen for the actor to connect their calendar, coming back to returnPath afterwards.

func (*GoogleCalendars) Complete added in v0.21.0

func (g *GoogleCalendars) Complete(ctx context.Context, s *Service, actor Actor, code, state string) error

Complete exchanges the code Google returned for the actor's tokens and keeps the refresh token.

func (*GoogleCalendars) Disconnect added in v0.21.0

func (g *GoogleCalendars) Disconnect(ctx context.Context, s *Service, actor Actor) error

Disconnect drops the actor's connection and asks Google to revoke its token. A revocation that fails still disconnects: Timeclock no longer has the token either way.

func (*GoogleCalendars) Drop added in v0.21.0

func (g *GoogleCalendars) Drop(s *Service, actor Actor)

Drop forgets the actor's access token, so the next read refreshes it.

func (*GoogleCalendars) Forget added in v0.21.0

func (g *GoogleCalendars) Forget(ctx context.Context, s *Service, actor Actor) error

Forget drops the actor's connection without asking Google, which has already let it go.

func (*GoogleCalendars) Return added in v0.21.0

func (g *GoogleCalendars) Return(s *Service, actor Actor, state string) (string, error)

Return reads where a consent started from, for whoever it was for: the path to go back to.

func (*GoogleCalendars) Status added in v0.21.0

func (g *GoogleCalendars) Status(ctx context.Context, s *Service, actor Actor) (GoogleCalendarStatus, error)

Status is whether the actor has connected their calendar.

func (*GoogleCalendars) TokenSource added in v0.21.0

func (g *GoogleCalendars) TokenSource(ctx context.Context, s *Service, actor Actor) (oauth2.TokenSource, bool, error)

TokenSource is the actor's own connection, or ok false when they have none.

type GoogleClient added in v0.21.0

type GoogleClient struct {
	ClientID, ClientSecret, RedirectURL string
	Endpoint                            oauth2.Endpoint
	RevokeURL                           string
}

GoogleClient is the OAuth client people connect their calendars through.

type Holiday added in v0.15.0

type Holiday struct {
	ID   uuid.UUID    `json:"id"`
	Name string       `json:"name"`
	Days []HolidayDay `json:"days"`
}

Holiday is a company holiday: a name and the observed days it covers, each with the hours it pays.

type HolidayDay added in v0.15.0

type HolidayDay struct {
	Day   Date    `json:"day" format:"date"`
	Hours float64 `json:"hours" doc:"Paid hours on the day."`
}

HolidayDay is one observed day of a holiday.

type HolidayDayInput added in v0.15.0

type HolidayDayInput struct {
	Day   Date    `json:"day" format:"date"`
	Hours float64 `json:"hours,omitempty" minimum:"0" maximum:"24" doc:"Paid hours on the day. Absent or 0 pays 8."`
}

HolidayDayInput is one observed day and what it pays.

type HolidayInput added in v0.15.0

type HolidayInput struct {
	Name string            `json:"name" minLength:"1" maxLength:"120"`
	Days []HolidayDayInput `json:"days" minItems:"1" maxItems:"31" doc:"The observed days. A holiday can cover several days, not necessarily in a row."`
}

HolidayInput is a holiday as an admin enters it: the days the company observes, so a holiday on a Saturday is entered on the Friday off.

type MeetingChoice added in v0.22.0

type MeetingChoice struct {
	ProjectID *uuid.UUID
}

MeetingChoice is the project a person copies a meeting to: nil for none.

type OvertimeRule

type OvertimeRule struct {
	// WeekStart is the day the workweek starts.
	WeekStart time.Weekday
	// Weekly is the time in one workweek beyond which work is overtime. Zero
	// means no overtime.
	Weekly time.Duration
}

OvertimeRule is how overtime is counted.

type PayCycle

type PayCycle string

PayCycle is how often payroll runs.

const (
	Weekly      PayCycle = "weekly"
	Biweekly    PayCycle = "biweekly"
	Semimonthly PayCycle = "semimonthly" // the 1st–15th and the 16th–end
	Monthly     PayCycle = "monthly"
)

The pay cycles.

type Period

type Period struct {
	Start Date `json:"start" format:"date"`
	End   Date `json:"end" format:"date"`
}

Period is a pay period: both days are in it.

func PeriodAfter

func PeriodAfter(cycle PayCycle, anchor Date, p Period) Period

PeriodAfter returns the pay period that starts the day after p ends.

func PeriodBefore

func PeriodBefore(cycle PayCycle, anchor Date, p Period) Period

PeriodBefore returns the pay period that ends the day before p starts.

func PeriodContaining

func PeriodContaining(cycle PayCycle, anchor, d Date) Period

PeriodContaining returns the pay period d falls in. anchor is the first day of some weekly or biweekly period; the other cycles follow the calendar.

func (Period) Contains

func (p Period) Contains(d Date) bool

Contains reports whether d is in the period.

func (Period) Days

func (p Period) Days() []Date

Days lists the period's days in order.

type PeriodSummary

type PeriodSummary struct {
	Person   Person       `json:"person"`
	Period   Period       `json:"period"`
	Days     []DaySummary `json:"days"`
	Regular  float64      `json:"regular"`
	Overtime float64      `json:"overtime"`
	Vacation float64      `json:"vacation" doc:"Approved vacation hours."`
	Sick     float64      `json:"sick" doc:"Approved sick hours."`
	Holiday  float64      `json:"holiday" doc:"Paid company holiday hours."`
	// PendingTimeOff is hours of time off still waiting for a decision,
	// which are not in Vacation or Sick.
	PendingTimeOff float64    `json:"pendingTimeOff"`
	Running        bool       `json:"running" doc:"A clock is running; its time isn't counted until it stops."`
	Timesheet      *Timesheet `json:"timesheet,omitempty" doc:"Absent until the person submits."`
}

PeriodSummary is a person's pay period: what payroll pays from.

type Person

type Person struct {
	ID                 string `json:"id"`
	Name               string `json:"name"`
	Email              string `json:"email"`
	AvatarURL          string `json:"avatarUrl"`
	Timezone           string `` /* 138-byte string literal not displayed */
	ManagerID          string `json:"managerId" doc:"Who approves this person's time; empty when no one is assigned."`
	ManagerOverrideID  string `json:"managerOverrideId" doc:"The local manager override; empty follows the directory."`
	DirectoryManagerID string `json:"directoryManagerId" doc:"The manager supplied by the host directory, used when no local override is set."`
	OvertimeExempt     bool   `json:"overtimeExempt"`
	PayrollID          string `json:"payrollId" doc:"The person's id in the payroll system, for the export."`
	Active             bool   `json:"active"`
	// SubmitsTimesheets is what applies: their own choice, or else the workspace's.
	SubmitsTimesheets bool `json:"submitsTimesheets" doc:"They submit timesheets and are in payroll. Without, their time is for reports only."`
	// Admin runs payroll and manages Timeclock: the host says so (HostAdmin),
	// or an admin granted it here.
	Admin     bool `json:"admin" doc:"Runs payroll and manages Timeclock."`
	HostAdmin bool `json:"hostAdmin" doc:"The host application makes them an admin; only the host can take that away."`
	// SubmitsTimesheetsOverride is their own choice; nil follows the workspace.
	SubmitsTimesheetsOverride *bool `json:"submitsTimesheetsOverride" nullable:"true" doc:"Set for this person; null follows the workspace's setting."`
	// HolidayPay is what applies: their own choice, or else the workspace's.
	HolidayPay bool `json:"holidayPay" doc:"They are paid for company holidays."`
	// HolidayPayOverride is their own choice; nil follows the workspace.
	HolidayPayOverride *bool `json:"holidayPayOverride" nullable:"true" doc:"Set for this person; null follows the workspace's holiday pay."`
}

Person is someone who tracks time.

type PersonUpdate

type PersonUpdate struct {
	Timezone       string `json:"timezone" doc:"The IANA time zone their days are cut in. Empty uses the organization's."`
	ManagerID      string `json:"managerId" doc:"Who approves this person's time. Empty defers to the host's directory."`
	OvertimeExempt bool   `json:"overtimeExempt"`
	PayrollID      string `json:"payrollId" maxLength:"64"`
	Active         bool   `json:"active" doc:"Inactive people are left out of payroll reports and exceptions."`
	// SubmitsTimesheets is this person's own choice; absent or null follows the workspace.
	SubmitsTimesheets *bool `` /* 155-byte string literal not displayed */
	// HolidayPay is this person's own choice; absent or null follows the workspace.
	HolidayPay *bool `` /* 141-byte string literal not displayed */
}

PersonUpdate is what an admin sets on a person.

type Project

type Project struct {
	ID           uuid.UUID  `json:"id"`
	CustomerID   *uuid.UUID `json:"customerId,omitempty" doc:"Absent for internal work."`
	CustomerName string     `json:"customerName" doc:"Empty for internal work."`
	Name         string     `json:"name"`
	Code         string     `json:"code" doc:"A charge code or contract number."`
	Billable     bool       `json:"billable"`
	Archived     bool       `json:"archived" doc:"The project itself is archived."`
	// CustomerArchived means the project takes no new time either way.
	CustomerArchived bool `json:"customerArchived"`
}

Project is a body of work that time is tagged with: for a customer, or internal when it has none.

type ProjectHours

type ProjectHours struct {
	CustomerName string  `json:"customerName"`
	ProjectID    string  `json:"projectId" doc:"Empty for time with no project."`
	ProjectName  string  `json:"projectName"`
	ProjectCode  string  `json:"projectCode"`
	Billable     bool    `json:"billable"`
	PersonID     string  `json:"personId"`
	PersonName   string  `json:"personName"`
	Hours        float64 `json:"hours"`
}

ProjectHours is time on one project by one person, for the project report.

type ProjectInput

type ProjectInput struct {
	CustomerID *uuid.UUID `json:"customerId,omitempty" doc:"Absent for internal work."`
	Name       string     `json:"name" minLength:"1" maxLength:"120"`
	Code       string     `json:"code,omitempty" maxLength:"64" doc:"A charge code or contract number."`
	Billable   bool       `json:"billable"`
	Archived   bool       `json:"archived,omitempty"`
}

ProjectInput is what an admin sets on a project.

type RecentWork added in v0.24.0

type RecentWork struct {
	Note      string     `json:"note"`
	ProjectID *uuid.UUID `json:"projectId,omitempty"`
}

RecentWork is something a person has tracked before: a note and its project.

type Reminder

type Reminder struct {
	PersonID string
	Kind     string
	// Key identifies what it is about, so it is sent once: the entry of a
	// running clock, or the first day of a pay period.
	Key string
	// Hours is how long the clock has run, for a running clock.
	Hours float64
	// Period is the pay period whose timesheet is due.
	Period Period
}

Reminder is a nudge for one person about their own time.

type Reminders

type Reminders struct {
	// contains filtered or unexported fields
}

Reminders is the periodic job that nudges people through the host's notifications.

func NewReminders

func NewReminders(notifier host.Notifier, logger *slog.Logger) *Reminders

NewReminders returns the job. It does nothing until Bind.

func (*Reminders) Bind

func (r *Reminders) Bind(svc *Service)

Bind sets the service, which is built after the client.

func (*Reminders) Register

func (r *Reminders) Register(workers *hopper.Workers) hopper.PeriodicJob

Register adds the job's worker and returns its schedule for hopper.Config.Periodic.

type Scheme added in v0.4.0

type Scheme = host.Scheme

Scheme is one of the two: the page's seed, and optionally the sidebar's.

type Service

type Service struct {
	// contains filtered or unexported fields
}

Service is Timeclock's payroll data and its rules. Every method takes the Actor it acts for and refuses what that person may not do.

func New

func New(pool *pgxpool.Pool) *Service

New returns a Service on a pool whose search_path is Timeclock's schema.

func (*Service) Absences added in v0.25.0

func (s *Service) Absences(ctx context.Context, from, to Date) ([]Absence, error)

Absences is everyone's approved time off on the days from..to, for those still active, by day.

func (*Service) Activity added in v0.2.0

func (s *Service) Activity(ctx context.Context, actor Actor) ([]Activity, error)

Activity lists, for everyone whose time actor may see and who still tracks time, what they are on now and their hours today and this workweek. People with a running clock come first.

func (*Service) Audit

func (s *Service) Audit(ctx context.Context, actor Actor, q AuditQuery) ([]AuditEntry, bool, error)

Audit lists a page of changes, newest first, and whether older ones follow. Only an auditor reads it: the host decides who, and being an admin is not enough.

func (*Service) AuditPeople added in v0.27.0

func (s *Service) AuditPeople(ctx context.Context, actor Actor) ([]AuditPerson, error)

AuditPeople names everyone in the workspace, for an auditor reading the log. An auditor need not be an admin, and People shows them only their own and their reports'.

func (*Service) CalendarFeed added in v0.25.0

func (s *Service) CalendarFeed(ctx context.Context, actor Actor) (CalendarFeedStatus, error)

CalendarFeed says whether actor has a calendar feed.

func (*Service) CalendarFeedOwner added in v0.25.0

func (s *Service) CalendarFeedOwner(ctx context.Context, token string) (FeedOwner, error)

CalendarFeedOwner is whose feed a secret opens, in any workspace: a feed is fetched by a calendar app, which says nothing about where it is.

func (*Service) CancelTimeOff

func (s *Service) CancelTimeOff(ctx context.Context, actor Actor, id uuid.UUID) error

CancelTimeOff removes time off on a day whose timesheet isn't submitted.

func (*Service) ClockIn

func (s *Service) ClockIn(ctx context.Context, actor Actor, projectID *uuid.UUID, note string) (Entry, error)

ClockIn starts the actor's clock. Required fields can be filled in while it runs.

func (*Service) ClockOut

func (s *Service) ClockOut(ctx context.Context, actor Actor) (Entry, error)

ClockOut stops the actor's clock.

func (*Service) CreateEntry

func (s *Service) CreateEntry(ctx context.Context, actor Actor, in EntryInput) (Entry, error)

CreateEntry records a finished stretch of work by hand.

func (*Service) Customers

func (s *Service) Customers(ctx context.Context, archived bool) ([]Customer, error)

Customers lists customers by name, with archived ones when asked.

func (*Service) Decide

func (s *Service) Decide(ctx context.Context, actor Actor, id uuid.UUID, approve bool, note string) (Timesheet, error)

Decide approves a submitted timesheet, or sends it back to its person with a note, which unlocks its time.

func (*Service) DecideTimeOff

func (s *Service) DecideTimeOff(ctx context.Context, actor Actor, id uuid.UUID, approve bool, note string) (TimeOff, error)

DecideTimeOff approves or rejects pending time off.

func (*Service) DeleteCustomer

func (s *Service) DeleteCustomer(ctx context.Context, actor Actor, id uuid.UUID) error

DeleteCustomer removes a customer with no projects.

func (*Service) DeleteEntry

func (s *Service) DeleteEntry(ctx context.Context, actor Actor, id uuid.UUID) error

DeleteEntry removes an entry.

func (*Service) DeleteHoliday added in v0.15.0

func (s *Service) DeleteHoliday(ctx context.Context, actor Actor, id uuid.UUID) error

DeleteHoliday removes a holiday whose days are outside settled timesheets.

func (*Service) DeleteProject

func (s *Service) DeleteProject(ctx context.Context, actor Actor, id uuid.UUID) error

DeleteProject removes a project no time was recorded on.

func (*Service) DueReminders

func (s *Service) DueReminders(ctx context.Context) ([]Reminder, error)

DueReminders finds what people should be nudged about now and haven't been: a clock running past the long-entry limit, and a timesheet for the last pay period that was never submitted. Each is recorded as sent, so a later call doesn't return it again.

func (*Service) EnsureWorkspace added in v0.4.0

func (s *Service) EnsureWorkspace(ctx context.Context, key string) (Workspace, error)

EnsureWorkspace returns the workspace with the given key, making it, with default settings, the first time it is asked for.

func (*Service) Entries

func (s *Service) Entries(ctx context.Context, actor Actor, personID string, from, to Date) ([]Entry, error)

Entries lists a person's time that touches the days from..to, oldest first.

func (*Service) Exceptions

func (s *Service) Exceptions(ctx context.Context, actor Actor, day Date) ([]Exception, error)

Exceptions lists what payroll should look at in the pay period containing day, for the people whose time actor may see.

func (*Service) Holidays added in v0.15.0

func (s *Service) Holidays(ctx context.Context) ([]Holiday, error)

Holidays lists the company's holidays by their first day.

func (*Service) HoursByDayAndProject added in v0.2.0

func (s *Service) HoursByDayAndProject(ctx context.Context, actor Actor, from, to Date, mine bool) ([]DayProjectHours, error)

HoursByDayAndProject adds up time on the days from..to by day and project: the caller's own when mine is set, and otherwise that of everyone whose time they may see. A running clock counts up to now. Time is cut at each midnight where the person is. Unlike worked hours, overlapping entries each count in full, so every project shows all the time recorded on it.

func (*Service) In added in v0.4.0

func (s *Service) In(workspace uuid.UUID) *Service

In returns the service for one workspace. Everything it reads and writes is that workspace's.

func (*Service) Meetings added in v0.22.0

func (s *Service) Meetings(ctx context.Context, actor Actor, meetings []string) (map[string]MeetingChoice, error)

Meetings are the projects the actor copies these meetings to, for those they have chosen one for. A project archived since is no choice: the next copy asks again.

func (*Service) NewCalendarFeed added in v0.25.0

func (s *Service) NewCalendarFeed(ctx context.Context, actor Actor, c FeedContents) (string, CalendarFeedStatus, error)

NewCalendarFeed makes actor a calendar feed listing c, and returns its secret. Any feed they had stops working.

func (*Service) Payroll

func (s *Service) Payroll(ctx context.Context, actor Actor, day Date) ([]PeriodSummary, error)

Payroll returns everyone's pay period containing day, for the export.

func (*Service) PendingTimeOff

func (s *Service) PendingTimeOff(ctx context.Context, actor Actor) ([]TimeOff, error)

PendingTimeOff lists the time off waiting for actor's decision: their reports', or everyone's for an admin.

func (*Service) People

func (s *Service) People(ctx context.Context, actor Actor) ([]Person, error)

People lists the people whose time actor may see: everyone for an admin, otherwise the actor and their reports.

func (*Service) ProjectReport

func (s *Service) ProjectReport(ctx context.Context, actor Actor, from, to Date) ([]ProjectHours, error)

ProjectReport adds up finished time on the days from..to by project and person, for the people whose time actor may see. Time is cut at the first and last midnight where each person is, so an entry across one counts only its part inside.

func (*Service) Projects

func (s *Service) Projects(ctx context.Context, archived bool) ([]Project, error)

Projects lists internal projects, then the rest by customer, each by name, with archived ones when asked: its own, and those of archived customers.

func (*Service) RecentWork added in v0.24.0

func (s *Service) RecentWork(ctx context.Context, actor Actor, limit int) ([]RecentWork, error)

RecentWork lists the notes the caller has tracked, each with the project it was on, the most recently worked first, so the clock can suggest them again.

func (*Service) RememberMeeting added in v0.22.0

func (s *Service) RememberMeeting(ctx context.Context, actor Actor, meeting string, projectID *uuid.UUID) error

RememberMeeting keeps the project the actor copies a meeting to.

func (*Service) Reopen

func (s *Service) Reopen(ctx context.Context, actor Actor, id uuid.UUID, note string) (Timesheet, error)

Reopen unlocks a timesheet so its time can be corrected: its person can take back one that is still waiting, and a manager or admin can send back one that was approved.

func (*Service) RequestTimeOff

func (s *Service) RequestTimeOff(ctx context.Context, actor Actor, in TimeOffInput) ([]TimeOff, error)

RequestTimeOff records time off on each day of a run. It waits for a decision when the organization approves time off, and is approved at once when it doesn't.

func (*Service) Running

func (s *Service) Running(ctx context.Context, actor Actor) (*Entry, error)

Running returns the person's running clock, or nil.

func (*Service) SaveCustomer

func (s *Service) SaveCustomer(ctx context.Context, actor Actor, id uuid.UUID, name string, archived bool) (Customer, error)

SaveCustomer creates a customer, or with an id renames or archives one.

func (*Service) SaveHoliday added in v0.15.0

func (s *Service) SaveHoliday(ctx context.Context, actor Actor, id uuid.UUID, in HolidayInput) (Holiday, error)

SaveHoliday adds a holiday, or with an id changes one. Only the days whose pay changes need to be outside settled timesheets.

func (*Service) SaveProject

func (s *Service) SaveProject(ctx context.Context, actor Actor, id uuid.UUID, in ProjectInput) (Project, error)

SaveProject creates a project, or with an id changes one.

func (*Service) SetAdmin added in v0.11.0

func (s *Service) SetAdmin(ctx context.Context, actor Actor, id string, admin bool) (Person, error)

SetAdmin grants a person admin in Timeclock, or takes the grant back. Someone the host makes an admin stays one: only the host can change that. No one takes their own admin away, so a workspace can't lock itself out.

func (*Service) SetCalendarFeed added in v0.25.0

func (s *Service) SetCalendarFeed(ctx context.Context, actor Actor, c FeedContents) (CalendarFeedStatus, error)

SetCalendarFeed changes what actor's calendar feed lists, at the same address: subscribers see it when they next fetch it.

func (*Service) SetOwnTimezone added in v0.2.0

func (s *Service) SetOwnTimezone(ctx context.Context, actor Actor, tz string) (Person, error)

SetOwnTimezone sets the time zone the actor's own days are cut in. Time in a submitted timesheet keeps the days it was submitted with.

func (*Service) SetTheme added in v0.4.0

func (s *Service) SetTheme(ctx context.Context, actor Actor, t Theme) (Theme, error)

SetTheme sets the workspace's look. An empty theme goes back to the host's, or Timeclock's own.

func (*Service) Settings

func (s *Service) Settings(ctx context.Context) (Settings, error)

Settings returns how the organization runs payroll.

func (*Service) StopCalendarFeed added in v0.25.0

func (s *Service) StopCalendarFeed(ctx context.Context, actor Actor) error

StopCalendarFeed turns actor's calendar feed off.

func (*Service) Submit

func (s *Service) Submit(ctx context.Context, actor Actor, personID string, day Date) (PeriodSummary, error)

Submit states that a person's pay period containing day is complete, which locks its time. It waits for a decision when the organization approves timesheets, and is approved at once when it doesn't.

func (*Service) Summary

func (s *Service) Summary(ctx context.Context, actor Actor, personID string, day Date) (PeriodSummary, error)

Summary returns a person's pay period containing day.

func (*Service) Switch added in v0.2.0

func (s *Service) Switch(ctx context.Context, actor Actor, projectID *uuid.UUID, note string) (Entry, error)

Switch moves the actor's running clock to other work without stopping it: the time so far stays on what it was recorded against, and the clock goes on, from this instant, against the given project and note.

func (*Service) Sync

func (s *Service) Sync(ctx context.Context, hp host.Person) (Actor, error)

Sync records the host's current answer about a person and returns them as an Actor. It is called for the caller of every request, so the people table always has everyone who has used Timeclock.

func (*Service) SyncAll

func (s *Service) SyncAll(ctx context.Context, people []host.Person) error

SyncAll records everyone in the host's directory, so admins can assign managers and report on people who haven't opened Timeclock yet.

func (*Service) Team

func (s *Service) Team(ctx context.Context, actor Actor, day Date) ([]PeriodSummary, error)

Team returns the pay period containing day for everyone whose time actor may see and who still tracks time.

func (*Service) Theme added in v0.4.0

func (s *Service) Theme(ctx context.Context) (Theme, error)

Theme returns the look an admin set for the workspace; its schemes are nil where they set none.

func (*Service) TimeOff

func (s *Service) TimeOff(ctx context.Context, actor Actor, personID string, from, to Date) ([]TimeOff, error)

TimeOff lists a person's time off on the days from..to.

func (*Service) Today

func (s *Service) Today(cfg Settings) Date

Today is the current day in the organization's time zone.

func (*Service) TodayFor added in v0.2.0

func (s *Service) TodayFor(cfg Settings, p Person) Date

TodayFor is the current day where a person is.

func (*Service) UpdateEntry

func (s *Service) UpdateEntry(ctx context.Context, actor Actor, id uuid.UUID, in EntryInput) (Entry, error)

UpdateEntry changes an entry. Giving a running clock an end stops it.

func (*Service) UpdatePerson

func (s *Service) UpdatePerson(ctx context.Context, actor Actor, id string, in PersonUpdate) (Person, error)

UpdatePerson sets a person's manager and payroll details.

func (*Service) UpdateSettings

func (s *Service) UpdateSettings(ctx context.Context, actor Actor, in Settings) (Settings, error)

UpdateSettings changes how payroll runs. Timesheets already submitted keep the pay period they were submitted for.

func (*Service) Workspace added in v0.4.0

func (s *Service) Workspace() uuid.UUID

Workspace is the workspace this service is for.

func (*Service) Workspaces added in v0.4.0

func (s *Service) Workspaces(ctx context.Context) ([]Workspace, error)

Workspaces lists every workspace, for jobs that run across all of them.

type Settings

type Settings struct {
	Timezone            string   `` /* 136-byte string literal not displayed */
	PayCycle            PayCycle `json:"payCycle" enum:"weekly,biweekly,semimonthly,monthly"`
	CycleAnchor         Date     `json:"cycleAnchor" format:"date" doc:"The first day of some weekly or biweekly pay period."`
	WeekStart           int      `json:"weekStart" minimum:"0" maximum:"6" doc:"The day the workweek starts, for overtime: 0 is Sunday."`
	OvertimeWeeklyHours float64  `` /* 131-byte string literal not displayed */
	SubmitTimesheets    bool     `` /* 181-byte string literal not displayed */
	HolidayPay          bool     `json:"holidayPay" doc:"People are paid for company holidays, unless set otherwise for them."`
	ApproveTimesheets   bool     `json:"approveTimesheets" doc:"A submitted timesheet waits for the person's manager or an admin."`
	ApproveTimeOff      bool     `json:"approveTimeOff" doc:"Time off waits for the person's manager or an admin."`
	RequireDescription  bool     `json:"requireDescription" doc:"Every time entry has a description."`
	RequireProject      bool     `json:"requireProject" doc:"Every time entry names a project."`
	LongEntryHours      float64  `json:"longEntryHours" exclusiveMinimum:"0" maximum:"24" doc:"An entry or a running clock longer than this is an exception."`
	AllowPlannedTime    bool     `` /* 228-byte string literal not displayed */
}

Settings is how the organization runs payroll.

func (Settings) Location

func (s Settings) Location() *time.Location

Location is the settings' time zone.

func (Settings) LocationOf added in v0.2.0

func (s Settings) LocationOf(p Person) *time.Location

LocationOf is the time zone a person's days and workweeks are cut in: their own, or the organization's when they have none.

func (Settings) Overtime

func (s Settings) Overtime(exempt bool) OvertimeRule

Overtime is the settings' overtime rule for a person.

func (Settings) PeriodOf

func (s Settings) PeriodOf(d Date) Period

PeriodOf is the pay period a day falls in.

type Span

type Span struct {
	Start, End time.Time
}

Span is a finished stretch of work.

type Theme added in v0.4.0

type Theme = host.Theme

Theme is a look for light and for dark.

type ThemeSeed added in v0.4.0

type ThemeSeed = host.ThemeSeed

ThemeSeed is the few values a whole scheme is derived from.

type TimeOff

type TimeOff struct {
	ID            uuid.UUID  `json:"id"`
	PersonID      string     `json:"personId"`
	Kind          string     `json:"kind" enum:"vacation,sick"`
	Day           Date       `json:"day" format:"date"`
	Hours         float64    `json:"hours"`
	Note          string     `json:"note"`
	Status        string     `json:"status" enum:"pending,approved,rejected"`
	DecidedBy     string     `json:"decidedBy,omitempty"`
	DecidedByName string     `json:"decidedByName,omitempty"`
	DecidedAt     *time.Time `json:"decidedAt,omitempty"`
	DecisionNote  string     `json:"decisionNote,omitempty"`
	Locked        bool       `json:"locked" doc:"Its day is in a submitted or approved timesheet, so it can't be removed."`
}

TimeOff is vacation or sick hours on one day.

type TimeOffInput

type TimeOffInput struct {
	PersonID string  `json:"personId,omitempty" doc:"Whose time off. Defaults to the caller; a manager or admin can record it for someone else."`
	Kind     string  `json:"kind" enum:"vacation,sick"`
	From     Date    `json:"from" format:"date"`
	To       Date    `json:"to" format:"date" doc:"The last day off; the same as from for one day."`
	Hours    float64 `json:"hours" exclusiveMinimum:"0" maximum:"24" doc:"Hours off on each day."`
	Weekends bool    `json:"weekends,omitempty" doc:"Include Saturdays and Sundays in the run."`
	Note     string  `json:"note,omitempty" maxLength:"2000"`
}

TimeOffInput is a request for time off on a run of days.

type Timesheet

type Timesheet struct {
	ID            uuid.UUID  `json:"id"`
	PersonID      string     `json:"personId"`
	Period        Period     `json:"period"`
	Status        string     `json:"status" enum:"submitted,approved,rejected"`
	SubmittedAt   time.Time  `json:"submittedAt"`
	DecidedBy     string     `json:"decidedBy,omitempty" doc:"Who approved it or sent it back; the person themselves when they took it back."`
	DecidedByName string     `json:"decidedByName,omitempty"`
	DecidedAt     *time.Time `json:"decidedAt,omitempty"`
	DecisionNote  string     `json:"decisionNote,omitempty"`
}

Timesheet is a person's statement that a pay period's time is complete.

type Toggl added in v0.5.0

type Toggl struct {
	// contains filtered or unexported fields
}

Toggl is the company migration bridge. Credentials are encrypted with a key kept outside the database; associated data binds ciphertext to its workspace.

func NewToggl added in v0.5.0

func NewToggl(s *Service, key string, logger *slog.Logger) (*Toggl, error)

func (*Toggl) Configure added in v0.5.0

func (t *Toggl) Configure(ctx context.Context, s *Service, actor Actor, in TogglSetup) error

func (*Toggl) Disconnect added in v0.5.0

func (t *Toggl) Disconnect(ctx context.Context, s *Service, actor Actor) error

func (*Toggl) Preview added in v0.5.0

func (t *Toggl) Preview(ctx context.Context, s *Service, actor Actor, token string, workspace int64) (TogglPreview, error)

func (*Toggl) Register added in v0.5.0

func (t *Toggl) Register(workers *hopper.Workers) hopper.PeriodicJob

func (*Toggl) RequestSync added in v0.5.0

func (t *Toggl) RequestSync(ctx context.Context, s *Service, actor Actor) error

func (*Toggl) Resolve added in v0.5.0

func (t *Toggl) Resolve(ctx context.Context, s *Service, actor Actor, id uuid.UUID, in TogglResolution) error

func (*Toggl) Status added in v0.5.0

func (t *Toggl) Status(ctx context.Context, s *Service, actor Actor) (TogglStatus, error)

type TogglIssue added in v0.5.0

type TogglIssue struct {
	EntryID  uuid.UUID  `json:"entryId"`
	PersonID string     `json:"personId"`
	RemoteID *int64     `json:"remoteId,omitempty"`
	Kind     string     `json:"kind"`
	Version  string     `json:"version"`
	Local    togglState `json:"local"`
	Remote   togglState `json:"remote"`
	// TimeOff is set for Toggl entries on the vacation or sick project, which
	// sync to time off on a day rather than to an entry.
	TimeOff *TogglTimeOff `json:"timeOff,omitempty"`
}

type TogglMapping added in v0.5.0

type TogglMapping struct {
	PersonID string `json:"personId"`
	UserID   int64  `json:"userId" minimum:"1"`
}

type TogglPreview added in v0.5.0

type TogglPreview struct {
	Workspaces []toggl.Workspace `json:"workspaces"`
	Users      []toggl.User      `json:"users"`
	Projects   []TogglProject    `json:"projects"`
	Suggested  []TogglMapping    `json:"suggested"`
}

type TogglProject added in v0.15.0

type TogglProject struct {
	ID     int64  `json:"id"`
	Name   string `json:"name"`
	Active bool   `json:"active"`
}

TogglProject is a project in the Toggl workspace, to choose roles from.

type TogglResolution added in v0.5.0

type TogglResolution struct {
	Choice   string `json:"choice" enum:"local,remote,retry,link"`
	Version  string `json:"version"`
	RemoteID int64  `json:"remoteId,omitempty"`
}

Resolve records an explicit choice against the exact versions the admin saw. The worker still validates payroll locks and rechecks Toggl before pushing.

type TogglRoles added in v0.15.0

type TogglRoles struct {
	HolidayProject  int64 `json:"holidayProject,omitempty" minimum:"0" doc:"Entries on it are left out: company holidays pay that time."`
	VacationProject int64 `json:"vacationProject,omitempty" minimum:"0" doc:"Entries on it become vacation."`
	SickProject     int64 `json:"sickProject,omitempty" minimum:"0" doc:"Entries on it become sick time."`
}

TogglRoles are the Toggl projects that record time away rather than work. Each applies to entries from the day it was chosen; zero is none.

type TogglSetup added in v0.5.0

type TogglSetup struct {
	Token       string         `json:"token" maxLength:"256"`
	WorkspaceID int64          `json:"workspaceId" minimum:"1"`
	From        string         `json:"from" doc:"Optional start date (YYYY-MM-DD). Empty imports all history."`
	People      []TogglMapping `json:"people" minItems:"1" maxItems:"1000"`
	TogglRoles
}

type TogglStatus added in v0.5.0

type TogglStatus struct {
	HistoryComplete bool       `json:"historyComplete"`
	HistoryThrough  string     `json:"historyThrough"`
	Available       bool       `json:"available"`
	Connected       bool       `json:"connected"`
	WorkspaceID     int64      `json:"workspaceId"`
	From            string     `json:"from"`
	LastSync        *time.Time `json:"lastSync,omitempty"`
	NextSync        *time.Time `json:"nextSync,omitempty"`
	Error           string     `json:"error"`
	// RateLimitedUntil is when Toggl's API limit allows Sync now again.
	RateLimitedUntil *time.Time     `json:"rateLimitedUntil,omitempty"`
	People           []TogglMapping `json:"people"`
	TogglRoles
	HolidayFrom  string       `json:"holidayFrom,omitempty"`
	VacationFrom string       `json:"vacationFrom,omitempty"`
	SickFrom     string       `json:"sickFrom,omitempty"`
	HolidayName  string       `json:"holidayName,omitempty" doc:"The project's name once a sync has brought it in."`
	VacationName string       `json:"vacationName,omitempty"`
	SickName     string       `json:"sickName,omitempty"`
	Issues       []TogglIssue `json:"issues"`
}

type TogglTimeOff added in v0.15.0

type TogglTimeOff struct {
	Kind  string  `json:"kind" enum:"vacation,sick"`
	Day   Date    `json:"day" format:"date"`
	Hours float64 `json:"hours" doc:"What the Toggl entries add up to."`
}

TogglTimeOff is the time off a day's Toggl entries add up to.

type Workspace added in v0.4.0

type Workspace struct {
	ID uuid.UUID `json:"id"`
	// Key is how the host, or the address, names it.
	Key  string `json:"key"`
	Name string `json:"name"`
}

Workspace is one organization's Timeclock.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL