Removal again
Release Inventarsystem / release-docker (push) Successful in 3m6s

This commit is contained in:
2026-08-22 15:14:26 +02:00
parent 17b58ff7ca
commit 2cd95ef808
3 changed files with 512 additions and 2 deletions
+255 -2
View File
@@ -1588,8 +1588,7 @@ def update_appointment_statuses():
f"Failed to create activation notification for {appointment.get('_id')}: {notif_err}"
)
elif it.is_library_item(appointment.get('Item')):
# Introduction of an messaging system and a Mahnstufen implementation for the library Book bookings after the designatet time.
pass
create_return_reminders()
client.close()
@@ -1713,6 +1712,111 @@ 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):
@@ -3529,6 +3633,155 @@ def library_loans_admin():
if client:
client.close()
@app.route('/mahnungen_admin')
def mahnungen_admin():
"""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(datetime.timezone.utc)
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()
@app.route('/api/library_items')
def api_library_items():
"""