feat(m1): backend scaffold - entries CRUD, start/stop, auth, migrations
This commit is contained in:
119
internal/domain/domain.go
Normal file
119
internal/domain/domain.go
Normal file
@@ -0,0 +1,119 @@
|
||||
package domain
|
||||
|
||||
// DayKind represents the kind of a closed day.
|
||||
type DayKind string
|
||||
|
||||
const (
|
||||
DayKindWork DayKind = "work"
|
||||
DayKindHoliday DayKind = "holiday"
|
||||
DayKindVacation DayKind = "vacation"
|
||||
DayKindSick DayKind = "sick"
|
||||
)
|
||||
|
||||
func (k DayKind) Valid() bool {
|
||||
switch k {
|
||||
case DayKindWork, DayKindHoliday, DayKindVacation, DayKindSick:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Entry is a tracked time range within a single day.
|
||||
type Entry struct {
|
||||
ID string `json:"id"`
|
||||
StartTime int64 `json:"start_time"` // unix ms UTC
|
||||
EndTime *int64 `json:"end_time"` // nil while running
|
||||
AutoStopped bool `json:"auto_stopped"`
|
||||
Note string `json:"note"`
|
||||
DayKey string `json:"day_key"` // YYYY-MM-DD in configured tz
|
||||
UpdatedAt int64 `json:"updated_at"`
|
||||
DeletedAt *int64 `json:"deleted_at,omitempty"`
|
||||
}
|
||||
|
||||
// IsRunning returns true if the entry has no end time.
|
||||
func (e *Entry) IsRunning() bool {
|
||||
return e.EndTime == nil
|
||||
}
|
||||
|
||||
// DurationMs returns the duration in milliseconds. Returns 0 for running entries.
|
||||
func (e *Entry) DurationMs() int64 {
|
||||
if e.EndTime == nil {
|
||||
return 0
|
||||
}
|
||||
return *e.EndTime - e.StartTime
|
||||
}
|
||||
|
||||
// ClosedDay is the merged result of closing a day.
|
||||
type ClosedDay struct {
|
||||
DayKey string `json:"day_key"`
|
||||
StartTime *int64 `json:"start_time"` // nil for non-work kinds
|
||||
EndTime *int64 `json:"end_time"` // nil for non-work kinds
|
||||
WorkedMs int64 `json:"worked_ms"`
|
||||
Kind DayKind `json:"kind"`
|
||||
ClosedAt int64 `json:"closed_at"`
|
||||
UpdatedAt int64 `json:"updated_at"`
|
||||
}
|
||||
|
||||
// ClosedWeek is the overtime/undertime snapshot for a week.
|
||||
type ClosedWeek struct {
|
||||
WeekKey string `json:"week_key"` // YYYY-Www ISO week
|
||||
ExpectedMs int64 `json:"expected_ms"`
|
||||
WorkedMs int64 `json:"worked_ms"`
|
||||
DeltaMs int64 `json:"delta_ms"` // worked - expected (signed)
|
||||
ClosedAt int64 `json:"closed_at"`
|
||||
UpdatedAt int64 `json:"updated_at"`
|
||||
}
|
||||
|
||||
// Settings holds the effective configuration for a period.
|
||||
type Settings struct {
|
||||
ID int64 `json:"id"`
|
||||
EffectiveFrom string `json:"effective_from"` // YYYY-MM-DD
|
||||
HoursPerWeek float64 `json:"hours_per_week"`
|
||||
WorkdaysMask int `json:"workdays_mask"` // bits Mon=1..Sun=64
|
||||
Timezone string `json:"timezone"`
|
||||
CreatedAt int64 `json:"created_at"`
|
||||
}
|
||||
|
||||
// DailyExpectedMs returns the expected milliseconds for a single workday.
|
||||
func (s *Settings) DailyExpectedMs() int64 {
|
||||
days := popcount(s.WorkdaysMask)
|
||||
if days == 0 {
|
||||
return 0
|
||||
}
|
||||
totalMs := int64(s.HoursPerWeek * 3_600_000)
|
||||
return totalMs / int64(days)
|
||||
}
|
||||
|
||||
// IsWorkday returns true if the given weekday (0=Sunday..6=Saturday, time.Weekday) is a workday.
|
||||
// We use Mon=bit0 .. Sun=bit6 internally.
|
||||
func (s *Settings) IsWorkday(wd int) bool {
|
||||
// time.Weekday: Sunday=0, Monday=1, ..., Saturday=6
|
||||
// our mask: Monday=bit0(1), Tuesday=bit1(2), ... Sunday=bit6(64)
|
||||
var bit int
|
||||
switch wd {
|
||||
case 0: // Sunday
|
||||
bit = 64
|
||||
case 1: // Monday
|
||||
bit = 1
|
||||
case 2:
|
||||
bit = 2
|
||||
case 3:
|
||||
bit = 4
|
||||
case 4:
|
||||
bit = 8
|
||||
case 5:
|
||||
bit = 16
|
||||
case 6: // Saturday
|
||||
bit = 32
|
||||
}
|
||||
return s.WorkdaysMask&bit != 0
|
||||
}
|
||||
|
||||
func popcount(n int) int {
|
||||
count := 0
|
||||
for n != 0 {
|
||||
count += n & 1
|
||||
n >>= 1
|
||||
}
|
||||
return count
|
||||
}
|
||||
Reference in New Issue
Block a user