impolementation of standardised time zone
Release Inventarsystem / release-docker (push) Successful in 2m17s
Release Inventarsystem / release-docker (push) Successful in 2m17s
This commit is contained in:
+380
-150
@@ -95,6 +95,7 @@ import Web.modules.database.settings as cfg
|
||||
from Web.modules.database.settings import MongoClient
|
||||
from tenant import get_tenant_context, get_tenant_db, get_tenant_trial_status, purge_expired_trial_tenants
|
||||
from pymongo.errors import DuplicateKeyError
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
|
||||
app = Flask(__name__, static_folder='static') # Correctly set static folder
|
||||
@@ -199,7 +200,7 @@ def rollover_student_card_classes(dry_run=False, *, max_class=None, graduate_lab
|
||||
client = MongoClient(MONGODB_HOST, MONGODB_PORT)
|
||||
db = client[MONGODB_DB]
|
||||
col = db['student_cards']
|
||||
now = datetime.datetime.now()
|
||||
now = datetime.datetime.now(ZoneInfo("Europe/Berlin"))
|
||||
|
||||
cursor = list(col.find({}, {'Klasse': 1}))
|
||||
for doc in cursor:
|
||||
@@ -269,7 +270,7 @@ def api_library_return_by_code():
|
||||
return jsonify({'ok': False, 'message': 'Kein Bibliotheksmedium für diesen Code gefunden.'}), 404
|
||||
|
||||
item_id = str(item_doc['_id'])
|
||||
now = datetime.datetime.now()
|
||||
now = datetime.datetime.now(ZoneInfo("Europe/Berlin"))
|
||||
|
||||
# If item already available -> nothing to return
|
||||
if item_doc.get('Verfuegbar', True):
|
||||
@@ -1025,7 +1026,7 @@ def _prepare_invoice_pdf_payload(invoice_data, borrow_doc=None, item_doc=None):
|
||||
item_id = str(item_doc.get('_id'))
|
||||
|
||||
return {
|
||||
'invoice_number': invoice_data.get('invoice_number') or _build_invoice_number(borrow_doc.get('_id', ''), datetime.datetime.now()),
|
||||
'invoice_number': invoice_data.get('invoice_number') or _build_invoice_number(borrow_doc.get('_id', ''), datetime.datetime.now(ZoneInfo("Europe/Berlin"))),
|
||||
'created_at': created_at_raw,
|
||||
'created_at_display': created_at_display,
|
||||
'borrower': invoice_data.get('borrower') or borrow_doc.get('User', '-'),
|
||||
@@ -1048,7 +1049,7 @@ def _create_notification(db, *, audience, notif_type, title, message, target_use
|
||||
if existing:
|
||||
return False
|
||||
|
||||
now = datetime.datetime.now()
|
||||
now = datetime.datetime.now(ZoneInfo("Europe/Berlin"))
|
||||
payload = {
|
||||
'Audience': audience,
|
||||
'Type': notif_type,
|
||||
@@ -1278,7 +1279,7 @@ def _build_reminder_message(item_name, start_dt=None, end_dt=None):
|
||||
|
||||
def create_return_reminders():
|
||||
"""Create one-time reminders for day-1 and planned-end events."""
|
||||
now = datetime.datetime.now()
|
||||
now = datetime.datetime.now(ZoneInfo("Europe/Berlin"))
|
||||
one_day_ago = now - datetime.timedelta(days=1)
|
||||
|
||||
client = None
|
||||
@@ -1456,7 +1457,7 @@ def update_appointment_statuses():
|
||||
- Geplante Termine, die aktiviert werden sollten
|
||||
- Aktive Termine, die beendet werden sollten
|
||||
"""
|
||||
current_time = datetime.datetime.now(datetime.timezone.utc)
|
||||
current_time = datetime.datetime.now(ZoneInfo("Europe/Berlin"))
|
||||
|
||||
try:
|
||||
# Hole alle Termine mit Status 'planned' oder 'active'
|
||||
@@ -1589,7 +1590,105 @@ def update_appointment_statuses():
|
||||
f"Failed to create activation notification for {appointment.get('_id')}: {notif_err}"
|
||||
)
|
||||
elif it.is_library_item(appointment.get('Item')):
|
||||
create_return_reminders()
|
||||
current_time = datetime.datetime.now(ZoneInfo("Europe/Berlin"))
|
||||
|
||||
try:
|
||||
client = MongoClient(MONGODB_HOST, MONGODB_PORT)
|
||||
db = client[MONGODB_DB]
|
||||
ausleihungen = db['ausleihungen']
|
||||
users_col = db['users']
|
||||
items_col = db['items']
|
||||
|
||||
# Finde alle aktiven Ausleihungen, die in der Vergangenheit fällig waren
|
||||
overdue_appointments = list(ausleihungen.find({
|
||||
'Status': 'active',
|
||||
'DueDate': {'$lt': current_time}
|
||||
}))
|
||||
|
||||
for appt in overdue_appointments:
|
||||
days_overdue = (current_time - appt.get('DueDate', current_time)).days
|
||||
mahnstufe = appt.get('Mahnstufe', 0)
|
||||
target_user = str(appt.get('User', '')).strip()
|
||||
item_id = appt.get('Item')
|
||||
|
||||
# 1. Objektdetails holen (für den E-Mail-Text)
|
||||
item_name = "Unbekannter Artikel"
|
||||
if item_id:
|
||||
try:
|
||||
item_doc = items_col.find_one({'_id': ObjectId(item_id)})
|
||||
if item_doc:
|
||||
item_name = item_doc.get('Name', str(item_id))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 2. Nutzerdetails holen (für die E-Mail-Adresse)
|
||||
user_doc = users_col.find_one({'username': target_user}) # Feldnamen ggf. anpassen (z.B. '_id')
|
||||
if not user_doc:
|
||||
app.logger.warning(f"Nutzer '{target_user}' für Mahnung nicht gefunden.")
|
||||
continue
|
||||
|
||||
user_email = user_doc.get('email')
|
||||
|
||||
# 3. Eskalationsstufen prüfen
|
||||
if days_overdue >= 14 and days_overdue < 28 and mahnstufe == 0:
|
||||
# STUFE 1: Erste Mahnung
|
||||
ausleihungen.update_one(
|
||||
{'_id': appt['_id']},
|
||||
{'$set': {'Mahnstufe': 1, 'LastUpdated': current_time}}
|
||||
)
|
||||
|
||||
_create_notification(
|
||||
db, audience='user', notif_type='warning',
|
||||
title='Erinnerung: Ausleihe überfällig',
|
||||
message=f'Deine Ausleihe für "{item_name}" ist seit {days_overdue} Tagen überfällig.',
|
||||
target_user=target_user, severity='warning'
|
||||
)
|
||||
|
||||
if user_email:
|
||||
subject = "1. Mahnung: Rückgabe überfällig"
|
||||
note = (f"Hallo {target_user},<br><br>"
|
||||
f"bitte beachte, dass die Ausleihe für den Artikel <b>{item_name}</b> "
|
||||
f"seit {days_overdue} Tagen überfällig ist. Bitte bringe den Artikel zeitnah zurück.")
|
||||
send(email=user_email, subject=subject, note=note, sender="Bibliotheksverwaltung")
|
||||
app.logger.info(f"1. Mahnung an {user_email} gesendet.")
|
||||
|
||||
elif days_overdue >= 28 and mahnstufe == 1:
|
||||
# STUFE 2: Letzte Mahnung, Sperrung und Report
|
||||
ausleihungen.update_one(
|
||||
{'_id': appt['_id']},
|
||||
{'$set': {'Mahnstufe': 2, 'LastUpdated': current_time}}
|
||||
)
|
||||
|
||||
# Nutzerkonto für weitere Ausleihen sperren
|
||||
users_col.update_one(
|
||||
{'_id': user_doc['_id']},
|
||||
{'$set': {
|
||||
'is_blocked': True,
|
||||
'block_reason': f'System-Sperre: Ausleihe von "{item_name}" {days_overdue} Tage überfällig.'
|
||||
}}
|
||||
)
|
||||
|
||||
_create_notification(
|
||||
db, audience='user', notif_type='error',
|
||||
title='Konto gesperrt - 2. Mahnung',
|
||||
message=f'Aufgrund der starken Überfälligkeit von "{item_name}" wurde dein Konto vorübergehend gesperrt.',
|
||||
target_user=target_user, severity='critical'
|
||||
)
|
||||
|
||||
if user_email:
|
||||
subject = "WICHTIG: Kontosperrung & 2. Mahnung"
|
||||
note = (f"Hallo {target_user},<br><br>"
|
||||
f"deine Ausleihe für den Artikel <b>{item_name}</b> ist nun seit {days_overdue} Tagen überfällig. "
|
||||
f"Dies ist ein automatischer Bericht über deinen Status: <b>Dein Konto wurde soeben für neue Reservierungen gesperrt.</b><br><br>"
|
||||
f"Die Sperre wird erst aufgehoben, sobald der Artikel im System als zurückgegeben gemeldet wurde.")
|
||||
send(email=user_email, subject=subject, note=note, sender="Bibliotheksverwaltung")
|
||||
app.logger.warning(
|
||||
f"Nutzer {target_user} gesperrt. 2. Mahnung an {user_email} gesendet.")
|
||||
|
||||
client.close()
|
||||
|
||||
except Exception as e:
|
||||
app.logger.error(f"Fehler bei der automatischen Mahnlauf-Prüfung: {e}")
|
||||
|
||||
client.close()
|
||||
|
||||
@@ -1621,7 +1720,7 @@ def _initialize_scheduler():
|
||||
|
||||
# 1. Generate a unique ID for this specific worker process
|
||||
_scheduler_worker_id = str(uuid.uuid4())
|
||||
now = datetime.datetime.now(datetime.timezone.utc)
|
||||
now = datetime.datetime.now(ZoneInfo("Europe/Berlin"))
|
||||
|
||||
# 2. Ensure the lock document exists (initialize if missing)
|
||||
try:
|
||||
@@ -1665,7 +1764,7 @@ def _initialize_scheduler():
|
||||
hb_db = hb_client[MONGODB_DB]
|
||||
hb_db['system_locks'].update_one(
|
||||
{'_id': 'scheduler_lock', 'worker_id': _scheduler_worker_id},
|
||||
{'$set': {'locked_at': datetime.datetime.now(datetime.timezone.utc)}}
|
||||
{'$set': {'locked_at': datetime.datetime.now(ZoneInfo("Europe/Berlin"))}}
|
||||
)
|
||||
hb_client.close()
|
||||
except Exception as e:
|
||||
@@ -1714,110 +1813,6 @@ def _shutdown_scheduler():
|
||||
atexit.register(_shutdown_scheduler)
|
||||
|
||||
|
||||
def create_return_reminders():
|
||||
"""
|
||||
Prüft aktive Ausleihungen auf Überschreitung der Rückgabefrist,
|
||||
erhöht Mahnstufen, versendet E-Mails/Benachrichtigungen und sperrt Nutzer.
|
||||
"""
|
||||
current_time = datetime.datetime.now(datetime.timezone.utc)
|
||||
|
||||
try:
|
||||
client = MongoClient(MONGODB_HOST, MONGODB_PORT)
|
||||
db = client[MONGODB_DB]
|
||||
ausleihungen = db['ausleihungen']
|
||||
users_col = db['users']
|
||||
items_col = db['items']
|
||||
|
||||
# Finde alle aktiven Ausleihungen, die in der Vergangenheit fällig waren
|
||||
overdue_appointments = list(ausleihungen.find({
|
||||
'Status': 'active',
|
||||
'DueDate': {'$lt': current_time}
|
||||
}))
|
||||
|
||||
for appt in overdue_appointments:
|
||||
days_overdue = (current_time - appt.get('DueDate', current_time)).days
|
||||
mahnstufe = appt.get('Mahnstufe', 0)
|
||||
target_user = str(appt.get('User', '')).strip()
|
||||
item_id = appt.get('Item')
|
||||
|
||||
# 1. Objektdetails holen (für den E-Mail-Text)
|
||||
item_name = "Unbekannter Artikel"
|
||||
if item_id:
|
||||
try:
|
||||
item_doc = items_col.find_one({'_id': ObjectId(item_id)})
|
||||
if item_doc:
|
||||
item_name = item_doc.get('Name', str(item_id))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 2. Nutzerdetails holen (für die E-Mail-Adresse)
|
||||
user_doc = users_col.find_one({'username': target_user}) # Feldnamen ggf. anpassen (z.B. '_id')
|
||||
if not user_doc:
|
||||
app.logger.warning(f"Nutzer '{target_user}' für Mahnung nicht gefunden.")
|
||||
continue
|
||||
|
||||
user_email = user_doc.get('email')
|
||||
|
||||
# 3. Eskalationsstufen prüfen
|
||||
if days_overdue >= 14 and days_overdue < 28 and mahnstufe == 0:
|
||||
# STUFE 1: Erste Mahnung
|
||||
ausleihungen.update_one(
|
||||
{'_id': appt['_id']},
|
||||
{'$set': {'Mahnstufe': 1, 'LastUpdated': current_time}}
|
||||
)
|
||||
|
||||
_create_notification(
|
||||
db, audience='user', notif_type='warning',
|
||||
title='Erinnerung: Ausleihe überfällig',
|
||||
message=f'Deine Ausleihe für "{item_name}" ist seit {days_overdue} Tagen überfällig.',
|
||||
target_user=target_user, severity='warning'
|
||||
)
|
||||
|
||||
if user_email:
|
||||
subject = "1. Mahnung: Rückgabe überfällig"
|
||||
note = (f"Hallo {target_user},<br><br>"
|
||||
f"bitte beachte, dass die Ausleihe für den Artikel <b>{item_name}</b> "
|
||||
f"seit {days_overdue} Tagen überfällig ist. Bitte bringe den Artikel zeitnah zurück.")
|
||||
send(email=user_email, subject=subject, note=note, sender="Bibliotheksverwaltung")
|
||||
app.logger.info(f"1. Mahnung an {user_email} gesendet.")
|
||||
|
||||
elif days_overdue >= 28 and mahnstufe == 1:
|
||||
# STUFE 2: Letzte Mahnung, Sperrung und Report
|
||||
ausleihungen.update_one(
|
||||
{'_id': appt['_id']},
|
||||
{'$set': {'Mahnstufe': 2, 'LastUpdated': current_time}}
|
||||
)
|
||||
|
||||
# Nutzerkonto für weitere Ausleihen sperren
|
||||
users_col.update_one(
|
||||
{'_id': user_doc['_id']},
|
||||
{'$set': {
|
||||
'is_blocked': True,
|
||||
'block_reason': f'System-Sperre: Ausleihe von "{item_name}" {days_overdue} Tage überfällig.'
|
||||
}}
|
||||
)
|
||||
|
||||
_create_notification(
|
||||
db, audience='user', notif_type='error',
|
||||
title='Konto gesperrt - 2. Mahnung',
|
||||
message=f'Aufgrund der starken Überfälligkeit von "{item_name}" wurde dein Konto vorübergehend gesperrt.',
|
||||
target_user=target_user, severity='critical'
|
||||
)
|
||||
|
||||
if user_email:
|
||||
subject = "WICHTIG: Kontosperrung & 2. Mahnung"
|
||||
note = (f"Hallo {target_user},<br><br>"
|
||||
f"deine Ausleihe für den Artikel <b>{item_name}</b> ist nun seit {days_overdue} Tagen überfällig. "
|
||||
f"Dies ist ein automatischer Bericht über deinen Status: <b>Dein Konto wurde soeben für neue Reservierungen gesperrt.</b><br><br>"
|
||||
f"Die Sperre wird erst aufgehoben, sobald der Artikel im System als zurückgegeben gemeldet wurde.")
|
||||
send(email=user_email, subject=subject, note=note, sender="Bibliotheksverwaltung")
|
||||
app.logger.warning(f"Nutzer {target_user} gesperrt. 2. Mahnung an {user_email} gesendet.")
|
||||
|
||||
client.close()
|
||||
|
||||
except Exception as e:
|
||||
app.logger.error(f"Fehler bei der automatischen Mahnlauf-Prüfung: {e}")
|
||||
|
||||
"""-------------------------------------------------------------File Upload Validation----------------------------------------------------------------------------- """
|
||||
|
||||
def allowed_file(filename, file_content=None, max_size_mb=cfg.MAX_UPLOAD_MB):
|
||||
@@ -2501,7 +2496,7 @@ def _upload_student_cards_excel():
|
||||
student_cards_col.insert_one({
|
||||
'AusweisId': row['ausweis_id'],
|
||||
'StandardAusleihdauer': int(row['default_borrow_days']),
|
||||
'Erstellt': datetime.datetime.now(),
|
||||
'Erstellt': datetime.datetime.now(ZoneInfo("Europe/Berlin")),
|
||||
**encrypted_payload,
|
||||
})
|
||||
created_total += 1
|
||||
@@ -3635,10 +3630,246 @@ def library_loans_admin():
|
||||
client.close()
|
||||
|
||||
|
||||
def generate_test_ausleihen():
|
||||
"""
|
||||
Generiert Testdaten für die Überprüfung der Mahnungs- und Sperrlogik.
|
||||
Erstellt Dummy-Nutzer, Dummy-Items und Ausleihungen mit manipulierten Daten (DueDate).
|
||||
"""
|
||||
current_time = datetime.datetime.now(ZoneInfo("Europe/Berlin"))
|
||||
|
||||
try:
|
||||
client = MongoClient(MONGODB_HOST, MONGODB_PORT)
|
||||
db = client[MONGODB_DB]
|
||||
ausleihungen = db['ausleihungen']
|
||||
users_col = db['users']
|
||||
items_col = db['items']
|
||||
|
||||
print("Generiere Testdaten...")
|
||||
|
||||
# 1. Test-Nutzer erstellen oder aktualisieren (upsert)
|
||||
test_users = [
|
||||
{"username": "test_user_stufe1", "email": "stufe1@test.local", "is_blocked": False},
|
||||
{"username": "test_user_stufe2", "email": "stufe2@test.local", "is_blocked": False},
|
||||
{"username": "test_user_frisch", "email": "frisch@test.local", "is_blocked": False}
|
||||
]
|
||||
|
||||
for u in test_users:
|
||||
users_col.update_one(
|
||||
{'username': u['username']},
|
||||
{'$set': u},
|
||||
upsert=True
|
||||
)
|
||||
|
||||
# 2. Test-Items (Gegenstände) erstellen
|
||||
item1_res = items_col.insert_one({"Name": "Biologie Buch (Test Stufe 1)"})
|
||||
item2_res = items_col.insert_one({"Name": "iPad Pro (Test Stufe 2)"})
|
||||
item3_res = items_col.insert_one({"Name": "Taschenrechner (Test Frisch)"})
|
||||
|
||||
# 3. Test-Ausleihungen generieren
|
||||
# Fall 1: 20 Tage drüber, Mahnstufe 0 -> Sollte Stufe 1 triggern
|
||||
due_date_stufe1 = current_time - datetime.timedelta(days=20)
|
||||
|
||||
# Fall 2: 30 Tage drüber, Mahnstufe 1 -> Sollte Stufe 2 triggern & Nutzer sperren
|
||||
due_date_stufe2 = current_time - datetime.timedelta(days=30)
|
||||
|
||||
# Fall 3: 5 Tage drüber, Mahnstufe 0 -> Sollte NICHTS tun (da < 14 Tage)
|
||||
due_date_frisch = current_time - datetime.timedelta(days=5)
|
||||
|
||||
test_ausleihungen = [
|
||||
{
|
||||
"User": encrypt_text("test_user_stufe1"),
|
||||
"Item": str(item1_res.inserted_id),
|
||||
"Status": "active",
|
||||
"DueDate": due_date_stufe1,
|
||||
"Mahnstufe": 0,
|
||||
"LastUpdated": current_time
|
||||
},
|
||||
{
|
||||
"User": "test_user_stufe2",
|
||||
"Item": str(item2_res.inserted_id),
|
||||
"Status": "active",
|
||||
"DueDate": due_date_stufe2,
|
||||
"Mahnstufe": 1, # Hat bereits Stufe 1
|
||||
"LastUpdated": due_date_stufe2 # Wurde in der Vergangenheit gemahnt
|
||||
},
|
||||
{
|
||||
"User": "test_user_frisch",
|
||||
"Item": str(item3_res.inserted_id),
|
||||
"Status": "active",
|
||||
"DueDate": due_date_frisch,
|
||||
"Mahnstufe": 0,
|
||||
"LastUpdated": current_time
|
||||
}
|
||||
]
|
||||
|
||||
# In die Datenbank einfügen
|
||||
ausleihungen.insert_many(test_ausleihungen)
|
||||
|
||||
print("Erfolgreich! Folgende Testdaten wurden in die DB geschrieben:")
|
||||
print(
|
||||
f"- Fall 1 (Mahnstufe 1 Trigger): Nutzer 'test_user_stufe1', fällig war am {due_date_stufe1.strftime('%Y-%m-%d')}")
|
||||
print(
|
||||
f"- Fall 2 (Mahnstufe 2 & Sperre Trigger): Nutzer 'test_user_stufe2', fällig war am {due_date_stufe2.strftime('%Y-%m-%d')}")
|
||||
print(
|
||||
f"- Fall 3 (Ignorieren - zu früh): Nutzer 'test_user_frisch', fällig war am {due_date_frisch.strftime('%Y-%m-%d')}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Fehler beim Erstellen der Testdaten: {e}")
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
@app.route('/test_mahnungen')
|
||||
def test_mahnungen():
|
||||
# Testdaten (Mock-Daten), um alle if/else Bedingungen im HTML zu testen
|
||||
test_overdue_list = [
|
||||
generate_test_ausleihen()
|
||||
create_return_reminders()
|
||||
"""Admin overview for overdue library items (Mahnungen)."""
|
||||
if 'username' not in session:
|
||||
flash(
|
||||
'Ihnen ist es nicht gestattet auf dieser Internetanwendung, die eben besuchte Adrrese zu nutzen, versuchen sie es erneut nach dem sie sich mit einem berechtigten Nutzer angemeldet haben!',
|
||||
'error')
|
||||
return redirect(url_for('login'))
|
||||
|
||||
current_permissions = us.get_effective_permissions(session['username'])
|
||||
|
||||
# Hier nutzen wir beispielhaft die library_loans_admin Berechtigung
|
||||
if not current_permissions['pages'].get('library_loans_admin', False):
|
||||
flash(
|
||||
'Ihnen ist es nicht gestattet auf dieser Internetanwendung, die eben besuchte Adrrese zu nutzen, versuchen sie es erneut nach dem sie sich mit einem berechtigten Nutzer angemeldet haben!',
|
||||
'error')
|
||||
return redirect(url_for('library_view'))
|
||||
|
||||
if not cfg.MODULES.is_enabled('library'):
|
||||
flash('Bibliotheks-Modul ist deaktiviert.', 'error')
|
||||
return redirect(url_for('home_admin'))
|
||||
|
||||
def fmt_dt(dt):
|
||||
try:
|
||||
return dt.strftime('%d.%m.%Y') if dt else 'Unbekannt'
|
||||
except Exception:
|
||||
return str(dt) if dt else 'Unbekannt'
|
||||
|
||||
def safe_decrypt(val):
|
||||
if not val or not isinstance(val, str):
|
||||
return val or ''
|
||||
try:
|
||||
return decrypt_text(val)
|
||||
except Exception:
|
||||
return val
|
||||
|
||||
current_time = datetime.datetime.now(ZoneInfo("Europe/Berlin"))
|
||||
client = None
|
||||
|
||||
try:
|
||||
client = MongoClient(MONGODB_HOST, MONGODB_PORT)
|
||||
db = client[MONGODB_DB]
|
||||
ausleihungen_col = db['ausleihungen']
|
||||
items_col = db['items']
|
||||
users_col = db['users']
|
||||
student_cards_col = db['student_cards']
|
||||
|
||||
# Überfällige Ausleihen abrufen (Status 'active' und DueDate in der Vergangenheit)
|
||||
overdue_records = list(ausleihungen_col.find({
|
||||
'Status': 'active',
|
||||
'DueDate': {'$lt': current_time}
|
||||
}).sort('DueDate', 1))
|
||||
|
||||
overdue_list = []
|
||||
|
||||
if overdue_records:
|
||||
# Bulk-Lookups zur Vermeidung von N+1 Queries
|
||||
item_ids = []
|
||||
for r in overdue_records:
|
||||
i_id = r.get('Item')
|
||||
if i_id:
|
||||
try:
|
||||
item_ids.append(ObjectId(str(i_id)))
|
||||
except Exception:
|
||||
item_ids.append(str(i_id))
|
||||
|
||||
items_cursor = items_col.find({
|
||||
'_id': {'$in': item_ids}
|
||||
}, {'Name': 1, 'Code_4': 1})
|
||||
item_map = {str(item['_id']): item for item in items_cursor}
|
||||
|
||||
raw_users = [r.get('User') for r in overdue_records if r.get('User')]
|
||||
users_cursor = users_col.find({'username': {'$in': raw_users}}, {'username': 1, 'is_blocked': 1})
|
||||
user_block_map = {u.get('username'): u.get('is_blocked', False) for u in users_cursor}
|
||||
|
||||
class_map = {}
|
||||
all_cards = list(student_cards_col.find({}, {'SchülerName': 1, 'Klasse': 1, 'AusweisId': 1}))
|
||||
for card in all_cards:
|
||||
raw_cls = card.get('Klasse')
|
||||
dec_cls = safe_decrypt(raw_cls)
|
||||
if not dec_cls:
|
||||
continue
|
||||
|
||||
raw_name = card.get('SchülerName')
|
||||
dec_name = safe_decrypt(raw_name)
|
||||
ausweis_id = card.get('AusweisId')
|
||||
|
||||
if dec_name:
|
||||
class_map[dec_name.strip().lower()] = dec_cls
|
||||
if raw_name:
|
||||
class_map[raw_name] = dec_cls
|
||||
if ausweis_id:
|
||||
class_map[str(ausweis_id).strip().lower()] = dec_cls
|
||||
|
||||
# Datenaufbereitung für das Template
|
||||
for record in overdue_records:
|
||||
item_id = str(record.get('Item') or '')
|
||||
item_doc = item_map.get(item_id, {})
|
||||
item_name = item_doc.get('Name', item_id)
|
||||
item_code = item_doc.get('Code_4', '')
|
||||
|
||||
if item_code:
|
||||
item_name = f"{item_name} ({item_code})"
|
||||
|
||||
raw_user = record.get('User', '')
|
||||
decrypted_user = safe_decrypt(raw_user)
|
||||
display_user = decrypted_user if decrypted_user else (raw_user or 'Unbekannt')
|
||||
|
||||
user_class = (
|
||||
safe_decrypt(record.get('Klasse'))
|
||||
or safe_decrypt(record.get('Class'))
|
||||
or safe_decrypt(record.get('school_class'))
|
||||
or class_map.get(raw_user, '')
|
||||
or class_map.get(decrypted_user, '')
|
||||
or class_map.get(display_user.strip().lower(), '')
|
||||
or '—'
|
||||
)
|
||||
|
||||
due_date_obj = record.get('DueDate')
|
||||
days_overdue = (current_time - due_date_obj).days if due_date_obj else 0
|
||||
is_blocked = user_block_map.get(raw_user, False)
|
||||
|
||||
overdue_list.append({
|
||||
'id': str(record.get('_id')),
|
||||
'item_name': item_name,
|
||||
'user': display_user,
|
||||
'klasse': user_class,
|
||||
'due_date': fmt_dt(due_date_obj),
|
||||
'days_overdue': days_overdue,
|
||||
'mahnstufe': record.get('Mahnstufe', 0),
|
||||
'is_blocked': is_blocked
|
||||
})
|
||||
|
||||
return render_template(
|
||||
'mahnungen_admin.html',
|
||||
overdue_list=overdue_list,
|
||||
library_module_enabled=cfg.MODULES.is_enabled('library'),
|
||||
student_cards_module_enabled=cfg.MODULES.is_enabled('student_cards'),
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
app.logger.error(f"Error loading mahnungen admin view: {e}")
|
||||
flash('Fehler beim Laden der Mahnungsverwaltung.', 'error')
|
||||
return redirect(url_for('home_admin'))
|
||||
finally:
|
||||
if client:
|
||||
client.close()
|
||||
|
||||
""" test_overdue_list = [
|
||||
{
|
||||
"user": "Max Mustermann",
|
||||
"klasse": "10A",
|
||||
@@ -3669,7 +3900,7 @@ def test_mahnungen():
|
||||
]
|
||||
|
||||
# Render das Template und übergebe die Testdaten
|
||||
return render_template('mahnungen_admin.html', overdue_list=test_overdue_list, APP_VERSION="1.0.0")
|
||||
return render_template('mahnungen_admin.html', overdue_list=test_overdue_list, APP_VERSION="1.0.0")"""
|
||||
|
||||
|
||||
@app.route('/mahnungen_admin')
|
||||
@@ -3708,7 +3939,7 @@ def mahnungen_admin():
|
||||
except Exception:
|
||||
return val
|
||||
|
||||
current_time = datetime.datetime.now(datetime.timezone.utc)
|
||||
current_time = datetime.datetime.now(ZoneInfo("Europe/Berlin"))
|
||||
client = None
|
||||
|
||||
try:
|
||||
@@ -4119,7 +4350,7 @@ def api_library_scan_action():
|
||||
|
||||
item_id = str(item_doc['_id'])
|
||||
borrower_name = card_doc.get('SchülerName') or f"Ausweis {student_card_id}"
|
||||
now = datetime.datetime.now()
|
||||
now = datetime.datetime.now(ZoneInfo("Europe/Berlin"))
|
||||
|
||||
if item_doc.get('Verfuegbar', True):
|
||||
borrow_duration_days = None
|
||||
@@ -4142,9 +4373,8 @@ def api_library_scan_action():
|
||||
card_default = cfg.STUDENT_DEFAULT_BORROW_DAYS
|
||||
borrow_duration_days = max(1, min(card_default, cfg.STUDENT_MAX_BORROW_DAYS))
|
||||
|
||||
end_date = now + datetime.timedelta(days=borrow_duration_days) if borrow_duration_days else None
|
||||
it.update_item_status(item_id, False, borrower_name)
|
||||
au.add_ausleihung(item_id, borrower_name, now, end_date=end_date)
|
||||
au.add_ausleihung(item_id, borrower_name, now)
|
||||
|
||||
_append_audit_event_standalone(
|
||||
event_type='ausleihung_borrowed',
|
||||
@@ -4406,7 +4636,7 @@ def api_library_item_update(item_id):
|
||||
'Autor': author,
|
||||
'Code_4': code_4,
|
||||
'ItemType': media_type,
|
||||
'LastUpdated': datetime.datetime.now()
|
||||
'LastUpdated': datetime.datetime.now(ZoneInfo("Europe/Berlin"))
|
||||
}
|
||||
|
||||
if normalized_isbn:
|
||||
@@ -4600,7 +4830,7 @@ def student_cards_admin():
|
||||
{'$set': {
|
||||
'AusweisId': ausweis_id,
|
||||
'StandardAusleihdauer': int(default_borrow_days),
|
||||
'Aktualisiert': datetime.datetime.now(),
|
||||
'Aktualisiert': datetime.datetime.now(ZoneInfo("Europe/Berlin")),
|
||||
**encrypted_payload
|
||||
}}
|
||||
)
|
||||
@@ -4639,7 +4869,7 @@ def student_cards_admin():
|
||||
student_cards.insert_one({
|
||||
'AusweisId': ausweis_id,
|
||||
'StandardAusleihdauer': int(default_borrow_days),
|
||||
'Erstellt': datetime.datetime.now(),
|
||||
'Erstellt': datetime.datetime.now(ZoneInfo("Europe/Berlin")),
|
||||
**encrypted_payload,
|
||||
})
|
||||
flash('Neuer Ausweis wurde hinzugefügt.', 'success')
|
||||
@@ -4696,7 +4926,7 @@ def student_cards_print():
|
||||
return render_template(
|
||||
'student_cards_print.html',
|
||||
student_cards=all_cards,
|
||||
current_datetime=datetime.datetime.now()
|
||||
current_datetime=datetime.datetime.now(ZoneInfo("Europe/Berlin"))
|
||||
)
|
||||
|
||||
|
||||
@@ -4730,7 +4960,7 @@ def student_card_barcode_print():
|
||||
return render_template(
|
||||
'student_card_barcode_print.html',
|
||||
student_cards=all_cards,
|
||||
current_datetime=datetime.datetime.now(),
|
||||
current_datetime=datetime.datetime.now(ZoneInfo("Europe/Berlin")),
|
||||
download_link=url_for('student_card_barcode_download')
|
||||
)
|
||||
|
||||
@@ -4926,7 +5156,7 @@ def student_card_barcode_download():
|
||||
pdf_buffer,
|
||||
mimetype='application/pdf',
|
||||
as_attachment=True,
|
||||
download_name=f'schuelerausweise_all_{datetime.datetime.now().strftime("%Y%m%d_%H%M%S")}.pdf'
|
||||
download_name=f'schuelerausweise_all_{datetime.datetime.now(ZoneInfo("Europe/Berlin")).strftime("%Y%m%d_%H%M%S")}.pdf'
|
||||
)
|
||||
except Exception as e:
|
||||
app.logger.error(f"Error occurred while generating PDF for card {card['AusweisId']}: {e}")
|
||||
@@ -5112,7 +5342,7 @@ def student_card_class_barcode_download():
|
||||
c.save()
|
||||
pdf_buffer.seek(0)
|
||||
|
||||
filename = f'ausweise_klasse_{class_name.replace(" ", "_")}_{datetime.datetime.now().strftime("%Y%m%d")}.pdf'
|
||||
filename = f'ausweise_klasse_{class_name.replace(" ", "_")}_{datetime.datetime.now(ZoneInfo("Europe/Berlin")).strftime("%Y%m%d")}.pdf'
|
||||
|
||||
return send_file(
|
||||
pdf_buffer,
|
||||
@@ -6395,7 +6625,7 @@ def upload_item():
|
||||
|
||||
def _soft_delete_item_groups(db, root_item_ids, username):
|
||||
"""Soft-delete one or more item groups and their borrow records."""
|
||||
now = datetime.datetime.now()
|
||||
now = datetime.datetime.now(ZoneInfo("Europe/Berlin"))
|
||||
unique_group_item_ids = []
|
||||
seen_ids = set()
|
||||
|
||||
@@ -6916,7 +7146,7 @@ def update_group():
|
||||
|
||||
# 1. Shared Fields (Group Logic)
|
||||
# These apply to every item in the group
|
||||
shared_update = {'LastUpdated': datetime.datetime.now()}
|
||||
shared_update = {'LastUpdated': datetime.datetime.now(ZoneInfo("Europe/Berlin"))}
|
||||
for source_key, target_key in (
|
||||
('name', 'Name'),
|
||||
('ort', 'Ort'),
|
||||
@@ -6991,7 +7221,7 @@ def report_damage(id):
|
||||
if not item_doc:
|
||||
return jsonify({'success': False, 'message': 'Objekt nicht gefunden.'}), 404
|
||||
|
||||
now = datetime.datetime.now()
|
||||
now = datetime.datetime.now(ZoneInfo("Europe/Berlin"))
|
||||
damage_entry = {
|
||||
'description': description,
|
||||
'reported_by': session['username'],
|
||||
@@ -7095,7 +7325,7 @@ def mark_damage_repaired(id):
|
||||
return jsonify({'success': False, 'message': 'Keine offenen Schäden vorhanden.'}), 400
|
||||
|
||||
active_borrow = ausleihungen_col.find_one({'Item': str(id), 'Status': 'active'}, {'_id': 1})
|
||||
now = datetime.datetime.now()
|
||||
now = datetime.datetime.now(ZoneInfo("Europe/Berlin"))
|
||||
repair_entry = {
|
||||
'repaired_by': session['username'],
|
||||
'repaired_at': now,
|
||||
@@ -7272,7 +7502,7 @@ def ausleihen(id):
|
||||
card_default = cfg.STUDENT_DEFAULT_BORROW_DAYS
|
||||
borrow_duration_days = max(1, min(card_default, cfg.STUDENT_MAX_BORROW_DAYS))
|
||||
|
||||
start_date = datetime.datetime.now()
|
||||
start_date = datetime.datetime.now(ZoneInfo("Europe/Berlin"))
|
||||
end_date = None
|
||||
if borrow_duration_days:
|
||||
end_date = start_date + datetime.timedelta(days=borrow_duration_days)
|
||||
@@ -7361,7 +7591,7 @@ def ausleihen(id):
|
||||
|
||||
# Before borrowing, block if there's a conflicting planned booking
|
||||
try:
|
||||
now = datetime.datetime.now()
|
||||
now = datetime.datetime.now(ZoneInfo("Europe/Berlin"))
|
||||
# Fetch planned bookings for this item from DB
|
||||
planned = au.get_planned_ausleihungen()
|
||||
# Count relevant upcoming planned bookings for today or ongoing
|
||||
@@ -7422,12 +7652,12 @@ def ausleihen(id):
|
||||
return redirect(url_for(redirect_target))
|
||||
|
||||
# If we reach here, we can borrow the requested number of exemplars
|
||||
current_date = datetime.datetime.now().strftime('%d.%m.%Y %H:%M')
|
||||
current_date = datetime.datetime.now(ZoneInfo("Europe/Berlin")).strftime('%d.%m.%Y %H:%M')
|
||||
|
||||
# If the item doesn't use exemplars (single item)
|
||||
if total_exemplare <= 1:
|
||||
it.update_item_status(id, False, effective_borrower)
|
||||
start_date = datetime.datetime.now()
|
||||
start_date = datetime.datetime.now(ZoneInfo("Europe/Berlin"))
|
||||
au.add_ausleihung(id, effective_borrower, start_date, end_date=end_date)
|
||||
_append_audit_event_standalone(
|
||||
event_type='ausleihung_returned',
|
||||
@@ -7470,7 +7700,7 @@ def ausleihen(id):
|
||||
it.update_item_status(id, False, username)
|
||||
|
||||
# Create ausleihung records for each borrowed exemplar
|
||||
start_date = datetime.datetime.now()
|
||||
start_date = datetime.datetime.now(ZoneInfo("Europe/Berlin"))
|
||||
for exemplar in new_borrowed_exemplars:
|
||||
exemplar_id = f"{id}_{exemplar['number']}"
|
||||
au.add_ausleihung(exemplar_id, effective_borrower, start_date, end_date=end_date, exemplar_data={
|
||||
@@ -7532,7 +7762,7 @@ def zurueckgeben(id):
|
||||
'Status': 'active'
|
||||
})
|
||||
|
||||
end_date = datetime.datetime.now()
|
||||
end_date = datetime.datetime.now(ZoneInfo("Europe/Berlin"))
|
||||
original_user = item.get('User', username)
|
||||
|
||||
updated_count = 0
|
||||
@@ -7544,7 +7774,7 @@ def zurueckgeben(id):
|
||||
{'$set': {
|
||||
'Status': 'completed',
|
||||
'End': end_date,
|
||||
'LastUpdated': datetime.datetime.now()
|
||||
'LastUpdated': datetime.datetime.now(ZoneInfo("Europe/Berlin"))
|
||||
}}
|
||||
)
|
||||
|
||||
@@ -7771,7 +8001,7 @@ def check_availability():
|
||||
# Also include current availability if checking today and item is borrowed now
|
||||
item_doc = items_col.find_one({'_id': ObjectId(item_id)})
|
||||
if item_doc and not item_doc.get('Verfuegbar', True):
|
||||
now = datetime.datetime.now()
|
||||
now = datetime.datetime.now(ZoneInfo("Europe/Berlin"))
|
||||
if req_start.date() == now.date():
|
||||
conflicts.append({'status': 'active', 'user': item_doc.get('User'), 'start': None, 'end': None, 'period': None, 'id': None})
|
||||
|
||||
@@ -8464,7 +8694,7 @@ def delete_user():
|
||||
items_col = db['items']
|
||||
users_col = db['users'] # Direkter Zugriff auf die User-Collection
|
||||
|
||||
now = datetime.datetime.now()
|
||||
now = datetime.datetime.now(ZoneInfo("Europe/Berlin"))
|
||||
|
||||
# 1. Aktive Ausleihen abschließen
|
||||
ausleihungen.update_many(
|
||||
@@ -8906,7 +9136,7 @@ def admin_reset_borrowing(borrow_id):
|
||||
item_id = rec.get('Item')
|
||||
user = rec.get('User')
|
||||
|
||||
now = datetime.datetime.now()
|
||||
now = datetime.datetime.now(ZoneInfo("Europe/Berlin"))
|
||||
if status == 'active':
|
||||
ausleihungen.update_one({'_id': rec['_id']}, {'$set': {'Status': 'completed', 'End': now, 'LastUpdated': now}})
|
||||
# Free the item
|
||||
@@ -9004,7 +9234,7 @@ def admin_create_invoice(borrow_id):
|
||||
|
||||
mark_destroyed = request.form.get('mark_destroyed') == 'on'
|
||||
close_borrowing = request.form.get('close_borrowing') == 'on'
|
||||
now = datetime.datetime.now()
|
||||
now = datetime.datetime.now(ZoneInfo("Europe/Berlin"))
|
||||
|
||||
existing_invoice = borrow_doc.get('InvoiceData') or {}
|
||||
if existing_invoice.get('invoice_number'):
|
||||
@@ -9173,7 +9403,7 @@ def admin_mark_invoice_paid(borrow_id):
|
||||
flash('Rechnung ist bereits als bezahlt markiert.', 'info')
|
||||
return redirect(url_for('admin_borrowings'))
|
||||
|
||||
now = datetime.datetime.now()
|
||||
now = datetime.datetime.now(ZoneInfo("Europe/Berlin"))
|
||||
result = ausleihungen.update_one(
|
||||
{'_id': borrow_doc['_id']},
|
||||
{
|
||||
@@ -9252,7 +9482,7 @@ def admin_pay_invoice_extended(borrow_id):
|
||||
flash('Für diese Ausleihung existiert keine Rechnung.', 'warning')
|
||||
return redirect(request.referrer or url_for('library_loans_admin'))
|
||||
|
||||
now = datetime.datetime.now()
|
||||
now = datetime.datetime.now(ZoneInfo("Europe/Berlin"))
|
||||
update_fields = {'LastUpdated': now}
|
||||
|
||||
if invoice_data.get('paid') is not True:
|
||||
@@ -9322,7 +9552,7 @@ def resolve_repaired_item(item_id):
|
||||
flash('Element nicht gefunden.', 'error')
|
||||
return redirect(request.referrer or url_for('library_loans_admin'))
|
||||
|
||||
now = datetime.datetime.now()
|
||||
now = datetime.datetime.now(ZoneInfo("Europe/Berlin"))
|
||||
open_reports = item_doc.get('DamageReports', []) or []
|
||||
|
||||
item_update = {
|
||||
@@ -9459,7 +9689,7 @@ def admin_add_invoice_correction(borrow_id):
|
||||
flash('Ungültiger Korrekturbetrag.', 'error')
|
||||
return redirect(url_for('admin_borrowings'))
|
||||
|
||||
now = datetime.datetime.now()
|
||||
now = datetime.datetime.now(ZoneInfo("Europe/Berlin"))
|
||||
correction_number = f"CORR-{now.strftime('%Y%m%d-%H%M%S')}-{str(borrow_doc.get('_id'))[-6:].upper()}"
|
||||
|
||||
correction_entry = {
|
||||
@@ -9549,7 +9779,7 @@ def resolve_repaired_item_funct(item_id, action, new_code_4="", current_user="ad
|
||||
return False, "Item nicht in der Datenbank gefunden."
|
||||
|
||||
series_group_id = item.get('SeriesGroupId')
|
||||
now = datetime.datetime.now()
|
||||
now = datetime.datetime.now(ZoneInfo("Europe/Berlin"))
|
||||
|
||||
legacy_damage_unset = {
|
||||
'has_damage': "",
|
||||
@@ -10060,7 +10290,7 @@ def admin_anonymize_names():
|
||||
|
||||
result = student_cards_col.update_one(
|
||||
{'_id': card_doc['_id']},
|
||||
{'$set': {'Aktualisiert': datetime.datetime.now(), **encrypted_payload}}
|
||||
{'$set': {'Aktualisiert': datetime.datetime.now(ZoneInfo("Europe/Berlin")), **encrypted_payload}}
|
||||
)
|
||||
if result.modified_count > 0:
|
||||
cards_updated += 1
|
||||
@@ -11174,7 +11404,7 @@ def mark_notification_read(notification_id):
|
||||
{'_id': ObjectId(notification_id)},
|
||||
{
|
||||
'$addToSet': {'ReadBy': username},
|
||||
'$set': {'UpdatedAt': datetime.datetime.now()}
|
||||
'$set': {'UpdatedAt': datetime.datetime.now(ZoneInfo("Europe/Berlin"))}
|
||||
}
|
||||
)
|
||||
if result.modified_count > 0:
|
||||
@@ -11814,7 +12044,7 @@ def schedule_appointment():
|
||||
return jsonify({'success': False, 'message': f'Fehler beim Prüfen der Verfügbarkeit'}), 500
|
||||
|
||||
# Check if the appointment should already be active
|
||||
now = datetime.datetime.now()
|
||||
now = datetime.datetime.now(ZoneInfo("Europe/Berlin"))
|
||||
initial_status = 'active' if start_datetime <= now else 'planned'
|
||||
|
||||
# Create the appointment
|
||||
|
||||
Reference in New Issue
Block a user