package models import ( "arbeitszeitmessung/helper" "arbeitszeitmessung/helper/logs" "database/sql" "fmt" "log" "log/slog" "net/url" "strconv" "time" ) type SameBookingError struct{} type BookingType struct { Id int8 `json:"anwesenheit_id"` Name string `json:"anwesenheit_name"` } func (e SameBookingError) Error() string { return "the same booking already exists!" } type Booking struct { CardUID string `json:"card_uid"` GeraetID int16 `json:"geraet_id"` CheckInOut int16 `json:"check_in_out"` Timestamp time.Time `json:"timestamp"` CounterId int `json:"counter_id"` BookingType BookingType `json:"anwesenheit_typ"` } type IDatabase interface { Prepare(query string) (*sql.Stmt, error) Exec(query string, args ...any) (sql.Result, error) } var DB IDatabase func (b *Booking) New(cardUid string, gereatId int16, checkInOut int16, typeId int8) Booking { bookingType, err := GetBookingTypeById(typeId) if err != nil { log.Printf("Cannot get booking type %d, from database!", typeId) } return Booking{ CardUID: cardUid, GeraetID: gereatId, CheckInOut: checkInOut, BookingType: bookingType, } } func (b *Booking) FromUrlParams(params url.Values) Booking { var booking Booking if _check_in_out, err := strconv.Atoi(params.Get("check_in_out")); err == nil { booking.CheckInOut = int16(_check_in_out) } if _geraet_id, err := strconv.Atoi(params.Get("geraet_id")); err == nil { booking.GeraetID = int16(_geraet_id) } if _booking_type, err := strconv.Atoi(params.Get("booking_type")); err == nil { booking.BookingType.Id = int8(_booking_type) } booking.CardUID = params.Get("card_uid") return booking } func (b *Booking) Verify() bool { //check for overlapping time + arbeitszeit verstoß if b.CardUID == "" { //|| b.GeraetID == 0 || b.CheckInOut == 0 { log.Println("Booking verify failed invalid CardUID!") return false } if b.CheckInOut == 0 { log.Println("Booking verify failed invalid CheckInOut!") return false } if bookingType, err := GetBookingTypeById(b.BookingType.Id); err != nil { log.Println("Booking verify failed invalid BookingType.Id!") return false } else { b.BookingType.Name = bookingType.Name } return true } func (b *Booking) IsSubmittedAndChecked() bool { qStr, err := DB.Prepare(`SELECT bestaetigt from wochen_report WHERE $1 = ANY(anwesenheiten);`) if err != nil { slog.Warn("Error when preparing SQL Statement", "error", err) return false } defer qStr.Close() var isSubmittedAndChecked bool = false err = qStr.QueryRow(b.CounterId).Scan(&isSubmittedAndChecked) if err == sql.ErrNoRows { // No rows found ==> not even submitted return false } if err != nil { slog.Warn("Unexpected error when executing SQL Statement", "error", err) } return isSubmittedAndChecked } func (b *Booking) Insert() error { if !checkLastBooking(*b) { return SameBookingError{} } stmt, err := DB.Prepare((`INSERT INTO anwesenheit (card_uid, geraet_id, check_in_out, anwesenheit_typ) VALUES ($1, $2, $3, $4) RETURNING counter_id, timestamp`)) if err != nil { return err } err = stmt.QueryRow(b.CardUID, b.GeraetID, b.CheckInOut, b.BookingType.Id).Scan(&b.CounterId, &b.Timestamp) if err != nil { return err } return nil } func (b *Booking) InsertWithTimestamp() error { if b.Timestamp.IsZero() { return b.Insert() } stmt, err := DB.Prepare((`INSERT INTO anwesenheit (card_uid, geraet_id, check_in_out, anwesenheit_typ, timestamp) VALUES ($1, $2, $3, $4, $5) RETURNING counter_id`)) if err != nil { return err } err = stmt.QueryRow(b.CardUID, b.GeraetID, b.CheckInOut, b.BookingType.Id, b.Timestamp).Scan(&b.CounterId) if err != nil { return err } return nil } func (b *Booking) GetBookingById(booking_id int) (Booking, error) { var booking Booking qStr, err := DB.Prepare((`SELECT counter_id, timestamp, card_uid, geraet_id, check_in_out, anwesenheit_typ FROM anwesenheit WHERE counter_id = $1`)) if err != nil { return booking, err } // TODO: also get booking type name err = qStr.QueryRow(booking_id).Scan(&booking.CounterId, &booking.Timestamp, &booking.CardUID, &booking.GeraetID, &booking.CheckInOut, &booking.BookingType.Id) if err != nil { return booking, err } return booking, nil } // Gets all booking based on a card uid // // optional filter parameter func (b *Booking) GetBookingsByCardID(card_uid string, tsFrom time.Time, tsTo time.Time) ([]Booking, error) { qStr, err := DB.Prepare((`SELECT counter_id, timestamp, card_uid, geraet_id, check_in_out FROM anwesenheit WHERE card_uid = $1 AND timestamp BETWEEN $2 AND $3 ORDER BY timestamp`)) if err != nil { return nil, err } defer qStr.Close() var bookings []Booking rows, err := qStr.Query(card_uid, tsFrom, tsTo) if err == sql.ErrNoRows { return bookings, err } if err != nil { return nil, err } defer rows.Close() for rows.Next() { var booking Booking if err := rows.Scan(&booking.CounterId, &booking.Timestamp, &booking.CardUID, &booking.GeraetID, &booking.CheckInOut); err != nil { return bookings, err } bookings = append(bookings, booking) } if err = rows.Err(); err != nil { return bookings, err } return bookings, nil } func (b Booking) Save() { qStr, err := DB.Prepare((`UPDATE "anwesenheit" SET "card_uid" = $2, "geraet_id" = $3, "check_in_out" = $4, "timestamp" = $5 WHERE "counter_id" = $1;`)) if err != nil { log.Fatalf("Error preparing query: %v", err) return } _, err = qStr.Query(b.CounterId, b.CardUID, b.GeraetID, b.CheckInOut, b.Timestamp) if err != nil { log.Fatalf("Error executing query: %v", err) return } } func (b *Booking) GetBookingType() string { debug := (helper.GetEnv("GO_ENV", "production") == "debug") switch b.CheckInOut { case 1: //manuelle Änderung return "kommen" case 3: if debug { return "kommen manuell" } return "kommen" case 2: //manuelle Änderung return "gehen" case 4: if debug { return "gehen manuell" } return "gehen" case 254: return "abgemeldet" default: return "Buchungs Typ unbekannt" } } func (b *Booking) Update(nb Booking) { auditLog, closeLog := logs.NewAudit() defer closeLog() if b.CheckInOut != nb.CheckInOut && nb.CheckInOut != 0 { b.CheckInOut = nb.CheckInOut } if b.CardUID != nb.CardUID && nb.CardUID != "" { b.CardUID = nb.CardUID } if b.GeraetID != nb.GeraetID && nb.GeraetID != 0 { 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)")) b.Timestamp = nb.Timestamp } } func checkLastBooking(b Booking) bool { var check_in_out int stmt, err := DB.Prepare((`SELECT check_in_out FROM "anwesenheit" WHERE "card_uid" = $1 ORDER BY "timestamp" DESC LIMIT 1;`)) if err != nil { log.Fatalf("Error preparing query: %v", err) return false } err = stmt.QueryRow(b.CardUID).Scan(&check_in_out) if err == sql.ErrNoRows { return true } if err != nil { log.Println("Error checking last booking: ", err) return false } if int16(check_in_out)%2 == b.CheckInOut%2 { return false } return true } func (b *Booking) UpdateTime(newTime time.Time) { hour, minute, _ := newTime.Clock() 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 { newBooking.CheckInOut = b.CheckInOut + 2 } if b.CheckInOut == 254 { 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 { return fmt.Sprintf("Booking %d: at: %s, CheckInOut: %d, TypeId: %d", b.CounterId, b.Timestamp.Format("15:04"), b.CheckInOut, b.BookingType.Id) } func GetBookingTypes() ([]BookingType, error) { var types []BookingType qStr, err := DB.Prepare("SELECT anwesenheit_id, anwesenheit_name FROM s_anwesenheit_typen;") if err != nil { return types, err } defer qStr.Close() rows, err := qStr.Query() if err != nil { log.Println("Error getting anwesenheit rows!", err) return types, err } defer rows.Close() for rows.Next() { var bookingType BookingType if err := rows.Scan(&bookingType.Id, &bookingType.Name); err != nil { log.Println("Error scanning row!", err) } types = append(types, bookingType) } return types, nil } func GetBookingTypeById(bookingTypeId int8) (BookingType, error) { var bookingType BookingType = BookingType{Id: bookingTypeId} qStr, err := DB.Prepare("SELECT anwesenheit_name FROM s_anwesenheit_typen WHERE anwesenheit_id = $1;") if err != nil { return bookingType, err } defer qStr.Close() err = qStr.QueryRow(bookingTypeId).Scan(&bookingType.Name) if err != nil { return bookingType, err } return bookingType, nil } func GetBookingTypesCached() []BookingType { types, err := definedTypes.Get("s_anwesenheit_typen") if err != nil { return []BookingType{} } return types.([]BookingType) }