Projeto bot pontos
This commit is contained in:
73
backend/internal/rules/workday.go
Normal file
73
backend/internal/rules/workday.go
Normal file
@@ -0,0 +1,73 @@
|
||||
// Package rules contém os cálculos determinísticos da jornada. Não conhece
|
||||
// banco, HTTP nem Telegram, e por isso é testado sem nenhuma dependência.
|
||||
package rules
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"botpontos/internal/domain"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNotEnoughPoints = errors.New("pontos insuficientes para o cálculo")
|
||||
ErrNoRemainingWork = errors.New("tempo restante da jornada igual ou menor que zero")
|
||||
)
|
||||
|
||||
// CurrentWorkday filtra os pontos da data (AAAA-MM-DD, no fuso loc), ordena e
|
||||
// classifica: mais de quatro pontos é hora extra; horários repetidos ou fora
|
||||
// de ordem tornam o dia inválido.
|
||||
func CurrentWorkday(points []domain.Point, day string, loc *time.Location) domain.Workday {
|
||||
filtered := make([]domain.Point, 0, len(points))
|
||||
for _, p := range points {
|
||||
if domain.LocalDay(p.OccurredAt, loc) == day {
|
||||
filtered = append(filtered, p)
|
||||
}
|
||||
}
|
||||
sort.Slice(filtered, func(i, j int) bool { return filtered[i].OccurredAt.Before(filtered[j].OccurredAt) })
|
||||
|
||||
workday := domain.Workday{Points: filtered}
|
||||
if len(filtered) > 4 {
|
||||
workday.Overtime = true
|
||||
return workday
|
||||
}
|
||||
for i := 1; i < len(filtered); i++ {
|
||||
if !filtered[i].OccurredAt.After(filtered[i-1].OccurredAt) {
|
||||
workday.Invalid = true
|
||||
}
|
||||
}
|
||||
return workday
|
||||
}
|
||||
|
||||
// Entrada2Time: retorno previsto do almoço = saida1 + almoço.
|
||||
func Entrada2Time(w domain.Workday, lunchMinutes int) (time.Time, error) {
|
||||
if w.Count() < 2 {
|
||||
return time.Time{}, ErrNotEnoughPoints
|
||||
}
|
||||
return w.Exit1().Add(time.Duration(lunchMinutes) * time.Minute), nil
|
||||
}
|
||||
|
||||
// Saida2Time: saída prevista = entrada2 + (jornada − (saida1 − entrada1)).
|
||||
func Saida2Time(w domain.Workday, dailyWorkMinutes int) (time.Time, error) {
|
||||
if w.Count() < 3 {
|
||||
return time.Time{}, ErrNotEnoughPoints
|
||||
}
|
||||
workedBeforeLunch := w.Exit1().Sub(w.Entry1())
|
||||
remaining := time.Duration(dailyWorkMinutes)*time.Minute - workedBeforeLunch
|
||||
if remaining <= 0 {
|
||||
return time.Time{}, ErrNoRemainingWork
|
||||
}
|
||||
return w.Entry2().Add(remaining), nil
|
||||
}
|
||||
|
||||
// ReminderAt é o horário do aviso: o horário previsto menos a antecedência.
|
||||
func ReminderAt(predicted time.Time, leadMinutes int) time.Time {
|
||||
return predicted.Add(-time.Duration(leadMinutes) * time.Minute)
|
||||
}
|
||||
|
||||
// IsWeekend indica sábado ou domingo no fuso informado.
|
||||
func IsWeekend(t time.Time, loc *time.Location) bool {
|
||||
wd := t.In(loc).Weekday()
|
||||
return wd == time.Saturday || wd == time.Sunday
|
||||
}
|
||||
Reference in New Issue
Block a user