separaded endpoints + cleaned page templates + added constants to time formatting
Some checks failed
Tests / Run Go Tests (push) Failing after 1m34s

This commit is contained in:
2025-10-27 22:53:07 +01:00
parent e1f0f85401
commit a634b7a69e
26 changed files with 654 additions and 639 deletions

View File

@@ -0,0 +1,161 @@
package endpoints
import (
"arbeitszeitmessung/helper"
"arbeitszeitmessung/helper/paramParser"
"arbeitszeitmessung/models"
"bytes"
"fmt"
"log"
"log/slog"
"net/http"
"time"
"github.com/Dadido3/go-typst"
)
func convertDaysToTypst(days []models.IWorkDay, u models.User) ([]typstDay, error) {
var typstDays []typstDay
for _, day := range days {
var thisTypstDay typstDay
work, pause, overtime := day.GetAllWorkTimesVirtual(u)
thisTypstDay.Date = day.Date().Format("02.01.2006")
thisTypstDay.Worktime = helper.FormatDurationFill(work, true)
thisTypstDay.Pausetime = helper.FormatDurationFill(pause, true)
thisTypstDay.Overtime = helper.FormatDurationFill(overtime, true)
thisTypstDay.IsFriday = day.Date().Weekday() == time.Friday
thisTypstDay.DayParts = convertDayToTypstDayParts(day, u)
typstDays = append(typstDays, thisTypstDay)
}
return typstDays, nil
}
func convertDayToTypstDayParts(day models.IWorkDay, user models.User) []typstDayPart {
var typstDayParts []typstDayPart
if day.IsWorkDay() {
workDay, _ := day.(*models.WorkDay)
for i := 0; i < len(workDay.Bookings); i += 2 {
var typstDayPart typstDayPart
typstDayPart.BookingFrom = workDay.Bookings[i].Timestamp.Format("15:04")
typstDayPart.BookingTo = workDay.Bookings[i+1].Timestamp.Format("15:04")
typstDayPart.WorkType = workDay.Bookings[i].BookingType.Name
typstDayPart.IsWorkDay = true
typstDayParts = append(typstDayParts, typstDayPart)
}
if day.IsKurzArbeit() {
tsFrom, tsTo := workDay.GenerateKurzArbeitBookings(user)
typstDayParts = append(typstDayParts, typstDayPart{
BookingFrom: tsFrom.Format("15:04"),
BookingTo: tsTo.Format("15:04"),
WorkType: "Kurzarbeit",
IsWorkDay: true,
})
}
} else {
absentDay, _ := day.(*models.Absence)
typstDayParts = append(typstDayParts, typstDayPart{IsWorkDay: false, WorkType: absentDay.AbwesenheitTyp.Name})
}
return typstDayParts
}
func renderPDF(days []typstDay, metadata typstMetadata) (bytes.Buffer, error) {
var markup bytes.Buffer
var output bytes.Buffer
if err := typst.InjectValues(&markup, map[string]any{"meta": metadata, "days": days}); err != nil {
return output, err
}
// Import the template and invoke the template function with the custom data.
// Show is used to replace the current document with whatever content the template function in `template.typ` returns.
markup.WriteString(`
#import "template.typ": abrechnung
#show: doc => abrechnung(meta, days)
`)
// Compile the prepared markup with Typst and write the result it into `output.pdf`.
// f, err := os.Create("output.pdf")
// if err != nil {
// log.Panicf("Failed to create output file: %v.", err)
// }
// defer f.Close()
//
typstCLI := typst.CLI{}
if err := typstCLI.Compile(&markup, &output, nil); err != nil {
return output, err
}
return output, nil
}
func PDFHandler(w http.ResponseWriter, r *http.Request) {
helper.RequiresLogin(Session, w, r)
pp := paramParser.New(r.URL.Query())
startDate := pp.ParseTimestamp("start_date", time.DateOnly, 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("02.01.2006"), endDate.Format("02.01.2006")),
Overtime: helper.FormatDurationFill(aggregatedOvertime, true),
WorkTime: helper.FormatDurationFill(aggregatedWorkTime, true),
OvertimeTotal: "",
CurrentTimestamp: time.Now().Format("02.01.2006 - 15:04 Uhr"),
}
output, err := renderPDF(typstDays, metadata)
if err != nil {
slog.Warn("Could not create pdf report", slog.Any("Error", err))
}
w.Header().Set("Content-type", "application/pdf")
output.WriteTo(w)
w.WriteHeader(200)
}
type typstMetadata struct {
TimeRange string `json:"time-range"`
EmployeeName string `json:"employee-name"`
WorkTime string `json:"worktime"`
Overtime string `json:"overtime"`
OvertimeTotal string `json:"overtime-total"`
CurrentTimestamp string `json:"current-timestamp"`
}
type typstDayPart struct {
BookingFrom string `json:"booking-from"`
BookingTo string `json:"booking-to"`
WorkType string `json:"worktype"`
IsWorkDay bool `json:"is-workday"`
}
type typstDay struct {
Date string `json:"date"`
DayParts []typstDayPart `json:"day-parts"`
Worktime string `json:"worktime"`
Pausetime string `json:"pausetime"`
Overtime string `json:"overtime"`
IsFriday bool `json:"is-weekend"`
}

View File

@@ -4,116 +4,10 @@ import (
"arbeitszeitmessung/helper"
"arbeitszeitmessung/models"
"arbeitszeitmessung/templates"
"bytes"
"fmt"
"log"
"log/slog"
"net/http"
"time"
"github.com/Dadido3/go-typst"
)
type typstMetadata struct {
TimeRange string `json:"time-range"`
EmployeeName string `json:"employee-name"`
WorkTime string `json:"worktime"`
Overtime string `json:"overtime"`
OvertimeTotal string `json:"overtime-total"`
CurrentTimestamp string `json:"current-timestamp"`
}
type typstDayPart struct {
BookingFrom string `json:"booking-from"`
BookingTo string `json:"booking-to"`
WorkType string `json:"worktype"`
IsWorkDay bool `json:"is-workday"`
}
type typstDay struct {
Date string `json:"date"`
DayParts []typstDayPart `json:"day-parts"`
Worktime string `json:"worktime"`
Pausetime string `json:"pausetime"`
Overtime string `json:"overtime"`
IsFriday bool `json:"is-weekend"`
}
func convertDaysToTypst(days []models.IWorkDay, u models.User) ([]typstDay, error) {
var typstDays []typstDay
for _, day := range days {
var thisTypstDay typstDay
work, pause, overtime := day.GetAllWorkTimesVirtual(u)
thisTypstDay.Date = day.Date().Format("02.01.2006")
thisTypstDay.Worktime = helper.FormatDurationFill(work, true)
thisTypstDay.Pausetime = helper.FormatDurationFill(pause, true)
thisTypstDay.Overtime = helper.FormatDurationFill(overtime, true)
thisTypstDay.IsFriday = day.Date().Weekday() == time.Friday
thisTypstDay.DayParts = convertDayToTypstDayParts(day, u)
typstDays = append(typstDays, thisTypstDay)
}
return typstDays, nil
}
func convertDayToTypstDayParts(day models.IWorkDay, user models.User) []typstDayPart {
var typstDayParts []typstDayPart
if day.IsWorkDay() {
workDay, _ := day.(*models.WorkDay)
for i := 0; i < len(workDay.Bookings); i += 2 {
var typstDayPart typstDayPart
typstDayPart.BookingFrom = workDay.Bookings[i].Timestamp.Format("15:04")
typstDayPart.BookingTo = workDay.Bookings[i+1].Timestamp.Format("15:04")
typstDayPart.WorkType = workDay.Bookings[i].BookingType.Name
typstDayPart.IsWorkDay = true
typstDayParts = append(typstDayParts, typstDayPart)
}
if day.IsKurzArbeit() {
tsFrom, tsTo := workDay.GenerateKurzArbeitBookings(user)
typstDayParts = append(typstDayParts, typstDayPart{
BookingFrom: tsFrom.Format("15:04"),
BookingTo: tsTo.Format("15:04"),
WorkType: "Kurzarbeit",
IsWorkDay: true,
})
}
} else {
absentDay, _ := day.(*models.Absence)
typstDayParts = append(typstDayParts, typstDayPart{IsWorkDay: false, WorkType: absentDay.AbwesenheitTyp.Name})
}
return typstDayParts
}
func renderPDF(days []typstDay, metadata typstMetadata) (bytes.Buffer, error) {
var markup bytes.Buffer
var output bytes.Buffer
if err := typst.InjectValues(&markup, map[string]any{"meta": metadata, "days": days}); err != nil {
return output, err
}
// Import the template and invoke the template function with the custom data.
// Show is used to replace the current document with whatever content the template function in `template.typ` returns.
markup.WriteString(`
#import "template.typ": abrechnung
#show: doc => abrechnung(meta, days)
`)
// Compile the prepared markup with Typst and write the result it into `output.pdf`.
// f, err := os.Create("output.pdf")
// if err != nil {
// log.Panicf("Failed to create output file: %v.", err)
// }
// defer f.Close()
//
typstCLI := typst.CLI{}
if err := typstCLI.Compile(&markup, &output, nil); err != nil {
return output, err
}
return output, nil
}
func PDFFormHandler(w http.ResponseWriter, r *http.Request) {
helper.RequiresLogin(Session, w, r)
@@ -129,53 +23,3 @@ func PDFFormHandler(w http.ResponseWriter, r *http.Request) {
}
templates.PDFForm(teamMembers).Render(r.Context(), w)
}
func PDFHandler(w http.ResponseWriter, r *http.Request) {
helper.RequiresLogin(Session, w, r)
startDate, err := parseTimestamp(r, "start_date", time.Now().Format("2006-01-02"))
if err != nil {
log.Println("Error parsing 'start_date' time", err)
http.Error(w, "Timestamp 'start_date' cannot be parsed!", http.StatusBadRequest)
return
}
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("02.01.2006"), endDate.Format("02.01.2006")),
Overtime: helper.FormatDurationFill(aggregatedOvertime, true),
WorkTime: helper.FormatDurationFill(aggregatedWorkTime, true),
OvertimeTotal: "",
CurrentTimestamp: time.Now().Format("02.01.2006 - 15:04 Uhr"),
}
output, err := renderPDF(typstDays, metadata)
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)
}

View File

@@ -65,7 +65,7 @@ func showWeeks(w http.ResponseWriter, r *http.Request) {
submissionDate := r.URL.Query().Get("submission_date")
lastSub := user.GetLastWorkWeekSubmission()
if submissionDate != "" {
submissionDate, err := time.Parse("2006-01-02", submissionDate)
submissionDate, err := time.Parse(time.DateOnly, submissionDate)
if err == nil {
lastSub = helper.GetMonday(submissionDate)
}

View File

@@ -52,7 +52,7 @@ func parseTimestamp(r *http.Request, getKey string, fallback string) (time.Time,
if getTimestamp == "" {
getTimestamp = fallback
}
Timestamp, err := time.Parse("2006-01-02", getTimestamp)
Timestamp, err := time.Parse(time.DateOnly, getTimestamp)
if err != nil {
return time.Now(), err
}
@@ -69,13 +69,13 @@ func getBookings(w http.ResponseWriter, r *http.Request) {
}
// TODO add config for timeoffset
tsFrom, err := parseTimestamp(r, "time_from", time.Now().AddDate(0, -1, 0).Format("2006-01-02"))
tsFrom, err := parseTimestamp(r, "time_from", time.Now().AddDate(0, -1, 0).Format(time.DateOnly))
if err != nil {
log.Println("Error parsing 'from' time", err)
http.Error(w, "Timestamp 'from' cannot be parsed!", http.StatusBadRequest)
return
}
tsTo, err := parseTimestamp(r, "time_to", time.Now().Format("2006-01-02"))
tsTo, err := parseTimestamp(r, "time_to", time.Now().Format(time.DateOnly))
if err != nil {
log.Println("Error parsing 'to' time", err)
http.Error(w, "Timestamp 'to' cannot be parsed!", http.StatusBadRequest)
@@ -193,13 +193,13 @@ func updateAbsence(r *http.Request) error {
loc = time.Local
}
dateFrom, err := time.ParseInLocation("2006-01-02", r.FormValue("date_from"), loc)
dateFrom, err := time.ParseInLocation(time.DateOnly, r.FormValue("date_from"), loc)
if err != nil {
log.Println("Error parsing date_from input for absence", err)
return err
}
dateTo, err := time.ParseInLocation("2006-01-02", r.FormValue("date_to"), loc)
dateTo, err := time.ParseInLocation(time.DateOnly, r.FormValue("date_to"), loc)
if err != nil {
log.Println("Error parsing date_to input for absence", err)
return err

View File

@@ -43,5 +43,5 @@ func showUserPage(w http.ResponseWriter, r *http.Request, status int) {
if user, err := models.GetUserFromSession(Session, r.Context()); err == nil {
ctx = context.WithValue(r.Context(), "user", user)
}
templates.UserPage(status).Render(ctx, w)
templates.SettingsPage(status).Render(ctx, w)
}