tried to add untertags krank
Some checks failed
Tests / Run Go Tests (push) Failing after 2m3s

This commit is contained in:
2025-12-05 15:03:34 +01:00
parent a6ea625e8f
commit f73c2b1a96
21 changed files with 508 additions and 653 deletions

View File

@@ -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 }}

View File

@@ -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"`

View File

@@ -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)

View File

@@ -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 {

View File

@@ -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))
}
})
}
}

View File

@@ -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")),

View File

@@ -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
}

View File

@@ -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

View File

@@ -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()

View File

@@ -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,10 +74,11 @@ 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
}
}
func (d *WorkDay) GetPausetimeVirtual(u User, base WorktimeBase) time.Duration {
return d.GetPausetimeReal(u, base)
@@ -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
// }

View File

@@ -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
func TestWorkdayWorktimeDay(t *testing.T) {
testCases := []struct {
testName string
bookings []models.Booking
expectedWorkTime time.Duration
expectedPauseTime time.Duration
expectedOvertime time.Duration
}
testCases := []testCase{testCase{
Name: "6hrs no pause",
expectedTime time.Duration
}{
{
testName: "Bookings6hrs",
bookings: testBookings6hrs,
expectedWorkTime: 6 * time.Hour,
expectedPauseTime: 0,
expectedOvertime: -2 * time.Hour,
expectedTime: time.Hour * 6,
},
testCase{
Name: "8hrs - 30min pause",
{
testName: "Bookings8hrs",
bookings: testBookings8hrs,
expectedWorkTime: 7*time.Hour + 30*time.Minute,
expectedPauseTime: 30 * time.Minute,
expectedOvertime: -30 * time.Minute,
expectedTime: time.Hour*7 + time.Minute*30,
},
testCase{
Name: "10hrs - 45min pause",
{
testName: "Bookings10hrs",
bookings: testBookings10hrs,
expectedWorkTime: 9*time.Hour + 15*time.Minute,
expectedPauseTime: 45 * time.Minute,
expectedOvertime: 1*time.Hour + 15*time.Minute,
}}
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))
}
})
}

View File

@@ -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)

View File

@@ -58,71 +58,70 @@ templ CheckboxComponent(id, label string) {
</div>
}
templ PDFReportEmploye(e models.User, overtime, worktime time.Duration, workDays []models.IWorkDay, tsStart time.Time, tsEnd time.Time) {
{{
_, kw := tsStart.ISOWeek()
noBorder := ""
}}
@Base()
<content class="p-8 relative flex flex-col gap-4 break-after-page">
<div>
<h1 class="text-2xl font-bold">{ e.Vorname } { e.Name }</h1>
<p>Zeitraum: <span>{ tsStart.Format("02.01.2006") }</span> - <span>{ tsEnd.Format("02.01.2006") }</span></p>
<p>Arbeitszeit: <span>{ helper.FormatDuration(worktime) }</span></p>
<p>Überstunden: <span>{ helper.FormatDuration(overtime) }</span></p>
</div>
<div class="grid grid-rows-6 grid-cols-[3fr_2fr_2fr_2fr_3fr_3fr_3fr] *:not-print:p-2 *:text-center auto-rows-min divide-neutral-300 divide-x-1 divide-y-1">
<p class="bg-neutral-300 border-neutral-600">{ kw }</p>
<p class="bg-neutral-300 border-neutral-600">Kommen</p>
<p class="bg-neutral-300 border-neutral-600">Gehen</p>
<p class="bg-neutral-300 border-neutral-600">Arbeitsart</p>
<p class="bg-neutral-300 border-neutral-600">Stunden</p>
<p class="bg-neutral-300 border-neutral-600">Pause</p>
<p class="bg-neutral-300 border-neutral-600 border-r-0">Überstunden</p>
for index, day := range workDays {
{{
if index == len(workDays)-1 {
noBorder = "border-b-0"
}
}}
<p class={ noBorder }>{ day.Date().Format("02.01.2006") }</p>
<div class={ "grid grid-cols-subgrid col-span-3 " + noBorder }>
if day.IsWorkDay() {
{{
workDay, _ := day.(*models.WorkDay)
}}
for bookingI := 0; bookingI < len(workDay.Bookings); bookingI+= 2 {
<p>{ workDay.Bookings[bookingI].Timestamp.Format("15:04") }</p>
<p>{ workDay.Bookings[bookingI+1].Timestamp.Format("15:04") }</p>
<p>{ workDay.Bookings[bookingI].BookingType.Name } </p>
}
if workDay.IsKurzArbeit() {
{{
timeFrom, timeTo := workDay.GenerateKurzArbeitBookings(e)
}}
<p>{ timeFrom.Format("15:04") }</p>
<p>{ timeTo.Format("15:04") }</p>
<p>Kurzarbeit</p>
}
} else {
{{
absentDay, _ := day.(*models.Absence)
}}
<p class="col-span-full">{ absentDay.AbwesenheitTyp.Name }</p>
}
</div>
{{ work, pause, overtime := day.GetAllWorkTimesVirtual(e) }}
@ColorDuration(work, noBorder)
@ColorDuration(pause, noBorder)
@ColorDuration(overtime, noBorder+" border-r-0")
if day.Date().Weekday() == time.Friday {
<p class="col-span-full bg-neutral-300">Wochenende</p>
}
}
</div>
</content>
}
// templ PDFReportEmploye(e models.User, overtime, worktime time.Duration, workDays []models.IWorkDay, tsStart time.Time, tsEnd time.Time) {
// {{
// _, kw := tsStart.ISOWeek()
// noBorder := ""
// }}
// @Base()
// <content class="p-8 relative flex flex-col gap-4 break-after-page">
// <div>
// <h1 class="text-2xl font-bold">{ e.Vorname } { e.Name }</h1>
// <p>Zeitraum: <span>{ tsStart.Format("02.01.2006") }</span> - <span>{ tsEnd.Format("02.01.2006") }</span></p>
// <p>Arbeitszeit: <span>{ helper.FormatDuration(worktime) }</span></p>
// <p>Überstunden: <span>{ helper.FormatDuration(overtime) }</span></p>
// </div>
// <div class="grid grid-rows-6 grid-cols-[3fr_2fr_2fr_2fr_3fr_3fr_3fr] *:not-print:p-2 *:text-center auto-rows-min divide-neutral-300 divide-x-1 divide-y-1">
// <p class="bg-neutral-300 border-neutral-600">{ kw }</p>
// <p class="bg-neutral-300 border-neutral-600">Kommen</p>
// <p class="bg-neutral-300 border-neutral-600">Gehen</p>
// <p class="bg-neutral-300 border-neutral-600">Arbeitsart</p>
// <p class="bg-neutral-300 border-neutral-600">Stunden</p>
// <p class="bg-neutral-300 border-neutral-600">Pause</p>
// <p class="bg-neutral-300 border-neutral-600 border-r-0">Überstunden</p>
// for index, day := range workDays {
// {{
// if index == len(workDays)-1 {
// noBorder = "border-b-0"
// }
// }}
// <p class={ noBorder }>{ day.Date().Format("02.01.2006") }</p>
// <div class={ "grid grid-cols-subgrid col-span-3 " + noBorder }>
// if day.IsWorkDay() {
// {{
// workDay, _ := day.(*models.WorkDay)
// }}
// for bookingI := 0; bookingI < len(workDay.Bookings); bookingI+= 2 {
// <p>{ workDay.Bookings[bookingI].Timestamp.Format("15:04") }</p>
// <p>{ workDay.Bookings[bookingI+1].Timestamp.Format("15:04") }</p>
// <p>{ workDay.Bookings[bookingI].BookingType.Name } </p>
// }
// if workDay.IsKurzArbeit() {
// {{
// timeFrom, timeTo := workDay.GenerateKurzArbeitBookings(e)
// }}
// <p>{ timeFrom.Format("15:04") }</p>
// <p>{ timeTo.Format("15:04") }</p>
// <p>Kurzarbeit</p>
// }
// } else {
// {{
// absentDay, _ := day.(*models.Absence)
// }}
// <p class="col-span-full">{ absentDay.AbwesenheitTyp.Name }</p>
// }
// </div>
// {{ work, pause, overtime := day.GetTimesVirtual(e) }}
// @ColorDuration(work, noBorder)
// @ColorDuration(pause, noBorder)
// @ColorDuration(overtime, noBorder+" border-r-0")
// if day.Date().Weekday() == time.Friday {
// <p class="col-span-full bg-neutral-300">Wochenende</p>
// }
// }
// </div>
// </content>
// }
templ ColorDuration(d time.Duration, classes string) {
{{
color := ""

View File

@@ -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()
// <content class="p-8 relative flex flex-col gap-4 break-after-page">
// <div>
// <h1 class="text-2xl font-bold">{ e.Vorname } { e.Name }</h1>
// <p>Zeitraum: <span>{ tsStart.Format("02.01.2006") }</span> - <span>{ tsEnd.Format("02.01.2006") }</span></p>
// <p>Arbeitszeit: <span>{ helper.FormatDuration(worktime) }</span></p>
// <p>Überstunden: <span>{ helper.FormatDuration(overtime) }</span></p>
// </div>
// <div class="grid grid-rows-6 grid-cols-[3fr_2fr_2fr_2fr_3fr_3fr_3fr] *:not-print:p-2 *:text-center auto-rows-min divide-neutral-300 divide-x-1 divide-y-1">
// <p class="bg-neutral-300 border-neutral-600">{ kw }</p>
// <p class="bg-neutral-300 border-neutral-600">Kommen</p>
// <p class="bg-neutral-300 border-neutral-600">Gehen</p>
// <p class="bg-neutral-300 border-neutral-600">Arbeitsart</p>
// <p class="bg-neutral-300 border-neutral-600">Stunden</p>
// <p class="bg-neutral-300 border-neutral-600">Pause</p>
// <p class="bg-neutral-300 border-neutral-600 border-r-0">Überstunden</p>
// for index, day := range workDays {
// {{
// if index == len(workDays)-1 {
// noBorder = "border-b-0"
// }
// }}
// <p class={ noBorder }>{ day.Date().Format("02.01.2006") }</p>
// <div class={ "grid grid-cols-subgrid col-span-3 " + noBorder }>
// if day.IsWorkDay() {
// {{
// workDay, _ := day.(*models.WorkDay)
// }}
// for bookingI := 0; bookingI < len(workDay.Bookings); bookingI+= 2 {
// <p>{ workDay.Bookings[bookingI].Timestamp.Format("15:04") }</p>
// <p>{ workDay.Bookings[bookingI+1].Timestamp.Format("15:04") }</p>
// <p>{ workDay.Bookings[bookingI].BookingType.Name } </p>
// }
// if workDay.IsKurzArbeit() {
// {{
// timeFrom, timeTo := workDay.GenerateKurzArbeitBookings(e)
// }}
// <p>{ timeFrom.Format("15:04") }</p>
// <p>{ timeTo.Format("15:04") }</p>
// <p>Kurzarbeit</p>
// }
// } else {
// {{
// absentDay, _ := day.(*models.Absence)
// }}
// <p class="col-span-full">{ absentDay.AbwesenheitTyp.Name }</p>
// }
// </div>
// {{ work, pause, overtime := day.GetTimesVirtual(e) }}
// @ColorDuration(work, noBorder)
// @ColorDuration(pause, noBorder)
// @ColorDuration(overtime, noBorder+" border-r-0")
// if day.Date().Weekday() == time.Friday {
// <p class="col-span-full bg-neutral-300">Wochenende</p>
// }
// }
// </div>
// </content>
// }
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, "<content class=\"p-8 relative flex flex-col gap-4 break-after-page\"><div><h1 class=\"text-2xl font-bold\">")
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, "<p class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(e.Name)
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var10).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 69, Col: 56}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</h1><p>Zeitraum: <span>")
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, "</span> - <span>")
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, "</span></p><p>Arbeitszeit: <span>")
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, "</span></p><p>Überstunden: <span>")
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, "</span></p></div><div class=\"grid grid-rows-6 grid-cols-[3fr_2fr_2fr_2fr_3fr_3fr_3fr] *:not-print:p-2 *:text-center auto-rows-min divide-neutral-300 divide-x-1 divide-y-1\"><p class=\"bg-neutral-300 border-neutral-600\">")
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, "</p><p class=\"bg-neutral-300 border-neutral-600\">Kommen</p><p class=\"bg-neutral-300 border-neutral-600\">Gehen</p><p class=\"bg-neutral-300 border-neutral-600\">Arbeitsart</p><p class=\"bg-neutral-300 border-neutral-600\">Stunden</p><p class=\"bg-neutral-300 border-neutral-600\">Pause</p><p class=\"bg-neutral-300 border-neutral-600 border-r-0\">Überstunden</p>")
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, "<p class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var18 string
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var17).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "\">")
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, "</p>")
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, "<div class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var21 string
templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var20).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "\">")
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, "<p>")
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, "</p><p>")
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, "</p><p>")
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, "</p>")
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, "<p>")
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, "</p><p>")
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, "</p><p>Kurzarbeit</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
} else {
absentDay, _ := day.(*models.Absence)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "<p class=\"col-span-full\">")
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, "</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "</div>")
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, "<p class=\"col-span-full bg-neutral-300\">Wochenende</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "</div></content>")
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, "<p class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var30 string
templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var29).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "\">")
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, "</p>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}

View File

@@ -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() {
<div class="flex flex-row gap-2">

View File

@@ -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, "<div class=\"flex flex-row gap-2\"><span class=\"text-accent\">")
if templ_7745c5c3_Err != nil {

View File

@@ -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() {
<p class="text-red-600">Bitte anpassen</p>

View File

@@ -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, "<p class=\"text-red-600\">Bitte anpassen</p>")
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 {

View File

@@ -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

View File

@@ -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
}
}
)