package models import ( "arbeitszeitmessung/helper" "database/sql" "encoding/json" "log" "strconv" "time" ) type WorkDay struct { Day time.Time `json:"day"` Bookings []Booking `json:"bookings"` workTime time.Duration pauseTime time.Duration TimeFrom time.Time TimeTo time.Time Absence Absence } func GetWorkDays(user User, tsFrom, tsTo time.Time) []WorkDay { var workDays []WorkDay var workSec, pauseSec float64 qStr, err := DB.Prepare(` WITH all_days AS ( SELECT generate_series($2::DATE, $3::DATE - INTERVAL '1 day', INTERVAL '1 day')::DATE AS work_date ), ordered_bookings AS ( SELECT a.timestamp::DATE AS work_date, a.timestamp, a.check_in_out, a.counter_id, a.anwesenheit_typ, sat.anwesenheit_name AS anwesenheit_typ_name, LAG(a.timestamp) OVER (PARTITION BY a.card_uid, a.timestamp::DATE ORDER BY a.timestamp) AS prev_timestamp, LAG(a.check_in_out) OVER (PARTITION BY a.card_uid, a.timestamp::DATE ORDER BY a.timestamp) AS prev_check FROM anwesenheit a LEFT JOIN s_anwesenheit_typen sat ON a.anwesenheit_typ = sat.anwesenheit_id WHERE a.card_uid = $1 AND a.timestamp::DATE >= $2 AND a.timestamp::DATE <= $3 ), abwesenheiten AS ( SELECT datum::DATE AS work_date, abwesenheit_typ FROM abwesenheit WHERE card_uid = $1 AND datum::DATE >= $2 AND datum::DATE <= $3 ) SELECT d.work_date, COALESCE(MIN(b.timestamp), NOW()) AS time_from, COALESCE(MAX(b.timestamp), NOW()) AS time_to, COALESCE( EXTRACT(EPOCH FROM SUM( CASE WHEN b.prev_check IN (1, 3) AND b.check_in_out IN (2, 4, 254) THEN b.timestamp - b.prev_timestamp ELSE INTERVAL '0' END )), 0 ) AS total_work_seconds, COALESCE( EXTRACT(EPOCH FROM SUM( CASE WHEN b.prev_check IN (2, 4, 254) AND b.check_in_out IN (1, 3) THEN b.timestamp - b.prev_timestamp ELSE INTERVAL '0' END )), 0 ) AS total_pause_seconds, COALESCE(jsonb_agg(jsonb_build_object( 'check_in_out', b.check_in_out, 'timestamp', b.timestamp, 'counter_id', b.counter_id, 'anwesenheit_typ', b.anwesenheit_typ, 'anwesenheit_typ', jsonb_build_object( 'anwesenheit_id', b.anwesenheit_typ, 'anwesenheit_name', b.anwesenheit_typ_name ) ) ORDER BY b.timestamp), '[]'::jsonb) AS bookings, a.abwesenheit_typ FROM all_days d LEFT JOIN ordered_bookings b ON d.work_date = b.work_date LEFT JOIN abwesenheiten a ON d.work_date = a.work_date GROUP BY d.work_date, a.abwesenheit_typ ORDER BY d.work_date ASC;`) if err != nil { log.Println("Error preparing SQL statement", err) return workDays } defer qStr.Close() rows, err := qStr.Query(user.CardUID, tsFrom, tsTo) if err != nil { log.Println("Error getting rows!") return workDays } defer rows.Close() emptyDays, _ := strconv.ParseBool(helper.GetEnv("EMPTY_DAYS", "false")) for rows.Next() { var workDay WorkDay var bookings []byte var absenceType sql.NullInt16 if err := rows.Scan(&workDay.Day, &workDay.TimeFrom, &workDay.TimeTo, &workSec, &pauseSec, &bookings, &absenceType); err != nil { log.Println("Error scanning row!", err) return workDays } workDay.workTime = time.Duration(workSec * float64(time.Second)) workDay.pauseTime = time.Duration(pauseSec * float64(time.Second)) err = json.Unmarshal(bookings, &workDay.Bookings) if err != nil { log.Println("Error parsing bookings JSON!", err) return nil } // better empty day handling if len(workDay.Bookings) == 1 && workDay.Bookings[0].CounterId == 0 { workDay.Bookings = []Booking{} } if absenceType.Valid { workDay.Absence, err = NewAbsence(user.CardUID, int(absenceType.Int16), workDay.Day) workDay.CalcRealWorkTime(user) } if workDay.Day.Equal(time.Now().Truncate(24 * time.Hour)) { workDay.CalcRealWorkTime(user) workDay.CalcWorkPauseDiff(user) } else { workDay.CalcWorkPauseDiff(user) } if emptyDays && workDay.Day.Weekday() >= 1 && workDay.Day.Weekday() <= 5 { workDays = append(workDays, workDay) } else if len(workDay.Bookings) > 0 || (workDay.Absence != Absence{}) { workDays = append(workDays, workDay) } } if err = rows.Err(); err != nil { return workDays } return workDays } func (d *WorkDay) CalcWorkPauseDiff(user User) (work, pause time.Duration) { if d.workTime == 0 { d.CalcRealWorkTime(user) } if d.Absence.AbwesenheitTyp.WorkTime > 0 { return d.workTime, d.pauseTime } if d.workTime <= 6*time.Hour || d.pauseTime > 45*time.Minute { return d.workTime, d.pauseTime } if d.workTime <= (9*time.Hour) && d.pauseTime < 30*time.Minute { diff := 30*time.Minute - d.pauseTime d.workTime -= diff d.pauseTime += diff } else if d.pauseTime < 45*time.Minute { diff := 45*time.Minute - d.pauseTime d.workTime -= diff d.pauseTime += diff } return d.workTime, d.pauseTime } func (d *WorkDay) CalcRealWorkTime(user User) time.Duration { if (len(d.Bookings) < 1 && d.Absence == Absence{}) { return 0 } var realWorkTime, realPauseTime time.Duration var lastBooking Booking for _, booking := range d.Bookings { if booking.CheckInOut%2 == 1 { if !lastBooking.Timestamp.IsZero() { realPauseTime += booking.Timestamp.Sub(lastBooking.Timestamp) } } else { realWorkTime += booking.Timestamp.Sub(lastBooking.Timestamp) } lastBooking = booking } if helper.IsSameDate(d.Day, time.Now()) && len(d.Bookings)%2 == 1 { realWorkTime += time.Since(lastBooking.Timestamp.Local()) } if d.Absence.AbwesenheitTyp.WorkTime > 0 { realWorkTime = time.Duration(user.ArbeitszeitPerTag * float32(time.Hour)).Round(time.Minute) log.Println("Rewriting worktime", realWorkTime) } d.workTime = realWorkTime d.pauseTime = realPauseTime return realWorkTime } func (d *WorkDay) GetWorkTimeString() (work string, pause string) { workString := helper.FormatDuration(d.workTime) pauseString := helper.FormatDuration(d.pauseTime) return workString, pauseString } func (d *WorkDay) GetAllWorkTimes(user User) (work, pause, overtime time.Duration) { return d.workTime.Round(time.Minute), d.pauseTime.Round(time.Minute), d.CalcOvertime(user) } // returns bool wheter the workday was ended with an automatic logout func (d *WorkDay) RequiresAction() bool { if len(d.Bookings) == 0 { return false } return d.Bookings[len(d.Bookings)-1].CheckInOut == 254 } // returns a integer percentage of how much day has been worked of func (d *WorkDay) GetWorkDayProgress(user User) uint8 { defaultWorkTime := time.Duration(user.ArbeitszeitPerTag * float32(time.Hour)).Round(time.Minute) progress := (d.workTime.Seconds() / defaultWorkTime.Seconds()) * 100 return uint8(progress) } func (d *WorkDay) CalcOvertime(user User) time.Duration { if d.workTime == 0 { d.CalcWorkPauseDiff(user) } if helper.IsWeekend(d.Day) && len(d.Bookings) == 0 { return 0 } var overtime time.Duration overtime = d.workTime - time.Duration(user.ArbeitszeitPerTag*float32(time.Hour)).Round(time.Minute) return overtime }