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/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 7d1ae45..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.GetTimesReal(u, models.WorktimeBaseWeek)
+ work, pause, overtime := day.GetTimes(u, models.WorktimeBaseDay, false)
+ workVirtual := day.GetWorktime(u, models.WorktimeBaseDay, true)
+ overtime = workVirtual - u.ArbeitszeitProWocheFrac(0.2)
thisTypstDay.Date = day.Date().Format(DE_DATE)
- thisTypstDay.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,6 +53,19 @@ func convertDayToTypstDayParts(day models.IWorkDay, user models.User) []typstDay
typstDayPart.IsWorkDay = true
typstDayParts = append(typstDayParts, typstDayPart)
}
+ if day.IsKurzArbeit() && len(workDay.Bookings) > 0 {
+ tsFrom, tsTo := workDay.GenerateKurzArbeitBookings(user)
+ typstDayParts = append(typstDayParts, typstDayPart{
+ BookingFrom: tsFrom.Format("15:04"),
+ BookingTo: tsTo.Format("15:04"),
+ WorkType: "Kurzarbeit",
+ IsWorkDay: true,
+ })
+ }
+ if workdayAbsence := workDay.GetWorktimeAbsence(); (workdayAbsence != models.Absence{}) {
+ typstDayParts = append(typstDayParts, typstDayPart{IsWorkDay: false, WorkType: workdayAbsence.AbwesenheitTyp.Name})
+ }
+
} else {
absentDay, _ := day.(*models.Absence)
typstDayParts = append(typstDayParts, typstDayPart{IsWorkDay: false, WorkType: absentDay.AbwesenheitTyp.Name})
@@ -52,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
@@ -80,119 +185,11 @@ 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.TimeWorkVirtual(employee)
- }
- 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.TimeOvertimeReal(user)
- aggregatedWorkTime += day.TimeWorkVirtual(user)
- }
-
- 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"`
WorkTime string `json:"worktime"`
+ Kurzarbeit string `json:"kurzarbeit"`
Overtime string `json:"overtime"`
OvertimeTotal string `json:"overtime-total"`
CurrentTimestamp string `json:"current-timestamp"`
@@ -206,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/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..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.TimeOvertimeReal(user)
+ 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/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 0f99bbb..0086337 100644
--- a/Backend/models/absence.go
+++ b/Backend/models/absence.go
@@ -49,75 +49,45 @@ 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 {
+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)
+ } 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)
+ }
+ 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.ArbeitszeitProTag()
+ return -u.ArbeitszeitProTagFrac(1)
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 -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) 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) ToString() string {
diff --git a/Backend/models/absence_test.go b/Backend/models/absence_test.go
new file mode 100644
index 0000000..235579b
--- /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.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))
+ }
+ })
+ }
+}
+
+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.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/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..4970ba6
--- /dev/null
+++ b/Backend/models/iworkday.go
@@ -0,0 +1,55 @@
+package models
+
+import (
+ "arbeitszeitmessung/helper"
+ "log"
+ "time"
+)
+
+type IWorkDay interface {
+ Date() time.Time
+ ToString() string
+ IsWorkDay() bool
+ IsKurzArbeit() bool
+ GetDayProgress(User) int8
+ RequiresAction() bool
+ 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 {
+ 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..373ae27 100644
--- a/Backend/models/user.go
+++ b/Backend/models/user.go
@@ -7,9 +7,11 @@ import (
"errors"
"fmt"
"log"
+ "log/slog"
"time"
"github.com/alexedwards/scs/v2"
+ "github.com/lib/pq"
)
type User struct {
@@ -61,7 +63,6 @@ func GetAllUsers() ([]User, error) {
qStr, err := DB.Prepare((`SELECT card_uid, vorname, nachname,arbeitszeit_per_tag, arbeitszeit_per_woche FROM s_personal_daten;`))
var users []User
if err != nil {
- fmt.Printf("Error preparing query statement %v\n", err)
return users, err
}
defer qStr.Close()
@@ -89,7 +90,6 @@ func (u *User) GetAll() ([]User, error) {
qStr, err := DB.Prepare((`SELECT card_uid, vorname, nachname FROM s_personal_daten;`))
var users []User
if err != nil {
- fmt.Printf("Error preparing query statement %v\n", err)
return users, err
}
defer qStr.Close()
@@ -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
@@ -127,7 +135,7 @@ func (u *User) ArbeitszeitProWoche() 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()
@@ -165,11 +173,43 @@ 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;`))
if err != nil {
- log.Println("Error preparing db statement", err)
+ slog.Debug("Error preparing query statement.", "error", err)
return false
}
defer qStr.Close()
@@ -261,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)
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..f6e88d1 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,101 +30,45 @@ 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
+func (d *WorkDay) GetWorktimeAbsence() Absence {
+ return d.worktimeAbsece
}
// Gets the time as is in the db (with corrected pause times)
-func (d *WorkDay) GetWorktimeReal(u User, base WorktimeBase) time.Duration {
+func (d *WorkDay) GetWorktime(u User, base WorktimeBase, includeKurzarbeit bool) time.Duration {
+ if includeKurzarbeit && d.IsKurzArbeit() && len(d.Bookings) > 0 {
+ return d.kurzArbeitAbsence.GetWorktime(u, base, true)
+ }
work, pause := calcWorkPause(d.Bookings)
work, pause = correctWorkPause(work, pause)
- return work
+ if (d.worktimeAbsece != Absence{}) {
+ work += d.worktimeAbsece.GetWorktime(u, base, false)
+ }
+ 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
+ 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)
-
+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:
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
-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 (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) {
@@ -206,81 +128,21 @@ 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
}
-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 +250,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 +261,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 +273,7 @@ func (d *WorkDay) GetDayProgress(u User) int8 {
if d.RequiresAction() {
return -1
}
- workTime := d.TimeWorkVirtual(u)
+ workTime := d.GetWorktime(u, WorktimeBaseDay, true)
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..9ad0e6d 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.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))
+ }
+ })
+ }
+}
+
+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.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))
+ }
+ })
+ }
+}
+
+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.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))
+ }
+ })
+ }
+}
+
+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.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 beaf504..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
}
@@ -47,17 +47,19 @@ 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.GetWorktime(w.User, WorktimeBaseDay, false)
+ w.WorktimeVirtual += day.GetWorktime(w.User, WorktimeBaseDay, true)
}
- 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()
+ 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()
- 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 { day.Date().Format("02.01.2006") } { workDay.Bookings[bookingI].Timestamp.Format("15:04") } { workDay.Bookings[bookingI+1].Timestamp.Format("15:04") } { workDay.Bookings[bookingI].BookingType.Name } { timeFrom.Format("15:04") } { timeTo.Format("15:04") } Kurzarbeit { absentDay.AbwesenheitTyp.Name } Wochenende 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 { day.Date().Format("02.01.2006") } { workDay.Bookings[bookingI].Timestamp.Format("15:04") } { workDay.Bookings[bookingI+1].Timestamp.Format("15:04") } { workDay.Bookings[bookingI].BookingType.Name } { timeFrom.Format("15:04") } { timeTo.Format("15:04") } Kurzarbeit { absentDay.AbwesenheitTyp.Name } Wochenende 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 { day.Date().Format("02.01.2006") } { workDay.Bookings[bookingI].Timestamp.Format("15:04") } { workDay.Bookings[bookingI+1].Timestamp.Format("15:04") } { workDay.Bookings[bookingI].BookingType.Name } { timeFrom.Format("15:04") } { timeTo.Format("15:04") } Kurzarbeit { absentDay.AbwesenheitTyp.Name } Wochenende 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
- }
- 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_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
- }
- 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
- }
- 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, " Wochenende{ e.Vorname } { e.Name }
- { e.Vorname } { e.Name }
+// { e.Vorname } { e.Name }
+// ")
- 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, "
") - 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..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.GetAllWorkTimesReal(u) + work, pause, _ := workDay.GetTimes(u, models.WorktimeBaseDay, false) }} if !workDay.RequiresAction() {Bitte anpassen
@@ -136,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 209fa28..506f94c 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.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 { @@ -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 { @@ -435,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 @@ -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..0735264 100644 --- a/DocumentCreator/templates/abrechnung.typ +++ b/DocumentCreator/templates/abrechnung.typ @@ -37,42 +37,47 @@ [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( - [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],) + } ) }, ) ] }, [#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 } } ) @@ -84,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), - ) }