impolementation of standardised time zone
Release Inventarsystem / release-docker (push) Successful in 2m17s

This commit is contained in:
2026-08-23 11:28:53 +02:00
parent 6f9994c4ce
commit 65fadf7f5f
+380 -150
View File
@@ -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