884 lines
32 KiB
Python
Executable File
884 lines
32 KiB
Python
Executable File
"""
|
|
Ausleihungssystem (Borrowing System)
|
|
====================================
|
|
|
|
Dieses Modul verwaltet sämtliche Ausleihungen im Inventarsystem.
|
|
Es bietet alle Funktionen, um Ausleihungen zu planen, zu aktivieren,
|
|
zu beenden und zu stornieren.
|
|
|
|
Hauptfunktionen:
|
|
- Erstellen neuer Ausleihungen (geplant oder sofort aktiv)
|
|
- Aktualisieren von Ausleihungsdaten
|
|
- Abschließen von Ausleihungen (Rückgabe)
|
|
- Suchen und Abrufen von Ausleihungen nach verschiedenen Kriterien
|
|
- Verwaltung des Ausleihungs-Lebenszyklus
|
|
|
|
Sammlungsstruktur:
|
|
- ausleihungen: Speichert alle Ausleihungsdatensätze mit ihrem Status
|
|
- Status-Werte: 'planned' (geplant), 'active' (aktiv), 'completed' (abgeschlossen), 'cancelled' (storniert)
|
|
"""
|
|
'''
|
|
Copyright 2025-2026 AIIrondev
|
|
|
|
Licensed under the Inventarsystem EULA (Endbenutzer-Lizenzvertrag).
|
|
See Legal/LICENSE for the full license text.
|
|
Unauthorized commercial use, SaaS hosting, or removal of branding is prohibited.
|
|
For commercial licensing inquiries: https://github.com/AIIrondev
|
|
'''
|
|
from bson.objectid import ObjectId, InvalidId
|
|
import datetime
|
|
from dateutil import parser
|
|
import os
|
|
import json
|
|
import Web.modules.database.settings as cfg
|
|
from Web.modules.database.settings import MongoClient
|
|
import Web.modules.inventarsystem.data_protection as dp
|
|
|
|
|
|
def _get_client():
|
|
"""Hilfsfunktion für die Erstellung einer MongoClient-Instanz."""
|
|
return MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
|
|
# Add this helper function after imports
|
|
def ensure_timezone_aware(dt):
|
|
"""Ensures a datetime is timezone-aware, using UTC if naive"""
|
|
if dt is None:
|
|
return None
|
|
if dt.tzinfo is None:
|
|
# Treat naive datetimes as UTC
|
|
return dt.replace(tzinfo=None)
|
|
return dt
|
|
|
|
def get_current_status(ausleihung, log_changes=False, user=None):
|
|
"""
|
|
Ermittelt den aktuellen Status einer Ausleihung basierend auf den Zeitstempeln
|
|
und dem gespeicherten Status. Diese Funktion berücksichtigt das aktuelle Datum
|
|
und aktualisiert den Status entsprechend dem realen Zustand.
|
|
|
|
Status-Werte:
|
|
- 'planned': Eine zukünftige Ausleihung, die noch nicht begonnen hat
|
|
- 'active': Eine aktive Ausleihung, die gerade läuft
|
|
- 'completed': Eine beendete Ausleihung
|
|
- 'cancelled': Eine stornierte Ausleihung
|
|
|
|
Args:
|
|
ausleihung (dict): Der Ausleihungsdatensatz
|
|
log_changes (bool): Ob Statusänderungen protokolliert werden sollen
|
|
user (str): Der Benutzer, der die Prüfung durchführt (für Logs)
|
|
|
|
Returns:
|
|
str: Der aktuelle Status ('planned', 'active', 'completed', 'cancelled')
|
|
"""
|
|
# Speichern Sie den ursprünglichen Status für Logging-Zwecke
|
|
original_status = ausleihung.get('Status', 'unknown')
|
|
|
|
# Bei stornierten Ausleihungen bleibt der Status immer storniert
|
|
if original_status == 'cancelled':
|
|
return 'cancelled'
|
|
|
|
# Wenn die Ausleihung bereits abgeschlossen ist, bleibt sie es
|
|
if original_status == 'completed':
|
|
return 'completed'
|
|
|
|
current_time = datetime.datetime.now()
|
|
start_time = ausleihung.get('Start')
|
|
end_time = ausleihung.get('End')
|
|
|
|
# Wenn kein Startdatum vorhanden ist, Status auf 'planned' setzen
|
|
if not start_time:
|
|
new_status = 'planned'
|
|
# Wenn die aktuelle Zeit vor dem Startdatum liegt, ist die Ausleihung geplant
|
|
elif current_time < start_time:
|
|
# DEBUG: Log info wenn Booking noch lange in der Zukunft ist
|
|
time_until_start = (start_time - current_time).total_seconds()
|
|
if time_until_start > 3600: # Mehr als 1 Stunde entfernt
|
|
ausleihung_id = str(ausleihung.get('_id', 'unknown'))[:12]
|
|
print(f"[DEBUG] Ausleihe {ausleihung_id} startet in {time_until_start/3600:.1f} Stunden ({start_time}), noch geplant")
|
|
new_status = 'planned'
|
|
# Wenn kein Enddatum gesetzt ist oder die aktuelle Zeit vor dem Enddatum liegt,
|
|
# ist die Ausleihung aktiv
|
|
elif not end_time or current_time < end_time:
|
|
new_status = 'active'
|
|
# Wenn die aktuelle Zeit nach dem Enddatum liegt, ist die Ausleihung beendet
|
|
else:
|
|
new_status = 'completed'
|
|
|
|
# Protokollieren Sie Statusänderungen, wenn aktiviert und eine Änderung stattgefunden hat
|
|
if log_changes and new_status != original_status and '_id' in ausleihung:
|
|
try:
|
|
# Importieren Sie das Modul nur bei Bedarf, um zirkuläre Importe zu vermeiden
|
|
import Web.modules.log.ausleihung_log as ausleihung_log
|
|
ausleihung_log.log_status_change(
|
|
str(ausleihung['_id']),
|
|
original_status,
|
|
new_status,
|
|
dp.encrypt_text(user)
|
|
)
|
|
except Exception as e:
|
|
print(f"Fehler beim Protokollieren der Statusänderung: {e}")
|
|
|
|
return new_status
|
|
|
|
def create_backup_database():
|
|
"""
|
|
Erstellt eine Sicherungskopie der Ausleihungsdatenbank.
|
|
|
|
Returns:
|
|
bool: True wenn Backup erfolgreich erstellt wurde, sonst False
|
|
"""
|
|
try:
|
|
# Verbindung zur Datenbank herstellen
|
|
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
|
|
db = client[cfg.MONGODB_DB]
|
|
ausleihungen = db['ausleihungen']
|
|
|
|
# Backup-Verzeichnis erstellen, falls es nicht existiert
|
|
backup_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'backups')
|
|
if not os.path.exists(backup_dir):
|
|
os.makedirs(backup_dir)
|
|
|
|
# Aktuelles Datum für den Dateinamen
|
|
current_date = datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
|
|
backup_file = os.path.join(backup_dir, f'ausleihungen_backup_{current_date}.json')
|
|
|
|
# Ausleihungen abrufen und als JSON speichern
|
|
all_ausleihungen = list(ausleihungen.find({}))
|
|
|
|
# ObjectId in String umwandeln für JSON-Serialisierung
|
|
for ausleihung in all_ausleihungen:
|
|
ausleihung['_id'] = str(ausleihung['_id'])
|
|
if 'Start' in ausleihung and ausleihung['Start']:
|
|
ausleihung['Start'] = ausleihung['Start'].isoformat()
|
|
if 'End' in ausleihung and ausleihung['End']:
|
|
ausleihung['End'] = ausleihung['End'].isoformat()
|
|
if 'LastUpdated' in ausleihung and ausleihung['LastUpdated']:
|
|
ausleihung['LastUpdated'] = ausleihung['LastUpdated'].isoformat()
|
|
|
|
with open(backup_file, 'w', encoding='utf-8') as f:
|
|
json.dump(all_ausleihungen, f, ensure_ascii=False, indent=4)
|
|
|
|
# Log-Eintrag erstellen
|
|
log_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'logs')
|
|
if not os.path.exists(log_dir):
|
|
os.makedirs(log_dir)
|
|
|
|
log_file = os.path.join(log_dir, 'ausleihungen_backup.log')
|
|
with open(log_file, 'a', encoding='utf-8') as f:
|
|
f.write(f"{current_date}: Backup erstellt: {backup_file}, {len(all_ausleihungen)} Einträge\n")
|
|
|
|
client.close()
|
|
return True
|
|
except Exception as e:
|
|
# Fehler protokollieren
|
|
log_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'logs')
|
|
if not os.path.exists(log_dir):
|
|
os.makedirs(log_dir)
|
|
|
|
log_file = os.path.join(log_dir, 'ausleihungen_error.log')
|
|
with open(log_file, 'a', encoding='utf-8') as f:
|
|
f.write(f"{datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}: Backup-Fehler: {str(e)}\n")
|
|
|
|
print(f"Fehler beim Erstellen des Backups: {e}")
|
|
return False
|
|
|
|
|
|
# === AUSLEIHUNG MANAGEMENT ===
|
|
|
|
def add_ausleihung(item_id, user, start_date, end_date=None, notes="", status="active", period=None, exemplar_data=None):
|
|
"""
|
|
Add a new borrowing record for an item.
|
|
|
|
Args:
|
|
item_id (str): ID of the item borrowed
|
|
user (str): Username of the borrower
|
|
start_date (datetime): Start date and time of the borrowing
|
|
end_date (datetime, optional): End date and time if already returned
|
|
notes (str, optional): Additional notes about the borrowing
|
|
status (str, optional): Status of the borrowing (active, completed, planned)
|
|
period (str, optional): School period for the borrowing
|
|
exemplar_data (dict, optional): Information about specific exemplar borrowed
|
|
|
|
Returns:
|
|
ObjectId: ID of the new borrowing record or None if failed
|
|
"""
|
|
try:
|
|
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
|
|
db = client[cfg.MONGODB_DB]
|
|
ausleihungen = db['ausleihungen']
|
|
|
|
ausleihung = {
|
|
'Item': item_id,
|
|
'User': dp.encrypt_text(user),
|
|
'Start': start_date,
|
|
'Status': status
|
|
}
|
|
|
|
if end_date:
|
|
ausleihung['End'] = end_date
|
|
|
|
if notes:
|
|
ausleihung['Notes'] = notes
|
|
|
|
if period:
|
|
ausleihung['Period'] = period
|
|
|
|
if exemplar_data:
|
|
ausleihung['ExemplarData'] = exemplar_data
|
|
|
|
result = ausleihungen.insert_one(ausleihung)
|
|
ausleihung_id = result.inserted_id
|
|
|
|
client.close()
|
|
return ausleihung_id
|
|
except Exception as e:
|
|
print(f"Error adding ausleihung: {e}")
|
|
return None
|
|
|
|
def update_ausleihung(id, item_id=None, user_id=None, start=None, end=None, notes=None, status=None, period=None):
|
|
try:
|
|
# ID-Validierung
|
|
try:
|
|
doc_id = ObjectId(id) if isinstance(id, str) else id
|
|
except InvalidId:
|
|
print(f"Ungültiges ObjectId-Format: {id}")
|
|
return False
|
|
|
|
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
|
|
db = client[cfg.MONGODB_DB]
|
|
ausleihungen = db['ausleihungen']
|
|
|
|
update_data = {}
|
|
|
|
if item_id is not None:
|
|
update_data['Item'] = item_id
|
|
if user_id is not None:
|
|
# WICHTIG: Gleiche Verschlüsselung wie in add_ausleihung nutzen!
|
|
update_data['User'] = dp.encrypt_text(user_id) if hasattr(dp, 'encrypt_text') else user_id
|
|
if start is not None:
|
|
update_data['Start'] = ensure_timezone_aware(start)
|
|
if end is not None:
|
|
update_data['End'] = ensure_timezone_aware(end)
|
|
if notes is not None:
|
|
update_data['Notes'] = notes
|
|
if status is not None:
|
|
update_data['Status'] = status
|
|
if period is not None:
|
|
update_data['Period'] = period
|
|
|
|
# Keine leeren Updates ausführen (falls nur None-Parameter übergeben wurden)
|
|
if not update_data:
|
|
client.close()
|
|
return True
|
|
|
|
# UTC Zeitstempel nutzen
|
|
update_data['LastUpdated'] = datetime.datetime.now(datetime.timezone.utc)
|
|
|
|
result = ausleihungen.update_one(
|
|
{'_id': doc_id},
|
|
{'$set': update_data}
|
|
)
|
|
|
|
client.close()
|
|
|
|
# Prüfen ob das Dokument überhaupt gefunden wurde (matched_count)
|
|
return result.matched_count > 0
|
|
|
|
except Exception as e:
|
|
print(f"Error updating ausleihung: {e}")
|
|
return False
|
|
|
|
def complete_ausleihung(id, end_time=None):
|
|
"""
|
|
Markiert eine Ausleihe als abgeschlossen, indem das Enddatum gesetzt
|
|
und der Status auf 'completed' geändert wird.
|
|
|
|
Args:
|
|
id (str): ID des abzuschließenden Ausleihungsdatensatzes
|
|
end_time (datetime, optional): Endzeitpunkt (Standard: aktuelle Zeit)
|
|
|
|
Returns:
|
|
bool: True bei Erfolg, sonst False
|
|
"""
|
|
try:
|
|
if end_time is None:
|
|
end_time = datetime.datetime.now()
|
|
|
|
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
|
|
db = client[cfg.MONGODB_DB]
|
|
ausleihungen = db['ausleihungen']
|
|
item = db['items']
|
|
|
|
result = ausleihungen.update_one(
|
|
{'_id': ObjectId(id)},
|
|
{'$set': {
|
|
'End': end_time,
|
|
'Status': 'completed',
|
|
'LastUpdated': datetime.datetime.now()
|
|
}}
|
|
)
|
|
|
|
item.update_one(
|
|
{'_id': ObjectId(id)},
|
|
{'$set': {
|
|
'Verfuegbar': True,
|
|
'LastUpdated': datetime.datetime.now()
|
|
}}
|
|
)
|
|
|
|
client.close()
|
|
return result.modified_count > 0
|
|
except Exception as e:
|
|
# print(f"Error completing ausleihung: {e}") # Log the error
|
|
return False
|
|
|
|
|
|
def cancel_ausleihung(id):
|
|
"""
|
|
Storniert eine geplante Ausleihe durch Änderung des Status auf 'cancelled'.
|
|
|
|
Args:
|
|
id (str): ID der zu stornierenden Ausleihe
|
|
|
|
Returns:
|
|
bool: True bei Erfolg, sonst False
|
|
"""
|
|
try:
|
|
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
|
|
db = client[cfg.MONGODB_DB]
|
|
ausleihungen = db['ausleihungen']
|
|
|
|
# Mark the booking as cancelled
|
|
result = ausleihungen.update_one(
|
|
{'_id': ObjectId(id)},
|
|
{'$set': {
|
|
'Status': 'cancelled',
|
|
'LastUpdated': datetime.datetime.now()
|
|
}}
|
|
)
|
|
|
|
client.close()
|
|
return result.modified_count > 0
|
|
except Exception as e:
|
|
# print(f"Error cancelling ausleihung: {e}") # Log the error
|
|
return False
|
|
|
|
|
|
def remove_ausleihung(id):
|
|
"""
|
|
Markiert einen Ausleihungsdatensatz als gelöscht (Soft-Delete).
|
|
|
|
Args:
|
|
id (str): ID des zu entfernenden Ausleihungsdatensatzes
|
|
|
|
Returns:
|
|
bool: True bei Erfolg, sonst False
|
|
"""
|
|
try:
|
|
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
|
|
db = client[cfg.MONGODB_DB]
|
|
ausleihungen = db['ausleihungen']
|
|
now = datetime.datetime.now()
|
|
result = ausleihungen.update_one(
|
|
{'_id': ObjectId(id), 'Status': {'$ne': 'deleted'}},
|
|
{'$set': {
|
|
'Status': 'deleted',
|
|
'DeletedAt': now,
|
|
'LastUpdated': now,
|
|
}}
|
|
)
|
|
client.close()
|
|
return result.modified_count > 0
|
|
except Exception as e:
|
|
# print(f"Error removing ausleihung: {e}") # Log the error
|
|
return False
|
|
|
|
|
|
# === AUSLEIHUNG RETRIEVAL ===
|
|
|
|
def get_ausleihung(id):
|
|
"""
|
|
Ruft einen bestimmten Ausleihungsdatensatz anhand seiner ID ab.
|
|
|
|
Args:
|
|
id (str): ID des abzurufenden Ausleihungsdatensatzes
|
|
|
|
Returns:
|
|
dict: Der Ausleihungsdatensatz oder None, wenn nicht gefunden
|
|
"""
|
|
try:
|
|
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
|
|
db = client[cfg.MONGODB_DB]
|
|
ausleihungen = db['ausleihungen']
|
|
ausleihung = ausleihungen.find_one({'_id': ObjectId(id)})
|
|
client.close()
|
|
return ausleihung
|
|
except Exception as e:
|
|
# print(f"Error retrieving ausleihung: {e}") # Log the error
|
|
return None
|
|
|
|
|
|
def get_ausleihungen(status=None, start=None, end=None, date_filter='overlap'):
|
|
try:
|
|
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
|
|
db = client[cfg.MONGODB_DB]
|
|
collection = db['ausleihungen']
|
|
|
|
query = {'Status': {'$ne': 'deleted'}}
|
|
|
|
# Status-Filter
|
|
if status is not None:
|
|
if isinstance(status, list):
|
|
allowed_status = [s for s in status if s != 'deleted']
|
|
query['Status'] = {'$in': allowed_status}
|
|
else:
|
|
query['Status'] = status if status != 'deleted' else '__blocked_deleted_status__'
|
|
|
|
# Datums-Parsing
|
|
if isinstance(start, str):
|
|
try:
|
|
start = parser.parse(start)
|
|
except Exception:
|
|
start = None
|
|
|
|
if isinstance(end, str):
|
|
try:
|
|
end = parser.parse(end)
|
|
except Exception:
|
|
end = None
|
|
|
|
# Datumsfilter-Logik
|
|
if start is not None and end is not None:
|
|
if date_filter == 'overlap':
|
|
query['$and'] = [
|
|
{'Start': {'$lte': end}},
|
|
{'$or': [
|
|
{'End': {'$gte': start}},
|
|
{'End': None}
|
|
]}
|
|
]
|
|
elif date_filter == 'start_in':
|
|
query['Start'] = {'$gte': start, '$lte': end}
|
|
elif date_filter == 'end_in':
|
|
query['End'] = {'$gte': start, '$lte': end}
|
|
elif date_filter == 'contained':
|
|
query['Start'] = {'$gte': start}
|
|
query['End'] = {'$exists': True, '$lte': end}
|
|
elif start is not None:
|
|
# Nur Startdatum angegeben -> alles was ab/nach start aktiv ist
|
|
query['$or'] = [{'End': {'$gte': start}}, {'End': None}]
|
|
elif end is not None:
|
|
# Nur Enddatum angegeben -> alles was vor end begonnen hat
|
|
query['Start'] = {'$lte': end}
|
|
|
|
results = list(collection.find(query))
|
|
client.close()
|
|
return results
|
|
except Exception as e:
|
|
# Sinnvolles Logging einbauen
|
|
return []
|
|
|
|
def get_active_ausleihungen(start=None, end=None):
|
|
"""
|
|
Ruft alle aktiven (laufenden) Ausleihungen ab.
|
|
|
|
Args:
|
|
start (str/datetime, optional): Startdatum für Datumsfilterung
|
|
end (str/datetime, optional): Enddatum für Datumsfilterung
|
|
|
|
Returns:
|
|
list: Liste aktiver Ausleihungsdatensätze
|
|
"""
|
|
return get_ausleihungen(status='active', start=start, end=end)
|
|
|
|
|
|
def get_planned_ausleihungen(start=None, end=None):
|
|
"""
|
|
Ruft alle geplanten Ausleihungen (Reservierungen) ab.
|
|
|
|
Args:
|
|
start (str/datetime, optional): Startdatum für Datumsfilterung
|
|
end (str/datetime, optional): Enddatum für Datumsfilterung
|
|
|
|
Returns:
|
|
list: Liste geplanter Ausleihungsdatensätze
|
|
"""
|
|
return get_ausleihungen(status='planned', start=start, end=end)
|
|
|
|
|
|
def get_completed_ausleihungen(start=None, end=None):
|
|
"""
|
|
Ruft alle abgeschlossenen Ausleihungen ab.
|
|
|
|
Args:
|
|
start (str/datetime, optional): Startdatum für Datumsfilterung
|
|
end (str/datetime, optional): Enddatum für Datumsfilterung
|
|
|
|
Returns:
|
|
list: Liste abgeschlossener Ausleihungsdatensätze
|
|
"""
|
|
return get_ausleihungen(status='completed', start=start, end=end)
|
|
|
|
|
|
def get_cancelled_ausleihungen(start=None, end=None):
|
|
"""
|
|
Ruft alle stornierten Ausleihungen ab.
|
|
|
|
Args:
|
|
start (str/datetime, optional): Startdatum für Datumsfilterung
|
|
end (str/datetime, optional): Enddatum für Datumsfilterung
|
|
|
|
Returns:
|
|
list: Liste stornierter Ausleihungsdatensätze
|
|
"""
|
|
return get_ausleihungen(status='cancelled', start=start, end=end)
|
|
|
|
|
|
# === SEARCH FUNCTIONS ===
|
|
|
|
def get_ausleihung_by_user(user_id, status=None, use_client_side_verification=True):
|
|
"""Ruft Ausleihungen für einen bestimmten Benutzer ab."""
|
|
try:
|
|
with _get_client() as client:
|
|
db = client[cfg.MONGODB_DB]
|
|
ausleihungen = db['ausleihungen']
|
|
|
|
# Ensure proper encrypted query string if user IDs are encrypted in DB
|
|
search_user = dp.encrypt_text(user_id) if hasattr(dp, 'encrypt_text') else user_id
|
|
query = {'User': search_user, 'Status': {'$ne': 'deleted'}}
|
|
|
|
if not use_client_side_verification:
|
|
if status is not None:
|
|
if isinstance(status, list):
|
|
query['Status'] = {'$in': status}
|
|
else:
|
|
query['Status'] = status
|
|
else:
|
|
query['Status'] = {'$ne': 'cancelled'}
|
|
return list(ausleihungen.find(query))
|
|
|
|
all_ausleihungen = list(ausleihungen.find(query))
|
|
|
|
for record in all_ausleihungen:
|
|
current_status = get_current_status(record) if 'get_current_status' in globals() else record.get(
|
|
'Status')
|
|
record['VerifiedStatus'] = current_status
|
|
|
|
if status is None:
|
|
return all_ausleihungen
|
|
|
|
filtered_results = []
|
|
target_statuses = status if isinstance(status, list) else [status]
|
|
|
|
for record in all_ausleihungen:
|
|
v_status = record.get('VerifiedStatus')
|
|
if v_status in target_statuses and v_status != 'deleted':
|
|
filtered_results.append(record)
|
|
|
|
return filtered_results
|
|
except Exception as e:
|
|
print(f"Error retrieving ausleihungen for user {user_id}: {e}")
|
|
return []
|
|
|
|
|
|
def get_ausleihung_by_item(item_id, status=None, include_history=False):
|
|
"""Get most recent ausleihung record for a specific item."""
|
|
try:
|
|
with _get_client() as client:
|
|
db = client[cfg.MONGODB_DB]
|
|
ausleihungen = db['ausleihungen']
|
|
|
|
query = {'Item': item_id, 'Status': {'$ne': 'deleted'}}
|
|
if status and not include_history:
|
|
query['Status'] = status
|
|
|
|
record = ausleihungen.find_one(query, sort=[('Start', -1)])
|
|
if record:
|
|
record['_id'] = str(record['_id'])
|
|
return record
|
|
except Exception as e:
|
|
print(f"Error getting ausleihung by item: {e}")
|
|
return None
|
|
|
|
|
|
def get_ausleihungen_by_date_range(start_date, end_date, status=None):
|
|
"""Ruft Ausleihungen ab, die in einem bestimmten Zeitraum aktiv waren."""
|
|
return get_ausleihungen(status=status, start=start_date, end=end_date)
|
|
|
|
|
|
def check_ausleihung_conflict(item_id, start_date, end_date, period=None):
|
|
"""Prüft, ob es Konflikte mit bestehenden Ausleihungen oder aktiven Ausleihen gibt."""
|
|
try:
|
|
start_date = ensure_timezone_aware(start_date)
|
|
end_date = ensure_timezone_aware(end_date)
|
|
|
|
with _get_client() as client:
|
|
db = client[cfg.MONGODB_DB]
|
|
ausleihungen = db['ausleihungen']
|
|
|
|
booking_date = start_date.date()
|
|
all_bookings = list(ausleihungen.find({
|
|
'Item': item_id,
|
|
'Status': {'$in': ['planned', 'active']}
|
|
}))
|
|
|
|
if period is not None:
|
|
period_int = int(period)
|
|
for booking in all_bookings:
|
|
booking_start = ensure_timezone_aware(booking.get('Start'))
|
|
if booking_start and booking_start.date() == booking_date:
|
|
booking_period = booking.get('Period')
|
|
if booking_period is not None and int(booking_period) == period_int:
|
|
return True
|
|
|
|
for booking in all_bookings:
|
|
b_start = ensure_timezone_aware(booking.get('Start'))
|
|
if not b_start:
|
|
continue
|
|
b_end = ensure_timezone_aware(booking.get('End')) or (b_start + datetime.timedelta(hours=1))
|
|
|
|
# Simplified and standard time range collision check
|
|
if start_date < b_end and end_date > b_start:
|
|
return True
|
|
|
|
return False
|
|
except Exception as e:
|
|
print(f"Error checking booking conflicts: {e}")
|
|
return True # Return conflict on exception for safety
|
|
|
|
|
|
def check_booking_period_range_conflict(item_id, start_date, end_date, period=None, period_end=None):
|
|
"""Checks for conflicts with existing bookings, supporting period ranges."""
|
|
try:
|
|
start_date = ensure_timezone_aware(start_date)
|
|
end_date = ensure_timezone_aware(end_date)
|
|
|
|
with _get_client() as client:
|
|
db = client[cfg.MONGODB_DB]
|
|
ausleihungen = db['ausleihungen']
|
|
|
|
booking_date = start_date.date()
|
|
all_bookings = list(ausleihungen.find({
|
|
'Item': item_id,
|
|
'Status': {'$in': ['planned', 'active']}
|
|
}))
|
|
|
|
if period is not None:
|
|
period_start = int(period)
|
|
periods_to_check = [period_start]
|
|
if period_end is not None:
|
|
periods_to_check = list(range(period_start, int(period_end) + 1))
|
|
|
|
for booking in all_bookings:
|
|
b_start = ensure_timezone_aware(booking.get('Start'))
|
|
if b_start and b_start.date() == booking_date:
|
|
booking_period = booking.get('Period')
|
|
try:
|
|
booking_period_int = int(booking_period) if booking_period is not None else None
|
|
except Exception:
|
|
booking_period_int = None
|
|
|
|
if booking_period_int in periods_to_check:
|
|
return True
|
|
|
|
for booking in all_bookings:
|
|
b_start = ensure_timezone_aware(booking.get('Start'))
|
|
if not b_start:
|
|
continue
|
|
b_end = ensure_timezone_aware(booking.get('End')) or (b_start + datetime.timedelta(hours=1))
|
|
|
|
if start_date < b_end and end_date > b_start:
|
|
return True
|
|
|
|
return False
|
|
except Exception as e:
|
|
print(f"Error checking booking range conflicts: {e}")
|
|
return True
|
|
|
|
|
|
def get_ausleihungen_starting_now(current_time):
|
|
"""Ruft Ausleihungen ab, die jetzt beginnen sollen."""
|
|
try:
|
|
current_time = ensure_timezone_aware(current_time)
|
|
start_time = current_time - datetime.timedelta(hours=3)
|
|
end_time = current_time + datetime.timedelta(hours=1)
|
|
today = current_time.date()
|
|
|
|
with _get_client() as client:
|
|
db = client[cfg.MONGODB_DB]
|
|
ausleihungen = db['ausleihungen']
|
|
|
|
query = {
|
|
'Status': 'planned',
|
|
'$or': [
|
|
{'Start': {'$lte': end_time, '$gte': start_time}},
|
|
{
|
|
'Period': {'$exists': True},
|
|
'Start': {
|
|
'$gte': datetime.datetime.combine(today, datetime.time.min, tzinfo=datetime.timezone.utc),
|
|
'$lt': datetime.datetime.combine(today + datetime.timedelta(days=1), datetime.time.min,
|
|
tzinfo=datetime.timezone.utc)
|
|
}
|
|
}
|
|
]
|
|
}
|
|
return list(ausleihungen.find(query))
|
|
except Exception as e:
|
|
print(f"Error in get_ausleihungen_starting_now: {e}")
|
|
return []
|
|
|
|
|
|
def get_ausleihungen_ending_now(current_time):
|
|
"""Ruft Ausleihungen ab, die jetzt enden sollen."""
|
|
try:
|
|
current_time = ensure_timezone_aware(current_time)
|
|
start_time = current_time - datetime.timedelta(minutes=15)
|
|
end_time = current_time + datetime.timedelta(minutes=5)
|
|
today = current_time.date()
|
|
|
|
with _get_client() as client:
|
|
db = client[cfg.MONGODB_DB]
|
|
ausleihungen = db['ausleihungen']
|
|
|
|
query = {
|
|
'Status': 'active',
|
|
'$or': [
|
|
{'End': {'$gte': start_time, '$lte': end_time}},
|
|
{
|
|
'Period': {'$exists': True},
|
|
'Start': {
|
|
'$gte': datetime.datetime.combine(today, datetime.time.min, tzinfo=datetime.timezone.utc),
|
|
'$lt': datetime.datetime.combine(today + datetime.timedelta(days=1), datetime.time.min,
|
|
tzinfo=datetime.timezone.utc)
|
|
}
|
|
}
|
|
]
|
|
}
|
|
return list(ausleihungen.find(query))
|
|
except Exception as e:
|
|
print(f"Error in get_ausleihungen_ending_now: {e}")
|
|
return []
|
|
|
|
|
|
def activate_ausleihung(id):
|
|
"""Aktiviert eine geplante Ausleihe."""
|
|
try:
|
|
doc_id = ObjectId(id) if isinstance(id, str) else id
|
|
with _get_client() as client:
|
|
db = client[cfg.MONGODB_DB]
|
|
ausleihungen = db['ausleihungen']
|
|
|
|
result = ausleihungen.update_one(
|
|
{'_id': doc_id, 'Status': 'planned'},
|
|
{'$set': {
|
|
'Status': 'active',
|
|
'LastUpdated': datetime.datetime.now(datetime.timezone.utc)
|
|
}}
|
|
)
|
|
return result.modified_count > 0
|
|
except Exception as e:
|
|
print(f"Error activating ausleihung: {e}")
|
|
return False
|
|
|
|
|
|
def reset_item_completely(item_id):
|
|
"""Setzt den Ausleihstatus eines Items vollständig zurück."""
|
|
try:
|
|
doc_id = ObjectId(item_id) if isinstance(item_id, str) else item_id
|
|
|
|
with _get_client() as client:
|
|
db = client[cfg.MONGODB_DB]
|
|
items_collection = db['items']
|
|
ausleihungen_collection = db['ausleihungen']
|
|
|
|
item = items_collection.find_one({'_id': doc_id})
|
|
if not item:
|
|
return {'success': False, 'message': 'Item nicht gefunden'}
|
|
|
|
item_name = item.get('Name', 'Unbekannt')
|
|
now_utc = datetime.datetime.now(datetime.timezone.utc)
|
|
|
|
# 1. Bulk update active borrowings
|
|
update_res = ausleihungen_collection.update_many(
|
|
{
|
|
'Item': str(item_id),
|
|
'Status': {'$in': ['active', 'planned']}
|
|
},
|
|
{
|
|
'$set': {
|
|
'Status': 'completed',
|
|
'End': now_utc,
|
|
'LastUpdated': now_utc,
|
|
'CompletedBy': 'System Reset'
|
|
}
|
|
}
|
|
)
|
|
completed_count = update_res.modified_count
|
|
|
|
# 2. Reset Item document with valid PyMongo update query format
|
|
update_query = {
|
|
'$set': {
|
|
'Verfuegbar': True,
|
|
'LastUpdated': now_utc
|
|
}
|
|
}
|
|
|
|
unsets = {}
|
|
if 'User' in item:
|
|
unsets['User'] = ''
|
|
if 'BorrowerInfo' in item:
|
|
unsets['BorrowerInfo'] = ''
|
|
if 'ExemplareStatus' in item:
|
|
unsets['ExemplareStatus'] = ''
|
|
|
|
if unsets:
|
|
update_query['$unset'] = unsets
|
|
|
|
result = items_collection.update_one({'_id': doc_id}, update_query)
|
|
|
|
return {
|
|
'success': True,
|
|
'message': f'Item "{item_name}" wurde erfolgreich zurückgesetzt',
|
|
'details': {
|
|
'completed_borrowings': completed_count,
|
|
'item_reset': result.modified_count > 0
|
|
}
|
|
}
|
|
except Exception as e:
|
|
print(f"Error resetting item: {e}")
|
|
return {'success': False, 'message': f'Fehler beim Zurücksetzen: {e}'}
|
|
|
|
|
|
# === KOMPATIBILITÄTSFUNKTIONEN ===
|
|
|
|
def add_planned_booking(item_id, user, start_date, end_date, notes="", period=None):
|
|
return add_ausleihung(item_id, user, start_date, end_date, notes, status='planned', period=period)
|
|
|
|
|
|
def check_booking_conflict(item_id, start_date, end_date, period=None):
|
|
return check_ausleihung_conflict(item_id, start_date, end_date, period)
|
|
|
|
|
|
def mark_booking_active(booking_id, ausleihung_id=None):
|
|
try:
|
|
doc_id = ObjectId(booking_id) if isinstance(booking_id, str) else booking_id
|
|
update_data = {
|
|
'Status': 'active',
|
|
'LastUpdated': datetime.datetime.now(datetime.timezone.utc)
|
|
}
|
|
if ausleihung_id:
|
|
update_data['AusleihungId'] = ausleihung_id
|
|
|
|
with _get_client() as client:
|
|
db = client[cfg.MONGODB_DB]
|
|
res = db['ausleihungen'].update_one({'_id': doc_id}, {'$set': update_data})
|
|
return res.matched_count > 0
|
|
except Exception as e:
|
|
print(f"Error activating booking: {e}")
|
|
return activate_ausleihung(booking_id)
|
|
|
|
|
|
def get_bookings_starting_now(current_time):
|
|
return get_ausleihungen_starting_now(current_time)
|
|
|
|
|
|
def get_bookings_ending_now(current_time):
|
|
return get_ausleihungen_ending_now(current_time) |