Files
bot-pontos/backend/internal/rules/workday.go
2026-10-23 22:31:02 -03:00

74 lines
2.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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