Interne Backupfunktion hinzugefügt
Downloads für Backups hinzugefügt Wiederherstellungsfunktion hinzugefügt
This commit is contained in:
parent
c248f1eb63
commit
0d5d977a1e
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,21 +0,0 @@
|
||||
# Zeiterfassung
|
||||
#
|
||||
# statische Werte
|
||||
|
||||
import os
|
||||
|
||||
# Quasi Konstanten:
|
||||
|
||||
scriptpath = os.path.dirname(os.path.abspath(__file__))
|
||||
suffix_userfolder = "users"
|
||||
suffix_settingsfolder = "settings"
|
||||
usersettingsfilename = "settings.json"
|
||||
photofilename = "photo.png"
|
||||
program_name = "Zeiterfassung"
|
||||
program_version = "Development"
|
||||
|
||||
status_in = "eingestempelt"
|
||||
status_out = "ausgestempelt"
|
||||
|
||||
userfolder = scriptpath + "/" + suffix_userfolder
|
||||
settingsfolder = scriptpath + "/" + suffix_settingsfolder
|
@ -1,36 +0,0 @@
|
||||
# Zeiterfassung
|
||||
# JSON Handling
|
||||
|
||||
# Imports
|
||||
|
||||
import json
|
||||
|
||||
# Datenstruktur:
|
||||
|
||||
# user: Benutzername
|
||||
# name: Vollständiger Name
|
||||
# password: gehashtes Passwort
|
||||
|
||||
# Montatsspezifische Informationen:
|
||||
# Gültigkeitsdatum, ab wann gülitg
|
||||
#
|
||||
# monday: Stunden
|
||||
# tuesday: Stunden
|
||||
# wednesday: Stunden
|
||||
# thursday: Stunden
|
||||
# friday: Stunden
|
||||
# saturday: Stunden
|
||||
# sunday: Stunden
|
||||
# pto: Tage pro Jahr
|
||||
|
||||
def load_settings(filename):
|
||||
|
||||
with open(filename) as json_file:
|
||||
data = json.load(json_file)
|
||||
|
||||
return data
|
||||
|
||||
def write_settings(filename, settings):
|
||||
|
||||
with open(filename, "w") as json_file:
|
||||
json.dump(settings, json_file, indent=4)
|
93
lib/admin.py
93
lib/admin.py
@ -14,6 +14,7 @@ from lib.web_ui import *
|
||||
|
||||
import os.path
|
||||
import os
|
||||
import zipfile
|
||||
from stat import S_IREAD, S_IRWXU
|
||||
import hashlib
|
||||
import calendar
|
||||
@ -45,6 +46,7 @@ def page_admin():
|
||||
time_overview = ui.tab('Zeitübersichten')
|
||||
settings = ui.tab('Einstellungen')
|
||||
users = ui.tab('Benutzer')
|
||||
backups = ui.tab('Backups')
|
||||
|
||||
userlist = [ ]
|
||||
|
||||
@ -1241,6 +1243,97 @@ 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):
|
||||
|
||||
ui.markdown('**Backups**')
|
||||
|
||||
date_format = '%Y-%m-%d_%H-%M'
|
||||
|
||||
@ui.refreshable
|
||||
def backup_list():
|
||||
if not os.path.isdir(os.path.join(scriptpath, backupfolder)):
|
||||
os.makedirs(os.path.join(scriptpath, backupfolder))
|
||||
|
||||
backup_files = []
|
||||
with ui.grid(columns='auto auto auto'):
|
||||
for file in os.listdir(os.path.join(scriptpath, backupfolder)):
|
||||
if file.endswith(".zip"):
|
||||
backup_files.append(file)
|
||||
backup_files.sort()
|
||||
backup_files.reverse()
|
||||
|
||||
if len(backup_files) == 0:
|
||||
ui.label("Keine Backups vorhanden")
|
||||
|
||||
for file in backup_files:
|
||||
date_string = file[0:-4]
|
||||
try:
|
||||
date_string_dt = datetime.datetime.strptime(date_string, date_format)
|
||||
button_string = date_string_dt.strftime('%d.%m.%Y - %H:%M')
|
||||
except ValueError:
|
||||
button_string = date_string
|
||||
ui.button(button_string,
|
||||
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):
|
||||
def del_backup():
|
||||
os.remove(os.path.join(scriptpath, backupfolder, f'{file}.zip'))
|
||||
dialog.close()
|
||||
ui.notify(f'Backupdatei {file}.zip gelöscht')
|
||||
backup_list.refresh()
|
||||
|
||||
with ui.dialog() as dialog, ui.card():
|
||||
ui.label(f"Soll das Backup {file}.zip wirklich gelöscht werden?")
|
||||
ui.label("Dies kann nicht rückgänig gemacht werden!").classes('font-bold')
|
||||
check = ui.checkbox("Ich habe verstanden.")
|
||||
with ui.grid(columns=2):
|
||||
ui.button("Löschen", on_click=del_backup).bind_enabled_from(check, 'value')
|
||||
ui.button("Abbrechen", on_click=dialog.close)
|
||||
dialog.open()
|
||||
|
||||
def restore_backup_dialog(file):
|
||||
def restore_backup():
|
||||
with zipfile.ZipFile(os.path.join(scriptpath, backupfolder, f'{file}.zip'), 'r') as source:
|
||||
source.extractall(scriptpath)
|
||||
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):
|
||||
ui.button("Neu laden", on_click=ui.navigate.reload)
|
||||
ui.button("Später selbst neu laden", on_click=dialog.close)
|
||||
confirm_dialog.open()
|
||||
|
||||
with ui.dialog() as dialog, ui.card():
|
||||
ui.label(f"Soll das Backup {file}.zip wiederhergestellt werden?")
|
||||
ui.label("Es werden dabei alle Dateien überschrieben!").classes('font-bold')
|
||||
ui.label("Dies kann nicht rückgängig gemacht werden!").classes('font-bold fg-red')
|
||||
check = ui.checkbox("Ich habe verstanden.")
|
||||
with ui.grid(columns=2):
|
||||
ui.button("Wiederherstellen", on_click=restore_backup).bind_enabled_from(check, 'value')
|
||||
ui.button("Abbrechen", on_click=dialog.close)
|
||||
|
||||
dialog.open()
|
||||
|
||||
ui.button(icon='delete', on_click=lambda file=date_string: del_backup_dialog(file)).tooltip("Backup löschen")
|
||||
ui.button(icon='undo', on_click=lambda file=date_string: restore_backup_dialog(file)).tooltip("Backup wiederherstellen")
|
||||
|
||||
backup_list()
|
||||
|
||||
ui.separator()
|
||||
|
||||
def make_backup():
|
||||
compress = zipfile.ZIP_DEFLATED
|
||||
filename = os.path.join(scriptpath, backupfolder,
|
||||
datetime.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)
|
||||
backup_list.refresh()
|
||||
|
||||
ui.button("Neues Backup erstellen", on_click=make_backup)
|
||||
|
||||
# Alternativ zur Loginseite navigieren
|
||||
else:
|
||||
|
584
lib/api.py
584
lib/api.py
@ -1,4 +1,6 @@
|
||||
import sys
|
||||
import os
|
||||
import zipfile
|
||||
from calendar import month_name
|
||||
from logging import exception
|
||||
|
||||
@ -16,289 +18,301 @@ import calendar
|
||||
@ui.page('/api/month/{username}/{year}-{month}')
|
||||
def page_overview_month(username: str, year: int, month: int):
|
||||
|
||||
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}')
|
||||
admin_auth = app.storage.user['admin_authenticated']
|
||||
except:
|
||||
admin_auth = False
|
||||
|
||||
pad_x = 4
|
||||
pad_y = 0
|
||||
if login_is_valid(username) or admin_auth:
|
||||
data = load_adminsettings()
|
||||
|
||||
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 = ''
|
||||
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):
|
||||
bg_color = ' bg-yellow-100'
|
||||
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}')
|
||||
|
||||
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}')
|
||||
pad_x = 4
|
||||
pad_y = 0
|
||||
|
||||
# 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
|
||||
color_weekend = "gray-100"
|
||||
color_holiday = "gray-100"
|
||||
|
||||
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)
|
||||
def overview_table():
|
||||
# Timestamp in ein Array schreiben
|
||||
timestamps = current_user.get_timestamps(year, month)
|
||||
timestamps.sort()
|
||||
|
||||
# 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
|
||||
# Abwesenheitsdaten in ein Dict schreiben
|
||||
user_absent = current_user.get_absence(year, month)
|
||||
|
||||
# Buchungen behandeln
|
||||
for i in range(0, len(timestamps_dict[day]), 2):
|
||||
# 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:
|
||||
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>"
|
||||
|
||||
# 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:
|
||||
if len(timestamps_dict[day]) % 2 != 0:
|
||||
booking_text += datetime.fromtimestamp(int(timestamps_dict[day][i])).strftime('%H:%M') + " - ***Buchung fehlt!***"
|
||||
pass
|
||||
|
||||
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
|
||||
# Buchungen behandeln
|
||||
for i in range(0, len(timestamps_dict[day]), 2):
|
||||
try:
|
||||
for i in list(user_absent):
|
||||
if int(i) == day and user_absent[i] != "UU":
|
||||
saldo = 0
|
||||
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:
|
||||
pass
|
||||
if len(timestamps_dict[day]) % 2 != 0:
|
||||
booking_text += datetime.fromtimestamp(int(timestamps_dict[day][i])).strftime('%H:%M') + " - ***Buchung fehlt!***"
|
||||
|
||||
general_saldo = general_saldo + saldo
|
||||
total = f"{convert_seconds_to_hours(saldo)} h"
|
||||
day_notes = current_user.get_day_notes(year, month, day)
|
||||
just_once = True
|
||||
|
||||
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}')
|
||||
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 = []
|
||||
|
||||
# Ü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}')
|
||||
# Suche mir alle timestamps für diesen Tag
|
||||
for i in timestamps:
|
||||
actual_timestamp = datetime.fromtimestamp(int(i))
|
||||
timestamp_day = actual_timestamp.strftime('%-d')
|
||||
|
||||
overview_table()
|
||||
if int(timestamp_day) == int(day):
|
||||
timestamps_of_this_day.append(i)
|
||||
|
||||
def absence_table():
|
||||
absences_this_month = current_user.get_absence(year, month)
|
||||
absence_dict = { }
|
||||
timestamps_of_this_day.sort()
|
||||
time_sum = 0
|
||||
if len(timestamps_of_this_day) > 1:
|
||||
|
||||
for abbr in list(absence_entries):
|
||||
absence_dict[abbr] = 0
|
||||
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
|
||||
|
||||
for key, value in absences_this_month.items():
|
||||
if value in list(absence_dict):
|
||||
absence_dict[value] += 1
|
||||
is_time = convert_seconds_to_hours(time_sum) + " h"
|
||||
else:
|
||||
is_time = "Kein"
|
||||
|
||||
total_absence_days = 0
|
||||
for key, value in absence_dict.items():
|
||||
total_absence_days += absence_dict[key]
|
||||
ui.markdown(is_time).classes(f'border px-{pad_x} py-{pad_y} text-center')
|
||||
# Sollzeit bestimmen
|
||||
|
||||
if total_absence_days > 0:
|
||||
ui.markdown("###Abwesenheitstage diesen Monat:")
|
||||
hours_to_work = int(current_user.get_day_workhours(year, month, day))
|
||||
|
||||
with ui.grid(columns='auto 25%').classes(f'gap-0 border px-0 py-0'):
|
||||
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)
|
||||
|
||||
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')
|
||||
ui.markdown(target_time).classes(f'border px-{pad_x} py-{pad_y} text-center')
|
||||
|
||||
absence_table()
|
||||
# Saldo für den Tag berechnen
|
||||
day_in_list = datetime(year, month, day)
|
||||
if time.time() > day_in_list.timestamp():
|
||||
|
||||
def archive():
|
||||
current_year = datetime.now().year
|
||||
current_month = datetime.now().month
|
||||
archivable = False
|
||||
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
|
||||
|
||||
if current_year > year:
|
||||
if current_user.get_archive_status(year, month) == False:
|
||||
archivable = True
|
||||
if current_year == year:
|
||||
if current_month > month:
|
||||
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}')
|
||||
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)
|
||||
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()
|
||||
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}')
|
||||
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_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))
|
||||
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):
|
||||
|
||||
if login_is_valid(username):
|
||||
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)
|
||||
@ -354,8 +368,12 @@ def page_overview_vacation(username: str, year: int):
|
||||
|
||||
@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):
|
||||
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')
|
||||
@ -513,4 +531,80 @@ def json_info(api_key: str):
|
||||
break
|
||||
|
||||
if not found_key:
|
||||
return { "data": "none"}
|
||||
return { "data": "none"}
|
||||
|
||||
@ui.page("/api/backup")
|
||||
def backup():
|
||||
try:
|
||||
admin_auth = app.storage.user['admin_authenticated']
|
||||
except:
|
||||
admin_auth = False
|
||||
|
||||
if admin_auth:
|
||||
|
||||
date_format = '%Y-%m-%d_%H-%M'
|
||||
|
||||
pageheader("Backup herunterladen")
|
||||
ui.page_title(f"{app_title} {app_version}")
|
||||
ui.label("Vorhandene Backups:")
|
||||
|
||||
@ui.refreshable
|
||||
def backup_list():
|
||||
if not os.path.isdir(os.path.join(scriptpath, backupfolder)):
|
||||
os.makedirs(os.path.join(scriptpath, backupfolder))
|
||||
|
||||
backup_files = [ ]
|
||||
with ui.grid(columns='auto auto'):
|
||||
for file in os.listdir(os.path.join(scriptpath, backupfolder)):
|
||||
if file.endswith(".zip"):
|
||||
backup_files.append(file)
|
||||
backup_files.sort()
|
||||
backup_files.reverse()
|
||||
|
||||
if len(backup_files) == 0:
|
||||
ui.label("Keine Backups vorhanden")
|
||||
|
||||
for file in backup_files:
|
||||
date_string = file[0:-4]
|
||||
try:
|
||||
date_string_dt = datetime.strptime(date_string, date_format)
|
||||
button_string = date_string_dt.strftime('%d.%m.%Y - %H:%M')
|
||||
except ValueError:
|
||||
button_string = date_string
|
||||
ui.button(button_string, on_click=lambda file=date_string: ui.download.file(f'{file}.zip'))
|
||||
def del_backup_dialog(file):
|
||||
def del_backup():
|
||||
os.remove(os.path.join(scriptpath, backupfolder, f'{file}.zip'))
|
||||
dialog.close()
|
||||
ui.notify(f'Backupdatei {file}.zip gelöscht')
|
||||
backup_list.refresh()
|
||||
with ui.dialog() as dialog, ui.card():
|
||||
ui.label(f"Soll das Backup {file}.zip wirklich gelöscht werden?")
|
||||
ui.label("Dies kann nicht rückgänig gemacht werden!").classes('font-bold')
|
||||
with ui.grid(columns=2):
|
||||
ui.button("Löschen", on_click=del_backup)
|
||||
ui.button("Abbrechen", on_click=dialog.close)
|
||||
dialog.open()
|
||||
|
||||
ui.button(icon='delete', on_click=lambda file=date_string: del_backup_dialog(file))
|
||||
|
||||
backup_list()
|
||||
|
||||
ui.separator()
|
||||
|
||||
def make_backup():
|
||||
compress = zipfile.ZIP_DEFLATED
|
||||
filename = os.path.join(scriptpath, backupfolder, 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)
|
||||
backup_list.refresh()
|
||||
|
||||
ui.button("Neues Backup erstellen", on_click=make_backup)
|
||||
|
||||
else:
|
||||
login_mask()
|
@ -10,6 +10,7 @@ app_version = ("0.0.0")
|
||||
# Standardpfade
|
||||
scriptpath = str(Path(os.path.dirname(os.path.abspath(__file__))).parent.absolute())
|
||||
userfolder = "users"
|
||||
backupfolder = "backup"
|
||||
|
||||
# Dateinamen
|
||||
|
||||
|
23
nice_gui.py
23
nice_gui.py
@ -1,23 +0,0 @@
|
||||
# Zeiterfassung
|
||||
# Nice GUI UI
|
||||
|
||||
from nicegui import ui
|
||||
from nicegui.events import ValueChangeEventArguments
|
||||
|
||||
def site_pinpad():
|
||||
|
||||
keys = [
|
||||
[ 1, 2, 3],
|
||||
[ 4, 5, 6],
|
||||
[ 7, 8, 9],
|
||||
[ "<-", 0, "OK"]
|
||||
]
|
||||
|
||||
with ui.row():
|
||||
for y, row in enumerate(keys, 1):
|
||||
for x, key in enumerate(row):
|
||||
button = ui.Button(text=keys[y][x])
|
||||
|
||||
ui.run(port=8090)
|
||||
|
||||
site_pinpad()
|
@ -1,28 +0,0 @@
|
||||
from nicegui import ui
|
||||
from users import *
|
||||
from definitions import *
|
||||
|
||||
@ui.page('/login')
|
||||
def page_login():
|
||||
ui.label('Loginseite')
|
||||
|
||||
@ui.page('/stamping')
|
||||
def page_stamping():
|
||||
ui.label('Stempelsteite')
|
||||
|
||||
@ui.page('/userlist')
|
||||
def page_userlist():
|
||||
|
||||
def click_button(button):
|
||||
ui.notify(button)
|
||||
|
||||
ui.label(app_title + " " + app_version)
|
||||
|
||||
userlist = list_users()
|
||||
buttons = { }
|
||||
|
||||
for name in userlist:
|
||||
button = ui.button(text=name, on_click=lambda name=name:click_button(name) )
|
||||
buttons[name] = button
|
||||
|
||||
ui.run(port=8090)
|
@ -1,29 +0,0 @@
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
|
||||
# Funktion, die die Farben des Buttons ändert
|
||||
def aendere_farbe(button, bg_farbe, fg_farbe):
|
||||
# Ändern der Hintergrund- und Textfarbe des Buttons
|
||||
button.configure(bg=bg_farbe, fg=fg_farbe)
|
||||
|
||||
# Erstellen des Hauptfensters
|
||||
root = tk.Tk()
|
||||
root.title("Buttons mit unterschiedlichen Farben")
|
||||
|
||||
# Liste von Button-Beschriftungen und den gewünschten Farben
|
||||
button_info = [
|
||||
('Button 1', 'green', 'white'),
|
||||
('Button 2', 'blue', 'yellow'),
|
||||
('Button 3', 'red', 'black')
|
||||
]
|
||||
|
||||
# Erstellen der Buttons aus der Liste
|
||||
buttons = []
|
||||
for text, bg, fg in button_info:
|
||||
# Einen Button erstellen und die Farben ändern
|
||||
button = tk.Button(root, text=text, command=lambda b=button, bg=bg, fg=fg: aendere_farbe(b, bg, fg))
|
||||
button.pack(pady=10)
|
||||
buttons.append(button)
|
||||
|
||||
# Hauptloop starten
|
||||
root.mainloop()
|
@ -1,95 +0,0 @@
|
||||
# Funktionen bzgl. Timestamps
|
||||
|
||||
import time
|
||||
import datetime
|
||||
import os
|
||||
from definitions import *
|
||||
|
||||
# Zeitstempel schreiben
|
||||
def append_timestamp(filename):
|
||||
# Hole den aktuellen Timestamp in Epoch-Zeit
|
||||
timestamp = int(time.time())
|
||||
|
||||
try:
|
||||
# Öffne die Datei im Anhang-Modus ('a')
|
||||
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)
|
||||
os.makedirs(folder_path, exist_ok=True)
|
||||
append_timestamp(filename)
|
||||
|
||||
# Anzahl der Zeilen zählen
|
||||
def len_timestamps(filename):
|
||||
try:
|
||||
# Öffne die Datei im Lese-Modus ('r')
|
||||
with open(filename, 'r') as file:
|
||||
# Zähle die Zeilen
|
||||
lines = file.readlines()
|
||||
file.close()
|
||||
print(len(lines))
|
||||
return len(lines)
|
||||
except FileNotFoundError:
|
||||
print(f"Die Datei {filename} wurde nicht gefunden.")
|
||||
return 0
|
||||
|
||||
# Stempelzustand auslesen
|
||||
def stempel_zustand(filename):
|
||||
lines = len_timestamps(filename)
|
||||
if lines == 0:
|
||||
print(f"Keine Einträge")
|
||||
elif lines % 2 == 0:
|
||||
return(status_out)
|
||||
else:
|
||||
return(status_in)
|
||||
|
||||
# Letzten Zeitstempel auswerten
|
||||
def get_last_2_timestmaps(filename):
|
||||
with open(filename, 'r') as file:
|
||||
lines = file.readlines()
|
||||
file.close()
|
||||
second_last_line = lines[-2]
|
||||
last_line = lines[-1]
|
||||
last_2_timestmaps = [second_last_line, last_line]
|
||||
return last_2_timestmaps
|
||||
|
||||
# Stempelübersicht zusammenstellen
|
||||
def overview(filename):
|
||||
|
||||
# Öffne die Datei im Lese-Modus ('r')
|
||||
with open(filename, 'r') as file:
|
||||
lines = file.readlines()
|
||||
|
||||
timelist = [[] for i in range(3)]
|
||||
|
||||
for i in range(0, len(lines)):
|
||||
if (i + 1) % 2 == 0:
|
||||
timelist[1].append(lines[i])
|
||||
else:
|
||||
timelist[0].append(lines[i])
|
||||
if len(timelist[0]) > len(timelist[1]):
|
||||
timelist[1].append("")
|
||||
|
||||
for i in range(0, len(timelist[0])):
|
||||
timelist[2].append(int(timelist[1][i])-int(timelist[0][i]))
|
||||
|
||||
for i in range(0, len(timelist[0])):
|
||||
print(convert_timestamp(timelist[0][i], "%d.%m.%Y %H:%M") + " - " + convert_timestamp(timelist[1][i], "%d.%m.%Y %H:%M") + " Dauer: " + convert_duration(timelist[2][i]))
|
||||
|
||||
# Timestamp konvertieren
|
||||
def convert_timestamp(timestamp, format):
|
||||
try:
|
||||
return str(datetime.datetime.fromtimestamp(int(timestamp)).strftime(format))
|
||||
except:
|
||||
return ("...")
|
||||
|
||||
#Zeitdauerdarstellung berechnen
|
||||
def convert_duration(duration):
|
||||
hours = int(duration / 3600)
|
||||
minutes = int((duration - hours * 3600) / 60)
|
||||
seconds = int(duration - hours * 3600 - minutes * 60)
|
||||
|
||||
return(f"{hours:02d}" + ":" + f"{minutes:02d}" + ":" + f"{seconds:02d}")
|
@ -1,15 +0,0 @@
|
||||
import tkinter as tk
|
||||
|
||||
def aktionSF():
|
||||
label3 = tk.Label(root, text="Aktion durchgeführt", bg="yellow")
|
||||
label3.pack()
|
||||
|
||||
root = tk.Tk()
|
||||
|
||||
label1 = tk.Label(root, text="Hallo Welt", bg="orange")
|
||||
label1.pack()
|
||||
|
||||
schaltf1 = tk.Button(root, text="Aktion durchführen", command=aktionSF)
|
||||
schaltf1.pack()
|
||||
|
||||
root.mainloop()
|
87
ui.py
87
ui.py
@ -1,87 +0,0 @@
|
||||
# Zeiterfassung
|
||||
# UI
|
||||
from definitions import *
|
||||
|
||||
import tkinter as tk
|
||||
from time import strftime
|
||||
from timestamping import *
|
||||
from users import determine_filename
|
||||
import locale
|
||||
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
|
||||
def update_time():
|
||||
string_time = strftime('%A, der %d.%m.%Y - %H:%M:%S')
|
||||
global digital_clock
|
||||
digital_clock.config(text=string_time)
|
||||
digital_clock.after(1000, update_time)
|
||||
|
||||
def ui_stempeln(line_index, user):
|
||||
append_timestamp(determine_filename(user))
|
||||
|
||||
global buttons
|
||||
global in_time_labels
|
||||
global out_time_labels
|
||||
|
||||
last_timestamps = get_last_2_timestmaps(determine_filename(user))
|
||||
|
||||
if stempel_zustand(determine_filename(user)) == status_out:
|
||||
buttons[line_index].configure(relief="raised", bg="red", text=user + "\n" + status_out)
|
||||
in_time_labels[line_index].configure(text=convert_timestamp(last_timestamps[0],"%H:%M"))
|
||||
out_time_labels[line_index].configure(text=convert_timestamp(last_timestamps[-1], "%H:%M"))
|
||||
elif stempel_zustand(determine_filename(user)) == status_in:
|
||||
buttons[line_index].configure(relief="sunken", bg="green", text=user + "\n" + status_in)
|
||||
in_time_labels[line_index].configure(text=convert_timestamp(last_timestamps[-1], "%H:%M"))
|
||||
out_time_labels[line_index].configure(text="")
|
||||
else:
|
||||
buttons[line_index].configure(bg="yellow", text="Fehler")
|
||||
|
||||
def win_stempeln(userlist):
|
||||
stempeln = tk.Tk()
|
||||
stempeln.title(program_name + " " + program_version)
|
||||
#stempeln.geometry("600x400")
|
||||
stempeln.minsize(width=200, height=200)
|
||||
|
||||
global buttons
|
||||
global in_time_labels
|
||||
global out_time_labels
|
||||
global digital_clock
|
||||
|
||||
buttons = [ ]
|
||||
in_time_labels = [ ]
|
||||
out_time_labels = [ ]
|
||||
|
||||
button_index = 0
|
||||
|
||||
digital_clock = tk.Label(stempeln)
|
||||
digital_clock.grid(column=1, row=0)
|
||||
|
||||
update_time()
|
||||
|
||||
frame_stempeln = tk.Frame(stempeln, borderwidth=5, relief="ridge", padx=10, pady=10)
|
||||
frame_stempeln.grid(row=1, column=1)
|
||||
|
||||
tk.Label(frame_stempeln, text="Benutzer:", anchor="w", width=10).grid(row=0, column=1, sticky="w")
|
||||
tk.Label(frame_stempeln, text="Gekommen:").grid(row=0, column=2)
|
||||
tk.Label(frame_stempeln, text="Gegangen:").grid(row=0, column=3)
|
||||
|
||||
#Schleife zur Erzeugung von Stempelzeilen für User
|
||||
for i in userlist:
|
||||
button = tk.Button(frame_stempeln, height=3, compound="left", command=lambda b=button_index, user=i: ui_stempeln(b, user))
|
||||
in_time = tk.Label(frame_stempeln, padx=10)
|
||||
out_time = tk.Label(frame_stempeln, padx=10)
|
||||
if len_timestamps(determine_filename(i)) % 2 == 0:
|
||||
button.configure(relief="raised", bg ="red", text=i + "\n" + status_out)
|
||||
else:
|
||||
button.configure(relief="sunken", bg="green", text=i + "\n" + status_in)
|
||||
button.grid(row=button_index+1, column=1, sticky="ew")
|
||||
in_time.grid(row=button_index+1, column=2)
|
||||
out_time.grid(row=button_index+1, column=3)
|
||||
buttons.append(button)
|
||||
in_time_labels.append(in_time)
|
||||
out_time_labels.append(out_time)
|
||||
button_index+=1
|
||||
|
||||
stempeln.mainloop()
|
||||
|
||||
win_stempeln( ["testuser", "testuser2"])
|
27
users.py
27
users.py
@ -1,27 +0,0 @@
|
||||
# Zeiterfassung
|
||||
# Benutzerfunktionen
|
||||
|
||||
import os
|
||||
import datetime
|
||||
|
||||
from definitions import *
|
||||
|
||||
# Benutzer anhand Verzeichnisse auflisten
|
||||
def list_users():
|
||||
users = [d for d in os.listdir(userfolder) if os.path.isdir(os.path.join(userfolder, d))]
|
||||
return users
|
||||
|
||||
# Dateinamen bestimmen
|
||||
def determine_filename(user, type="stamping"):
|
||||
if type == "stamping":
|
||||
year = str(datetime.datetime.now().year)
|
||||
month = str(datetime.datetime.now().month)
|
||||
completepath = userfolder + "/" + user + "/" + year + "-" + month + ".txt"
|
||||
return completepath
|
||||
|
||||
elif type == "settings":
|
||||
completepath = userfolder +"/" + user + "/" + usersettingsfilename
|
||||
return completepath
|
||||
elif type == "photo":
|
||||
completepath = userfolder + "/" + user + "/" + photofilename
|
||||
return completepath
|
@ -1,4 +0,0 @@
|
||||
{
|
||||
"archived": 0,
|
||||
"total_hours": 0
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
1747642816
|
||||
1747642898
|
||||
1747642972
|
||||
1747642976
|
||||
1747643508
|
||||
1747643521
|
||||
1747643564
|
||||
1747643566
|
||||
1747643603
|
||||
1747644615
|
@ -1,18 +0,0 @@
|
||||
{
|
||||
"username": "filler2",
|
||||
"fullname": "filler2",
|
||||
"password": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"workhours": {
|
||||
"2025-05-16": {
|
||||
"1": 0,
|
||||
"2": 0,
|
||||
"3": 0,
|
||||
"4": 0,
|
||||
"5": 0,
|
||||
"6": 0,
|
||||
"7": 0,
|
||||
"vacation": 0
|
||||
}
|
||||
},
|
||||
"api_key": "43ec918e7d773cb23ab3113d18059a83fee389ac"
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
{
|
||||
"archived": 0,
|
||||
"total_hours": 0
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
1747391900
|
||||
1747391907
|
@ -1,18 +0,0 @@
|
||||
{
|
||||
"username": "filler3",
|
||||
"fullname": "filler3",
|
||||
"password": "37a8eec1ce19687d132fe29051dca629d164e2c4958ba141d5f4133a33f0688f",
|
||||
"workhours": {
|
||||
"2025-05-16": {
|
||||
"1": "6",
|
||||
"2": "6",
|
||||
"3": "6",
|
||||
"4": "6",
|
||||
"5": "6",
|
||||
"6": 0,
|
||||
"7": 0,
|
||||
"vacation": 0
|
||||
}
|
||||
},
|
||||
"api_key": "9e3f37809cd898a3db340c453df53bd0793a99fa"
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
{
|
||||
"username": "filler4",
|
||||
"fullname": "filler4",
|
||||
"password": "37a8eec1ce19687d132fe29051dca629d164e2c4958ba141d5f4133a33f0688f",
|
||||
"api_key": "614e31aab9fcf1373558f100cb2c7a9918349eec",
|
||||
"workhours": {
|
||||
"2025-05-16": {
|
||||
"1": 0,
|
||||
"2": 0,
|
||||
"3": 0,
|
||||
"4": 0,
|
||||
"5": 0,
|
||||
"6": 0,
|
||||
"7": 0,
|
||||
"vacation": 0
|
||||
}
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
{
|
||||
"username": "filler5",
|
||||
"fullname": "filler5",
|
||||
"password": "37a8eec1ce19687d132fe29051dca629d164e2c4958ba141d5f4133a33f0688f",
|
||||
"api_key": "ad32682beb4e19f78efc1bdae259aee3ccbf9883",
|
||||
"workhours": {
|
||||
"2025-05-16": {
|
||||
"1": 0,
|
||||
"2": 0,
|
||||
"3": 0,
|
||||
"4": 0,
|
||||
"5": 0,
|
||||
"6": 0,
|
||||
"7": 0,
|
||||
"vacation": 0
|
||||
}
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
{
|
||||
"username": "filler6",
|
||||
"fullname": "filler6",
|
||||
"password": "37a8eec1ce19687d132fe29051dca629d164e2c4958ba141d5f4133a33f0688f",
|
||||
"api_key": "68d974e4ed516795d48d5cb8b7dc8b8ca4144a9b",
|
||||
"workhours": {
|
||||
"2025-05-16": {
|
||||
"1": 0,
|
||||
"2": 0,
|
||||
"3": 0,
|
||||
"4": 0,
|
||||
"5": 0,
|
||||
"6": 0,
|
||||
"7": 0,
|
||||
"vacation": 0
|
||||
}
|
||||
}
|
||||
}
|
@ -1,112 +0,0 @@
|
||||
1743965819
|
||||
1743965909
|
||||
1743966022
|
||||
1743966045
|
||||
1743966047
|
||||
1743966049
|
||||
1743967346
|
||||
1744024713
|
||||
1744024974
|
||||
1744107474
|
||||
1744194019
|
||||
1744194047
|
||||
1744194048
|
||||
1744194049
|
||||
1744194602
|
||||
1744194612
|
||||
1744194613
|
||||
1744194613
|
||||
1744194614
|
||||
1744194709
|
||||
1744196085
|
||||
1744196089
|
||||
1744196089
|
||||
1744196120
|
||||
1744196189
|
||||
1744196243
|
||||
1744196307
|
||||
1744196315
|
||||
1744196324
|
||||
1744196333
|
||||
1744196334
|
||||
1744196335
|
||||
1744196337
|
||||
1744196338
|
||||
1744196339
|
||||
1744196340
|
||||
1744196342
|
||||
1744196343
|
||||
1744196344
|
||||
1744196345
|
||||
1744196346
|
||||
1744196500
|
||||
1744196505
|
||||
1744196506
|
||||
1744196507
|
||||
1744196663
|
||||
1744196665
|
||||
1744196667
|
||||
1744196667
|
||||
1744196719
|
||||
1744196721
|
||||
1744196991
|
||||
1744197071
|
||||
1744197205
|
||||
1744197211
|
||||
1744197213
|
||||
1744197215
|
||||
1744197217
|
||||
1744197218
|
||||
1744197219
|
||||
1744197253
|
||||
1744197300
|
||||
1744197301
|
||||
1744197302
|
||||
1744197303
|
||||
1744197765
|
||||
1744197766
|
||||
1744197769
|
||||
1744197868
|
||||
1744197871
|
||||
1744198070
|
||||
1744198071
|
||||
1744198392
|
||||
1744198393
|
||||
1744210902
|
||||
1744210904
|
||||
1744221414
|
||||
1744221415
|
||||
1744221946
|
||||
1744221947
|
||||
1744222133
|
||||
1744222135
|
||||
1744260500
|
||||
1744260507
|
||||
1744266057
|
||||
1744266059
|
||||
1744266162
|
||||
1744266164
|
||||
1744266742
|
||||
1744266745
|
||||
1744266778
|
||||
1744266779
|
||||
1744266990
|
||||
1744266993
|
||||
1744267045
|
||||
1744267050
|
||||
1744269810
|
||||
1744269812
|
||||
1744269823
|
||||
1744269829
|
||||
1744269915
|
||||
1744269919
|
||||
1744269921
|
||||
1744269922
|
||||
1744269957
|
||||
1744269959
|
||||
1744269961
|
||||
1744269971
|
||||
1744269973
|
||||
1744269974
|
||||
1744270075
|
||||
1744270081
|
Binary file not shown.
Before Width: | Height: | Size: 560 KiB |
Binary file not shown.
Before Width: | Height: | Size: 49 KiB |
@ -1,25 +0,0 @@
|
||||
{
|
||||
"username": "testuser",
|
||||
"name": "Der neue Tester",
|
||||
"password": "123456789",
|
||||
"2024-04-01": {
|
||||
"0": "0",
|
||||
"1": "8",
|
||||
"2": "8",
|
||||
"3": "8",
|
||||
"4": "8",
|
||||
"5": "8",
|
||||
"6": "0",
|
||||
"vacation": "30"
|
||||
},
|
||||
"2024-04-07": {
|
||||
"0": "0",
|
||||
"1": "6",
|
||||
"2": "6",
|
||||
"3": "6",
|
||||
"4": "8",
|
||||
"5": "6",
|
||||
"6": "0",
|
||||
"vacation": "28"
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user