diff --git a/.gitignore b/.gitignore index 7b5fe5d..187bb2d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ **/*.pyc +Testplan.md .idea .nicegui .venv diff --git a/docker-compose.yml b/docker-compose.yml index 2237bb1..36814c2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,8 +7,8 @@ services: environment: - PYTHONUNBUFFERED=1 volumes: - #- /home/alexander/Dokumente/Python/Zeiterfassung/lib:/app/lib - #- /home/alexander/Dokumente/Python/Zeiterfassung/main.py:/app/main.py + - /home/alexander/Dokumente/Python/Zeiterfassung/lib:/app/lib + - /home/alexander/Dokumente/Python/Zeiterfassung/main.py:/app/main.py - /home/alexander/Dokumente/Python/Zeiterfassung/docker-work/users:/users - /home/alexander/Dokumente/Python/Zeiterfassung/docker-work/backup:/backup - /home/alexander/Dokumente/Python/Zeiterfassung/docker-work/settings:/settings \ No newline at end of file diff --git a/lib/admin.py b/lib/admin.py index d7c3a25..0e632ab 100644 --- a/lib/admin.py +++ b/lib/admin.py @@ -1,4 +1,4 @@ -from datetime import datetime +from datetime import datetime, timedelta import dateutil.easter from dateutil.easter import * @@ -20,6 +20,7 @@ import hashlib import calendar import locale import segno +import shutil @ui.page('/admin') def page_admin(): @@ -524,7 +525,8 @@ def page_admin(): if str(actual_date.day) in list(absences): current_user.del_absence(actual_date.year, actual_date.month, actual_date.day) ui.notify(f"Eintrag {absence_entries[absences[str(actual_date.day)]]['name']} am {actual_date.day}.{actual_date.month}.{actual_date.year} überschrieben.") - current_user.update_absence(actual_date.year, actual_date.month, actual_date.day, absence_type) + if current_user.get_day_workhours(actual_date.year, actual_date.month, actual_date.day) > 0: + current_user.update_absence(actual_date.year, actual_date.month, actual_date.day, absence_type) actual_date = actual_date + datetime.timedelta(days=1) clear_card() @@ -589,12 +591,20 @@ def page_admin(): with ui.button(icon='menu').props('square') as menu_button: with ui.menu() as menu: + no_contract = False + start_of_contract = current_user.get_starting_day() + if datetime.datetime(int(select_year.value), int(select_month.value), day) < datetime.datetime(int(start_of_contract[0]), int(start_of_contract[1]), int(start_of_contract[2])): + no_contract = True + menu_item = ui.menu_item("Zeiteintrag hinzufügen", lambda day=day: add_entry(day)) if archive_status: menu_item.disable() if datetime.datetime.now().day < day: menu_item.disable() menu_item.tooltip("Kann keine Zeiteinträge für die Zukunft vornehmen.") + if no_contract: + menu_item.disable() + menu_item.tooltip("Kann keine Zeiteinträge für Zeit vor der Einstellung vornehmen") ui.separator() menu_item = ui.menu_item("Notizen bearbeiten", lambda day=day: edit_notes(day)) if archive_status: @@ -606,6 +616,10 @@ def page_admin(): menu_item.disable() if str(day) in list(user_absent): menu_item.disable() + if no_contract: + menu_item.disable() + menu_item.tooltip( + "Kann keine Zeiteinträge für Zeit vor der Einstellung vornehmen") if archive_status: menu_button.disable() @@ -984,8 +998,21 @@ def page_admin(): holiday_buttons_grid.refresh() with ui.column(): - starting_year = ui.number(value=datetime.datetime.now().year, label="Startjahr") - end_year = ui.number(value=starting_year.value, label="Endjahr") + end_year_binder = ValueBinder() + start_year_binder = ValueBinder() + def correct_end_year(): + if starting_year.value > end_year_binder.value: + end_year_binder.value = starting_year.value + + def correct_start_year(): + if starting_year.value > end_year_binder.value: + starting_year.value = end_year_binder.value + + start_year_binder.value = datetime.datetime.now().year + starting_year = ui.number(value=datetime.datetime.now().year, label="Startjahr", on_change=correct_end_year).bind_value(start_year_binder, 'value') + + end_year_binder.value = starting_year.value + end_year = ui.number(value=starting_year.value, label="Endjahr", on_change=correct_start_year).bind_value(end_year_binder, 'value') with ui.row(): ui.button("Anwenden", on_click=enter_holidays) ui.button("Abbrechen", on_click=dialog.close) @@ -1044,7 +1071,9 @@ def page_admin(): reset_visibility.value = False timetable.refresh() - ui.button("Speichern", on_click=save_admin_settings).tooltip("Hiermit werden sämtliche oben gemachten Einstellungen gespeichert.") + with ui.button("Speichern", on_click=save_admin_settings): + with ui.tooltip(): + ui.label("Hiermit werden sämtliche oben gemachten Einstellungen gespeichert.\nGgf. müssen Sie die Seite neu laden um die Auswirkungen sichtbar zu machen.").style('white-space: pre-wrap') with ui.tab_panel(users): ui.label("Benutzerverwaltung").classes(h3) @@ -1341,7 +1370,7 @@ def page_admin(): sum = float(days[i].value) + sum except: pass - workhours_sum.value = str(sum) + workhours_sum.text = str(sum) with ui.grid(columns='auto auto auto').classes('items-baseline'): ui.label("gültig ab:") @@ -1371,9 +1400,19 @@ def page_admin(): def add_workhours_entry(): workhours_dict = { } - for i in range(1, 8): - workhours_dict[i] = 0 - workhours_dict["vacation"] = 0 + if not use_last_entries_chkb.value: + for i in range(1, 8): + workhours_dict[i] = 0 + workhours_dict["vacation"] = 0 + else: + validity_date_dt = datetime.datetime.strptime(date_picker.value, "%Y-%m-%d") + for i in range (1, 8): + check_date_dt = validity_date_dt - datetime.timedelta(days=i) + weekday_of_check_date = check_date_dt.weekday() + 1 + workhours_of_check_date = current_user.get_day_workhours(check_date_dt.year, check_date_dt.month, check_date_dt.day) + workhours_dict[weekday_of_check_date] = workhours_of_check_date + workhours_dict["vacation"] = current_user.get_vacation_claim(validity_date_dt.year, validity_date_dt.month, validity_date_dt.day) + current_user.workhours[date_picker.value] = workhours_dict current_user.write_settings() @@ -1393,7 +1432,7 @@ def page_admin(): with ui.dialog() as dialog, ui.card(): ui.label("Geben Sie das Gültigkeitsdatum an, ab wann die Einträge gültig sein sollen.") date_picker = ui.date() - + use_last_entries_chkb = ui.checkbox("Werte von letztem gültigen Eintrag übernehmen.") with ui.row(): ui.button("OK", on_click=add_workhours_entry) ui.button("Abbrechen", on_click=dialog.close) @@ -1497,11 +1536,13 @@ def page_admin(): else: ui.label(f'{round(size / 1_000, 2)} kB') ui.label(version) + from lib.definitions import scriptpath ui.button(icon='download', on_click=lambda file=date_string: ui.download.file( os.path.join(scriptpath, backupfolder, f'{file}.zip'))).tooltip( "Backup herunterladen") def del_backup_dialog(file): + from lib.definitions import scriptpath def del_backup(): os.remove(os.path.join(scriptpath, backupfolder, f'{file}.zip')) dialog.close() @@ -1518,9 +1559,30 @@ def page_admin(): dialog.open() def restore_backup_dialog(file): + from lib.definitions import scriptpath def restore_backup(): + if is_docker(): + folder_to_delete = "/users" + else: + folder_to_delete = os.path.join(scriptpath, userfolder) + for file_path in os.listdir(folder_to_delete): + delete_item = os.path.join(folder_to_delete, file_path) + if os.path.isfile(delete_item) or os.path.islink(delete_item): + os.unlink(delete_item) + elif os.path.isdir(delete_item): + shutil.rmtree(delete_item) + with zipfile.ZipFile(os.path.join(scriptpath, backupfolder, f'{file}.zip'), 'r') as source: - source.extractall(scriptpath) + if not is_docker(): + source.extractall(scriptpath) + os.unlink(os.path.join(scriptpath, "app_version.txt")) + else: + filelist = source.namelist() + print(filelist) + for file_list_item in filelist: + if file_list_item.startswith("users"): + source.extract(file_list_item, "/") + source.extract("settings.json", "/settings/") with ui.dialog() as confirm_dialog, ui.card(): ui.label("Das Backup wurde wiederhergestellt. Um Änderungen anzuzeigen, muss die Seite neu geladen werden.") with ui.grid(columns=2): @@ -1547,6 +1609,7 @@ def page_admin(): ui.separator() async def make_backup(): + from lib.definitions import scriptpath n = ui.notification("Backup wird erzeugt...") compress = zipfile.ZIP_DEFLATED filename = os.path.join(searchpath, datetime.datetime.now().strftime(date_format) + '.zip') diff --git a/lib/homepage.py b/lib/homepage.py index 0f05cd9..1f9a8f3 100644 --- a/lib/homepage.py +++ b/lib/homepage.py @@ -227,13 +227,7 @@ def homepage(): ui.label("Fehlzeitenübersicht").classes('col-span-2 font-bold') absences_select = ui.select(list(reversed(available_years)), on_change=activate_absence) absences_button = ui.button("Anzeigen", on_click=lambda: ui.navigate.to(f"api/absence/{current_user.username}/{absences_select.value}", new_tab=True)).bind_enabled_from(binder_absence, 'value') - ui.separator().classes('col-span-2') - def logout(): - app.storage.user.pop("active_user", None) - 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') @@ -307,6 +301,16 @@ def homepage(): ui.button("Speichern", on_click=save_new_password) ui.button("Zurücksetzen", on_click=revert_pw_inputs) ui.space() + ui.space() + with ui.column(): + ui.separator() + + def logout(): + app.storage.user.pop("active_user", None) + ui.navigate.to("/") + + ui.button("Logout", on_click=logout).classes('w-full') + ui.space() else: login_mask() diff --git a/main.py b/main.py index 144988a..be43a16 100644 --- a/main.py +++ b/main.py @@ -54,130 +54,11 @@ def main(): ui.toggle.default_props('rounded') ui.row.default_classes('items-baseline') - favicon = ''' - - - - - - - - - - - - - image/svg+xml - - - - - Openclipart - - - - - - - - - - - -''' - if not os.path.exists(os.path.join(scriptpath, "favicon.svg")): - with open(os.path.join(scriptpath, "favicon.svg"), 'w') as favicon_file: - favicon_file.write(favicon) + #if not os.path.exists(os.path.join(scriptpath, "favicon.svg")): + # with open(os.path.join(scriptpath, "favicon.svg"), 'w') as favicon_file: + # favicon_file.write(favicon) - ui.run(favicon=os.path.join(scriptpath, "favicon.svg"), port=port, storage_secret=secret, language='de-DE', show_welcome_message=False) + ui.run(favicon='⏲', port=port, storage_secret=secret, language='de-DE', show_welcome_message=False) if __name__ in ("__main__", "__mp_main__"): parser = argparse.ArgumentParser(description=f'{app_title} {app_version}')