Compare commits
7 Commits
23896e4f08
...
2.0.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 085287c7a5 | |||
| c29a952e1d | |||
| b12a467ef9 | |||
| 8bb1777519 | |||
| f21ce9a3c3 | |||
| b4bf550863 | |||
| 10df10a606 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -30,7 +30,8 @@ DB/pg_data
|
|||||||
|
|
||||||
.env.*
|
.env.*
|
||||||
.env
|
.env
|
||||||
!.env.example
|
|
||||||
|
Docker/config
|
||||||
|
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import (
|
|||||||
const DE_DATE string = "02.01.2006"
|
const DE_DATE string = "02.01.2006"
|
||||||
const FILE_YEAR_MONTH string = "2006_01"
|
const FILE_YEAR_MONTH string = "2006_01"
|
||||||
|
|
||||||
var PDF_DIRECTORY = helper.GetEnv("PDF_PATH", "/doc/") // TODO
|
var PDF_DIRECTORY = helper.GetEnv("PDF_PATH", "/doc/")
|
||||||
|
|
||||||
func PDFCreateController(w http.ResponseWriter, r *http.Request) {
|
func PDFCreateController(w http.ResponseWriter, r *http.Request) {
|
||||||
helper.RequiresLogin(Session, w, r)
|
helper.RequiresLogin(Session, w, r)
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Relevant for arduino inputs -> creates new Booking from get and put method
|
// 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 := (*models.Booking).FromUrlParams(nil, r.URL.Query())
|
||||||
booking.Timestamp = time.Now()
|
// booking.Timestamp = time.Now()
|
||||||
if booking.Verify() {
|
if booking.Verify() {
|
||||||
err := booking.Insert()
|
err := booking.Insert()
|
||||||
if errors.Is(models.SameBookingError{}, err) {
|
if errors.Is(models.SameBookingError{}, err) {
|
||||||
|
|||||||
@@ -257,12 +257,6 @@ func updateAbsence(r *http.Request) error {
|
|||||||
log.Println("Cannot get Absence for id: ", absenceId, err)
|
log.Println("Cannot get Absence for id: ", absenceId, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if r.FormValue("action") == "delete" {
|
|
||||||
log.Println("Deleting Absence!", "Not implemented")
|
|
||||||
// TODO
|
|
||||||
//absence.Delete()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if absence.Update(newAbsence) {
|
if absence.Update(newAbsence) {
|
||||||
err = absence.Save()
|
err = absence.Save()
|
||||||
@@ -272,5 +266,4 @@ func updateAbsence(r *http.Request) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ type FileLog struct {
|
|||||||
var Logs map[string]FileLog = make(map[string]FileLog)
|
var Logs map[string]FileLog = make(map[string]FileLog)
|
||||||
|
|
||||||
func NewAudit() (i *log.Logger, close func() error) {
|
func NewAudit() (i *log.Logger, close func() error) {
|
||||||
LOG_FILE := "logs/" + time.Now().Format(time.DateOnly) + ".log"
|
logName := "logs/" + time.Now().Format(time.DateOnly) + ".log"
|
||||||
logFile, err := os.OpenFile(LOG_FILE, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
logFile, err := os.OpenFile(logName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panic(err)
|
log.Panic(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ func main() {
|
|||||||
|
|
||||||
defer models.DB.(*sql.DB).Close()
|
defer models.DB.(*sql.DB).Close()
|
||||||
|
|
||||||
|
models.Options = configure()
|
||||||
|
|
||||||
if helper.GetEnv("GO_ENV", "production") != "debug" {
|
if helper.GetEnv("GO_ENV", "production") != "debug" {
|
||||||
err = Migrate()
|
err = Migrate()
|
||||||
if err != nil {
|
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()))
|
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",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -39,6 +39,11 @@ type Booking struct {
|
|||||||
Valid bool `json:"valid"`
|
Valid bool `json:"valid"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type BookingOptions struct {
|
||||||
|
AllowOutOfBounds bool
|
||||||
|
AllowUnknownUser bool
|
||||||
|
}
|
||||||
|
|
||||||
type IDatabase interface {
|
type IDatabase interface {
|
||||||
Prepare(query string) (*sql.Stmt, error)
|
Prepare(query string) (*sql.Stmt, error)
|
||||||
Exec(query string, args ...any) (sql.Result, error)
|
Exec(query string, args ...any) (sql.Result, error)
|
||||||
@@ -46,6 +51,8 @@ type IDatabase interface {
|
|||||||
|
|
||||||
var DB IDatabase
|
var DB IDatabase
|
||||||
|
|
||||||
|
var Options BookingOptions
|
||||||
|
|
||||||
func (b *Booking) NewBooking(cardUid string, gereatId int16, checkInOut int16, typeId int8) Booking {
|
func (b *Booking) NewBooking(cardUid string, gereatId int16, checkInOut int16, typeId int8) Booking {
|
||||||
bookingType, err := GetBookingTypeById(typeId)
|
bookingType, err := GetBookingTypeById(typeId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -92,10 +99,44 @@ func (b *Booking) Verify() bool {
|
|||||||
} else {
|
} else {
|
||||||
b.BookingType.Name = bookingType.Name
|
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
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Booking) Insert() error {
|
func (b *Booking) Insert() error {
|
||||||
|
if !b.Timestamp.IsZero() {
|
||||||
|
return b.InsertWithTimestamp()
|
||||||
|
}
|
||||||
if !checkLastBooking(*b) {
|
if !checkLastBooking(*b) {
|
||||||
return SameBookingError{}
|
return SameBookingError{}
|
||||||
}
|
}
|
||||||
@@ -224,20 +265,21 @@ func (b *Booking) Update(nb Booking) {
|
|||||||
b.GeraetID = nb.GeraetID
|
b.GeraetID = nb.GeraetID
|
||||||
}
|
}
|
||||||
if b.Timestamp != nb.Timestamp {
|
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
|
b.Timestamp = nb.Timestamp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkLastBooking(b Booking) bool {
|
func checkLastBooking(b Booking) bool {
|
||||||
var check_in_out int
|
var check_in_out int
|
||||||
slog.Info("Checking with timestamp:", "timestamp", b.Timestamp.String())
|
var timestamp time.Time
|
||||||
stmt, err := DB.Prepare((`SELECT check_in_out FROM "anwesenheit" WHERE "card_uid" = $1 AND "timestamp" <= $2 ORDER BY "timestamp" DESC LIMIT 1;`))
|
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 {
|
if err != nil {
|
||||||
log.Fatalf("Error preparing query: %v", err)
|
log.Fatalf("Error preparing query: %v", err)
|
||||||
return false
|
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)
|
slog.Info("Checking last bookings check_in_out", "Check", check_in_out)
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
return true
|
return true
|
||||||
@@ -246,9 +288,13 @@ func checkLastBooking(b Booking) bool {
|
|||||||
log.Println("Error checking last booking: ", err)
|
log.Println("Error checking last booking: ", err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if int16(check_in_out)%2 == b.CheckInOut%2 {
|
if int16(check_in_out)%2 == b.CheckInOut%2 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if timestamp.Equal(b.Timestamp) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,8 +303,6 @@ func (b *Booking) UpdateTime(newTime time.Time) {
|
|||||||
if hour == b.Timestamp.Hour() && minute == b.Timestamp.Minute() {
|
if hour == b.Timestamp.Hour() && minute == b.Timestamp.Minute() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// TODO: add check for time overlap
|
|
||||||
|
|
||||||
var newBooking Booking
|
var newBooking Booking
|
||||||
newBooking.Timestamp = time.Date(b.Timestamp.Year(), b.Timestamp.Month(), b.Timestamp.Day(), hour, minute, 0, 0, b.Timestamp.Location())
|
newBooking.Timestamp = time.Date(b.Timestamp.Year(), b.Timestamp.Month(), b.Timestamp.Day(), hour, minute, 0, 0, b.Timestamp.Location())
|
||||||
if b.CheckInOut < 3 {
|
if b.CheckInOut < 3 {
|
||||||
@@ -268,14 +312,11 @@ func (b *Booking) UpdateTime(newTime time.Time) {
|
|||||||
newBooking.CheckInOut = 4
|
newBooking.CheckInOut = 4
|
||||||
}
|
}
|
||||||
b.Update(newBooking)
|
b.Update(newBooking)
|
||||||
// TODO Check verify
|
|
||||||
if b.Verify() {
|
if b.Verify() {
|
||||||
b.Save()
|
b.Save()
|
||||||
} else {
|
} else {
|
||||||
log.Println("Cannot save updated booking!", b.ToString())
|
log.Println("Cannot save updated booking!", b.ToString())
|
||||||
}
|
}
|
||||||
// b.Verify()
|
|
||||||
// b.Save()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Booking) ToString() string {
|
func (b *Booking) ToString() string {
|
||||||
@@ -327,3 +368,12 @@ func GetBookingTypesCached() []BookingType {
|
|||||||
}
|
}
|
||||||
return types.([]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
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,35 +13,250 @@ var testBookingType = models.BookingType{
|
|||||||
var testBookings8hrs = []models.Booking{{
|
var testBookings8hrs = []models.Booking{{
|
||||||
CardUID: "aaaa-aaaa",
|
CardUID: "aaaa-aaaa",
|
||||||
CheckInOut: 1,
|
CheckInOut: 1,
|
||||||
Timestamp: CatchError(time.Parse("2006-01-02 15:04", "2025-01-01 08:00")),
|
Timestamp: time.Date(2025, 01, 01, 8, 0, 0, 0, time.UTC),
|
||||||
BookingType: testBookingType,
|
BookingType: testBookingType,
|
||||||
}, {
|
}, {
|
||||||
CardUID: "aaaa-aaaa",
|
CardUID: "aaaa-aaaa",
|
||||||
CheckInOut: 2,
|
CheckInOut: 2,
|
||||||
Timestamp: CatchError(time.Parse("2006-01-02 15:04", "2025-01-01 16:00")),
|
Timestamp: time.Date(2025, 01, 01, 16, 0, 0, 0, time.UTC),
|
||||||
BookingType: testBookingType,
|
BookingType: testBookingType,
|
||||||
}}
|
}}
|
||||||
|
|
||||||
var testBookings6hrs = []models.Booking{{
|
var testBookings6hrs = []models.Booking{{
|
||||||
CardUID: "aaaa-aaaa",
|
CardUID: "aaaa-aaaa",
|
||||||
CheckInOut: 1,
|
CheckInOut: 1,
|
||||||
Timestamp: CatchError(time.Parse("2006-01-02 15:04", "2025-01-01 08:00")),
|
Timestamp: time.Date(2025, 01, 01, 8, 0, 0, 0, time.UTC),
|
||||||
BookingType: testBookingType,
|
BookingType: testBookingType,
|
||||||
}, {
|
}, {
|
||||||
CardUID: "aaaa-aaaa",
|
CardUID: "aaaa-aaaa",
|
||||||
CheckInOut: 2,
|
CheckInOut: 2,
|
||||||
Timestamp: CatchError(time.Parse("2006-01-02 15:04", "2025-01-01 14:00")),
|
Timestamp: time.Date(2025, 01, 01, 14, 0, 0, 0, time.UTC),
|
||||||
BookingType: testBookingType,
|
BookingType: testBookingType,
|
||||||
}}
|
}}
|
||||||
|
|
||||||
var testBookings10hrs = []models.Booking{{
|
var testBookings10hrs = []models.Booking{{
|
||||||
CardUID: "aaaa-aaaa",
|
CardUID: "aaaa-aaaa",
|
||||||
CheckInOut: 1,
|
CheckInOut: 1,
|
||||||
Timestamp: CatchError(time.Parse("2006-01-02 15:04", "2025-01-01 08:00")),
|
Timestamp: time.Date(2025, 01, 01, 8, 0, 0, 0, time.UTC),
|
||||||
BookingType: testBookingType,
|
BookingType: testBookingType,
|
||||||
}, {
|
}, {
|
||||||
CardUID: "aaaa-aaaa",
|
CardUID: "aaaa-aaaa",
|
||||||
CheckInOut: 2,
|
CheckInOut: 2,
|
||||||
Timestamp: CatchError(time.Parse("2006-01-02 15:04", "2025-01-01 18:00")),
|
Timestamp: time.Date(2025, 01, 01, 18, 0, 0, 0, time.UTC),
|
||||||
BookingType: testBookingType,
|
BookingType: testBookingType,
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
var testBookings6hrsBreak30min = []models.Booking{
|
||||||
|
{
|
||||||
|
CardUID: "aaaa-aaaa",
|
||||||
|
CheckInOut: 1,
|
||||||
|
Timestamp: time.Date(2025, 01, 01, 8, 0, 0, 0, time.UTC),
|
||||||
|
BookingType: testBookingType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CardUID: "aaaa-aaaa",
|
||||||
|
CheckInOut: 2,
|
||||||
|
Timestamp: time.Date(2025, 01, 01, 9, 0, 0, 0, time.UTC),
|
||||||
|
BookingType: testBookingType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CardUID: "aaaa-aaaa",
|
||||||
|
CheckInOut: 1,
|
||||||
|
Timestamp: time.Date(2025, 01, 01, 9, 30, 0, 0, time.UTC),
|
||||||
|
BookingType: testBookingType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CardUID: "aaaa-aaaa",
|
||||||
|
CheckInOut: 2,
|
||||||
|
Timestamp: time.Date(2025, 01, 01, 14, 30, 0, 0, time.UTC),
|
||||||
|
BookingType: testBookingType,
|
||||||
|
}}
|
||||||
|
|
||||||
|
var testBookings610hrsBreak30min = []models.Booking{
|
||||||
|
{
|
||||||
|
CardUID: "aaaa-aaaa",
|
||||||
|
CheckInOut: 1,
|
||||||
|
Timestamp: time.Date(2025, 01, 01, 8, 0, 0, 0, time.UTC),
|
||||||
|
BookingType: testBookingType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CardUID: "aaaa-aaaa",
|
||||||
|
CheckInOut: 2,
|
||||||
|
Timestamp: time.Date(2025, 01, 01, 9, 0, 0, 0, time.UTC),
|
||||||
|
BookingType: testBookingType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CardUID: "aaaa-aaaa",
|
||||||
|
CheckInOut: 1,
|
||||||
|
Timestamp: time.Date(2025, 01, 01, 9, 30, 0, 0, time.UTC),
|
||||||
|
BookingType: testBookingType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CardUID: "aaaa-aaaa",
|
||||||
|
CheckInOut: 2,
|
||||||
|
Timestamp: time.Date(2025, 01, 01, 14, 40, 0, 0, time.UTC),
|
||||||
|
BookingType: testBookingType,
|
||||||
|
}}
|
||||||
|
|
||||||
|
var testBookings9hrsBreak30min = []models.Booking{
|
||||||
|
{
|
||||||
|
CardUID: "aaaa-aaaa",
|
||||||
|
CheckInOut: 1,
|
||||||
|
Timestamp: time.Date(2025, 01, 01, 8, 0, 0, 0, time.UTC),
|
||||||
|
BookingType: testBookingType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CardUID: "aaaa-aaaa",
|
||||||
|
CheckInOut: 2,
|
||||||
|
Timestamp: time.Date(2025, 01, 01, 9, 0, 0, 0, time.UTC),
|
||||||
|
BookingType: testBookingType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CardUID: "aaaa-aaaa",
|
||||||
|
CheckInOut: 1,
|
||||||
|
Timestamp: time.Date(2025, 01, 01, 9, 30, 0, 0, time.UTC),
|
||||||
|
BookingType: testBookingType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CardUID: "aaaa-aaaa",
|
||||||
|
CheckInOut: 2,
|
||||||
|
Timestamp: time.Date(2025, 01, 01, 17, 30, 0, 0, time.UTC),
|
||||||
|
BookingType: testBookingType,
|
||||||
|
}}
|
||||||
|
|
||||||
|
var testBookings930hrs = []models.Booking{
|
||||||
|
{
|
||||||
|
CardUID: "aaaa-aaaa",
|
||||||
|
CheckInOut: 1,
|
||||||
|
Timestamp: time.Date(2025, 01, 01, 8, 0, 0, 0, time.UTC),
|
||||||
|
BookingType: testBookingType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CardUID: "aaaa-aaaa",
|
||||||
|
CheckInOut: 2,
|
||||||
|
Timestamp: time.Date(2025, 01, 01, 17, 30, 0, 0, time.UTC),
|
||||||
|
BookingType: testBookingType,
|
||||||
|
}}
|
||||||
|
|
||||||
|
var testBookings910hrsBreak30min = []models.Booking{
|
||||||
|
{
|
||||||
|
CardUID: "aaaa-aaaa",
|
||||||
|
CheckInOut: 1,
|
||||||
|
Timestamp: time.Date(2025, 01, 01, 8, 0, 0, 0, time.UTC),
|
||||||
|
BookingType: testBookingType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CardUID: "aaaa-aaaa",
|
||||||
|
CheckInOut: 2,
|
||||||
|
Timestamp: time.Date(2025, 01, 01, 9, 0, 0, 0, time.UTC),
|
||||||
|
BookingType: testBookingType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CardUID: "aaaa-aaaa",
|
||||||
|
CheckInOut: 1,
|
||||||
|
Timestamp: time.Date(2025, 01, 01, 9, 30, 0, 0, time.UTC),
|
||||||
|
BookingType: testBookingType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CardUID: "aaaa-aaaa",
|
||||||
|
CheckInOut: 2,
|
||||||
|
Timestamp: time.Date(2025, 01, 01, 17, 40, 0, 0, time.UTC),
|
||||||
|
BookingType: testBookingType,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var testBookings910hrsBreak35min = []models.Booking{
|
||||||
|
{
|
||||||
|
CardUID: "aaaa-aaaa",
|
||||||
|
CheckInOut: 1,
|
||||||
|
Timestamp: time.Date(2025, 01, 01, 8, 0, 0, 0, time.UTC),
|
||||||
|
BookingType: testBookingType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CardUID: "aaaa-aaaa",
|
||||||
|
CheckInOut: 2,
|
||||||
|
Timestamp: time.Date(2025, 01, 01, 9, 0, 0, 0, time.UTC),
|
||||||
|
BookingType: testBookingType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CardUID: "aaaa-aaaa",
|
||||||
|
CheckInOut: 1,
|
||||||
|
Timestamp: time.Date(2025, 01, 01, 9, 35, 0, 0, time.UTC),
|
||||||
|
BookingType: testBookingType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CardUID: "aaaa-aaaa",
|
||||||
|
CheckInOut: 2,
|
||||||
|
Timestamp: time.Date(2025, 01, 01, 17, 45, 0, 0, time.UTC),
|
||||||
|
BookingType: testBookingType,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var testBookings945hrs = []models.Booking{
|
||||||
|
{
|
||||||
|
CardUID: "aaaa-aaaa",
|
||||||
|
CheckInOut: 1,
|
||||||
|
Timestamp: time.Date(2025, 01, 01, 8, 0, 0, 0, time.UTC),
|
||||||
|
BookingType: testBookingType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CardUID: "aaaa-aaaa",
|
||||||
|
CheckInOut: 2,
|
||||||
|
Timestamp: time.Date(2025, 01, 01, 17, 45, 0, 0, time.UTC),
|
||||||
|
BookingType: testBookingType,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var testBookings10hrsBreak45min = []models.Booking{
|
||||||
|
{
|
||||||
|
CardUID: "aaaa-aaaa",
|
||||||
|
CheckInOut: 1,
|
||||||
|
Timestamp: time.Date(2025, 01, 01, 8, 0, 0, 0, time.UTC),
|
||||||
|
BookingType: testBookingType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CardUID: "aaaa-aaaa",
|
||||||
|
CheckInOut: 2,
|
||||||
|
Timestamp: time.Date(2025, 01, 01, 9, 0, 0, 0, time.UTC),
|
||||||
|
BookingType: testBookingType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CardUID: "aaaa-aaaa",
|
||||||
|
CheckInOut: 1,
|
||||||
|
Timestamp: time.Date(2025, 01, 01, 9, 45, 0, 0, time.UTC),
|
||||||
|
BookingType: testBookingType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CardUID: "aaaa-aaaa",
|
||||||
|
CheckInOut: 2,
|
||||||
|
Timestamp: time.Date(2025, 01, 01, 18, 00, 0, 0, time.UTC),
|
||||||
|
BookingType: testBookingType,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var testBookings1030hrsBreak45min = []models.Booking{
|
||||||
|
{
|
||||||
|
CardUID: "aaaa-aaaa",
|
||||||
|
CheckInOut: 1,
|
||||||
|
Timestamp: time.Date(2025, 01, 01, 8, 0, 0, 0, time.UTC),
|
||||||
|
BookingType: testBookingType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CardUID: "aaaa-aaaa",
|
||||||
|
CheckInOut: 2,
|
||||||
|
Timestamp: time.Date(2025, 01, 01, 9, 0, 0, 0, time.UTC),
|
||||||
|
BookingType: testBookingType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CardUID: "aaaa-aaaa",
|
||||||
|
CheckInOut: 1,
|
||||||
|
Timestamp: time.Date(2025, 01, 01, 9, 45, 0, 0, time.UTC),
|
||||||
|
BookingType: testBookingType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CardUID: "aaaa-aaaa",
|
||||||
|
CheckInOut: 2,
|
||||||
|
Timestamp: time.Date(2025, 01, 01, 18, 30, 0, 0, time.UTC),
|
||||||
|
BookingType: testBookingType,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|||||||
@@ -21,9 +21,7 @@ type CompoundDay struct {
|
|||||||
func (c *CompoundDay) IsSubmittedAndAccepted() bool {
|
func (c *CompoundDay) IsSubmittedAndAccepted() bool {
|
||||||
var isSubmittedAndAccepted = true
|
var isSubmittedAndAccepted = true
|
||||||
for _, day := range c.DayParts {
|
for _, day := range c.DayParts {
|
||||||
_isSubmittedAndAccepted := day.IsSubmittedAndAccepted()
|
isSubmittedAndAccepted = isSubmittedAndAccepted && day.IsSubmittedAndAccepted()
|
||||||
isSubmittedAndAccepted = isSubmittedAndAccepted && _isSubmittedAndAccepted
|
|
||||||
slog.Info("Result from IsSubmittedCheck", "Result", _isSubmittedAndAccepted, "compount", day.ToString())
|
|
||||||
}
|
}
|
||||||
return isSubmittedAndAccepted
|
return isSubmittedAndAccepted
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ type User struct {
|
|||||||
ArbeitszeitPerTag float32 //`json:"arbeitszeit_per_tag"`
|
ArbeitszeitPerTag float32 //`json:"arbeitszeit_per_tag"`
|
||||||
ArbeitszeitPerWoche float32 //`json:"arbeitszeit_per_woche"`
|
ArbeitszeitPerWoche float32 //`json:"arbeitszeit_per_woche"`
|
||||||
Overtime time.Duration
|
Overtime time.Duration
|
||||||
|
ArbeitMinStart time.Time
|
||||||
|
ArbeitMaxEnde time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetUserFromSession(Session *scs.SessionManager, ctx context.Context) (User, error) {
|
func GetUserFromSession(Session *scs.SessionManager, ctx context.Context) (User, error) {
|
||||||
@@ -65,8 +67,41 @@ func (u *User) GetReportedOvertime(startDate time.Time) (time.Duration, error) {
|
|||||||
return overtime, nil
|
return overtime, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetUserByCardUID(cardUid string) (User, error) {
|
||||||
|
var user 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
|
||||||
|
}
|
||||||
|
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 user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
func GetAllUsers() ([]User, error) {
|
||||||
qStr, err := DB.Prepare((`SELECT card_uid, vorname, nachname,arbeitszeit_per_tag, arbeitszeit_per_woche FROM s_personal_daten;`))
|
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
|
var users []User
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return users, err
|
return users, err
|
||||||
@@ -80,34 +115,7 @@ func GetAllUsers() ([]User, error) {
|
|||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
|
|
||||||
var user User
|
var user User
|
||||||
if err := rows.Scan(&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 {
|
||||||
log.Println("Error creating user!", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
users = append(users, user)
|
|
||||||
}
|
|
||||||
if err = rows.Err(); err != nil {
|
|
||||||
return users, nil
|
|
||||||
}
|
|
||||||
return users, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *User) GetAll() ([]User, error) {
|
|
||||||
qStr, err := DB.Prepare((`SELECT card_uid, vorname, nachname 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() {
|
|
||||||
|
|
||||||
var user User
|
|
||||||
if err := rows.Scan(&user.CardUID, &user.Vorname, &user.Name); err != nil {
|
|
||||||
log.Println("Error creating user!", err)
|
log.Println("Error creating user!", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -167,11 +175,11 @@ func (u *User) CheckOut() error {
|
|||||||
func GetUserByPersonalNr(personalNummer int) (User, error) {
|
func GetUserByPersonalNr(personalNummer int) (User, error) {
|
||||||
var user User
|
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 {
|
if err != nil {
|
||||||
return user, err
|
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 {
|
if err != nil {
|
||||||
return user, err
|
return user, err
|
||||||
@@ -185,7 +193,7 @@ func GetUserByPersonalNrMulti(personalNummerMulti []int) ([]User, error) {
|
|||||||
return users, errors.New("No personalNumbers provided")
|
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 {
|
if err != nil {
|
||||||
return users, err
|
return users, err
|
||||||
}
|
}
|
||||||
@@ -200,7 +208,7 @@ func GetUserByPersonalNrMulti(personalNummerMulti []int) ([]User, error) {
|
|||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var user User
|
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
|
return users, err
|
||||||
}
|
}
|
||||||
users = append(users, user)
|
users = append(users, user)
|
||||||
@@ -246,6 +254,7 @@ func (u *User) ChangePass(password, newPassword string) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) GetTeamMembers() ([]User, error) {
|
func (u *User) GetTeamMembers() ([]User, error) {
|
||||||
|
var teamMemberPNrs []int
|
||||||
var teamMembers []User
|
var teamMembers []User
|
||||||
qStr, err := DB.Prepare(`SELECT personal_nummer FROM s_personal_daten WHERE vorgesetzter_pers_nr = $1 ORDER BY "nachname";`)
|
qStr, err := DB.Prepare(`SELECT personal_nummer FROM s_personal_daten WHERE vorgesetzter_pers_nr = $1 ORDER BY "nachname";`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -261,12 +270,16 @@ func (u *User) GetTeamMembers() ([]User, error) {
|
|||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var personalNr int
|
var personalNr int
|
||||||
err := rows.Scan(&personalNr)
|
err := rows.Scan(&personalNr)
|
||||||
user, err := GetUserByPersonalNr(personalNr)
|
teamMemberPNrs = append(teamMemberPNrs, personalNr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Error getting user!")
|
log.Println("Error getting user!")
|
||||||
return teamMembers, err
|
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
|
return teamMembers, nil
|
||||||
@@ -343,22 +356,6 @@ LIMIT 1;
|
|||||||
return lastSub
|
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 {
|
func (u *User) IsSuperior(e User) bool {
|
||||||
var isSuperior int
|
var isSuperior int
|
||||||
qStr, err := DB.Prepare(`SELECT COUNT(1) FROM s_personal_daten WHERE personal_nummer = $1 AND vorgesetzter_pers_nr = $2`)
|
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 false
|
||||||
}
|
}
|
||||||
return isSuperior == 1
|
return isSuperior == 1
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getMonday(ts time.Time) time.Time {
|
func getMonday(ts time.Time) time.Time {
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ func (d *WorkDay) GetTimes(u User, base WorktimeBase, includeKurzarbeit bool) (w
|
|||||||
|
|
||||||
func getWorkPause(d *WorkDay) (work, pause time.Duration) {
|
func getWorkPause(d *WorkDay) (work, pause time.Duration) {
|
||||||
//if today calc, else take from db
|
//if today calc, else take from db
|
||||||
if helper.IsSameDate(d.Date(), time.Now()) {
|
if d.workTime == 0 && d.pauseTime == 0 && len(d.Bookings) > 0 {
|
||||||
return calcWorkPause(d.Bookings)
|
return calcWorkPause(d.Bookings)
|
||||||
} else {
|
} else {
|
||||||
return d.workTime, d.pauseTime
|
return d.workTime, d.pauseTime
|
||||||
@@ -116,7 +116,8 @@ func correctWorkPause(workIn, pauseIn time.Duration) (work, pause time.Duration)
|
|||||||
}
|
}
|
||||||
|
|
||||||
var diff time.Duration
|
var diff time.Duration
|
||||||
if workIn <= (9*time.Hour) && pauseIn < 30*time.Minute {
|
|
||||||
|
if (workIn+pauseIn) <= (9*time.Hour+30*time.Minute) && pauseIn <= 30*time.Minute {
|
||||||
diff = 30*time.Minute - pauseIn
|
diff = 30*time.Minute - pauseIn
|
||||||
} else if pauseIn < 45*time.Minute {
|
} else if pauseIn < 45*time.Minute {
|
||||||
diff = 45*time.Minute - pauseIn
|
diff = 45*time.Minute - pauseIn
|
||||||
@@ -423,10 +424,12 @@ GROUP BY
|
|||||||
|
|
||||||
// returns bool wheter the workday was ended with an automatic logout
|
// returns bool wheter the workday was ended with an automatic logout
|
||||||
func (d *WorkDay) RequiresAction() bool {
|
func (d *WorkDay) RequiresAction() bool {
|
||||||
if len(d.Bookings) == 0 {
|
for i := range d.Bookings {
|
||||||
return false
|
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 {
|
func (d *WorkDay) GetDayProgress(u User) int8 {
|
||||||
|
|||||||
@@ -16,10 +16,10 @@ func CatchError[T any](val T, err error) T {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var testWorkDay = models.WorkDay{
|
var testWorkDay = models.WorkDay{
|
||||||
Day: CatchError(time.Parse(time.DateOnly, "2025-01-01")),
|
Day: time.Date(2025, 01, 01, 0, 0, 0, 0, time.Local),
|
||||||
Bookings: testBookings8hrs,
|
Bookings: testBookings8hrs,
|
||||||
TimeFrom: CatchError(time.Parse("2006-01-02 15:04", "2025-01-01 08:00")),
|
TimeFrom: time.Date(2025, 01, 01, 8, 0, 0, 0, time.Local),
|
||||||
TimeTo: CatchError(time.Parse("2006-01-02 15:04", "2025-01-01 16:30")),
|
TimeTo: time.Date(2025, 01, 01, 16, 30, 0, 0, time.Local),
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWorkdayWorktimeDay(t *testing.T) {
|
func TestWorkdayWorktimeDay(t *testing.T) {
|
||||||
@@ -30,18 +30,63 @@ func TestWorkdayWorktimeDay(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
testName: "Bookings6hrs",
|
testName: "Bookings6hrs",
|
||||||
bookings: testBookings6hrs,
|
bookings: testBookings6hrs, //work 6h
|
||||||
expectedTime: time.Hour * 6,
|
expectedTime: time.Hour * 6, //pause 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
testName: "Bookings8hrs",
|
testName: "Bookings8hrs",
|
||||||
bookings: testBookings8hrs,
|
bookings: testBookings8hrs, //work 8 pause 0
|
||||||
expectedTime: time.Hour*7 + time.Minute*30,
|
expectedTime: time.Hour*7 + time.Minute*30, //pause 30 --> corrected
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
testName: "Bookings10hrs",
|
testName: "Bookings10hrs",
|
||||||
bookings: testBookings10hrs,
|
bookings: testBookings10hrs, //work 10 pause 0
|
||||||
expectedTime: time.Hour*9 + time.Minute*15,
|
expectedTime: time.Hour*9 + time.Minute*15, //pause 45 --> corrected
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "Booking 6h with 30 min Break",
|
||||||
|
bookings: testBookings6hrsBreak30min, //work 6 pause 30
|
||||||
|
expectedTime: time.Hour * 6, //pause 30 --> bc real pause
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "Booking 6h 10min with 30 min Break",
|
||||||
|
bookings: testBookings610hrsBreak30min, //work 6 10 pause 30
|
||||||
|
expectedTime: time.Hour*6 + time.Minute*10, //pause 30 --> real pause
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "Booking 9h with 30 min Break",
|
||||||
|
bookings: testBookings9hrsBreak30min, //work 9 pause 30
|
||||||
|
expectedTime: time.Hour * 9, //pause 30 --> real pause
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "Booking 9h 30min",
|
||||||
|
bookings: testBookings930hrs, //work 9 30 pause 0
|
||||||
|
expectedTime: time.Hour * 9, //pause 30 --> corrected
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "Booking 9h 40min with 30min Break",
|
||||||
|
bookings: testBookings910hrsBreak30min, //work 9 10 pause 30
|
||||||
|
expectedTime: time.Hour*8 + time.Minute*55, //pause 45 --> real + corrected
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "Booking 9h 40min with 35min Break",
|
||||||
|
bookings: testBookings910hrsBreak35min, //work 9 10 pause 35
|
||||||
|
expectedTime: time.Hour * 9, //pause 45 --> real + corrected
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "Booking 9h 45min",
|
||||||
|
bookings: testBookings945hrs, //work 9 45 pause 0
|
||||||
|
expectedTime: time.Hour * 9, //pause 45 --> corrected
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "Booking 10h Break 45min",
|
||||||
|
bookings: testBookings10hrsBreak45min, //work 9 15 pause 45
|
||||||
|
expectedTime: time.Hour*9 + time.Minute*15, //pause 45 --> real
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "Booking 10h 30min Break 45min",
|
||||||
|
bookings: testBookings1030hrsBreak45min, //work 9 45 pause 45
|
||||||
|
expectedTime: time.Hour*9 + time.Minute*45, //pause 45 --> real
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,6 +158,51 @@ func TestWorkdayPausetimeDay(t *testing.T) {
|
|||||||
bookings: testBookings10hrs,
|
bookings: testBookings10hrs,
|
||||||
expectedTime: time.Minute * 45,
|
expectedTime: time.Minute * 45,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
testName: "Booking 6h with 30 min Break",
|
||||||
|
bookings: testBookings6hrsBreak30min, //work 6 pause 30
|
||||||
|
expectedTime: time.Minute * 30, //pause 30 --> bc real pause
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "Booking 6h 10min with 30 min Break",
|
||||||
|
bookings: testBookings610hrsBreak30min, //work 6 10 pause 30
|
||||||
|
expectedTime: time.Minute * 30, //pause 30 --> real pause
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "Booking 9h with 30 min Break",
|
||||||
|
bookings: testBookings9hrsBreak30min, //work 9 pause 30
|
||||||
|
expectedTime: time.Minute * 30, //pause 30 --> real pause
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "Booking 9h 30min",
|
||||||
|
bookings: testBookings930hrs, //work 9 30 pause 0
|
||||||
|
expectedTime: time.Minute * 30, //pause 30 --> corrected
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "Booking 9h 40min with 30min Break",
|
||||||
|
bookings: testBookings910hrsBreak30min, //work 9 10 pause 30
|
||||||
|
expectedTime: time.Minute * 45, //pause 45 --> real + corrected
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "Booking 9h 40min with 35min Break",
|
||||||
|
bookings: testBookings910hrsBreak35min, //work 9 10 pause 35
|
||||||
|
expectedTime: time.Minute * 45, //pause 45 --> real + corrected
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "Booking 9h 45min",
|
||||||
|
bookings: testBookings945hrs, //work 9 45 pause 0
|
||||||
|
expectedTime: time.Minute * 45, //pause 45 --> corrected
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "Booking 10h Break 45min",
|
||||||
|
bookings: testBookings10hrsBreak45min, //work 9 15 pause 45
|
||||||
|
expectedTime: time.Minute * 45, //pause 45 --> real
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "Booking 10h 30min Break 45min",
|
||||||
|
bookings: testBookings1030hrsBreak45min, //work 9 45 pause 45
|
||||||
|
expectedTime: time.Minute * 45, //pause 45 --> real
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
|||||||
@@ -61,6 +61,8 @@ templ SettingsPage(status int) {
|
|||||||
<div class="grid-cell col-span-3">
|
<div class="grid-cell col-span-3">
|
||||||
<p>Nutzername: <span class="text-neutral-500">{ user.Vorname } { user.Name }</span></p>
|
<p>Nutzername: <span class="text-neutral-500">{ user.Vorname } { user.Name }</span></p>
|
||||||
<p>Personalnummer: <span class="text-neutral-500">{ user.PersonalNummer }</span></p>
|
<p>Personalnummer: <span class="text-neutral-500">{ user.PersonalNummer }</span></p>
|
||||||
|
<p>Frühester Arbeitsbegin: <span class="text-neutral-500">{ user.ArbeitMinStart.Format("15:06") } Uhr</span></p>
|
||||||
|
<p>Spätester Arbeitsende: <span class="text-neutral-500">{ user.ArbeitMaxEnde.Format("15:06") } Uhr</span></p>
|
||||||
<p>Arbeitszeit pro Tag: <span class="text-neutral-500">{ helper.FormatDuration(user.ArbeitszeitProTag()) }</span></p>
|
<p>Arbeitszeit pro Tag: <span class="text-neutral-500">{ helper.FormatDuration(user.ArbeitszeitProTag()) }</span></p>
|
||||||
<p>Arbeitszeit pro Woche: <span class="text-neutral-500">{ helper.FormatDuration(user.ArbeitszeitProWoche()) }</span></p>
|
<p>Arbeitszeit pro Woche: <span class="text-neutral-500">{ helper.FormatDuration(user.ArbeitszeitProWoche()) }</span></p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# cron-timing: 05 01 * * 1
|
# cron-timing: 05 01 * * 1
|
||||||
container_name="arbeitszeitmessung-main-db-1"
|
container_name="arbeitszeitmessung-main-db-1"
|
||||||
filename=backup-$(date '+%d%m%Y').sql
|
filename=backup-$(date '+%Y%m%d').sql
|
||||||
backup_folder=__BACKUP_FOLDER__
|
backup_folder=__BACKUP_FOLDER__
|
||||||
database_name=__DATABASE__
|
database_name=__DATABASE__
|
||||||
docker exec $container_name pg_dump $database_name > $backup_folder/$filename
|
docker exec $container_name pg_dump $database_name > $backup_folder/$filename
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
# cron-timing: 01 00 01 01 *
|
||||||
# Calls endpoint to write all public Holidays for the current year inside a database.
|
# Calls endpoint to write all public Holidays for the current year inside a database.
|
||||||
port=__PORT__
|
port=__PORT__
|
||||||
curl localhost:$port/auto/feiertage
|
curl localhost:$port/auto/feiertage
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
POSTGRES_USER=root # Postgres ADMIN Nutzername
|
POSTGRES_USER=root # Postgres ADMIN Nutzername. regex:^\w+$
|
||||||
POSTGRES_PASSWORD=very_secure # Postgres ADMIN Passwort
|
POSTGRES_PASSWORD=very_secure # Postgres ADMIN Passwort
|
||||||
POSTGRES_API_USER=api_nutzer # Postgres API Nutzername (für Arbeitszeitmessung)
|
POSTGRES_API_USER=api_nutzer # Postgres API Nutzername (für Arbeitszeitmessung). regex:^\w+$
|
||||||
POSTGRES_API_PASS=password # Postgres API Passwort (für Arbeitszeitmessung)
|
POSTGRES_API_PASS=password # Postgres API Passwort (für Arbeitszeitmessung)
|
||||||
POSTGRES_PATH=__ROOT__/DB # Datebank Pfad (relativ zu Docker Ordner oder absoluter pfad mit /...)
|
POSTGRES_PATH=__ROOT__/DB # Datebank Pfad (relativ zu Docker Ordner oder absoluter pfad mit /...)
|
||||||
POSTGRES_DB=arbeitszeitmessung # Postgres Datenbank Name
|
POSTGRES_DB=arbeitszeitmessung # Postgres Datenbank Name. regex:^[a-z]+$
|
||||||
POSTGRES_PORT=5432 # Postgres Port normalerweise nicht freigegeben. regex:^[0-9]{1,5}$
|
POSTGRES_PORT=5432 # Postgres Port normalerweise nicht freigegeben. regex:^[0-9]{1,5}$
|
||||||
TZ=Europe/Berlin # Zeitzone
|
TZ=Europe/Berlin # Zeitzone
|
||||||
API_TOKEN=dont_access # API Token für ESP Endpoints
|
API_TOKEN=dont_access # API Token für ESP32 Endpoints
|
||||||
WEB_PORT=8000 # Port unter welchem Webserver erreichbar ist. regex:^[0-9]{1,5}$
|
WEB_PORT=8000 # Port unter welchem Webserver erreichbar ist. regex:^[0-9]{1,5}$
|
||||||
LOG_PATH=__ROOT__/logs # Pfad für Audit Logs
|
LOG_PATH=__ROOT__/logs # Pfad für Audit Logs
|
||||||
LOG_LEVEL=warn # Welche Log-Nachrichten werden in der Konsole erscheinen
|
LOG_LEVEL=warn # Welche Log-Nachrichten werden in der Konsole erscheinen. regex:^(debug|info|warn|error)$
|
||||||
BACKUP_FOLDER=__ROOT__/backup # Pfad für DB Backup Datein
|
BACKUP_FOLDER=__ROOT__/backup # Pfad für DB Backup Datein
|
||||||
|
|
||||||
|
BOOKING_OUT_OF_BOUNDS=true # Buchungen außerhalb der festgelegten Arbeitszeit erlauben und auf Arbeitszeit anpassen. regex:^(true|false)$
|
||||||
|
BOOKING_FOR_UNKNOWN_USER=true # Buchungen mit unbekannter CardUID erlauben. regex:^(true|false)$
|
||||||
|
|||||||
91
Readme.md
91
Readme.md
@@ -50,9 +50,100 @@ cd arbeitszeitmessung
|
|||||||
- `BACKUP_FOLDER` Pfad für DB Backup Datein
|
- `BACKUP_FOLDER` Pfad für DB Backup Datein
|
||||||
- `LOG_PATH` Pfad für Audit Logs
|
- `LOG_PATH` Pfad für Audit Logs
|
||||||
|
|
||||||
|
## Administration:
|
||||||
|
|
||||||
|
### Nutzer erstellen:
|
||||||
|
|
||||||
|
Nutzerdaten erstellen:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
INSERT INTO "s_personal_daten"
|
||||||
|
(
|
||||||
|
"personal_nummer",
|
||||||
|
"vorname",
|
||||||
|
"nachname",
|
||||||
|
"card_uid",
|
||||||
|
"geburtsdatum",
|
||||||
|
"geschlecht",
|
||||||
|
"adresse",
|
||||||
|
"plz",
|
||||||
|
"hauptbeschaeftigungs_ort",
|
||||||
|
"aktiv_beschaeftigt",
|
||||||
|
"vorgesetzter_pers_nr",
|
||||||
|
"arbeitszeit_min_start",
|
||||||
|
"arbeitszeit_max_ende",
|
||||||
|
"arbeitszeit_per_tag",
|
||||||
|
"arbeitszeit_per_woche",
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
1,
|
||||||
|
'Max',
|
||||||
|
'Mustermann',
|
||||||
|
'acde-edca',
|
||||||
|
'2003-02-01',
|
||||||
|
1,
|
||||||
|
'Musterstr. 42',
|
||||||
|
'00001',
|
||||||
|
1,
|
||||||
|
true,
|
||||||
|
123,
|
||||||
|
'07:00:00',
|
||||||
|
'20:00:00',
|
||||||
|
8,
|
||||||
|
40
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
Nutzerpasswort generieren (kann auch später als Passwort reset genutzt werden):
|
||||||
|
|
||||||
|
```sql
|
||||||
|
INSERT INTO "user_password"
|
||||||
|
("personal_nummer", "pass_hash")
|
||||||
|
VALUES (123, crypt('password', gen_salt('bf')));
|
||||||
|
```
|
||||||
|
|
||||||
|
### Buchungstypen erstellen:
|
||||||
|
|
||||||
|
Ohne definierte Anwesenheits und Abwesenheitstypen funktioniert die Anwendung nicht!
|
||||||
|
|
||||||
|
Anwesenheiten:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
INSERT INTO "s_anwesenheit_typen"
|
||||||
|
("anwesenheit_id", "anwesenheit_name")
|
||||||
|
VALUES (1, 'Büro');
|
||||||
|
```
|
||||||
|
|
||||||
|
Abwesenheiten:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
INSERT INTO "s_abwesenheit_typen"
|
||||||
|
("abwesenheit_id", "abwesenheit_name", "arbeitszeit_equivalent")
|
||||||
|
VALUES (1, 'Urlaub', 100);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Feiertage erstellen:
|
||||||
|
|
||||||
|
Die gesetzlichen Feiertage für Deutschland/Sachsen werden automatisch mit der Route `auto/feiertage` für das aktuelle Kalenderjahr erzeugt. Um weitere Unternehmensspezifische Feiertage (z.B. 24.12. oder 31.12.) mit in die Liste der Feiertage aufzunehmen, müssen diese manuell erstellt werden.
|
||||||
|
|
||||||
|
```sql
|
||||||
|
INSERT INTO "s_feiertage"
|
||||||
|
("datum", "name", "arbeitszeit_equivalent", "wiederholen")
|
||||||
|
VALUES ('2026-12-24', 'Helligabend', 50, 1);
|
||||||
|
```
|
||||||
|
|
||||||
|
Wenn `wiederholen` == 1 wird der Feiertag automatisch beim Aufruf von `auto/feiertage` mit ins nächste Jahr (am selben Datum) übernommen.
|
||||||
|
|
||||||
|
Das Feld `arbeitszeit_equivalent` `arbeitszeit_equivalent` ist die prozentuelle Zeit am Tag welche durch diesen Eintrag eingenommen wird. (dies gilt auch für die [Buchungstypen](#buchungstypen-erstellen))
|
||||||
|
|
||||||
|
Alle weiteren Tabellen sollte ausschließlich über die Weboberfläche oder per API befüllt werden.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
# Filestrukture
|
# Filestrukture
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
├── Backend (Webserver)
|
├── Backend (Webserver)
|
||||||
│ ├── doc (Templates for Document Creator --> typst used to create PDF Reports)
|
│ ├── doc (Templates for Document Creator --> typst used to create PDF Reports)
|
||||||
│ │ ├── static
|
│ │ ├── static
|
||||||
|
|||||||
178
install.sh
178
install.sh
@@ -1,13 +1,19 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
#©Tom Tröger 2026
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
envFile=Docker/.env
|
envFile=Docker/.env
|
||||||
|
envBkp=Docker/.env.old
|
||||||
envExample=Docker/env.example
|
envExample=Docker/env.example
|
||||||
|
|
||||||
autoBackupScript=Cron/autoBackup.sh
|
cronFilePath=Cron
|
||||||
autoHolidaysScript=Cron/autoHolidays.sh
|
customCronFilePath=Docker/config/cron
|
||||||
autoLogoutScript=Cron/autoLogout.sh
|
|
||||||
|
|
||||||
|
autoBackupScript=autoBackup.sh
|
||||||
|
autoHolidaysScript=autoHolidays.sh
|
||||||
|
autoLogoutScript=autoLogout.sh
|
||||||
|
|
||||||
|
function checkDocker() {
|
||||||
echo "Checking Docker installation..."
|
echo "Checking Docker installation..."
|
||||||
if ! command -v docker >/dev/null 2>&1; then
|
if ! command -v docker >/dev/null 2>&1; then
|
||||||
echo "Docker not found. Install Docker? [y/N]"
|
echo "Docker not found. Install Docker? [y/N]"
|
||||||
@@ -29,31 +35,57 @@ if ! docker compose version >/dev/null 2>&1; then
|
|||||||
echo "Docker Compose plugin missing. You may need to update Docker."
|
echo "Docker Compose plugin missing. You may need to update Docker."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
}
|
||||||
###########################################################################
|
###########################################################################
|
||||||
|
|
||||||
|
function setupConfig() {
|
||||||
|
local reconfig=false
|
||||||
|
if [ $# -gt 0 ]; then
|
||||||
|
if ask_reconfig $1 "Reconfigure .env File?"
|
||||||
|
then
|
||||||
|
reconfig=true
|
||||||
|
else
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo -e "\r\n==================================================\r\n"
|
||||||
echo "Preparing .env file..."
|
echo "Preparing .env file..."
|
||||||
if [ ! -f $envFile ]; then
|
if [ ! -f $envFile ] || [ $reconfig == true ]; then
|
||||||
if [ -f $envExample ]; then
|
if [ -f $envExample ]; then
|
||||||
|
if [ $reconfig == true ]; then
|
||||||
|
echo "Reconfiguring env file. Backup stored at $envBkp"
|
||||||
|
echo "All previous values will be used as defaults!"
|
||||||
|
cp $envFile $envBkp
|
||||||
|
else
|
||||||
echo ".env not found. Creating interactively from .env.example."
|
echo ".env not found. Creating interactively from .env.example."
|
||||||
|
fi
|
||||||
> $envFile
|
> $envFile
|
||||||
|
|
||||||
|
|
||||||
while IFS= read -r line; do
|
while IFS= read -r line; do
|
||||||
|
|
||||||
#ignore empty lines and comments
|
#ignore empty lines and comments
|
||||||
[[ "$line" =~ ^#.*$ || -z "$line" ]] && continue
|
[[ "$line" =~ ^#.*$ || -z "$line" ]] && continue
|
||||||
|
|
||||||
|
|
||||||
key=$(printf "%s" "$line" | cut -d '=' -f 1)
|
local key=$(printf "%s" "$line" | cut -d '=' -f 1)
|
||||||
rest=$(printf "%s" "$line" | cut -d '=' -f 2-)
|
local rest=$(printf "%s" "$line" | cut -d '=' -f 2-)
|
||||||
|
|
||||||
# extract inline comment portion
|
# extract inline comment portion
|
||||||
comment=$(printf "%s" "$rest" | sed -n 's/.*# \(.*\)$/\1/p')
|
local comment=$(printf "%s" "$rest" | sed -n 's/.*# \(.*\)$/\1/p')
|
||||||
raw_val=$(printf "%s" "$rest" | sed 's/ *#.*//')
|
local raw_val=$(printf "%s" "$rest" | sed 's/ *#.*//')
|
||||||
default_value=$(printf "%s" "$raw_val" | sed 's/"//g')
|
|
||||||
|
local default_value=$(printf "%s" "$raw_val" | sed 's/"//g')
|
||||||
|
|
||||||
|
if [ $reconfig == true ]; then
|
||||||
|
local previous_value=$(grep -E "^$key=" $envBkp | cut -d= -f2)
|
||||||
|
if [ -n "$previous_value" ]; then
|
||||||
|
default_value=$previous_value
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# Replace __ROOT__ with script pwd
|
# Replace __ROOT__ with script pwd
|
||||||
default_value="${default_value/__ROOT__/$(pwd)}"
|
local default_value="${default_value/__ROOT__/$(pwd)}"
|
||||||
|
|
||||||
regex=""
|
regex=""
|
||||||
if [[ "$comment" =~ regex:(.*)$ ]]; then
|
if [[ "$comment" =~ regex:(.*)$ ]]; then
|
||||||
@@ -106,23 +138,71 @@ if [ ! -f $envFile ]; then
|
|||||||
else
|
else
|
||||||
echo "Using existing .env. (found at $envFile)"
|
echo "Using existing .env. (found at $envFile)"
|
||||||
fi
|
fi
|
||||||
|
}
|
||||||
###########################################################################
|
###########################################################################
|
||||||
|
|
||||||
|
function setupFolders(){
|
||||||
|
if [ $# -gt 0 ]; then
|
||||||
|
if ! ask_reconfig $1 "Recreate Folders?"
|
||||||
|
then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
LOG_PATH=$(grep -E '^LOG_PATH=' $envFile | cut -d= -f2)
|
LOG_PATH=$(grep -E '^LOG_PATH=' $envFile | cut -d= -f2)
|
||||||
if [ -z "$LOG_PATH" ]; then
|
if [ -z "$LOG_PATH" ]; then
|
||||||
echo "LOG_PATH not found in .env using default $(pwd)/logs"
|
echo "LOG_PATH not found in .env using default $(pwd)/logs"
|
||||||
LOG_PATH=$(pwd)/logs
|
LOG_PATH=$(pwd)/logs
|
||||||
fi
|
fi
|
||||||
|
if [ ! -d "$LOG_PATH" ]; then
|
||||||
mkdir -p $LOG_PATH
|
mkdir -p $LOG_PATH
|
||||||
echo "Created logs folder at $LOG_PATH"
|
echo "Created logs folder at $LOG_PATH"
|
||||||
|
fi
|
||||||
|
|
||||||
|
POSTGRES_PATH=$(grep -E '^POSTGRES_PATH=' $envFile | cut -d= -f2)
|
||||||
|
if [ -z "$POSTGRES_PATH" ]; then
|
||||||
|
echo "POSTGRES_PATH not found in .env using default $(pwd)/DB"
|
||||||
|
POSTGRES_PATH=$(pwd)/DB
|
||||||
|
fi
|
||||||
|
if [ ! -d "$POSTGRES_PATH" ]; then
|
||||||
|
mkdir -p $POSTGRES_PATH
|
||||||
|
echo "Created DB folder at $POSTGRES_PATH"
|
||||||
|
fi
|
||||||
|
|
||||||
|
BACKUP_FOLDER=$(grep -E '^BACKUP_FOLDER=' $envFile | cut -d= -f2)
|
||||||
|
if [ -z "$BACKUP_FOLDER" ]; then
|
||||||
|
echo "BACKUP_FOLDER not found in .env using default $(pwd)/backup"
|
||||||
|
BACKUP_FOLDER=$(pwd)/backup
|
||||||
|
fi
|
||||||
|
if [ ! -d "$BACKUP_FOLDER" ]; then
|
||||||
|
mkdir -p $BACKUP_FOLDER
|
||||||
|
echo "Created backup folder at $BACKUP_FOLDER"
|
||||||
|
fi
|
||||||
|
}
|
||||||
###########################################################################
|
###########################################################################
|
||||||
|
|
||||||
echo -e "\n\n"
|
function setupCron(){
|
||||||
|
echo -e "\r\n==================================================\r\n"
|
||||||
echo "Setup Crontab for automatic logout, backup and holiday creation? [y/N]"
|
echo "Setup Crontab for automatic logout, backup and holiday creation? [y/N]"
|
||||||
read -r setup_cron
|
read -r setup_cron
|
||||||
if [[ "$setup_cron" =~ ^[Yy]$ ]]; then
|
if [[ "$setup_cron" =~ ^[Yy]$ ]]; then
|
||||||
|
echo "Copying custom cron files to $customCronFilePath"
|
||||||
|
mkdir -p "$customCronFilePath"
|
||||||
|
|
||||||
|
if [ ! -s "$customCronFilePath/$autoBackupScript" ];then
|
||||||
|
cp "$cronFilePath/$autoBackupScript" "$customCronFilePath/$autoBackupScript"
|
||||||
|
echo "Copied $autoBackupScript"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -s "$customCronFilePath/$autoLogoutScript" ];then
|
||||||
|
cp "$cronFilePath/$autoLogoutScript" "$customCronFilePath/$autoLogoutScript"
|
||||||
|
echo "Copied $autoLogoutScript"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -s "$customCronFilePath/$autoHolidaysScript" ];then
|
||||||
|
cp "$cronFilePath/$autoHolidaysScript" "$customCronFilePath/$autoHolidaysScript"
|
||||||
|
echo "Copied $autoHolidaysScript"
|
||||||
|
fi
|
||||||
|
|
||||||
WEB_PORT=$(grep -E '^WEB_PORT=' $envFile | cut -d= -f2)
|
WEB_PORT=$(grep -E '^WEB_PORT=' $envFile | cut -d= -f2)
|
||||||
if [ -z "$WEB_PORT" ]; then
|
if [ -z "$WEB_PORT" ]; then
|
||||||
echo "WEB_PORT not found in .env using default 8000"
|
echo "WEB_PORT not found in .env using default 8000"
|
||||||
@@ -141,19 +221,20 @@ if [[ "$setup_cron" =~ ^[Yy]$ ]]; then
|
|||||||
BACKUP_FOLDER="$(pwd)/backup"
|
BACKUP_FOLDER="$(pwd)/backup"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
sed -i "s/__PORT__/$WEB_PORT/" $autoHolidaysScript
|
sed -i "s|__PORT__|$WEB_PORT|" $customCronFilePath/$autoHolidaysScript && \
|
||||||
sed -i "s/__PORT__/$WEB_PORT/" $autoLogoutScript
|
sed -i "s|__PORT__|$WEB_PORT|" $customCronFilePath/$autoLogoutScript && \
|
||||||
sed -i "s/__DATABASE__/$POSTGRES_DB/" $autoBackupScript
|
sed -i "s|__DATABASE__|$POSTGRES_DB|" $customCronFilePath/$autoBackupScript && \
|
||||||
sed -i "s/__BACKUP_FOLDER__/$BACKUP_FOLDER" $autoBackupScript
|
sed -i "s|__BACKUP_FOLDER__|$BACKUP_FOLDER|" $customCronFilePath/$autoBackupScript
|
||||||
|
|
||||||
chmod +x $autoBackupScript $autoHolidaysScript $autoLogoutScript
|
chmod +x "$customCronFilePath/$autoBackupScript" "$customCronFilePath/$autoHolidaysScript" "$customCronFilePath/$autoLogoutScript"
|
||||||
|
|
||||||
# echo "Scripts build with PORT=$WEB_PORT and DATABSE=$POSTGRES_DB!"
|
# echo "Scripts build with PORT=$WEB_PORT and DATABSE=$POSTGRES_DB!"
|
||||||
echo "Adding rules to crontab."
|
echo "Adding rules to crontab."
|
||||||
|
|
||||||
cron_commands=$(mktemp /tmp/arbeitszeitmessung-cron.XXX)
|
cron_commands=$(mktemp /tmp/arbeitszeitmessung-cron.XXX)
|
||||||
|
pwd
|
||||||
|
|
||||||
for file in Cron/*; do
|
for file in $customCronFilePath/*; do
|
||||||
cron_timing=$(grep -E '^# cron-timing:' "$file" | sed 's/^# cron-timing:[[:space:]]*//')
|
cron_timing=$(grep -E '^# cron-timing:' "$file" | sed 's/^# cron-timing:[[:space:]]*//')
|
||||||
|
|
||||||
if [ -z "$cron_timing" ]; then
|
if [ -z "$cron_timing" ]; then
|
||||||
@@ -163,7 +244,6 @@ if [[ "$setup_cron" =~ ^[Yy]$ ]]; then
|
|||||||
|
|
||||||
( crontab -l ; echo "$cron_timing $(pwd)/$file" )| awk '!x[$0]++' | crontab -
|
( crontab -l ; echo "$cron_timing $(pwd)/$file" )| awk '!x[$0]++' | crontab -
|
||||||
echo "Added entry to crontab: $cron_timing $(pwd)/$file."
|
echo "Added entry to crontab: $cron_timing $(pwd)/$file."
|
||||||
sleep 2
|
|
||||||
done
|
done
|
||||||
|
|
||||||
if systemctl is-active --quiet cron.service ; then
|
if systemctl is-active --quiet cron.service ; then
|
||||||
@@ -176,10 +256,11 @@ if [[ "$setup_cron" =~ ^[Yy]$ ]]; then
|
|||||||
else
|
else
|
||||||
echo "Please setup cron manually by executing crontab -e and adding all files from inside the Cron directory!"
|
echo "Please setup cron manually by executing crontab -e and adding all files from inside the Cron directory!"
|
||||||
fi
|
fi
|
||||||
|
}
|
||||||
###########################################################################
|
###########################################################################
|
||||||
|
|
||||||
echo -e "\n\n"
|
function startContainer(){
|
||||||
|
echo -e "\r\n==================================================\r\n"
|
||||||
echo "Start containers with docker compose up -d? [y/N]"
|
echo "Start containers with docker compose up -d? [y/N]"
|
||||||
read -r start_containers
|
read -r start_containers
|
||||||
if [[ "$start_containers" =~ ^[Yy]$ ]]; then
|
if [[ "$start_containers" =~ ^[Yy]$ ]]; then
|
||||||
@@ -189,5 +270,58 @@ if [[ "$start_containers" =~ ^[Yy]$ ]]; then
|
|||||||
else
|
else
|
||||||
echo "You can start them manually with: docker compose up -d"
|
echo "You can start them manually with: docker compose up -d"
|
||||||
fi
|
fi
|
||||||
|
}
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
function help(){
|
||||||
|
echo "Installer Script für Arbeitszeitmessung Software"
|
||||||
|
echo -e "\r\n==================================================\r\n"
|
||||||
|
echo "Nutzung: ./install.sh [options]"
|
||||||
|
echo -e "\r\n==================================================\r\n"
|
||||||
|
echo "Optionen:"
|
||||||
|
echo " -h zeigt diese Übersicht"
|
||||||
|
echo " -c .env Datei bearbeiten/aktualisieren && cron neu configurieren"
|
||||||
|
echo -e "\r\n=================================================="
|
||||||
|
}
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
function main(){
|
||||||
|
echo -e "================Arbeitszeitmessung================\r\n"
|
||||||
|
if [ $# -gt 0 ];then
|
||||||
|
if [ $1 == reconfig ]; then
|
||||||
|
echo -e "================Reconfiguring================\r\n"
|
||||||
|
setupConfig $1
|
||||||
|
setupFolders $1
|
||||||
|
setupCron $1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
checkDocker
|
||||||
|
setupConfig
|
||||||
|
setupFolders
|
||||||
|
setupCron
|
||||||
|
startContainer
|
||||||
|
fi
|
||||||
echo "Installation finished, you can re-run the script any time!"
|
echo "Installation finished, you can re-run the script any time!"
|
||||||
|
}
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
function ask_reconfig(){
|
||||||
|
echo -e "\r\n==================================================\r\n"
|
||||||
|
echo "$2 [y/N]"
|
||||||
|
read -r do_reconfig
|
||||||
|
|
||||||
|
[[ "$do_reconfig" =~ ^[Yy]$ ]] && return # true
|
||||||
|
echo "Skipping..."
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
while getopts ":hc" opt; do
|
||||||
|
case $opt in
|
||||||
|
h) help; exit 0 ;;
|
||||||
|
c) main reconfig; exit 0 ;;
|
||||||
|
*) echo "Ungültiges Argument"; exit 1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
main
|
||||||
|
|||||||
Reference in New Issue
Block a user