217 lines
6.2 KiB
Go
217 lines
6.2 KiB
Go
package models
|
|
|
|
import (
|
|
"arbeitszeitmessung/helper"
|
|
"database/sql"
|
|
"encoding/json"
|
|
"fmt"
|
|
"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 (d *WorkDay) GetWorkDays(card_uid string, tsFrom, tsTo time.Time) []WorkDay {
|
|
var workDays []WorkDay
|
|
var workSec, pauseSec float64
|
|
|
|
qStr, err := DB.Prepare(`
|
|
WITH all_days AS (
|
|
SELECT generate_series($2, $3, INTERVAL '1 day')::DATE AS work_date
|
|
),
|
|
ordered_bookings AS (
|
|
SELECT
|
|
timestamp::DATE AS work_date,
|
|
timestamp,
|
|
check_in_out,
|
|
counter_id,
|
|
LAG(timestamp) OVER (PARTITION BY card_uid, timestamp::DATE ORDER BY timestamp) AS prev_timestamp,
|
|
LAG(check_in_out) OVER (PARTITION BY card_uid, timestamp::DATE ORDER BY timestamp) AS prev_check
|
|
FROM anwesenheit
|
|
WHERE card_uid = $1
|
|
AND timestamp::DATE >= $2
|
|
AND 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, 255)
|
|
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, 255) 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
|
|
) 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;`)
|
|
|
|
if err != nil {
|
|
log.Println("Error preparing SQL statement", err)
|
|
return workDays
|
|
}
|
|
|
|
defer qStr.Close()
|
|
rows, err := qStr.Query(card_uid, 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 = NewAbsence(card_uid, int8(absenceType.Int16), workDay.Day)
|
|
log.Println("Found absence", workDay.Absence)
|
|
}
|
|
|
|
if workDay.Day.Equal(time.Now().Truncate(24 * time.Hour)) {
|
|
workDay.getWorkTime()
|
|
} else {
|
|
workDay.calcPauseTime()
|
|
}
|
|
if emptyDays || workDay.Bookings[0].CounterId != 0 {
|
|
workDays = append(workDays, workDay)
|
|
} else {
|
|
log.Println("no booking on day", workDay.Day.Format("02.01.2006"))
|
|
}
|
|
}
|
|
if err = rows.Err(); err != nil {
|
|
return workDays
|
|
}
|
|
return workDays
|
|
}
|
|
|
|
func (d *WorkDay) calcPauseTime() {
|
|
if d.workTime > 6*time.Hour && d.pauseTime < 45*time.Minute {
|
|
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
|
|
}
|
|
}
|
|
}
|
|
|
|
// Gets the duration someone worked that day
|
|
func (d *WorkDay) getWorkTime() {
|
|
if len(d.Bookings) <= 1 && d.Bookings[0].CounterId == 0 {
|
|
return
|
|
}
|
|
var workTime, pauseTime time.Duration
|
|
var lastBooking Booking
|
|
for _, booking := range d.Bookings {
|
|
if booking.CheckInOut%2 == 1 {
|
|
if !lastBooking.Timestamp.IsZero() {
|
|
pauseTime += booking.Timestamp.Sub(lastBooking.Timestamp)
|
|
}
|
|
} else {
|
|
workTime += booking.Timestamp.Sub(lastBooking.Timestamp)
|
|
}
|
|
lastBooking = booking
|
|
}
|
|
// checks if booking is today and has no gehen yet, so the time since last kommen booking is added to workTime
|
|
if d.Day.Day() == time.Now().Day() && len(d.Bookings)%2 == 1 {
|
|
workTime += time.Since(lastBooking.Timestamp.Local())
|
|
}
|
|
d.workTime = workTime
|
|
d.pauseTime = pauseTime
|
|
|
|
d.calcPauseTime()
|
|
}
|
|
|
|
// Converts duration to string
|
|
func formatDuration(d time.Duration) string {
|
|
hours := int(d.Abs().Hours())
|
|
minutes := int(d.Abs().Minutes()) % 60
|
|
switch {
|
|
case hours > 0:
|
|
return fmt.Sprintf("%dh %dmin", hours, minutes)
|
|
case minutes > 0:
|
|
return fmt.Sprintf("%dmin", minutes)
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
func (d *WorkDay) GetWorkTimeString() (string, string) {
|
|
workString := formatDuration(d.workTime)
|
|
pauseString := formatDuration(d.pauseTime)
|
|
return workString, pauseString
|
|
}
|
|
|
|
// returns bool wheter the workday was ended with an automatic logout
|
|
func (d *WorkDay) RequiresAction() bool {
|
|
if len(d.Bookings) > 0 {
|
|
return d.Bookings[len(d.Bookings)-1].CheckInOut == 254
|
|
}
|
|
return false
|
|
}
|
|
|
|
// 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))
|
|
progress := (d.workTime.Seconds() / defaultWorkTime.Seconds()) * 100
|
|
return uint8(progress)
|
|
}
|