|
|
|
@@ -49,6 +49,7 @@ import Web.modules.log.audit_log as al
|
|
|
|
|
import push_notifications as pn
|
|
|
|
|
import Web.modules.inventarsystem.pdf_export as pdf_export
|
|
|
|
|
import Web.modules.inventarsystem.excel_export as excel_export
|
|
|
|
|
from Web.modules.emailservice.email import send
|
|
|
|
|
import datetime
|
|
|
|
|
from apscheduler.schedulers.background import BackgroundScheduler
|
|
|
|
|
from bson.objectid import ObjectId, InvalidId
|
|
|
|
@@ -94,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
|
|
|
|
@@ -198,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:
|
|
|
|
@@ -268,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):
|
|
|
|
@@ -1024,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', '-'),
|
|
|
|
@@ -1047,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,
|
|
|
|
@@ -1277,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
|
|
|
|
@@ -1455,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'
|
|
|
|
@@ -1588,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()
|
|
|
|
|
|
|
|
|
@@ -1620,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:
|
|
|
|
@@ -1664,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:
|
|
|
|
@@ -1713,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):
|
|
|
|
@@ -2500,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
|
|
|
|
@@ -3634,6 +3630,279 @@ 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
|
|
|
|
|
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",
|
|
|
|
|
"item_name": "Biologie heute 2",
|
|
|
|
|
"due_date": "10.08.2026",
|
|
|
|
|
"days_overdue": 12,
|
|
|
|
|
"mahnstufe": 2,
|
|
|
|
|
"is_blocked": True
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"user": "Anna Schmidt",
|
|
|
|
|
"klasse": "9B",
|
|
|
|
|
"item_name": "Taschenrechner TI-30",
|
|
|
|
|
"due_date": "15.08.2026",
|
|
|
|
|
"days_overdue": 7,
|
|
|
|
|
"mahnstufe": 1,
|
|
|
|
|
"is_blocked": False
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"user": "Lukas Weber",
|
|
|
|
|
"klasse": "12",
|
|
|
|
|
"item_name": "Faust - Der Tragödie erster Teil",
|
|
|
|
|
"due_date": "21.08.2026",
|
|
|
|
|
"days_overdue": 1,
|
|
|
|
|
"mahnstufe": 0,
|
|
|
|
|
"is_blocked": False
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
# Render das Template und übergebe die Testdaten
|
|
|
|
|
return render_template('mahnungen_admin.html', overdue_list=test_overdue_list, APP_VERSION="1.0.0")"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.route('/mahnungen_admin')
|
|
|
|
|
def mahnungen_admin():
|
|
|
|
|
"""Admin overview for overdue library items (Mahnungen)."""
|
|
|
|
@@ -3670,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:
|
|
|
|
@@ -4081,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
|
|
|
|
@@ -4104,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',
|
|
|
|
@@ -4368,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:
|
|
|
|
@@ -4562,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
|
|
|
|
|
}}
|
|
|
|
|
)
|
|
|
|
@@ -4601,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')
|
|
|
|
@@ -4658,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"))
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -4692,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')
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
@@ -4888,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}")
|
|
|
|
@@ -5074,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,
|
|
|
|
@@ -6357,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()
|
|
|
|
|
|
|
|
|
@@ -6878,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'),
|
|
|
|
@@ -6953,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'],
|
|
|
|
@@ -7057,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,
|
|
|
|
@@ -7234,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)
|
|
|
|
@@ -7323,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
|
|
|
|
@@ -7384,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',
|
|
|
|
@@ -7432,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={
|
|
|
|
@@ -7494,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
|
|
|
|
@@ -7506,7 +7774,7 @@ def zurueckgeben(id):
|
|
|
|
|
{'$set': {
|
|
|
|
|
'Status': 'completed',
|
|
|
|
|
'End': end_date,
|
|
|
|
|
'LastUpdated': datetime.datetime.now()
|
|
|
|
|
'LastUpdated': datetime.datetime.now(ZoneInfo("Europe/Berlin"))
|
|
|
|
|
}}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
@@ -7733,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})
|
|
|
|
|
|
|
|
|
@@ -8426,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(
|
|
|
|
@@ -8868,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
|
|
|
|
@@ -8966,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'):
|
|
|
|
@@ -9135,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']},
|
|
|
|
|
{
|
|
|
|
@@ -9214,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:
|
|
|
|
@@ -9284,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 = {
|
|
|
|
@@ -9421,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 = {
|
|
|
|
@@ -9511,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': "",
|
|
|
|
@@ -10022,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
|
|
|
|
@@ -11136,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:
|
|
|
|
@@ -11776,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
|
|
|
|
|