refactor + added kurzarbeit to pdf export #66

Merged
tom_trgr merged 5 commits from dev/broken into dev/pdf 2025-12-12 14:17:17 +01:00
24 changed files with 725 additions and 883 deletions

View File

@@ -73,5 +73,5 @@ jobs:
push: true push: true
context: Backend context: Backend
tags: | tags: |
git.letsstein.de/tom/arbeitszeitmessung:latest git.letsstein.de/tom/arbeitszeitmessung-webserver:latest
git.letsstein.de/tom/arbeitszeitmessung:${{ github.ref_name }} git.letsstein.de/tom/arbeitszeitmessung-webserver:${{ github.ref_name }}

View File

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

View File

@@ -20,13 +20,21 @@ func convertDaysToTypst(days []models.IWorkDay, u models.User) ([]typstDay, erro
var typstDays []typstDay var typstDays []typstDay
for _, day := range days { for _, day := range days {
var thisTypstDay typstDay var thisTypstDay typstDay
work, pause, overtime := day.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.Date = day.Date().Format(DE_DATE)
thisTypstDay.Worktime = helper.FormatDurationFill(work, true) thisTypstDay.Worktime = helper.FormatDurationFill(workVirtual, true)
thisTypstDay.Pausetime = helper.FormatDurationFill(pause, true) thisTypstDay.Pausetime = helper.FormatDurationFill(pause, true)
thisTypstDay.Overtime = helper.FormatDurationFill(overtime, true) thisTypstDay.Overtime = helper.FormatDurationFill(overtime, true)
thisTypstDay.IsFriday = day.Date().Weekday() == time.Friday thisTypstDay.IsFriday = day.Date().Weekday() == time.Friday
if workVirtual > work {
thisTypstDay.Kurzarbeit = helper.FormatDurationFill(workVirtual-work, true)
} else {
thisTypstDay.Kurzarbeit = helper.FormatDurationFill(0, true)
}
thisTypstDay.DayParts = convertDayToTypstDayParts(day, u) thisTypstDay.DayParts = convertDayToTypstDayParts(day, u)
typstDays = append(typstDays, thisTypstDay) typstDays = append(typstDays, thisTypstDay)
} }
@@ -45,6 +53,19 @@ func convertDayToTypstDayParts(day models.IWorkDay, user models.User) []typstDay
typstDayPart.IsWorkDay = true typstDayPart.IsWorkDay = true
typstDayParts = append(typstDayParts, typstDayPart) typstDayParts = append(typstDayParts, typstDayPart)
} }
if day.IsKurzArbeit() && 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 { } else {
absentDay, _ := day.(*models.Absence) absentDay, _ := day.(*models.Absence)
typstDayParts = append(typstDayParts, typstDayPart{IsWorkDay: false, WorkType: absentDay.AbwesenheitTyp.Name}) 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 return typstDayParts
} }
func PDFCreateController(w http.ResponseWriter, r *http.Request) {
helper.RequiresLogin(Session, w, r)
switch r.Method {
case http.MethodGet:
user, err := models.GetUserFromSession(Session, r.Context())
if err != nil {
log.Println("Error getting user!")
return
}
pp := paramParser.New(r.URL.Query())
startDate := pp.ParseTimestampFallback("start_date", time.DateOnly, time.Now())
personalNumbers := pp.ParseIntListFallback("employe_list", ",", make([]int, 0))
employes, err := models.GetUserByPersonalNrMulti(personalNumbers)
if err != nil {
slog.Warn("Error getting employes!", slog.Any("Error", err))
return
}
output, err := createReports(user, employes, startDate)
if err != nil {
slog.Warn("Could not create pdf report", slog.Any("Error", err))
}
w.Header().Set("Content-type", "application/pdf")
output.WriteTo(w)
w.WriteHeader(http.StatusOK)
default:
http.Error(w, "Method not allowed!", http.StatusMethodNotAllowed)
}
}
func createReports(user models.User, employes []models.User, startDate time.Time) (bytes.Buffer, error) {
startDate = helper.GetFirstOfMonth(startDate)
endDate := startDate.AddDate(0, 1, -1)
var employeData []typstData
for _, employe := range employes {
if data, err := createEmployeReport(employe, startDate, endDate); err != nil {
slog.Warn("Error when creating employeReport", slog.Any("user", employe), slog.Any("error", err))
} else {
employeData = append(employeData, data)
}
}
return renderPDF(employeData)
}
func createEmployeReport(employee models.User, startDate, endDate time.Time) (typstData, error) {
targetHoursThisMonth := employee.ArbeitszeitProWocheFrac(.2) * time.Duration(helper.GetWorkingDays(startDate, endDate))
workDaysThisMonth := models.GetDays(employee, startDate, endDate.AddDate(0, 0, 1), false)
slog.Debug("Baseline Working hours", "targetHours", targetHoursThisMonth.Hours())
var workHours, kurzarbeitHours time.Duration
for _, day := range workDaysThisMonth {
tmpvirtualHours := day.GetWorktime(employee, models.WorktimeBaseDay, true)
tmpactualHours := day.GetWorktime(employee, models.WorktimeBaseDay, false)
if day.IsKurzArbeit() && tmpvirtualHours > tmpactualHours {
slog.Debug("Adding kurzarbeit to workday", "day", day.Date())
kurzarbeitHours += tmpvirtualHours - tmpactualHours
}
workHours += tmpvirtualHours
}
worktimeBalance := workHours - targetHoursThisMonth
typstDays, err := convertDaysToTypst(workDaysThisMonth, employee)
if err != nil {
slog.Warn("Failed to convert to days", slog.Any("error", err))
return typstData{}, err
}
metadata := typstMetadata{
EmployeeName: fmt.Sprintf("%s %s", employee.Vorname, employee.Name),
TimeRange: fmt.Sprintf("%s - %s", startDate.Format(DE_DATE), endDate.Format(DE_DATE)),
Overtime: helper.FormatDurationFill(worktimeBalance, true),
WorkTime: helper.FormatDurationFill(workHours, true),
Kurzarbeit: helper.FormatDurationFill(kurzarbeitHours, true),
OvertimeTotal: "",
CurrentTimestamp: time.Now().Format("02.01.2006 - 15:04 Uhr"),
}
return typstData{Meta: metadata, Days: typstDays}, nil
}
func renderPDF(data []typstData) (bytes.Buffer, error) { func renderPDF(data []typstData) (bytes.Buffer, error) {
var markup bytes.Buffer var markup bytes.Buffer
var output bytes.Buffer var output bytes.Buffer
@@ -80,119 +185,11 @@ func renderPDF(data []typstData) (bytes.Buffer, error) {
return output, nil return output, nil
} }
func PDFCreateController(w http.ResponseWriter, r *http.Request) {
helper.RequiresLogin(Session, w, r)
switch r.Method {
case http.MethodGet:
user, err := models.GetUserFromSession(Session, r.Context())
if err != nil {
log.Println("Error getting user!")
return
}
pp := paramParser.New(r.URL.Query())
startDate := pp.ParseTimestampFallback("start_date", time.DateOnly, time.Now())
var members []models.User = make([]models.User, 0)
output, err := createReports(user, members, startDate)
if err != nil {
slog.Warn("Could not create pdf report", slog.Any("Error", err))
}
w.Header().Set("Content-type", "application/pdf")
output.WriteTo(w)
w.WriteHeader(http.StatusOK)
default:
http.Error(w, "Method not allowed!", http.StatusMethodNotAllowed)
}
}
func createReports(user models.User, employes []models.User, startDate time.Time) (bytes.Buffer, error) {
if startDate.Day() > 1 {
startDate = startDate.AddDate(0, 0, -(startDate.Day() - 1))
}
endDate := startDate.AddDate(0, 1, -1)
return createEmployeReport(user, startDate, endDate)
}
func createEmployeReport(employee models.User, startDate, endDate time.Time) (bytes.Buffer, error) {
targetHours := (employee.ArbeitszeitProWoche() / 5) * time.Duration(helper.GetWorkingDays(startDate, endDate))
workingDays := models.GetDays(employee, startDate, endDate, false)
slog.Debug("Baseline Working hours", "targetHours", targetHours.Hours())
var actualHours time.Duration
for _, day := range workingDays {
actualHours += day.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 { type typstMetadata struct {
TimeRange string `json:"time-range"` TimeRange string `json:"time-range"`
EmployeeName string `json:"employee-name"` EmployeeName string `json:"employee-name"`
WorkTime string `json:"worktime"` WorkTime string `json:"worktime"`
Kurzarbeit string `json:"kurzarbeit"`
Overtime string `json:"overtime"` Overtime string `json:"overtime"`
OvertimeTotal string `json:"overtime-total"` OvertimeTotal string `json:"overtime-total"`
CurrentTimestamp string `json:"current-timestamp"` CurrentTimestamp string `json:"current-timestamp"`
@@ -206,12 +203,13 @@ type typstDayPart struct {
} }
type typstDay struct { type typstDay struct {
Date string `json:"date"` Date string `json:"date"`
DayParts []typstDayPart `json:"day-parts"` DayParts []typstDayPart `json:"day-parts"`
Worktime string `json:"worktime"` Worktime string `json:"worktime"`
Pausetime string `json:"pausetime"` Pausetime string `json:"pausetime"`
Overtime string `json:"overtime"` Overtime string `json:"overtime"`
IsFriday bool `json:"is-weekend"` Kurzarbeit string `json:"kurzarbeit"`
IsFriday bool `json:"is-weekend"`
} }
type typstData struct { type typstData struct {

View File

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

View File

@@ -5,6 +5,7 @@ import (
"log/slog" "log/slog"
"net/url" "net/url"
"strconv" "strconv"
"strings"
"time" "time"
) )
@@ -12,6 +13,30 @@ type ParamsParser struct {
urlParams url.Values urlParams url.Values
} }
func (p ParamsParser) ParseStringListFallback(key string, delimiter string, fallback []string) []string {
if !p.urlParams.Has(key) {
return fallback
}
paramList := p.urlParams.Get(key)
list := strings.Split(paramList, delimiter)
return list
}
func (p ParamsParser) ParseIntListFallback(key string, delimiter string, fallback []int) []int {
if !p.urlParams.Has(key) {
return fallback
}
paramList := p.urlParams.Get(key)
list := strings.Split(paramList, delimiter)
parsedList := make([]int, 0)
for _, item := range list {
if parsedItem, err := strconv.Atoi(item); err == nil {
parsedList = append(parsedList, parsedItem)
}
}
return parsedList
}
type NoValueError struct { type NoValueError struct {
Key string Key string
} }

View File

@@ -16,6 +16,13 @@ func GetMonday(ts time.Time) time.Time {
return ts return ts
} }
func GetFirstOfMonth(ts time.Time) time.Time {
if ts.Day() > 1 {
return ts.AddDate(0, 0, -(ts.Day() - 1))
}
return ts
}
func IsWeekend(ts time.Time) bool { func IsWeekend(ts time.Time) bool {
return ts.Weekday() == time.Saturday || ts.Weekday() == time.Sunday return ts.Weekday() == time.Saturday || ts.Weekday() == time.Sunday
} }

View File

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

View File

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

View File

@@ -10,36 +10,36 @@ var testBookingType = models.BookingType{
Name: "Büro", Name: "Büro",
} }
var testBookings8hrs = []models.Booking{models.Booking{ var testBookings8hrs = []models.Booking{{
CardUID: "aaaa-aaaa", CardUID: "aaaa-aaaa",
CheckInOut: 1, CheckInOut: 1,
Timestamp: CatchError(time.Parse("2006-01-02 15:04", "2025-01-01 08:00")), Timestamp: CatchError(time.Parse("2006-01-02 15:04", "2025-01-01 08:00")),
BookingType: testBookingType, BookingType: testBookingType,
}, models.Booking{ }, {
CardUID: "aaaa-aaaa", CardUID: "aaaa-aaaa",
CheckInOut: 2, CheckInOut: 2,
Timestamp: CatchError(time.Parse("2006-01-02 15:04", "2025-01-01 16:00")), Timestamp: CatchError(time.Parse("2006-01-02 15:04", "2025-01-01 16:00")),
BookingType: testBookingType, BookingType: testBookingType,
}} }}
var testBookings6hrs = []models.Booking{models.Booking{ var testBookings6hrs = []models.Booking{{
CardUID: "aaaa-aaaa", CardUID: "aaaa-aaaa",
CheckInOut: 1, CheckInOut: 1,
Timestamp: CatchError(time.Parse("2006-01-02 15:04", "2025-01-01 08:00")), Timestamp: CatchError(time.Parse("2006-01-02 15:04", "2025-01-01 08:00")),
BookingType: testBookingType, BookingType: testBookingType,
}, models.Booking{ }, {
CardUID: "aaaa-aaaa", CardUID: "aaaa-aaaa",
CheckInOut: 2, CheckInOut: 2,
Timestamp: CatchError(time.Parse("2006-01-02 15:04", "2025-01-01 14:00")), Timestamp: CatchError(time.Parse("2006-01-02 15:04", "2025-01-01 14:00")),
BookingType: testBookingType, BookingType: testBookingType,
}} }}
var testBookings10hrs = []models.Booking{models.Booking{ var testBookings10hrs = []models.Booking{{
CardUID: "aaaa-aaaa", CardUID: "aaaa-aaaa",
CheckInOut: 1, CheckInOut: 1,
Timestamp: CatchError(time.Parse("2006-01-02 15:04", "2025-01-01 08:00")), Timestamp: CatchError(time.Parse("2006-01-02 15:04", "2025-01-01 08:00")),
BookingType: testBookingType, BookingType: testBookingType,
}, models.Booking{ }, {
CardUID: "aaaa-aaaa", CardUID: "aaaa-aaaa",
CheckInOut: 2, CheckInOut: 2,
Timestamp: CatchError(time.Parse("2006-01-02 15:04", "2025-01-01 18:00")), Timestamp: CatchError(time.Parse("2006-01-02 15:04", "2025-01-01 18:00")),

View File

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

View File

@@ -7,9 +7,11 @@ import (
"errors" "errors"
"fmt" "fmt"
"log" "log"
"log/slog"
"time" "time"
"github.com/alexedwards/scs/v2" "github.com/alexedwards/scs/v2"
"github.com/lib/pq"
) )
type User struct { type User struct {
@@ -61,7 +63,6 @@ func GetAllUsers() ([]User, error) {
qStr, err := DB.Prepare((`SELECT card_uid, vorname, nachname,arbeitszeit_per_tag, arbeitszeit_per_woche FROM s_personal_daten;`)) qStr, err := DB.Prepare((`SELECT card_uid, vorname, nachname,arbeitszeit_per_tag, arbeitszeit_per_woche FROM s_personal_daten;`))
var users []User var users []User
if err != nil { if err != nil {
fmt.Printf("Error preparing query statement %v\n", err)
return users, err return users, err
} }
defer qStr.Close() defer qStr.Close()
@@ -89,7 +90,6 @@ func (u *User) GetAll() ([]User, error) {
qStr, err := DB.Prepare((`SELECT card_uid, vorname, nachname FROM s_personal_daten;`)) qStr, err := DB.Prepare((`SELECT card_uid, vorname, nachname FROM s_personal_daten;`))
var users []User var users []User
if err != nil { if err != nil {
fmt.Printf("Error preparing query statement %v\n", err)
return users, err return users, err
} }
defer qStr.Close() defer qStr.Close()
@@ -113,13 +113,21 @@ func (u *User) GetAll() ([]User, error) {
return users, nil return users, nil
} }
// Returns the worktime per day rounded to minutes
func (u *User) ArbeitszeitProTag() time.Duration { 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 { 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 // 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 { func (u *User) CheckAnwesenheit() bool {
qStr, err := DB.Prepare((`SELECT check_in_out FROM anwesenheit WHERE card_uid = $1 AND "timestamp"::date = now()::date ORDER BY "timestamp" DESC LIMIT 1;`)) qStr, err := DB.Prepare((`SELECT check_in_out FROM anwesenheit WHERE card_uid = $1 AND "timestamp"::date = now()::date ORDER BY "timestamp" DESC LIMIT 1;`))
if err != nil { if err != nil {
fmt.Printf("Error preparing query statement %v\n", err) slog.Debug("Error preparing query statement.", "error", err)
return false return false
} }
defer qStr.Close() defer qStr.Close()
@@ -165,11 +173,43 @@ func GetUserByPersonalNr(personalNummer int) (User, error) {
return user, nil return user, nil
} }
func GetUserByPersonalNrMulti(personalNummerMulti []int) ([]User, error) {
var users []User
if len(personalNummerMulti) == 0 {
return users, errors.New("No personalNumbers provided")
}
qStr, err := DB.Prepare((`SELECT personal_nummer, card_uid, vorname, nachname, arbeitszeit_per_tag, arbeitszeit_per_woche FROM s_personal_daten WHERE personal_nummer = ANY($1::int[]);`))
if err != nil {
return users, err
}
rows, err := qStr.Query(pq.Array(personalNummerMulti))
if err == sql.ErrNoRows {
return users, err
}
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
var user User
if err := rows.Scan(&user.PersonalNummer, &user.CardUID, &user.Vorname, &user.Name, &user.ArbeitszeitPerTag, &user.ArbeitszeitPerWoche); err != nil {
return users, err
}
users = append(users, user)
}
if err = rows.Err(); err != nil {
return users, err
}
return users, nil
}
func (u *User) Login(password string) bool { func (u *User) Login(password string) bool {
var loginSuccess bool var loginSuccess bool
qStr, err := DB.Prepare((`SELECT (pass_hash = crypt($2, pass_hash)) AS pass_hash FROM user_password WHERE personal_nummer = $1;`)) qStr, err := DB.Prepare((`SELECT (pass_hash = crypt($2, pass_hash)) AS pass_hash FROM user_password WHERE personal_nummer = $1;`))
if err != nil { if err != nil {
log.Println("Error preparing db statement", err) slog.Debug("Error preparing query statement.", "error", err)
return false return false
} }
defer qStr.Close() defer qStr.Close()
@@ -261,7 +301,7 @@ func (u *User) GetLastWorkWeekSubmission() time.Time {
) AS letzte_buchung; ) AS letzte_buchung;
`) `)
if err != nil { if err != nil {
log.Println("Error preparing statement!", err) slog.Debug("Error preparing query statement.", "error", err)
return lastSub return lastSub
} }
err = qStr.QueryRow(u.PersonalNummer, u.CardUID).Scan(&lastSub) err = qStr.QueryRow(u.PersonalNummer, u.CardUID).Scan(&lastSub)

View File

@@ -6,7 +6,7 @@ import (
"testing" "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) { func SetupUserFixture(t *testing.T, db models.IDatabase) {
t.Helper() t.Helper()

View File

@@ -10,39 +10,17 @@ import (
"time" "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 { type WorkDay struct {
Day time.Time `json:"day"` Day time.Time `json:"day"`
Bookings []Booking `json:"bookings"` Bookings []Booking `json:"bookings"`
workTime time.Duration workTime time.Duration
pauseTime time.Duration pauseTime time.Duration
realWorkTime time.Duration
realPauseTime time.Duration
TimeFrom time.Time TimeFrom time.Time
TimeTo time.Time TimeTo time.Time
kurzArbeit bool kurzArbeit bool
kurzArbeitAbsence Absence kurzArbeitAbsence Absence
// Urlaub untertags
worktimeAbsece Absence
} }
type WorktimeBase string type WorktimeBase string
@@ -52,101 +30,45 @@ const (
WorktimeBaseDay WorktimeBase = "day" WorktimeBaseDay WorktimeBase = "day"
) )
func GetDays(user User, tsFrom, tsTo time.Time, orderedForward bool) []IWorkDay { func (d *WorkDay) GetWorktimeAbsence() Absence {
var allDays map[string]IWorkDay = make(map[string]IWorkDay) return d.worktimeAbsece
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) // Gets the time as is in the db (with corrected pause times)
func (d *WorkDay) GetWorktimeReal(u User, base WorktimeBase) time.Duration { func (d *WorkDay) GetWorktime(u User, base WorktimeBase, includeKurzarbeit bool) time.Duration {
if includeKurzarbeit && d.IsKurzArbeit() && len(d.Bookings) > 0 {
return d.kurzArbeitAbsence.GetWorktime(u, base, true)
}
work, pause := calcWorkPause(d.Bookings) work, pause := calcWorkPause(d.Bookings)
work, pause = correctWorkPause(work, pause) work, pause = correctWorkPause(work, pause)
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 // Gets the corrected pause times based on db entries
func (d *WorkDay) GetPausetimeReal(u User, base WorktimeBase) time.Duration { func (d *WorkDay) GetPausetime(u User, base WorktimeBase, includeKurzarbeit bool) time.Duration {
work, pause := calcWorkPause(d.Bookings) work, pause := calcWorkPause(d.Bookings)
work, pause = correctWorkPause(work, pause) work, pause = correctWorkPause(work, pause)
return pause return pause.Round(time.Minute)
} }
// Returns the overtime based on the db entries // Returns the overtime based on the db entries
func (d *WorkDay) GetOvertimeReal(u User, base WorktimeBase) time.Duration { func (d *WorkDay) GetOvertime(u User, base WorktimeBase, includeKurzarbeit bool) time.Duration {
work, pause := calcWorkPause(d.Bookings) work := d.GetWorktime(u, base, includeKurzarbeit)
work, pause = correctWorkPause(work, pause)
var targetHours time.Duration var targetHours time.Duration
switch base { switch base {
case WorktimeBaseDay: case WorktimeBaseDay:
targetHours = u.ArbeitszeitProTag() targetHours = u.ArbeitszeitProTag()
case WorktimeBaseWeek: 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) GetTimes(u User, base WorktimeBase, includeKurzarbeit bool) (work, pause, overtime time.Duration) {
func (d *WorkDay) GetWorktimeVirtual(u User, base WorktimeBase) time.Duration { return d.GetWorktime(u, base, includeKurzarbeit), d.GetPausetime(u, base, includeKurzarbeit), d.GetOvertime(u, base, includeKurzarbeit)
if !d.IsKurzArbeit() {
return d.GetWorktimeReal(u, base)
}
switch base {
case WorktimeBaseDay:
return u.ArbeitszeitProTag()
case WorktimeBaseWeek:
return u.ArbeitszeitProWoche() / 5
}
return 0
}
func (d *WorkDay) GetPausetimeVirtual(u User, base WorktimeBase) time.Duration {
return d.GetPausetimeReal(u, base)
}
func (d *WorkDay) GetOvertimeVirtual(u User, base WorktimeBase) time.Duration {
work := d.GetWorktimeVirtual(u, base)
var targetHours time.Duration
switch base {
case WorktimeBaseDay:
targetHours = u.ArbeitszeitProTag()
case WorktimeBaseWeek:
targetHours = u.ArbeitszeitProWoche() / 5
}
return work - targetHours
}
func (d *WorkDay) GetTimesReal(u User, base WorktimeBase) (work, pause, overtime time.Duration) {
return d.GetWorktimeReal(u, base), d.GetPausetimeReal(u, base), d.GetOvertimeReal(u, base)
}
func (d *WorkDay) GetTimesVirtual(u User, base WorktimeBase) (work, pause, overtime time.Duration) {
return d.GetWorktimeVirtual(u, base), d.GetPausetimeVirtual(u, base), d.GetOvertimeVirtual(u, base)
} }
func calcWorkPause(bookings []Booking) (work, pause time.Duration) { 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) { func (d *WorkDay) GenerateKurzArbeitBookings(u User) (time.Time, time.Time) {
var timeFrom, timeTo time.Time var timeFrom, timeTo time.Time
if d.workTime >= u.ArbeitszeitProTag() { if d.GetWorktime(u, WorktimeBaseDay, false) >= u.ArbeitszeitProTag() {
return timeFrom, timeTo return timeFrom, timeTo
} }
timeFrom = d.Bookings[len(d.Bookings)-1].Timestamp.Add(time.Minute) timeFrom = d.Bookings[len(d.Bookings)-1].Timestamp.Add(time.Minute)
timeTo = timeFrom.Add(u.ArbeitszeitProTag() - d.workTime) timeTo = timeFrom.Add(u.ArbeitszeitProTag() - d.GetWorktime(u, WorktimeBaseDay, false))
slog.Debug("Added duration as Kurzarbeit", "date", d.Date().String(), "duration", timeTo.Sub(timeFrom).String()) slog.Debug("Added duration as Kurzarbeit", "date", d.Date().String(), "duration", timeTo.Sub(timeFrom).String())
return timeFrom, timeTo return timeFrom, timeTo
} }
func (d *WorkDay) TimeWorkVirtual(u User) time.Duration {
if d.IsKurzArbeit() {
return u.ArbeitszeitProTag()
}
return d.workTime
}
func (d *WorkDay) GetKurzArbeit() *Absence { func (d *WorkDay) GetKurzArbeit() *Absence {
return &d.kurzArbeitAbsence 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 { 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)) 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 { if len(workDay.Bookings) == 1 && workDay.Bookings[0].CounterId == 0 {
workDay.Bookings = []Booking{} workDay.Bookings = []Booking{}
} }
workDay.TimePauseReal(user)
if len(workDay.Bookings) > 1 || !helper.IsWeekend(workDay.Date()) { if len(workDay.Bookings) > 1 || !helper.IsWeekend(workDay.Date()) {
workDays = append(workDays, workDay) workDays = append(workDays, workDay)
} }
@@ -400,18 +261,6 @@ func GetWorkDays(user User, tsFrom, tsTo time.Time) []WorkDay {
return workDays 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 // returns bool wheter the workday was ended with an automatic logout
func (d *WorkDay) RequiresAction() bool { func (d *WorkDay) RequiresAction() bool {
if len(d.Bookings) == 0 { if len(d.Bookings) == 0 {
@@ -424,19 +273,7 @@ func (d *WorkDay) GetDayProgress(u User) int8 {
if d.RequiresAction() { if d.RequiresAction() {
return -1 return -1
} }
workTime := d.TimeWorkVirtual(u) workTime := d.GetWorktime(u, WorktimeBaseDay, true)
progress := (workTime.Seconds() / u.ArbeitszeitProTag().Seconds()) * 100 progress := (workTime.Seconds() / u.ArbeitszeitProTag().Seconds()) * 100
return int8(progress) return int8(progress)
} }
// 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")), TimeTo: CatchError(time.Parse("2006-01-02 15:04", "2025-01-01 16:30")),
} }
func TestCalcRealWorkTime(t *testing.T) { func TestWorkdayWorktimeDay(t *testing.T) {
workTime := testWorkDay.TimeWorkReal(testUser) testCases := []struct {
if workTime != time.Hour*8 { testName string
t.Errorf("Calc Worktime Default not working, time should be 8h, but was %s", helper.FormatDuration(workTime)) bookings []models.Booking
} expectedTime time.Duration
} }{
{
func TestCalcWorkPauseDiff(t *testing.T) { testName: "Bookings6hrs",
type testCase struct { bookings: testBookings6hrs,
Name string expectedTime: time.Hour * 6,
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,
}, },
testCase{ {
Name: "10hrs - 45min pause", testName: "Bookings8hrs",
bookings: testBookings10hrs, bookings: testBookings8hrs,
expectedWorkTime: 9*time.Hour + 15*time.Minute, expectedTime: time.Hour*7 + time.Minute*30,
expectedPauseTime: 45 * time.Minute, },
expectedOvertime: 1*time.Hour + 15*time.Minute, {
}} testName: "Bookings10hrs",
bookings: testBookings10hrs,
expectedTime: time.Hour*9 + time.Minute*15,
},
}
for _, test := range testCases { for _, tc := range testCases {
t.Run(test.Name, func(t *testing.T) { t.Run("Calc Absence Worktime: "+tc.testName, func(t *testing.T) {
testWorkDay.Bookings = test.bookings var testCase = testWorkDay
testWorkDay.TimeWorkReal(testUser) testCase.Bookings = tc.bookings
testWorkDay.TimePauseReal(testUser) workTime := testCase.GetWorktime(testUser, models.WorktimeBaseDay, false)
testWorkDay.TimeOvertimeReal(testUser) if workTime != tc.expectedTime {
workTime, pauseTime, overTime := testWorkDay.GetAllWorkTimesReal(testUser) t.Errorf("GetWorktimeReal not working, time should be %s, but was %s", helper.FormatDurationFill(tc.expectedTime, true), helper.FormatDurationFill(workTime, true))
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))
} func TestWorkdayWorktimeWeek(t *testing.T) {
if overTime != test.expectedOvertime { testCases := []struct {
t.Errorf("Calculated wrong overtime: should be %s, but was %s", helper.FormatDuration(test.expectedOvertime), helper.FormatDuration(overTime)) 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))
} }
}) })
} }

View File

@@ -20,7 +20,7 @@ type WorkWeek struct {
User User User User
WeekStart time.Time WeekStart time.Time
Worktime time.Duration Worktime time.Duration
WorkTimeVirtual time.Duration WorktimeVirtual time.Duration
Overtime time.Duration Overtime time.Duration
Status WeekStatus Status WeekStatus
} }
@@ -47,17 +47,19 @@ func NewWorkWeek(user User, tsMonday time.Time, populate bool) WorkWeek {
} }
func (w *WorkWeek) PopulateWithDays(worktime time.Duration, overtime time.Duration) { 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())) 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) w.Days = GetDays(w.User, w.WeekStart, w.WeekStart.Add(6*24*time.Hour), false)
for _, day := range w.Days { for _, day := range w.Days {
work, _ := day.TimePauseReal(w.User) w.Worktime += day.GetWorktime(w.User, WorktimeBaseDay, false)
w.Worktime += work w.WorktimeVirtual += day.GetWorktime(w.User, WorktimeBaseDay, true)
w.WorkTimeVirtual += day.TimeWorkVirtual(w.User)
} }
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.Worktime = w.Worktime.Round(time.Minute)
w.Overtime = w.Overtime.Round(time.Minute) w.Overtime = w.Overtime.Round(time.Minute)

View File

@@ -58,71 +58,70 @@ templ CheckboxComponent(id, label string) {
</div> </div>
} }
templ PDFReportEmploye(e models.User, overtime, worktime time.Duration, workDays []models.IWorkDay, tsStart time.Time, tsEnd time.Time) { // templ PDFReportEmploye(e models.User, overtime, worktime time.Duration, workDays []models.IWorkDay, tsStart time.Time, tsEnd time.Time) {
{{ // {{
_, kw := tsStart.ISOWeek() // _, kw := tsStart.ISOWeek()
noBorder := "" // noBorder := ""
}} // }}
@Base() // @Base()
<content class="p-8 relative flex flex-col gap-4 break-after-page"> // <content class="p-8 relative flex flex-col gap-4 break-after-page">
<div> // <div>
<h1 class="text-2xl font-bold">{ e.Vorname } { e.Name }</h1> // <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>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>Arbeitszeit: <span>{ helper.FormatDuration(worktime) }</span></p>
<p>Überstunden: <span>{ helper.FormatDuration(overtime) }</span></p> // <p>Überstunden: <span>{ helper.FormatDuration(overtime) }</span></p>
</div> // </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"> // <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">{ kw }</p>
<p class="bg-neutral-300 border-neutral-600">Kommen</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">Gehen</p>
<p class="bg-neutral-300 border-neutral-600">Arbeitsart</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">Stunden</p>
<p class="bg-neutral-300 border-neutral-600">Pause</p> // <p class="bg-neutral-300 border-neutral-600">Pause</p>
<p class="bg-neutral-300 border-neutral-600 border-r-0">Überstunden</p> // <p class="bg-neutral-300 border-neutral-600 border-r-0">Überstunden</p>
for index, day := range workDays { // for index, day := range workDays {
{{ // {{
if index == len(workDays)-1 { // if index == len(workDays)-1 {
noBorder = "border-b-0" // noBorder = "border-b-0"
} // }
}} // }}
<p class={ noBorder }>{ day.Date().Format("02.01.2006") }</p> // <p class={ noBorder }>{ day.Date().Format("02.01.2006") }</p>
<div class={ "grid grid-cols-subgrid col-span-3 " + noBorder }> // <div class={ "grid grid-cols-subgrid col-span-3 " + noBorder }>
if day.IsWorkDay() { // if day.IsWorkDay() {
{{ // {{
workDay, _ := day.(*models.WorkDay) // workDay, _ := day.(*models.WorkDay)
}} // }}
for bookingI := 0; bookingI < len(workDay.Bookings); bookingI+= 2 { // for bookingI := 0; bookingI < len(workDay.Bookings); bookingI+= 2 {
<p>{ workDay.Bookings[bookingI].Timestamp.Format("15:04") }</p> // <p>{ workDay.Bookings[bookingI].Timestamp.Format("15:04") }</p>
<p>{ workDay.Bookings[bookingI+1].Timestamp.Format("15:04") }</p> // <p>{ workDay.Bookings[bookingI+1].Timestamp.Format("15:04") }</p>
<p>{ workDay.Bookings[bookingI].BookingType.Name } </p> // <p>{ workDay.Bookings[bookingI].BookingType.Name } </p>
} // }
if workDay.IsKurzArbeit() { // if workDay.IsKurzArbeit() {
{{ // {{
timeFrom, timeTo := workDay.GenerateKurzArbeitBookings(e) // timeFrom, timeTo := workDay.GenerateKurzArbeitBookings(e)
}} // }}
<p>{ timeFrom.Format("15:04") }</p> // <p>{ timeFrom.Format("15:04") }</p>
<p>{ timeTo.Format("15:04") }</p> // <p>{ timeTo.Format("15:04") }</p>
<p>Kurzarbeit</p> // <p>Kurzarbeit</p>
} // }
} else { // } else {
{{ // {{
absentDay, _ := day.(*models.Absence) // absentDay, _ := day.(*models.Absence)
}} // }}
<p class="col-span-full">{ absentDay.AbwesenheitTyp.Name }</p> // <p class="col-span-full">{ absentDay.AbwesenheitTyp.Name }</p>
} // }
</div> // </div>
{{ work, pause, overtime := day.GetAllWorkTimesVirtual(e) }} // {{ work, pause, overtime := day.GetTimesVirtual(e) }}
@ColorDuration(work, noBorder) // @ColorDuration(work, noBorder)
@ColorDuration(pause, noBorder) // @ColorDuration(pause, noBorder)
@ColorDuration(overtime, noBorder+" border-r-0") // @ColorDuration(overtime, noBorder+" border-r-0")
if day.Date().Weekday() == time.Friday { // if day.Date().Weekday() == time.Friday {
<p class="col-span-full bg-neutral-300">Wochenende</p> // <p class="col-span-full bg-neutral-300">Wochenende</p>
} // }
} // }
</div> // </div>
</content> // </content>
} // }
templ ColorDuration(d time.Duration, classes string) { templ ColorDuration(d time.Duration, classes string) {
{{ {{
color := "" 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) { 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 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { 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) ctx = templ.ClearChildren(ctx)
_, kw := tsStart.ISOWeek() color := ""
noBorder := "" if d.Abs() < time.Minute {
templ_7745c5c3_Err = Base().Render(ctx, templ_7745c5c3_Buffer) 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 { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err 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\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<p class=\"")
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, " ")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var11 string 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 { 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)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err 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 { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var12 string 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 { 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)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</span> - <span>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</p>")
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>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }

View File

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

View File

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

View File

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

View File

@@ -297,7 +297,8 @@ func defaultDayComponent(day models.IWorkDay) templ.Component {
if day.IsWorkDay() { if day.IsWorkDay() {
workDay, _ := day.(*models.WorkDay) workDay, _ := day.(*models.WorkDay)
work, pause, overtime := workDay.GetAllWorkTimesReal(user) work, pause, overtime := workDay.GetTimes(user, models.WorktimeBaseDay, true)
work = workDay.GetWorktime(user, models.WorktimeBaseDay, false)
if day.RequiresAction() { if day.RequiresAction() {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "<p class=\"text-red-600\">Bitte anpassen</p>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "<p class=\"text-red-600\">Bitte anpassen</p>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -312,7 +313,7 @@ func defaultDayComponent(day models.IWorkDay) templ.Component {
var templ_7745c5c3_Var14 string var templ_7745c5c3_Var14 string
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(helper.FormatDuration(work)) templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(helper.FormatDuration(work))
if templ_7745c5c3_Err != nil { 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)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -335,7 +336,7 @@ func defaultDayComponent(day models.IWorkDay) templ.Component {
var templ_7745c5c3_Var15 string var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(helper.FormatDuration(pause)) templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(helper.FormatDuration(pause))
if templ_7745c5c3_Err != nil { 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)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -358,7 +359,7 @@ func defaultDayComponent(day models.IWorkDay) templ.Component {
var templ_7745c5c3_Var16 string var templ_7745c5c3_Var16 string
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(helper.FormatDuration(overtime)) templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(helper.FormatDuration(overtime))
if templ_7745c5c3_Err != nil { 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)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -391,7 +392,7 @@ func defaultDayComponent(day models.IWorkDay) templ.Component {
var templ_7745c5c3_Var18 string var templ_7745c5c3_Var18 string
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs("time-" + day.Date().Format(time.DateOnly)) templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs("time-" + day.Date().Format(time.DateOnly))
if templ_7745c5c3_Err != nil { 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)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -435,7 +436,7 @@ func defaultDayComponent(day models.IWorkDay) templ.Component {
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
if workDay.IsKurzArbeit() { if workDay.IsKurzArbeit() && len(workDay.Bookings) > 0 {
templ_7745c5c3_Err = absenceComponent(workDay.GetKurzArbeit(), true).Render(ctx, templ_7745c5c3_Buffer) templ_7745c5c3_Err = absenceComponent(workDay.GetKurzArbeit(), true).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
@@ -507,7 +508,7 @@ func absentInput(a models.Absence) templ.Component {
var templ_7745c5c3_Var21 string var templ_7745c5c3_Var21 string
templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(a.DateFrom.Format(time.DateOnly)) templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(a.DateFrom.Format(time.DateOnly))
if templ_7745c5c3_Err != nil { 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)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -520,7 +521,7 @@ func absentInput(a models.Absence) templ.Component {
var templ_7745c5c3_Var22 string var templ_7745c5c3_Var22 string
templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(a.DateTo.Format(time.DateOnly)) templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(a.DateTo.Format(time.DateOnly))
if templ_7745c5c3_Err != nil { 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)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -533,7 +534,7 @@ func absentInput(a models.Absence) templ.Component {
var templ_7745c5c3_Var23 string var templ_7745c5c3_Var23 string
templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(a.AbwesenheitTyp.Id) templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(a.AbwesenheitTyp.Id)
if templ_7745c5c3_Err != nil { 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)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -546,7 +547,7 @@ func absentInput(a models.Absence) templ.Component {
var templ_7745c5c3_Var24 string var templ_7745c5c3_Var24 string
templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(a.CounterId) templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(a.CounterId)
if templ_7745c5c3_Err != nil { 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)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {

View File

@@ -104,7 +104,7 @@ CREATE TABLE "s_abwesenheit_typen" (
"arbeitszeit_equivalent" float4 NOT NULL "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 -- Adds crypto extension

View File

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