4 Commits

Author SHA1 Message Date
tom
a1b225478a fixed sonarqube issue
All checks were successful
Tests / Run Go Tests (push) Successful in 1m45s
2025-12-12 14:13:24 +01:00
588bf908c6 fixed tests
Some checks failed
Tests / Run Go Tests (push) Failing after 1m37s
2025-12-12 12:58:41 +01:00
76b23133d0 fixed #61, #62 refactored getTime variants
Some checks failed
Tests / Run Go Tests (push) Failing after 1m20s
2025-12-12 12:26:40 +01:00
1ccc19b85c removed and refactored virtual and real worktime 2025-12-12 06:31:03 +01:00
17 changed files with 252 additions and 265 deletions

View File

@@ -46,11 +46,11 @@ func fillKurzarbeit(r *http.Request, w http.ResponseWriter) {
if !day.IsKurzArbeit() || !day.IsWorkDay() { if !day.IsKurzArbeit() || !day.IsWorkDay() {
continue continue
} }
if day.GetWorktimeReal(user, models.WorktimeBaseDay) >= day.GetWorktimeVirtual(user, models.WorktimeBaseDay) { if day.GetWorktime(user, models.WorktimeBaseDay, false) >= day.GetWorktime(user, models.WorktimeBaseDay, true) {
continue continue
} }
worktimeKurzarbeit := day.GetWorktimeVirtual(user, models.WorktimeBaseDay) - day.GetWorktimeReal(user, models.WorktimeBaseDay) worktimeKurzarbeit := day.GetWorktime(user, models.WorktimeBaseDay, true) - day.GetWorktime(user, models.WorktimeBaseDay, false)
if wDay, ok := day.(*models.WorkDay); !ok || len(wDay.Bookings) == 0 { if wDay, ok := day.(*models.WorkDay); !ok || len(wDay.Bookings) == 0 {
continue continue

View File

@@ -20,13 +20,21 @@ func convertDaysToTypst(days []models.IWorkDay, u models.User) ([]typstDay, erro
var typstDays []typstDay var typstDays []typstDay
for _, day := range days { for _, day := range days {
var thisTypstDay typstDay var thisTypstDay typstDay
work, pause, overtime := day.GetTimesVirtual(u, models.WorktimeBaseWeek) 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.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.Pausetime = helper.FormatDurationFill(pause, true)
thisTypstDay.Overtime = helper.FormatDurationFill(overtime, true) thisTypstDay.Overtime = helper.FormatDurationFill(overtime, true)
thisTypstDay.IsFriday = day.Date().Weekday() == time.Friday 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) thisTypstDay.DayParts = convertDayToTypstDayParts(day, u)
typstDays = append(typstDays, thisTypstDay) typstDays = append(typstDays, thisTypstDay)
} }
@@ -45,7 +53,7 @@ func convertDayToTypstDayParts(day models.IWorkDay, user models.User) []typstDay
typstDayPart.IsWorkDay = true typstDayPart.IsWorkDay = true
typstDayParts = append(typstDayParts, typstDayPart) typstDayParts = append(typstDayParts, typstDayPart)
} }
if day.IsKurzArbeit() { if day.IsKurzArbeit() && len(workDay.Bookings) > 0 {
tsFrom, tsTo := workDay.GenerateKurzArbeitBookings(user) tsFrom, tsTo := workDay.GenerateKurzArbeitBookings(user)
typstDayParts = append(typstDayParts, typstDayPart{ typstDayParts = append(typstDayParts, typstDayPart{
BookingFrom: tsFrom.Format("15:04"), BookingFrom: tsFrom.Format("15:04"),
@@ -54,6 +62,9 @@ func convertDayToTypstDayParts(day models.IWorkDay, user models.User) []typstDay
IsWorkDay: true, IsWorkDay: true,
}) })
} }
if workdayAbsence := workDay.GetWorktimeAbsence(); (workdayAbsence != models.Absence{}) {
typstDayParts = append(typstDayParts, typstDayPart{IsWorkDay: false, WorkType: workdayAbsence.AbwesenheitTyp.Name})
}
} else { } else {
absentDay, _ := day.(*models.Absence) absentDay, _ := day.(*models.Absence)
@@ -62,6 +73,90 @@ func convertDayToTypstDayParts(day models.IWorkDay, user models.User) []typstDay
return typstDayParts 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) { func renderPDF(data []typstData) (bytes.Buffer, error) {
var markup bytes.Buffer var markup bytes.Buffer
var output bytes.Buffer var output bytes.Buffer
@@ -90,115 +185,6 @@ func renderPDF(data []typstData) (bytes.Buffer, error) {
return output, nil 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.GetWorktimeVirtual(employee, models.WorktimeBaseDay)
}
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.GetOvertimeReal(user, models.WorktimeBaseWeek)
aggregatedWorkTime += day.GetWorktimeVirtual(user, models.WorktimeBaseWeek)
}
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 { type typstMetadata struct {
TimeRange string `json:"time-range"` TimeRange string `json:"time-range"`
EmployeeName string `json:"employee-name"` EmployeeName string `json:"employee-name"`
@@ -222,6 +208,7 @@ type typstDay struct {
Worktime string `json:"worktime"` Worktime string `json:"worktime"`
Pausetime string `json:"pausetime"` Pausetime string `json:"pausetime"`
Overtime string `json:"overtime"` Overtime string `json:"overtime"`
Kurzarbeit string `json:"kurzarbeit"`
IsFriday bool `json:"is-weekend"` IsFriday bool `json:"is-weekend"`
} }

View File

@@ -84,7 +84,7 @@ func getBookings(w http.ResponseWriter, r *http.Request) {
if day.Date().Before(lastSub) { if day.Date().Before(lastSub) {
continue continue
} }
aggregatedOvertime += day.GetOvertimeReal(user, models.WorktimeBaseDay) aggregatedOvertime += day.GetOvertime(user, models.WorktimeBaseDay, false)
} }
if reportedOvertime, err := user.GetReportedOvertime(); err == nil { if reportedOvertime, err := user.GetReportedOvertime(); err == nil {
user.Overtime = (reportedOvertime + aggregatedOvertime).Round(time.Minute) user.Overtime = (reportedOvertime + aggregatedOvertime).Round(time.Minute)

View File

@@ -5,6 +5,7 @@ import (
"log/slog" "log/slog"
"net/url" "net/url"
"strconv" "strconv"
"strings"
"time" "time"
) )
@@ -12,6 +13,30 @@ type ParamsParser struct {
urlParams url.Values 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 { type NoValueError struct {
Key string Key string
} }

View File

@@ -16,6 +16,13 @@ func GetMonday(ts time.Time) time.Time {
return ts 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 { func IsWeekend(ts time.Time) bool {
return ts.Weekday() == time.Saturday || ts.Weekday() == time.Sunday return ts.Weekday() == time.Saturday || ts.Weekday() == time.Sunday
} }

View File

@@ -49,75 +49,45 @@ func (a *Absence) IsMultiDay() bool {
return !a.DateFrom.Equal(a.DateTo) return !a.DateFrom.Equal(a.DateTo)
} }
func (a *Absence) GetWorktimeReal(u User, base WorktimeBase) time.Duration { func (a *Absence) GetWorktime(u User, base WorktimeBase, includeKurzarbeit bool) time.Duration {
if a.AbwesenheitTyp.WorkTime <= 0 {
return 0
}
switch base { switch base {
case WorktimeBaseDay: case WorktimeBaseDay:
if a.AbwesenheitTyp.WorkTime <= 0 && includeKurzarbeit {
return u.ArbeitszeitProTagFrac(1)
} else if a.AbwesenheitTyp.WorkTime <= 0 {
return 0
}
return u.ArbeitszeitProTagFrac(float32(a.AbwesenheitTyp.WorkTime) / 100) return u.ArbeitszeitProTagFrac(float32(a.AbwesenheitTyp.WorkTime) / 100)
case WorktimeBaseWeek: case WorktimeBaseWeek:
if a.AbwesenheitTyp.WorkTime <= 0 && includeKurzarbeit {
return u.ArbeitszeitProTagFrac(0.2)
} else if a.AbwesenheitTyp.WorkTime <= 0 {
return 0
}
return u.ArbeitszeitProWocheFrac(0.2 * float32(a.AbwesenheitTyp.WorkTime) / 100) return u.ArbeitszeitProWocheFrac(0.2 * float32(a.AbwesenheitTyp.WorkTime) / 100)
} }
return 0 return 0
} }
func (a *Absence) GetPausetimeReal(u User, base WorktimeBase) time.Duration {
func (a *Absence) GetPausetime(u User, base WorktimeBase, includeKurzarbeit bool) time.Duration {
return 0 return 0
} }
func (a *Absence) GetOvertimeReal(u User, base WorktimeBase) time.Duration { func (a *Absence) GetOvertime(u User, base WorktimeBase, includeKurzarbeit bool) time.Duration {
if a.AbwesenheitTyp.WorkTime > 0 { if a.AbwesenheitTyp.WorkTime > 0 {
return 0 return 0
} }
switch base { switch base {
case WorktimeBaseDay: case WorktimeBaseDay:
return -u.ArbeitszeitProTag() return -u.ArbeitszeitProTagFrac(1)
case WorktimeBaseWeek: case WorktimeBaseWeek:
return -u.ArbeitszeitProWoche() / 5 return -u.ArbeitszeitProWocheFrac(0.2)
} }
return 0 return 0
} }
func (a *Absence) GetWorktimeVirtual(u User, base WorktimeBase) time.Duration { func (a *Absence) GetTimes(u User, base WorktimeBase, includeKurzarbeit bool) (work, pause, overtime time.Duration) {
return a.GetWorktimeReal(u, base) return a.GetWorktime(u, base, includeKurzarbeit), a.GetPausetime(u, base, includeKurzarbeit), a.GetOvertime(u, base, includeKurzarbeit)
}
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 { func (a *Absence) ToString() string {

View File

@@ -52,7 +52,7 @@ func TestCalcRealWorkTimeDayAbsence(t *testing.T) {
t.Run("Calc Absence Worktime: "+tc.absenceType.Name, func(t *testing.T) { t.Run("Calc Absence Worktime: "+tc.absenceType.Name, func(t *testing.T) {
var testCase = testAbsence var testCase = testAbsence
testCase.AbwesenheitTyp = tc.absenceType testCase.AbwesenheitTyp = tc.absenceType
workTime := testCase.GetWorktimeReal(testUser, models.WorktimeBaseDay) workTime := testCase.GetWorktime(testUser, models.WorktimeBaseDay, false)
if workTime != tc.expectedTime { 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)) 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) { t.Run("Calc Absence Worktime: "+tc.absenceType.Name, func(t *testing.T) {
var testCase = testAbsence var testCase = testAbsence
testCase.AbwesenheitTyp = tc.absenceType testCase.AbwesenheitTyp = tc.absenceType
workTime := testCase.GetWorktimeReal(testUser, models.WorktimeBaseWeek) workTime := testCase.GetWorktime(testUser, models.WorktimeBaseWeek, false)
if workTime != tc.expectedTime { 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)) t.Errorf("Calc Worktime Default not working, time should be %s, but was %s", helper.FormatDurationFill(tc.expectedTime, true), helper.FormatDurationFill(workTime, true))
} }

View File

@@ -13,14 +13,10 @@ type IWorkDay interface {
IsKurzArbeit() bool IsKurzArbeit() bool
GetDayProgress(User) int8 GetDayProgress(User) int8
RequiresAction() bool RequiresAction() bool
GetWorktimeReal(User, WorktimeBase) time.Duration GetWorktime(User, WorktimeBase, bool) time.Duration
GetPausetimeReal(User, WorktimeBase) time.Duration GetPausetime(User, WorktimeBase, bool) time.Duration
GetOvertimeReal(User, WorktimeBase) time.Duration GetTimes(User, WorktimeBase, bool) (work, pause, overtime time.Duration)
GetWorktimeVirtual(User, WorktimeBase) time.Duration GetOvertime(User, WorktimeBase, bool) time.Duration
GetPausetimeVirtual(User, WorktimeBase) time.Duration
GetOvertimeVirtual(User, WorktimeBase) time.Duration
GetTimesReal(User, WorktimeBase) (work, pause, overtime time.Duration)
GetTimesVirtual(User, WorktimeBase) (work, pause, overtime time.Duration)
} }
func GetDays(user User, tsFrom, tsTo time.Time, orderedForward bool) []IWorkDay { func GetDays(user User, tsFrom, tsTo time.Time, orderedForward bool) []IWorkDay {

View File

@@ -7,9 +7,11 @@ import (
"errors" "errors"
"fmt" "fmt"
"log" "log"
"log/slog"
"time" "time"
"github.com/alexedwards/scs/v2" "github.com/alexedwards/scs/v2"
"github.com/lib/pq"
) )
type User struct { type User struct {
@@ -61,7 +63,6 @@ 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 card_uid, vorname, nachname,arbeitszeit_per_tag, arbeitszeit_per_woche FROM s_personal_daten;`))
var users []User var users []User
if err != nil { if err != nil {
fmt.Printf("Error preparing query statement %v\n", err)
return users, err return users, err
} }
defer qStr.Close() defer qStr.Close()
@@ -89,7 +90,6 @@ func (u *User) GetAll() ([]User, error) {
qStr, err := DB.Prepare((`SELECT card_uid, vorname, nachname FROM s_personal_daten;`)) qStr, err := DB.Prepare((`SELECT card_uid, vorname, nachname FROM s_personal_daten;`))
var users []User var users []User
if err != nil { if err != nil {
fmt.Printf("Error preparing query statement %v\n", err)
return users, err return users, err
} }
defer qStr.Close() defer qStr.Close()
@@ -135,7 +135,7 @@ func (u *User) ArbeitszeitProWocheFrac(fraction float32) time.Duration {
func (u *User) CheckAnwesenheit() bool { func (u *User) CheckAnwesenheit() bool {
qStr, err := DB.Prepare((`SELECT check_in_out FROM anwesenheit WHERE card_uid = $1 AND "timestamp"::date = now()::date ORDER BY "timestamp" DESC LIMIT 1;`)) qStr, err := DB.Prepare((`SELECT check_in_out FROM anwesenheit WHERE card_uid = $1 AND "timestamp"::date = now()::date ORDER BY "timestamp" DESC LIMIT 1;`))
if err != nil { if err != nil {
fmt.Printf("Error preparing query statement %v\n", err) slog.Debug("Error preparing query statement.", "error", err)
return false return false
} }
defer qStr.Close() defer qStr.Close()
@@ -173,11 +173,43 @@ func GetUserByPersonalNr(personalNummer int) (User, error) {
return user, nil 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 { func (u *User) Login(password string) bool {
var loginSuccess 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;`)) qStr, err := DB.Prepare((`SELECT (pass_hash = crypt($2, pass_hash)) AS pass_hash FROM user_password WHERE personal_nummer = $1;`))
if err != nil { if err != nil {
log.Println("Error preparing db statement", err) slog.Debug("Error preparing query statement.", "error", err)
return false return false
} }
defer qStr.Close() defer qStr.Close()
@@ -269,7 +301,7 @@ func (u *User) GetLastWorkWeekSubmission() time.Time {
) AS letzte_buchung; ) AS letzte_buchung;
`) `)
if err != nil { if err != nil {
log.Println("Error preparing statement!", err) slog.Debug("Error preparing query statement.", "error", err)
return lastSub return lastSub
} }
err = qStr.QueryRow(u.PersonalNummer, u.CardUID).Scan(&lastSub) err = qStr.QueryRow(u.PersonalNummer, u.CardUID).Scan(&lastSub)

View File

@@ -30,31 +30,33 @@ const (
WorktimeBaseDay WorktimeBase = "day" WorktimeBaseDay WorktimeBase = "day"
) )
func (d *WorkDay) GetWorktimeAbsence() Absence {
return d.worktimeAbsece
}
// Gets the time as is in the db (with corrected pause times) // Gets the time as is in the db (with corrected pause times)
func (d *WorkDay) GetWorktimeReal(u User, base WorktimeBase) time.Duration { func (d *WorkDay) GetWorktime(u User, base WorktimeBase, includeKurzarbeit bool) time.Duration {
if includeKurzarbeit && d.IsKurzArbeit() && len(d.Bookings) > 0 {
return d.kurzArbeitAbsence.GetWorktime(u, base, true)
}
work, pause := calcWorkPause(d.Bookings) work, pause := calcWorkPause(d.Bookings)
work, pause = correctWorkPause(work, pause) work, pause = correctWorkPause(work, pause)
if (d.worktimeAbsece != Absence{}) { if (d.worktimeAbsece != Absence{}) {
work += d.worktimeAbsece.GetWorktimeReal(u, WorktimeBaseDay) work += d.worktimeAbsece.GetWorktime(u, base, false)
} }
return work.Round(time.Minute) return work.Round(time.Minute)
} }
// Gets the corrected pause times based on db entries // Gets the corrected pause times based on db entries
func (d *WorkDay) GetPausetimeReal(u User, base WorktimeBase) time.Duration { func (d *WorkDay) GetPausetime(u User, base WorktimeBase, includeKurzarbeit bool) time.Duration {
work, pause := calcWorkPause(d.Bookings) work, pause := calcWorkPause(d.Bookings)
work, pause = correctWorkPause(work, pause) work, pause = correctWorkPause(work, pause)
return pause.Round(time.Minute) return pause.Round(time.Minute)
} }
// Returns the overtime based on the db entries // Returns the overtime based on the db entries
func (d *WorkDay) GetOvertimeReal(u User, base WorktimeBase) time.Duration { func (d *WorkDay) GetOvertime(u User, base WorktimeBase, includeKurzarbeit bool) time.Duration {
work, pause := calcWorkPause(d.Bookings) work := d.GetWorktime(u, base, includeKurzarbeit)
work, pause = correctWorkPause(work, pause)
if (d.worktimeAbsece != Absence{}) {
work += d.worktimeAbsece.GetWorktimeReal(u, base)
}
var targetHours time.Duration var targetHours time.Duration
switch base { switch base {
case WorktimeBaseDay: case WorktimeBaseDay:
@@ -65,44 +67,8 @@ func (d *WorkDay) GetOvertimeReal(u User, base WorktimeBase) time.Duration {
return (work - targetHours).Round(time.Minute) return (work - targetHours).Round(time.Minute)
} }
// Returns the worktime based on absence or kurzarbeit func (d *WorkDay) GetTimes(u User, base WorktimeBase, includeKurzarbeit bool) (work, pause, overtime time.Duration) {
func (d *WorkDay) GetWorktimeVirtual(u User, base WorktimeBase) time.Duration { return d.GetWorktime(u, base, includeKurzarbeit), d.GetPausetime(u, base, includeKurzarbeit), d.GetOvertime(u, base, includeKurzarbeit)
if !d.IsKurzArbeit() {
return d.GetWorktimeReal(u, base)
}
switch base {
case WorktimeBaseDay:
return u.ArbeitszeitProTag()
case WorktimeBaseWeek:
return u.ArbeitszeitProWocheFrac(0.2)
default:
return 0
}
}
func (d *WorkDay) GetPausetimeVirtual(u User, base WorktimeBase) time.Duration {
return d.GetPausetimeReal(u, base)
}
func (d *WorkDay) GetOvertimeVirtual(u User, base WorktimeBase) time.Duration {
work := d.GetWorktimeVirtual(u, base)
var targetHours time.Duration
switch base {
case WorktimeBaseDay:
targetHours = u.ArbeitszeitProTag()
case WorktimeBaseWeek:
targetHours = u.ArbeitszeitProWocheFrac(0.2)
}
return (work - targetHours).Round(time.Minute)
}
func (d *WorkDay) GetTimesReal(u User, base WorktimeBase) (work, pause, overtime time.Duration) {
return d.GetWorktimeReal(u, base), d.GetPausetimeReal(u, base), d.GetOvertimeReal(u, base)
}
func (d *WorkDay) GetTimesVirtual(u User, base WorktimeBase) (work, pause, overtime time.Duration) {
return d.GetWorktimeVirtual(u, base), d.GetPausetimeVirtual(u, base), d.GetOvertimeVirtual(u, base)
} }
func calcWorkPause(bookings []Booking) (work, pause time.Duration) { func calcWorkPause(bookings []Booking) (work, pause time.Duration) {
@@ -162,12 +128,12 @@ func (d *WorkDay) Date() time.Time {
func (d *WorkDay) GenerateKurzArbeitBookings(u User) (time.Time, time.Time) { func (d *WorkDay) GenerateKurzArbeitBookings(u User) (time.Time, time.Time) {
var timeFrom, timeTo time.Time var timeFrom, timeTo time.Time
if d.workTime >= u.ArbeitszeitProTag() { if d.GetWorktime(u, WorktimeBaseDay, false) >= u.ArbeitszeitProTag() {
return timeFrom, timeTo return timeFrom, timeTo
} }
timeFrom = d.Bookings[len(d.Bookings)-1].Timestamp.Add(time.Minute) 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()) slog.Debug("Added duration as Kurzarbeit", "date", d.Date().String(), "duration", timeTo.Sub(timeFrom).String())
return timeFrom, timeTo return timeFrom, timeTo
@@ -307,7 +273,7 @@ func (d *WorkDay) GetDayProgress(u User) int8 {
if d.RequiresAction() { if d.RequiresAction() {
return -1 return -1
} }
workTime := d.GetWorktimeVirtual(u, WorktimeBaseDay) workTime := d.GetWorktime(u, WorktimeBaseDay, true)
progress := (workTime.Seconds() / u.ArbeitszeitProTag().Seconds()) * 100 progress := (workTime.Seconds() / u.ArbeitszeitProTag().Seconds()) * 100
return int8(progress) return int8(progress)
} }

View File

@@ -49,7 +49,7 @@ func TestWorkdayWorktimeDay(t *testing.T) {
t.Run("Calc Absence Worktime: "+tc.testName, func(t *testing.T) { t.Run("Calc Absence Worktime: "+tc.testName, func(t *testing.T) {
var testCase = testWorkDay var testCase = testWorkDay
testCase.Bookings = tc.bookings testCase.Bookings = tc.bookings
workTime := testCase.GetWorktimeReal(testUser, models.WorktimeBaseDay) workTime := testCase.GetWorktime(testUser, models.WorktimeBaseDay, false)
if workTime != tc.expectedTime { if workTime != tc.expectedTime {
t.Errorf("GetWorktimeReal not working, time should be %s, but was %s", helper.FormatDurationFill(tc.expectedTime, true), helper.FormatDurationFill(workTime, true)) t.Errorf("GetWorktimeReal not working, time should be %s, but was %s", helper.FormatDurationFill(tc.expectedTime, true), helper.FormatDurationFill(workTime, true))
} }
@@ -84,7 +84,7 @@ func TestWorkdayWorktimeWeek(t *testing.T) {
t.Run("Calc Absence Worktime: "+tc.testName, func(t *testing.T) { t.Run("Calc Absence Worktime: "+tc.testName, func(t *testing.T) {
var testCase = testWorkDay var testCase = testWorkDay
testCase.Bookings = tc.bookings testCase.Bookings = tc.bookings
workTime := testCase.GetWorktimeReal(testUser, models.WorktimeBaseWeek) workTime := testCase.GetWorktime(testUser, models.WorktimeBaseWeek, false)
if workTime != tc.expectedTime { if workTime != tc.expectedTime {
t.Errorf("GetWorktimeReal not working, time should be %s, but was %s", helper.FormatDurationFill(tc.expectedTime, true), helper.FormatDurationFill(workTime, true)) t.Errorf("GetWorktimeReal not working, time should be %s, but was %s", helper.FormatDurationFill(tc.expectedTime, true), helper.FormatDurationFill(workTime, true))
} }
@@ -119,7 +119,7 @@ func TestWorkdayPausetimeDay(t *testing.T) {
t.Run("Calc Absence Worktime: "+tc.testName, func(t *testing.T) { t.Run("Calc Absence Worktime: "+tc.testName, func(t *testing.T) {
var testCase = testWorkDay var testCase = testWorkDay
testCase.Bookings = tc.bookings testCase.Bookings = tc.bookings
workTime := testCase.GetPausetimeReal(testUser, models.WorktimeBaseDay) workTime := testCase.GetPausetime(testUser, models.WorktimeBaseDay, false)
if workTime != tc.expectedTime { if workTime != tc.expectedTime {
t.Errorf("GetPausetimeReal not working, time should be %s, but was %s", helper.FormatDurationFill(tc.expectedTime, true), helper.FormatDurationFill(workTime, true)) t.Errorf("GetPausetimeReal not working, time should be %s, but was %s", helper.FormatDurationFill(tc.expectedTime, true), helper.FormatDurationFill(workTime, true))
} }
@@ -154,7 +154,7 @@ func TestWorkdayPausetimeWeek(t *testing.T) {
t.Run("Calc Absence Worktime: "+tc.testName, func(t *testing.T) { t.Run("Calc Absence Worktime: "+tc.testName, func(t *testing.T) {
var testCase = testWorkDay var testCase = testWorkDay
testCase.Bookings = tc.bookings testCase.Bookings = tc.bookings
workTime := testCase.GetPausetimeReal(testUser, models.WorktimeBaseWeek) workTime := testCase.GetPausetime(testUser, models.WorktimeBaseWeek, false)
if workTime != tc.expectedTime { if workTime != tc.expectedTime {
t.Errorf("GetPausetimeReal not working, time should be %s, but was %s", helper.FormatDurationFill(tc.expectedTime, true), helper.FormatDurationFill(workTime, true)) t.Errorf("GetPausetimeReal not working, time should be %s, but was %s", helper.FormatDurationFill(tc.expectedTime, true), helper.FormatDurationFill(workTime, true))
} }

View File

@@ -20,7 +20,7 @@ type WorkWeek struct {
User User User User
WeekStart time.Time WeekStart time.Time
Worktime time.Duration Worktime time.Duration
WorkTimeVirtual time.Duration WorktimeVirtual time.Duration
Overtime time.Duration Overtime time.Duration
Status WeekStatus Status WeekStatus
} }
@@ -52,14 +52,14 @@ func (w *WorkWeek) PopulateWithDays(worktime time.Duration, overtime time.Durati
w.Days = GetDays(w.User, w.WeekStart, w.WeekStart.Add(6*24*time.Hour), false) w.Days = GetDays(w.User, w.WeekStart, w.WeekStart.Add(6*24*time.Hour), false)
for _, day := range w.Days { for _, day := range w.Days {
w.Worktime += day.GetWorktimeReal(w.User, WorktimeBaseDay) w.Worktime += day.GetWorktime(w.User, WorktimeBaseDay, false)
w.WorkTimeVirtual += day.GetWorktimeVirtual(w.User, WorktimeBaseDay) w.WorktimeVirtual += day.GetWorktime(w.User, WorktimeBaseDay, true)
} }
slog.Debug("Got worktime for user", "worktime", w.Worktime.String(), "virtualWorkTime", w.WorkTimeVirtual.String()) slog.Debug("Got worktime for user", "worktime", w.Worktime.String(), "virtualWorkTime", w.WorktimeVirtual.String())
w.Overtime = w.WorkTimeVirtual - w.User.ArbeitszeitProWoche() w.Overtime = w.WorktimeVirtual - w.User.ArbeitszeitProWoche()
slog.Debug("Calculated overtime", "worktime", w.Worktime.String(), "virtualWorkTime", w.WorkTimeVirtual.String()) slog.Debug("Calculated overtime", "worktime", w.Worktime.String(), "virtualWorkTime", w.WorktimeVirtual.String())
w.Worktime = w.Worktime.Round(time.Minute) w.Worktime = w.Worktime.Round(time.Minute)
w.Overtime = w.Overtime.Round(time.Minute) w.Overtime = w.Overtime.Round(time.Minute)

View File

@@ -36,7 +36,7 @@ templ defaultWeekDayComponent(u models.User, day models.IWorkDay) {
if day.IsWorkDay() { if day.IsWorkDay() {
{{ {{
workDay, _ := day.(*models.WorkDay) workDay, _ := day.(*models.WorkDay)
work, pause, _ := workDay.GetTimesReal(u, models.WorktimeBaseDay) work, pause, _ := workDay.GetTimes(u, models.WorktimeBaseDay, false)
}} }}
if !workDay.RequiresAction() { if !workDay.RequiresAction() {
<div class="flex flex-row gap-2"> <div class="flex flex-row gap-2">
@@ -81,7 +81,7 @@ templ weekDayComponent(user models.User, day models.WorkDay) {
templ workWeekComponent(week models.WorkWeek, onlyAccept bool) { templ workWeekComponent(week models.WorkWeek, onlyAccept bool) {
{{ {{
year, kw := week.WeekStart.ISOWeek() year, kw := week.WeekStart.ISOWeek()
progress := (float32(week.WorkTimeVirtual.Hours()) / week.User.ArbeitszeitPerWoche) * 100 progress := (float32(week.WorktimeVirtual.Hours()) / week.User.ArbeitszeitPerWoche) * 100
}} }}
<div class="employeComponent grid-sub responsive lg:divide-x-1 max-md:divide-y-1 @container"> <div class="employeComponent grid-sub responsive lg:divide-x-1 max-md:divide-y-1 @container">
<div class="grid-cell flex flex-col max-md:bg-neutral-300 gap-2"> <div class="grid-cell flex flex-col max-md:bg-neutral-300 gap-2">

View File

@@ -179,7 +179,7 @@ func defaultWeekDayComponent(u models.User, day models.IWorkDay) templ.Component
if day.IsWorkDay() { if day.IsWorkDay() {
workDay, _ := day.(*models.WorkDay) workDay, _ := day.(*models.WorkDay)
work, pause, _ := workDay.GetTimesReal(u, models.WorktimeBaseDay) work, pause, _ := workDay.GetTimes(u, models.WorktimeBaseDay, false)
if !workDay.RequiresAction() { if !workDay.RequiresAction() {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<div class=\"flex flex-row gap-2\"><span class=\"text-accent\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<div class=\"flex flex-row gap-2\"><span class=\"text-accent\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -346,7 +346,7 @@ func workWeekComponent(week models.WorkWeek, onlyAccept bool) templ.Component {
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
year, kw := week.WeekStart.ISOWeek() year, kw := week.WeekStart.ISOWeek()
progress := (float32(week.WorkTimeVirtual.Hours()) / week.User.ArbeitszeitPerWoche) * 100 progress := (float32(week.WorktimeVirtual.Hours()) / week.User.ArbeitszeitPerWoche) * 100
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "<div class=\"employeComponent grid-sub responsive lg:divide-x-1 max-md:divide-y-1 @container\"><div class=\"grid-cell flex flex-col max-md:bg-neutral-300 gap-2\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "<div class=\"employeComponent grid-sub responsive lg:divide-x-1 max-md:divide-y-1 @container\"><div class=\"grid-cell flex flex-col max-md:bg-neutral-300 gap-2\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err

View File

@@ -103,8 +103,8 @@ templ defaultDayComponent(day models.IWorkDay) {
if day.IsWorkDay() { if day.IsWorkDay() {
{{ {{
workDay, _ := day.(*models.WorkDay) workDay, _ := day.(*models.WorkDay)
work, pause, overtime := workDay.GetTimesVirtual(user, models.WorktimeBaseDay) work, pause, overtime := workDay.GetTimes(user, models.WorktimeBaseDay, true)
work = workDay.GetWorktimeReal(user, models.WorktimeBaseDay) work = workDay.GetWorktime(user, models.WorktimeBaseDay, false)
}} }}
if day.RequiresAction() { if day.RequiresAction() {
<p class="text-red-600">Bitte anpassen</p> <p class="text-red-600">Bitte anpassen</p>
@@ -137,7 +137,7 @@ templ defaultDayComponent(day models.IWorkDay) {
if len(workDay.Bookings) < 1 { if len(workDay.Bookings) < 1 {
<p class="text group-[.edit]:hidden">Keine Buchung gefunden. Bitte Arbeitsstunden oder Grund der Abwesenheit eingeben!</p> <p class="text group-[.edit]:hidden">Keine Buchung gefunden. Bitte Arbeitsstunden oder Grund der Abwesenheit eingeben!</p>
} }
if workDay.IsKurzArbeit() { if workDay.IsKurzArbeit() && len(workDay.Bookings) > 0 {
@absenceComponent(workDay.GetKurzArbeit(), true) @absenceComponent(workDay.GetKurzArbeit(), true)
} }
for _, booking := range workDay.Bookings { for _, booking := range workDay.Bookings {

View File

@@ -297,8 +297,8 @@ func defaultDayComponent(day models.IWorkDay) templ.Component {
if day.IsWorkDay() { if day.IsWorkDay() {
workDay, _ := day.(*models.WorkDay) workDay, _ := day.(*models.WorkDay)
work, pause, overtime := workDay.GetTimesVirtual(user, models.WorktimeBaseDay) work, pause, overtime := workDay.GetTimes(user, models.WorktimeBaseDay, true)
work = workDay.GetWorktimeReal(user, models.WorktimeBaseDay) work = workDay.GetWorktime(user, models.WorktimeBaseDay, false)
if day.RequiresAction() { if day.RequiresAction() {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "<p class=\"text-red-600\">Bitte anpassen</p>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "<p class=\"text-red-600\">Bitte anpassen</p>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -436,7 +436,7 @@ func defaultDayComponent(day models.IWorkDay) templ.Component {
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err 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) templ_7745c5c3_Err = absenceComponent(workDay.GetKurzArbeit(), true).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err

View File

@@ -41,27 +41,31 @@
fill: (x, y) => fill: (x, y) =>
if y == 0 { oklch(87%, 0, 0deg) }, if y == 0 { oklch(87%, 0, 0deg) },
table-header( table-header(
[Datum], [Kommen], [Gehen], [Arbeitsart], [Stunden], [Pause], [Überstunden] [Datum], [Kommen], [Gehen], [Arbeitsart], [Stunden], [Kurzarbeit], [Pause], [Überstunden]
), ),
.. for day in days { .. for day in days {
( (
[#day.Date], [#day.Date],
if day.DayParts.len() == 0{ if day.DayParts.len() == 0{
table.cell(colspan: 3)[Keine Buchungen] 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] table.cell(colspan: 3)[#day.DayParts.first().WorkType]
} }
else { else {
table.cell(colspan: 3, inset: 0em)[ table.cell(colspan: 3, inset: 0em)[
#table( #table(
columns: (1fr, 1fr, 1fr), columns: (1fr, 1fr, 1fr),
.. for Zeit in day.DayParts { .. for Zeit in day.DayParts {
( (
[#Zeit.BookingFrom], if Zeit.IsWorkDay{
[#Zeit.BookingTo], (
[#Zeit.WorkType], 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, stroke: none,
table.hline(start: 0, end: 2, stroke: stroke(dash:"dashed", thickness:.5pt)), table.hline(start: 0, end: 2, stroke: stroke(dash:"dashed", thickness:.5pt)),
[Arbeitszeit :], table.cell(align: left)[#meta.WorkTime], [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.Overtime],
[Überstunden :],table.cell(align: left)[#meta.OvertimeTotal], [Überstunden lfd. :],table.cell(align: left)[#meta.OvertimeTotal],
table.hline(start: 0, end: 2), table.hline(start: 0, end: 2),
) )
} }