diff --git a/Web/modules/database/ausleihung.py b/Web/modules/database/ausleihung.py index 4845850..a2b18af 100755 --- a/Web/modules/database/ausleihung.py +++ b/Web/modules/database/ausleihung.py @@ -25,8 +25,9 @@ Sammlungsstruktur: Unauthorized commercial use, SaaS hosting, or removal of branding is prohibited. For commercial licensing inquiries: https://github.com/AIIrondev ''' -from bson.objectid import ObjectId +from bson.objectid import ObjectId, InvalidId import datetime +from dateutil import parser import pytz from datetime import timezone import os @@ -34,6 +35,7 @@ import json import shutil import Web.modules.database.settings as cfg from Web.modules.database.settings import MongoClient +import Web.modules.inventarsystem.data_protection as dp # Add this helper function after imports def ensure_timezone_aware(dt): @@ -108,7 +110,7 @@ def get_current_status(ausleihung, log_changes=False, user=None): str(ausleihung['_id']), original_status, new_status, - user + dp.encrypt_text(user) ) except Exception as e: print(f"Fehler beim Protokollieren der Statusänderung: {e}") @@ -204,7 +206,7 @@ def add_ausleihung(item_id, user, start_date, end_date=None, notes="", status="a ausleihung = { 'Item': item_id, - 'User': user, + 'User': dp.encrypt_text(user), 'Start': start_date, 'Status': status } @@ -231,39 +233,28 @@ def add_ausleihung(item_id, user, start_date, end_date=None, notes="", status="a return None def update_ausleihung(id, item_id=None, user_id=None, start=None, end=None, notes=None, status=None, period=None): - """ - Update an existing ausleihung record. - - Args: - id (str): ID of the ausleihung to update - item_id (str, optional): New item ID - user_id (str, optional): New user ID - start (datetime, optional): New start time - end (datetime, optional): New end time - notes (str, optional): New notes - status (str, optional): New status - period (int, optional): New period - - Returns: - bool: True if successful, False otherwise - """ 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'] - - # Build update data with only the fields that are provided + update_data = {} - + if item_id is not None: update_data['Item'] = item_id if user_id is not None: - update_data['User'] = user_id + # 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: - # Ensure timezone-aware datetime update_data['Start'] = ensure_timezone_aware(start) if end is not None: - # Ensure timezone-aware datetime update_data['End'] = ensure_timezone_aware(end) if notes is not None: update_data['Notes'] = notes @@ -271,22 +262,24 @@ def update_ausleihung(id, item_id=None, user_id=None, start=None, end=None, note update_data['Status'] = status if period is not None: update_data['Period'] = period - - # Always update the LastUpdated timestamp - update_data['LastUpdated'] = datetime.datetime.now() - - # Perform the update + + # 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': ObjectId(id)}, + {'_id': doc_id}, {'$set': update_data} ) - + client.close() - - # Log the update for debugging - print(f"Updated ausleihung {id}: modified_count={result.modified_count}, update_data={update_data}") - - return result.modified_count > 0 + + # 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}") @@ -423,82 +416,65 @@ def get_ausleihung(id): def get_ausleihungen(status=None, start=None, end=None, date_filter='overlap'): - """ - Ruft Ausleihungen nach verschiedenen Kriterien ab. - - Args: - status (str/list, optional): Status(se) der Ausleihungen ('planned', 'active', 'completed', 'cancelled') - start (str/datetime, optional): Startdatum für Datumsfilterung - end (str/datetime, optional): Enddatum für Datumsfilterung - date_filter (str, optional): Art des Datumsfilters ('overlap', 'start_in', 'end_in', 'contained') - - Returns: - list: Liste von Ausleihungsdatensätzen - """ try: client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) db = client[cfg.MONGODB_DB] collection = db['ausleihungen'] - - # Query erstellen + query = {'Status': {'$ne': 'deleted'}} - - # Status-Filter hinzufügen + + # 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__' - - # Datum parsen, wenn als String angegeben - if start is not None and isinstance(start, str): + + # Datums-Parsing + if isinstance(start, str): try: - from dateutil import parser start = parser.parse(start) - except: + except Exception: start = None - - if end is not None and isinstance(end, str): + + if isinstance(end, str): try: - from dateutil import parser end = parser.parse(end) - except: + except Exception: end = None - - # Datumsfilter hinzufügen + + # Datumsfilter-Logik if start is not None and end is not None: if date_filter == 'overlap': - # Überlappende Ausleihungen (Standard) - query['$or'] = [ - # Ausleihe beginnt im Bereich - {'Start': {'$gte': start, '$lte': end}}, - # Ausleihe endet im Bereich - {'End': {'$gte': start, '$lte': end}}, - # Ausleihe umfasst den gesamten Bereich - {'Start': {'$lte': start}, 'End': {'$gte': end}}, - # Aktive Ausleihungen ohne Ende, die vor dem Ende beginnen - {'Start': {'$lte': end}, 'End': None} + query['$and'] = [ + {'Start': {'$lte': end}}, + {'$or': [ + {'End': {'$gte': start}}, + {'End': None} + ]} ] elif date_filter == 'start_in': - # Nur Ausleihungen, die im Bereich beginnen query['Start'] = {'$gte': start, '$lte': end} elif date_filter == 'end_in': - # Nur Ausleihungen, die im Bereich enden query['End'] = {'$gte': start, '$lte': end} elif date_filter == 'contained': - # Nur Ausleihungen, die vollständig im Bereich liegen query['Start'] = {'$gte': start} - query['End'] = {'$lte': end} - + 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: - # print(f"Error retrieving ausleihungen: {e}") # Log the error + # Sinnvolles Logging einbauen return [] - def get_active_ausleihungen(start=None, end=None): """ Ruft alle aktiven (laufenden) Ausleihungen ab. @@ -558,682 +534,350 @@ def get_cancelled_ausleihungen(start=None, end=None): # === SEARCH FUNCTIONS === def get_ausleihung_by_user(user_id, status=None, use_client_side_verification=True): - """ - Ruft Ausleihungen für einen bestimmten Benutzer ab und verifiziert den Status clientseitig. - - Args: - user_id (str): ID oder Benutzername des Benutzers - status (str/list, optional): Status(se) der Ausleihungen - use_client_side_verification (bool, optional): Ob der Status clientseitig verifiziert werden soll - - Returns: - list: Liste von Ausleihungsdatensätzen des Benutzers - """ + """Ruft Ausleihungen für einen bestimmten Benutzer ab.""" try: - client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) - db = client[cfg.MONGODB_DB] - ausleihungen = db['ausleihungen'] - - query = {'User': user_id, 'Status': {'$ne': 'deleted'}} - - # Wenn clientseitige Verifikation verwendet wird, holen wir ALLE Ausleihungen - # und filtern später clientseitig - if use_client_side_verification: - # Bei clientseitiger Verifikation alle Ausleihungen holen (auch cancelled) - # da wir den Status später neu berechnen - pass # query bleibt unverändert - else: - # Exclude only cancelled status by default - we want to see planned, active, and completed - if status is not None: - if isinstance(status, list): - query['Status'] = {'$in': status} + 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'] = status - else: - # Otherwise exclude only cancelled appointments - query['Status'] = {'$ne': 'cancelled'} - - # Get appointments from database - if not use_client_side_verification: - results = list(ausleihungen.find(query)) - client.close() - return results - - # Wenn clientseitige Statusverifikation aktiviert ist, holen wir alle Ausleihungen - # des Benutzers und verifizieren den Status anschließend - all_ausleihungen = list(ausleihungen.find(query)) - client.close() - - # Immer clientseitige Statusverifikation durchführen wenn aktiviert - if use_client_side_verification: - for ausleihung in all_ausleihungen: - # Clientseitige Statusverifizierung für alle Ausleihungen - current_status = get_current_status(ausleihung) - ausleihung['VerifiedStatus'] = current_status - - # Wenn keine Filterung erforderlich ist, geben wir alle Ausleihungen zurück - if status is None: - return all_ausleihungen - - # Statusfilterung durchführen - filtered_results = [] - for ausleihung in all_ausleihungen: - # Clientseitige Statusverifizierung - current_status = get_current_status(ausleihung) - - # Status-Matching - if isinstance(status, list): - if current_status in status and current_status != 'deleted': - # Status aktualisieren und zur Ergebnismenge hinzufügen - ausleihung['VerifiedStatus'] = current_status - filtered_results.append(ausleihung) - else: - if current_status == status and current_status != 'deleted': - # Status aktualisieren und zur Ergebnismenge hinzufügen - ausleihung['VerifiedStatus'] = current_status - filtered_results.append(ausleihung) - - return filtered_results + 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}") # Log the error + print(f"Error retrieving ausleihungen for user {user_id}: {e}") return [] def get_ausleihung_by_item(item_id, status=None, include_history=False): - """ - Get ausleihung record(s) for a specific item. - - Args: - item_id (str): ID of the item - status (str, optional): Filter by status - include_history (bool): If True, return the most recent record regardless of status - - Returns: - dict or None: Ausleihung record or None if not found - """ + """Get most recent ausleihung record for a specific item.""" try: - client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) - db = client[cfg.MONGODB_DB] - ausleihungen = db['ausleihungen'] - - # Build query - query = {'Item': item_id, 'Status': {'$ne': 'deleted'}} - if status and not include_history: - query['Status'] = status - - # Get the most recent record by sorting by Start date descending - ausleihung = ausleihungen.find(query).sort('Start', -1).limit(1) - - result = None - for record in ausleihung: - record['_id'] = str(record['_id']) - result = record - break - - client.close() - return result - + 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. - - Args: - start_date (datetime): Beginn des Zeitraums - end_date (datetime): Ende des Zeitraums - status (str/list, optional): Status(se) der Ausleihungen - - Returns: - list: Liste von Ausleihungsdatensätzen im Zeitraum - """ + """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. - - Args: - item_id (str): ID des zu prüfenden Gegenstands - start_date (datetime): Vorgeschlagenes Startdatum - end_date (datetime): Vorgeschlagenes Enddatum - period (int, optional): Schulstunde für die Prüfung - - Returns: - bool: True, wenn ein Konflikt besteht, sonst False - """ + """Prüft, ob es Konflikte mit bestehenden Ausleihungen oder aktiven Ausleihen gibt.""" try: - print(f"Checking booking conflict for item {item_id}, period {period}, start {start_date}, end {end_date}") - - if start_date and hasattr(start_date, 'tzinfo') and start_date.tzinfo: - start_date = start_date.replace(tzinfo=None) - if end_date and hasattr(end_date, 'tzinfo') and end_date.tzinfo: - end_date = end_date.replace(tzinfo=None) - - client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) - db = client[cfg.MONGODB_DB] - ausleihungen = db['ausleihungen'] - - # Get the date component for filtering - booking_date = start_date.date() - - # First, get all active and planned bookings for this item - all_bookings = list(ausleihungen.find({ - 'Item': item_id, - 'Status': {'$in': ['planned', 'active']} - })) - - # Print all relevant bookings for debugging - print(f"Found {len(all_bookings)} existing bookings for this item") - for bk in all_bookings: - bk_id = str(bk.get('_id')) - bk_status = bk.get('Status') - bk_period = bk.get('Period', 'None') - bk_start = bk.get('Start') - bk_user = bk.get('User') - print(f" - Booking {bk_id}: Status={bk_status}, Period={bk_period}, Start={bk_start}, User={bk_user}") + start_date = ensure_timezone_aware(start_date) + end_date = ensure_timezone_aware(end_date) - # If we're booking by period, check for period conflicts - if period is not None: - period_int = int(period) - - # Check bookings on the same day with the same period - for booking in all_bookings: - booking_start = booking.get('Start') - if not booking_start: - continue - - # Compare just the date part - try: - # Ensure we're comparing date objects, not datetime objects - existing_date = booking_start.date() - if existing_date == booking_date: - # If this booking has the same period, it's a conflict + 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') - # Convert to integer for proper comparison if booking_period is not None and int(booking_period) == period_int: - print(f"CONFLICT: Same day, same period. Period: {period_int}, Date: {booking_date}") - client.close() return True - except Exception as e: - print(f"Error comparing dates: {e}") - # Continue checking other bookings if there's an error with one - - # Always check for time overlaps, regardless of whether period was specified - for booking in all_bookings: - booking_start = booking.get('Start') - booking_end = booking.get('End') - - if not booking_start: - continue - - # Set default end time if not specified - if not booking_end: - booking_end = booking_start + datetime.timedelta(hours=1) - - # Check for overlap - # 1. New booking starts during existing booking - # 2. New booking ends during existing booking - # 3. New booking completely contains existing booking - # 4. Existing booking completely contains new booking - if ((start_date >= booking_start and start_date < booking_end) or - (end_date > booking_start and end_date <= booking_end) or - (start_date <= booking_start and end_date >= booking_end) or - (start_date >= booking_start and end_date <= booking_end)): - print(f"CONFLICT: Time overlap. New booking: {start_date}-{end_date}, Existing: {booking_start}-{booking_end}") - client.close() - return True - - print("No conflicts found!") - client.close() - return False - + + 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}") - import traceback - traceback.print_exc() - return True # Bei Fehler Konflikt annehmen, um auf Nummer sicher zu gehen + 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 - - Args: - item_id (str): ID of the item to check - start_date (datetime): Start time for the booking - end_date (datetime): End time for the booking - period (int): Optional period number (for period-based booking) - period_end (int): Optional end period number for period ranges - - Returns: - bool: True if there's a conflict, False otherwise - """ + """Checks for conflicts with existing bookings, supporting period ranges.""" try: - if start_date and hasattr(start_date, 'tzinfo') and start_date.tzinfo: - start_date = start_date.replace(tzinfo=None) - if end_date and hasattr(end_date, 'tzinfo') and end_date.tzinfo: - end_date = end_date.replace(tzinfo=None) - - client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) - db = client[cfg.MONGODB_DB] - ausleihungen = db['ausleihungen'] - - # Get the date component for filtering - booking_date = start_date.date() - - # First, get all active and planned bookings for this item - all_bookings = list(ausleihungen.find({ - 'Item': item_id, - 'Status': {'$in': ['planned', 'active']} - })) - - # Print all relevant bookings for debugging - print(f"Found {len(all_bookings)} existing bookings for this item") - - # If we're booking by period, check for period conflicts - if period is not None: - period_start = int(period) - periods_to_check = [period_start] - - # If period_end is specified, it's a range of periods - if period_end is not None: - period_end = int(period_end) - periods_to_check = list(range(period_start, period_end + 1)) - - # Check bookings on the same day with any overlapping period + 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: - booking_start = booking.get('Start') - if not booking_start: + b_start = ensure_timezone_aware(booking.get('Start')) + if not b_start: continue - - # Compare just the date part - existing_date = booking_start.date() - if existing_date == booking_date: - booking_period = booking.get('Period') - # Normalize to int if possible - try: - booking_period_int = int(booking_period) if booking_period is not None else None - except Exception: - booking_period_int = None - - # If this booking has any period in our range, it's a conflict - if booking_period_int is not None and booking_period_int in periods_to_check: - print(f"CONFLICT: Same day, overlapping period. Booking period: {booking_period_int}") - client.close() - return True - # Always also check time overlaps against any existing bookings (incl. those without Period) - for booking in all_bookings: - booking_start = booking.get('Start') - booking_end = booking.get('End') - - if not booking_start: - continue - - # Set default end time if not specified - if not booking_end: - booking_end = booking_start + datetime.timedelta(hours=1) - - # Check for overlap of [start_date, end_date] with [booking_start, booking_end] - if ((start_date >= booking_start and start_date < booking_end) or - (end_date > booking_start and end_date <= booking_end) or - (start_date <= booking_start and end_date >= booking_end) or - (start_date >= booking_start and end_date <= booking_end)): - print(f"CONFLICT: Time overlap. New: {start_date}-{end_date}, Existing: {booking_start}-{booking_end}") - client.close() - return True - - print("No conflicts found!") - client.close() - return False - + 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 conflicts: {e}") - import traceback - traceback.print_exc() - return True # Assume conflict on error for safety + print(f"Error checking booking range conflicts: {e}") + return True -# === AUTOMATISIERTE VERARBEITUNG === - def get_ausleihungen_starting_now(current_time): - """ - Ruft Ausleihungen ab, die jetzt beginnen sollen (innerhalb eines Zeitfensters). - - Args: - current_time (datetime): Aktuelle Zeit für den Vergleich - - Returns: - list: Liste von Ausleihungen, die jetzt beginnen sollen - """ + """Ruft Ausleihungen ab, die jetzt beginnen sollen.""" try: - # Define a wider time window (3 hours before to 1 hour after) - # This helps catch bookings that might have been missed - hours_before = datetime.timedelta(hours=3) - hours_after = datetime.timedelta(hours=1) - start_time = current_time - hours_before - end_time = current_time + hours_after - - # Get today's date for date comparison + 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() - - client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) - db = client[cfg.MONGODB_DB] - ausleihungen = db['ausleihungen'] - - # Build a query to find planned bookings that: - # 1. Are scheduled to start within our time window - # 2. OR have a period set for today - query = { - 'Status': 'planned', - '$or': [ - # Time-based bookings within our window - {'Start': {'$lte': end_time, '$gte': start_time}}, - - # Period-based bookings for today - { - 'Period': {'$exists': True}, - 'Start': { - '$gte': datetime.datetime.combine(today, datetime.time.min), - '$lt': datetime.datetime.combine(today + datetime.timedelta(days=1), datetime.time.min) + + 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) + } } - } - ] - } - - print(f"Query for bookings starting now: {query}") - bookings = list(ausleihungen.find(query)) - - print(f"Found {len(bookings)} bookings that might be starting now") - for b in bookings: - print(f" - Booking {b.get('_id')}: Start={b.get('Start')}, Period={b.get('Period')}") - - client.close() - return bookings + ] + } + return list(ausleihungen.find(query)) except Exception as e: print(f"Error in get_ausleihungen_starting_now: {e}") - import traceback - traceback.print_exc() return [] def get_ausleihungen_ending_now(current_time): - """ - Ruft Ausleihungen ab, die jetzt enden sollen (innerhalb eines Zeitfensters). - - Args: - current_time (datetime): Aktuelle Zeit für den Vergleich - - Returns: - list: Liste von Ausleihungen, die jetzt enden sollen - """ + """Ruft Ausleihungen ab, die jetzt enden sollen.""" try: - client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) - db = client[cfg.MONGODB_DB] - ausleihungen = db['ausleihungen'] - - # Create a wider time window (15 minutes before to catch any missed endings) - window_before = datetime.timedelta(minutes=15) - window_after = datetime.timedelta(minutes=5) - start_time = current_time - window_before - end_time = current_time + window_after - - # Get today's date for period-based checks + 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() - - # Find active bookings that: - # 1. Have an end time within our window, OR - # 2. Are from today with a period (will check the period in process_bookings) - query = { - 'Status': 'active', - '$or': [ - {'End': {'$gte': start_time, '$lte': end_time}}, - { - 'Period': {'$exists': True}, - 'Start': { - '$gte': datetime.datetime.combine(today, datetime.time.min), - '$lt': datetime.datetime.combine(today + datetime.timedelta(days=1), datetime.time.min) + + 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) + } } - } - ] - } - - print(f"Looking for bookings ending now with query: {query}") - bookings = list(ausleihungen.find(query)) - print(f"Found {len(bookings)} bookings that might be ending now") - for b in bookings: - print(f" - Potential ending booking {b.get('_id')}: End={b.get('End')}, Period={b.get('Period')}") - - client.close() - return bookings + ] + } + return list(ausleihungen.find(query)) except Exception as e: print(f"Error in get_ausleihungen_ending_now: {e}") - import traceback - traceback.print_exc() return [] def activate_ausleihung(id): - """ - Aktiviert eine geplante Ausleihe. - - Args: - id (str): ID der zu aktivierenden Ausleihe - - Returns: - bool: True bei Erfolg, sonst False - """ + """Aktiviert eine geplante Ausleihe.""" try: - client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) - db = client[cfg.MONGODB_DB] - ausleihungen = db['ausleihungen'] - - # Zuerst prüfen, ob die Ausleihe existiert und den Status 'planned' hat - ausleihung = ausleihungen.find_one({'_id': ObjectId(id)}) - if not ausleihung or ausleihung.get('Status') != 'planned': - client.close() - return False - - # Ausleihe aktivieren - result = ausleihungen.update_one( - {'_id': ObjectId(id)}, - {'$set': { - 'Status': 'active', - 'LastUpdated': datetime.datetime.now() - }} - ) - - client.close() - return result.modified_count > 0 + 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 -# === KOMPATIBILITÄTSFUNKTIONEN === - -# Hilfsmethoden für alte Funktionsaufrufe, um Abwärtskompatibilität zu gewährleisten - -def add_planned_booking(item_id, user, start_date, end_date, notes="", period=None): - """Kompatibilitätsfunktion - erstellt eine geplante Ausleihe""" - 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): - """Kompatibilitätsfunktion - prüft auf Ausleihungskonflikte mit Periodenunterstützung""" - return check_ausleihung_conflict(item_id, start_date, end_date, period) - -def cancel_booking(booking_id): - """Kompatibilitätsfunktion - storniert eine Ausleihe""" - return cancel_ausleihung(booking_id) - -def get_booking(booking_id): - """Kompatibilitätsfunktion - ruft eine einzelne Ausleihe ab""" - return get_ausleihung(booking_id) - -def get_active_bookings(start=None, end=None): - """Kompatibilitätsfunktion - ruft aktive Ausleihungen ab""" - return get_active_ausleihungen(start, end) - -def get_planned_bookings(start=None, end=None): - """Kompatibilitätsfunktion - ruft geplante Ausleihungen ab""" - return get_planned_ausleihungen(start, end) - -def get_completed_bookings(start=None, end=None): - """Kompatibilitätsfunktion - ruft abgeschlossene Ausleihungen ab""" - return get_completed_ausleihungen(start, end) - -def mark_booking_active(booking_id, ausleihung_id=None): - """Kompatibilitätsfunktion - markiert eine Ausleihe als aktiv und verknüpft optional eine Ausleihungs-ID""" - try: - client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) - db = client[cfg.MONGODB_DB] - ausleihungen = db['ausleihungen'] - - # Basisupdate-Daten mit Status-Änderung - update_data = { - 'Status': 'active', - 'LastUpdated': datetime.datetime.now() - } - - # Wenn eine Ausleihungs-ID angegeben wurde, diese auch verknüpfen - if ausleihung_id: - update_data['AusleihungId'] = ausleihung_id - - # Update durchführen - result = ausleihungen.update_one( - {'_id': ObjectId(booking_id)}, - {'$set': update_data} - ) - - client.close() - return result.modified_count > 0 - except Exception as e: - print(f"Error activating booking: {e}") - # Fallback zur alten Methode bei Fehlern - return activate_ausleihung(booking_id) - -def mark_booking_completed(booking_id): - """Kompatibilitätsfunktion - markiert eine Ausleihe als abgeschlossen""" - return complete_ausleihung(booking_id) - -def get_bookings_starting_now(current_time): - """Kompatibilitätsfunktion - ruft startende Ausleihungen ab""" - -def get_bookings_starting_now(current_time): - """Kompatibilitätsfunktion - ruft startende Ausleihungen ab""" - return get_ausleihungen_starting_now(current_time) - -def get_bookings_ending_now(current_time): - """Kompatibilitätsfunktion - ruft endende Ausleihungen ab""" - return get_ausleihungen_ending_now(current_time) - - - - def reset_item_completely(item_id): - """ - Setzt den Ausleihstatus eines Items vollständig zurück. - - Diese Funktion: - - Markiert das Item als verfügbar - - Löscht alle Ausleihungsinformationen - - Setzt Exemplar-Status zurück - - Beendet alle aktiven Ausleihungen - - Args: - item_id (str): Die ID des Items das zurückgesetzt werden soll - - Returns: - dict: Erfolg-/Fehlerstatus mit Details - """ + """Setzt den Ausleihstatus eines Items vollständig zurück.""" try: - client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) - db = client[cfg.MONGODB_DB] - items_collection = db['items'] - ausleihungen_collection = db['ausleihungen'] - - # Item abrufen - item = items_collection.find_one({'_id': ObjectId(item_id)}) - if not item: - return {'success': False, 'message': 'Item nicht gefunden'} - - item_name = item.get('Name', 'Unbekannt') - - # 1. Alle aktiven Ausleihungen für dieses Item beenden - active_borrowings = ausleihungen_collection.find({ - 'Item': item_id, - 'Status': {'$in': ['active', 'planned']} - }) - - completed_count = 0 - for borrowing in active_borrowings: - ausleihungen_collection.update_one( - {'_id': borrowing['_id']}, + 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': datetime.datetime.now(), - 'LastUpdated': datetime.datetime.now(), + 'End': now_utc, + 'LastUpdated': now_utc, 'CompletedBy': 'System Reset' } } ) - completed_count += 1 - - # 2. Item-Status zurücksetzen - update_data = { - 'Verfuegbar': True, - 'LastUpdated': datetime.datetime.now() - } - - # Entferne User-Zuordnung falls vorhanden - if 'User' in item: - update_data['$unset'] = {'User': ''} - - # Entferne BorrowerInfo falls vorhanden - if 'BorrowerInfo' in item: - if '$unset' not in update_data: - update_data['$unset'] = {} - update_data['$unset']['BorrowerInfo'] = '' - - # Setze ExemplareStatus zurück falls vorhanden - if 'ExemplareStatus' in item: - if '$unset' not in update_data: - update_data['$unset'] = {} - update_data['$unset']['ExemplareStatus'] = '' - - # Item aktualisieren - result = items_collection.update_one( - {'_id': ObjectId(item_id)}, - update_data - ) - - client.close() - - if result.modified_count > 0: + 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': True + 'item_reset': result.modified_count > 0 } } - else: - return { - 'success': True, - 'message': f'Item "{item_name}" war bereits im korrekten Status', - 'details': { - 'completed_borrowings': completed_count, - 'item_reset': False - } - } - except Exception as e: - return { - 'success': False, - 'message': f'Fehler beim Zurücksetzen.' - } \ No newline at end of file + 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) \ No newline at end of file diff --git a/Web/modules/database/items.py b/Web/modules/database/items.py index 50513ad..f2c188a 100755 --- a/Web/modules/database/items.py +++ b/Web/modules/database/items.py @@ -102,42 +102,70 @@ def add_item(name, ort, beschreibung, images=None, filter=None, filter2=None, fi isbn=None, item_type='general', library_category=None, is_library=False): """ Add a new item to the inventory. + + Args: + name (str): Name of the item + ort (str): Location of the item + beschreibung (str): Description of the item + images (list, optional): List of image filenames for the item + filter (str, optional): Primary filter/category for the item + filter2 (str, optional): Secondary filter/category for the item + filter3 (str, optional): Tertiary filter/category for the item + ansch_jahr (int, optional): Year of acquisition + ansch_kost (float, optional): Cost of acquisition + code_4 (str, optional): 4-digit identification code + reservierbar (bool, optional): Whether the item can be reserved in advance + series_group_id (str, optional): Shared group id for same-type batch items + series_count (int, optional): Total items in the created batch + series_position (int, optional): Position inside the batch (1-based) + is_grouped_sub_item (bool, optional): Whether this item is hidden as sub-item + parent_item_id (str, optional): Parent item id if this is a sub-item + isbn (str, optional): ISBN for books or media items + item_type (str, optional): Type of the item (e.g., 'general', 'book', 'cd') + library_category (str, optional): Library category for the item + + Returns: + ObjectId: ID of the new item or None if failed """ try: - with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client: - db = client[cfg.MONGODB_DB] - items = db['items'] + client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) + db = client[cfg.MONGODB_DB] + items = db['items'] - if images is None: - images = [] + # Set default values for optional parameters + if images is None: + images = [] - item = { - 'Name': name, - 'Ort': ort, - 'Beschreibung': beschreibung, - 'Images': images, - 'Verfuegbar': True, - 'Reservierbar': reservierbar, - 'Filter': filter, - 'Filter2': filter2, - 'Filter3': filter3, - 'Anschaffungsjahr': ansch_jahr, - 'Anschaffungskosten': ansch_kost, - 'Code_4': code_4, - 'ISBN': isbn, - 'library_category': library_category, - 'ItemType': item_type, - 'is_library': is_library, - 'SeriesGroupId': series_group_id, - 'SeriesCount': series_count, - 'SeriesPosition': series_position, - 'IsGroupedSubItem': is_grouped_sub_item, - 'ParentItemId': parent_item_id, - 'Created': datetime.datetime.now(), - 'LastUpdated': datetime.datetime.now() - } - result = items.insert_one(item) - return result.inserted_id + item = { + 'Name': name, + 'Ort': ort, + 'Beschreibung': beschreibung, + 'Images': images, + 'Verfuegbar': True, + 'Reservierbar': reservierbar, + 'Filter': filter, + 'Filter2': filter2, + 'Filter3': filter3, + 'Anschaffungsjahr': ansch_jahr, + 'Anschaffungskosten': ansch_kost, + 'Code_4': code_4, + 'ISBN': isbn, + 'library_category': library_category, + 'ItemType': item_type, + 'is_library': is_library, + 'SeriesGroupId': series_group_id, + 'SeriesCount': series_count, + 'SeriesPosition': series_position, + 'IsGroupedSubItem': is_grouped_sub_item, + 'ParentItemId': parent_item_id, + 'Created': datetime.datetime.now(), + 'LastUpdated': datetime.datetime.now() + } + result = items.insert_one(item) + item_id = result.inserted_id + + client.close() + return item_id except Exception as e: print(f"Error adding item: {e}") return None @@ -146,21 +174,28 @@ def add_item(name, ort, beschreibung, images=None, filter=None, filter2=None, fi def remove_item(id): """ Soft-delete an item from the inventory. + + Args: + id (str): ID of the item to remove + + Returns: + bool: True if successful, False otherwise """ try: - with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client: - db = client[cfg.MONGODB_DB] - items = db['items'] - result = items.update_one( - {'_id': ObjectId(id), 'Deleted': {'$ne': True}}, - {'$set': { - 'Deleted': True, - 'DeletedAt': datetime.datetime.now(), - 'LastUpdated': datetime.datetime.now(), - 'Verfuegbar': False, - }} - ) - return result.modified_count > 0 + client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) + db = client[cfg.MONGODB_DB] + items = db['items'] + result = items.update_one( + {'_id': ObjectId(id), 'Deleted': {'$ne': True}}, + {'$set': { + 'Deleted': True, + 'DeletedAt': datetime.datetime.now(), + 'LastUpdated': datetime.datetime.now(), + 'Verfuegbar': False, + }} + ) + client.close() + return result.modified_count > 0 except Exception as e: print(f"Error removing item: {e}") return False @@ -169,35 +204,46 @@ def remove_item(id): def get_group_item_ids(id): """ Resolve all item ids that belong to the same grouped series as the given item. + + For non-grouped items, this returns only the provided id. + + Args: + id (str): ID of any item in the group + + Returns: + list[str]: All related item IDs (including the parent item) """ try: - with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client: - db = client[cfg.MONGODB_DB] - items = db['items'] + client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) + db = client[cfg.MONGODB_DB] + items = db['items'] - base_item = items.find_one(_active_record_query({'_id': ObjectId(id)})) - if not base_item: - return [] + base_item = items.find_one(_active_record_query({'_id': ObjectId(id)})) + if not base_item: + client.close() + return [] - resolved_ids = set() - series_group_id = base_item.get('SeriesGroupId') + resolved_ids = set() - if series_group_id: - for group_item in items.find(_active_record_query({'SeriesGroupId': series_group_id}), {'_id': 1}): - resolved_ids.add(str(group_item['_id'])) + # Prefer SeriesGroupId because it represents the full logical group. + series_group_id = base_item.get('SeriesGroupId') + if series_group_id: + for group_item in items.find(_active_record_query({'SeriesGroupId': series_group_id}), {'_id': 1}): + resolved_ids.add(str(group_item['_id'])) + else: + resolved_ids.add(str(base_item['_id'])) + + parent_item_id = base_item.get('ParentItemId') + if parent_item_id: + resolved_ids.add(str(parent_item_id)) + for sibling in items.find(_active_record_query({'ParentItemId': str(parent_item_id), 'IsGroupedSubItem': True}), {'_id': 1}): + resolved_ids.add(str(sibling['_id'])) else: - resolved_ids.add(str(base_item['_id'])) + for child in items.find(_active_record_query({'ParentItemId': str(base_item['_id']), 'IsGroupedSubItem': True}), {'_id': 1}): + resolved_ids.add(str(child['_id'])) - parent_item_id = base_item.get('ParentItemId') - if parent_item_id: - resolved_ids.add(str(parent_item_id)) - for sibling in items.find(_active_record_query({'ParentItemId': str(parent_item_id), 'IsGroupedSubItem': True}), {'_id': 1}): - resolved_ids.add(str(sibling['_id'])) - else: - for child in items.find(_active_record_query({'ParentItemId': str(base_item['_id']), 'IsGroupedSubItem': True}), {'_id': 1}): - resolved_ids.add(str(child['_id'])) - - return list(resolved_ids) + client.close() + return list(resolved_ids) except Exception as e: print(f"Error resolving group item IDs: {e}") return [] @@ -206,80 +252,90 @@ def get_group_item_ids(id): def update_item(id, name, ort, beschreibung, images, verfuegbar, filter1, filter2, filter3, ansch_jahr, ansch_kost, code_4, reservierbar, isbn=None, item_type='general'): try: - with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client: - db = client[cfg.MONGODB_DB] - items = db['items'] + client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) + db = client[cfg.MONGODB_DB] + items = db['items'] - old_item = items.find_one({'_id': ObjectId(id)}) - if not old_item: - return False + old_item = items.find_one({'_id': ObjectId(id)}) + if not old_item: + return False - series_group_id = old_item.get('SeriesGroupId') + series_group_id = old_item.get('SeriesGroupId') - shared_update = { - 'Name': name, - 'Ort': ort, - 'Beschreibung': beschreibung, - 'Images': images, - 'Filter': filter1, - 'Filter2': filter2, - 'Filter3': filter3, - 'Anschaffungsjahr': ansch_jahr, - 'Anschaffungskosten': ansch_kost, - 'Reservierbar': reservierbar, - 'ISBN': isbn, - 'ItemType': item_type, - 'Verfuegbar': verfuegbar, - 'LastUpdated': datetime.datetime.now() - } + shared_update = { + 'Name': name, + 'Ort': ort, + 'Beschreibung': beschreibung, + 'Images': images, + 'Filter': filter1, + 'Filter2': filter2, + 'Filter3': filter3, + 'Anschaffungsjahr': ansch_jahr, + 'Anschaffungskosten': ansch_kost, + 'Reservierbar': reservierbar, + 'ISBN': isbn, + 'ItemType': item_type, + 'Verfuegbar': verfuegbar, + 'LastUpdated': datetime.datetime.now() + } - specific_update = shared_update.copy() - specific_update['Code_4'] = code_4 + specific_update = shared_update.copy() + specific_update['Code_4'] = code_4 - items.update_one({'_id': ObjectId(id)}, {'$set': specific_update}) + items.update_one({'_id': ObjectId(id)}, {'$set': specific_update}) - if series_group_id: - items.update_many( - { - 'SeriesGroupId': series_group_id, - '_id': {'$ne': ObjectId(id)} - }, - {'$set': shared_update} - ) + if series_group_id: + items.update_many( + { + 'SeriesGroupId': series_group_id, + '_id': {'$ne': ObjectId(id)} + }, + {'$set': shared_update} + ) - return True + client.close() + return True except Exception as e: print(f"Error updating item: {e}") return False - def update_item_status(id, verfuegbar, user=None): """ Update the availability status of an inventory item. + + Args: + id (str): ID of the item to update + verfuegbar (bool): New availability status + user (str, optional): Username of person who borrowed the item + + Returns: + bool: True if successful, False otherwise """ try: - with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client: - db = client[cfg.MONGODB_DB] - items = db['items'] + client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) + db = client[cfg.MONGODB_DB] + items = db['items'] - update_data = { - 'Verfuegbar': verfuegbar, - 'LastUpdated': datetime.datetime.now() - } + update_data = { + 'Verfuegbar': verfuegbar, + 'LastUpdated': datetime.datetime.now() + } - update_query = {'$set': update_data} + update_query = {'$set': update_data} - if user is not None: - update_data['User'] = dp.encrypt_text(user) - elif verfuegbar: - update_query['$unset'] = {'User': ""} + if user is not None: + update_data['User'] = dp.encrypt_text(user) + elif verfuegbar: + # If item is being marked as available, clear the user field + update_query['$unset'] = {'User': ""} - result = items.update_one( - {'_id': ObjectId(id)}, - update_query - ) + result = items.update_one( + {'_id': ObjectId(id)}, + update_query + ) - return result.modified_count > 0 + client.close() + return result.modified_count > 0 except Exception as e: print(f"Error updating item status: {e}") return False @@ -288,23 +344,31 @@ def update_item_status(id, verfuegbar, user=None): def update_item_exemplare_status(id, exemplare_status): """ Update the exemplar status of an inventory item. + + Args: + id (str): ID of the item to update + exemplare_status (list): List of status objects for each exemplar + + Returns: + bool: True if successful, False otherwise """ try: - with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client: - db = client[cfg.MONGODB_DB] - items = db['items'] + client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) + db = client[cfg.MONGODB_DB] + items = db['items'] - update_data = { - 'ExemplareStatus': exemplare_status, - 'LastUpdated': datetime.datetime.now() - } + update_data = { + 'ExemplareStatus': exemplare_status, + 'LastUpdated': datetime.datetime.now() + } - result = items.update_one( - {'_id': ObjectId(id)}, - {'$set': update_data} - ) + result = items.update_one( + {'_id': ObjectId(id)}, + {'$set': update_data} + ) - return result.modified_count > 0 + client.close() + return result.modified_count > 0 except Exception as e: print(f"Error updating exemplar status: {e}") return False @@ -313,21 +377,34 @@ def update_item_exemplare_status(id, exemplare_status): def is_code_unique(code_4, exclude_id=None): """ Check if a given code is unique (not used by any other item). + + Args: + code_4 (str): The code to check + exclude_id (str, optional): ID of item to exclude from the check (for edit operations) + + Returns: + bool: True if code is unique, False if already in use """ if not code_4 or code_4.strip() == "": + # Empty codes are not considered unique return False - with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client: - db = client[cfg.MONGODB_DB] - items = db['items'] + client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) + db = client[cfg.MONGODB_DB] + items = db['items'] - query = {'Code_4': code_4, 'Deleted': {'$ne': True}} + # Build query to find items with this code + query = {'Code_4': code_4, 'Deleted': {'$ne': True}} - if exclude_id: - query['_id'] = {'$ne': ObjectId(exclude_id)} + # If we're editing an item, exclude it from the uniqueness check + if exclude_id: + query['_id'] = {'$ne': ObjectId(exclude_id)} - count = items.count_documents(query) - return count == 0 + # Check if any items with this code exist + count = items.count_documents(query) + + client.close() + return count == 0 # === ITEM RETRIEVAL === @@ -335,19 +412,21 @@ def is_code_unique(code_4, exclude_id=None): def get_items(): """ Retrieve all inventory items. + + Returns: + list: List of all inventory item documents with string IDs """ try: - with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client: - db = client[cfg.MONGODB_DB] - items = db['items'] - items_return = items.find(_active_record_query(_non_library_query())) - - items_list = [] - for item in items_return: - item['_id'] = str(item['_id']) - items_list.append(item) - - return items_list + client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) + db = client[cfg.MONGODB_DB] + items = db['items'] + items_return = items.find(_active_record_query(_non_library_query())) + items_list = [] + for item in items_return: + item['_id'] = str(item['_id']) + items_list.append(item) + client.close() + return items_list except Exception as e: print(f"Error retrieving items: {e}") return [] @@ -356,19 +435,21 @@ def get_items(): def get_available_items(): """ Retrieve all available inventory items. + + Returns: + list: List of available inventory item documents with string IDs """ try: - with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client: - db = client[cfg.MONGODB_DB] - items = db['items'] - items_return = items.find(_active_record_query(_non_library_query({'Verfuegbar': True}))) - - items_list = [] - for item in items_return: - item['_id'] = str(item['_id']) - items_list.append(item) - - return items_list + client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) + db = client[cfg.MONGODB_DB] + items = db['items'] + items_return = items.find(_active_record_query(_non_library_query({'Verfuegbar': True}))) + items_list = [] + for item in items_return: + item['_id'] = str(item['_id']) + items_list.append(item) + client.close() + return items_list except Exception as e: print(f"Error retrieving available items: {e}") return [] @@ -377,24 +458,25 @@ def get_available_items(): def get_borrowed_items(): """ Retrieve all currently borrowed inventory items. + + Returns: + list: List of borrowed inventory item documents with string IDs """ try: - with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client: - db = client[cfg.MONGODB_DB] - items = db['items'] - items_return = items.find(_active_record_query(_non_library_query({'Verfuegbar': False}))) - - items_list = [] - for item in items_return: - item['_id'] = str(item['_id']) - items_list.append(item) - - return items_list + client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) + db = client[cfg.MONGODB_DB] + items = db['items'] + items_return = items.find(_active_record_query(_non_library_query({'Verfuegbar': False}))) + items_list = [] + for item in items_return: + item['_id'] = str(item['_id']) + items_list.append(item) + client.close() + return items_list except Exception as e: print(f"Error retrieving borrowed items: {e}") return [] - def get_item(id, decrypt=True): """ Retrieve an inventory item by ID, with optional decryption. @@ -404,32 +486,37 @@ def get_item(id, decrypt=True): return None try: - with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client: - db = client[cfg.MONGODB_DB] - items = db['items'] - query = _active_record_query({'_id': item_id}) - item = items.find_one(query) - - if item: - item['_id'] = str(item['_id']) - if decrypt: - decrypt_item_user_data(item) - return item + client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) + db = client[cfg.MONGODB_DB] + items = db['items'] + query = _active_record_query({'_id': item_id}) + item = items.find_one(query) + if item: + item['_id'] = str(item['_id']) + if decrypt: + decrypt_item_user_data(item) + return item except Exception as e: print(f"Error retrieving item {id}: {e}") return None - def get_item_by_name(name): """ Retrieve a specific inventory item by its name. + + Args: + name (str): Name of the item to retrieve + + Returns: + dict: The inventory item document or None if not found """ try: - with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client: - db = client[cfg.MONGODB_DB] - items = db['items'] - item = items.find_one(_active_record_query(_non_library_query({'Name': name}))) - return item + client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) + db = client[cfg.MONGODB_DB] + items = db['items'] + item = items.find_one(_active_record_query({'Name': name})) + client.close() + return item except Exception as e: print(f"Error retrieving item by name: {e}") return None @@ -438,25 +525,35 @@ def get_item_by_name(name): def get_items_by_filter(filter_value): """ Retrieve inventory items matching a specific filter/category. + + Args: + filter_value (str): Filter value to search for + + Returns: + list: List of items matching the filter in primary, secondary, or tertiary category """ try: - with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client: - db = client[cfg.MONGODB_DB] - items = db['items'] + client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) + db = client[cfg.MONGODB_DB] + items = db['items'] - query = _active_record_query(_non_library_query({ - '$or': [ - {'Filter': filter_value}, - {'Filter2': filter_value}, - {'Filter3': filter_value} - ] - })) + # Use $or to find matches in any filter field + query = _active_record_query(_non_library_query({ + '$or': [ + {'Filter': filter_value}, + {'Filter2': filter_value}, + {'Filter3': filter_value} + ] + })) - results = list(items.find(query)) - for item in results: - item['_id'] = str(item['_id']) + results = list(items.find(query)) + client.close() - return results + # Convert ObjectId to string + for item in results: + item['_id'] = str(item['_id']) + + return results except Exception as e: print(f"Error retrieving items by filter: {e}") return [] @@ -465,70 +562,96 @@ def get_items_by_filter(filter_value): def get_filters(): """ Retrieve all unique filter/category values from the inventory. + + Returns: + list: Combined list of all primary, secondary and tertiary filter values """ try: - with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client: - db = client[cfg.MONGODB_DB] - items = db['items'] - non_library = _active_record_query(_non_library_query()) + client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) + db = client[cfg.MONGODB_DB] + items = db['items'] + non_library = _active_record_query(_non_library_query()) + filters = items.distinct('Filter', non_library) + filters2 = items.distinct('Filter2', non_library) + filters3 = items.distinct('Filter3', non_library) - filters = items.distinct('Filter', non_library) - filters2 = items.distinct('Filter2', non_library) - filters3 = items.distinct('Filter3', non_library) + # Combine filters and remove None/empty values + all_filters = [f for f in filters + filters2 + filters3 if f] - all_filters = [f for f in filters + filters2 + filters3 if f] + # Remove duplicates while preserving order + unique_filters = [] + for f in all_filters: + if f not in unique_filters: + unique_filters.append(f) - unique_filters = [] - for f in all_filters: - if f not in unique_filters: - unique_filters.append(f) - - return unique_filters + client.close() + return unique_filters except Exception as e: print(f"Error retrieving filters: {e}") return [] def get_primary_filters(): - """Retrieve all unique primary filter values.""" - try: - with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client: - db = client[cfg.MONGODB_DB] - items = db['items'] - filters = [f for f in items.distinct('Filter', _active_record_query(_non_library_query())) if f] + """ + Retrieve all unique primary filter values. - predefined = get_predefined_filter_values(1) - return sorted(list(set(filters + predefined))) + Returns: + list: List of all primary filter values + """ + try: + client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) + db = client[cfg.MONGODB_DB] + items = db['items'] + filters = [f for f in items.distinct('Filter', _active_record_query(_non_library_query())) if f] + client.close() + + # Add predefined values + predefined = get_predefined_filter_values(1) + return sorted(list(set(filters + predefined))) except Exception as e: print(f"Error retrieving primary filters: {e}") return [] def get_secondary_filters(): - """Retrieve all unique secondary filter values.""" - try: - with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client: - db = client[cfg.MONGODB_DB] - items = db['items'] - filters = [f for f in items.distinct('Filter2', _active_record_query(_non_library_query())) if f] + """ + Retrieve all unique secondary filter values. - predefined = get_predefined_filter_values(2) - return sorted(list(set(filters + predefined))) + Returns: + list: List of all secondary filter values + """ + try: + client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) + db = client[cfg.MONGODB_DB] + items = db['items'] + filters = [f for f in items.distinct('Filter2', _active_record_query(_non_library_query())) if f] + client.close() + + # Add predefined values + predefined = get_predefined_filter_values(2) + return sorted(list(set(filters + predefined))) except Exception as e: print(f"Error retrieving secondary filters: {e}") return [] def get_tertiary_filters(): - """Retrieve all unique tertiary filter values.""" - try: - with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client: - db = client[cfg.MONGODB_DB] - items = db['items'] - filters = [f for f in items.distinct('Filter3', _active_record_query(_non_library_query())) if f] + """ + Retrieve all unique tertiary filter values. - predefined = get_predefined_filter_values(3) - return sorted(list(set(filters + predefined))) + Returns: + list: List of all tertiary filter values + """ + try: + client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) + db = client[cfg.MONGODB_DB] + items = db['items'] + filters = [f for f in items.distinct('Filter3', _active_record_query(_non_library_query())) if f] + client.close() + + # Add predefined values + predefined = get_predefined_filter_values(3) + return sorted(list(set(filters + predefined))) except Exception as e: print(f"Error retrieving tertiary filters: {e}") return [] @@ -537,17 +660,25 @@ def get_tertiary_filters(): def get_item_by_code_4(code_4): """ Retrieve inventory items matching a specific 4-digit code. + + Args: + code_4 (str): 4-digit code to search for + + Returns: + list: List of items matching the code """ try: - with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client: - db = client[cfg.MONGODB_DB] - items = db['items'] - results = list(items.find(_active_record_query(_non_library_query({"Code_4": code_4})))) + client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) + db = client[cfg.MONGODB_DB] + items = db['items'] + results = list(items.find(_active_record_query(_non_library_query({"Code_4": code_4})))) - for item in results: - item['_id'] = str(item['_id']) + # Convert ObjectId to string + for item in results: + item['_id'] = str(item['_id']) - return results + client.close() + return results except Exception as e: print(f"Error retrieving item by code: {e}") return [] @@ -558,33 +689,42 @@ def get_item_by_code_4(code_4): def unstuck_item(id): """ Remove all borrowing records for a specific item to reset its status. + Used to fix problematic or stuck items. + + Args: + id (str): ID of the item to unstick + + Returns: + bool: True if successful, False otherwise """ try: - with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client: - db = client[cfg.MONGODB_DB] - ausleihungen = db['ausleihungen'] + client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) + db = client[cfg.MONGODB_DB] + ausleihungen = db['ausleihungen'] + result = ausleihungen.update_many( + {'Item': id, 'Status': {'$nin': ['cancelled', 'deleted']}}, + {'$set': { + 'Status': 'cancelled', + 'CancelledReason': 'unstuck_reset', + 'LastUpdated': datetime.datetime.now() + }} + ) - ausleihungen.update_many( - {'Item': id, 'Status': {'$nin': ['cancelled', 'deleted']}}, - {'$set': { - 'Status': 'cancelled', - 'CancelledReason': 'unstuck_reset', + # Also reset the item status + items = db['items'] + items.update_one( + {'_id': ObjectId(id)}, + { + '$set': { + 'Verfuegbar': True, 'LastUpdated': datetime.datetime.now() - }} - ) + }, + '$unset': {'User': ""} + } + ) - items = db['items'] - items.update_one( - {'_id': ObjectId(id)}, - { - '$set': { - 'Verfuegbar': True, - 'LastUpdated': datetime.datetime.now() - }, - '$unset': {'User': ""} - } - ) - return True + client.close() + return True except Exception as e: print(f"Error unsticking item: {e}") return False @@ -593,125 +733,173 @@ def unstuck_item(id): def get_predefined_filter_values(filter_num): """ Get predefined values for a specific filter. + + Args: + filter_num (int): Filter number (1 for Unterrichtsfach, 2 for Jahrgangsstufe) + + Returns: + list: List of predefined filter values """ - with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client: + client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) + db = client[cfg.MONGODB_DB] + + # Use a dedicated collection for filter presets + filter_presets = db['filter_presets'] + + # Find the document for the specified filter + filter_doc = filter_presets.find_one({'filter_num': filter_num}) + + client.close() + + if filter_doc and 'values' in filter_doc: + # Sort values alphabetically + return sorted(filter_doc['values']) + else: + # Create empty document if it doesn't exist + client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) db = client[cfg.MONGODB_DB] filter_presets = db['filter_presets'] - - filter_doc = filter_presets.find_one({'filter_num': filter_num}) - - if filter_doc and 'values' in filter_doc: - return sorted(filter_doc['values']) - else: - filter_presets.update_one( - {'filter_num': filter_num}, - {'$set': {'values': []}}, - upsert=True - ) - return [] - + filter_presets.update_one( + {'filter_num': filter_num}, + {'$set': {'values': []}}, + upsert=True + ) + client.close() + return [] def add_predefined_filter_value(filter_num, value): """ Add a new predefined value to a filter. + + Args: + filter_num (int): Filter number (1 for Unterrichtsfach, 2 for Jahrgangsstufe) + value (str): Value to add + + Returns: + bool: True if value was added, False if it already existed """ - with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client: - db = client[cfg.MONGODB_DB] - filter_presets = db['filter_presets'] + client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) + db = client[cfg.MONGODB_DB] + filter_presets = db['filter_presets'] - filter_doc = filter_presets.find_one({ - 'filter_num': filter_num, - 'values': value - }) + # Check if value already exists + filter_doc = filter_presets.find_one({ + 'filter_num': filter_num, + 'values': value + }) - if filter_doc: - return False + if filter_doc: + # Value already exists + client.close() + return False - result = filter_presets.update_one( - {'filter_num': filter_num}, - {'$push': {'values': value}}, - upsert=True - ) - return result.modified_count > 0 or result.upserted_id is not None + # Add the value to the filter + result = filter_presets.update_one( + {'filter_num': filter_num}, + {'$push': {'values': value}}, + upsert=True + ) + client.close() + return result.modified_count > 0 or result.upserted_id is not None def remove_predefined_filter_value(filter_num, value): """ Remove a predefined value from a filter. - """ - with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client: - db = client[cfg.MONGODB_DB] - filter_presets = db['filter_presets'] - result = filter_presets.update_one( - {'filter_num': filter_num}, - {'$pull': {'values': value}} - ) - return result.modified_count > 0 + Args: + filter_num (int): Filter number (1 for Unterrichtsfach, 2 for Jahrgangsstufe) + value (str): Value to remove + + Returns: + bool: True if value was removed, False otherwise + """ + client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) + db = client[cfg.MONGODB_DB] + filter_presets = db['filter_presets'] + + # Remove the value from the filter + result = filter_presets.update_one( + {'filter_num': filter_num}, + {'$pull': {'values': value}} + ) + + client.close() + return result.modified_count > 0 def edit_predefined_filter_value(filter_num, old_value, new_value): """ Edit a predefined value from a filter and update all matching items. + + Args: + filter_num (int): Filter number (1 for Unterrichtsfach, 2 for Jahrgangsstufe) + old_value (str): Value to replace + new_value (str): New value + + Returns: + bool: True if value was updated, False otherwise """ - with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client: - db = client[cfg.MONGODB_DB] - filter_presets = db['filter_presets'] + client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) + db = client[cfg.MONGODB_DB] + filter_presets = db['filter_presets'] - existing = filter_presets.find_one({ - 'filter_num': filter_num, - 'values': new_value - }) + # Check if the new value already exists + existing = filter_presets.find_one({ + 'filter_num': filter_num, + 'values': new_value + }) - if existing and old_value != new_value: - return False + if existing and old_value != new_value: + client.close() + return False - result = filter_presets.update_one( - {'filter_num': filter_num, 'values': old_value}, - {'$set': {'values.$': new_value}} + # Update the value in the filter + result = filter_presets.update_one( + {'filter_num': filter_num, 'values': old_value}, + {'$set': {'values.$': new_value}} + ) + + if result.modified_count > 0: + items = db['items'] + filter_field = 'Filter' if filter_num == 1 else f'Filter{filter_num}' + + # Also update all items that use this filter + items.update_many( + {filter_field: old_value}, + {'$set': {filter_field: new_value}} ) - if result.modified_count > 0: - items = db['items'] - filter_field = 'Filter' if filter_num == 1 else f'Filter{filter_num}' - - items.update_many( - {filter_field: old_value}, - {'$set': {filter_field: new_value}} - ) - - return result.modified_count > 0 - + client.close() + return result.modified_count > 0 def get_filter_names(): """Get customized filter category names.""" - with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client: - db = client[cfg.MONGODB_DB] - names_doc = db.settings.find_one({'setting_type': 'filter_names'}) - - if names_doc and 'names' in names_doc: - return names_doc['names'] - - return { - '1': 'Fach/Kategorie', - '2': 'System/Bereich', - '3': 'Typ/Art' - } - + client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) + db = client[cfg.MONGODB_DB] + names_doc = db.settings.find_one({'setting_type': 'filter_names'}) + client.close() + if names_doc and 'names' in names_doc: + return names_doc['names'] + return { + '1': 'Fach/Kategorie', + '2': 'System/Bereich', + '3': 'Typ/Art' + } def set_filter_name(filter_num, name): """Set custom name for a filter category.""" - with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client: - db = client[cfg.MONGODB_DB] - names = get_filter_names() - names[str(filter_num)] = name - - db.settings.update_one( - {'setting_type': 'filter_names'}, - {'$set': {'names': names}}, - upsert=True - ) - return True + client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) + db = client[cfg.MONGODB_DB] + names = get_filter_names() + names[str(filter_num)] = name + db.settings.update_one( + {'setting_type': 'filter_names'}, + {'$set': {'names': names}}, + upsert=True + ) + client.close() + return True # === LOCATION MANAGEMENT === @@ -719,26 +907,34 @@ def set_filter_name(filter_num, name): def get_predefined_locations(): """ Get list of all predefined locations/placement options. + + Returns: + list: List of predefined location strings """ try: - with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client: - db = client[cfg.MONGODB_DB] + client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) + db = client[cfg.MONGODB_DB] - if 'settings' not in db.list_collection_names(): - db.create_collection('settings') + # Check if settings collection exists, create if not + if 'settings' not in db.list_collection_names(): + db.create_collection('settings') - settings_collection = db['settings'] - location_settings = settings_collection.find_one({'setting_type': 'predefined_locations'}) + # Get settings document or create if it doesn't exist + settings_collection = db['settings'] + location_settings = settings_collection.find_one({'setting_type': 'predefined_locations'}) - if not location_settings: - settings_collection.insert_one({ - 'setting_type': 'predefined_locations', - 'locations': [] - }) - return [] + if not location_settings: + # Create default settings document if it doesn't exist + settings_collection.insert_one({ + 'setting_type': 'predefined_locations', + 'locations': [] + }) + return [] - locations = location_settings.get('locations', []) - return sorted(locations) + # Return the predefined locations + locations = location_settings.get('locations', []) + client.close() + return sorted(locations) except Exception as e: print(f"Error getting predefined locations: {str(e)}") @@ -748,6 +944,12 @@ def get_predefined_locations(): def add_predefined_location(location): """ Add a new predefined location. + + Args: + location (str): Location to add + + Returns: + bool: True if added successfully, False if already exists """ if not location or not isinstance(location, str): return False @@ -757,29 +959,37 @@ def add_predefined_location(location): return False try: - with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client: - db = client[cfg.MONGODB_DB] - settings_collection = db['settings'] + client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) + db = client[cfg.MONGODB_DB] + settings_collection = db['settings'] - location_settings = settings_collection.find_one({'setting_type': 'predefined_locations'}) + # Check if settings document exists, create if not + location_settings = settings_collection.find_one({'setting_type': 'predefined_locations'}) - if not location_settings: - settings_collection.insert_one({ - 'setting_type': 'predefined_locations', - 'locations': [location] - }) - return True - - current_locations = location_settings.get('locations', []) - if any(loc.lower() == location.lower() for loc in current_locations): - return False - - settings_collection.update_one( - {'setting_type': 'predefined_locations'}, - {'$push': {'locations': location}} - ) + if not location_settings: + # Create with the new location + settings_collection.insert_one({ + 'setting_type': 'predefined_locations', + 'locations': [location] + }) + client.close() return True + # Check if location already exists (case-insensitive) + current_locations = location_settings.get('locations', []) + if any(loc.lower() == location.lower() for loc in current_locations): + client.close() + return False + + # Add the new location + settings_collection.update_one( + {'setting_type': 'predefined_locations'}, + {'$push': {'locations': location}} + ) + + client.close() + return True + except Exception as e: print(f"Error adding predefined location: {str(e)}") return False @@ -788,20 +998,28 @@ def add_predefined_location(location): def remove_predefined_location(location): """ Remove a predefined location. + + Args: + location (str): Location to remove + + Returns: + bool: True if removed successfully """ if not location: return False try: - with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client: - db = client[cfg.MONGODB_DB] - settings_collection = db['settings'] + client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) + db = client[cfg.MONGODB_DB] + settings_collection = db['settings'] - result = settings_collection.update_one( - {'setting_type': 'predefined_locations'}, - {'$pull': {'locations': location}} - ) - return result.modified_count > 0 + result = settings_collection.update_one( + {'setting_type': 'predefined_locations'}, + {'$pull': {'locations': location}} + ) + + client.close() + return result.modified_count > 0 except Exception as e: print(f"Error removing predefined location: {str(e)}") @@ -811,36 +1029,122 @@ def remove_predefined_location(location): def update_item_next_appointment(item_id, appointment_data): """ Update an item with information about its next scheduled appointment. + + Args: + item_id (str): ID of the item + appointment_data (dict or None): Dictionary containing appointment details + (e.g., user, start_time, end_time) or None to clear it. + + Returns: + bool: True if successful, False otherwise """ try: - with MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) as client: - db = client[cfg.MONGODB_DB] - items = db['items'] + client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) + db = client[cfg.MONGODB_DB] + items = db['items'] - if appointment_data is None: - update_query = { - '$unset': {'NextAppointment': ""}, - '$set': {'LastUpdated': datetime.datetime.now()} + # If clearing the appointment + if appointment_data is None: + update_query = { + '$unset': {'NextAppointment': ""}, + '$set': {'LastUpdated': datetime.datetime.now()} + } + else: + # Create a copy so we don't mutate the original dictionary passed in + data_to_save = appointment_data.copy() + + # Encrypt the user field if it exists to match the decryption logic at the top + if 'user' in data_to_save and data_to_save['user']: + data_to_save['user'] = dp.encrypt_text(data_to_save['user']) + + update_query = { + '$set': { + 'NextAppointment': data_to_save, + 'LastUpdated': datetime.datetime.now() } - else: - data_to_save = appointment_data.copy() + } - if 'user' in data_to_save and data_to_save['user']: - data_to_save['user'] = dp.encrypt_text(data_to_save['user']) + result = items.update_one( + {'_id': ObjectId(item_id)}, + update_query + ) - update_query = { - '$set': { - 'NextAppointment': data_to_save, - 'LastUpdated': datetime.datetime.now() - } - } - - result = items.update_one( - {'_id': ObjectId(item_id)}, - update_query - ) - - return result.modified_count > 0 + client.close() + return result.modified_count > 0 except Exception as e: print(f"Error updating item next appointment: {e}") - return False \ No newline at end of file + return False + + +def clear_item_next_appointment(item_id): + """ + Clear the next appointment information from an item. + + Args: + item_id (str): ID of the item to update + + Returns: + bool: True if successful, False otherwise + """ + try: + client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) + db = client[cfg.MONGODB_DB] + items = db['items'] + + result = items.update_one( + {'_id': ObjectId(item_id)}, + {'$unset': {'NextAppointment': ""}, '$set': {'LastUpdated': datetime.datetime.now()}} + ) + + client.close() + return result.modified_count > 0 + except Exception as e: + print(f"Error clearing item next appointment: {e}") + return False + + +def get_items_with_appointments(): + """ + Retrieve all items that have scheduled appointments. + + Returns: + list: List of items with NextAppointment field + """ + try: + client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) + db = client[cfg.MONGODB_DB] + items = db['items'] + + items_return = items.find({'NextAppointment': {'$exists': True}, 'Deleted': {'$ne': True}}) + items_list = [] + for item in items_return: + item['_id'] = str(item['_id']) + items_list.append(item) + client.close() + return items_list + except Exception as e: + print(f"Error retrieving items with appointments: {e}") + return [] + +def get_current_status(item_id, decrypt=True): + """ + Retrieve the current status of an item, decrypting the user field if present. + """ + oid = _to_object_id(item_id) + if not oid: + return None + + try: + client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) + db = client[cfg.MONGODB_DB] + items = db['items'] + item = items.find_one({'_id': oid}, {'Verfuegbar': 1, 'User': 1}) + if item: + item['_id'] = str(item['_id']) + if decrypt: + decrypt_item_user_data(item) + return item + return None + except Exception as e: + print(f"Error retrieving current status for item {item_id}: {e}") + return None \ No newline at end of file