seperated pdf-generate endpoint + added new helper in Time

This commit is contained in:
2025-10-28 22:59:33 +01:00
parent 4bc5594dc5
commit cf5238f024
6 changed files with 104 additions and 7 deletions

View File

@@ -89,10 +89,71 @@ func renderPDF(days []typstDay, metadata typstMetadata) (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(200)
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)
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("02.01.2006"), endDate.Format("02.01.2006")),
Overtime: helper.FormatDurationFill(worktimeBalance, true),
WorkTime: helper.FormatDurationFill(actualHours, true),
OvertimeTotal: "",
CurrentTimestamp: time.Now().Format("02.01.2006 - 15:04 Uhr"),
}
return renderPDF(typstDays, metadata)
}
func PDFHandler(w http.ResponseWriter, r *http.Request) { func PDFHandler(w http.ResponseWriter, r *http.Request) {
helper.RequiresLogin(Session, w, r) helper.RequiresLogin(Session, w, r)
pp := paramParser.New(r.URL.Query()) startDate := time.Now()
startDate := pp.ParseTimestamp("start_date", time.DateOnly, time.Now())
if startDate.Day() > 1 { if startDate.Day() > 1 {
startDate = startDate.AddDate(0, 0, -(startDate.Day() - 1)) startDate = startDate.AddDate(0, 0, -(startDate.Day() - 1))
} }

View File

@@ -55,3 +55,16 @@ func FormatDurationFill(d time.Duration, fill bool) string {
func IsSameDate(a, b time.Time) bool { func IsSameDate(a, b time.Time) bool {
return a.Truncate(24 * time.Hour).Equal(b.Truncate(24 * time.Hour)) return a.Truncate(24 * time.Hour).Equal(b.Truncate(24 * time.Hour))
} }
func GetWorkingDays(startDate, endDate time.Time) int {
if endDate.Before(startDate) {
return 0
}
var count int = 0
for d := startDate.Truncate(24 * time.Hour); !d.After(endDate); d = d.Add(24 * time.Hour) {
if !IsWeekend(d) {
count++
}
}
return count
}

View File

@@ -1,6 +1,7 @@
package helper package helper
import ( import (
"fmt"
"testing" "testing"
"time" "time"
) )
@@ -35,3 +36,25 @@ func TestFormatDuration(t *testing.T) {
}) })
} }
} }
func TestGetWorkingDays(t *testing.T) {
testCases := []struct {
start string
end string
days int
}{
{"2025-10-01", "2025-10-02", 2},
{"2025-10-02", "2025-10-01", 0},
{"2025-10-01", "2025-10-31", 23},
}
for _, tc := range testCases {
t.Run(fmt.Sprintf("WorkingDayTest: %d days", tc.days), func(t *testing.T) {
startDate, _ := time.Parse(time.DateOnly, tc.start)
endDate, _ := time.Parse(time.DateOnly, tc.end)
if GetWorkingDays(startDate, endDate) != tc.days {
t.Error("Calculated workdays do not match target")
}
})
}
}

View File

@@ -53,7 +53,7 @@ func main() {
server.HandleFunc("/team", endpoints.TeamHandler) server.HandleFunc("/team", endpoints.TeamHandler)
server.HandleFunc("/presence", endpoints.TeamPresenceHandler) server.HandleFunc("/presence", endpoints.TeamPresenceHandler)
server.Handle("/pdf", ParamsMiddleware(endpoints.PDFFormHandler)) server.Handle("/pdf", ParamsMiddleware(endpoints.PDFFormHandler))
server.HandleFunc("/pdf/generate", endpoints.PDFHandler) server.HandleFunc("/pdf/generate", endpoints.PDFCreateController)
server.Handle("/", http.RedirectHandler("/time", http.StatusPermanentRedirect)) server.Handle("/", http.RedirectHandler("/time", http.StatusPermanentRedirect))
server.Handle("/static/", http.StripPrefix("/static/", fs)) server.Handle("/static/", http.StripPrefix("/static/", fs))

View File

@@ -36,10 +36,10 @@ templ PDFForm(teamMembers []models.User) {
<div></div> <div></div>
</div> </div>
<div class="grid-sub divide-x-1 responsive"> <div class="grid-sub divide-x-1 responsive">
<div class="grid-cell">Direktvorschau oder Download</div> <div class="grid-cell">PDFs Bündeln</div>
<div class="grid-cell col-span-3 flex gap-2 flex-col md:flex-row"> <div class="grid-cell col-span-3 flex gap-2 flex-col md:flex-row">
<button class="btn" type="button" name="action" value="download">Download</button> <button class="btn" type="button" name="action" value="download">Einzeln</button>
<button class="btn" type="button" name="action" value="preview" onclick="">Vorschau</button> <button class="btn" type="button" name="action" value="preview" onclick="">Bündel</button>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -88,7 +88,7 @@ func PDFForm(teamMembers []models.User) templ.Component {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</div><div></div></div><div class=\"grid-sub divide-x-1 responsive\"><div class=\"grid-cell\">Direktvorschau oder Download</div><div class=\"grid-cell col-span-3 flex gap-2 flex-col md:flex-row\"><button class=\"btn\" type=\"button\" name=\"action\" value=\"download\">Download</button> <button class=\"btn\" type=\"button\" name=\"action\" value=\"preview\" onclick=\"\">Vorschau</button></div></div></div>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</div><div></div></div><div class=\"grid-sub divide-x-1 responsive\"><div class=\"grid-cell\">PDFs Bündeln</div><div class=\"grid-cell col-span-3 flex gap-2 flex-col md:flex-row\"><button class=\"btn\" type=\"button\" name=\"action\" value=\"download\">Einzeln</button> <button class=\"btn\" type=\"button\" name=\"action\" value=\"preview\" onclick=\"\">Bündel</button></div></div></div>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }