diff --git a/Backend/endpoints/pdf-create.go b/Backend/endpoints/pdf-create.go index 17db0db..cfeab6a 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.GetAllWorkTimesVirtual(u) + 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) @@ -129,6 +129,8 @@ func createEmployeReport(employee models.User, startDate, endDate time.Time) (by 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.TimeWorkVirtual(employee) diff --git a/Backend/helper/time.go b/Backend/helper/time.go index 30198fc..f55a92c 100644 --- a/Backend/helper/time.go +++ b/Backend/helper/time.go @@ -31,6 +31,7 @@ func FormatDuration(d time.Duration) string { // Converts duration to string func FormatDurationFill(d time.Duration, fill bool) string { + return fmt.Sprintf("%.1f", d.Hours()) hours := int(d.Abs().Hours()) minutes := int(d.Abs().Minutes()) % 60 sign := "" diff --git a/Backend/models/absence.go b/Backend/models/absence.go index e3296a4..0f99bbb 100644 --- a/Backend/models/absence.go +++ b/Backend/models/absence.go @@ -49,6 +49,55 @@ func (a *Absence) IsMultiDay() bool { return !a.DateFrom.Equal(a.DateTo) } +func (a *Absence) GetWorktimeReal(u User, base WorktimeBase) time.Duration { + if a.AbwesenheitTyp.WorkTime <= 1 { + return 0 + } + switch base { + case WorktimeBaseDay: + return u.ArbeitszeitProTag() + case WorktimeBaseWeek: + return u.ArbeitszeitProWoche() / 5 + } + 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 > 1 { + return 0 + } + switch base { + case WorktimeBaseDay: + return -u.ArbeitszeitProTag() + case WorktimeBaseWeek: + return -u.ArbeitszeitProWoche() / 5 + } + 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) } diff --git a/Backend/models/workDay.go b/Backend/models/workDay.go index be74595..b7d3254 100644 --- a/Backend/models/workDay.go +++ b/Backend/models/workDay.go @@ -22,6 +22,14 @@ 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) } type WorkDay struct { @@ -37,6 +45,13 @@ type WorkDay struct { kurzArbeitAbsence Absence } +type WorktimeBase string + +const ( + WorktimeBaseWeek WorktimeBase = "week" + WorktimeBaseDay WorktimeBase = "day" +) + func GetDays(user User, tsFrom, tsTo time.Time, orderedForward bool) []IWorkDay { var allDays map[string]IWorkDay = make(map[string]IWorkDay) @@ -66,6 +81,108 @@ func GetDays(user User, tsFrom, tsTo time.Time, orderedForward bool) []IWorkDay 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 +} + +// 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 +} + +// 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) + + var targetHours time.Duration + switch base { + case WorktimeBaseDay: + targetHours = u.ArbeitszeitProTag() + case WorktimeBaseWeek: + targetHours = u.ArbeitszeitProWoche() / 5 + } + return work - targetHours +} + +// 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.ArbeitszeitProWoche() / 5 + } + 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.ArbeitszeitProWoche() / 5 + } + return work - targetHours +} + +func (d *WorkDay) GetTimesReal(u User, base WorktimeBase) (work, pause, overtime time.Duration) { + return d.GetWorktimeReal(u, base), d.GetPausetimeReal(u, base), d.GetOvertimeReal(u, base) +} + +func (d *WorkDay) GetTimesVirtual(u User, base WorktimeBase) (work, pause, overtime time.Duration) { + return d.GetWorktimeVirtual(u, base), d.GetPausetimeVirtual(u, base), d.GetOvertimeVirtual(u, base) +} + +func calcWorkPause(bookings []Booking) (work, pause time.Duration) { + var lastBooking Booking + for _, b := range bookings { + if b.CheckInOut%2 == 1 { + if !lastBooking.Timestamp.IsZero() { + pause += b.Timestamp.Sub(lastBooking.Timestamp) + } + } else { + work += b.Timestamp.Sub(lastBooking.Timestamp) + } + lastBooking = b + } + if len(bookings)%2 == 1 { + work += time.Since(lastBooking.Timestamp.Local()) + } + return work, pause +} + +func correctWorkPause(workIn, pauseIn time.Duration) (work, pause time.Duration) { + if workIn <= 6*time.Hour || pauseIn > 45*time.Minute { + return workIn, pauseIn + } + + var diff time.Duration + if workIn <= (9*time.Hour) && pauseIn < 30*time.Minute { + diff = 30*time.Minute - pauseIn + } else if pauseIn < 45*time.Minute { + diff = 45*time.Minute - pauseIn + } + work = workIn - diff + pause = pauseIn + diff + return work, pause +} + func sortDays(days map[string]IWorkDay, forward bool) []IWorkDay { var sortedDays []IWorkDay for _, day := range days { @@ -127,7 +244,7 @@ func (d *WorkDay) TimeWorkReal(u User) time.Duration { 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())) + // slog.Debug("Calculated RealWorkTime for user", "user", u, slog.String("worktime", d.realWorkTime.String())) return d.realWorkTime }