package models import ( "database/sql" "errors" "log" "log/slog" "time" "github.com/lib/pq" ) // Workweeks are type WorkWeek struct { Id int WorkDays []WorkDay Absences []Absence Days []IWorkDay User User WeekStart time.Time Worktime time.Duration WorkTimeVirtual time.Duration Overtime time.Duration Status WeekStatus } type WeekStatus int8 const ( WeekStatusNone WeekStatus = iota WeekStatusSent WeekStatusAccepted WeekStatusDifferences ) func NewWorkWeek(user User, tsMonday time.Time, populate bool) WorkWeek { var week WorkWeek = WorkWeek{ User: user, WeekStart: tsMonday, Status: WeekStatusNone, } if populate { week.PopulateWithDays(0, 0) } return week } func (w *WorkWeek) PopulateWithDays(worktime time.Duration, overtime time.Duration) { slog.Debug("Got Days with overtime and worktime", slog.String("worktime", worktime.String()), slog.String("overtime", overtime.String())) w.Days = GetDays(w.User, w.WeekStart, w.WeekStart.Add(6*24*time.Hour), false) for _, day := range w.Days { work, _ := day.TimePauseReal(w.User) w.Worktime += work w.WorkTimeVirtual += day.TimeWorkVirtual(w.User) } slog.Debug("Got worktime for user", "user", w.User, "worktime", w.Worktime.String(), "virtualWorkTime", w.WorkTimeVirtual.String()) w.Overtime = w.WorkTimeVirtual - w.User.ArbeitszeitProWoche() w.Worktime = w.Worktime.Round(time.Minute) w.Overtime = w.Overtime.Round(time.Minute) if overtime == 0 && worktime == 0 { return } if overtime != w.Overtime || worktime != w.Worktime { w.Status = WeekStatusDifferences } } func (w *WorkWeek) CheckStatus() WeekStatus { if w.Status != WeekStatusNone { return w.Status } if DB == nil { log.Println("Cannot access Database!") return w.Status } qStr, err := DB.Prepare(`SELECT bestaetigt FROM wochen_report WHERE woche_start = $1::DATE AND personal_nummer = $2;`) if err != nil { log.Println("Error preparing SQL statement", err) return w.Status } defer qStr.Close() var beastatigt bool err = qStr.QueryRow(w.WeekStart, w.User.PersonalNummer).Scan(&beastatigt) if err == sql.ErrNoRows { return w.Status } if err != nil { log.Println("Error querying database", err) return w.Status } if beastatigt { w.Status = WeekStatusAccepted } else { w.Status = WeekStatusSent } return w.Status } func (w *WorkWeek) aggregateWorkTime() time.Duration { var workTime time.Duration for _, day := range w.WorkDays { workTime += day.workTime } // for _, absence := range w.Absences { // log.Println(absence) // absenceWorkTime := float32(8) // := absences.AbwesenheitTyp.WorkTime - (absences.AbwesenheitTyp.WorkTime - w.User.ArbeitszeitPerTag) // workTime Equivalent of Absence is capped at user Worktime per Day // workTime += time.Duration(absenceWorkTime * float32(time.Hour)).Round(time.Minute) // } return workTime } func (w *WorkWeek) GetSendWeeks(user User) []WorkWeek { var weeks []WorkWeek qStr, err := DB.Prepare(`SELECT id, woche_start::DATE, (EXTRACT(epoch FROM arbeitszeit)*1000000000)::BIGINT, (EXTRACT(epoch FROM ueberstunden)*1000000000)::BIGINT FROM wochen_report WHERE bestaetigt = FALSE AND personal_nummer = $1;`) if err != nil { log.Println("Error preparing SQL statement", err) return weeks } defer qStr.Close() rows, err := qStr.Query(user.PersonalNummer) if err != nil { log.Println("Error querining db!", err) return weeks } defer rows.Close() for rows.Next() { var week WorkWeek = WorkWeek{User: user} var workTime, overTime time.Duration if err := rows.Scan(&week.Id, &week.WeekStart, &workTime, &overTime); err != nil { log.Println("Error scanning row!", err) return weeks } week.PopulateWithDays(workTime, overTime) weeks = append(weeks, week) } if err = rows.Err(); err != nil { return weeks } return weeks } var ErrRunningWeek = errors.New("Week is in running week") func (w *WorkWeek) GetBookingIds() (anwesenheitsIds, abwesenheitsIds []int64, err error) { qStr, err := DB.Prepare(` SELECT (SELECT array_agg(counter_id ORDER BY counter_id) FROM anwesenheit WHERE card_uid = $1 AND timestamp::DATE >= $2 AND timestamp::DATE < $3) AS anwesenheit, (SELECT array_agg(counter_id ORDER BY counter_id) FROM abwesenheit WHERE card_uid = $1 AND datum_from < $3 AND datum_to >= $2) AS abwesenheit; `) if err != nil { return nil, nil, err } defer qStr.Close() slog.Debug("Inserting parameters into qStr:", "user card_uid", w.User.CardUID, "week_start", w.WeekStart, "week_end", w.WeekStart.AddDate(0, 0, 5)) err = qStr.QueryRow(w.User.CardUID, w.WeekStart, w.WeekStart.AddDate(0, 0, 5)).Scan(pq.Array(&anwesenheitsIds), pq.Array(&abwesenheitsIds)) if err != nil { return anwesenheitsIds, abwesenheitsIds, err } return anwesenheitsIds, abwesenheitsIds, nil } // creates a new entry in the woche_report table with the given workweek func (w *WorkWeek) SendWeek() error { var qStr *sql.Stmt var err error slog.Info("Sending workWeek to team head", "week", w.WeekStart.String()) anwBookings, awBookings, err := w.GetBookingIds() if err != nil { slog.Warn("Error querying bookings from work week", slog.Any("error", err)) return err } slog.Debug("Recieved Booking Ids", "anwesenheiten", anwBookings) if time.Since(w.WeekStart) < 5*24*time.Hour { slog.Warn("Cannot send week, because it's the running week!") return ErrRunningWeek } if w.CheckStatus() != WeekStatusNone { qStr, err = DB.Prepare(`UPDATE "wochen_report" SET bestaetigt = FALSE, arbeitszeit = make_interval(secs => $3::numeric / 1000000000), ueberstunden = make_interval(secs => $4::numeric / 1000000000), anwesenheiten=$5, abwesenheiten=$6 WHERE personal_nummer = $1 AND woche_start = $2;`) if err != nil { slog.Warn("Error preparing SQL statement", "error", err) return err } } else { qStr, err = DB.Prepare(`INSERT INTO wochen_report (personal_nummer, woche_start, arbeitszeit, ueberstunden, anwesenheiten, abwesenheiten) VALUES ($1, $2, make_interval(secs => $3::numeric / 1000000000), make_interval(secs => $4::numeric / 1000000000), $5, $6);`) if err != nil { slog.Warn("Error preparing SQL statement", "error", err) return err } } _, err = qStr.Exec(w.User.PersonalNummer, w.WeekStart, int64(w.Worktime), int64(w.Overtime), pq.Array(anwBookings), pq.Array(awBookings)) if err != nil { log.Println("Error executing query!", err) return err } return nil } func (w *WorkWeek) Accept() error { qStr, err := DB.Prepare(`UPDATE "wochen_report" SET bestaetigt = TRUE WHERE personal_nummer = $1 AND woche_start = $2;`) if err != nil { log.Println("Error preparing SQL statement", err) return err } _, err = qStr.Exec(w.User.PersonalNummer, w.WeekStart) if err != nil { log.Println("Error executing query!", err) return err } return nil } func (w *WorkWeek) RequiresAction() bool { var requiresAction bool = false for _, day := range w.Days { requiresAction = requiresAction || day.RequiresAction() } return requiresAction }