Projeto bot pontos

This commit is contained in:
2026-10-23 22:31:02 -03:00
parent 29614855ff
commit b35e1a7d69
116 changed files with 14523 additions and 0 deletions

View 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
}