dev/finalFixes #81
@@ -21,66 +21,7 @@ import (
|
|||||||
const DE_DATE string = "02.01.2006"
|
const DE_DATE string = "02.01.2006"
|
||||||
const FILE_YEAR_MONTH string = "2006_01"
|
const FILE_YEAR_MONTH string = "2006_01"
|
||||||
|
|
||||||
func convertDaysToTypst(days []models.IWorkDay, u models.User) ([]typstDay, error) {
|
const PDF_DIRECTORY = "/home/tom/Code/arbeitszeitmessung/Backend/doc"
|
||||||
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
|
|
||||||
switch day.Type() {
|
|
||||||
case models.DayTypeWorkday:
|
|
||||||
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")
|
|
||||||
if i+1 < len(workDay.Bookings) {
|
|
||||||
typstDayPart.BookingTo = workDay.Bookings[i+1].Timestamp.Format("15:04")
|
|
||||||
} else {
|
|
||||||
typstDayPart.BookingTo = workDay.Bookings[i].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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
case models.DayTypeCompound:
|
|
||||||
for _, c := range day.(*models.CompoundDay).DayParts {
|
|
||||||
typstDayParts = append(typstDayParts, convertDayToTypstDayParts(c, user)...)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
typstDayParts = append(typstDayParts, typstDayPart{IsWorkDay: false, WorkType: day.ToString()})
|
|
||||||
}
|
|
||||||
return typstDayParts
|
|
||||||
}
|
|
||||||
|
|
||||||
func PDFCreateController(w http.ResponseWriter, r *http.Request) {
|
func PDFCreateController(w http.ResponseWriter, r *http.Request) {
|
||||||
helper.RequiresLogin(Session, w, r)
|
helper.RequiresLogin(Session, w, r)
|
||||||
@@ -101,14 +42,16 @@ func PDFCreateController(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
n := 0
|
if !helper.IsDebug() {
|
||||||
for _, e := range employes {
|
n := 0
|
||||||
if user.IsSuperior(e) {
|
for _, e := range employes {
|
||||||
employes[n] = e
|
if user.IsSuperior(e) {
|
||||||
n++
|
employes[n] = e
|
||||||
|
n++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
employes = employes[:n]
|
||||||
}
|
}
|
||||||
employes = employes[:n]
|
|
||||||
|
|
||||||
reportData := createReports(employes, startDate)
|
reportData := createReports(employes, startDate)
|
||||||
|
|
||||||
@@ -119,8 +62,9 @@ func PDFCreateController(w http.ResponseWriter, r *http.Request) {
|
|||||||
slog.Warn("Could not create pdf report", slog.Any("Error", err))
|
slog.Warn("Could not create pdf report", slog.Any("Error", err))
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-type", "application/pdf")
|
w.Header().Set("Content-type", "application/pdf")
|
||||||
w.Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=Monatsabrechnung_%s", startDate.Format(FILE_YEAR_MONTH)))
|
w.Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=Monatsabrechnung_%s.pdf", startDate.Format(FILE_YEAR_MONTH)))
|
||||||
output.WriteTo(w)
|
output.WriteTo(w)
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
case "download":
|
case "download":
|
||||||
@@ -131,11 +75,12 @@ func PDFCreateController(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
output, err := zipPfd(pdfReports, &reportData)
|
output, err := zipPfd(pdfReports, &reportData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Warn("Could not create pdf report", slog.Any("Error", err))
|
slog.Warn("Could not zip pdf reports", slog.Any("Error", err))
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-type", "application/zip")
|
w.Header().Set("Content-type", "application/zip")
|
||||||
w.Header().Set("Content-Disposition", fmt.Sprintf("attachement; filename=Monatsabrechnung_%s", startDate.Format(FILE_YEAR_MONTH)))
|
w.Header().Set("Content-Disposition", fmt.Sprintf("attachement; filename=Monatsabrechnung_%s.zip", startDate.Format(FILE_YEAR_MONTH)))
|
||||||
output.WriteTo(w)
|
output.WriteTo(w)
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
}
|
}
|
||||||
@@ -145,6 +90,73 @@ func PDFCreateController(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func convertDaysToTypst(days []models.IWorkDay, u models.User, weekbase models.WorktimeBase) ([]typstDay, error) {
|
||||||
|
var typstDays []typstDay
|
||||||
|
for i, day := range days {
|
||||||
|
if !day.IsSubmittedAndAccepted() && !helper.IsDebug() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var thisTypstDay typstDay
|
||||||
|
workVirtual, pause, overtime := day.GetTimes(u, weekbase, true)
|
||||||
|
|
||||||
|
if day.Type() != models.DayTypeHoliday {
|
||||||
|
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 = i == len(days)-1
|
||||||
|
|
||||||
|
if work := day.GetWorktime(u, weekbase, false); workVirtual > work {
|
||||||
|
thisTypstDay.Kurzarbeit = helper.FormatDurationFill(workVirtual-work, true)
|
||||||
|
} else {
|
||||||
|
thisTypstDay.Kurzarbeit = helper.FormatDurationFill(0, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
thisTypstDay.DayParts = convertDayToTypstDayParts(day, u, weekbase)
|
||||||
|
typstDays = append(typstDays, thisTypstDay)
|
||||||
|
}
|
||||||
|
return typstDays, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertDayToTypstDayParts(day models.IWorkDay, user models.User, weekBase models.WorktimeBase) []typstDayPart {
|
||||||
|
var typstDayParts []typstDayPart
|
||||||
|
switch day.Type() {
|
||||||
|
case models.DayTypeWorkday:
|
||||||
|
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")
|
||||||
|
if i+1 < len(workDay.Bookings) {
|
||||||
|
typstDayPart.BookingTo = workDay.Bookings[i+1].Timestamp.Format("15:04")
|
||||||
|
} else {
|
||||||
|
typstDayPart.BookingTo = workDay.Bookings[i].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, weekBase)
|
||||||
|
typstDayParts = append(typstDayParts, typstDayPart{
|
||||||
|
BookingFrom: tsFrom.Format("15:04"),
|
||||||
|
BookingTo: tsTo.Format("15:04"),
|
||||||
|
WorkType: "Kurzarbeit",
|
||||||
|
IsWorkDay: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
case models.DayTypeCompound:
|
||||||
|
for _, c := range day.(*models.CompoundDay).DayParts {
|
||||||
|
typstDayParts = append(typstDayParts, convertDayToTypstDayParts(c, user, weekBase)...)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
typstDayParts = append(typstDayParts, typstDayPart{IsWorkDay: false, WorkType: day.ToString()})
|
||||||
|
}
|
||||||
|
return typstDayParts
|
||||||
|
}
|
||||||
|
|
||||||
func createReports(employes []models.User, startDate time.Time) []typstData {
|
func createReports(employes []models.User, startDate time.Time) []typstData {
|
||||||
startDate = helper.GetFirstOfMonth(startDate)
|
startDate = helper.GetFirstOfMonth(startDate)
|
||||||
endDate := startDate.AddDate(0, 1, -1)
|
endDate := startDate.AddDate(0, 1, -1)
|
||||||
@@ -161,28 +173,67 @@ func createReports(employes []models.User, startDate time.Time) []typstData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func createEmployeReport(employee models.User, startDate, endDate time.Time) (typstData, error) {
|
func createEmployeReport(employee models.User, startDate, endDate time.Time) (typstData, error) {
|
||||||
publicHolidays, err := models.GetHolidaysFromTo(startDate, endDate)
|
publicHolidays, _ := models.GetHolidaysFromTo(startDate, endDate)
|
||||||
targetHoursThisMonth := employee.ArbeitszeitProWocheFrac(.2) * time.Duration(helper.GetWorkingDays(startDate, endDate)-len(publicHolidays))
|
targetHoursThisMonth := employee.ArbeitszeitProWocheFrac(.2) * time.Duration(helper.GetWorkingDays(startDate, endDate)-len(publicHolidays))
|
||||||
workDaysThisMonth := models.GetDays(employee, startDate, endDate.AddDate(0, 0, 1), false)
|
daysThisMonth := helper.GenerateDateRange(startDate, endDate)
|
||||||
|
mondaysThisMonth := helper.GetMondays(daysThisMonth, false)
|
||||||
slog.Debug("Baseline Working hours", "targetHours", targetHoursThisMonth.Hours())
|
|
||||||
|
|
||||||
|
var weeks []models.WorkWeek
|
||||||
var workHours, kurzarbeitHours time.Duration
|
var workHours, kurzarbeitHours time.Duration
|
||||||
for _, day := range workDaysThisMonth {
|
for _, monday := range mondaysThisMonth {
|
||||||
tmpvirtualHours := day.GetWorktime(employee, models.WorktimeBaseDay, true)
|
var week models.WorkWeek
|
||||||
tmpactualHours := day.GetWorktime(employee, models.WorktimeBaseDay, false)
|
if monday.After(startDate) {
|
||||||
if day.IsKurzArbeit() && tmpvirtualHours > tmpactualHours {
|
week = models.NewWorkWeekSimple(employee, monday, true)
|
||||||
slog.Debug("Adding kurzarbeit to workday", "day", day.Date())
|
} else if startDate.Sub(monday) < time.Hour*24*6 {
|
||||||
kurzarbeitHours += tmpvirtualHours - tmpactualHours
|
week = models.NewWorkWeek(employee, startDate, monday.Add(6*24*time.Hour), true)
|
||||||
}
|
}
|
||||||
workHours += tmpvirtualHours
|
workHours += week.WorktimeVirtual
|
||||||
|
kurzarbeitHours += week.WorktimeVirtual - week.Worktime
|
||||||
|
weeks = append(weeks, week)
|
||||||
}
|
}
|
||||||
|
var typstDays []typstDay
|
||||||
|
for _, week := range weeks {
|
||||||
|
weekTypstDays, err := convertDaysToTypst(week.Days, employee, week.WeekBase)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Error converting days into typst", "error", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
typstDays = append(typstDays, weekTypstDays...)
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Info("Weeks for the month", "week len", len(weeks), "week", weeks)
|
||||||
|
// workDaysThisMonth := models.GetDays(employee, startDate, endDate.AddDate(0, 0, 1), false)
|
||||||
|
|
||||||
|
// var weekbase models.WorktimeBase
|
||||||
|
// if lenWorkDays(workDaysThisMonth) == helper.GetWorkingDays(startDate, endDate) {
|
||||||
|
// weekbase = models.WorktimeBaseWeek
|
||||||
|
// } else {
|
||||||
|
// weekbase = models.WorktimeBaseDay
|
||||||
|
// }
|
||||||
|
|
||||||
|
// slog.Debug("Baseline Working hours", "targetHours", targetHoursThisMonth.Hours(), "days", helper.GetWorkingDays(startDate, endDate), "workdays", lenWorkDays(workDaysThisMonth))
|
||||||
|
|
||||||
|
// var workHours, kurzarbeitHours time.Duration
|
||||||
|
// for _, day := range workDaysThisMonth {
|
||||||
|
// tmpvirtualHours := day.GetWorktime(employee, weekbase, true)
|
||||||
|
// tmpactualHours := day.GetWorktime(employee, weekbase, false)
|
||||||
|
// if day.IsKurzArbeit() && tmpvirtualHours > tmpactualHours {
|
||||||
|
// slog.Debug("Adding kurzarbeit to workday", "day", day.Date())
|
||||||
|
// kurzarbeitHours += tmpvirtualHours - tmpactualHours
|
||||||
|
// }
|
||||||
|
// workHours += tmpvirtualHours
|
||||||
|
// }
|
||||||
worktimeBalance := workHours - targetHoursThisMonth
|
worktimeBalance := workHours - targetHoursThisMonth
|
||||||
|
|
||||||
typstDays, err := convertDaysToTypst(workDaysThisMonth, employee)
|
// typstDays, err := convertDaysToTypst(workDaysThisMonth, employee, weekbase)
|
||||||
|
// if err != nil {
|
||||||
|
// slog.Warn("Failed to convert to days", slog.Any("error", err))
|
||||||
|
// return typstData{}, err
|
||||||
|
// }
|
||||||
|
|
||||||
|
totalOvertime, err := employee.GetReportedOvertime(endDate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Warn("Failed to convert to days", slog.Any("error", err))
|
slog.Error("Cannot retrieve total Overtime", "Error", err)
|
||||||
return typstData{}, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
metadata := typstMetadata{
|
metadata := typstMetadata{
|
||||||
@@ -191,7 +242,7 @@ func createEmployeReport(employee models.User, startDate, endDate time.Time) (ty
|
|||||||
Overtime: helper.FormatDurationFill(worktimeBalance, true),
|
Overtime: helper.FormatDurationFill(worktimeBalance, true),
|
||||||
WorkTime: helper.FormatDurationFill(workHours, true),
|
WorkTime: helper.FormatDurationFill(workHours, true),
|
||||||
Kurzarbeit: helper.FormatDurationFill(kurzarbeitHours, true),
|
Kurzarbeit: helper.FormatDurationFill(kurzarbeitHours, true),
|
||||||
OvertimeTotal: "",
|
OvertimeTotal: helper.FormatDurationFill(totalOvertime+worktimeBalance, true),
|
||||||
CurrentTimestamp: time.Now().Format("02.01.2006 - 15:04 Uhr"),
|
CurrentTimestamp: time.Now().Format("02.01.2006 - 15:04 Uhr"),
|
||||||
}
|
}
|
||||||
return typstData{Meta: metadata, Days: typstDays, FileName: fmt.Sprintf("%s_%s.pdf", startDate.Format(FILE_YEAR_MONTH), employee.Name)}, nil
|
return typstData{Meta: metadata, Days: typstDays, FileName: fmt.Sprintf("%s_%s.pdf", startDate.Format(FILE_YEAR_MONTH), employee.Name)}, nil
|
||||||
@@ -202,8 +253,7 @@ func renderPDFSingle(data []typstData) (bytes.Buffer, error) {
|
|||||||
var output bytes.Buffer
|
var output bytes.Buffer
|
||||||
|
|
||||||
typstCLI := typst.CLI{
|
typstCLI := typst.CLI{
|
||||||
WorkingDirectory: "/doc/",
|
WorkingDirectory: PDF_DIRECTORY,
|
||||||
// ContainerName: helper.GetEnv("TYPST_CONTAINER", "arbeitszeitmessung-doc-creator"),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := typst.InjectValues(&markup, map[string]any{"data": data}); err != nil {
|
if err := typst.InjectValues(&markup, map[string]any{"data": data}); err != nil {
|
||||||
@@ -230,8 +280,7 @@ func renderPDFMulti(data []typstData) ([]bytes.Buffer, error) {
|
|||||||
var outputMulti []bytes.Buffer
|
var outputMulti []bytes.Buffer
|
||||||
|
|
||||||
typstRender := typst.CLI{
|
typstRender := typst.CLI{
|
||||||
WorkingDirectory: "/doc/",
|
WorkingDirectory: PDF_DIRECTORY,
|
||||||
// ContainerName: helper.GetEnv("TYPST_CONTAINER", "arbeitszeitmessung-doc-creator"),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, d := range data {
|
for _, d := range data {
|
||||||
@@ -273,6 +322,16 @@ func zipPfd(pdfReports []bytes.Buffer, reportData *[]typstData) (bytes.Buffer, e
|
|||||||
return zipOutput, err
|
return zipOutput, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func lenWorkDays(workDays []models.IWorkDay) int {
|
||||||
|
var lenght int
|
||||||
|
for _, day := range workDays {
|
||||||
|
if !day.IsEmpty() || day.IsKurzArbeit() {
|
||||||
|
lenght += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lenght
|
||||||
|
}
|
||||||
|
|
||||||
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"`
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ func submitReport(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
workWeek := models.NewWorkWeek(user, weekTs, true)
|
workWeek := models.NewWorkWeekSimple(user, weekTs, true)
|
||||||
|
|
||||||
switch r.FormValue("method") {
|
switch r.FormValue("method") {
|
||||||
case "send":
|
case "send":
|
||||||
@@ -70,7 +70,7 @@ func showWeeks(w http.ResponseWriter, r *http.Request) {
|
|||||||
submissionDate := pp.ParseTimestampFallback("submission_date", time.DateOnly, user.GetLastWorkWeekSubmission())
|
submissionDate := pp.ParseTimestampFallback("submission_date", time.DateOnly, user.GetLastWorkWeekSubmission())
|
||||||
lastSub := helper.GetMonday(submissionDate)
|
lastSub := helper.GetMonday(submissionDate)
|
||||||
|
|
||||||
userWeek := models.NewWorkWeek(user, lastSub, true)
|
userWeek := models.NewWorkWeekSimple(user, lastSub, true)
|
||||||
|
|
||||||
var workWeeks []models.WorkWeek
|
var workWeeks []models.WorkWeek
|
||||||
teamMembers, err := user.GetTeamMembers()
|
teamMembers, err := user.GetTeamMembers()
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ func getBookings(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
aggregatedOvertime += day.GetOvertime(user, models.WorktimeBaseDay, true)
|
aggregatedOvertime += day.GetOvertime(user, models.WorktimeBaseDay, true)
|
||||||
}
|
}
|
||||||
if reportedOvertime, err := user.GetReportedOvertime(); err == nil {
|
if reportedOvertime, err := user.GetReportedOvertime(time.Now()); err == nil {
|
||||||
user.Overtime = (reportedOvertime + aggregatedOvertime).Round(time.Minute)
|
user.Overtime = (reportedOvertime + aggregatedOvertime).Round(time.Minute)
|
||||||
} else {
|
} else {
|
||||||
log.Println("Cannot calculate overtime: ", err)
|
log.Println("Cannot calculate overtime: ", err)
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ func GetEnv(key, fallback string) string {
|
|||||||
return fallback
|
return fallback
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsDebug() bool {
|
||||||
|
return GetEnv("GO_ENV", "production") == "debug"
|
||||||
|
}
|
||||||
|
|
||||||
type CacheItem struct {
|
type CacheItem struct {
|
||||||
value any
|
value any
|
||||||
expiration time.Time
|
expiration time.Time
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ package helper
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -18,6 +19,64 @@ func GetMonday(ts time.Time) time.Time {
|
|||||||
return ts
|
return ts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetMondays(allDays []time.Time, onlyInRange bool) []time.Time {
|
||||||
|
var mondays []time.Time
|
||||||
|
var start, end time.Time
|
||||||
|
|
||||||
|
for _, day := range allDays {
|
||||||
|
mondays = append(mondays, GetMonday(day))
|
||||||
|
|
||||||
|
if start.IsZero() || day.Before(start) {
|
||||||
|
start = day
|
||||||
|
}
|
||||||
|
if end.IsZero() || day.After(end) {
|
||||||
|
end = day
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mondays = slices.Compact(mondays)
|
||||||
|
if onlyInRange {
|
||||||
|
return DaysInRange(mondays, start, end)
|
||||||
|
}
|
||||||
|
return mondays
|
||||||
|
}
|
||||||
|
|
||||||
|
func DaysInRange(days []time.Time, startDate, endDate time.Time) []time.Time {
|
||||||
|
filtered := []time.Time{}
|
||||||
|
startDate = startDate.Add(-time.Minute)
|
||||||
|
endDate = endDate.Add(time.Minute)
|
||||||
|
|
||||||
|
for _, day := range days {
|
||||||
|
if day.After(startDate) && day.Before(endDate) {
|
||||||
|
filtered = append(filtered, day)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filtered
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsMonday(day time.Time) bool {
|
||||||
|
return day.Weekday() == time.Monday
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateDateRange returns a slice of all dates between start and end (inclusive).
|
||||||
|
func GenerateDateRange(start, end time.Time) []time.Time {
|
||||||
|
var dates []time.Time
|
||||||
|
|
||||||
|
// Ensure start is before or equal to end
|
||||||
|
if start.After(end) {
|
||||||
|
return dates
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize times to midnight
|
||||||
|
current := start.Truncate(time.Hour * 24)
|
||||||
|
end = end.Truncate(time.Hour * 24)
|
||||||
|
|
||||||
|
for !current.After(end) {
|
||||||
|
dates = append(dates, current)
|
||||||
|
current = current.AddDate(0, 0, 1) // Add one day
|
||||||
|
}
|
||||||
|
return dates
|
||||||
|
}
|
||||||
|
|
||||||
func GetFirstOfMonth(ts time.Time) time.Time {
|
func GetFirstOfMonth(ts time.Time) time.Time {
|
||||||
if ts.Day() > 1 {
|
if ts.Day() > 1 {
|
||||||
return ts.AddDate(0, 0, -(ts.Day() - 1))
|
return ts.AddDate(0, 0, -(ts.Day() - 1))
|
||||||
|
|||||||
@@ -26,6 +26,101 @@ func TestGetMonday(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIsMonday_ReturnsTrueForMonday(t *testing.T) {
|
||||||
|
monday := time.Date(2023, 4, 3, 0, 0, 0, 0, time.UTC)
|
||||||
|
|
||||||
|
if !IsMonday(monday) {
|
||||||
|
t.Errorf("Expected IsMonday to return true for Monday, got false")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsMonday_ReturnsFalseForNonMonday(t *testing.T) {
|
||||||
|
tuesday := time.Date(2023, 4, 4, 0, 0, 0, 0, time.UTC)
|
||||||
|
|
||||||
|
if IsMonday(tuesday) {
|
||||||
|
t.Errorf("Expected IsMonday to return false for Tuesday, got true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateDateRange(t *testing.T) {
|
||||||
|
start := time.Date(2026, 2, 9, 0, 0, 0, 0, time.UTC)
|
||||||
|
end := time.Date(2026, 2, 11, 0, 0, 0, 0, time.UTC)
|
||||||
|
|
||||||
|
dates := GenerateDateRange(start, end)
|
||||||
|
|
||||||
|
if len(dates) != 3 {
|
||||||
|
t.Fatalf("expected 3 dates, got %d", len(dates))
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := []string{"2026-02-09", "2026-02-10", "2026-02-11"}
|
||||||
|
for i, d := range dates {
|
||||||
|
got := d.Format("2006-01-02")
|
||||||
|
if got != expected[i] {
|
||||||
|
t.Errorf("expected %s, got %s", expected[i], got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetMondays_ReturnsOnlyMondays(t *testing.T) {
|
||||||
|
startDate := time.Date(2026, 01, 01, 0, 0, 0, 0, time.UTC)
|
||||||
|
endDate := time.Date(2026, 01, 31, 0, 0, 0, 0, time.UTC)
|
||||||
|
|
||||||
|
daysInMonth := GenerateDateRange(startDate, endDate)
|
||||||
|
result := GetMondays(daysInMonth, false)
|
||||||
|
if len(result) < 5 {
|
||||||
|
t.Errorf("Expected 5 monday, got %d", len(result))
|
||||||
|
} else if len(result) > 5 {
|
||||||
|
t.Errorf("Expected 5 monday, got %d", len(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
if result[0] != time.Date(2025, 12, 29, 0, 0, 0, 0, time.UTC) {
|
||||||
|
t.Errorf("Expected first monday to be %v, got %v", "2025-12-29", result[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetMondays_ReturnsOnlyMondaysInRange(t *testing.T) {
|
||||||
|
startDate := time.Date(2026, 01, 01, 0, 0, 0, 0, time.UTC)
|
||||||
|
endDate := time.Date(2026, 01, 31, 0, 0, 0, 0, time.UTC)
|
||||||
|
|
||||||
|
daysInMonth := GenerateDateRange(startDate, endDate)
|
||||||
|
result := GetMondays(daysInMonth, true)
|
||||||
|
if len(result) < 4 {
|
||||||
|
t.Errorf("Expected 4 monday, got %d", len(result))
|
||||||
|
} else if len(result) > 4 {
|
||||||
|
t.Errorf("Expected 4 monday, got %d", len(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
if result[0] != time.Date(2026, 1, 5, 0, 0, 0, 0, time.UTC) {
|
||||||
|
t.Errorf("Expected first monday to be %v, got %v", "2026-01-05", result[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDaysInRange(t *testing.T) {
|
||||||
|
days := []time.Time{
|
||||||
|
time.Date(2023, 4, 3, 0, 0, 0, 0, time.UTC), // Tuesday
|
||||||
|
time.Date(2023, 4, 4, 0, 0, 0, 0, time.UTC), // Wednesday
|
||||||
|
time.Date(2023, 4, 5, 0, 0, 0, 0, time.UTC), // Thursday
|
||||||
|
time.Date(2023, 4, 6, 0, 0, 0, 0, time.UTC), // Friday
|
||||||
|
}
|
||||||
|
|
||||||
|
start := time.Date(2023, 4, 3, 0, 0, 0, 0, time.UTC)
|
||||||
|
end := time.Date(2023, 4, 5, 0, 0, 0, 0, time.UTC)
|
||||||
|
|
||||||
|
daysInRange := DaysInRange(days, start, end)
|
||||||
|
|
||||||
|
if len(daysInRange) != 3 {
|
||||||
|
t.Errorf("Expected 3 days in range, got %d", len(daysInRange))
|
||||||
|
}
|
||||||
|
|
||||||
|
if daysInRange[0] != days[0] {
|
||||||
|
t.Errorf("Expected first day in range to be %v, got %v", days[0], daysInRange[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if daysInRange[2] != days[2] {
|
||||||
|
t.Errorf("Expected third day in range to be %v, got %v", days[2], daysInRange[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestFormatDurationFill(t *testing.T) {
|
func TestFormatDurationFill(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ func SetCors(w http.ResponseWriter) {
|
|||||||
|
|
||||||
func RequiresLogin(session *scs.SessionManager, w http.ResponseWriter, r *http.Request) {
|
func RequiresLogin(session *scs.SessionManager, w http.ResponseWriter, r *http.Request) {
|
||||||
r = r.WithContext(context.WithValue(r.Context(), "session", session))
|
r = r.WithContext(context.WithValue(r.Context(), "session", session))
|
||||||
if GetEnv("GO_ENV", "production") == "debug" {
|
if IsDebug() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if session.Exists(r.Context(), "user") {
|
if session.Exists(r.Context(), "user") {
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ func main() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Info("No .env file found in directory!")
|
slog.Info("No .env file found in directory!")
|
||||||
}
|
}
|
||||||
if helper.GetEnv("GO_ENV", "production") == "debug" {
|
if helper.IsDebug() {
|
||||||
logLevel.Set(slog.LevelDebug)
|
logLevel.Set(slog.LevelDebug)
|
||||||
envs := os.Environ()
|
envs := os.Environ()
|
||||||
slog.Debug("Debug mode enabled", "Environment Variables", envs)
|
slog.Debug("Debug mode enabled", "Environment Variables", envs)
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ func (a *Absence) GetWorktime(u User, base WorktimeBase, includeKurzarbeit bool)
|
|||||||
return u.ArbeitszeitProTagFrac(float32(a.AbwesenheitTyp.WorkTime) / 100)
|
return u.ArbeitszeitProTagFrac(float32(a.AbwesenheitTyp.WorkTime) / 100)
|
||||||
case WorktimeBaseWeek:
|
case WorktimeBaseWeek:
|
||||||
if a.AbwesenheitTyp.WorkTime <= 0 && includeKurzarbeit {
|
if a.AbwesenheitTyp.WorkTime <= 0 && includeKurzarbeit {
|
||||||
return u.ArbeitszeitProTagFrac(0.2)
|
return u.ArbeitszeitProWocheFrac(0.2)
|
||||||
} else if a.AbwesenheitTyp.WorkTime <= 0 {
|
} else if a.AbwesenheitTyp.WorkTime <= 0 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ func (b Booking) Save() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Booking) GetBookingType() string {
|
func (b *Booking) GetBookingType() string {
|
||||||
debug := (helper.GetEnv("GO_ENV", "production") == "debug")
|
debug := helper.IsDebug()
|
||||||
switch b.CheckInOut {
|
switch b.CheckInOut {
|
||||||
case 1: //manuelle Änderung
|
case 1: //manuelle Änderung
|
||||||
return "kommen"
|
return "kommen"
|
||||||
|
|||||||
@@ -55,7 +55,9 @@ func GetDays(user User, tsFrom, tsTo time.Time, orderedForward bool) []IWorkDay
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, absentDay := range absences {
|
for _, absentDay := range absences {
|
||||||
|
if weekDay := absentDay.Date().Weekday(); weekDay == time.Saturday || weekDay == time.Sunday {
|
||||||
|
continue
|
||||||
|
}
|
||||||
// Check if there is already a day
|
// Check if there is already a day
|
||||||
existingDay, ok := allDays[absentDay.Date().Format(time.DateOnly)]
|
existingDay, ok := allDays[absentDay.Date().Format(time.DateOnly)]
|
||||||
switch {
|
switch {
|
||||||
|
|||||||
@@ -119,6 +119,8 @@ func (p *PublicHoliday) RequiresAction() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *PublicHoliday) GetWorktime(u User, base WorktimeBase, includeKurzarbeit bool) time.Duration {
|
func (p *PublicHoliday) GetWorktime(u User, base WorktimeBase, includeKurzarbeit bool) time.Duration {
|
||||||
|
return 0
|
||||||
|
|
||||||
switch base {
|
switch base {
|
||||||
case WorktimeBaseDay:
|
case WorktimeBaseDay:
|
||||||
return u.ArbeitszeitProTagFrac(float32(p.worktime) / 100)
|
return u.ArbeitszeitProTagFrac(float32(p.worktime) / 100)
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ type User struct {
|
|||||||
func GetUserFromSession(Session *scs.SessionManager, ctx context.Context) (User, error) {
|
func GetUserFromSession(Session *scs.SessionManager, ctx context.Context) (User, error) {
|
||||||
var user User
|
var user User
|
||||||
var err error
|
var err error
|
||||||
if helper.GetEnv("GO_ENV", "production") == "debug" {
|
if helper.IsDebug() {
|
||||||
user, err = GetUserByPersonalNr(123)
|
user, err = GetUserByPersonalNr(123)
|
||||||
} else {
|
} else {
|
||||||
if !Session.Exists(ctx, "user") {
|
if !Session.Exists(ctx, "user") {
|
||||||
@@ -50,15 +50,15 @@ func GetUserFromSession(Session *scs.SessionManager, ctx context.Context) (User,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Returns the actual overtime for this moment
|
// Returns the actual overtime for this moment
|
||||||
func (u *User) GetReportedOvertime() (time.Duration, error) {
|
func (u *User) GetReportedOvertime(startDate time.Time) (time.Duration, error) {
|
||||||
var overtime time.Duration
|
var overtime time.Duration
|
||||||
|
|
||||||
qStr, err := DB.Prepare("SELECT COALESCE(SUM(EXTRACT(EPOCH FROM ueberstunden) * 1000000000)::BIGINT, 0) AS total_ueberstunden_ns FROM wochen_report WHERE personal_nummer = $1;")
|
qStr, err := DB.Prepare("SELECT COALESCE(SUM(EXTRACT(EPOCH FROM ueberstunden) * 1000000000)::BIGINT, 0) AS total_ueberstunden_ns FROM wochen_report WHERE personal_nummer = $1 AND woche_start::DATE <= $2::DATE;")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
defer qStr.Close()
|
defer qStr.Close()
|
||||||
err = qStr.QueryRow(u.PersonalNummer).Scan(&overtime)
|
err = qStr.QueryRow(u.PersonalNummer, startDate).Scan(&overtime)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -151,12 +151,21 @@ func (d *WorkDay) Type() DayType {
|
|||||||
return DayTypeWorkday
|
return DayTypeWorkday
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *WorkDay) GenerateKurzArbeitBookings(u User) (time.Time, time.Time) {
|
func (d *WorkDay) GenerateKurzArbeitBookings(u User, weekBase WorktimeBase) (time.Time, time.Time) {
|
||||||
var timeFrom, timeTo time.Time
|
var timeFrom, timeTo time.Time
|
||||||
if d.GetWorktime(u, WorktimeBaseDay, false) >= u.ArbeitszeitProTag() {
|
if d.GetWorktime(u, WorktimeBaseDay, false) >= u.ArbeitszeitProTag() {
|
||||||
return timeFrom, timeTo
|
return timeFrom, timeTo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if d.IsEmpty() {
|
||||||
|
switch weekBase {
|
||||||
|
case WorktimeBaseDay:
|
||||||
|
return d.Day.Add(time.Hour * 8), d.Day.Add(time.Hour * 8).Add(u.ArbeitszeitProTag())
|
||||||
|
case WorktimeBaseWeek:
|
||||||
|
return d.Day.Add(time.Hour * 8), d.Day.Add(time.Hour * 8).Add(u.ArbeitszeitProWocheFrac(0.2))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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.GetWorktime(u, WorktimeBaseDay, false))
|
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())
|
||||||
@@ -169,7 +178,7 @@ func (d *WorkDay) GetKurzArbeit() *Absence {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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. Is KurzArbeit %v", d.Date().Format(time.DateOnly), len(d.Bookings), helper.FormatDuration(d.workTime), d.IsKurzArbeit())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *WorkDay) IsWorkDay() bool {
|
func (d *WorkDay) IsWorkDay() bool {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ package models
|
|||||||
// this type is based on the "wochen_report" table
|
// this type is based on the "wochen_report" table
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"arbeitszeitmessung/helper"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
"log"
|
"log"
|
||||||
@@ -24,10 +25,12 @@ type WorkWeek struct {
|
|||||||
Days []IWorkDay
|
Days []IWorkDay
|
||||||
User User
|
User User
|
||||||
WeekStart time.Time
|
WeekStart time.Time
|
||||||
|
weekEnd time.Time
|
||||||
Worktime time.Duration
|
Worktime time.Duration
|
||||||
WorktimeVirtual time.Duration
|
WorktimeVirtual time.Duration
|
||||||
Overtime time.Duration
|
Overtime time.Duration
|
||||||
Status WeekStatus
|
Status WeekStatus
|
||||||
|
WeekBase WorktimeBase
|
||||||
}
|
}
|
||||||
|
|
||||||
type WeekStatus int8
|
type WeekStatus int8
|
||||||
@@ -40,10 +43,15 @@ const (
|
|||||||
WeekStatusDifferences
|
WeekStatusDifferences
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewWorkWeek(user User, tsMonday time.Time, populate bool) WorkWeek {
|
func NewWorkWeekSimple(user User, tsMonday time.Time, populate bool) WorkWeek {
|
||||||
|
return NewWorkWeek(user, tsMonday, tsMonday.Add(6*24*time.Hour), populate)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWorkWeek(user User, tsStart, tsEnd time.Time, populate bool) WorkWeek {
|
||||||
var week WorkWeek = WorkWeek{
|
var week WorkWeek = WorkWeek{
|
||||||
User: user,
|
User: user,
|
||||||
WeekStart: tsMonday,
|
WeekStart: tsStart,
|
||||||
|
weekEnd: tsEnd,
|
||||||
Status: WeekStatusNone,
|
Status: WeekStatusNone,
|
||||||
}
|
}
|
||||||
if populate {
|
if populate {
|
||||||
@@ -53,13 +61,20 @@ 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.weekEnd, false)
|
||||||
|
slog.Debug("Populating Workweek for user", "user", w.User.Name, "Days", lenWorkDays(w.Days), "Start", w.WeekStart, "End", w.weekEnd, "workdays", helper.GetWorkingDays(w.WeekStart, w.weekEnd))
|
||||||
|
|
||||||
|
if lenWorkDays(w.Days) == helper.GetWorkingDays(w.WeekStart, w.weekEnd) {
|
||||||
|
w.WeekBase = WorktimeBaseWeek
|
||||||
|
} else {
|
||||||
|
w.WeekBase = WorktimeBaseDay
|
||||||
|
}
|
||||||
|
|
||||||
for _, day := range w.Days {
|
for _, day := range w.Days {
|
||||||
w.Worktime += day.GetWorktime(w.User, WorktimeBaseDay, false)
|
w.Worktime += day.GetWorktime(w.User, w.WeekBase, false)
|
||||||
w.WorktimeVirtual += day.GetWorktime(w.User, WorktimeBaseDay, true)
|
w.WorktimeVirtual += day.GetWorktime(w.User, w.WeekBase, true)
|
||||||
|
slog.Debug("Calculated Worktime", "Day", day.ToString(), "worktime", w.Worktime.String())
|
||||||
}
|
}
|
||||||
slog.Debug("Got worktime for user", "worktime", w.Worktime.String(), "virtualWorkTime", w.WorktimeVirtual.String())
|
slog.Debug("Got worktime for user", "worktime", w.Worktime.String(), "virtualWorkTime", w.WorktimeVirtual.String())
|
||||||
|
|
||||||
@@ -79,6 +94,16 @@ func (w *WorkWeek) PopulateWithDays(worktime time.Duration, overtime time.Durati
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func lenWorkDays(workDays []IWorkDay) int {
|
||||||
|
var lenght int
|
||||||
|
for _, day := range workDays {
|
||||||
|
if !day.IsEmpty() || day.IsKurzArbeit() {
|
||||||
|
lenght += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lenght
|
||||||
|
}
|
||||||
|
|
||||||
func (w *WorkWeek) CheckStatus() WeekStatus {
|
func (w *WorkWeek) CheckStatus() WeekStatus {
|
||||||
if w.Status != WeekStatusNone {
|
if w.Status != WeekStatusNone {
|
||||||
return w.Status
|
return w.Status
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ func TestNewWorkWeekNoPopulate(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
workWeek := models.NewWorkWeek(testUser, monday, false)
|
workWeek := models.NewWorkWeekSimple(testUser, monday, false)
|
||||||
|
|
||||||
if workWeek.User != testUser || workWeek.WeekStart != monday {
|
if workWeek.User != testUser || workWeek.WeekStart != monday {
|
||||||
t.Error("No populate workweek does not have right values!")
|
t.Error("No populate workweek does not have right values!")
|
||||||
|
|||||||
@@ -202,15 +202,9 @@
|
|||||||
.top-0 {
|
.top-0 {
|
||||||
top: calc(var(--spacing) * 0);
|
top: calc(var(--spacing) * 0);
|
||||||
}
|
}
|
||||||
.top-1 {
|
|
||||||
top: calc(var(--spacing) * 1);
|
|
||||||
}
|
|
||||||
.top-1\/2 {
|
.top-1\/2 {
|
||||||
top: calc(1/2 * 100%);
|
top: calc(1/2 * 100%);
|
||||||
}
|
}
|
||||||
.top-2 {
|
|
||||||
top: calc(var(--spacing) * 2);
|
|
||||||
}
|
|
||||||
.top-2\.5 {
|
.top-2\.5 {
|
||||||
top: calc(var(--spacing) * 2.5);
|
top: calc(var(--spacing) * 2.5);
|
||||||
}
|
}
|
||||||
@@ -220,15 +214,9 @@
|
|||||||
.right-1 {
|
.right-1 {
|
||||||
right: calc(var(--spacing) * 1);
|
right: calc(var(--spacing) * 1);
|
||||||
}
|
}
|
||||||
.right-2 {
|
|
||||||
right: calc(var(--spacing) * 2);
|
|
||||||
}
|
|
||||||
.right-2\.5 {
|
.right-2\.5 {
|
||||||
right: calc(var(--spacing) * 2.5);
|
right: calc(var(--spacing) * 2.5);
|
||||||
}
|
}
|
||||||
.left-1 {
|
|
||||||
left: calc(var(--spacing) * 1);
|
|
||||||
}
|
|
||||||
.left-1\/2 {
|
.left-1\/2 {
|
||||||
left: calc(1/2 * 100%);
|
left: calc(1/2 * 100%);
|
||||||
}
|
}
|
||||||
@@ -253,9 +241,6 @@
|
|||||||
.my-2 {
|
.my-2 {
|
||||||
margin-block: calc(var(--spacing) * 2);
|
margin-block: calc(var(--spacing) * 2);
|
||||||
}
|
}
|
||||||
.my-4 {
|
|
||||||
margin-block: calc(var(--spacing) * 4);
|
|
||||||
}
|
|
||||||
.mt-1 {
|
.mt-1 {
|
||||||
margin-top: calc(var(--spacing) * 1);
|
margin-top: calc(var(--spacing) * 1);
|
||||||
}
|
}
|
||||||
@@ -428,20 +413,9 @@
|
|||||||
width: calc(var(--spacing) * 6);
|
width: calc(var(--spacing) * 6);
|
||||||
height: calc(var(--spacing) * 6);
|
height: calc(var(--spacing) * 6);
|
||||||
}
|
}
|
||||||
.size-8 {
|
|
||||||
width: calc(var(--spacing) * 8);
|
|
||||||
height: calc(var(--spacing) * 8);
|
|
||||||
}
|
|
||||||
.size-10 {
|
|
||||||
width: calc(var(--spacing) * 10);
|
|
||||||
height: calc(var(--spacing) * 10);
|
|
||||||
}
|
|
||||||
.h-2 {
|
.h-2 {
|
||||||
height: calc(var(--spacing) * 2);
|
height: calc(var(--spacing) * 2);
|
||||||
}
|
}
|
||||||
.h-3 {
|
|
||||||
height: calc(var(--spacing) * 3);
|
|
||||||
}
|
|
||||||
.h-3\.5 {
|
.h-3\.5 {
|
||||||
height: calc(var(--spacing) * 3.5);
|
height: calc(var(--spacing) * 3.5);
|
||||||
}
|
}
|
||||||
@@ -466,9 +440,6 @@
|
|||||||
.w-2 {
|
.w-2 {
|
||||||
width: calc(var(--spacing) * 2);
|
width: calc(var(--spacing) * 2);
|
||||||
}
|
}
|
||||||
.w-3 {
|
|
||||||
width: calc(var(--spacing) * 3);
|
|
||||||
}
|
|
||||||
.w-3\.5 {
|
.w-3\.5 {
|
||||||
width: calc(var(--spacing) * 3.5);
|
width: calc(var(--spacing) * 3.5);
|
||||||
}
|
}
|
||||||
@@ -478,9 +449,6 @@
|
|||||||
.w-5 {
|
.w-5 {
|
||||||
width: calc(var(--spacing) * 5);
|
width: calc(var(--spacing) * 5);
|
||||||
}
|
}
|
||||||
.w-9 {
|
|
||||||
width: calc(var(--spacing) * 9);
|
|
||||||
}
|
|
||||||
.w-9\/10 {
|
.w-9\/10 {
|
||||||
width: calc(9/10 * 100%);
|
width: calc(9/10 * 100%);
|
||||||
}
|
}
|
||||||
@@ -493,9 +461,6 @@
|
|||||||
.w-full {
|
.w-full {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
.flex-shrink {
|
|
||||||
flex-shrink: 1;
|
|
||||||
}
|
|
||||||
.flex-shrink-0 {
|
.flex-shrink-0 {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
@@ -511,21 +476,10 @@
|
|||||||
.basis-\[content\] {
|
.basis-\[content\] {
|
||||||
flex-basis: content;
|
flex-basis: content;
|
||||||
}
|
}
|
||||||
.border-collapse {
|
|
||||||
border-collapse: collapse;
|
|
||||||
}
|
|
||||||
.-translate-x-1 {
|
|
||||||
--tw-translate-x: calc(var(--spacing) * -1);
|
|
||||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
|
||||||
}
|
|
||||||
.-translate-x-1\/2 {
|
.-translate-x-1\/2 {
|
||||||
--tw-translate-x: calc(calc(1/2 * 100%) * -1);
|
--tw-translate-x: calc(calc(1/2 * 100%) * -1);
|
||||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||||
}
|
}
|
||||||
.-translate-y-1 {
|
|
||||||
--tw-translate-y: calc(var(--spacing) * -1);
|
|
||||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
|
||||||
}
|
|
||||||
.-translate-y-1\/2 {
|
.-translate-y-1\/2 {
|
||||||
--tw-translate-y: calc(calc(1/2 * 100%) * -1);
|
--tw-translate-y: calc(calc(1/2 * 100%) * -1);
|
||||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||||
@@ -536,9 +490,6 @@
|
|||||||
.cursor-pointer {
|
.cursor-pointer {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.resize {
|
|
||||||
resize: both;
|
|
||||||
}
|
|
||||||
.scroll-m-2 {
|
.scroll-m-2 {
|
||||||
scroll-margin: calc(var(--spacing) * 2);
|
scroll-margin: calc(var(--spacing) * 2);
|
||||||
}
|
}
|
||||||
@@ -665,18 +616,12 @@
|
|||||||
.bg-red-600 {
|
.bg-red-600 {
|
||||||
background-color: var(--color-red-600);
|
background-color: var(--color-red-600);
|
||||||
}
|
}
|
||||||
.mask-repeat {
|
|
||||||
mask-repeat: repeat;
|
|
||||||
}
|
|
||||||
.p-1 {
|
.p-1 {
|
||||||
padding: calc(var(--spacing) * 1);
|
padding: calc(var(--spacing) * 1);
|
||||||
}
|
}
|
||||||
.p-2 {
|
.p-2 {
|
||||||
padding: calc(var(--spacing) * 2);
|
padding: calc(var(--spacing) * 2);
|
||||||
}
|
}
|
||||||
.px-2 {
|
|
||||||
padding-inline: calc(var(--spacing) * 2);
|
|
||||||
}
|
|
||||||
.px-3 {
|
.px-3 {
|
||||||
padding-inline: calc(var(--spacing) * 3);
|
padding-inline: calc(var(--spacing) * 3);
|
||||||
}
|
}
|
||||||
@@ -737,16 +682,9 @@
|
|||||||
.uppercase {
|
.uppercase {
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
.underline {
|
|
||||||
text-decoration-line: underline;
|
|
||||||
}
|
|
||||||
.opacity-0 {
|
.opacity-0 {
|
||||||
opacity: 0%;
|
opacity: 0%;
|
||||||
}
|
}
|
||||||
.outline {
|
|
||||||
outline-style: var(--tw-outline-style);
|
|
||||||
outline-width: 1px;
|
|
||||||
}
|
|
||||||
.filter {
|
.filter {
|
||||||
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
|
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
|
||||||
}
|
}
|
||||||
@@ -1180,11 +1118,6 @@
|
|||||||
syntax: "*";
|
syntax: "*";
|
||||||
inherits: false;
|
inherits: false;
|
||||||
}
|
}
|
||||||
@property --tw-outline-style {
|
|
||||||
syntax: "*";
|
|
||||||
inherits: false;
|
|
||||||
initial-value: solid;
|
|
||||||
}
|
|
||||||
@property --tw-blur {
|
@property --tw-blur {
|
||||||
syntax: "*";
|
syntax: "*";
|
||||||
inherits: false;
|
inherits: false;
|
||||||
@@ -1257,7 +1190,6 @@
|
|||||||
--tw-border-style: solid;
|
--tw-border-style: solid;
|
||||||
--tw-divide-y-reverse: 0;
|
--tw-divide-y-reverse: 0;
|
||||||
--tw-font-weight: initial;
|
--tw-font-weight: initial;
|
||||||
--tw-outline-style: solid;
|
|
||||||
--tw-blur: initial;
|
--tw-blur: initial;
|
||||||
--tw-brightness: initial;
|
--tw-brightness: initial;
|
||||||
--tw-contrast: initial;
|
--tw-contrast: initial;
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ templ workWeekComponent(week models.WorkWeek, onlyAccept bool) {
|
|||||||
<div class="flex flex-row gap-2 col-span-3">
|
<div class="flex flex-row gap-2 col-span-3">
|
||||||
@timeGaugeComponent(int8(progress), false)
|
@timeGaugeComponent(int8(progress), false)
|
||||||
<div>
|
<div>
|
||||||
<p>Arbeitszeit: { fmt.Sprintf("%s", helper.FormatDuration(week.Worktime)) }</p>
|
<p>Arbeitszeit: { fmt.Sprintf("%s", helper.FormatDurationFill(week.Worktime, true)) }</p>
|
||||||
<p>Überstunden: { fmt.Sprintf("%s", helper.FormatDurationFill(week.Overtime, true)) }</p>
|
<p>Überstunden: { fmt.Sprintf("%s", helper.FormatDurationFill(week.Overtime, true)) }</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user