Merge branch 'urlaubsantrag'
This commit is contained in:
commit
ad9b6d6be6
1125
lib/admin.py
1125
lib/admin.py
File diff suppressed because it is too large
Load Diff
@ -525,7 +525,7 @@ def json_info(api_key: str):
|
|||||||
data["time"]["overall"] = time_saldo
|
data["time"]["overall"] = time_saldo
|
||||||
data["vacation"] = { }
|
data["vacation"] = { }
|
||||||
data["vacation"]["claim"] = current_user.get_vacation_claim(now_dt.year, now_dt.month, now_dt.day)
|
data["vacation"]["claim"] = current_user.get_vacation_claim(now_dt.year, now_dt.month, now_dt.day)
|
||||||
data["vacation"]["used"] = current_user.count_vacation_days(now_dt.year)
|
data["vacation"]["used"] = current_user.count_absence_days("U", now_dt.year)
|
||||||
data["vacation"]["remaining"] = data["vacation"]["claim"] - data["vacation"]["used"]
|
data["vacation"]["remaining"] = data["vacation"]["claim"] - data["vacation"]["used"]
|
||||||
return data
|
return data
|
||||||
break
|
break
|
||||||
|
@ -17,6 +17,7 @@ backupfolder = str(os.path.join(scriptpath, "backup"))
|
|||||||
|
|
||||||
usersettingsfilename = "settings.json"
|
usersettingsfilename = "settings.json"
|
||||||
photofilename = "photo.jpg"
|
photofilename = "photo.jpg"
|
||||||
|
va_file = "vacation_application.json"
|
||||||
|
|
||||||
# Status
|
# Status
|
||||||
|
|
||||||
|
100
lib/homepage.py
100
lib/homepage.py
@ -187,38 +187,92 @@ def homepage():
|
|||||||
|
|
||||||
ui.separator()
|
ui.separator()
|
||||||
|
|
||||||
with ui.grid(columns='1fr auto 1fr').classes('w-full justify-center'):
|
with ui.tabs().classes('w-full items-center') as tabs:
|
||||||
|
|
||||||
|
overviews = ui.tab('Übersichten')
|
||||||
|
absence = ui.tab('Urlaubsantrag')
|
||||||
|
|
||||||
|
with ui.grid(columns='1fr auto 1fr').classes('w-full items-center'):
|
||||||
ui.space()
|
ui.space()
|
||||||
|
with ui.tab_panels(tabs, value=overviews):
|
||||||
|
with ui.tab_panel(overviews):
|
||||||
|
|
||||||
def activate_vacation():
|
def activate_vacation():
|
||||||
binder_vacation.value = True
|
binder_vacation.value = True
|
||||||
|
|
||||||
def activate_absence():
|
def activate_absence():
|
||||||
binder_absence.value = True
|
binder_absence.value = True
|
||||||
|
|
||||||
with ui.grid(columns='1fr 1fr'):
|
with ui.grid(columns='1fr 1fr'):
|
||||||
|
|
||||||
ui.markdown("**Monatsübersicht:**").classes('col-span-2')
|
ui.markdown("**Monatsübersicht:**").classes('col-span-2')
|
||||||
|
|
||||||
month_year_select = ui.select(list(reversed(available_years)), label="Jahr", on_change=update_month).bind_value_to(binder_available_years, 'value')
|
month_year_select = ui.select(list(reversed(available_years)), label="Jahr", on_change=update_month).bind_value_to(binder_available_years, 'value')
|
||||||
month_month_select = ui.select(available_months, label="Monat", on_change=enable_month)
|
month_month_select = ui.select(available_months, label="Monat", on_change=enable_month)
|
||||||
month_month_select.disable()
|
month_month_select.disable()
|
||||||
|
|
||||||
ui.space()
|
ui.space()
|
||||||
month_button = ui.button("Anzeigen", on_click=lambda: ui.navigate.to(f"/api/month/{current_user.username}/{month_year_select.value}-{month_month_select.value}", new_tab=True)).bind_enabled_from(binder_month_button, 'value')
|
month_button = ui.button("Anzeigen", on_click=lambda: ui.navigate.to(f"/api/month/{current_user.username}/{month_year_select.value}-{month_month_select.value}", new_tab=True)).bind_enabled_from(binder_month_button, 'value')
|
||||||
ui.markdown("**Urlaubsanspruch**").classes('col-span-2')
|
ui.markdown("**Urlaubsanspruch**").classes('col-span-2')
|
||||||
vacation_select = ui.select(list(reversed(available_years)), on_change=activate_vacation)
|
vacation_select = ui.select(list(reversed(available_years)), on_change=activate_vacation)
|
||||||
vacation_button = ui.button("Anzeigen", on_click=lambda: ui.navigate.to(f"/api/vacation/{current_user.username}/{vacation_select.value}", new_tab=True)).bind_enabled_from(binder_vacation, 'value')
|
vacation_button = ui.button("Anzeigen", on_click=lambda: ui.navigate.to(f"/api/vacation/{current_user.username}/{vacation_select.value}", new_tab=True)).bind_enabled_from(binder_vacation, 'value')
|
||||||
ui.markdown("**Fehlzeitenübersicht**").classes('col-span-2')
|
ui.markdown("**Fehlzeitenübersicht**").classes('col-span-2')
|
||||||
absences_select = ui.select(list(reversed(available_years)), on_change=activate_absence)
|
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')
|
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')
|
ui.separator().classes('col-span-2')
|
||||||
|
|
||||||
def logout():
|
def logout():
|
||||||
app.storage.user.pop("active_user", None)
|
app.storage.user.pop("active_user", None)
|
||||||
ui.navigate.to("/")
|
ui.navigate.to("/")
|
||||||
|
|
||||||
ui.button("Logout", on_click=logout).classes('col-span-2')
|
ui.button("Logout", on_click=logout).classes('col-span-2')
|
||||||
|
with ui.tab_panel(absence):
|
||||||
|
ui.label("Urlaub für folgenden Zeitraum beantragen:")
|
||||||
|
vacation_date = ui.date().props('range today-btn')
|
||||||
|
def vacation_submission():
|
||||||
|
if vacation_date.value == None:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
current_user.vacation_application(vacation_date.value["from"], vacation_date.value["to"])
|
||||||
|
except TypeError:
|
||||||
|
current_user.vacation_application(vacation_date.value, vacation_date.value)
|
||||||
|
vacation_date.value = ""
|
||||||
|
with ui.dialog() as dialog, ui.card():
|
||||||
|
ui.label("Urlaubsantrag wurde abgeschickt")
|
||||||
|
ui.button("OK", on_click=dialog.close)
|
||||||
|
open_vacation_applications.refresh()
|
||||||
|
dialog.open()
|
||||||
|
|
||||||
|
ui.button("Einreichen", on_click=vacation_submission).classes('w-full items-center').tooltip("Hiermit reichen Sie einen Urlaubsantrag für den oben markierten Zeitraum ein.")
|
||||||
|
|
||||||
|
@ui.refreshable
|
||||||
|
def open_vacation_applications():
|
||||||
|
open_applications = current_user.get_open_vacation_applications()
|
||||||
|
if len(list(open_applications)) > 0:
|
||||||
|
ui.separator()
|
||||||
|
ui.label("Offene Urlaubsanträge:").classes('font-bold')
|
||||||
|
va_columns = [ {'label': 'Index', 'name': 'index', 'field': 'index', 'classes': 'hidden', 'headerClasses': 'hidden'},
|
||||||
|
{'label': 'Start', 'name': 'start', 'field': 'start'},
|
||||||
|
{'label': 'Ende', 'name': 'end', 'field': 'end'}]
|
||||||
|
va_rows = [ ]
|
||||||
|
date_string = '%d.%m.%Y'
|
||||||
|
for i, dates in open_applications.items():
|
||||||
|
startdate_dt = datetime.datetime.strptime(dates[0], '%Y-%m-%d')
|
||||||
|
enddate_dt = datetime.datetime.strptime(dates[1], '%Y-%m-%d')
|
||||||
|
va_rows.append({'index': i, 'start': startdate_dt.strftime(date_string), 'end': enddate_dt.strftime(date_string)})
|
||||||
|
|
||||||
|
va_table = ui.table(columns=va_columns, rows=va_rows, selection="single", row_key="index").classes('w-full')
|
||||||
|
def retract_va():
|
||||||
|
try:
|
||||||
|
retract_result = current_user.revoke_vacation_application(va_table.selected[0]["index"])
|
||||||
|
open_vacation_applications.refresh()
|
||||||
|
if retract_result == 0:
|
||||||
|
ui.notify("Urlaubsantrag zurückgezogen")
|
||||||
|
except IndexError:
|
||||||
|
ui.notify("Kein Urlaubsanstrag ausgewählt")
|
||||||
|
ui.button("Zurückziehen", on_click=retract_va).tooltip("Hiermit wird der oben gewählte Urlaubsantrag zurückgezogen.").classes('w-full')
|
||||||
|
|
||||||
|
open_vacation_applications()
|
||||||
ui.space()
|
ui.space()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
59
lib/users.py
59
lib/users.py
@ -5,13 +5,17 @@ import hashlib
|
|||||||
import os
|
import os
|
||||||
from calendar import monthrange
|
from calendar import monthrange
|
||||||
from stat import S_IREAD, S_IWUSR
|
from stat import S_IREAD, S_IWUSR
|
||||||
|
from nicegui import ui
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import time
|
import time
|
||||||
import json
|
import json
|
||||||
import shutil
|
import shutil
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from lib.definitions import userfolder, scriptpath, usersettingsfilename, photofilename, status_in, status_out, standard_adminsettings, standard_usersettings
|
from lib.definitions import userfolder, scriptpath, usersettingsfilename, photofilename, status_in, status_out, \
|
||||||
|
standard_adminsettings, standard_usersettings, va_file
|
||||||
|
|
||||||
|
|
||||||
# Benutzerklasse
|
# Benutzerklasse
|
||||||
|
|
||||||
@ -59,7 +63,6 @@ class user:
|
|||||||
with open(filename, 'a') as file:
|
with open(filename, 'a') as file:
|
||||||
# Schreibe den Timestamp in die Datei und füge einen Zeilenumbruch hinzu
|
# Schreibe den Timestamp in die Datei und füge einen Zeilenumbruch hinzu
|
||||||
file.write(f"{timestamp}\n")
|
file.write(f"{timestamp}\n")
|
||||||
file.close()
|
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
# Fehlende Verzeichnisse anlegen
|
# Fehlende Verzeichnisse anlegen
|
||||||
folder_path = os.path.dirname(filename)
|
folder_path = os.path.dirname(filename)
|
||||||
@ -261,7 +264,7 @@ class user:
|
|||||||
filename_txt = os.path.join(self.userfolder, f"{year}-{month}.txt")
|
filename_txt = os.path.join(self.userfolder, f"{year}-{month}.txt")
|
||||||
os.chmod(filename_txt, S_IREAD)
|
os.chmod(filename_txt, S_IREAD)
|
||||||
|
|
||||||
def get_last_months_overtime(self, year, month):
|
def get_last_months_overtime(self, year=datetime.datetime.now().year, month=datetime.datetime.now().month):
|
||||||
try:
|
try:
|
||||||
if int(month) == 1:
|
if int(month) == 1:
|
||||||
year = str(int(year) - 1)
|
year = str(int(year) - 1)
|
||||||
@ -385,7 +388,7 @@ class user:
|
|||||||
hours_to_work = -1
|
hours_to_work = -1
|
||||||
return hours_to_work
|
return hours_to_work
|
||||||
|
|
||||||
def get_vacation_claim(self, year, month, day):
|
def get_vacation_claim(self, year=datetime.datetime.now().year, month=datetime.datetime.now().month, day=datetime.datetime.now().day):
|
||||||
workhour_entries = list(self.workhours)
|
workhour_entries = list(self.workhours)
|
||||||
workhour_entries.sort()
|
workhour_entries.sort()
|
||||||
day_to_check = datetime.datetime(int(year), int(month), int(day))
|
day_to_check = datetime.datetime(int(year), int(month), int(day))
|
||||||
@ -403,18 +406,18 @@ class user:
|
|||||||
|
|
||||||
return int(claim)
|
return int(claim)
|
||||||
|
|
||||||
def count_vacation_days(self, year):
|
def count_absence_days(self, absence_code: str, year=datetime.datetime.now().year):
|
||||||
vacation_used = 0
|
absence_days = 0
|
||||||
for month in range(0, 13):
|
for month in range(0, 13):
|
||||||
try:
|
try:
|
||||||
absence_dict = self.get_absence(year, month)
|
absence_dict = self.get_absence(year, month)
|
||||||
for entry, absence_type in absence_dict.items():
|
for entry, absence_type in absence_dict.items():
|
||||||
if absence_type == "U":
|
if absence_type == absence_code:
|
||||||
vacation_used += 1
|
absence_days += 1
|
||||||
|
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
return vacation_used
|
return absence_days
|
||||||
|
|
||||||
def delete_photo(self):
|
def delete_photo(self):
|
||||||
os.remove(self.photofile)
|
os.remove(self.photofile)
|
||||||
@ -451,6 +454,44 @@ class user:
|
|||||||
|
|
||||||
return [total_time, in_time_stamp]
|
return [total_time, in_time_stamp]
|
||||||
|
|
||||||
|
def vacation_application(self, startdate, enddate):
|
||||||
|
application_file = os.path.join(self.userfolder, va_file)
|
||||||
|
try:
|
||||||
|
with open(application_file, 'r') as json_file:
|
||||||
|
applications = json.load(json_file)
|
||||||
|
except FileNotFoundError:
|
||||||
|
applications = { }
|
||||||
|
applications[str(len(list(applications)))] = (startdate, enddate)
|
||||||
|
with open(application_file, 'w') as json_file:
|
||||||
|
json_file.write(json.dumps(applications, indent=4))
|
||||||
|
|
||||||
|
def get_open_vacation_applications(self):
|
||||||
|
application_file = os.path.join(self.userfolder, va_file)
|
||||||
|
try:
|
||||||
|
with open(application_file, 'r') as json_file:
|
||||||
|
applications = json.load(json_file)
|
||||||
|
except FileNotFoundError:
|
||||||
|
applications = { }
|
||||||
|
return applications
|
||||||
|
|
||||||
|
def revoke_vacation_application(self, index):
|
||||||
|
application_file = os.path.join(self.userfolder, va_file)
|
||||||
|
with open(application_file, 'r') as json_file:
|
||||||
|
applications = json.load(json_file)
|
||||||
|
try:
|
||||||
|
del(applications[index])
|
||||||
|
new_applications = { }
|
||||||
|
new_index = 0
|
||||||
|
for index, dates in applications.items():
|
||||||
|
new_applications[new_index] = dates
|
||||||
|
new_index += 1
|
||||||
|
with open(application_file, 'w') as json_file:
|
||||||
|
json_file.write(json.dumps(new_applications, indent=4))
|
||||||
|
return 0
|
||||||
|
except KeyError:
|
||||||
|
ui.notify("Urlaubsantrag wurde schon bearbeitet")
|
||||||
|
return -1
|
||||||
|
|
||||||
# Benutzer auflisten
|
# Benutzer auflisten
|
||||||
def list_users():
|
def list_users():
|
||||||
|
|
||||||
|
@ -71,6 +71,7 @@
|
|||||||
"2030-10-03": "Tag der deutschen Einheit",
|
"2030-10-03": "Tag der deutschen Einheit",
|
||||||
"2030-10-30": "Reformationstag",
|
"2030-10-30": "Reformationstag",
|
||||||
"2030-12-25": "1. Weihnachtsfeiertag",
|
"2030-12-25": "1. Weihnachtsfeiertag",
|
||||||
"2030-12-26": "2. Weihnachtsfeiertag"
|
"2030-12-26": "2. Weihnachtsfeiertag",
|
||||||
|
"2025-06-11": "Testeintrag"
|
||||||
}
|
}
|
||||||
}
|
}
|
1
users/testuser1/vacation_application.json
Normal file
1
users/testuser1/vacation_application.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{}
|
@ -1,8 +1,7 @@
|
|||||||
{
|
{
|
||||||
"username": "testuser10",
|
"username": "testuser10",
|
||||||
"fullname": "Diego Dieci",
|
"fullname": "Diego Dieci",
|
||||||
"password": "123456789",
|
"password": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
|
||||||
"api_key": "807518cd5bd85c1e4855d340f9b77b23eac21b7f",
|
|
||||||
"workhours": {
|
"workhours": {
|
||||||
"2024-04-01": {
|
"2024-04-01": {
|
||||||
"1": "1",
|
"1": "1",
|
||||||
@ -14,5 +13,6 @@
|
|||||||
"7": "7",
|
"7": "7",
|
||||||
"vacation": "30"
|
"vacation": "30"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"api_key": "807518cd5bd85c1e4855d340f9b77b23eac21b7f"
|
||||||
}
|
}
|
@ -1,14 +1,6 @@
|
|||||||
{
|
{
|
||||||
"0": [
|
"0": [
|
||||||
"2025-05-05",
|
"2025-06-09",
|
||||||
"2025-05-05"
|
"2025-06-19"
|
||||||
],
|
|
||||||
"1": [
|
|
||||||
"2025-05-06",
|
|
||||||
"2025-05-14"
|
|
||||||
],
|
|
||||||
"2": [
|
|
||||||
"2025-05-19",
|
|
||||||
"2025-05-22"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
18
users/testuser2/settings.json
Normal file
18
users/testuser2/settings.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"username": "testuser2",
|
||||||
|
"fullname": "testuser2",
|
||||||
|
"password": "37a8eec1ce19687d132fe29051dca629d164e2c4958ba141d5f4133a33f0688f",
|
||||||
|
"api_key": "84799b1cbb92514f047bc2186cb4b4aafb352d69",
|
||||||
|
"workhours": {
|
||||||
|
"2025-05-27": {
|
||||||
|
"1": 0,
|
||||||
|
"2": 0,
|
||||||
|
"3": 0,
|
||||||
|
"4": 0,
|
||||||
|
"5": 0,
|
||||||
|
"6": 0,
|
||||||
|
"7": 0,
|
||||||
|
"vacation": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user