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) ui.button("Logout", on_click=admin_logout)
updates_available = ValueBinder()
updates_available.value = False
with ui.tabs() as tabs: with ui.tabs() as tabs:
time_overview = ui.tab('Zeitübersichten') time_overview = ui.tab('Zeitdaten')
settings = ui.tab('Einstellungen') settings = ui.tab('Einstellungen')
users = ui.tab('Benutzer') users = ui.tab('Benutzer')
backups = ui.tab('Backups') backups = ui.tab('Backups')
@ -56,9 +59,16 @@ def page_admin():
update_userlist() 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.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") ui.markdown("##Übersichten")
# Tabelle konstruieren # Tabelle konstruieren
@ -83,6 +93,7 @@ def page_admin():
def update_user(): def update_user():
current_user = user(time_user.value) current_user = user(time_user.value)
available_years = current_user.get_years() available_years = current_user.get_years()
try: try:
select_year.clear() select_year.clear()
select_year.set_options(available_years) select_year.set_options(available_years)
@ -91,6 +102,7 @@ def page_admin():
select_year.value = str(datetime.datetime.now().year) select_year.value = str(datetime.datetime.now().year)
except: except:
select_year.value = list(available_years)[0] select_year.value = list(available_years)[0]
update_months()
try: try:
select_month.value = datetime.datetime.now().month select_month.value = datetime.datetime.now().month
except: 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}**")
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() timetable()
button_update = ui.button("Aktualisieren", on_click=timetable.refresh) button_update = ui.button("Aktualisieren", on_click=timetable.refresh)
button_update.move(timetable_header) 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.tab_panel(settings):
with ui.grid(columns='auto auto'): 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("Speichern", on_click=save_workhours)
ui.button("Löschen", on_click=delete_workhour_entry) ui.button("Löschen", on_click=delete_workhour_entry)
user_selection_changed() user_selection_changed()
with ui.tab_panel(backups): with ui.tab_panel(backups):
try: try:

View File

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

View File

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

View File

@ -187,8 +187,15 @@ def homepage():
ui.separator() 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() ui.space()
with ui.tab_panels(tabs, value=overviews):
with ui.tab_panel(overviews):
def activate_vacation(): def activate_vacation():
binder_vacation.value = True binder_vacation.value = True
@ -219,6 +226,53 @@ def homepage():
ui.navigate.to("/") ui.navigate.to("/")
ui.button("Logout", on_click=logout).classes('col-span-2') 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() ui.space()
else: else:

View File

@ -5,13 +5,17 @@ import hashlib
import os import os
from calendar import monthrange from calendar import monthrange
from stat import S_IREAD, S_IWUSR from stat import S_IREAD, S_IWUSR
from nicegui import ui
import datetime import datetime
import time import time
import json import json
import shutil import shutil
import re 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 # Benutzerklasse
@ -59,7 +63,6 @@ class user:
with open(filename, 'a') as file: with open(filename, 'a') as file:
# Schreibe den Timestamp in die Datei und füge einen Zeilenumbruch hinzu # Schreibe den Timestamp in die Datei und füge einen Zeilenumbruch hinzu
file.write(f"{timestamp}\n") file.write(f"{timestamp}\n")
file.close()
except FileNotFoundError: except FileNotFoundError:
# Fehlende Verzeichnisse anlegen # Fehlende Verzeichnisse anlegen
folder_path = os.path.dirname(filename) folder_path = os.path.dirname(filename)
@ -261,7 +264,7 @@ class user:
filename_txt = os.path.join(self.userfolder, f"{year}-{month}.txt") filename_txt = os.path.join(self.userfolder, f"{year}-{month}.txt")
os.chmod(filename_txt, S_IREAD) 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: try:
if int(month) == 1: if int(month) == 1:
year = str(int(year) - 1) year = str(int(year) - 1)
@ -385,7 +388,7 @@ class user:
hours_to_work = -1 hours_to_work = -1
return hours_to_work 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 = list(self.workhours)
workhour_entries.sort() workhour_entries.sort()
day_to_check = datetime.datetime(int(year), int(month), int(day)) day_to_check = datetime.datetime(int(year), int(month), int(day))
@ -403,18 +406,18 @@ class user:
return int(claim) return int(claim)
def count_vacation_days(self, year): def count_absence_days(self, absence_code: str, year=datetime.datetime.now().year):
vacation_used = 0 absence_days = 0
for month in range(0, 13): for month in range(0, 13):
try: try:
absence_dict = self.get_absence(year, month) absence_dict = self.get_absence(year, month)
for entry, absence_type in absence_dict.items(): for entry, absence_type in absence_dict.items():
if absence_type == "U": if absence_type == absence_code:
vacation_used += 1 absence_days += 1
except: except:
pass pass
return vacation_used return absence_days
def delete_photo(self): def delete_photo(self):
os.remove(self.photofile) os.remove(self.photofile)
@ -451,6 +454,44 @@ class user:
return [total_time, in_time_stamp] 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 # Benutzer auflisten
def list_users(): def list_users():

View File

@ -71,6 +71,7 @@
"2030-10-03": "Tag der deutschen Einheit", "2030-10-03": "Tag der deutschen Einheit",
"2030-10-30": "Reformationstag", "2030-10-30": "Reformationstag",
"2030-12-25": "1. Weihnachtsfeiertag", "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", "username": "testuser10",
"fullname": "Diego Dieci", "fullname": "Diego Dieci",
"password": "123456789", "password": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"api_key": "807518cd5bd85c1e4855d340f9b77b23eac21b7f",
"workhours": { "workhours": {
"2024-04-01": { "2024-04-01": {
"1": "1", "1": "1",
@ -14,5 +13,6 @@
"7": "7", "7": "7",
"vacation": "30" "vacation": "30"
} }
} },
"api_key": "807518cd5bd85c1e4855d340f9b77b23eac21b7f"
} }

View File

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

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
}
}
}