diff --git a/Backend/endpoints/time-create.go b/Backend/endpoints/time-create.go index 6e3097b..4478691 100644 --- a/Backend/endpoints/time-create.go +++ b/Backend/endpoints/time-create.go @@ -10,7 +10,6 @@ import ( "errors" "log" "net/http" - "time" ) // Relevant for arduino inputs -> creates new Booking from get and put method @@ -40,7 +39,7 @@ func createBooking(w http.ResponseWriter, r *http.Request) { } booking := (*models.Booking).FromUrlParams(nil, r.URL.Query()) - booking.Timestamp = time.Now() + // booking.Timestamp = time.Now() if booking.Verify() { err := booking.Insert() if errors.Is(models.SameBookingError{}, err) { diff --git a/Backend/main.go b/Backend/main.go index 761cf17..49fca64 100644 --- a/Backend/main.go +++ b/Backend/main.go @@ -52,6 +52,8 @@ func main() { defer models.DB.(*sql.DB).Close() + models.Options = configure() + if helper.GetEnv("GO_ENV", "production") != "debug" { err = Migrate() if err != nil { @@ -114,3 +116,10 @@ func loggingMiddleware(next http.Handler) http.Handler { slog.Info("Completet Request", slog.String("Time", time.Since(start).String())) }) } + +func configure() models.BookingOptions { + return models.BookingOptions{ + AllowOutOfBounds: helper.GetEnv("BOOKING_OUT_OF_BOUNDS", "false") == "true", + AllowUnknownUser: helper.GetEnv("BOOKING_FOR_UNKNOWN_USER", "false") == "true", + } +} diff --git a/Backend/models/booking.go b/Backend/models/booking.go index 8072a0f..fb947ca 100644 --- a/Backend/models/booking.go +++ b/Backend/models/booking.go @@ -39,6 +39,11 @@ type Booking struct { Valid bool `json:"valid"` } +type BookingOptions struct { + AllowOutOfBounds bool + AllowUnknownUser bool +} + type IDatabase interface { Prepare(query string) (*sql.Stmt, error) Exec(query string, args ...any) (sql.Result, error) @@ -46,6 +51,8 @@ type IDatabase interface { var DB IDatabase +var Options BookingOptions + func (b *Booking) NewBooking(cardUid string, gereatId int16, checkInOut int16, typeId int8) Booking { bookingType, err := GetBookingTypeById(typeId) if err != nil { @@ -92,10 +99,44 @@ func (b *Booking) Verify() bool { } else { b.BookingType.Name = bookingType.Name } + + user, err := GetUserByCardUID(b.CardUID) + if err == sql.ErrNoRows { + log.Println("Cannot find user with given CardUID") + return Options.AllowUnknownUser // if allow do not fail verify if not allow fail verify + } + + if err != nil { + slog.Error("Cannot get user from CardUID", "error", err) + return false + } + + if bookingOutOfBounds(b, &user) { + auditLog, closeLog := logs.NewAudit() + defer closeLog() + if !Options.AllowOutOfBounds { + return false + } + + oldTime := b.Timestamp + if oldTime.IsZero() { + oldTime = time.Now() + } + if b.CheckInOut%2 == 1 && b.CheckInOut < 200 { //kommen Booking + b.Timestamp = user.ArbeitMinStartTime(oldTime) + } else { + b.Timestamp = user.ArbeitMaxEndeTime(oldTime) + } + auditLog.Printf("Buchung (%s) von '%s' außerhalb der regulaeren Zeit. Verschieben der Zeit %s -> %s", b.GetBookingType(), user.CardUID, oldTime.Format(time.TimeOnly), b.Timestamp.Format(time.TimeOnly)) + slog.Info("Booking is out of work time bounds, setting time to match worktime bounds", "new_time", b.Timestamp.String(), "old_time", oldTime) + } return true } func (b *Booking) Insert() error { + if !b.Timestamp.IsZero() { + return b.InsertWithTimestamp() + } if !checkLastBooking(*b) { return SameBookingError{} } @@ -224,20 +265,21 @@ func (b *Booking) Update(nb Booking) { b.GeraetID = nb.GeraetID } if b.Timestamp != nb.Timestamp { - auditLog.Printf("Änderung in Buchung %d von '%s': Buchungszeit (%s -> %s).", b.CounterId, b.CardUID, b.Timestamp.Format("15:04"), nb.Timestamp.Format("15:04)")) + auditLog.Printf("Änderung in Buchung %d von '%s': Buchungszeit (%s -> %s).", b.CounterId, b.CardUID, b.Timestamp.Format(time.TimeOnly), nb.Timestamp.Format(time.TimeOnly)) b.Timestamp = nb.Timestamp } } func checkLastBooking(b Booking) bool { var check_in_out int - slog.Info("Checking with timestamp:", "timestamp", b.Timestamp.String()) - stmt, err := DB.Prepare((`SELECT check_in_out FROM "anwesenheit" WHERE "card_uid" = $1 AND "timestamp" <= $2 ORDER BY "timestamp" DESC LIMIT 1;`)) + var timestamp time.Time + slog.Debug("Checking with timestamp:", "timestamp", b.Timestamp) + stmt, err := DB.Prepare((`SELECT check_in_out, timestamp FROM "anwesenheit" WHERE "card_uid" = $1 AND "timestamp" <= $2 ORDER BY "timestamp" DESC LIMIT 1;`)) if err != nil { log.Fatalf("Error preparing query: %v", err) return false } - err = stmt.QueryRow(b.CardUID, b.Timestamp).Scan(&check_in_out) + err = stmt.QueryRow(b.CardUID, b.Timestamp).Scan(&check_in_out, ×tamp) slog.Info("Checking last bookings check_in_out", "Check", check_in_out) if err == sql.ErrNoRows { return true @@ -246,9 +288,13 @@ func checkLastBooking(b Booking) bool { log.Println("Error checking last booking: ", err) return false } + if int16(check_in_out)%2 == b.CheckInOut%2 { return false } + if timestamp.Equal(b.Timestamp) { + return false + } return true } @@ -257,8 +303,6 @@ func (b *Booking) UpdateTime(newTime time.Time) { if hour == b.Timestamp.Hour() && minute == b.Timestamp.Minute() { return } - // TODO: add check for time overlap - var newBooking Booking newBooking.Timestamp = time.Date(b.Timestamp.Year(), b.Timestamp.Month(), b.Timestamp.Day(), hour, minute, 0, 0, b.Timestamp.Location()) if b.CheckInOut < 3 { @@ -268,14 +312,11 @@ func (b *Booking) UpdateTime(newTime time.Time) { newBooking.CheckInOut = 4 } b.Update(newBooking) - // TODO Check verify if b.Verify() { b.Save() } else { log.Println("Cannot save updated booking!", b.ToString()) } - // b.Verify() - // b.Save() } func (b *Booking) ToString() string { @@ -327,3 +368,12 @@ func GetBookingTypesCached() []BookingType { } return types.([]BookingType) } + +func bookingOutOfBounds(b *Booking, u *User) bool { + bookingTime := b.Timestamp + if b.Timestamp.IsZero() { + bookingTime = time.Now() + } + res := bookingTime.Before(u.ArbeitMinStartTime(bookingTime)) || bookingTime.After(u.ArbeitMaxEndeTime(bookingTime)) + return res +} diff --git a/Backend/models/user.go b/Backend/models/user.go index cf74fd9..f6fd885 100644 --- a/Backend/models/user.go +++ b/Backend/models/user.go @@ -28,6 +28,8 @@ type User struct { ArbeitszeitPerTag float32 //`json:"arbeitszeit_per_tag"` ArbeitszeitPerWoche float32 //`json:"arbeitszeit_per_woche"` Overtime time.Duration + ArbeitMinStart time.Time + ArbeitMaxEnde time.Time } func GetUserFromSession(Session *scs.SessionManager, ctx context.Context) (User, error) { @@ -65,35 +67,41 @@ func (u *User) GetReportedOvertime(startDate time.Time) (time.Duration, error) { return overtime, nil } -func GetAllUsers() ([]User, error) { - qStr, err := DB.Prepare((`SELECT card_uid, vorname, nachname,arbeitszeit_per_tag, arbeitszeit_per_woche FROM s_personal_daten;`)) - var users []User - if err != nil { - return users, err - } - defer qStr.Close() - rows, err := qStr.Query() - if err != nil { - return users, err - } - defer rows.Close() - for rows.Next() { +func GetUserByCardUID(cardUid string) (User, error) { + var user User - var user User - if err := rows.Scan(&user.CardUID, &user.Vorname, &user.Name, &user.ArbeitszeitPerTag, &user.ArbeitszeitPerWoche); err != nil { - log.Println("Error creating user!", err) - continue - } - users = append(users, user) + qStr, err := DB.Prepare((`SELECT personal_nummer, vorname, nachname, card_uid, arbeitszeit_per_tag, arbeitszeit_per_woche, arbeitszeit_min_start, arbeitszeit_max_ende FROM s_personal_daten WHERE card_uid = $1;`)) + if err != nil { + return user, err } - if err = rows.Err(); err != nil { - return users, nil + err = qStr.QueryRow(cardUid).Scan(&user.PersonalNummer, &user.Vorname, &user.Name, &user.CardUID, &user.ArbeitszeitPerTag, &user.ArbeitszeitPerWoche, &user.ArbeitMinStart, &user.ArbeitMaxEnde) + + if err != nil { + return user, err } - return users, nil + return user, nil } -func (u *User) GetAll() ([]User, error) { - qStr, err := DB.Prepare((`SELECT card_uid, vorname, nachname FROM s_personal_daten;`)) +func (u *User) ArbeitMinStartTime(date time.Time) time.Time { + if date.Hour() > 0 { + date = date.Truncate(24 * time.Hour).Add(-time.Hour) + } + date = date.Truncate(time.Hour) + slog.Info("Date truncate", "date", date) + return date.Add(time.Hour*time.Duration(u.ArbeitMinStart.Hour()) + time.Minute*time.Duration(u.ArbeitMinStart.Minute())) +} + +func (u *User) ArbeitMaxEndeTime(date time.Time) time.Time { + if date.Hour() > 0 { + date = date.Truncate(24 * time.Hour).Add(-time.Hour) + } + date = date.Truncate(time.Hour) + slog.Info("Date truncate", "date", date) + return date.Add(time.Hour*time.Duration(u.ArbeitMaxEnde.Hour()) + time.Minute*time.Duration(u.ArbeitMaxEnde.Minute())) +} + +func GetAllUsers() ([]User, error) { + qStr, err := DB.Prepare((`SELECT personal_nummer, vorname, nachname, card_uid, arbeitszeit_per_tag, arbeitszeit_per_woche, arbeitszeit_min_start, arbeitszeit_max_ende FROM s_personal_daten;`)) var users []User if err != nil { return users, err @@ -107,7 +115,7 @@ func (u *User) GetAll() ([]User, error) { for rows.Next() { var user User - if err := rows.Scan(&user.CardUID, &user.Vorname, &user.Name); err != nil { + if err := rows.Scan(&user.PersonalNummer, &user.Vorname, &user.Name, &user.CardUID, &user.ArbeitszeitPerTag, &user.ArbeitszeitPerWoche, &user.ArbeitMinStart, &user.ArbeitMaxEnde); err != nil { log.Println("Error creating user!", err) continue } @@ -167,11 +175,11 @@ func (u *User) CheckOut() error { func GetUserByPersonalNr(personalNummer int) (User, error) { var user User - qStr, err := DB.Prepare((`SELECT personal_nummer, card_uid, vorname, nachname, arbeitszeit_per_tag, arbeitszeit_per_woche FROM s_personal_daten WHERE personal_nummer = $1;`)) + qStr, err := DB.Prepare((`SELECT personal_nummer, vorname, nachname, card_uid, arbeitszeit_per_tag, arbeitszeit_per_woche, arbeitszeit_min_start, arbeitszeit_max_ende FROM s_personal_daten WHERE personal_nummer = $1;`)) if err != nil { return user, err } - err = qStr.QueryRow(personalNummer).Scan(&user.PersonalNummer, &user.CardUID, &user.Vorname, &user.Name, &user.ArbeitszeitPerTag, &user.ArbeitszeitPerWoche) + err = qStr.QueryRow(personalNummer).Scan(&user.PersonalNummer, &user.Vorname, &user.Name, &user.CardUID, &user.ArbeitszeitPerTag, &user.ArbeitszeitPerWoche, &user.ArbeitMinStart, &user.ArbeitMaxEnde) if err != nil { return user, err @@ -185,7 +193,7 @@ func GetUserByPersonalNrMulti(personalNummerMulti []int) ([]User, error) { return users, errors.New("No personalNumbers provided") } - qStr, err := DB.Prepare((`SELECT personal_nummer, card_uid, vorname, nachname, arbeitszeit_per_tag, arbeitszeit_per_woche FROM s_personal_daten WHERE personal_nummer = ANY($1::int[]);`)) + qStr, err := DB.Prepare((`SELECT personal_nummer, vorname, nachname, card_uid, arbeitszeit_per_tag, arbeitszeit_per_woche, arbeitszeit_min_start, arbeitszeit_max_ende FROM s_personal_daten WHERE personal_nummer = ANY($1::int[]);`)) if err != nil { return users, err } @@ -200,7 +208,7 @@ func GetUserByPersonalNrMulti(personalNummerMulti []int) ([]User, error) { defer rows.Close() for rows.Next() { var user User - if err := rows.Scan(&user.PersonalNummer, &user.CardUID, &user.Vorname, &user.Name, &user.ArbeitszeitPerTag, &user.ArbeitszeitPerWoche); err != nil { + if err := rows.Scan(&user.PersonalNummer, &user.Vorname, &user.Name, &user.CardUID, &user.ArbeitszeitPerTag, &user.ArbeitszeitPerWoche, &user.ArbeitMinStart, &user.ArbeitMaxEnde); err != nil { return users, err } users = append(users, user) @@ -246,6 +254,7 @@ func (u *User) ChangePass(password, newPassword string) (bool, error) { } func (u *User) GetTeamMembers() ([]User, error) { + var teamMemberPNrs []int var teamMembers []User qStr, err := DB.Prepare(`SELECT personal_nummer FROM s_personal_daten WHERE vorgesetzter_pers_nr = $1 ORDER BY "nachname";`) if err != nil { @@ -261,12 +270,16 @@ func (u *User) GetTeamMembers() ([]User, error) { for rows.Next() { var personalNr int err := rows.Scan(&personalNr) - user, err := GetUserByPersonalNr(personalNr) + teamMemberPNrs = append(teamMemberPNrs, personalNr) if err != nil { log.Println("Error getting user!") return teamMembers, err } - teamMembers = append(teamMembers, user) + } + teamMembers, err = GetUserByPersonalNrMulti(teamMemberPNrs) + if err != nil { + log.Println("Error getting users!") + return teamMembers, err } return teamMembers, nil @@ -343,22 +356,6 @@ LIMIT 1; return lastSub } -func (u *User) GetFromCardUID(card_uid string) (User, error) { - user := User{} - var err error - - qStr, err := DB.Prepare((`SELECT personal_nummer, card_uid, vorname, nachname, arbeitszeit_per_tag FROM s_personal_daten WHERE card_uid = $1;`)) - if err != nil { - return user, err - } - err = qStr.QueryRow(card_uid).Scan(&user.PersonalNummer, &user.CardUID, &user.Vorname, &user.Name, &user.ArbeitszeitPerTag) - - if err != nil { - return user, err - } - return user, nil -} - func (u *User) IsSuperior(e User) bool { var isSuperior int qStr, err := DB.Prepare(`SELECT COUNT(1) FROM s_personal_daten WHERE personal_nummer = $1 AND vorgesetzter_pers_nr = $2`) @@ -372,7 +369,6 @@ func (u *User) IsSuperior(e User) bool { return false } return isSuperior == 1 - } func getMonday(ts time.Time) time.Time { diff --git a/Backend/models/workDay.go b/Backend/models/workDay.go index 7a6e6e3..ff3df58 100644 --- a/Backend/models/workDay.go +++ b/Backend/models/workDay.go @@ -424,10 +424,12 @@ GROUP BY // returns bool wheter the workday was ended with an automatic logout func (d *WorkDay) RequiresAction() bool { - if len(d.Bookings) == 0 { - return false + for i := range d.Bookings { + if d.Bookings[i].CheckInOut > 250 { + return true + } } - return d.Bookings[len(d.Bookings)-1].CheckInOut == 254 + return false } func (d *WorkDay) GetDayProgress(u User) int8 { diff --git a/Backend/templates/basePages.templ b/Backend/templates/basePages.templ index 7dd131c..8e0b79f 100644 --- a/Backend/templates/basePages.templ +++ b/Backend/templates/basePages.templ @@ -61,6 +61,8 @@ templ SettingsPage(status int) {
Nutzername: { user.Vorname } { user.Name }
Personalnummer: { user.PersonalNummer }
+Frühester Arbeitsbegin: { user.ArbeitMinStart.Format("15:06") } Uhr
+Spätester Arbeitsende: { user.ArbeitMaxEnde.Format("15:06") } Uhr
Arbeitszeit pro Tag: { helper.FormatDuration(user.ArbeitszeitProTag()) }
Arbeitszeit pro Woche: { helper.FormatDuration(user.ArbeitszeitProWoche()) }