Merge branch 'urlaubsantrag'

This commit is contained in:
Alexander Malzkuhn 2025-05-28 12:46:44 +02:00
commit ad9b6d6be6
10 changed files with 777 additions and 550 deletions

View File

@ -41,9 +41,12 @@ def page_admin():
ui.button("Logout", on_click=admin_logout)
updates_available = ValueBinder()
updates_available.value = False
with ui.tabs() as tabs:
time_overview = ui.tab('Zeitübersichten')
time_overview = ui.tab('Zeitdaten')
settings = ui.tab('Einstellungen')
users = ui.tab('Benutzer')
backups = ui.tab('Backups')
@ -56,9 +59,16 @@ def page_admin():
update_userlist()
with (((ui.tab_panels(tabs, value=time_overview)))):
with ui.tab_panels(tabs, value=time_overview):
with ui.tab_panel(time_overview):
with ui.tabs() as overview_tabs:
user_month_overview = ui.tab('Monatsansicht')
user_summary = ui.tab("Zusammenfassung")
vacation_applications = ui.tab("Urlaubsanträge")
with ui.tab_panels(overview_tabs, value = user_month_overview):
with ui.tab_panel(user_month_overview).classes('w-full'):
ui.markdown("##Übersichten")
# Tabelle konstruieren
@ -83,6 +93,7 @@ def page_admin():
def update_user():
current_user = user(time_user.value)
available_years = current_user.get_years()
try:
select_year.clear()
select_year.set_options(available_years)
@ -91,6 +102,7 @@ def page_admin():
select_year.value = str(datetime.datetime.now().year)
except:
select_year.value = list(available_years)[0]
update_months()
try:
select_month.value = datetime.datetime.now().month
except:
@ -601,9 +613,115 @@ Dies kann nicht rückgängig gemacht werden!''')
month_header.set_content(f"###Buchungen für **{current_user.fullname}** für **{calendar.month_name[int(select_month.value)]} {select_year.value}**")
month_header.set_content(f"###Buchungen für **{current_user.fullname}** für **{calendar.month_name[int(select_month.value)]} {select_year.value}**")
timetable()
button_update = ui.button("Aktualisieren", on_click=timetable.refresh)
button_update.move(timetable_header)
with ui.tab_panel(user_summary):
global overview_table
@ui.refreshable
def overview_table():
ov_columns = [ {'label': 'Benutzername', 'name': 'username', 'field': 'username'},
{'label': 'Name', 'name': 'name', 'field': 'name'},
{'label': 'Zeitsaldo', 'name': 'time', 'field': 'time'},
{'label': 'Urlaub', 'name': 'vacation', 'field': 'vacation'},
{'label': 'Resturlaub', 'name': 'vacation_remaining', 'field': 'vacation_remaining'},
{'label': 'Krankheit', 'name': 'illness', 'field': 'illness'}]
ov_rows = [ ]
for username in userlist:
actual_user = user(username)
ov_rows.append(
{'username': username, 'name': actual_user.fullname, 'time': f'{convert_seconds_to_hours(actual_user.get_last_months_overtime() + actual_user.get_worked_time()[0])} h', 'vacation': f'{actual_user.count_absence_days("U")} Tage',
'vacation_remaining': f'{actual_user.get_vacation_claim() - actual_user.count_absence_days("U")} Tage', 'illness': f'{actual_user.count_absence_days("K")} Tage'})
ui.table(columns=ov_columns, rows=ov_rows, row_key='username', column_defaults={'align': 'left', 'sortable': True})
overview_table()
ui.button("Aktualisieren", on_click=overview_table.refresh)
with ui.tab_panel(vacation_applications):
date_string = '%d.%m.%Y'
@ui.refreshable
def va_table():
va_columns = [ {'label': 'Benutzername', 'name': 'username', 'field': 'username'},
{'label': 'Name', 'name': 'name', 'field': 'name'},
{'label': 'key', 'name': 'key', 'field': 'key', 'classes': 'hidden', 'headerClasses': 'hidden'},
{'label': 'Anfang', 'name': 'start', 'field': 'start'},
{'label': 'Ende', 'name': 'end', 'field': 'end'},
{'label': 'Resturlaub', 'name': 'vacation_remaining', 'field': 'vacation_remaining'},
{'label': 'Resturlaub nach Genehmigung', 'name': 'vacation_remaining_after_submission', 'field': 'vacation_remaining_after_submission'}
]
va_rows = [ ]
for username in userlist:
actual_user = user(username)
open_va = actual_user.get_open_vacation_applications()
for i, dates in open_va.items():
startdate_dt = datetime.datetime.strptime(dates[0], '%Y-%m-%d')
enddate_dt = datetime.datetime.strptime(dates[1], '%Y-%m-%d')
vacation_start_to_end = (enddate_dt-startdate_dt).days
vacation_duration = 0
for day_counter in range(0, vacation_start_to_end):
new_date = startdate_dt + datetime.timedelta(days=day_counter)
if int(actual_user.get_day_workhours(new_date.year, new_date.month, new_date.day)) > 0:
if not datetime.datetime(new_date.year, new_date.month, new_date.day).strftime('%Y-%m-%d') in list(load_adminsettings()["holidays"]):
vacation_duration += 1
va_rows.append({'username': username,
'name': actual_user.fullname,
'key': f'{username}-{i}',
'start': startdate_dt.strftime(date_string),
'end': enddate_dt.strftime(date_string),
'vacation_remaining': f'{actual_user.get_vacation_claim() - actual_user.count_absence_days("U", startdate_dt.year)} Tage',
'vacation_remaining_after_submission': f'{actual_user.get_vacation_claim() - actual_user.count_absence_days("U", startdate_dt.year) - vacation_duration } Tage'})
global vacation_table
vacation_table = ui.table(columns=va_columns, rows=va_rows, row_key='key', selection='single', column_defaults={'align': 'left', 'sortable': True})
va_table()
def approve_vacation():
global vacation_table
for selection in vacation_table.selected:
key = selection["key"]
username = key.split("-")[0]
index = key.split("-")[1]
actual_user = user(username)
startdate_dt = datetime.datetime.strptime(selection["start"], date_string)
enddate_dt = datetime.datetime.strptime(selection["end"], date_string)
delta = (enddate_dt - startdate_dt).days
for i in range(0, delta):
new_date = startdate_dt + datetime.timedelta(days=i)
if int(actual_user.get_day_workhours(new_date.year, new_date.month, new_date.day)) > 0:
if not datetime.datetime(new_date.year, new_date.month, new_date.day).strftime('%Y-%m-%d') in list(load_adminsettings()["holidays"]):
actual_user.update_absence(new_date.year, new_date.month, new_date.day, "U")
ui.notify(f"Urlaub vom {selection['start']} bis {selection['end']} für {actual_user.fullname} eingetragen")
try:
retract_result = actual_user.revoke_vacation_application(index)
except IndexError:
ui.notify("Fehler beim Entfernen des Urlaubsantrages nach dem Eintragen.")
va_table.refresh()
timetable.refresh()
overview_table.refresh()
def deny_vacation():
for selection in vacation_table.selected:
key = selection["key"]
username = key.split("-")[0]
index = key.split("-")[1]
actual_user = user(username)
try:
retract_result = actual_user.revoke_vacation_application(index)
ui.notify(f"Urlaubsantrag vom {selection['start']} bis {selection['end']} für {actual_user.fullname} entfernt.")
except IndexError:
ui.notify("Fehler beim Entfernen des Urlaubsantrages. Ggf. wurde dieser zurückgezogen.")
va_table.refresh()
overview_table.refresh()
ui.button("Aktualisieren", on_click=va_table.refresh)
ui.separator()
with ui.grid(columns=2):
ui.button("Genehmigen", on_click=approve_vacation)
ui.button("Ablehnen", on_click=deny_vacation)
with ui.tab_panel(settings):
with ui.grid(columns='auto auto'):
@ -1240,6 +1358,7 @@ Dies kann nicht rückgängig gemacht werden!''')
ui.button("Speichern", on_click=save_workhours)
ui.button("Löschen", on_click=delete_workhour_entry)
user_selection_changed()
with ui.tab_panel(backups):
try:

View File

@ -525,7 +525,7 @@ def json_info(api_key: str):
data["time"]["overall"] = time_saldo
data["vacation"] = { }
data["vacation"]["claim"] = current_user.get_vacation_claim(now_dt.year, now_dt.month, now_dt.day)
data["vacation"]["used"] = current_user.count_vacation_days(now_dt.year)
data["vacation"]["used"] = current_user.count_absence_days("U", now_dt.year)
data["vacation"]["remaining"] = data["vacation"]["claim"] - data["vacation"]["used"]
return data
break

View File

@ -17,6 +17,7 @@ backupfolder = str(os.path.join(scriptpath, "backup"))
usersettingsfilename = "settings.json"
photofilename = "photo.jpg"
va_file = "vacation_application.json"
# Status

View File

@ -187,8 +187,15 @@ def homepage():
ui.separator()
with ui.grid(columns='1fr auto 1fr').classes('w-full justify-center'):
with ui.tabs().classes('w-full items-center') as tabs:
overviews = ui.tab('Übersichten')
absence = ui.tab('Urlaubsantrag')
with ui.grid(columns='1fr auto 1fr').classes('w-full items-center'):
ui.space()
with ui.tab_panels(tabs, value=overviews):
with ui.tab_panel(overviews):
def activate_vacation():
binder_vacation.value = True
@ -219,6 +226,53 @@ def homepage():
ui.navigate.to("/")
ui.button("Logout", on_click=logout).classes('col-span-2')
with ui.tab_panel(absence):
ui.label("Urlaub für folgenden Zeitraum beantragen:")
vacation_date = ui.date().props('range today-btn')
def vacation_submission():
if vacation_date.value == None:
return None
try:
current_user.vacation_application(vacation_date.value["from"], vacation_date.value["to"])
except TypeError:
current_user.vacation_application(vacation_date.value, vacation_date.value)
vacation_date.value = ""
with ui.dialog() as dialog, ui.card():
ui.label("Urlaubsantrag wurde abgeschickt")
ui.button("OK", on_click=dialog.close)
open_vacation_applications.refresh()
dialog.open()
ui.button("Einreichen", on_click=vacation_submission).classes('w-full items-center').tooltip("Hiermit reichen Sie einen Urlaubsantrag für den oben markierten Zeitraum ein.")
@ui.refreshable
def open_vacation_applications():
open_applications = current_user.get_open_vacation_applications()
if len(list(open_applications)) > 0:
ui.separator()
ui.label("Offene Urlaubsanträge:").classes('font-bold')
va_columns = [ {'label': 'Index', 'name': 'index', 'field': 'index', 'classes': 'hidden', 'headerClasses': 'hidden'},
{'label': 'Start', 'name': 'start', 'field': 'start'},
{'label': 'Ende', 'name': 'end', 'field': 'end'}]
va_rows = [ ]
date_string = '%d.%m.%Y'
for i, dates in open_applications.items():
startdate_dt = datetime.datetime.strptime(dates[0], '%Y-%m-%d')
enddate_dt = datetime.datetime.strptime(dates[1], '%Y-%m-%d')
va_rows.append({'index': i, 'start': startdate_dt.strftime(date_string), 'end': enddate_dt.strftime(date_string)})
va_table = ui.table(columns=va_columns, rows=va_rows, selection="single", row_key="index").classes('w-full')
def retract_va():
try:
retract_result = current_user.revoke_vacation_application(va_table.selected[0]["index"])
open_vacation_applications.refresh()
if retract_result == 0:
ui.notify("Urlaubsantrag zurückgezogen")
except IndexError:
ui.notify("Kein Urlaubsanstrag ausgewählt")
ui.button("Zurückziehen", on_click=retract_va).tooltip("Hiermit wird der oben gewählte Urlaubsantrag zurückgezogen.").classes('w-full')
open_vacation_applications()
ui.space()
else:

View File

@ -5,13 +5,17 @@ import hashlib
import os
from calendar import monthrange
from stat import S_IREAD, S_IWUSR
from nicegui import ui
import datetime
import time
import json
import shutil
import re
from lib.definitions import userfolder, scriptpath, usersettingsfilename, photofilename, status_in, status_out, standard_adminsettings, standard_usersettings
from lib.definitions import userfolder, scriptpath, usersettingsfilename, photofilename, status_in, status_out, \
standard_adminsettings, standard_usersettings, va_file
# Benutzerklasse
@ -59,7 +63,6 @@ class user:
with open(filename, 'a') as file:
# Schreibe den Timestamp in die Datei und füge einen Zeilenumbruch hinzu
file.write(f"{timestamp}\n")
file.close()
except FileNotFoundError:
# Fehlende Verzeichnisse anlegen
folder_path = os.path.dirname(filename)
@ -261,7 +264,7 @@ class user:
filename_txt = os.path.join(self.userfolder, f"{year}-{month}.txt")
os.chmod(filename_txt, S_IREAD)
def get_last_months_overtime(self, year, month):
def get_last_months_overtime(self, year=datetime.datetime.now().year, month=datetime.datetime.now().month):
try:
if int(month) == 1:
year = str(int(year) - 1)
@ -385,7 +388,7 @@ class user:
hours_to_work = -1
return hours_to_work
def get_vacation_claim(self, year, month, day):
def get_vacation_claim(self, year=datetime.datetime.now().year, month=datetime.datetime.now().month, day=datetime.datetime.now().day):
workhour_entries = list(self.workhours)
workhour_entries.sort()
day_to_check = datetime.datetime(int(year), int(month), int(day))
@ -403,18 +406,18 @@ class user:
return int(claim)
def count_vacation_days(self, year):
vacation_used = 0
def count_absence_days(self, absence_code: str, year=datetime.datetime.now().year):
absence_days = 0
for month in range(0, 13):
try:
absence_dict = self.get_absence(year, month)
for entry, absence_type in absence_dict.items():
if absence_type == "U":
vacation_used += 1
if absence_type == absence_code:
absence_days += 1
except:
pass
return vacation_used
return absence_days
def delete_photo(self):
os.remove(self.photofile)
@ -451,6 +454,44 @@ class user:
return [total_time, in_time_stamp]
def vacation_application(self, startdate, enddate):
application_file = os.path.join(self.userfolder, va_file)
try:
with open(application_file, 'r') as json_file:
applications = json.load(json_file)
except FileNotFoundError:
applications = { }
applications[str(len(list(applications)))] = (startdate, enddate)
with open(application_file, 'w') as json_file:
json_file.write(json.dumps(applications, indent=4))
def get_open_vacation_applications(self):
application_file = os.path.join(self.userfolder, va_file)
try:
with open(application_file, 'r') as json_file:
applications = json.load(json_file)
except FileNotFoundError:
applications = { }
return applications
def revoke_vacation_application(self, index):
application_file = os.path.join(self.userfolder, va_file)
with open(application_file, 'r') as json_file:
applications = json.load(json_file)
try:
del(applications[index])
new_applications = { }
new_index = 0
for index, dates in applications.items():
new_applications[new_index] = dates
new_index += 1
with open(application_file, 'w') as json_file:
json_file.write(json.dumps(new_applications, indent=4))
return 0
except KeyError:
ui.notify("Urlaubsantrag wurde schon bearbeitet")
return -1
# Benutzer auflisten
def list_users():

View File

@ -71,6 +71,7 @@
"2030-10-03": "Tag der deutschen Einheit",
"2030-10-30": "Reformationstag",
"2030-12-25": "1. Weihnachtsfeiertag",
"2030-12-26": "2. Weihnachtsfeiertag"
"2030-12-26": "2. Weihnachtsfeiertag",
"2025-06-11": "Testeintrag"
}
}

View File

@ -0,0 +1 @@
{}

View File

@ -1,8 +1,7 @@
{
"username": "testuser10",
"fullname": "Diego Dieci",
"password": "123456789",
"api_key": "807518cd5bd85c1e4855d340f9b77b23eac21b7f",
"password": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"workhours": {
"2024-04-01": {
"1": "1",
@ -14,5 +13,6 @@
"7": "7",
"vacation": "30"
}
}
},
"api_key": "807518cd5bd85c1e4855d340f9b77b23eac21b7f"
}

View File

@ -1,14 +1,6 @@
{
"0": [
"2025-05-05",
"2025-05-05"
],
"1": [
"2025-05-06",
"2025-05-14"
],
"2": [
"2025-05-19",
"2025-05-22"
"2025-06-09",
"2025-06-19"
]
}

View File

@ -0,0 +1,18 @@
{
"username": "testuser2",
"fullname": "testuser2",
"password": "37a8eec1ce19687d132fe29051dca629d164e2c4958ba141d5f4133a33f0688f",
"api_key": "84799b1cbb92514f047bc2186cb4b4aafb352d69",
"workhours": {
"2025-05-27": {
"1": 0,
"2": 0,
"3": 0,
"4": 0,
"5": 0,
"6": 0,
"7": 0,
"vacation": 0
}
}
}