From f73c2b1a962285092774164b002737758b02db92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Tr=C3=B6ger?= Date: Fri, 5 Dec 2025 15:03:34 +0100 Subject: [PATCH 1/5] tried to add untertags krank --- .gitea/workflows/build.yaml | 4 +- Backend/endpoints/pdf-create.go | 19 +- .../{team_presence.go => team-presence.go} | 0 Backend/endpoints/time.go | 2 +- Backend/models/absence.go | 8 +- Backend/models/absence_test.go | 92 ++++ Backend/models/booking_test.go | 12 +- Backend/models/iworkday.go | 59 +++ Backend/models/user.go | 14 +- Backend/models/user_test.go | 2 +- Backend/models/workDay.go | 165 +------ Backend/models/workDay_test.go | 184 +++++--- Backend/models/workWeek.go | 10 +- Backend/templates/pdf.templ | 129 +++--- Backend/templates/pdf_templ.go | 428 ++++-------------- Backend/templates/teamComponents.templ | 2 +- Backend/templates/teamComponents_templ.go | 2 +- Backend/templates/timePage.templ | 3 +- Backend/templates/timePage_templ.go | 19 +- DB/initdb/01_schema.sql | 2 +- DocumentCreator/templates/abrechnung.typ | 5 +- 21 files changed, 508 insertions(+), 653 deletions(-) rename Backend/endpoints/{team_presence.go => team-presence.go} (100%) create mode 100644 Backend/models/absence_test.go create mode 100644 Backend/models/iworkday.go diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml index a73ba14..e112b79 100644 --- a/.gitea/workflows/build.yaml +++ b/.gitea/workflows/build.yaml @@ -73,5 +73,5 @@ jobs: push: true context: Backend tags: | - git.letsstein.de/tom/arbeitszeitmessung:latest - git.letsstein.de/tom/arbeitszeitmessung:${{ github.ref_name }} + git.letsstein.de/tom/arbeitszeitmessung-webserver:latest + git.letsstein.de/tom/arbeitszeitmessung-webserver:${{ github.ref_name }} diff --git a/Backend/endpoints/pdf-create.go b/Backend/endpoints/pdf-create.go index 7d1ae45..b70ea52 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.GetTimesReal(u, models.WorktimeBaseWeek) + work, pause, overtime := day.GetTimesVirtual(u, models.WorktimeBaseWeek) thisTypstDay.Date = day.Date().Format(DE_DATE) thisTypstDay.Worktime = helper.FormatDurationFill(work, true) thisTypstDay.Pausetime = helper.FormatDurationFill(pause, true) @@ -45,6 +45,16 @@ 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}) @@ -122,7 +132,7 @@ func createEmployeReport(employee models.User, startDate, endDate time.Time) (by var actualHours time.Duration for _, day := range workingDays { - actualHours += day.TimeWorkVirtual(employee) + actualHours += day.GetWorktimeVirtual(employee, models.WorktimeBaseDay) } worktimeBalance := actualHours - targetHours @@ -162,8 +172,8 @@ func PDFHandler(w http.ResponseWriter, r *http.Request) { weeks := models.GetDays(user, startDate, endDate, false) var aggregatedOvertime, aggregatedWorkTime time.Duration for _, day := range weeks { - aggregatedOvertime += day.TimeOvertimeReal(user) - aggregatedWorkTime += day.TimeWorkVirtual(user) + aggregatedOvertime += day.GetOvertimeReal(user, models.WorktimeBaseWeek) + aggregatedWorkTime += day.GetWorktimeVirtual(user, models.WorktimeBaseWeek) } typstDays, err := convertDaysToTypst(weeks, user) @@ -193,6 +203,7 @@ type typstMetadata struct { TimeRange string `json:"time-range"` EmployeeName string `json:"employee-name"` WorkTime string `json:"worktime"` + Kurzarbeit string `json:"kurzarbeit"` Overtime string `json:"overtime"` OvertimeTotal string `json:"overtime-total"` CurrentTimestamp string `json:"current-timestamp"` diff --git a/Backend/endpoints/team_presence.go b/Backend/endpoints/team-presence.go similarity index 100% rename from Backend/endpoints/team_presence.go rename to Backend/endpoints/team-presence.go diff --git a/Backend/endpoints/time.go b/Backend/endpoints/time.go index d8bb4c3..a3cf760 100644 --- a/Backend/endpoints/time.go +++ b/Backend/endpoints/time.go @@ -84,7 +84,7 @@ func getBookings(w http.ResponseWriter, r *http.Request) { if day.Date().Before(lastSub) { continue } - aggregatedOvertime += day.TimeOvertimeReal(user) + aggregatedOvertime += day.GetOvertimeReal(user, models.WorktimeBaseDay) } if reportedOvertime, err := user.GetReportedOvertime(); err == nil { user.Overtime = (reportedOvertime + aggregatedOvertime).Round(time.Minute) diff --git a/Backend/models/absence.go b/Backend/models/absence.go index 0f99bbb..493261e 100644 --- a/Backend/models/absence.go +++ b/Backend/models/absence.go @@ -50,14 +50,14 @@ func (a *Absence) IsMultiDay() bool { } func (a *Absence) GetWorktimeReal(u User, base WorktimeBase) time.Duration { - if a.AbwesenheitTyp.WorkTime <= 1 { + if a.AbwesenheitTyp.WorkTime <= 0 { return 0 } switch base { case WorktimeBaseDay: - return u.ArbeitszeitProTag() + return u.ArbeitszeitProTagFrac(float32(a.AbwesenheitTyp.WorkTime) / 100) case WorktimeBaseWeek: - return u.ArbeitszeitProWoche() / 5 + return u.ArbeitszeitProWocheFrac(0.2 * float32(a.AbwesenheitTyp.WorkTime) / 100) } return 0 } @@ -66,7 +66,7 @@ func (a *Absence) GetPausetimeReal(u User, base WorktimeBase) time.Duration { } func (a *Absence) GetOvertimeReal(u User, base WorktimeBase) time.Duration { - if a.AbwesenheitTyp.WorkTime > 1 { + if a.AbwesenheitTyp.WorkTime > 0 { return 0 } switch base { diff --git a/Backend/models/absence_test.go b/Backend/models/absence_test.go new file mode 100644 index 0000000..7e40dea --- /dev/null +++ b/Backend/models/absence_test.go @@ -0,0 +1,92 @@ +package models_test + +import ( + "arbeitszeitmessung/helper" + "arbeitszeitmessung/models" + "testing" + "time" +) + +var testAbsence = models.Absence{ + Day: CatchError(time.Parse(time.DateOnly, "2025-01-01")), + AbwesenheitTyp: models.AbsenceType{}, + DateFrom: CatchError(time.Parse(time.DateOnly, "2025-01-01")), + DateTo: CatchError(time.Parse(time.DateOnly, "2025-01-03")), +} + +var testKurzarbeit = models.AbsenceType{ + Name: "Kurzarbeit", + WorkTime: -1, +} + +var testUrlaub = models.AbsenceType{ + Name: "Urlaub", + WorkTime: 100, +} + +var testUrlaubUntertags = models.AbsenceType{ + Name: "Urlaub untertags", + WorkTime: 50, +} + +func TestCalcRealWorkTimeDayAbsence(t *testing.T) { + testCases := []struct { + absenceType models.AbsenceType + expectedTime time.Duration + }{ + { + absenceType: testUrlaub, + expectedTime: time.Hour * 8, + }, + { + absenceType: testUrlaubUntertags, + expectedTime: time.Hour * 4, + }, + { + absenceType: testKurzarbeit, + expectedTime: 0, + }, + } + + for _, tc := range testCases { + 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) + 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)) + } + }) + } +} + +func TestCalcRealWorkTimeWeekAbsence(t *testing.T) { + testCases := []struct { + absenceType models.AbsenceType + expectedTime time.Duration + }{ + { + absenceType: testUrlaub, + expectedTime: time.Hour * 7, + }, + { + absenceType: testUrlaubUntertags, + expectedTime: time.Hour*3 + time.Minute*30, + }, + { + absenceType: testKurzarbeit, + expectedTime: 0, + }, + } + + for _, tc := range testCases { + 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) + 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/booking_test.go b/Backend/models/booking_test.go index 032c59e..c5f3e67 100644 --- a/Backend/models/booking_test.go +++ b/Backend/models/booking_test.go @@ -10,36 +10,36 @@ var testBookingType = models.BookingType{ Name: "Büro", } -var testBookings8hrs = []models.Booking{models.Booking{ +var testBookings8hrs = []models.Booking{{ CardUID: "aaaa-aaaa", CheckInOut: 1, Timestamp: CatchError(time.Parse("2006-01-02 15:04", "2025-01-01 08:00")), BookingType: testBookingType, -}, models.Booking{ +}, { CardUID: "aaaa-aaaa", CheckInOut: 2, Timestamp: CatchError(time.Parse("2006-01-02 15:04", "2025-01-01 16:00")), BookingType: testBookingType, }} -var testBookings6hrs = []models.Booking{models.Booking{ +var testBookings6hrs = []models.Booking{{ CardUID: "aaaa-aaaa", CheckInOut: 1, Timestamp: CatchError(time.Parse("2006-01-02 15:04", "2025-01-01 08:00")), BookingType: testBookingType, -}, models.Booking{ +}, { CardUID: "aaaa-aaaa", CheckInOut: 2, Timestamp: CatchError(time.Parse("2006-01-02 15:04", "2025-01-01 14:00")), BookingType: testBookingType, }} -var testBookings10hrs = []models.Booking{models.Booking{ +var testBookings10hrs = []models.Booking{{ CardUID: "aaaa-aaaa", CheckInOut: 1, Timestamp: CatchError(time.Parse("2006-01-02 15:04", "2025-01-01 08:00")), BookingType: testBookingType, -}, models.Booking{ +}, { CardUID: "aaaa-aaaa", CheckInOut: 2, Timestamp: CatchError(time.Parse("2006-01-02 15:04", "2025-01-01 18:00")), diff --git a/Backend/models/iworkday.go b/Backend/models/iworkday.go new file mode 100644 index 0000000..cf0344e --- /dev/null +++ b/Backend/models/iworkday.go @@ -0,0 +1,59 @@ +package models + +import ( + "arbeitszeitmessung/helper" + "log" + "time" +) + +type IWorkDay interface { + Date() time.Time + ToString() string + IsWorkDay() bool + IsKurzArbeit() bool + GetDayProgress(User) int8 + RequiresAction() bool + GetWorktimeReal(User, WorktimeBase) time.Duration + GetPausetimeReal(User, WorktimeBase) time.Duration + GetOvertimeReal(User, WorktimeBase) time.Duration + GetWorktimeVirtual(User, WorktimeBase) 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 { + var allDays map[string]IWorkDay = make(map[string]IWorkDay) + + for _, day := range GetWorkDays(user, tsFrom, tsTo) { + allDays[day.Date().Format(time.DateOnly)] = &day + } + absences, err := GetAbsencesByCardUID(user.CardUID, tsFrom, tsTo) + if err != nil { + log.Println("Error gettings absences for all Days!", err) + return nil + } + for _, day := range absences { + if helper.IsWeekend(day.Date()) { + continue + } + // Absence should be integrated in workday + switch { + case day.AbwesenheitTyp.WorkTime < 0: + if workDay, ok := allDays[day.Date().Format(time.DateOnly)].(*WorkDay); ok { + workDay.kurzArbeit = true + workDay.kurzArbeitAbsence = day + } + case day.AbwesenheitTyp.WorkTime < 100: + if workDay, ok := allDays[day.Date().Format(time.DateOnly)].(*WorkDay); ok { + workDay.worktimeAbsece = day + } + default: + allDays[day.Date().Format(time.DateOnly)] = &day + } + } + + sortedDays := sortDays(allDays, orderedForward) + return sortedDays +} diff --git a/Backend/models/user.go b/Backend/models/user.go index 646a561..8f654e0 100644 --- a/Backend/models/user.go +++ b/Backend/models/user.go @@ -113,13 +113,21 @@ func (u *User) GetAll() ([]User, error) { return users, nil } -// Returns the worktime per day rounded to minutes func (u *User) ArbeitszeitProTag() time.Duration { - return time.Duration(u.ArbeitszeitPerTag * float32(time.Hour)).Round(time.Minute) + return u.ArbeitszeitProTagFrac(1) +} + +// Returns the worktime per day rounded to minutes +func (u *User) ArbeitszeitProTagFrac(fraction float32) time.Duration { + return time.Duration(u.ArbeitszeitPerTag * float32(time.Hour) * fraction).Round(time.Minute) } func (u *User) ArbeitszeitProWoche() time.Duration { - return time.Duration(u.ArbeitszeitPerWoche * float32(time.Hour)).Round(time.Minute) + return u.ArbeitszeitProWocheFrac(1) +} + +func (u *User) ArbeitszeitProWocheFrac(fraction float32) time.Duration { + return time.Duration(u.ArbeitszeitPerWoche * float32(time.Hour) * fraction).Round(time.Minute) } // Returns true if there is a booking 1 for today -> meaning the user is at work diff --git a/Backend/models/user_test.go b/Backend/models/user_test.go index c214625..0fe0a71 100644 --- a/Backend/models/user_test.go +++ b/Backend/models/user_test.go @@ -6,7 +6,7 @@ import ( "testing" ) -var testUser models.User = models.User{Vorname: "Kim", Name: "Mustermensch", PersonalNummer: 456, CardUID: "aaaa-aaaa", ArbeitszeitPerTag: 8, ArbeitszeitPerWoche: 40} +var testUser models.User = models.User{Vorname: "Kim", Name: "Mustermensch", PersonalNummer: 456, CardUID: "aaaa-aaaa", ArbeitszeitPerTag: 8, ArbeitszeitPerWoche: 35} func SetupUserFixture(t *testing.T, db models.IDatabase) { t.Helper() diff --git a/Backend/models/workDay.go b/Backend/models/workDay.go index b7d3254..adf8895 100644 --- a/Backend/models/workDay.go +++ b/Backend/models/workDay.go @@ -10,39 +10,17 @@ import ( "time" ) -type IWorkDay interface { - Date() time.Time - TimeWorkVirtual(User) time.Duration - TimeWorkReal(User) time.Duration - TimePauseReal(User) (work, pause time.Duration) - TimeOvertimeReal(User) time.Duration - GetAllWorkTimesVirtual(User) (work, pause, overtime time.Duration) - ToString() string - IsWorkDay() bool - IsKurzArbeit() bool - GetDayProgress(User) int8 - RequiresAction() bool - GetWorktimeReal(User, WorktimeBase) time.Duration - GetPausetimeReal(User, WorktimeBase) time.Duration - GetOvertimeReal(User, WorktimeBase) time.Duration - GetWorktimeVirtual(User, WorktimeBase) 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) -} - type WorkDay struct { Day time.Time `json:"day"` Bookings []Booking `json:"bookings"` workTime time.Duration pauseTime time.Duration - realWorkTime time.Duration - realPauseTime time.Duration TimeFrom time.Time TimeTo time.Time kurzArbeit bool kurzArbeitAbsence Absence + // Urlaub untertags + worktimeAbsece Absence } type WorktimeBase string @@ -52,62 +30,39 @@ const ( WorktimeBaseDay WorktimeBase = "day" ) -func GetDays(user User, tsFrom, tsTo time.Time, orderedForward bool) []IWorkDay { - var allDays map[string]IWorkDay = make(map[string]IWorkDay) - - for _, day := range GetWorkDays(user, tsFrom, tsTo) { - allDays[day.Date().Format(time.DateOnly)] = &day - } - absences, err := GetAbsencesByCardUID(user.CardUID, tsFrom, tsTo) - if err != nil { - log.Println("Error gettings absences for all Days!", err) - return nil - } - for _, day := range absences { - if helper.IsWeekend(day.Date()) { - continue - } - if day.AbwesenheitTyp.WorkTime == 1 { - if workDay, ok := allDays[day.Date().Format(time.DateOnly)].(*WorkDay); ok && len(workDay.Bookings) > 0 { - workDay.kurzArbeit = true - workDay.kurzArbeitAbsence = day - } - } else { - allDays[day.Date().Format(time.DateOnly)] = &day - } - } - - sortedDays := sortDays(allDays, orderedForward) - return sortedDays -} - // Gets the time as is in the db (with corrected pause times) func (d *WorkDay) GetWorktimeReal(u User, base WorktimeBase) time.Duration { work, pause := calcWorkPause(d.Bookings) work, pause = correctWorkPause(work, pause) - return work + if (d.worktimeAbsece != Absence{}) { + work += d.worktimeAbsece.GetWorktimeReal(u, WorktimeBaseDay) + } + return work.Round(time.Minute) } // Gets the corrected pause times based on db entries func (d *WorkDay) GetPausetimeReal(u User, base WorktimeBase) time.Duration { work, pause := calcWorkPause(d.Bookings) work, pause = correctWorkPause(work, pause) - return pause + return pause.Round(time.Minute) } // Returns the overtime based on the db entries func (d *WorkDay) GetOvertimeReal(u User, base WorktimeBase) time.Duration { work, pause := calcWorkPause(d.Bookings) work, pause = correctWorkPause(work, pause) + if (d.worktimeAbsece != Absence{}) { + work += d.worktimeAbsece.GetWorktimeReal(u, base) + } var targetHours time.Duration switch base { case WorktimeBaseDay: targetHours = u.ArbeitszeitProTag() case WorktimeBaseWeek: - targetHours = u.ArbeitszeitProWoche() / 5 + targetHours = u.ArbeitszeitProWocheFrac(0.2) } - return work - targetHours + return (work - targetHours).Round(time.Minute) } // Returns the worktime based on absence or kurzarbeit @@ -119,9 +74,10 @@ func (d *WorkDay) GetWorktimeVirtual(u User, base WorktimeBase) time.Duration { case WorktimeBaseDay: return u.ArbeitszeitProTag() case WorktimeBaseWeek: - return u.ArbeitszeitProWoche() / 5 + return u.ArbeitszeitProWocheFrac(0.2) + default: + return 0 } - return 0 } func (d *WorkDay) GetPausetimeVirtual(u User, base WorktimeBase) time.Duration { @@ -136,9 +92,9 @@ func (d *WorkDay) GetOvertimeVirtual(u User, base WorktimeBase) time.Duration { case WorktimeBaseDay: targetHours = u.ArbeitszeitProTag() case WorktimeBaseWeek: - targetHours = u.ArbeitszeitProWoche() / 5 + targetHours = u.ArbeitszeitProWocheFrac(0.2) } - return work - targetHours + return (work - targetHours).Round(time.Minute) } func (d *WorkDay) GetTimesReal(u User, base WorktimeBase) (work, pause, overtime time.Duration) { @@ -217,70 +173,10 @@ func (d *WorkDay) GenerateKurzArbeitBookings(u User) (time.Time, time.Time) { return timeFrom, timeTo } -func (d *WorkDay) TimeWorkVirtual(u User) time.Duration { - if d.IsKurzArbeit() { - return u.ArbeitszeitProTag() - } - return d.workTime -} - func (d *WorkDay) GetKurzArbeit() *Absence { return &d.kurzArbeitAbsence } -func (d *WorkDay) TimeWorkReal(u User) time.Duration { - d.realWorkTime, d.realPauseTime = 0, 0 - var lastBooking Booking - for _, booking := range d.Bookings { - if booking.CheckInOut%2 == 1 { - if !lastBooking.Timestamp.IsZero() { - d.realPauseTime += booking.Timestamp.Sub(lastBooking.Timestamp) - } - } else { - d.realWorkTime += booking.Timestamp.Sub(lastBooking.Timestamp) - } - lastBooking = booking - } - if helper.IsSameDate(d.Date(), time.Now()) && len(d.Bookings)%2 == 1 { - d.realWorkTime += time.Since(lastBooking.Timestamp.Local()) - } - // slog.Debug("Calculated RealWorkTime for user", "user", u, slog.String("worktime", d.realWorkTime.String())) - return d.realWorkTime -} - -func (d *WorkDay) TimeOvertimeReal(u User) time.Duration { - workTime := d.TimeWorkVirtual(u) - if workTime == 0 { - workTime, _ = d.TimePauseReal(u) - } - if helper.IsWeekend(d.Day) && len(d.Bookings) == 0 { - return 0 - } - var overtime time.Duration - overtime = workTime - u.ArbeitszeitProTag() - return overtime -} - -func (d *WorkDay) TimePauseReal(u User) (work, pause time.Duration) { - if d.realWorkTime == 0 { - d.TimeWorkReal(u) - } - d.workTime, d.pauseTime = d.realWorkTime, d.realPauseTime - if d.realWorkTime <= 6*time.Hour || d.realPauseTime > 45*time.Minute { - return d.realWorkTime, d.realPauseTime - } - if d.realWorkTime <= (9*time.Hour) && d.realPauseTime < 30*time.Minute { - diff := 30*time.Minute - d.pauseTime - d.workTime -= diff - d.pauseTime += diff - } else if d.realPauseTime < 45*time.Minute { - diff := 45*time.Minute - d.pauseTime - d.workTime -= diff - d.pauseTime += diff - } - return d.workTime, d.pauseTime -} - func (d *WorkDay) ToString() string { return fmt.Sprintf("WorkDay: %s with %d bookings and worktime: %s", d.Date().Format(time.DateOnly), len(d.Bookings), helper.FormatDuration(d.workTime)) } @@ -388,7 +284,6 @@ func GetWorkDays(user User, tsFrom, tsTo time.Time) []WorkDay { if len(workDay.Bookings) == 1 && workDay.Bookings[0].CounterId == 0 { workDay.Bookings = []Booking{} } - workDay.TimePauseReal(user) if len(workDay.Bookings) > 1 || !helper.IsWeekend(workDay.Date()) { workDays = append(workDays, workDay) } @@ -400,18 +295,6 @@ func GetWorkDays(user User, tsFrom, tsTo time.Time) []WorkDay { return workDays } -func (d *WorkDay) GetAllWorkTimesReal(user User) (work, pause, overtime time.Duration) { - if d.pauseTime == 0 || d.workTime == 0 { - d.TimePauseReal(user) - } - return d.workTime.Round(time.Minute), d.pauseTime.Round(time.Minute), d.TimeOvertimeReal(user) -} - -func (d *WorkDay) GetAllWorkTimesVirtual(user User) (work, pause, overtime time.Duration) { - _, pause, overtime = d.GetAllWorkTimesReal(user) - return d.TimeWorkVirtual(user), pause, overtime -} - // returns bool wheter the workday was ended with an automatic logout func (d *WorkDay) RequiresAction() bool { if len(d.Bookings) == 0 { @@ -424,19 +307,7 @@ func (d *WorkDay) GetDayProgress(u User) int8 { if d.RequiresAction() { return -1 } - workTime := d.TimeWorkVirtual(u) + workTime := d.GetWorktimeVirtual(u, WorktimeBaseDay) progress := (workTime.Seconds() / u.ArbeitszeitProTag().Seconds()) * 100 return int8(progress) } - -// func (d *WorkDay) CalcOvertime(user User) time.Duration { -// if d.workTime == 0 { -// d.TimePauseReal(user) -// } -// if helper.IsWeekend(d.Day) && len(d.Bookings) == 0 { -// return 0 -// } -// var overtime time.Duration -// overtime = d.workTime - user.ArbeitszeitProTag() -// return overtime -// } diff --git a/Backend/models/workDay_test.go b/Backend/models/workDay_test.go index 5b1aef7..cae5d38 100644 --- a/Backend/models/workDay_test.go +++ b/Backend/models/workDay_test.go @@ -22,59 +22,141 @@ var testWorkDay = models.WorkDay{ TimeTo: CatchError(time.Parse("2006-01-02 15:04", "2025-01-01 16:30")), } -func TestCalcRealWorkTime(t *testing.T) { - workTime := testWorkDay.TimeWorkReal(testUser) - if workTime != time.Hour*8 { - t.Errorf("Calc Worktime Default not working, time should be 8h, but was %s", helper.FormatDuration(workTime)) - } -} - -func TestCalcWorkPauseDiff(t *testing.T) { - type testCase struct { - Name string - bookings []models.Booking - expectedWorkTime time.Duration - expectedPauseTime time.Duration - expectedOvertime time.Duration - } - - testCases := []testCase{testCase{ - Name: "6hrs no pause", - bookings: testBookings6hrs, - expectedWorkTime: 6 * time.Hour, - expectedPauseTime: 0, - expectedOvertime: -2 * time.Hour, - }, - testCase{ - Name: "8hrs - 30min pause", - bookings: testBookings8hrs, - expectedWorkTime: 7*time.Hour + 30*time.Minute, - expectedPauseTime: 30 * time.Minute, - expectedOvertime: -30 * time.Minute, +func TestWorkdayWorktimeDay(t *testing.T) { + testCases := []struct { + testName string + bookings []models.Booking + expectedTime time.Duration + }{ + { + testName: "Bookings6hrs", + bookings: testBookings6hrs, + expectedTime: time.Hour * 6, }, - testCase{ - Name: "10hrs - 45min pause", - bookings: testBookings10hrs, - expectedWorkTime: 9*time.Hour + 15*time.Minute, - expectedPauseTime: 45 * time.Minute, - expectedOvertime: 1*time.Hour + 15*time.Minute, - }} + { + testName: "Bookings8hrs", + bookings: testBookings8hrs, + expectedTime: time.Hour*7 + time.Minute*30, + }, + { + testName: "Bookings10hrs", + bookings: testBookings10hrs, + expectedTime: time.Hour*9 + time.Minute*15, + }, + } - for _, test := range testCases { - t.Run(test.Name, func(t *testing.T) { - testWorkDay.Bookings = test.bookings - testWorkDay.TimeWorkReal(testUser) - testWorkDay.TimePauseReal(testUser) - testWorkDay.TimeOvertimeReal(testUser) - workTime, pauseTime, overTime := testWorkDay.GetAllWorkTimesReal(testUser) - if workTime != test.expectedWorkTime { - t.Errorf("Calculated wrong workTime: should be %s, but was %s", helper.FormatDuration(test.expectedWorkTime), helper.FormatDuration(workTime)) - } - if pauseTime != test.expectedPauseTime { - t.Errorf("Calculated wrong pauseTime: should be %s, but was %s", helper.FormatDuration(test.expectedPauseTime), helper.FormatDuration(pauseTime)) - } - if overTime != test.expectedOvertime { - t.Errorf("Calculated wrong overtime: should be %s, but was %s", helper.FormatDuration(test.expectedOvertime), helper.FormatDuration(overTime)) + for _, tc := range testCases { + t.Run("Calc Absence Worktime: "+tc.testName, func(t *testing.T) { + var testCase = testWorkDay + testCase.Bookings = tc.bookings + workTime := testCase.GetWorktimeReal(testUser, models.WorktimeBaseDay) + 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)) + } + }) + } +} + +func TestWorkdayWorktimeWeek(t *testing.T) { + testCases := []struct { + testName string + bookings []models.Booking + expectedTime time.Duration + }{ + { + testName: "Bookings6hrs", + bookings: testBookings6hrs, + expectedTime: time.Hour * 6, + }, + { + testName: "Bookings8hrs", + bookings: testBookings8hrs, + expectedTime: time.Hour*7 + time.Minute*30, + }, + { + testName: "Bookings10hrs", + bookings: testBookings10hrs, + expectedTime: time.Hour*9 + time.Minute*15, + }, + } + + for _, tc := range testCases { + t.Run("Calc Absence Worktime: "+tc.testName, func(t *testing.T) { + var testCase = testWorkDay + testCase.Bookings = tc.bookings + workTime := testCase.GetWorktimeReal(testUser, models.WorktimeBaseWeek) + 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)) + } + }) + } +} + +func TestWorkdayPausetimeDay(t *testing.T) { + testCases := []struct { + testName string + bookings []models.Booking + expectedTime time.Duration + }{ + { + testName: "Bookings6hrs", + bookings: testBookings6hrs, + expectedTime: 0, + }, + { + testName: "Bookings8hrs", + bookings: testBookings8hrs, + expectedTime: time.Minute * 30, + }, + { + testName: "Bookings10hrs", + bookings: testBookings10hrs, + expectedTime: time.Minute * 45, + }, + } + + for _, tc := range testCases { + t.Run("Calc Absence Worktime: "+tc.testName, func(t *testing.T) { + var testCase = testWorkDay + testCase.Bookings = tc.bookings + workTime := testCase.GetPausetimeReal(testUser, models.WorktimeBaseDay) + 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)) + } + }) + } +} + +func TestWorkdayPausetimeWeek(t *testing.T) { + testCases := []struct { + testName string + bookings []models.Booking + expectedTime time.Duration + }{ + { + testName: "Bookings6hrs", + bookings: testBookings6hrs, + expectedTime: 0, + }, + { + testName: "Bookings8hrs", + bookings: testBookings8hrs, + expectedTime: time.Minute * 30, + }, + { + testName: "Bookings10hrs", + bookings: testBookings10hrs, + expectedTime: time.Minute * 45, + }, + } + + for _, tc := range testCases { + t.Run("Calc Absence Worktime: "+tc.testName, func(t *testing.T) { + var testCase = testWorkDay + testCase.Bookings = tc.bookings + workTime := testCase.GetPausetimeReal(testUser, models.WorktimeBaseWeek) + 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)) } }) } diff --git a/Backend/models/workWeek.go b/Backend/models/workWeek.go index beaf504..ea353d0 100644 --- a/Backend/models/workWeek.go +++ b/Backend/models/workWeek.go @@ -47,18 +47,20 @@ func NewWorkWeek(user User, tsMonday time.Time, populate bool) WorkWeek { } func (w *WorkWeek) PopulateWithDays(worktime time.Duration, overtime time.Duration) { + slog.Debug("Populating Workweek for user", "user", w.User) slog.Debug("Got Days with overtime and worktime", slog.String("worktime", worktime.String()), slog.String("overtime", overtime.String())) w.Days = GetDays(w.User, w.WeekStart, w.WeekStart.Add(6*24*time.Hour), false) for _, day := range w.Days { - work, _ := day.TimePauseReal(w.User) - w.Worktime += work - w.WorkTimeVirtual += day.TimeWorkVirtual(w.User) + w.Worktime += day.GetWorktimeReal(w.User, WorktimeBaseDay) + w.WorkTimeVirtual += day.GetWorktimeVirtual(w.User, WorktimeBaseDay) } - slog.Debug("Got worktime for user", "user", w.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() + slog.Debug("Calculated overtime", "worktime", w.Worktime.String(), "virtualWorkTime", w.WorkTimeVirtual.String()) + w.Worktime = w.Worktime.Round(time.Minute) w.Overtime = w.Overtime.Round(time.Minute) diff --git a/Backend/templates/pdf.templ b/Backend/templates/pdf.templ index c00d786..332ab33 100644 --- a/Backend/templates/pdf.templ +++ b/Backend/templates/pdf.templ @@ -58,71 +58,70 @@ templ CheckboxComponent(id, label string) { } -templ PDFReportEmploye(e models.User, overtime, worktime time.Duration, workDays []models.IWorkDay, tsStart time.Time, tsEnd time.Time) { - {{ - _, kw := tsStart.ISOWeek() - noBorder := "" - }} - @Base() - -
-

{ e.Vorname } { e.Name }

-

Zeitraum: { tsStart.Format("02.01.2006") } - { tsEnd.Format("02.01.2006") }

-

Arbeitszeit: { helper.FormatDuration(worktime) }

-

Überstunden: { helper.FormatDuration(overtime) }

-
-
-

{ kw }

-

Kommen

-

Gehen

-

Arbeitsart

-

Stunden

-

Pause

-

Überstunden

- for index, day := range workDays { - {{ - if index == len(workDays)-1 { - noBorder = "border-b-0" - } - }} -

{ day.Date().Format("02.01.2006") }

-
- if day.IsWorkDay() { - {{ - workDay, _ := day.(*models.WorkDay) - }} - for bookingI := 0; bookingI < len(workDay.Bookings); bookingI+= 2 { -

{ workDay.Bookings[bookingI].Timestamp.Format("15:04") }

-

{ workDay.Bookings[bookingI+1].Timestamp.Format("15:04") }

-

{ workDay.Bookings[bookingI].BookingType.Name }

- } - if workDay.IsKurzArbeit() { - {{ - timeFrom, timeTo := workDay.GenerateKurzArbeitBookings(e) - }} -

{ timeFrom.Format("15:04") }

-

{ timeTo.Format("15:04") }

-

Kurzarbeit

- } - } else { - {{ - absentDay, _ := day.(*models.Absence) - }} -

{ absentDay.AbwesenheitTyp.Name }

- } -
- {{ work, pause, overtime := day.GetAllWorkTimesVirtual(e) }} - @ColorDuration(work, noBorder) - @ColorDuration(pause, noBorder) - @ColorDuration(overtime, noBorder+" border-r-0") - if day.Date().Weekday() == time.Friday { -

Wochenende

- } - } -
-
-} - +// templ PDFReportEmploye(e models.User, overtime, worktime time.Duration, workDays []models.IWorkDay, tsStart time.Time, tsEnd time.Time) { +// {{ +// _, kw := tsStart.ISOWeek() +// noBorder := "" +// }} +// @Base() +// +//
+//

{ e.Vorname } { e.Name }

+//

Zeitraum: { tsStart.Format("02.01.2006") } - { tsEnd.Format("02.01.2006") }

+//

Arbeitszeit: { helper.FormatDuration(worktime) }

+//

Überstunden: { helper.FormatDuration(overtime) }

+//
+//
+//

{ kw }

+//

Kommen

+//

Gehen

+//

Arbeitsart

+//

Stunden

+//

Pause

+//

Überstunden

+// for index, day := range workDays { +// {{ +// if index == len(workDays)-1 { +// noBorder = "border-b-0" +// } +// }} +//

{ day.Date().Format("02.01.2006") }

+//
+// if day.IsWorkDay() { +// {{ +// workDay, _ := day.(*models.WorkDay) +// }} +// for bookingI := 0; bookingI < len(workDay.Bookings); bookingI+= 2 { +//

{ workDay.Bookings[bookingI].Timestamp.Format("15:04") }

+//

{ workDay.Bookings[bookingI+1].Timestamp.Format("15:04") }

+//

{ workDay.Bookings[bookingI].BookingType.Name }

+// } +// if workDay.IsKurzArbeit() { +// {{ +// timeFrom, timeTo := workDay.GenerateKurzArbeitBookings(e) +// }} +//

{ timeFrom.Format("15:04") }

+//

{ timeTo.Format("15:04") }

+//

Kurzarbeit

+// } +// } else { +// {{ +// absentDay, _ := day.(*models.Absence) +// }} +//

{ absentDay.AbwesenheitTyp.Name }

+// } +//
+// {{ work, pause, overtime := day.GetTimesVirtual(e) }} +// @ColorDuration(work, noBorder) +// @ColorDuration(pause, noBorder) +// @ColorDuration(overtime, noBorder+" border-r-0") +// if day.Date().Weekday() == time.Friday { +//

Wochenende

+// } +// } +//
+//
+// } templ ColorDuration(d time.Duration, classes string) { {{ color := "" diff --git a/Backend/templates/pdf_templ.go b/Backend/templates/pdf_templ.go index a15eca1..997a431 100644 --- a/Backend/templates/pdf_templ.go +++ b/Backend/templates/pdf_templ.go @@ -177,7 +177,71 @@ func CheckboxComponent(id, label string) templ.Component { }) } -func PDFReportEmploye(e models.User, overtime, worktime time.Duration, workDays []models.IWorkDay, tsStart time.Time, tsEnd time.Time) templ.Component { +// templ PDFReportEmploye(e models.User, overtime, worktime time.Duration, workDays []models.IWorkDay, tsStart time.Time, tsEnd time.Time) { +// {{ +// _, kw := tsStart.ISOWeek() +// noBorder := "" +// }} +// @Base() +// +//
+//

{ e.Vorname } { e.Name }

+//

Zeitraum: { tsStart.Format("02.01.2006") } - { tsEnd.Format("02.01.2006") }

+//

Arbeitszeit: { helper.FormatDuration(worktime) }

+//

Überstunden: { helper.FormatDuration(overtime) }

+//
+//
+//

{ kw }

+//

Kommen

+//

Gehen

+//

Arbeitsart

+//

Stunden

+//

Pause

+//

Überstunden

+// for index, day := range workDays { +// {{ +// if index == len(workDays)-1 { +// noBorder = "border-b-0" +// } +// }} +//

{ day.Date().Format("02.01.2006") }

+//
+// if day.IsWorkDay() { +// {{ +// workDay, _ := day.(*models.WorkDay) +// }} +// for bookingI := 0; bookingI < len(workDay.Bookings); bookingI+= 2 { +//

{ workDay.Bookings[bookingI].Timestamp.Format("15:04") }

+//

{ workDay.Bookings[bookingI+1].Timestamp.Format("15:04") }

+//

{ workDay.Bookings[bookingI].BookingType.Name }

+// } +// if workDay.IsKurzArbeit() { +// {{ +// timeFrom, timeTo := workDay.GenerateKurzArbeitBookings(e) +// }} +//

{ timeFrom.Format("15:04") }

+//

{ timeTo.Format("15:04") }

+//

Kurzarbeit

+// } +// } else { +// {{ +// absentDay, _ := day.(*models.Absence) +// }} +//

{ absentDay.AbwesenheitTyp.Name }

+// } +//
+// {{ work, pause, overtime := day.GetTimesVirtual(e) }} +// @ColorDuration(work, noBorder) +// @ColorDuration(pause, noBorder) +// @ColorDuration(overtime, noBorder+" border-r-0") +// if day.Date().Weekday() == time.Friday { +//

Wochenende

+// } +// } +//
+//
+// } +func ColorDuration(d time.Duration, classes string) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { @@ -199,378 +263,42 @@ func PDFReportEmploye(e models.User, overtime, worktime time.Duration, workDays } ctx = templ.ClearChildren(ctx) - _, kw := tsStart.ISOWeek() - noBorder := "" - templ_7745c5c3_Err = Base().Render(ctx, templ_7745c5c3_Buffer) + color := "" + if d.Abs() < time.Minute { + color = "text-neutral-300" + } + var templ_7745c5c3_Var10 = []any{color + " " + classes} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var10...) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var10 string - templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(e.Vorname) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 69, Col: 45} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, " ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "

Zeitraum: ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var12 string - templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(tsStart.Format("02.01.2006")) + templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(helper.FormatDurationFill(d, true)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 70, Col: 52} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 132, Col: 72} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, " - ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var13 string - templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(tsEnd.Format("02.01.2006")) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 70, Col: 98} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "

Arbeitszeit: ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var14 string - templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(helper.FormatDuration(worktime)) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 71, Col: 58} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "

Überstunden: ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var15 string - templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(helper.FormatDuration(overtime)) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 72, Col: 59} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var16 string - templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(kw) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 75, Col: 52} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "

Kommen

Gehen

Arbeitsart

Stunden

Pause

Überstunden

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - for index, day := range workDays { - - if index == len(workDays)-1 { - noBorder = "border-b-0" - } - var templ_7745c5c3_Var17 = []any{noBorder} - templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var17...) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var19 string - templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(day.Date().Format("02.01.2006")) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 88, Col: 59} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var20 = []any{"grid grid-cols-subgrid col-span-3 " + noBorder} - templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var20...) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if day.IsWorkDay() { - - workDay, _ := day.(*models.WorkDay) - for bookingI := 0; bookingI < len(workDay.Bookings); bookingI += 2 { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var22 string - templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(workDay.Bookings[bookingI].Timestamp.Format("15:04")) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 95, Col: 64} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var23 string - templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(workDay.Bookings[bookingI+1].Timestamp.Format("15:04")) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 96, Col: 66} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var24 string - templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(workDay.Bookings[bookingI].BookingType.Name) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 97, Col: 55} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, " ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if workDay.IsKurzArbeit() { - - timeFrom, timeTo := workDay.GenerateKurzArbeitBookings(e) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var25 string - templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(timeFrom.Format("15:04")) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 103, Col: 36} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var26 string - templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(timeTo.Format("15:04")) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 104, Col: 34} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "

Kurzarbeit

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - } else { - - absentDay, _ := day.(*models.Absence) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var27 string - templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(absentDay.AbwesenheitTyp.Name) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 111, Col: 62} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - work, pause, overtime := day.GetAllWorkTimesVirtual(e) - templ_7745c5c3_Err = ColorDuration(work, noBorder).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, " ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = ColorDuration(pause, noBorder).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, " ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = ColorDuration(overtime, noBorder+" border-r-0").Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, " ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if day.Date().Weekday() == time.Friday { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "

Wochenende

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) -} - -func ColorDuration(d time.Duration, classes string) templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var28 := templ.GetChildren(ctx) - if templ_7745c5c3_Var28 == nil { - templ_7745c5c3_Var28 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - - color := "" - if d.Abs() < time.Minute { - color = "text-neutral-300" - } - var templ_7745c5c3_Var29 = []any{color + " " + classes} - templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var29...) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var31 string - templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(helper.FormatDurationFill(d, true)) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 133, Col: 72} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/Backend/templates/teamComponents.templ b/Backend/templates/teamComponents.templ index 086bd8d..b0e324a 100644 --- a/Backend/templates/teamComponents.templ +++ b/Backend/templates/teamComponents.templ @@ -36,7 +36,7 @@ templ defaultWeekDayComponent(u models.User, day models.IWorkDay) { if day.IsWorkDay() { {{ workDay, _ := day.(*models.WorkDay) - work, pause, _ := workDay.GetAllWorkTimesReal(u) + work, pause, _ := workDay.GetTimesReal(u, models.WorktimeBaseDay) }} if !workDay.RequiresAction() {
diff --git a/Backend/templates/teamComponents_templ.go b/Backend/templates/teamComponents_templ.go index 6314bce..80a940e 100644 --- a/Backend/templates/teamComponents_templ.go +++ b/Backend/templates/teamComponents_templ.go @@ -179,7 +179,7 @@ func defaultWeekDayComponent(u models.User, day models.IWorkDay) templ.Component if day.IsWorkDay() { workDay, _ := day.(*models.WorkDay) - work, pause, _ := workDay.GetAllWorkTimesReal(u) + work, pause, _ := workDay.GetTimesReal(u, models.WorktimeBaseDay) if !workDay.RequiresAction() { templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "
") if templ_7745c5c3_Err != nil { diff --git a/Backend/templates/timePage.templ b/Backend/templates/timePage.templ index 88cd3ec..a59bc47 100644 --- a/Backend/templates/timePage.templ +++ b/Backend/templates/timePage.templ @@ -103,7 +103,8 @@ templ defaultDayComponent(day models.IWorkDay) { if day.IsWorkDay() { {{ workDay, _ := day.(*models.WorkDay) - work, pause, overtime := workDay.GetAllWorkTimesReal(user) + work, pause, overtime := workDay.GetTimesVirtual(user, models.WorktimeBaseDay) + work = workDay.GetWorktimeReal(user, models.WorktimeBaseDay) }} if day.RequiresAction() {

Bitte anpassen

diff --git a/Backend/templates/timePage_templ.go b/Backend/templates/timePage_templ.go index 209fa28..264a21b 100644 --- a/Backend/templates/timePage_templ.go +++ b/Backend/templates/timePage_templ.go @@ -297,7 +297,8 @@ func defaultDayComponent(day models.IWorkDay) templ.Component { if day.IsWorkDay() { workDay, _ := day.(*models.WorkDay) - work, pause, overtime := workDay.GetAllWorkTimesReal(user) + work, pause, overtime := workDay.GetTimesVirtual(user, models.WorktimeBaseDay) + work = workDay.GetWorktimeReal(user, models.WorktimeBaseDay) if day.RequiresAction() { templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "

Bitte anpassen

") if templ_7745c5c3_Err != nil { @@ -312,7 +313,7 @@ func defaultDayComponent(day models.IWorkDay) templ.Component { var templ_7745c5c3_Var14 string templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(helper.FormatDuration(work)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timePage.templ`, Line: 113, Col: 155} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timePage.templ`, Line: 114, Col: 155} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) if templ_7745c5c3_Err != nil { @@ -335,7 +336,7 @@ func defaultDayComponent(day models.IWorkDay) templ.Component { var templ_7745c5c3_Var15 string templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(helper.FormatDuration(pause)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timePage.templ`, Line: 116, Col: 173} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timePage.templ`, Line: 117, Col: 173} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) if templ_7745c5c3_Err != nil { @@ -358,7 +359,7 @@ func defaultDayComponent(day models.IWorkDay) templ.Component { var templ_7745c5c3_Var16 string templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(helper.FormatDuration(overtime)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timePage.templ`, Line: 121, Col: 41} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timePage.templ`, Line: 122, Col: 41} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) if templ_7745c5c3_Err != nil { @@ -391,7 +392,7 @@ func defaultDayComponent(day models.IWorkDay) templ.Component { var templ_7745c5c3_Var18 string templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs("time-" + day.Date().Format(time.DateOnly)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timePage.templ`, Line: 130, Col: 56} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timePage.templ`, Line: 131, Col: 56} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) if templ_7745c5c3_Err != nil { @@ -507,7 +508,7 @@ func absentInput(a models.Absence) templ.Component { var templ_7745c5c3_Var21 string templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(a.DateFrom.Format(time.DateOnly)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timePage.templ`, Line: 162, Col: 79} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timePage.templ`, Line: 163, Col: 79} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21)) if templ_7745c5c3_Err != nil { @@ -520,7 +521,7 @@ func absentInput(a models.Absence) templ.Component { var templ_7745c5c3_Var22 string templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(a.DateTo.Format(time.DateOnly)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timePage.templ`, Line: 163, Col: 75} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timePage.templ`, Line: 164, Col: 75} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22)) if templ_7745c5c3_Err != nil { @@ -533,7 +534,7 @@ func absentInput(a models.Absence) templ.Component { var templ_7745c5c3_Var23 string templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(a.AbwesenheitTyp.Id) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timePage.templ`, Line: 164, Col: 64} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timePage.templ`, Line: 165, Col: 64} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23)) if templ_7745c5c3_Err != nil { @@ -546,7 +547,7 @@ func absentInput(a models.Absence) templ.Component { var templ_7745c5c3_Var24 string templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(a.CounterId) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timePage.templ`, Line: 165, Col: 54} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timePage.templ`, Line: 166, Col: 54} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24)) if templ_7745c5c3_Err != nil { diff --git a/DB/initdb/01_schema.sql b/DB/initdb/01_schema.sql index 70cfefe..b434f4c 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; <=1 => Arbeitszeit'; +COMMENT ON COLUMN "s_abwesenheit_typen"."arbeitszeit_equivalent" IS '0=keine Arbeitszeit; -1=Arbeitszeit auffüllen; <=1 - 100 => Arbeitszeit pro Tag prozentual'; -- Adds crypto extension diff --git a/DocumentCreator/templates/abrechnung.typ b/DocumentCreator/templates/abrechnung.typ index 5eb7bb0..ee71ebc 100644 --- a/DocumentCreator/templates/abrechnung.typ +++ b/DocumentCreator/templates/abrechnung.typ @@ -37,7 +37,7 @@ [Zeitraum: #meta.TimeRange] table( - columns: (1fr, 1fr, 1fr, 1fr, 1fr, 1fr, 1.25fr), + columns: (1fr, 1fr, 1fr, 1fr, 1fr, 1fr, .875fr, 1.25fr), fill: (x, y) => if y == 0 { oklch(87%, 0, 0deg) }, table-header( @@ -68,11 +68,12 @@ ] }, [#day.Worktime], + [#day.Kurzarbeit], [#day.Pausetime], [#day.Overtime], ) if day.IsFriday { - ( table.cell(colspan: 7, fill: oklch(87%, 0, 0deg))[Wochenende], ) // note the trailing comma + ( table.cell(colspan: 8, fill: oklch(87%, 0, 0deg))[Wochenende], ) // note the trailing comma } } ) -- 2.49.1 From 1ccc19b85cb1c2626f287688878457aec79eb2e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Tr=C3=B6ger?= Date: Fri, 12 Dec 2025 06:31:03 +0100 Subject: [PATCH 2/5] removed and refactored virtual and real worktime --- Backend/endpoints/auto-kurzarbeit.go | 4 +- Backend/endpoints/pdf-create.go | 8 +-- Backend/endpoints/time.go | 2 +- Backend/models/absence.go | 41 +++++++++++++++- Backend/models/iworkday.go | 12 ++--- Backend/models/workDay.go | 60 +++++------------------ Backend/models/workDay_test.go | 8 +-- Backend/models/workWeek.go | 12 ++--- Backend/templates/teamComponents.templ | 4 +- Backend/templates/teamComponents_templ.go | 4 +- Backend/templates/timePage.templ | 4 +- Backend/templates/timePage_templ.go | 4 +- 12 files changed, 79 insertions(+), 84 deletions(-) diff --git a/Backend/endpoints/auto-kurzarbeit.go b/Backend/endpoints/auto-kurzarbeit.go index 3a1e394..10b2cd5 100644 --- a/Backend/endpoints/auto-kurzarbeit.go +++ b/Backend/endpoints/auto-kurzarbeit.go @@ -46,11 +46,11 @@ func fillKurzarbeit(r *http.Request, w http.ResponseWriter) { if !day.IsKurzArbeit() || !day.IsWorkDay() { 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 } - 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 { continue diff --git a/Backend/endpoints/pdf-create.go b/Backend/endpoints/pdf-create.go index b70ea52..9f20759 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.GetTimes(u, models.WorktimeBaseWeek, true) thisTypstDay.Date = day.Date().Format(DE_DATE) thisTypstDay.Worktime = helper.FormatDurationFill(work, true) thisTypstDay.Pausetime = helper.FormatDurationFill(pause, true) @@ -132,7 +132,7 @@ func createEmployeReport(employee models.User, startDate, endDate time.Time) (by var actualHours time.Duration for _, day := range workingDays { - actualHours += day.GetWorktimeVirtual(employee, models.WorktimeBaseDay) + actualHours += day.GetWorktime(employee, models.WorktimeBaseDay, true) } worktimeBalance := actualHours - targetHours @@ -172,8 +172,8 @@ func PDFHandler(w http.ResponseWriter, r *http.Request) { 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) + aggregatedOvertime += day.GetOvertime(user, models.WorktimeBaseWeek, false) + aggregatedWorkTime += day.GetWorktime(user, models.WorktimeBaseWeek, true) } typstDays, err := convertDaysToTypst(weeks, user) diff --git a/Backend/endpoints/time.go b/Backend/endpoints/time.go index a3cf760..15a6375 100644 --- a/Backend/endpoints/time.go +++ b/Backend/endpoints/time.go @@ -84,7 +84,7 @@ func getBookings(w http.ResponseWriter, r *http.Request) { if day.Date().Before(lastSub) { continue } - aggregatedOvertime += day.GetOvertimeReal(user, models.WorktimeBaseDay) + aggregatedOvertime += day.GetOvertime(user, models.WorktimeBaseDay, false) } if reportedOvertime, err := user.GetReportedOvertime(); err == nil { user.Overtime = (reportedOvertime + aggregatedOvertime).Round(time.Minute) diff --git a/Backend/models/absence.go b/Backend/models/absence.go index 493261e..1ff28e2 100644 --- a/Backend/models/absence.go +++ b/Backend/models/absence.go @@ -49,6 +49,43 @@ func (a *Absence) IsMultiDay() bool { return !a.DateFrom.Equal(a.DateTo) } +func (a *Absence) GetWorktime(u User, base WorktimeBase, includeKurzarbeit bool) time.Duration { + switch base { + case WorktimeBaseDay: + if a.AbwesenheitTyp.WorkTime <= 0 && includeKurzarbeit { + return u.ArbeitszeitProTagFrac(1) + } + return u.ArbeitszeitProTagFrac(float32(a.AbwesenheitTyp.WorkTime) / 100) + case WorktimeBaseWeek: + if a.AbwesenheitTyp.WorkTime <= 0 && includeKurzarbeit { + return u.ArbeitszeitProTagFrac(0.2) + } + return u.ArbeitszeitProWocheFrac(0.2 * float32(a.AbwesenheitTyp.WorkTime) / 100) + } + return 0 +} + +func (a *Absence) GetPausetime(u User, base WorktimeBase, includeKurzarbeit bool) time.Duration { + return 0 +} + +func (a *Absence) GetOvertime(u User, base WorktimeBase, includeKurzarbeit bool) 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) GetTimes(u User, base WorktimeBase, includeKurzarbeit bool) (work, pause, overtime time.Duration) { + 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 @@ -71,9 +108,9 @@ func (a *Absence) GetOvertimeReal(u User, base WorktimeBase) time.Duration { } switch base { case WorktimeBaseDay: - return -u.ArbeitszeitProTag() + return -u.ArbeitszeitProTagFrac(1) case WorktimeBaseWeek: - return -u.ArbeitszeitProWoche() / 5 + return -u.ArbeitszeitProWocheFrac(0.2) } return 0 } diff --git a/Backend/models/iworkday.go b/Backend/models/iworkday.go index cf0344e..4970ba6 100644 --- a/Backend/models/iworkday.go +++ b/Backend/models/iworkday.go @@ -13,14 +13,10 @@ type IWorkDay interface { IsKurzArbeit() bool GetDayProgress(User) int8 RequiresAction() bool - GetWorktimeReal(User, WorktimeBase) time.Duration - GetPausetimeReal(User, WorktimeBase) time.Duration - GetOvertimeReal(User, WorktimeBase) time.Duration - GetWorktimeVirtual(User, WorktimeBase) 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) + GetWorktime(User, WorktimeBase, bool) time.Duration + GetPausetime(User, WorktimeBase, bool) time.Duration + GetTimes(User, WorktimeBase, bool) (work, pause, overtime time.Duration) + GetOvertime(User, WorktimeBase, bool) time.Duration } func GetDays(user User, tsFrom, tsTo time.Time, orderedForward bool) []IWorkDay { diff --git a/Backend/models/workDay.go b/Backend/models/workDay.go index adf8895..e82bd5a 100644 --- a/Backend/models/workDay.go +++ b/Backend/models/workDay.go @@ -31,30 +31,28 @@ const ( ) // 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() { + 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, WorktimeBaseDay) + work += d.worktimeAbsece.GetWorktimeReal(u, base) } return work.Round(time.Minute) } // 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 = correctWorkPause(work, pause) return pause.Round(time.Minute) } // Returns the overtime based on the db entries -func (d *WorkDay) GetOvertimeReal(u User, base WorktimeBase) time.Duration { - work, pause := calcWorkPause(d.Bookings) - work, pause = correctWorkPause(work, pause) - if (d.worktimeAbsece != Absence{}) { - work += d.worktimeAbsece.GetWorktimeReal(u, base) - } - +func (d *WorkDay) GetOvertime(u User, base WorktimeBase, includeKurzarbeit bool) time.Duration { + work := d.GetWorktime(u, base, includeKurzarbeit) var targetHours time.Duration switch base { case WorktimeBaseDay: @@ -65,44 +63,8 @@ func (d *WorkDay) GetOvertimeReal(u User, base WorktimeBase) time.Duration { return (work - targetHours).Round(time.Minute) } -// Returns the worktime based on absence or kurzarbeit -func (d *WorkDay) GetWorktimeVirtual(u User, base WorktimeBase) time.Duration { - 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 (d *WorkDay) GetTimes(u User, base WorktimeBase, includeKurzarbeit bool) (work, pause, overtime time.Duration) { + return d.GetWorktime(u, base, includeKurzarbeit), d.GetPausetime(u, base, includeKurzarbeit), d.GetOvertime(u, base, includeKurzarbeit) } func calcWorkPause(bookings []Booking) (work, pause time.Duration) { @@ -307,7 +269,7 @@ func (d *WorkDay) GetDayProgress(u User) int8 { if d.RequiresAction() { return -1 } - workTime := d.GetWorktimeVirtual(u, WorktimeBaseDay) + workTime := d.GetWorktime(u, WorktimeBaseDay, true) progress := (workTime.Seconds() / u.ArbeitszeitProTag().Seconds()) * 100 return int8(progress) } diff --git a/Backend/models/workDay_test.go b/Backend/models/workDay_test.go index cae5d38..9ad0e6d 100644 --- a/Backend/models/workDay_test.go +++ b/Backend/models/workDay_test.go @@ -49,7 +49,7 @@ func TestWorkdayWorktimeDay(t *testing.T) { t.Run("Calc Absence Worktime: "+tc.testName, func(t *testing.T) { var testCase = testWorkDay testCase.Bookings = tc.bookings - workTime := testCase.GetWorktimeReal(testUser, models.WorktimeBaseDay) + workTime := testCase.GetWorktime(testUser, models.WorktimeBaseDay, false) 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)) } @@ -84,7 +84,7 @@ func TestWorkdayWorktimeWeek(t *testing.T) { t.Run("Calc Absence Worktime: "+tc.testName, func(t *testing.T) { var testCase = testWorkDay testCase.Bookings = tc.bookings - workTime := testCase.GetWorktimeReal(testUser, models.WorktimeBaseWeek) + workTime := testCase.GetWorktime(testUser, models.WorktimeBaseWeek, false) 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)) } @@ -119,7 +119,7 @@ func TestWorkdayPausetimeDay(t *testing.T) { t.Run("Calc Absence Worktime: "+tc.testName, func(t *testing.T) { var testCase = testWorkDay testCase.Bookings = tc.bookings - workTime := testCase.GetPausetimeReal(testUser, models.WorktimeBaseDay) + workTime := testCase.GetPausetime(testUser, models.WorktimeBaseDay, false) 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)) } @@ -154,7 +154,7 @@ func TestWorkdayPausetimeWeek(t *testing.T) { t.Run("Calc Absence Worktime: "+tc.testName, func(t *testing.T) { var testCase = testWorkDay testCase.Bookings = tc.bookings - workTime := testCase.GetPausetimeReal(testUser, models.WorktimeBaseWeek) + workTime := testCase.GetPausetime(testUser, models.WorktimeBaseWeek, false) 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)) } diff --git a/Backend/models/workWeek.go b/Backend/models/workWeek.go index ea353d0..168365a 100644 --- a/Backend/models/workWeek.go +++ b/Backend/models/workWeek.go @@ -20,7 +20,7 @@ type WorkWeek struct { User User WeekStart time.Time Worktime time.Duration - WorkTimeVirtual time.Duration + WorktimeVirtual time.Duration Overtime time.Duration 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) for _, day := range w.Days { - w.Worktime += day.GetWorktimeReal(w.User, WorktimeBaseDay) - w.WorkTimeVirtual += day.GetWorktimeVirtual(w.User, WorktimeBaseDay) + w.Worktime += day.GetWorktime(w.User, WorktimeBaseDay, false) + 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.Overtime = w.Overtime.Round(time.Minute) diff --git a/Backend/templates/teamComponents.templ b/Backend/templates/teamComponents.templ index b0e324a..26501a4 100644 --- a/Backend/templates/teamComponents.templ +++ b/Backend/templates/teamComponents.templ @@ -36,7 +36,7 @@ templ defaultWeekDayComponent(u models.User, day models.IWorkDay) { if day.IsWorkDay() { {{ workDay, _ := day.(*models.WorkDay) - work, pause, _ := workDay.GetTimesReal(u, models.WorktimeBaseDay) + work, pause, _ := workDay.GetTimes(u, models.WorktimeBaseDay, false) }} if !workDay.RequiresAction() {
@@ -81,7 +81,7 @@ templ weekDayComponent(user models.User, day models.WorkDay) { templ workWeekComponent(week models.WorkWeek, onlyAccept bool) { {{ year, kw := week.WeekStart.ISOWeek() - progress := (float32(week.WorkTimeVirtual.Hours()) / week.User.ArbeitszeitPerWoche) * 100 + progress := (float32(week.WorktimeVirtual.Hours()) / week.User.ArbeitszeitPerWoche) * 100 }}
diff --git a/Backend/templates/teamComponents_templ.go b/Backend/templates/teamComponents_templ.go index 80a940e..8cdc91c 100644 --- a/Backend/templates/teamComponents_templ.go +++ b/Backend/templates/teamComponents_templ.go @@ -179,7 +179,7 @@ func defaultWeekDayComponent(u models.User, day models.IWorkDay) templ.Component if day.IsWorkDay() { workDay, _ := day.(*models.WorkDay) - work, pause, _ := workDay.GetTimesReal(u, models.WorktimeBaseDay) + work, pause, _ := workDay.GetTimes(u, models.WorktimeBaseDay, false) if !workDay.RequiresAction() { templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "
") if templ_7745c5c3_Err != nil { @@ -346,7 +346,7 @@ func workWeekComponent(week models.WorkWeek, onlyAccept bool) templ.Component { ctx = templ.ClearChildren(ctx) 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, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err diff --git a/Backend/templates/timePage.templ b/Backend/templates/timePage.templ index a59bc47..297d2d9 100644 --- a/Backend/templates/timePage.templ +++ b/Backend/templates/timePage.templ @@ -103,8 +103,8 @@ templ defaultDayComponent(day models.IWorkDay) { if day.IsWorkDay() { {{ workDay, _ := day.(*models.WorkDay) - work, pause, overtime := workDay.GetTimesVirtual(user, models.WorktimeBaseDay) - work = workDay.GetWorktimeReal(user, models.WorktimeBaseDay) + work, pause, overtime := workDay.GetTimes(user, models.WorktimeBaseDay, true) + work = workDay.GetWorktime(user, models.WorktimeBaseDay, false) }} if day.RequiresAction() {

Bitte anpassen

diff --git a/Backend/templates/timePage_templ.go b/Backend/templates/timePage_templ.go index 264a21b..4bfba6c 100644 --- a/Backend/templates/timePage_templ.go +++ b/Backend/templates/timePage_templ.go @@ -297,8 +297,8 @@ func defaultDayComponent(day models.IWorkDay) templ.Component { if day.IsWorkDay() { workDay, _ := day.(*models.WorkDay) - work, pause, overtime := workDay.GetTimesVirtual(user, models.WorktimeBaseDay) - work = workDay.GetWorktimeReal(user, models.WorktimeBaseDay) + work, pause, overtime := workDay.GetTimes(user, models.WorktimeBaseDay, true) + work = workDay.GetWorktime(user, models.WorktimeBaseDay, false) if day.RequiresAction() { templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "

Bitte anpassen

") if templ_7745c5c3_Err != nil { -- 2.49.1 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 3/5] 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), - ) } -- 2.49.1 From 588bf908c6f35f01b88ecd0268cad10b70c45f42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Tr=C3=B6ger?= Date: Fri, 12 Dec 2025 12:58:41 +0100 Subject: [PATCH 4/5] fixed tests --- Backend/models/absence.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Backend/models/absence.go b/Backend/models/absence.go index 0d1beaa..0086337 100644 --- a/Backend/models/absence.go +++ b/Backend/models/absence.go @@ -54,11 +54,15 @@ func (a *Absence) GetWorktime(u User, base WorktimeBase, includeKurzarbeit bool) 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) 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) } -- 2.49.1 From a1b225478aef5f5aced46303a168f6323fda139f Mon Sep 17 00:00:00 2001 From: tom Date: Fri, 12 Dec 2025 14:13:24 +0100 Subject: [PATCH 5/5] fixed sonarqube issue --- Backend/models/user.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Backend/models/user.go b/Backend/models/user.go index 1e87b3d..373ae27 100644 --- a/Backend/models/user.go +++ b/Backend/models/user.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "log" + "log/slog" "time" "github.com/alexedwards/scs/v2" @@ -62,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;`)) var users []User if err != nil { - fmt.Printf("Error preparing query statement %v\n", err) return users, err } defer qStr.Close() @@ -90,7 +90,6 @@ 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 { - fmt.Printf("Error preparing query statement %v\n", err) return users, err } defer qStr.Close() @@ -136,7 +135,7 @@ func (u *User) ArbeitszeitProWocheFrac(fraction float32) time.Duration { 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;`)) if err != nil { - fmt.Printf("Error preparing query statement %v\n", err) + slog.Debug("Error preparing query statement.", "error", err) return false } defer qStr.Close() @@ -210,7 +209,7 @@ 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;`)) if err != nil { - log.Println("Error preparing db statement", err) + slog.Debug("Error preparing query statement.", "error", err) return false } defer qStr.Close() @@ -302,7 +301,7 @@ func (u *User) GetLastWorkWeekSubmission() time.Time { ) AS letzte_buchung; `) if err != nil { - log.Println("Error preparing statement!", err) + slog.Debug("Error preparing query statement.", "error", err) return lastSub } err = qStr.QueryRow(u.PersonalNummer, u.CardUID).Scan(&lastSub) -- 2.49.1