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 }