219 lines
7.4 KiB
Go
219 lines
7.4 KiB
Go
package endpoints
|
|
|
|
import (
|
|
"arbeitszeitmessung/helper"
|
|
"arbeitszeitmessung/helper/paramParser"
|
|
"arbeitszeitmessung/models"
|
|
"bytes"
|
|
"fmt"
|
|
"log"
|
|
"log/slog"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/Dadido3/go-typst"
|
|
)
|
|
|
|
const DE_DATE string = "02.01.2006"
|
|
|
|
func convertDaysToTypst(days []models.IWorkDay, u models.User) ([]typstDay, error) {
|
|
var typstDays []typstDay
|
|
for _, day := range days {
|
|
var thisTypstDay typstDay
|
|
work, pause, overtime := day.GetTimes(u, models.WorktimeBaseDay, false)
|
|
workVirtual := day.GetWorktime(u, models.WorktimeBaseDay, true)
|
|
overtime = workVirtual - u.ArbeitszeitProWocheFrac(0.2)
|
|
thisTypstDay.Date = day.Date().Format(DE_DATE)
|
|
thisTypstDay.Worktime = helper.FormatDurationFill(workVirtual, true)
|
|
thisTypstDay.Pausetime = helper.FormatDurationFill(pause, true)
|
|
thisTypstDay.Overtime = helper.FormatDurationFill(overtime, true)
|
|
thisTypstDay.IsFriday = day.Date().Weekday() == time.Friday
|
|
|
|
if workVirtual > work {
|
|
thisTypstDay.Kurzarbeit = helper.FormatDurationFill(workVirtual-work, true)
|
|
} else {
|
|
thisTypstDay.Kurzarbeit = helper.FormatDurationFill(0, true)
|
|
}
|
|
|
|
thisTypstDay.DayParts = convertDayToTypstDayParts(day, u)
|
|
typstDays = append(typstDays, thisTypstDay)
|
|
}
|
|
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() && len(workDay.Bookings) > 0 {
|
|
tsFrom, tsTo := workDay.GenerateKurzArbeitBookings(user)
|
|
typstDayParts = append(typstDayParts, typstDayPart{
|
|
BookingFrom: tsFrom.Format("15:04"),
|
|
BookingTo: tsTo.Format("15:04"),
|
|
WorkType: "Kurzarbeit",
|
|
IsWorkDay: true,
|
|
})
|
|
}
|
|
if workdayAbsence := workDay.GetWorktimeAbsence(); (workdayAbsence != models.Absence{}) {
|
|
typstDayParts = append(typstDayParts, typstDayPart{IsWorkDay: false, WorkType: workdayAbsence.AbwesenheitTyp.Name})
|
|
}
|
|
|
|
} else {
|
|
absentDay, _ := day.(*models.Absence)
|
|
typstDayParts = append(typstDayParts, typstDayPart{IsWorkDay: false, WorkType: absentDay.AbwesenheitTyp.Name})
|
|
}
|
|
return typstDayParts
|
|
}
|
|
|
|
func PDFCreateController(w http.ResponseWriter, r *http.Request) {
|
|
helper.RequiresLogin(Session, w, r)
|
|
switch r.Method {
|
|
case http.MethodGet:
|
|
user, err := models.GetUserFromSession(Session, r.Context())
|
|
if err != nil {
|
|
log.Println("Error getting user!")
|
|
return
|
|
}
|
|
pp := paramParser.New(r.URL.Query())
|
|
startDate := pp.ParseTimestampFallback("start_date", time.DateOnly, time.Now())
|
|
personalNumbers := pp.ParseIntListFallback("employe_list", ",", make([]int, 0))
|
|
|
|
employes, err := models.GetUserByPersonalNrMulti(personalNumbers)
|
|
if err != nil {
|
|
slog.Warn("Error getting employes!", slog.Any("Error", err))
|
|
return
|
|
}
|
|
|
|
output, err := createReports(user, employes, startDate)
|
|
if err != nil {
|
|
slog.Warn("Could not create pdf report", slog.Any("Error", err))
|
|
}
|
|
|
|
w.Header().Set("Content-type", "application/pdf")
|
|
output.WriteTo(w)
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
default:
|
|
http.Error(w, "Method not allowed!", http.StatusMethodNotAllowed)
|
|
}
|
|
}
|
|
|
|
func createReports(user models.User, employes []models.User, startDate time.Time) (bytes.Buffer, error) {
|
|
startDate = helper.GetFirstOfMonth(startDate)
|
|
endDate := startDate.AddDate(0, 1, -1)
|
|
|
|
var employeData []typstData
|
|
for _, employe := range employes {
|
|
if data, err := createEmployeReport(employe, startDate, endDate); err != nil {
|
|
slog.Warn("Error when creating employeReport", slog.Any("user", employe), slog.Any("error", err))
|
|
} else {
|
|
employeData = append(employeData, data)
|
|
}
|
|
}
|
|
return renderPDF(employeData)
|
|
}
|
|
|
|
func createEmployeReport(employee models.User, startDate, endDate time.Time) (typstData, error) {
|
|
targetHoursThisMonth := employee.ArbeitszeitProWocheFrac(.2) * time.Duration(helper.GetWorkingDays(startDate, endDate))
|
|
workDaysThisMonth := models.GetDays(employee, startDate, endDate.AddDate(0, 0, 1), false)
|
|
|
|
slog.Debug("Baseline Working hours", "targetHours", targetHoursThisMonth.Hours())
|
|
|
|
var workHours, kurzarbeitHours time.Duration
|
|
for _, day := range workDaysThisMonth {
|
|
tmpvirtualHours := day.GetWorktime(employee, models.WorktimeBaseDay, true)
|
|
tmpactualHours := day.GetWorktime(employee, models.WorktimeBaseDay, false)
|
|
if day.IsKurzArbeit() && tmpvirtualHours > tmpactualHours {
|
|
slog.Debug("Adding kurzarbeit to workday", "day", day.Date())
|
|
kurzarbeitHours += tmpvirtualHours - tmpactualHours
|
|
}
|
|
workHours += tmpvirtualHours
|
|
}
|
|
worktimeBalance := workHours - targetHoursThisMonth
|
|
|
|
typstDays, err := convertDaysToTypst(workDaysThisMonth, employee)
|
|
if err != nil {
|
|
slog.Warn("Failed to convert to days", slog.Any("error", err))
|
|
return typstData{}, err
|
|
}
|
|
|
|
metadata := typstMetadata{
|
|
EmployeeName: fmt.Sprintf("%s %s", employee.Vorname, employee.Name),
|
|
TimeRange: fmt.Sprintf("%s - %s", startDate.Format(DE_DATE), endDate.Format(DE_DATE)),
|
|
Overtime: helper.FormatDurationFill(worktimeBalance, true),
|
|
WorkTime: helper.FormatDurationFill(workHours, true),
|
|
Kurzarbeit: helper.FormatDurationFill(kurzarbeitHours, true),
|
|
OvertimeTotal: "",
|
|
CurrentTimestamp: time.Now().Format("02.01.2006 - 15:04 Uhr"),
|
|
}
|
|
return typstData{Meta: metadata, Days: typstDays}, nil
|
|
}
|
|
|
|
func renderPDF(data []typstData) (bytes.Buffer, error) {
|
|
var markup bytes.Buffer
|
|
var output bytes.Buffer
|
|
|
|
if err := typst.InjectValues(&markup, map[string]any{"data": data}); 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 "templates/abrechnung.typ": abrechnung
|
|
#for d in data {
|
|
abrechnung(d.Meta, d.Days)
|
|
}
|
|
`)
|
|
|
|
// Compile the prepared markup with Typst and write the result it into `output.pdf`.
|
|
|
|
typstCLI := typst.DockerExec{
|
|
ContainerName: helper.GetEnv("TYPST_CONTAINER", "arbeitszeitmessung-doc-creator"),
|
|
}
|
|
if err := typstCLI.Compile(&markup, &output, nil); err != nil {
|
|
return output, err
|
|
}
|
|
return output, nil
|
|
}
|
|
|
|
type typstMetadata struct {
|
|
TimeRange string `json:"time-range"`
|
|
EmployeeName string `json:"employee-name"`
|
|
WorkTime string `json:"worktime"`
|
|
Kurzarbeit string `json:"kurzarbeit"`
|
|
Overtime string `json:"overtime"`
|
|
OvertimeTotal string `json:"overtime-total"`
|
|
CurrentTimestamp string `json:"current-timestamp"`
|
|
}
|
|
|
|
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"`
|
|
Kurzarbeit string `json:"kurzarbeit"`
|
|
IsFriday bool `json:"is-weekend"`
|
|
}
|
|
|
|
type typstData struct {
|
|
Meta typstMetadata `json:"meta"`
|
|
Days []typstDay `json:"days"`
|
|
}
|