Files
bot-pontos/backend/internal/adapters/postgres/schedules.go
2026-10-23 22:31:02 -03:00

94 lines
3.4 KiB
Go

package postgres
import (
"context"
"fmt"
"botpontos/internal/domain"
)
func scheduleCols(p string) string {
return fmt.Sprintf(`%[1]suser_id, %[1]slunch_minutes, %[1]sdaily_work_minutes, %[1]sentrada2_lead_minutes,
%[1]ssaida2_lead_minutes,
to_char(%[1]squery_window_start, 'HH24:MI'), to_char(%[1]squery_window_end, 'HH24:MI'),
to_char(%[1]sday_check_deadline, 'HH24:MI'), to_char(%[1]sno_points_recheck_until, 'HH24:MI')`, p)
}
type scheduleScan struct {
s domain.WorkSchedule
start, end, deadline, recheckUntil *string
}
func (s *scheduleScan) dest() []any {
return []any{&s.s.UserID, &s.s.LunchMinutes, &s.s.DailyWorkMinutes, &s.s.Entrada2LeadMinutes,
&s.s.Saida2LeadMinutes, &s.start, &s.end, &s.deadline, &s.recheckUntil}
}
func (s *scheduleScan) result() (domain.WorkSchedule, error) {
out := s.s
if s.start == nil || s.end == nil || s.deadline == nil || s.recheckUntil == nil {
return out, nil
}
var w domain.QueryWindow
var err error
if w.Start, err = domain.ParseClock(*s.start); err != nil {
return out, err
}
if w.End, err = domain.ParseClock(*s.end); err != nil {
return out, err
}
if w.DayCheckDeadline, err = domain.ParseClock(*s.deadline); err != nil {
return out, err
}
if w.NoPointsRecheckUntil, err = domain.ParseClock(*s.recheckUntil); err != nil {
return out, err
}
out.Window = &w
return out, nil
}
// GetSchedule devolve a jornada do usuário ou a padrão, se ainda não houver linha.
func (s *Store) GetSchedule(ctx context.Context, userID int64) (domain.WorkSchedule, error) {
var ss scheduleScan
err := s.pool.QueryRow(ctx, `SELECT `+scheduleCols("")+` FROM work_schedules WHERE user_id = $1`, userID).Scan(ss.dest()...)
if err != nil {
if mapErr(err) == domain.ErrNotFound {
return domain.DefaultSchedule(userID), nil
}
return domain.WorkSchedule{}, err
}
return ss.result()
}
func (s *Store) SaveSchedule(ctx context.Context, sch domain.WorkSchedule) error {
var start, end, deadline, recheck *string
if w := sch.Window; w != nil {
start, end = strPtr(w.Start.String()), strPtr(w.End.String())
deadline, recheck = strPtr(w.DayCheckDeadline.String()), strPtr(w.NoPointsRecheckUntil.String())
}
_, err := s.pool.Exec(ctx, `
INSERT INTO work_schedules (user_id, lunch_minutes, daily_work_minutes, entrada2_lead_minutes, saida2_lead_minutes,
query_window_start, query_window_end, day_check_deadline, no_points_recheck_until)
VALUES ($1, $2, $3, $4, $5, $6::text::time, $7::text::time, $8::text::time, $9::text::time)
ON CONFLICT (user_id) DO UPDATE SET
lunch_minutes = EXCLUDED.lunch_minutes,
daily_work_minutes = EXCLUDED.daily_work_minutes,
entrada2_lead_minutes = EXCLUDED.entrada2_lead_minutes,
saida2_lead_minutes = EXCLUDED.saida2_lead_minutes,
query_window_start = EXCLUDED.query_window_start,
query_window_end = EXCLUDED.query_window_end,
day_check_deadline = EXCLUDED.day_check_deadline,
no_points_recheck_until = EXCLUDED.no_points_recheck_until`,
sch.UserID, sch.LunchMinutes, sch.DailyWorkMinutes, sch.Entrada2LeadMinutes, sch.Saida2LeadMinutes,
start, end, deadline, recheck)
return mapErr(err)
}
func (s *Store) CountCustomWindows(ctx context.Context) (int, error) {
var n int
err := s.pool.QueryRow(ctx, `
SELECT count(*) FROM work_schedules ws JOIN users u ON u.id = ws.user_id
WHERE ws.query_window_start IS NOT NULL AND u.status = 'active'`).Scan(&n)
return n, err
}