Bot handlers: clock‑in/out, remote flag, day‑off toggle, daily report, monthly Excel export

This commit is contained in:
2026-06-23 20:22:54 +03:30
parent 4146e35f35
commit 7176f9b81d
3 changed files with 346 additions and 0 deletions

80
internal/bot/totals.go Normal file
View File

@@ -0,0 +1,80 @@
package bot
import (
"sort"
"worktimeBot/internal/db"
)
type State int
const (
StateIdle State = iota
StateWorking
StateOnBreak
)
// DailyTotals holds aggregated times for a single day.
type DailyTotals struct {
TotalSeconds int64
BreakSeconds int64
PairCount int
}
// ComputeDailyTotals pairs events with break inference.
// Multiout sequences: first out ends work, second out starts break,
// next in ends break, next out ends work, etc.
func ComputeDailyTotals(events []db.Event) DailyTotals {
if len(events) == 0 {
return DailyTotals{}
}
sorted := make([]db.Event, len(events))
copy(sorted, events)
sort.Slice(sorted, func(i, j int) bool {
return sorted[i].OccurredAt < sorted[j].OccurredAt
})
var workTotal, breakTotal int64
var workStart, breakStart int64
pairs := 0
state := StateIdle
for _, e := range sorted {
switch {
case e.EventType == "in":
switch state {
case StateIdle, StateOnBreak:
if state == StateOnBreak {
breakTotal += e.OccurredAt - breakStart
breakStart = 0
}
workStart = e.OccurredAt
state = StateWorking
// Working → ignore duplicate in (shouldn't happen)
case StateWorking:
// already working, ignore
}
case e.EventType == "out":
switch state {
case StateWorking:
// End of work period
workTotal += e.OccurredAt - workStart
workStart = 0
pairs++
state = StateIdle
case StateIdle:
// Second consecutive out → break start
breakStart = e.OccurredAt
state = StateOnBreak
case StateOnBreak:
// Duplicate break start ignore
}
}
}
return DailyTotals{
TotalSeconds: workTotal,
BreakSeconds: breakTotal,
PairCount: pairs,
}
}