added control tables (s_*) + working on implementing absence and booking types
Some checks failed
arbeitszeitmessung/pipeline/head There was a failure building this commit

This commit is contained in:
2025-08-02 08:55:40 +02:00
parent 4201ed7b1c
commit 7670efa99b
10 changed files with 176 additions and 54 deletions

View File

@@ -163,7 +163,12 @@ func createAbsence(absenceType int, user models.User, loc *time.Location, r *htt
log.Println("Cannot get date from input! Skipping absence creation", err) log.Println("Cannot get date from input! Skipping absence creation", err)
return return
} }
absence := models.NewAbsence(user.CardUID, int8(absenceType), absenceDate)
absence, err := models.NewAbsence(user.CardUID, absenceType, absenceDate)
if err != nil {
log.Println("Error creating absence!", err)
return
}
err = absence.Insert() err = absence.Insert()
if err != nil { if err != nil {
log.Println("Error inserting absence!", err) log.Println("Error inserting absence!", err)

View File

@@ -1,52 +1,65 @@
package models package models
import ( import (
"errors"
"log" "log"
"time" "time"
) )
type AbsenceType struct { type AbsenceType struct {
Value int8 Id int8
Label string Name string
} }
const ( // const (
AbsenceNone int8 = iota // AbsenceNone int8 = iota
AbsenceUrlaub // AbsenceUrlaub
AbsenceKurzarbeit // AbsenceKurzarbeit
AbsenceKrank // AbsenceKrank
AbsenceKindkrank // AbsenceKindkrank
) // )
var AbsenceTypes = []AbsenceType{ // var AbsenceTypes = []AbsenceType{
// {Value: AbsenceNone, Label: "Abwesenheit"}, // // {Value: AbsenceNone, Label: "Abwesenheit"},
{Value: AbsenceUrlaub, Label: "Urlaub"}, // {Id: AbsenceUrlaub, Name: "Urlaub"},
{Value: AbsenceKurzarbeit, Label: "Kurzarbeit"}, // {Id: AbsenceKurzarbeit, Name: "Kurzarbeit"},
{Value: AbsenceKrank, Label: "Krank"}, // {Id: AbsenceKrank, Name: "Krank"},
{Value: AbsenceKindkrank, Label: "Kindkrank"}, // {Id: AbsenceKindkrank, Name: "Kindkrank"},
} // }
var AbsenceTypesLabel = map[int8]string{ // var AbsenceTypesLabel = map[int8]string{
0: "None", // 0: "None",
AbsenceUrlaub: "Urlaub", // AbsenceUrlaub: "Urlaub",
AbsenceKurzarbeit: "Kurzarbeit", // AbsenceKurzarbeit: "Kurzarbeit",
AbsenceKrank: "Krank", // AbsenceKrank: "Krank",
AbsenceKindkrank: "Kindkrank", // AbsenceKindkrank: "Kindkrank",
} // }
type Absence struct { type Absence struct {
CounterId int CounterId int
CardUID string CardUID string
AbwesenheitTyp int8 AbwesenheitTyp AbsenceType
Datum time.Time Datum time.Time
// Comment string
} }
func NewAbsence(card_uid string, abwesenheit_typ int8, datum time.Time) Absence { func NewAbsence(card_uid string, abwesenheit_typ int, datum time.Time) (Absence, error) {
if abwesenheit_typ < 0 {
return Absence{ return Absence{
CardUID: card_uid, CardUID: card_uid,
AbwesenheitTyp: abwesenheit_typ, AbwesenheitTyp: AbsenceType{0, "Custom absence"},
Datum: datum, Datum: datum,
}, nil
} }
_absenceType, ok := GetAbsenceTypesCached()[int8(abwesenheit_typ)]
if !ok {
return Absence{}, errors.New("Invalid absencetype")
}
return Absence{
CardUID: card_uid,
AbwesenheitTyp: _absenceType,
Datum: datum,
}, nil
} }
func (a *Absence) Insert() error { func (a *Absence) Insert() error {
@@ -63,6 +76,37 @@ func (a *Absence) Insert() error {
return nil return nil
} }
func (a *Absence) GetStringType() string { // func (a *Absence) GetStringType() string {
return AbsenceTypesLabel[a.AbwesenheitTyp] // return AbsenceTypesLabel[a.AbwesenheitTyp]
// }
func GetAbsenceTypes() (map[int8]AbsenceType, error) {
var types = make(map[int8]AbsenceType)
qStr, err := DB.Prepare("SELECT abwesenheit_id, abwesenheit_name FROM s_abwesenheit_typen;")
if err != nil {
return types, err
}
defer qStr.Close()
rows, err := qStr.Query()
if err != nil {
log.Println("Error getting abwesenheit rows!", err)
return types, err
}
defer rows.Close()
for rows.Next() {
var absenceType AbsenceType
if err := rows.Scan(&absenceType.Id, &absenceType.Name); err != nil {
log.Println("Error scanning absence row!", err)
}
types[absenceType.Id] = absenceType
}
return types, nil
}
func GetAbsenceTypesCached() map[int8]AbsenceType {
types, err := definedTypes.Get("s_abwesenheit_typen")
if err != nil {
return map[int8]AbsenceType{}
}
return types.(map[int8]AbsenceType)
} }

View File

@@ -13,6 +13,11 @@ import (
type SameBookingError struct{} type SameBookingError struct{}
type BookingType struct {
Id int8
Name string
}
func (e SameBookingError) Error() string { func (e SameBookingError) Error() string {
return "the same booking already exists!" return "the same booking already exists!"
} }
@@ -261,3 +266,34 @@ func (b *Booking) UpdateTime(newTime time.Time) {
func (b *Booking) ToString() string { func (b *Booking) ToString() string {
return fmt.Sprintf("Booking %d: at: %s, as type: %d", b.CounterId, b.Timestamp.Format("15:04"), b.CheckInOut) return fmt.Sprintf("Booking %d: at: %s, as type: %d", b.CounterId, b.Timestamp.Format("15:04"), b.CheckInOut)
} }
func GetBokkingTypes() ([]BookingType, error) {
var types []BookingType
qStr, err := DB.Prepare("SELECT anwesenheit_id, anwesenheit_name FROM s_anwesenheit_typen;")
if err != nil {
return types, err
}
defer qStr.Close()
rows, err := qStr.Query()
if err != nil {
log.Println("Error getting anwesenheit rows!", err)
return types, err
}
defer rows.Close()
for rows.Next() {
var bookingType BookingType
if err := rows.Scan(&bookingType.Id, &bookingType.Name); err != nil {
log.Println("Error scanning row!", err)
}
types = append(types, bookingType)
}
return types, nil
}
func GetBookingTypesCached() []BookingType {
types, err := definedTypes.Get("s_anwesenheit_typen")
if err != nil {
return []BookingType{}
}
return types.([]BookingType)
}

View File

@@ -0,0 +1,15 @@
package models
import (
"arbeitszeitmessung/helper"
)
var definedTypes = helper.NewCache(3600, func(key string) (any, error) {
switch key {
case "s_abwesenheit_typen":
return GetAbsenceTypes()
case "s_anwesenheit_typen":
return GetBokkingTypes()
}
return nil, nil
})

View File

@@ -40,7 +40,7 @@ func (u *User) GetUserFromSession(Session *scs.SessionManager, ctx context.Conte
} }
func (u *User) GetAll() ([]User, error) { func (u *User) GetAll() ([]User, error) {
qStr, err := DB.Prepare((`SELECT card_uid, vorname, nachname FROM personal_daten;`)) qStr, err := DB.Prepare((`SELECT card_uid, vorname, nachname FROM s_personal_daten;`))
var users []User var users []User
if err != nil { if err != nil {
fmt.Printf("Error preparing query statement %v\n", err) fmt.Printf("Error preparing query statement %v\n", err)
@@ -98,7 +98,7 @@ func (u *User) CheckOut() error {
func (u *User) GetByPersonalNummer(personalNummer int) (User, error) { func (u *User) GetByPersonalNummer(personalNummer int) (User, error) {
var user User var user User
qStr, err := DB.Prepare((`SELECT personal_nummer, card_uid, vorname, nachname, arbeitszeit_per_tag FROM personal_daten WHERE personal_nummer = $1;`)) qStr, err := DB.Prepare((`SELECT personal_nummer, card_uid, vorname, nachname, arbeitszeit_per_tag FROM s_personal_daten WHERE personal_nummer = $1;`))
if err != nil { if err != nil {
return user, err return user, err
} }
@@ -146,7 +146,7 @@ func (u *User) ChangePass(password, newPassword string) (bool, error) {
func (u *User) GetTeamMembers() ([]User, error) { func (u *User) GetTeamMembers() ([]User, error) {
var teamMembers []User var teamMembers []User
qStr, err := DB.Prepare(`SELECT personal_nummer, card_uid, vorname, nachname, arbeitszeit_per_tag FROM personal_daten WHERE vorgesetzter_pers_nr = $1`) qStr, err := DB.Prepare(`SELECT personal_nummer, card_uid, vorname, nachname, arbeitszeit_per_tag FROM s_personal_daten WHERE vorgesetzter_pers_nr = $1`)
if err != nil { if err != nil {
return teamMembers, err return teamMembers, err
} }
@@ -234,7 +234,7 @@ func (u *User) GetFromCardUID(card_uid string) (User, error) {
user := User{} user := User{}
var err error var err error
qStr, err := DB.Prepare((`SELECT personal_nummer, card_uid, vorname, nachname, arbeitszeit_per_tag FROM personal_daten WHERE card_uid = $1;`)) qStr, err := DB.Prepare((`SELECT personal_nummer, card_uid, vorname, nachname, arbeitszeit_per_tag FROM s_personal_daten WHERE card_uid = $1;`))
if err != nil { if err != nil {
return user, err return user, err
} }

View File

@@ -117,8 +117,8 @@ func (d *WorkDay) GetWorkDays(card_uid string, tsFrom, tsTo time.Time) []WorkDay
} }
if absenceType.Valid { if absenceType.Valid {
workDay.Absence = NewAbsence(card_uid, int8(absenceType.Int16), workDay.Day) workDay.Absence, err = NewAbsence(card_uid, int(absenceType.Int16), workDay.Day)
log.Println("Found absence", workDay.Absence) // log.Println("Found absence", workDay.Absence)
} }
if workDay.Day.Equal(time.Now().Truncate(24 * time.Hour)) { if workDay.Day.Equal(time.Now().Truncate(24 * time.Hour)) {

View File

@@ -68,7 +68,7 @@ templ dayComponent(workDay models.WorkDay) {
@lineComponent() @lineComponent()
<form id={ "time-" + workDay.Day.Format("2006-01-02") } class="flex flex-col gap-2 group w-full justify-between" style={ justify } method="post"> <form id={ "time-" + workDay.Day.Format("2006-01-02") } class="flex flex-col gap-2 group w-full justify-between" style={ justify } method="post">
if (workDay.Absence != models.Absence{}) { if (workDay.Absence != models.Absence{}) {
<p>{ workDay.Absence.GetStringType() }</p> <p>{ workDay.Absence.AbwesenheitTyp.Name }</p>
} }
if len(workDay.Bookings) < 1 && (workDay.Absence == models.Absence{}) { if len(workDay.Bookings) < 1 && (workDay.Absence == models.Absence{}) {
<p class="text group-[.edit]:hidden">Keine Buchung gefunden. Bitte Arbeitsstunden oder Grund der Abwesenheit eingeben!</p> <p class="text group-[.edit]:hidden">Keine Buchung gefunden. Bitte Arbeitsstunden oder Grund der Abwesenheit eingeben!</p>
@@ -147,8 +147,8 @@ templ absenceComponent(d models.WorkDay) {
<div class="no-booking-component hidden group-[.edit]:flex flex-col gap-2 align-center"> <div class="no-booking-component hidden group-[.edit]:flex flex-col gap-2 align-center">
<select name="absence" onchange={ templ.JSFuncCall("editAbwesenheit", templ.JSExpression("this"), templ.JSExpression("event")) } class="grow cursor-pointer rounded-md text-neutral-800 p-2 md:px-4 border text-center text-sm transition-colors border-neutral-900" disabled> <select name="absence" onchange={ templ.JSFuncCall("editAbwesenheit", templ.JSExpression("this"), templ.JSExpression("event")) } class="grow cursor-pointer rounded-md text-neutral-800 p-2 md:px-4 border text-center text-sm transition-colors border-neutral-900" disabled>
<option value="0">Abwesenheit?</option> <option value="0">Abwesenheit?</option>
for _, absence := range models.AbsenceTypes { for _, absence := range models.GetAbsenceTypesCached() {
<option value={ strconv.Itoa(int(absence.Value)) }>{ absence.Label }</option> <option value={ strconv.Itoa(int(absence.Id)) }>{ absence.Name }</option>
} }
</select> </select>
</div> </div>

View File

@@ -267,9 +267,9 @@ func dayComponent(workDay models.WorkDay) templ.Component {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var13 string var templ_7745c5c3_Var13 string
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(workDay.Absence.GetStringType()) templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(workDay.Absence.AbwesenheitTyp.Name)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 71, Col: 41} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 71, Col: 45}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -554,15 +554,15 @@ func absenceComponent(d models.WorkDay) templ.Component {
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
for _, absence := range models.AbsenceTypes { for _, absence := range models.GetAbsenceTypesCached() {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "<option value=\"") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "<option value=\"")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var25 string var templ_7745c5c3_Var25 string
templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(int(absence.Value))) templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(int(absence.Id)))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 151, Col: 52} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 151, Col: 49}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -573,9 +573,9 @@ func absenceComponent(d models.WorkDay) templ.Component {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var26 string var templ_7745c5c3_Var26 string
templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(absence.Label) templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(absence.Name)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 151, Col: 70} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 151, Col: 66}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {

View File

@@ -7,16 +7,27 @@ CREATE TABLE "anwesenheit" (
"timestamp" timestamptz(6) DEFAULT CURRENT_TIMESTAMP, "timestamp" timestamptz(6) DEFAULT CURRENT_TIMESTAMP,
"card_uid" varchar(255), "card_uid" varchar(255),
"check_in_out" int2, "check_in_out" int2,
"geraet_id" int2 "geraet_id" int2,
"manuelle_buchung" bool
); );
COMMENT ON COLUMN "anwesenheit"."check_in_out" IS '1=Check In 2=Check Out , 3=Check in Manuell, 4=Check out manuell255=Automatic Check Out'; COMMENT ON COLUMN "anwesenheit"."check_in_out" IS '1=Check In 2=Check Out , 3=Check in Manuell, 4=Check out manuell255=Automatic Check Out';
COMMENT ON COLUMN "anwesenheit"."geraet_id" IS 'ID des Lesegerätes'; COMMENT ON COLUMN "anwesenheit"."geraet_id" IS 'ID des Lesegerätes';
-- ----------------------------
-- Table structure for anwesenheitstypen
-- ----------------------------
DROP TABLE IF EXISTS "s_anwesenheit_typen";
CREATE TABLE "s_anwesenheit_typen" (
"anwesenheit_id" int2 PRIMARY KEY,
"anwesenheit_name" varchar(255)
);
-- ---------------------------- -- ----------------------------
-- Table structure for personal_daten -- Table structure for personal_daten
-- ---------------------------- -- ----------------------------
DROP TABLE IF EXISTS "personal_daten"; DROP TABLE IF EXISTS "s_personal_daten";
CREATE TABLE "personal_daten" ( CREATE TABLE "s_personal_daten" (
"personal_nummer" int4 NOT NULL PRIMARY KEY, "personal_nummer" int4 NOT NULL PRIMARY KEY,
"aktiv_beschaeftigt" bool, "aktiv_beschaeftigt" bool,
"vorname" varchar(255), "vorname" varchar(255),
@@ -32,7 +43,7 @@ CREATE TABLE "personal_daten" (
"arbeitszeit_max_ende" time(6), "arbeitszeit_max_ende" time(6),
"vorgesetzter_pers_nr" int4 "vorgesetzter_pers_nr" int4
); );
COMMENT ON COLUMN "personal_daten"."geschlecht" IS '1==weiblich, 2==maennlich, 3==divers'; COMMENT ON COLUMN "s_personal_daten"."geschlecht" IS '1==weiblich, 2==maennlich, 3==divers';
DROP TABLE IF EXISTS "user_password"; DROP TABLE IF EXISTS "user_password";
CREATE TABLE "user_password" ( CREATE TABLE "user_password" (
@@ -78,15 +89,25 @@ CREATE TABLE "abwesenheit" (
"datum" timestamptz(6) DEFAULT NOW()::DATE "datum" timestamptz(6) DEFAULT NOW()::DATE
); );
DROP TABLE IF EXISTS "s_abwesenheit_typen";
CREATE TABLE "s_abwesenheit_typen" (
"abwesenheit_id" int2 PRIMARY KEY,
"abwesenheit_name" varchar(255)
);
-- Adds crypto extension -- Adds crypto extension
CREATE EXTENSION IF NOT EXISTS pgcrypto; CREATE EXTENSION IF NOT EXISTS pgcrypto;
-- ----------------------------
-- Insert example data
-- ----------------------------
-- Insert into personal_daten INSERT INTO "s_personal_daten" ("personal_nummer", "aktiv_beschaeftigt", "vorname", "nachname", "geburtsdatum", "plz", "adresse", "geschlecht", "card_uid", "hauptbeschaeftigungs_ort", "arbeitszeit_per_tag", "arbeitszeit_min_start", "arbeitszeit_max_ende", "vorgesetzter_pers_nr") VALUES
INSERT INTO "personal_daten" ("personal_nummer", "aktiv_beschaeftigt", "vorname", "nachname", "geburtsdatum", "plz", "adresse", "geschlecht", "card_uid", "hauptbeschaeftigungs_ort", "arbeitszeit_per_tag", "arbeitszeit_min_start", "arbeitszeit_max_ende", "vorgesetzter_pers_nr") VALUES
(123, 't', 'Max', 'Mustermann', '2003-02-01', '08963', 'Altenburger Str. 44A', 1, 'acde-edca', 1, 7.5, '07:00:00', '20:00:00', 0); (123, 't', 'Max', 'Mustermann', '2003-02-01', '08963', 'Altenburger Str. 44A', 1, 'acde-edca', 1, 7.5, '07:00:00', '20:00:00', 0);
INSERT INTO "user_password" ("personal_nummer", "pass_hash") VALUES INSERT INTO "user_password" ("personal_nummer", "pass_hash") VALUES
(123, crypt('max_pass', gen_salt('bf'))); (123, crypt('max_pass', gen_salt('bf')));
INSERT INTO "s_anwesenheit_typen" ("anwesenheit_id", "anwesenheit_name") VALUES (1, 'Büro');
INSERT INTO "s_abwesenheit_typen" ("abwesenheit_id", "abwesenheit_name") VALUES (1, 'Urlaub'), (2, 'Krank'), (3, 'Kurzarbeit');

View File

@@ -7,7 +7,8 @@ psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-E
CREATE USER $POSTGRES_API_USER WITH ENCRYPTED PASSWORD '$POSTGRES_API_PASS'; CREATE USER $POSTGRES_API_USER WITH ENCRYPTED PASSWORD '$POSTGRES_API_PASS';
GRANT CONNECT ON DATABASE $POSTGRES_DB TO $POSTGRES_API_USER; GRANT CONNECT ON DATABASE $POSTGRES_DB TO $POSTGRES_API_USER;
GRANT USAGE ON SCHEMA public TO $POSTGRES_API_USER; GRANT USAGE ON SCHEMA public TO $POSTGRES_API_USER;
GRANT SELECT, INSERT, UPDATE ON anwesenheit, abwesenheit, personal_daten, user_password, wochen_report TO $POSTGRES_API_USER; GRANT SELECT, INSERT, UPDATE ON anwesenheit, abwesenheit, user_password, wochen_report TO $POSTGRES_API_USER;
GRANT SELECT ON s_personal_daten, s_abwesenheit_typen, s_anwesenheit_typen TO $POSTGRES_API_USER;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO $POSTGRES_API_USER; GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO $POSTGRES_API_USER;
EOSQL EOSQL