From 76b23133d0d5bb94be880a4c940c1b3fb4bfc613 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Tr=C3=B6ger?= Date: Fri, 12 Dec 2025 12:26:40 +0100 Subject: [PATCH] fixed #61, #62 refactored getTime variants --- Backend/endpoints/pdf-create.go | 223 +++++++++++------------ Backend/helper/paramParser/main.go | 25 +++ Backend/helper/time.go | 7 + Backend/models/absence.go | 71 -------- Backend/models/absence_test.go | 4 +- Backend/models/user.go | 33 ++++ Backend/models/workDay.go | 12 +- Backend/templates/timePage.templ | 2 +- Backend/templates/timePage_templ.go | 2 +- DocumentCreator/templates/abrechnung.typ | 24 ++- 10 files changed, 196 insertions(+), 207 deletions(-) diff --git a/Backend/endpoints/pdf-create.go b/Backend/endpoints/pdf-create.go index 9f20759..5c74326 100644 --- a/Backend/endpoints/pdf-create.go +++ b/Backend/endpoints/pdf-create.go @@ -20,13 +20,21 @@ func convertDaysToTypst(days []models.IWorkDay, u models.User) ([]typstDay, erro var typstDays []typstDay for _, day := range days { var thisTypstDay typstDay - work, pause, overtime := day.GetTimes(u, models.WorktimeBaseWeek, true) + work, pause, overtime := day.GetTimes(u, models.WorktimeBaseDay, false) + workVirtual := day.GetWorktime(u, models.WorktimeBaseDay, true) + overtime = workVirtual - u.ArbeitszeitProWocheFrac(0.2) thisTypstDay.Date = day.Date().Format(DE_DATE) - thisTypstDay.Worktime = helper.FormatDurationFill(work, true) + thisTypstDay.Worktime = helper.FormatDurationFill(workVirtual, true) thisTypstDay.Pausetime = helper.FormatDurationFill(pause, true) thisTypstDay.Overtime = helper.FormatDurationFill(overtime, true) thisTypstDay.IsFriday = day.Date().Weekday() == time.Friday + if workVirtual > work { + thisTypstDay.Kurzarbeit = helper.FormatDurationFill(workVirtual-work, true) + } else { + thisTypstDay.Kurzarbeit = helper.FormatDurationFill(0, true) + } + thisTypstDay.DayParts = convertDayToTypstDayParts(day, u) typstDays = append(typstDays, thisTypstDay) } @@ -45,7 +53,7 @@ func convertDayToTypstDayParts(day models.IWorkDay, user models.User) []typstDay typstDayPart.IsWorkDay = true typstDayParts = append(typstDayParts, typstDayPart) } - if day.IsKurzArbeit() { + if day.IsKurzArbeit() && len(workDay.Bookings) > 0 { tsFrom, tsTo := workDay.GenerateKurzArbeitBookings(user) typstDayParts = append(typstDayParts, typstDayPart{ BookingFrom: tsFrom.Format("15:04"), @@ -54,6 +62,9 @@ func convertDayToTypstDayParts(day models.IWorkDay, user models.User) []typstDay IsWorkDay: true, }) } + if workdayAbsence := workDay.GetWorktimeAbsence(); (workdayAbsence != models.Absence{}) { + typstDayParts = append(typstDayParts, typstDayPart{IsWorkDay: false, WorkType: workdayAbsence.AbwesenheitTyp.Name}) + } } else { absentDay, _ := day.(*models.Absence) @@ -62,6 +73,90 @@ func convertDayToTypstDayParts(day models.IWorkDay, user models.User) []typstDay return typstDayParts } +func PDFCreateController(w http.ResponseWriter, r *http.Request) { + helper.RequiresLogin(Session, w, r) + switch r.Method { + case http.MethodGet: + user, err := models.GetUserFromSession(Session, r.Context()) + if err != nil { + log.Println("Error getting user!") + return + } + pp := paramParser.New(r.URL.Query()) + startDate := pp.ParseTimestampFallback("start_date", time.DateOnly, time.Now()) + personalNumbers := pp.ParseIntListFallback("employe_list", ",", make([]int, 0)) + + employes, err := models.GetUserByPersonalNrMulti(personalNumbers) + if err != nil { + slog.Warn("Error getting employes!", slog.Any("Error", err)) + return + } + + output, err := createReports(user, employes, startDate) + if err != nil { + slog.Warn("Could not create pdf report", slog.Any("Error", err)) + } + + w.Header().Set("Content-type", "application/pdf") + output.WriteTo(w) + w.WriteHeader(http.StatusOK) + + default: + http.Error(w, "Method not allowed!", http.StatusMethodNotAllowed) + } +} + +func createReports(user models.User, employes []models.User, startDate time.Time) (bytes.Buffer, error) { + startDate = helper.GetFirstOfMonth(startDate) + endDate := startDate.AddDate(0, 1, -1) + + var employeData []typstData + for _, employe := range employes { + if data, err := createEmployeReport(employe, startDate, endDate); err != nil { + slog.Warn("Error when creating employeReport", slog.Any("user", employe), slog.Any("error", err)) + } else { + employeData = append(employeData, data) + } + } + return renderPDF(employeData) +} + +func createEmployeReport(employee models.User, startDate, endDate time.Time) (typstData, error) { + targetHoursThisMonth := employee.ArbeitszeitProWocheFrac(.2) * time.Duration(helper.GetWorkingDays(startDate, endDate)) + workDaysThisMonth := models.GetDays(employee, startDate, endDate.AddDate(0, 0, 1), false) + + slog.Debug("Baseline Working hours", "targetHours", targetHoursThisMonth.Hours()) + + var workHours, kurzarbeitHours time.Duration + for _, day := range workDaysThisMonth { + tmpvirtualHours := day.GetWorktime(employee, models.WorktimeBaseDay, true) + tmpactualHours := day.GetWorktime(employee, models.WorktimeBaseDay, false) + if day.IsKurzArbeit() && tmpvirtualHours > tmpactualHours { + slog.Debug("Adding kurzarbeit to workday", "day", day.Date()) + kurzarbeitHours += tmpvirtualHours - tmpactualHours + } + workHours += tmpvirtualHours + } + worktimeBalance := workHours - targetHoursThisMonth + + typstDays, err := convertDaysToTypst(workDaysThisMonth, employee) + if err != nil { + slog.Warn("Failed to convert to days", slog.Any("error", err)) + return typstData{}, err + } + + metadata := typstMetadata{ + EmployeeName: fmt.Sprintf("%s %s", employee.Vorname, employee.Name), + TimeRange: fmt.Sprintf("%s - %s", startDate.Format(DE_DATE), endDate.Format(DE_DATE)), + Overtime: helper.FormatDurationFill(worktimeBalance, true), + WorkTime: helper.FormatDurationFill(workHours, true), + Kurzarbeit: helper.FormatDurationFill(kurzarbeitHours, true), + OvertimeTotal: "", + CurrentTimestamp: time.Now().Format("02.01.2006 - 15:04 Uhr"), + } + return typstData{Meta: metadata, Days: typstDays}, nil +} + func renderPDF(data []typstData) (bytes.Buffer, error) { var markup bytes.Buffer var output bytes.Buffer @@ -90,115 +185,6 @@ func renderPDF(data []typstData) (bytes.Buffer, error) { return output, nil } -func PDFCreateController(w http.ResponseWriter, r *http.Request) { - helper.RequiresLogin(Session, w, r) - switch r.Method { - case http.MethodGet: - user, err := models.GetUserFromSession(Session, r.Context()) - if err != nil { - log.Println("Error getting user!") - return - } - pp := paramParser.New(r.URL.Query()) - startDate := pp.ParseTimestampFallback("start_date", time.DateOnly, time.Now()) - var members []models.User = make([]models.User, 0) - output, err := createReports(user, members, startDate) - if err != nil { - slog.Warn("Could not create pdf report", slog.Any("Error", err)) - } - - w.Header().Set("Content-type", "application/pdf") - output.WriteTo(w) - w.WriteHeader(http.StatusOK) - - default: - http.Error(w, "Method not allowed!", http.StatusMethodNotAllowed) - } -} - -func createReports(user models.User, employes []models.User, startDate time.Time) (bytes.Buffer, error) { - if startDate.Day() > 1 { - startDate = startDate.AddDate(0, 0, -(startDate.Day() - 1)) - } - endDate := startDate.AddDate(0, 1, -1) - return createEmployeReport(user, startDate, endDate) -} - -func createEmployeReport(employee models.User, startDate, endDate time.Time) (bytes.Buffer, error) { - targetHours := (employee.ArbeitszeitProWoche() / 5) * time.Duration(helper.GetWorkingDays(startDate, endDate)) - workingDays := models.GetDays(employee, startDate, endDate, false) - - slog.Debug("Baseline Working hours", "targetHours", targetHours.Hours()) - - var actualHours time.Duration - for _, day := range workingDays { - actualHours += day.GetWorktime(employee, models.WorktimeBaseDay, true) - } - worktimeBalance := actualHours - targetHours - - typstDays, err := convertDaysToTypst(workingDays, employee) - if err != nil { - log.Panicf("Failed to convert days!") - } - - metadata := typstMetadata{ - EmployeeName: fmt.Sprintf("%s %s", employee.Vorname, employee.Name), - TimeRange: fmt.Sprintf("%s - %s", startDate.Format(DE_DATE), endDate.Format(DE_DATE)), - Overtime: helper.FormatDurationFill(worktimeBalance, true), - WorkTime: helper.FormatDurationFill(actualHours, true), - OvertimeTotal: "", - CurrentTimestamp: time.Now().Format("02.01.2006 - 15:04 Uhr"), - } - - return renderPDF([]typstData{{Meta: metadata, Days: typstDays}, {Meta: metadata, Days: typstDays}}) -} - -func PDFHandler(w http.ResponseWriter, r *http.Request) { - helper.RequiresLogin(Session, w, r) - startDate := time.Now() - - if startDate.Day() > 1 { - startDate = startDate.AddDate(0, 0, -(startDate.Day() - 1)) - } - endDate := startDate.AddDate(0, 1, -1) - - user, err := models.GetUserFromSession(Session, r.Context()) - if err != nil { - log.Println("Error getting user!") - } - - //TODO: only accepted weeks - - weeks := models.GetDays(user, startDate, endDate, false) - var aggregatedOvertime, aggregatedWorkTime time.Duration - for _, day := range weeks { - aggregatedOvertime += day.GetOvertime(user, models.WorktimeBaseWeek, false) - aggregatedWorkTime += day.GetWorktime(user, models.WorktimeBaseWeek, true) - } - - typstDays, err := convertDaysToTypst(weeks, user) - if err != nil { - log.Panicf("Failed to convert days!") - } - metadata := typstMetadata{ - EmployeeName: fmt.Sprintf("%s %s", user.Vorname, user.Name), - TimeRange: fmt.Sprintf("%s - %s", startDate.Format(DE_DATE), endDate.Format(DE_DATE)), - Overtime: helper.FormatDurationFill(aggregatedOvertime, true), - WorkTime: helper.FormatDurationFill(aggregatedWorkTime, true), - OvertimeTotal: "", - CurrentTimestamp: time.Now().Format("02.01.2006 - 15:04 Uhr"), - } - - output, err := renderPDF([]typstData{{Meta: metadata, Days: typstDays}}) - if err != nil { - slog.Warn("Could not create pdf report", slog.Any("Error", err)) - } - - w.Header().Set("Content-type", "application/pdf") - output.WriteTo(w) - w.WriteHeader(200) -} - type typstMetadata struct { TimeRange string `json:"time-range"` EmployeeName string `json:"employee-name"` @@ -217,12 +203,13 @@ type typstDayPart struct { } type typstDay struct { - Date string `json:"date"` - DayParts []typstDayPart `json:"day-parts"` - Worktime string `json:"worktime"` - Pausetime string `json:"pausetime"` - Overtime string `json:"overtime"` - IsFriday bool `json:"is-weekend"` + Date string `json:"date"` + DayParts []typstDayPart `json:"day-parts"` + Worktime string `json:"worktime"` + Pausetime string `json:"pausetime"` + Overtime string `json:"overtime"` + Kurzarbeit string `json:"kurzarbeit"` + IsFriday bool `json:"is-weekend"` } type typstData struct { diff --git a/Backend/helper/paramParser/main.go b/Backend/helper/paramParser/main.go index 828b01f..dcce66d 100644 --- a/Backend/helper/paramParser/main.go +++ b/Backend/helper/paramParser/main.go @@ -5,6 +5,7 @@ import ( "log/slog" "net/url" "strconv" + "strings" "time" ) @@ -12,6 +13,30 @@ type ParamsParser struct { urlParams url.Values } +func (p ParamsParser) ParseStringListFallback(key string, delimiter string, fallback []string) []string { + if !p.urlParams.Has(key) { + return fallback + } + paramList := p.urlParams.Get(key) + list := strings.Split(paramList, delimiter) + return list +} + +func (p ParamsParser) ParseIntListFallback(key string, delimiter string, fallback []int) []int { + if !p.urlParams.Has(key) { + return fallback + } + paramList := p.urlParams.Get(key) + list := strings.Split(paramList, delimiter) + parsedList := make([]int, 0) + for _, item := range list { + if parsedItem, err := strconv.Atoi(item); err == nil { + parsedList = append(parsedList, parsedItem) + } + } + return parsedList +} + type NoValueError struct { Key string } diff --git a/Backend/helper/time.go b/Backend/helper/time.go index fcd739d..1337485 100644 --- a/Backend/helper/time.go +++ b/Backend/helper/time.go @@ -16,6 +16,13 @@ func GetMonday(ts time.Time) time.Time { return ts } +func GetFirstOfMonth(ts time.Time) time.Time { + if ts.Day() > 1 { + return ts.AddDate(0, 0, -(ts.Day() - 1)) + } + return ts +} + func IsWeekend(ts time.Time) bool { return ts.Weekday() == time.Saturday || ts.Weekday() == time.Sunday } diff --git a/Backend/models/absence.go b/Backend/models/absence.go index 1ff28e2..0d1beaa 100644 --- a/Backend/models/absence.go +++ b/Backend/models/absence.go @@ -86,77 +86,6 @@ func (a *Absence) GetTimes(u User, base WorktimeBase, includeKurzarbeit bool) (w return a.GetWorktime(u, base, includeKurzarbeit), a.GetPausetime(u, base, includeKurzarbeit), a.GetOvertime(u, base, includeKurzarbeit) } -func (a *Absence) GetWorktimeReal(u User, base WorktimeBase) time.Duration { - if a.AbwesenheitTyp.WorkTime <= 0 { - return 0 - } - switch base { - case WorktimeBaseDay: - return u.ArbeitszeitProTagFrac(float32(a.AbwesenheitTyp.WorkTime) / 100) - case WorktimeBaseWeek: - return u.ArbeitszeitProWocheFrac(0.2 * float32(a.AbwesenheitTyp.WorkTime) / 100) - } - return 0 -} -func (a *Absence) GetPausetimeReal(u User, base WorktimeBase) time.Duration { - return 0 -} - -func (a *Absence) GetOvertimeReal(u User, base WorktimeBase) time.Duration { - if a.AbwesenheitTyp.WorkTime > 0 { - return 0 - } - switch base { - case WorktimeBaseDay: - return -u.ArbeitszeitProTagFrac(1) - case WorktimeBaseWeek: - return -u.ArbeitszeitProWocheFrac(0.2) - } - return 0 -} - -func (a *Absence) GetWorktimeVirtual(u User, base WorktimeBase) time.Duration { - return a.GetWorktimeReal(u, base) -} - -func (a *Absence) GetPausetimeVirtual(u User, base WorktimeBase) time.Duration { - return a.GetPausetimeReal(u, base) -} - -func (a *Absence) GetOvertimeVirtual(u User, base WorktimeBase) time.Duration { - return a.GetOvertimeReal(u, base) -} - -func (a *Absence) GetTimesReal(u User, base WorktimeBase) (work, pause, overtime time.Duration) { - return a.GetWorktimeReal(u, base), a.GetPausetimeReal(u, base), a.GetOvertimeReal(u, base) -} - -func (a *Absence) GetTimesVirtual(u User, base WorktimeBase) (work, pause, overtime time.Duration) { - return a.GetWorktimeVirtual(u, base), a.GetPausetimeVirtual(u, base), a.GetOvertimeVirtual(u, base) -} - -func (a *Absence) TimeWorkVirtual(u User) time.Duration { - return a.TimeWorkReal(u) -} - -func (a *Absence) TimeWorkReal(u User) time.Duration { - if a.AbwesenheitTyp.WorkTime > 1 { - return time.Duration(u.ArbeitszeitPerTag * float32(time.Hour)).Round(time.Minute) - } - return 0 -} - -func (a *Absence) TimePauseReal(u User) (work, pause time.Duration) { - return 0, 0 -} - -func (a *Absence) TimeOvertimeReal(u User) time.Duration { - if a.AbwesenheitTyp.WorkTime > 1 { - return 0 - } - return -u.ArbeitszeitProTag() -} - func (a *Absence) ToString() string { return "Abwesenheit" } diff --git a/Backend/models/absence_test.go b/Backend/models/absence_test.go index 7e40dea..235579b 100644 --- a/Backend/models/absence_test.go +++ b/Backend/models/absence_test.go @@ -52,7 +52,7 @@ func TestCalcRealWorkTimeDayAbsence(t *testing.T) { t.Run("Calc Absence Worktime: "+tc.absenceType.Name, func(t *testing.T) { var testCase = testAbsence testCase.AbwesenheitTyp = tc.absenceType - workTime := testCase.GetWorktimeReal(testUser, models.WorktimeBaseDay) + workTime := testCase.GetWorktime(testUser, models.WorktimeBaseDay, false) if workTime != tc.expectedTime { t.Errorf("Calc Worktime Default not working, time should be %s, but was %s", helper.FormatDurationFill(tc.expectedTime, true), helper.FormatDurationFill(workTime, true)) } @@ -83,7 +83,7 @@ func TestCalcRealWorkTimeWeekAbsence(t *testing.T) { t.Run("Calc Absence Worktime: "+tc.absenceType.Name, func(t *testing.T) { var testCase = testAbsence testCase.AbwesenheitTyp = tc.absenceType - workTime := testCase.GetWorktimeReal(testUser, models.WorktimeBaseWeek) + workTime := testCase.GetWorktime(testUser, models.WorktimeBaseWeek, false) if workTime != tc.expectedTime { t.Errorf("Calc Worktime Default not working, time should be %s, but was %s", helper.FormatDurationFill(tc.expectedTime, true), helper.FormatDurationFill(workTime, true)) } diff --git a/Backend/models/user.go b/Backend/models/user.go index 8f654e0..1e87b3d 100644 --- a/Backend/models/user.go +++ b/Backend/models/user.go @@ -10,6 +10,7 @@ import ( "time" "github.com/alexedwards/scs/v2" + "github.com/lib/pq" ) type User struct { @@ -173,6 +174,38 @@ func GetUserByPersonalNr(personalNummer int) (User, error) { return user, nil } +func GetUserByPersonalNrMulti(personalNummerMulti []int) ([]User, error) { + var users []User + if len(personalNummerMulti) == 0 { + 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[]);`)) + if err != nil { + return users, err + } + + rows, err := qStr.Query(pq.Array(personalNummerMulti)) + if err == sql.ErrNoRows { + return users, err + } + if err != nil { + return nil, err + } + 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 { + return users, err + } + users = append(users, user) + } + if err = rows.Err(); err != nil { + return users, err + } + return users, nil +} + func (u *User) Login(password string) bool { var loginSuccess bool qStr, err := DB.Prepare((`SELECT (pass_hash = crypt($2, pass_hash)) AS pass_hash FROM user_password WHERE personal_nummer = $1;`)) diff --git a/Backend/models/workDay.go b/Backend/models/workDay.go index e82bd5a..f6e88d1 100644 --- a/Backend/models/workDay.go +++ b/Backend/models/workDay.go @@ -30,15 +30,19 @@ const ( WorktimeBaseDay WorktimeBase = "day" ) +func (d *WorkDay) GetWorktimeAbsence() Absence { + return d.worktimeAbsece +} + // Gets the time as is in the db (with corrected pause times) func (d *WorkDay) GetWorktime(u User, base WorktimeBase, includeKurzarbeit bool) time.Duration { - if includeKurzarbeit && d.IsKurzArbeit() { + if includeKurzarbeit && d.IsKurzArbeit() && len(d.Bookings) > 0 { return d.kurzArbeitAbsence.GetWorktime(u, base, true) } work, pause := calcWorkPause(d.Bookings) work, pause = correctWorkPause(work, pause) if (d.worktimeAbsece != Absence{}) { - work += d.worktimeAbsece.GetWorktimeReal(u, base) + work += d.worktimeAbsece.GetWorktime(u, base, false) } return work.Round(time.Minute) } @@ -124,12 +128,12 @@ func (d *WorkDay) Date() time.Time { func (d *WorkDay) GenerateKurzArbeitBookings(u User) (time.Time, time.Time) { var timeFrom, timeTo time.Time - if d.workTime >= u.ArbeitszeitProTag() { + if d.GetWorktime(u, WorktimeBaseDay, false) >= u.ArbeitszeitProTag() { return timeFrom, timeTo } timeFrom = d.Bookings[len(d.Bookings)-1].Timestamp.Add(time.Minute) - timeTo = timeFrom.Add(u.ArbeitszeitProTag() - d.workTime) + timeTo = timeFrom.Add(u.ArbeitszeitProTag() - d.GetWorktime(u, WorktimeBaseDay, false)) slog.Debug("Added duration as Kurzarbeit", "date", d.Date().String(), "duration", timeTo.Sub(timeFrom).String()) return timeFrom, timeTo diff --git a/Backend/templates/timePage.templ b/Backend/templates/timePage.templ index 297d2d9..92cb0a2 100644 --- a/Backend/templates/timePage.templ +++ b/Backend/templates/timePage.templ @@ -137,7 +137,7 @@ templ defaultDayComponent(day models.IWorkDay) { if len(workDay.Bookings) < 1 {

Keine Buchung gefunden. Bitte Arbeitsstunden oder Grund der Abwesenheit eingeben!

} - if workDay.IsKurzArbeit() { + if workDay.IsKurzArbeit() && len(workDay.Bookings) > 0 { @absenceComponent(workDay.GetKurzArbeit(), true) } for _, booking := range workDay.Bookings { diff --git a/Backend/templates/timePage_templ.go b/Backend/templates/timePage_templ.go index 4bfba6c..506f94c 100644 --- a/Backend/templates/timePage_templ.go +++ b/Backend/templates/timePage_templ.go @@ -436,7 +436,7 @@ func defaultDayComponent(day models.IWorkDay) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - if workDay.IsKurzArbeit() { + if workDay.IsKurzArbeit() && len(workDay.Bookings) > 0 { templ_7745c5c3_Err = absenceComponent(workDay.GetKurzArbeit(), true).Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err diff --git a/DocumentCreator/templates/abrechnung.typ b/DocumentCreator/templates/abrechnung.typ index ee71ebc..0735264 100644 --- a/DocumentCreator/templates/abrechnung.typ +++ b/DocumentCreator/templates/abrechnung.typ @@ -37,31 +37,35 @@ [Zeitraum: #meta.TimeRange] table( - columns: (1fr, 1fr, 1fr, 1fr, 1fr, 1fr, .875fr, 1.25fr), + columns: (1fr, 1fr, 1fr, 1fr, 1fr, 1fr, .875fr, 1.25fr), fill: (x, y) => if y == 0 { oklch(87%, 0, 0deg) }, table-header( - [Datum], [Kommen], [Gehen], [Arbeitsart], [Stunden], [Pause], [Überstunden] + [Datum], [Kommen], [Gehen], [Arbeitsart], [Stunden], [Kurzarbeit], [Pause], [Überstunden] ), .. for day in days { ( [#day.Date], if day.DayParts.len() == 0{ table.cell(colspan: 3)[Keine Buchungen] - }else if not day.DayParts.first().IsWorkDay{ + }else if day.DayParts.len() == 1 and not day.DayParts.first().IsWorkDay{ table.cell(colspan: 3)[#day.DayParts.first().WorkType] } else { - table.cell(colspan: 3, inset: 0em)[ - #table( columns: (1fr, 1fr, 1fr), .. for Zeit in day.DayParts { ( - [#Zeit.BookingFrom], - [#Zeit.BookingTo], - [#Zeit.WorkType], + if Zeit.IsWorkDay{ + ( + table.cell()[#Zeit.BookingFrom], + table.cell()[#Zeit.BookingTo], + table.cell()[#Zeit.WorkType], + ) + }else{ + (table.cell(colspan: 3)[#Zeit.WorkType],) + } ) }, ) @@ -85,9 +89,9 @@ stroke: none, table.hline(start: 0, end: 2, stroke: stroke(dash:"dashed", thickness:.5pt)), [Arbeitszeit :], table.cell(align: left)[#meta.WorkTime], + [Kurzarbeit :], table.cell(align: left)[#meta.Kurzarbeit], [Überstunden :], table.cell(align: left)[#meta.Overtime], - [Überstunden :],table.cell(align: left)[#meta.OvertimeTotal], + [Überstunden lfd. :],table.cell(align: left)[#meta.OvertimeTotal], table.hline(start: 0, end: 2), - ) }