feat: add schoolyear based calculation
This commit is contained in:
parent
e65ba85c43
commit
c07019e3eb
5 changed files with 675 additions and 44 deletions
|
|
@ -66,6 +66,14 @@ func createTables(db *sql.DB) {
|
|||
details TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)`,
|
||||
`CREATE TABLE IF NOT EXISTS school_years (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
start_date DATE NOT NULL,
|
||||
end_date DATE NOT NULL,
|
||||
is_active BOOLEAN NOT NULL DEFAULT 0,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)`,
|
||||
}
|
||||
|
||||
for _, query := range queries {
|
||||
|
|
@ -92,6 +100,8 @@ func createIndexes(db *sql.DB) {
|
|||
`CREATE INDEX IF NOT EXISTS idx_audit_logs_user ON audit_logs(user_id)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_audit_logs_created ON audit_logs(created_at)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_schedules_day ON schedules(day_of_week)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_school_years_active ON school_years(is_active)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_school_years_dates ON school_years(start_date, end_date)`,
|
||||
}
|
||||
|
||||
for _, idx := range indexes {
|
||||
|
|
@ -349,56 +359,56 @@ func GetWeeklyHours(db *sql.DB) ([]WeeklyHours, error) {
|
|||
return result, nil
|
||||
}
|
||||
|
||||
func GetYearlyHoursSummary(db *sql.DB) ([]WeeklyHours, error) {
|
||||
users, err := GetAllUsers(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// func GetYearlyHoursSummary(db *sql.DB) ([]WeeklyHours, error) {
|
||||
// users, err := GetAllUsers(db)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
entries, err := GetAllTimeEntries(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// entries, err := GetAllTimeEntries(db)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
userTotals := make(map[int]float64)
|
||||
usernames := make(map[int]string)
|
||||
// userTotals := make(map[int]float64)
|
||||
// usernames := make(map[int]string)
|
||||
|
||||
for _, entry := range entries {
|
||||
var hours float64
|
||||
if entry.Type == "lesson" {
|
||||
hours = 1.0
|
||||
} else {
|
||||
hours = calculateHoursDiff(entry.StartTime, entry.EndTime)
|
||||
}
|
||||
userTotals[entry.UserID] += hours
|
||||
usernames[entry.UserID] = entry.Username
|
||||
}
|
||||
// for _, entry := range entries {
|
||||
// var hours float64
|
||||
// if entry.Type == "lesson" {
|
||||
// hours = 1.0
|
||||
// } else {
|
||||
// hours = calculateHoursDiff(entry.StartTime, entry.EndTime)
|
||||
// }
|
||||
// userTotals[entry.UserID] += hours
|
||||
// usernames[entry.UserID] = entry.Username
|
||||
// }
|
||||
|
||||
var result []WeeklyHours
|
||||
for _, user := range users {
|
||||
if !user.IsAdmin {
|
||||
total := userTotals[user.ID]
|
||||
remaining := user.YearlyHours - total
|
||||
// var result []WeeklyHours
|
||||
// for _, user := range users {
|
||||
// if !user.IsAdmin {
|
||||
// total := userTotals[user.ID]
|
||||
// remaining := user.YearlyHours - total
|
||||
|
||||
result = append(result, WeeklyHours{
|
||||
UserID: user.ID,
|
||||
Username: user.Username,
|
||||
Year: time.Now().Year(),
|
||||
Week: 0,
|
||||
TotalHours: total,
|
||||
YearlyTarget: user.YearlyHours,
|
||||
YearlyActual: total,
|
||||
RemainingYearly: remaining,
|
||||
})
|
||||
}
|
||||
}
|
||||
// result = append(result, WeeklyHours{
|
||||
// UserID: user.ID,
|
||||
// Username: user.Username,
|
||||
// Year: time.Now().Year(),
|
||||
// Week: 0,
|
||||
// TotalHours: total,
|
||||
// YearlyTarget: user.YearlyHours,
|
||||
// YearlyActual: total,
|
||||
// RemainingYearly: remaining,
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
||||
sort.Slice(result, func(i, j int) bool {
|
||||
return result[i].Username < result[j].Username
|
||||
})
|
||||
// sort.Slice(result, func(i, j int) bool {
|
||||
// return result[i].Username < result[j].Username
|
||||
// })
|
||||
|
||||
return result, nil
|
||||
}
|
||||
// return result, nil
|
||||
// }
|
||||
|
||||
func calculateHoursDiff(startTime, endTime string) float64 {
|
||||
parseTime := func(timeStr string) float64 {
|
||||
|
|
@ -469,3 +479,130 @@ func CheckUserHasEntriesForWeek(db *sql.DB, userID int, year int, week int) (boo
|
|||
|
||||
return count > 0, nil
|
||||
}
|
||||
|
||||
func GetActiveSchoolYear(db *sql.DB) (*SchoolYear, error) {
|
||||
var sy SchoolYear
|
||||
err := db.QueryRow(`
|
||||
SELECT id, name, start_date, end_date, is_active, created_at
|
||||
FROM school_years
|
||||
WHERE is_active = 1
|
||||
`).Scan(&sy.ID, &sy.Name, &sy.StartDate, &sy.EndDate, &sy.IsActive, &sy.CreatedAt)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil // Kein aktives Schuljahr
|
||||
}
|
||||
return &sy, err
|
||||
}
|
||||
|
||||
func GetAllSchoolYears(db *sql.DB) ([]SchoolYear, error) {
|
||||
rows, err := db.Query(`
|
||||
SELECT id, name, start_date, end_date, is_active, created_at
|
||||
FROM school_years
|
||||
ORDER BY start_date DESC
|
||||
`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
years := []SchoolYear{}
|
||||
for rows.Next() {
|
||||
var sy SchoolYear
|
||||
if err := rows.Scan(&sy.ID, &sy.Name, &sy.StartDate, &sy.EndDate, &sy.IsActive, &sy.CreatedAt); err != nil {
|
||||
continue
|
||||
}
|
||||
years = append(years, sy)
|
||||
}
|
||||
return years, rows.Err()
|
||||
}
|
||||
|
||||
func CreateSchoolYear(db *sql.DB, name, startDate, endDate string) error {
|
||||
_, err := db.Exec(`
|
||||
INSERT INTO school_years (name, start_date, end_date, is_active)
|
||||
VALUES (?, ?, ?, 0)
|
||||
`, name, startDate, endDate)
|
||||
return err
|
||||
}
|
||||
|
||||
func SetActiveSchoolYear(db *sql.DB, id int) error {
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := tx.Exec("UPDATE school_years SET is_active = 0"); err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := tx.Exec("UPDATE school_years SET is_active = 1 WHERE id = ?", id); err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func GetYearlyHoursSummary(db *sql.DB) ([]WeeklyHours, error) {
|
||||
schoolYear, err := GetActiveSchoolYear(db)
|
||||
if err != nil || schoolYear == nil {
|
||||
return []WeeklyHours{}, err
|
||||
}
|
||||
|
||||
users, err := GetAllUsers(db)
|
||||
if err != nil {
|
||||
return []WeeklyHours{}, err
|
||||
}
|
||||
|
||||
rows, err := db.Query(`
|
||||
SELECT user_id, date, start_time, end_time, type
|
||||
FROM time_entries
|
||||
WHERE date >= ? AND date <= ?
|
||||
ORDER BY date DESC
|
||||
`, schoolYear.StartDate, schoolYear.EndDate)
|
||||
|
||||
if err != nil {
|
||||
return []WeeklyHours{}, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
userTotals := make(map[int]float64)
|
||||
|
||||
for rows.Next() {
|
||||
var userID int
|
||||
var date, startTime, endTime, entryType string
|
||||
|
||||
if err := rows.Scan(&userID, &date, &startTime, &endTime, &entryType); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var hours float64
|
||||
if entryType == "lesson" {
|
||||
hours = 1.0
|
||||
} else {
|
||||
hours = calculateHoursDiff(startTime, endTime)
|
||||
}
|
||||
userTotals[userID] += hours
|
||||
}
|
||||
|
||||
var result []WeeklyHours
|
||||
for _, user := range users {
|
||||
if !user.IsAdmin {
|
||||
total := userTotals[user.ID]
|
||||
remaining := user.YearlyHours - total
|
||||
|
||||
result = append(result, WeeklyHours{
|
||||
UserID: user.ID,
|
||||
Username: user.Username,
|
||||
Year: 0,
|
||||
Week: 0,
|
||||
TotalHours: total,
|
||||
YearlyTarget: user.YearlyHours,
|
||||
YearlyActual: total,
|
||||
RemainingYearly: remaining,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -448,3 +448,51 @@ func (app *App) CreateUserHandler(c echo.Context) error {
|
|||
|
||||
return c.NoContent(http.StatusCreated)
|
||||
}
|
||||
|
||||
func (app *App) GetSchoolYearsHandler(c echo.Context) error {
|
||||
years, err := GetAllSchoolYears(app.DB)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
if years == nil {
|
||||
years = []SchoolYear{}
|
||||
}
|
||||
return c.JSON(http.StatusOK, years)
|
||||
}
|
||||
|
||||
func (app *App) CreateSchoolYearHandler(c echo.Context) error {
|
||||
var req CreateSchoolYearRequest
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
if err := CreateSchoolYear(app.DB, req.Name, req.StartDate, req.EndDate); err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return c.NoContent(http.StatusCreated)
|
||||
}
|
||||
|
||||
func (app *App) SetActiveSchoolYearHandler(c echo.Context) error {
|
||||
id, err := strconv.Atoi(c.Param("id"))
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "Invalid ID")
|
||||
}
|
||||
|
||||
if err := SetActiveSchoolYear(app.DB, id); err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return c.NoContent(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func (app *App) GetActiveSchoolYearHandler(c echo.Context) error {
|
||||
year, err := GetActiveSchoolYear(app.DB)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
if year == nil {
|
||||
return c.JSON(http.StatusOK, map[string]any{"active": false})
|
||||
}
|
||||
return c.JSON(http.StatusOK, year)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ func main() {
|
|||
protected.GET("/week-has-entries", app.CheckWeekHasEntries)
|
||||
protected.GET("/yearly-hours-summary", app.GetYearlyHoursSummaryHandler)
|
||||
protected.GET("/my-info", app.GetMyInfoHandler)
|
||||
protected.GET("/school-year/active", app.GetActiveSchoolYearHandler)
|
||||
}
|
||||
|
||||
admin := e.Group("/api/admin")
|
||||
|
|
@ -64,6 +65,9 @@ func main() {
|
|||
admin.PUT("/time-entries/:id", app.UpdateTimeEntryHandler)
|
||||
admin.DELETE("/time-entries/:id", app.DeleteTimeEntryHandler)
|
||||
admin.POST("/time-entry", app.AdminCreateTimeEntryHandler)
|
||||
admin.GET("/school-years", app.GetSchoolYearsHandler)
|
||||
admin.POST("/school-years", app.CreateSchoolYearHandler)
|
||||
admin.PUT("/school-years/:id/activate", app.SetActiveSchoolYearHandler)
|
||||
}
|
||||
|
||||
e.Static("/", "./static")
|
||||
|
|
|
|||
|
|
@ -61,6 +61,21 @@ type CreateUserRequest struct {
|
|||
YearlyHours float64 `json:"yearly_hours"`
|
||||
}
|
||||
|
||||
type SchoolYear struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
StartDate string `json:"start_date"`
|
||||
EndDate string `json:"end_date"`
|
||||
IsActive bool `json:"is_active"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
type CreateSchoolYearRequest struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
StartDate string `json:"start_date" validate:"required"`
|
||||
EndDate string `json:"end_date" validate:"required"`
|
||||
}
|
||||
|
||||
type UpdateUserRequest struct {
|
||||
Username string `json:"username"`
|
||||
YearlyHours float64 `json:"yearly_hours"`
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue