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('') 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.label(f'Bericht für {current_user.fullname} für {calendar.month_name[month]} {year}').classes(h1) pad_x = 4 pad_y = 2 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.label("Datum").classes(f'border px-{pad_x} py-{pad_y} text-bold') ui.label("Buchungen").classes(f'border px-{pad_x} py-{pad_y} text-bold') ui.label("Ist").classes(f'border px-{pad_x} py-{pad_y} text-bold') ui.label("Soll").classes(f'border px-{pad_x} py-{pad_y} text-bold') ui.label("Saldo").classes(f'border px-{pad_x} py-{pad_y} text-bold') # 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.label(current_day_date) # Abwesenheitseinträge booking_color = "inherit" booking_text_color = "inherit" bold = '' try: # Abwesenheitszeiten behandeln for i in list(user_absent): if int(i) == day: booking_text += absence_entries[user_absent[i]]["name"] + "\n" booking_color = absence_entries[user_absent[i]]["color"] booking_text_color = absence_entries[user_absent[i]]["text-color"] bold = 'text-bold' 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')) + "\n" 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.label(booking_text).style('white-space: pre-wrap').classes(bold) 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.label(f"Administrator:\n{notes}").style('white-space: pre-wrap') else: with ui.element(): ui.label(f"{current_user.fullname}:\n{notes}").style('white-space: pre-wrap') 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.label(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.classes('text-bold') booking_text_element.text = booking_text ui.label(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.value = 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.label(total).classes(total_class).classes(f'border px-{pad_x} py-{pad_y}') # Überstundenzusammenfassung ui.label("Ü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.label(f"{convert_seconds_to_hours(last_months_overtime)} h").classes(f'text-right border px-{pad_x} py-{pad_y}') ui.label("Überstunden diesen Monat:").classes(f'col-span-4 text-right border px-{pad_x} py-{pad_y}') ui.label(f"{convert_seconds_to_hours(general_saldo)} h").classes(f'text-right border px-{pad_x} py-{pad_y}') ui.label("Überstunden Gesamt:").classes(f'col-span-4 text-right border px-{pad_x} py-{pad_y} text-bold') global overtime_overall overtime_overall = last_months_overtime + general_saldo ui.label(f"{convert_seconds_to_hours(overtime_overall)} h").classes(f'text-right border px-{pad_x} py-{pad_y} text-bold') 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.label("Abwesenheitstage diesen Monat:").classes(h3) 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.label(absence_entries[key]['name']).classes(f"border px-{pad_x} py-{pad_y}") ui.label(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.label("Hiermit bestätigen Sie, dass die Zeitbuchungen im Montagsjournal korrekt sind.\nSollte dies nicht der Fall sein, wenden Sie sich für eine Korrektur an den Administrator.").classes('col-span-2').style('white-space: pre-wrap') 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.label('Fehler').classes(h1) ui.label('Benutzer existiert nicht') else: ui.label('Fehler').classes(h1) ui.label(str(type(e))) ui.label(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('w-full text-right') ui.label(f'Urlaubsanspruch für {current_user.fullname} für {year}').classes(h1) pad_x = 4 pad_y = 2 vacationclaim = int(current_user.get_vacation_claim(year, month, day)) if vacationclaim == -1: ui.label(f"Kein Urlaubsanspruch für {year}").classes(h3) else: with ui.grid(columns='auto auto').classes(f'gap-0 border px-0 py-0'): ui.label(f"Urlaubsanspruch für {year}:").classes(f'border px-{pad_x} py-{pad_y}') ui.label(f"{vacationclaim} Tage").classes(f'text-right border px-{pad_x} py-{pad_y}') ui.label("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.label(day_in_list).classes(f'border px-{pad_x} py-{pad_y}') ui.label("-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.label("Resturlaub:").classes(f'border px-{pad_x} py-{pad_y} text-bold') ui.label(f'{str(vacationclaim - vacation_counter)} Tage').classes(f'border px-{pad_x} py-{pad_y} text-center text-bold') except Exception as e: print(str(type(e).__name__) + " " + str(e)) if type(e) == UnboundLocalError: ui.label('Fehler').classes(h1) ui.label('Benutzer existiert nicht') else: ui.label('Fehler').classes(h1) ui.label(str(type(e))) ui.label(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('w-full text-right') pageheader(f"Abwesenheitsübersicht für {current_user.fullname} für {year}") pad_x = 2 pad_y = 1 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.label(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.label(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.label(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 items-baseline'): ui.label('Summen').classes('col-span-2 px-2 text-bold') for type in list(absence_entries): number_of_days = 0 ui.label(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.label(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.replace(f"{scriptpath}/") 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}