diff --git a/Backend/endpoints/auto-kurzarbeit.go b/Backend/endpoints/auto-kurzarbeit.go new file mode 100644 index 0000000..3a1e394 --- /dev/null +++ b/Backend/endpoints/auto-kurzarbeit.go @@ -0,0 +1,82 @@ +package endpoints + +import ( + "arbeitszeitmessung/helper" + "arbeitszeitmessung/helper/paramParser" + "arbeitszeitmessung/models" + "encoding/json" + "errors" + "log/slog" + "net/http" + "time" +) + +func KurzarbeitFillHandler(w http.ResponseWriter, r *http.Request) { + helper.SetCors(w) + switch r.Method { + case "GET": + fillKurzarbeit(r, w) + default: + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + } +} + +func fillKurzarbeit(r *http.Request, w http.ResponseWriter) { + bookingTypeKurzarbeit, err := getKurzarbeitBookingType() + if err != nil { + slog.Info("Error getting BookingType Kurzarbeit %v\n", slog.Any("Error", err)) + } + users, err := models.GetAllUsers() + if err != nil { + slog.Info("Error getting user list %v\n", slog.Any("Error", err)) + } + + pp := paramParser.New(r.URL.Query()) + startDate := pp.ParseTimestampFallback("date", time.DateOnly, time.Now()) + + var kurzarbeitAdded int + + for _, user := range users { + days := models.GetDays(user, startDate, startDate.AddDate(0, 0, 1), false) + if len(days) == 0 { + continue + } + + day := days[len(days)-1] + if !day.IsKurzArbeit() || !day.IsWorkDay() { + continue + } + if day.GetWorktimeReal(user, models.WorktimeBaseDay) >= day.GetWorktimeVirtual(user, models.WorktimeBaseDay) { + continue + } + + worktimeKurzarbeit := day.GetWorktimeVirtual(user, models.WorktimeBaseDay) - day.GetWorktimeReal(user, models.WorktimeBaseDay) + + if wDay, ok := day.(*models.WorkDay); !ok || len(wDay.Bookings) == 0 { + continue + } + workday, _ := day.(*models.WorkDay) + + lastBookingTime := workday.Bookings[len(workday.Bookings)-1].Timestamp + kurzarbeitBegin := (*models.Booking).New(nil, user.CardUID, 0, 1, bookingTypeKurzarbeit.Id) + kurzarbeitEnd := (*models.Booking).New(nil, user.CardUID, 0, 2, bookingTypeKurzarbeit.Id) + kurzarbeitBegin.Timestamp = lastBookingTime.Add(time.Minute) + kurzarbeitEnd.Timestamp = lastBookingTime.Add(worktimeKurzarbeit) + + kurzarbeitBegin.InsertWithTimestamp() + kurzarbeitEnd.InsertWithTimestamp() + kurzarbeitAdded += 1 + } + + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(kurzarbeitAdded) +} + +func getKurzarbeitBookingType() (models.BookingType, error) { + for _, bookingType := range models.GetBookingTypesCached() { + if bookingType.Name == "Kurzarbeit" { + return bookingType, nil + } + } + return models.BookingType{}, errors.New("No Booking Type found") +} diff --git a/Backend/endpoints/auto_logout.go b/Backend/endpoints/auto-logout.go similarity index 95% rename from Backend/endpoints/auto_logout.go rename to Backend/endpoints/auto-logout.go index 7d6de92..2f38541 100644 --- a/Backend/endpoints/auto_logout.go +++ b/Backend/endpoints/auto-logout.go @@ -20,7 +20,7 @@ func LogoutHandler(w http.ResponseWriter, r *http.Request) { } func autoLogout(w http.ResponseWriter) { - users, err := (*models.User).GetAll(nil) + users, err := models.GetAllUsers() var logged_out_users []models.User if err != nil { fmt.Printf("Error getting user list %v\n", err) diff --git a/Backend/endpoints/pdf-create.go b/Backend/endpoints/pdf-create.go index 2672371..7d1ae45 100644 --- a/Backend/endpoints/pdf-create.go +++ b/Backend/endpoints/pdf-create.go @@ -20,7 +20,7 @@ func convertDaysToTypst(days []models.IWorkDay, u models.User) ([]typstDay, erro var typstDays []typstDay for _, day := range days { var thisTypstDay typstDay - work, pause, overtime := day.GetTimesVirtual(u, models.WorktimeBaseWeek) + work, pause, overtime := day.GetTimesReal(u, models.WorktimeBaseWeek) thisTypstDay.Date = day.Date().Format(DE_DATE) thisTypstDay.Worktime = helper.FormatDurationFill(work, true) thisTypstDay.Pausetime = helper.FormatDurationFill(pause, true) @@ -45,15 +45,6 @@ func convertDayToTypstDayParts(day models.IWorkDay, user models.User) []typstDay typstDayPart.IsWorkDay = true typstDayParts = append(typstDayParts, typstDayPart) } - if day.IsKurzArbeit() { - tsFrom, tsTo := workDay.GenerateKurzArbeitBookings(user) - typstDayParts = append(typstDayParts, typstDayPart{ - BookingFrom: tsFrom.Format("15:04"), - BookingTo: tsTo.Format("15:04"), - WorkType: "Kurzarbeit", - IsWorkDay: true, - }) - } } else { absentDay, _ := day.(*models.Absence) typstDayParts = append(typstDayParts, typstDayPart{IsWorkDay: false, WorkType: absentDay.AbwesenheitTyp.Name}) @@ -61,11 +52,11 @@ func convertDayToTypstDayParts(day models.IWorkDay, user models.User) []typstDay return typstDayParts } -func renderPDF(days []typstDay, metadata typstMetadata) (bytes.Buffer, error) { +func renderPDF(data []typstData) (bytes.Buffer, error) { var markup bytes.Buffer var output bytes.Buffer - if err := typst.InjectValues(&markup, map[string]any{"meta": metadata, "days": days}); err != nil { + if err := typst.InjectValues(&markup, map[string]any{"data": data}); err != nil { return output, err } @@ -73,18 +64,13 @@ func renderPDF(days []typstDay, metadata typstMetadata) (bytes.Buffer, error) { // Show is used to replace the current document with whatever content the template function in `template.typ` returns. markup.WriteString(` #import "templates/abrechnung.typ": abrechnung - #show: doc => abrechnung(meta, days) + #for d in data { + abrechnung(d.Meta, d.Days) + } `) // Compile the prepared markup with Typst and write the result it into `output.pdf`. - // f, err := os.Create("output.pdf") - // if err != nil { - // log.Panicf("Failed to create output file: %v.", err) - // } - // defer f.Close() - // - // typstCLI := typst.CLI{} typstCLI := typst.DockerExec{ ContainerName: helper.GetEnv("TYPST_CONTAINER", "arbeitszeitmessung-doc-creator"), } @@ -113,7 +99,7 @@ func PDFCreateController(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-type", "application/pdf") output.WriteTo(w) - w.WriteHeader(200) + w.WriteHeader(http.StatusOK) default: http.Error(w, "Method not allowed!", http.StatusMethodNotAllowed) @@ -154,7 +140,7 @@ func createEmployeReport(employee models.User, startDate, endDate time.Time) (by CurrentTimestamp: time.Now().Format("02.01.2006 - 15:04 Uhr"), } - return renderPDF(typstDays, metadata) + return renderPDF([]typstData{{Meta: metadata, Days: typstDays}, {Meta: metadata, Days: typstDays}}) } func PDFHandler(w http.ResponseWriter, r *http.Request) { @@ -193,7 +179,7 @@ func PDFHandler(w http.ResponseWriter, r *http.Request) { CurrentTimestamp: time.Now().Format("02.01.2006 - 15:04 Uhr"), } - output, err := renderPDF(typstDays, metadata) + output, err := renderPDF([]typstData{{Meta: metadata, Days: typstDays}}) if err != nil { slog.Warn("Could not create pdf report", slog.Any("Error", err)) } @@ -227,3 +213,8 @@ type typstDay struct { Overtime string `json:"overtime"` IsFriday bool `json:"is-weekend"` } + +type typstData struct { + Meta typstMetadata `json:"meta"` + Days []typstDay `json:"days"` +} diff --git a/Backend/helper/paramParser/main.go b/Backend/helper/paramParser/main.go index be046a8..828b01f 100644 --- a/Backend/helper/paramParser/main.go +++ b/Backend/helper/paramParser/main.go @@ -59,7 +59,7 @@ func (p *ParamsParser) ParseStringFallback(key string, fallback string) string { return p.urlParams.Get(key) } -func (p *ParamsParser) ParseString(key string, fallback string) (string, error) { +func (p *ParamsParser) ParseString(key string) (string, error) { if !p.urlParams.Has(key) { return "", &NoValueError{Key: key} } diff --git a/Backend/helper/time_test.go b/Backend/helper/time_test.go index 01b3dd8..bb10294 100644 --- a/Backend/helper/time_test.go +++ b/Backend/helper/time_test.go @@ -30,16 +30,18 @@ func TestFormatDuration(t *testing.T) { testCases := []struct { name string duration time.Duration + fill bool }{ - {"2h", time.Duration(120 * time.Minute)}, - {"30min", time.Duration(30 * time.Minute)}, - {"1h 30min", time.Duration(90 * time.Minute)}, - {"-1h 30min", time.Duration(-90 * time.Minute)}, - {"0min", 0}, + {"2h", time.Duration(120 * time.Minute), true}, + {"30min", time.Duration(30 * time.Minute), true}, + {"1h 30min", time.Duration(90 * time.Minute), true}, + {"-1h 30min", time.Duration(-90 * time.Minute), true}, + {"0min", 0, true}, + {"", 0, false}, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - if FormatDurationFill(tc.duration, true) != tc.name { + if FormatDurationFill(tc.duration, tc.fill) != tc.name { t.Error("Format missmatch in Formatduration.") } }) diff --git a/Backend/main.go b/Backend/main.go index dbb6d87..e74d7bb 100644 --- a/Backend/main.go +++ b/Backend/main.go @@ -46,7 +46,8 @@ func main() { server.HandleFunc("/time/new", endpoints.TimeCreateHandler) server.Handle("/absence", ParamsMiddleware(endpoints.AbsencHandler)) server.Handle("/time", ParamsMiddleware(endpoints.TimeHandler)) - server.HandleFunc("/logout", endpoints.LogoutHandler) + server.HandleFunc("/auto/logout", endpoints.LogoutHandler) + server.HandleFunc("/auto/kurzarbeit", endpoints.KurzarbeitFillHandler) server.HandleFunc("/user/{action}", endpoints.UserHandler) // server.HandleFunc("/user/login", endpoints.LoginHandler) // server.HandleFunc("/user/settings", endpoints.UserSettingsHandler) diff --git a/Backend/models/user.go b/Backend/models/user.go index e3d94a9..646a561 100644 --- a/Backend/models/user.go +++ b/Backend/models/user.go @@ -57,6 +57,34 @@ func (u *User) GetReportedOvertime() (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 { + fmt.Printf("Error preparing query statement %v\n", err) + 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, &user.ArbeitszeitPerTag, &user.ArbeitszeitPerWoche); 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 diff --git a/DB/initdb/01_schema.sql b/DB/initdb/01_schema.sql index 21e48ea..70cfefe 100755 --- a/DB/initdb/01_schema.sql +++ b/DB/initdb/01_schema.sql @@ -104,7 +104,7 @@ CREATE TABLE "s_abwesenheit_typen" ( "arbeitszeit_equivalent" float4 NOT NULL ); -COMMENT ON COLUMN "s_abwesenheit_typen"."arbeitszeit_equivalent" IS '0=keine Arbeitszeit; 1=Arbeitszeit auffüllen; 2=Arbeitszeit austauschen'; +COMMENT ON COLUMN "s_abwesenheit_typen"."arbeitszeit_equivalent" IS '0=keine Arbeitszeit; -1=Arbeitszeit auffüllen; <=1 => Arbeitszeit'; -- Adds crypto extension diff --git a/DocumentCreator/templates/abrechnung.typ b/DocumentCreator/templates/abrechnung.typ index ade04ab..5eb7bb0 100644 --- a/DocumentCreator/templates/abrechnung.typ +++ b/DocumentCreator/templates/abrechnung.typ @@ -11,7 +11,7 @@ columns: (3fr, .65fr), align: left + horizon, inset: .5em, - [#meta.EmployeeName -- #meta.TimeRange], grid.cell(rowspan: 2)[#image("static/logo.png")], + [#meta.EmployeeName -- #meta.TimeRange], grid.cell(rowspan: 2)[#image("/static/logo.png")], [Arbeitszeitrechnung maschinell erstellt am #meta.CurrentTimestamp], ) ])