Alexander Malzkuhn a12bd1e15a Zusätzliche Übersichtsansicht im Adminbereich
Genehmigungsfunktion für Urlaube mit automatischer Eintragung
2025-05-28 12:32:48 +02:00

556 lines
26 KiB
Python

import sys
import os
import zipfile
from calendar import month_name
from logging import exception
from nicegui import *
from lib.definitions import *
from lib.web_ui import *
from lib.users import *
from datetime import datetime
import calendar
# Überblicksseite zum Ausdrucken oder als PDF speichern
@ui.page('/api/month/{username}/{year}-{month}')
def page_overview_month(username: str, year: int, month: int):
try:
admin_auth = app.storage.user['admin_authenticated']
except:
admin_auth = False
if login_is_valid(username) or admin_auth:
data = load_adminsettings()
try:
current_user = user(username)
days_with_errors = current_user.archiving_validity_check(year, month)
ui.page_title(f"Bericht für {current_user.fullname} für {calendar.month_name[month]} {year}")
if current_user.get_archive_status(year, month):
with ui.column().classes('w-full items-end gap-0'):
ui.label(f"Bericht erstellt am {datetime.now().strftime('%d.%m.%Y %H:%M:%S')}")
ui.label('Archiviert').classes('italic').classes('text-red text-bold text-xl')
#ui.add_head_html('<style>body {background-color: #FFF7B1; }</style>')
else:
with ui.column().classes('w-full items-end gap-0'):
ui.label(f"Bericht erstellt am {datetime.now().strftime('%d.%m.%Y %H:%M:%S')}")
ui.markdown(f'#Bericht für {current_user.fullname} für {calendar.month_name[month]} {year}')
pad_x = 4
pad_y = 0
color_weekend = "gray-100"
color_holiday = "gray-100"
def overview_table():
# Timestamp in ein Array schreiben
timestamps = current_user.get_timestamps(year, month)
timestamps.sort()
# Abwesenheitsdaten in ein Dict schreiben
user_absent = current_user.get_absence(year, month)
# Dictionary für sortierte Timestamps
timestamps_dict = { }
# Dictionary mit zunächst leeren Tageinträgen befüllen
for day in range(1, monthrange(year, month)[1] + 1):
# Jeder Tag bekommt eine leere Liste
timestamps_dict[day] = [ ]
# Timestamps den Monatstagen zuordnen
for stamp in timestamps:
day_of_month_of_timestamp = datetime.fromtimestamp(int(stamp)).day
timestamps_dict[day_of_month_of_timestamp].append(int(stamp))
general_saldo = 0
bg_color = ''
if current_user.get_archive_status(year, month):
bg_color = ' bg-yellow-100'
with ui.grid(columns='auto auto 1fr 1fr 1fr').classes(f'gap-0 border px-0 py-0{bg_color}'):
ui.markdown("**Datum**").classes(f'border px-{pad_x} py-{pad_y}')
ui.markdown("**Buchungen**").classes(f'border px-{pad_x} py-{pad_y}')
ui.markdown("**Ist**").classes(f'border px-{pad_x} py-{pad_y}')
ui.markdown("**Soll**").classes(f'border px-{pad_x} py-{pad_y}')
ui.markdown("**Saldo**").classes(f'border px-{pad_x} py-{pad_y}')
# Gehe jeden einzelnen Tag des Dictionaries für die Timestamps durch
for day in list(timestamps_dict):
booking_text = ""
color_day = 'inherit'
if datetime(year, month, day).strftime('%w') in ["0", "6"]:
color_day = color_weekend
current_day_date = f"{datetime(year, month, day).strftime('%a')}, {day}.{month}.{year}"
with ui.link_target(day).classes(f'border px-{pad_x} py-{pad_y} bg-{color_day}'):
ui.markdown(current_day_date)
# Abwesenheitseinträge
booking_color = "inherit"
booking_text_color = "inherit"
try:
# Abwesenheitszeiten behandeln
for i in list(user_absent):
if int(i) == day:
booking_text += absence_entries[user_absent[i]]["name"] + "<br>"
booking_color = absence_entries[user_absent[i]]["color"]
booking_text_color = absence_entries[user_absent[i]]["text-color"]
except:
pass
# Buchungen behandeln
for i in range(0, len(timestamps_dict[day]), 2):
try:
temp_pair = [timestamps_dict[day][i], timestamps_dict[day][i + 1]]
booking_text = booking_text + str(datetime.fromtimestamp(temp_pair[0]).strftime('%H:%M')) + " - " + str(datetime.fromtimestamp(temp_pair[1]).strftime('%H:%M')) + "<br>"
except:
if len(timestamps_dict[day]) % 2 != 0:
booking_text += datetime.fromtimestamp(int(timestamps_dict[day][i])).strftime('%H:%M') + " - ***Buchung fehlt!***"
day_notes = current_user.get_day_notes(year, month, day)
just_once = True
with ui.column().classes(f'border px-{pad_x} py-{pad_y} bg-{booking_color} text-{booking_text_color}'):
booking_text_element = ui.markdown(booking_text)
if len(day_notes) > 0:
if len(timestamps_dict[day]) > 0 or day in list(map(int, list(user_absent))):
ui.separator()
for user_key, notes in day_notes.items():
if user_key == "admin":
ui.markdown(f"Administrator:<br>{notes}")
else:
with ui.element():
ui.markdown(f"{current_user.fullname}:<br>{notes}")
if len(day_notes) > 1 and just_once:
ui.separator()
just_once = False
# Ist-Zeiten berechnen
timestamps_of_this_day = []
# Suche mir alle timestamps für diesen Tag
for i in timestamps:
actual_timestamp = datetime.fromtimestamp(int(i))
timestamp_day = actual_timestamp.strftime('%-d')
if int(timestamp_day) == int(day):
timestamps_of_this_day.append(i)
timestamps_of_this_day.sort()
time_sum = 0
if len(timestamps_of_this_day) > 1:
if len(timestamps_of_this_day) % 2 == 0:
for i in range(0, len(timestamps_of_this_day), 2):
time_delta = int(
timestamps_of_this_day[i + 1]) - int(
timestamps_of_this_day[i])
time_sum = time_sum + time_delta
else:
for i in range(0, len(timestamps_of_this_day) - 1, 2):
time_delta = int(
timestamps_of_this_day[i + 1]) - int(
timestamps_of_this_day[i])
time_sum = time_sum + time_delta
is_time = convert_seconds_to_hours(time_sum) + " h"
else:
is_time = "Kein"
ui.markdown(is_time).classes(f'border px-{pad_x} py-{pad_y} text-center')
# Sollzeit bestimmen
hours_to_work = int(current_user.get_day_workhours(year, month, day))
if hours_to_work < 0:
target_time = ""
else:
target_time = f"{convert_seconds_to_hours(int(hours_to_work) * 3600)} h"
if int(hours_to_work) == 0:
booking_text = "Kein Arbeitstag"
date_dt = datetime(year, month, day)
if date_dt.strftime("%Y-%m-%d") in data["holidays"]:
booking_text = f'**{data["holidays"][date_dt.strftime("%Y-%m-%d")]}**'
booking_text_element.set_content(booking_text)
ui.markdown(target_time).classes(f'border px-{pad_x} py-{pad_y} text-center')
# Saldo für den Tag berechnen
day_in_list = datetime(year, month, day)
if time.time() > day_in_list.timestamp():
time_duty = int(current_user.get_day_workhours(year, month, day)) * 3600
if time_duty < 0:
saldo = 0
total = ""
booking_text = "Kein Arbeitsverhältnis"
booking_text_element.set_content(booking_text)
else:
saldo = int(time_sum) - int(time_duty)
# Nach Abwesenheitseinträgen suchen
try:
for i in list(user_absent):
if int(i) == day and user_absent[i] != "UU":
saldo = 0
except:
pass
general_saldo = general_saldo + saldo
total = f"{convert_seconds_to_hours(saldo)} h"
else:
total = "-"
if total == "-":
total_class = 'text-center'
else:
total_class = 'text-right'
ui.markdown(total).classes(total_class).classes(f'border px-{pad_x} py-{pad_y}')
# Überstundenzusammenfassung
ui.markdown("Überstunden aus Vormonat:").classes(f'col-span-4 text-right border px-{pad_x} py-{pad_y}')
last_months_overtime = current_user.get_last_months_overtime(year, month)
ui.markdown(f"{convert_seconds_to_hours(last_months_overtime)} h").classes(f'text-right border px-{pad_x} py-{pad_y}')
ui.markdown("Überstunden diesen Monat:").classes(f'col-span-4 text-right border px-{pad_x} py-{pad_y}')
ui.markdown(f"{convert_seconds_to_hours(general_saldo)} h").classes(f'text-right border px-{pad_x} py-{pad_y}')
ui.markdown("**Überstunden Gesamt:**").classes(f'col-span-4 text-right border px-{pad_x} py-{pad_y}')
global overtime_overall
overtime_overall = last_months_overtime + general_saldo
ui.markdown(f"**{convert_seconds_to_hours(overtime_overall)} h**").classes(f'text-right border px-{pad_x} py-{pad_y}')
overview_table()
def absence_table():
absences_this_month = current_user.get_absence(year, month)
absence_dict = { }
for abbr in list(absence_entries):
absence_dict[abbr] = 0
for key, value in absences_this_month.items():
if value in list(absence_dict):
absence_dict[value] += 1
total_absence_days = 0
for key, value in absence_dict.items():
total_absence_days += absence_dict[key]
if total_absence_days > 0:
ui.markdown("###Abwesenheitstage diesen Monat:")
with ui.grid(columns='auto 25%').classes(f'gap-0 border px-0 py-0'):
for key, value in absence_dict.items():
if value > 0:
ui.markdown(absence_entries[key]['name']).classes(f"border px-{pad_x} py-{pad_y}")
ui.markdown(str(value)).classes(f'border px-{pad_x} py-{pad_y} text-center')
absence_table()
def archive():
current_year = datetime.now().year
current_month = datetime.now().month
archivable = False
if current_year > year:
if current_user.get_archive_status(year, month) == False:
archivable = True
if current_year == year:
if current_month > month:
if current_user.get_archive_status(year, month) == False:
archivable = True
def archive_dialog():
def do_archiving():
global overtime_overall
current_user.archive_hours(year, month, overtime_overall)
dialog.close()
ui.navigate.to(f'/api/month/{username}/{year}-{month}')
with ui.dialog() as dialog, ui.card():
with ui.grid(columns='1fr 1fr'):
ui.markdown("Hiermit bestätigen Sie, dass die Zeitbuchungen im Montagsjournal korrekt sind.<br>Sollte dies nicht der Fall sein, wenden Sie sich für eine Korrektur an den Administrator.").classes('col-span-2')
ui.button("Archivieren", on_click=do_archiving)
ui.button("Abbrechen", on_click=dialog.close)
dialog.open()
if archivable == True:
if len(days_with_errors) > 0:
ui.label("Es gibt Inkonsistenzen in den Buchungen. Folgende Tage müssen überprüft werden:")
with ui.grid(columns=len(days_with_errors)):
for i in days_with_errors:
ui.link(f"{i}.", f'#{i}')
archive_button = ui.button("Archivieren", on_click=archive_dialog)
if len(days_with_errors) > 0:
archive_button.disable()
archive()
except Exception as e:
print(str(type(e).__name__) + " " + str(e))
if type(e) == UnboundLocalError:
ui.markdown('#Fehler')
ui.markdown('Benutzer existiert nicht')
else:
ui.markdown('#Fehler')
ui.markdown(str(type(e)))
ui.markdown(str(e))
else:
login_mask(target=f'/api/month/{username}/{year}-{month}')
@ui.page('/api/vacation/{username}/{year}')
def page_overview_vacation(username: str, year: int):
try:
admin_auth = app.storage.user['admin_authenticated']
except:
admin_auth = False
if login_is_valid(username) or admin_auth:
try:
current_user = user(username)
month = datetime.now().month
day = datetime.now().day
ui.page_title(f"Urlaubsanspruch für {current_user.fullname} für {year}")
ui.label(datetime.now().strftime('%d.%m.%Y')).classes('absolute top-5 right-5')
ui.space()
ui.markdown(f'#Urlaubsanspruch für {current_user.fullname} für {year}')
pad_x = 4
pad_y = 0
vacationclaim = int(current_user.get_vacation_claim(year, month, day))
if vacationclaim == -1:
ui.markdown(f"###Kein Urlaubsanspruch für {year}")
else:
with ui.grid(columns='auto auto').classes(f'gap-0 border px-0 py-0'):
ui.markdown(f"Urlaubsanspruch für {year}:").classes(f'border px-{pad_x} py-{pad_y}')
ui.markdown(f"{vacationclaim} Tage").classes(f'text-right border px-{pad_x} py-{pad_y}')
ui.markdown("Registrierte Urlaubstage").classes(f'border px-{pad_x} py-{pad_y} col-span-2')
vacation_counter = 0
try:
for i in range(1, 13):
absence_entries = current_user.get_absence(year, i)
for day, absence_type in absence_entries.items():
# print(day + "." + str(i) + " " + absence_type)
if absence_type == "U":
day_in_list = datetime(int(year), int(i), int(day)).strftime("%d.%m.%Y")
ui.markdown(day_in_list).classes(f'border px-{pad_x} py-{pad_y}')
ui.markdown("-1 Tag").classes(f'border px-{pad_x} py-{pad_y} text-center')
vacation_counter += 1
except Exception as e:
print(str(type(e).__name__) + " " + str(e))
ui.markdown("**Resturlaub:**").classes(f'border px-{pad_x} py-{pad_y}')
ui.markdown(f'**{str(vacationclaim - vacation_counter)} Tage**').classes(f'border px-{pad_x} py-{pad_y} text-center')
except Exception as e:
print(str(type(e).__name__) + " " + str(e))
if type(e) == UnboundLocalError:
ui.markdown('#Fehler')
ui.markdown('Benutzer existiert nicht')
else:
ui.markdown('#Fehler')
ui.markdown(str(type(e)))
ui.markdown(str(e))
else:
login = login_mask(target=f'/api/vacation/{username}/{year}')
@ui.page('/api/absence/{username}/{year}')
def page_overview_absence(username: str, year: int):
try:
admin_auth = app.storage.user['admin_authenticated']
except:
admin_auth = False
if login_is_valid(username) or admin_auth:
current_user = user(username)
ui.page_title(f"Abwesenheitsübersicht für {current_user.fullname} für {year}")
ui.label(datetime.now().strftime('%d.%m.%Y')).classes('absolute top-5 right-5')
ui.space()
pageheader(f"Abwesenheitsübersicht für {current_user.fullname} für {year}")
pad_x = 2
pad_y = 0
def absence_calender():
column_constructor = 'auto '
for j in range(1, 31):
column_constructor += "1fr "
column_constructor += 'auto'
with ui.grid(columns=column_constructor).classes(f'gap-0 border px-0 py-0') as calendar_grid:
# Erste Zeile
ui.space()
for i in range(1, 32):
ui.markdown(str(i)).classes(f'border px-{pad_x} py-{pad_y} text-center')
# Monate durchgehen
for month in range(1, 13):
for column in range(0, 32):
if column == 0:
ui.markdown(month_name[month]).classes(f'border px-{pad_x} py-{pad_y} text.center')
else:
absences = current_user.get_absence(year, month)
if str(column) in list(absences):
bg_color = absence_entries[absences[str(column)]]['color']
text_color = absence_entries[absences[str(column)]]['text-color']
tooltip_text = absence_entries[absences[str(column)]]['name']
with ui.element():
ui.markdown(absences[str(column)]).classes(f'border px-{pad_x} py-{pad_y} bg-{bg_color} text-{text_color} align-middle text-center')
ui.tooltip(tooltip_text)
else:
tooltip_text = ""
if column > monthrange(year, month)[1]:
bg_color = 'gray-500'
tooltip_text="Tag exisitiert nicht"
elif int(current_user.get_day_workhours(year, month, column)) == 0:
bg_color = 'gray-300'
tooltip_text = "Kein Arbeitstag"
elif int(current_user.get_day_workhours(year, month, column)) == -1:
bg_color = 'gray-400'
tooltip_text = "Kein Arbeitsverhältnis"
else:
bg_color = 'inherit'
with ui.label("").classes(f'border px-{pad_x} py-{pad_y} bg-{bg_color}'):
if tooltip_text != "":
ui.tooltip(tooltip_text)
absence_calender()
def absence_table():
with ui.grid(columns='auto auto').classes(f'gap-0 px-0 py-0'):
ui.markdown('**Summen**').classes('col-span-2 px-2')
for type in list(absence_entries):
number_of_days = 0
ui.markdown(absence_entries[type]["name"]).classes(f'border px-{pad_x} py-{pad_y}')
for month in range(1, 13):
absences_of_month = current_user.get_absence(year, month)
for i in list(absences_of_month):
if absences_of_month[i] == type:
number_of_days += 1
ui.markdown(str(number_of_days)).classes(f'border px-{pad_x} py-{pad_y} text-center')
absence_table()
else:
login = login_mask(target=f'/api/absence/{username}/{year}')
@app.get('/api/stamp/{api_key}')
def json_stamp(api_key: str):
userlist = list_users()
user_dict = {}
# Dictionary mit Usernamen befüllen
for i in userlist:
user_dict[i] = ""
for entry in list(user_dict):
try:
temp_user = user(entry)
user_dict[entry] = temp_user.api_key
except:
pass
returndata = {}
for user_key, api_value in user_dict.items():
if api_key == api_value:
current_user = user(user_key)
current_user.timestamp()
returndata["username"] = current_user.username
if current_user.stamp_status() == status_in:
returndata["stampstatus"] = True
else:
returndata["stampstatus"] = False
break
else:
returndata["username"] = None
return returndata
@app.get("/api/json/{api_key}")
def json_info(api_key: str):
userlist = list_users()
user_dict = {}
# Dictionary mit Usernamen befüllen
for i in userlist:
user_dict[i] = ""
for entry in list(user_dict):
try:
temp_user = user(entry)
user_dict[entry] = temp_user.api_key
except:
pass
found_key = False
for user_key, api_value in user_dict.items():
if api_key == api_value:
current_user = user(user_key)
now_dt = datetime.now()
year = now_dt.year
month = now_dt.month
day = now_dt.day
data = { }
data["user"] = current_user.username
if current_user.stamp_status() == status_in:
data["status"] = 1
else:
data["status"] = 0
absences = current_user.get_absence(now_dt.year, now_dt.month)
data["absence"] = 0
if str(now_dt.day) in list(absences):
data["absence"] = absences[str(now_dt.day)]
data["time"] = { }
data["time"]["today"] = current_user.get_worked_time(now_dt.year, now_dt.month, now_dt.day)[0]
# Arbeitszeit berechnen
months_time_sum = 0
for checkday in range(1, day + 1):
months_time_sum += (int(current_user.get_worked_time(year, month, checkday)[0]) - int(current_user.get_day_workhours(year, month, checkday))*3600)
time_saldo = months_time_sum + current_user.get_last_months_overtime(year, month)
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_absence_days("U", now_dt.year)
data["vacation"]["remaining"] = data["vacation"]["claim"] - data["vacation"]["used"]
return data
break
if not found_key:
return { "data": "none"}
@app.get('/api/backup/{api_key}')
def backup_api(api_key: str):
date_format = '%Y-%m-%d_%H-%M'
searchpath = backupfolder
def make_backup():
compress = zipfile.ZIP_DEFLATED
filename = os.path.join(searchpath, datetime.now().strftime(date_format) + '.zip')
folder = userfolder
with zipfile.ZipFile(filename, 'w', compress) as target:
for root, dirs, files in os.walk(folder):
for file in files:
add = os.path.join(root, file)
target.write(add)
target.write(usersettingsfilename)
target.writestr("app_version.txt", data=app_version)
if api_key == load_adminsettings()["backup_api_key"]:
make_backup()
return {"backup": datetime.now().strftime(date_format), "success": True}
else:
return {"backup": datetime.now().strftime(date_format), "success": False}