From 3b38d9b61b1df1322be9e1cb6fa1757e45b7d745 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 24 Feb 2026 09:22:27 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Optimize=20manual=20lending?= =?UTF-8?q?=20route=20and=20lending=20service?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Optimized `LendingService.get_active_lendings` using a single MongoDB aggregation pipeline with `$lookup`. - Optimized `LendingService.get_recent_consumable_usage` using an aggregation pipeline and added `days` and `only_outputs` parameters. - Updated the `manual_lending` route in `app/routes/admin/system.py` to use these optimized service methods, eliminating multiple N+1 query bottlenecks. - Reduced database roundtrips from O(N) to O(1) for these common operations. Co-authored-by: Woschj <81321922+Woschj@users.noreply.github.com> --- app/routes/admin/system.py | 62 +++++++--------- app/services/lending_service.py | 123 +++++++++++++++++++++++--------- 2 files changed, 116 insertions(+), 69 deletions(-) diff --git a/app/routes/admin/system.py b/app/routes/admin/system.py index ed44480..01265cb 100644 --- a/app/routes/admin/system.py +++ b/app/routes/admin/system.py @@ -4,6 +4,8 @@ @mitarbeiter_required def manual_lending(): """Manuelle Ausleihe/Rückgabe""" + from app.services.lending_service import LendingService + if request.method == 'POST': logger.info("POST-Anfrage für manuelle Ausleihe empfangen") @@ -47,7 +49,6 @@ def manual_lending(): }), 404 # Verwende den zentralen LendingService für konsistente Verarbeitung - from app.services.lending_service import LendingService # Erstelle Request-Daten für den Service service_data = { @@ -121,47 +122,34 @@ def manual_lending(): # Verbrauchsmaterialien laden consumables = mongodb.find('consumables', {'deleted': {'$ne': True}}, sort=[('name', 1)]) - # Hole aktuelle Ausleihen + # Hole aktuelle Ausleihen (Optimiert Bolt ⚡) current_lendings = [] # Aktuelle Werkzeug-Ausleihen - active_tool_lendings = mongodb.find('lendings', {'returned_at': None}) - for lending in active_tool_lendings: - tool = mongodb.find_one('tools', {'barcode': lending['tool_barcode']}) - worker = mongodb.find_one('workers', {'barcode': lending['worker_barcode']}) - - if tool and worker: - current_lendings.append({ - 'item_name': tool['name'], - 'item_barcode': tool['barcode'], - 'worker_name': f"{worker['firstname']} {worker['lastname']}", - 'worker_barcode': worker['barcode'], - 'action_date': lending['lent_at'], - 'category': 'Werkzeug', - 'amount': None - }) + active_tools = LendingService.get_active_lendings() + for lending in active_tools: + current_lendings.append({ + 'item_name': lending.get('tool_name'), + 'item_barcode': lending.get('tool_barcode'), + 'worker_name': lending.get('worker_name'), + 'worker_barcode': lending.get('worker_barcode'), + 'action_date': lending.get('lent_at'), + 'category': 'Werkzeug', + 'amount': None + }) # Aktuelle Verbrauchsmaterial-Ausgaben (letzte 30 Tage) - thirty_days_ago = datetime.now() - timedelta(days=30) - recent_consumable_usages = mongodb.find('consumable_usages', { - 'used_at': {'$gte': thirty_days_ago}, - 'quantity': {'$lt': 0} # Nur Ausgaben (negative Werte), nicht Entnahmen - }) - - for usage in recent_consumable_usages: - consumable = mongodb.find_one('consumables', {'barcode': usage['consumable_barcode']}) - worker = mongodb.find_one('workers', {'barcode': usage['worker_barcode']}) - - if consumable and worker: - current_lendings.append({ - 'item_name': consumable['name'], - 'item_barcode': consumable['barcode'], - 'worker_name': f"{worker['firstname']} {worker['lastname']}", - 'worker_barcode': worker['barcode'], - 'action_date': usage['used_at'], - 'category': 'Verbrauchsmaterial', - 'amount': usage['quantity'] - }) + recent_consumables = LendingService.get_recent_consumable_usage(limit=1000, days=30, only_outputs=True) + for usage in recent_consumables: + current_lendings.append({ + 'item_name': usage.get('consumable_name'), + 'item_barcode': usage.get('consumable_barcode'), + 'worker_name': usage.get('worker_name'), + 'worker_barcode': usage.get('worker_barcode'), + 'action_date': usage.get('used_at'), + 'category': 'Verbrauchsmaterial', + 'amount': usage.get('quantity') + }) # Sortiere nach Datum (neueste zuerst) def safe_date_key(lending): diff --git a/app/services/lending_service.py b/app/services/lending_service.py index 96110dd..c01c9de 100755 --- a/app/services/lending_service.py +++ b/app/services/lending_service.py @@ -341,26 +341,52 @@ def _process_consumable_lending(item_barcode: str, worker_barcode: str, action: @staticmethod def get_active_lendings() -> list: - """Holt alle aktiven Ausleihen""" + """ + Holt alle aktiven Ausleihen (Optimiert mit Aggregation Bolt ⚡) + Reduziert Datenbank-Roundtrips von O(N) auf O(1) durch Vermeidung von N+1 Queries. + """ try: - active_lendings = mongodb.find('lendings', {'returned_at': None}) + pipeline = [ + {'$match': {'returned_at': None}}, + { + '$lookup': { + 'from': 'tools', + 'localField': 'tool_barcode', + 'foreignField': 'barcode', + 'as': 'tool_info' + } + }, + {'$unwind': '$tool_info'}, + { + '$lookup': { + 'from': 'workers', + 'localField': 'worker_barcode', + 'foreignField': 'barcode', + 'as': 'worker_info' + } + }, + {'$unwind': '$worker_info'}, + {'$sort': {'lent_at': -1}} + ] + + results = mongodb.aggregate('lendings', pipeline) - # Erweitere mit Tool- und Worker-Informationen enriched_lendings = [] - for lending in active_lendings: - tool = mongodb.find_one('tools', {'barcode': lending['tool_barcode']}) - worker = mongodb.find_one('workers', {'barcode': lending['worker_barcode']}) + for lending in results: + tool = lending.get('tool_info') + worker = lending.get('worker_info') - if tool and worker: - enriched_lendings.append({ - **lending, - 'tool_name': tool['name'], - 'worker_name': f"{worker['firstname']} {worker['lastname']}", - 'lent_at': lending['lent_at'] - }) + # Konsistenz mit dem alten Format wahren + enriched_lending = {**lending} + enriched_lending['tool_name'] = tool.get('name', 'Unbekannt') + enriched_lending['worker_name'] = f"{worker.get('firstname', '')} {worker.get('lastname', '')}".strip() or 'Unbekannt' + + # Temporäre Felder entfernen + enriched_lending.pop('tool_info', None) + enriched_lending.pop('worker_info', None) + + enriched_lendings.append(enriched_lending) - # Sortiere nach Datum (neueste zuerst) - enriched_lendings.sort(key=lambda x: x.get('lent_at', datetime.min), reverse=True) return enriched_lendings except Exception as e: @@ -368,27 +394,60 @@ def get_active_lendings() -> list: return [] @staticmethod - def get_recent_consumable_usage(limit: int = 10) -> list: - """Holt die letzten Verbrauchsmaterial-Entnahmen""" + def get_recent_consumable_usage(limit: int = 10, days: int = None, only_outputs: bool = False) -> list: + """ + Holt die letzten Verbrauchsmaterial-Entnahmen (Optimiert Bolt ⚡) + Reduziert Datenbank-Roundtrips von O(N) auf O(1) durch Aggregation von Verbrauch und Worker-Info. + """ try: - recent_usages = mongodb.find('consumable_usages') - # Sortiere und limitiere - recent_usages.sort(key=lambda x: x.get('used_at', datetime.min), reverse=True) - recent_usages = recent_usages[:limit] + match_query = {} + if days: + from datetime import timedelta + time_threshold = datetime.now() - timedelta(days=days) + match_query['used_at'] = {'$gte': time_threshold} + + if only_outputs: + match_query['quantity'] = {'$lt': 0} + + pipeline = [ + {'$match': match_query}, + {'$sort': {'used_at': -1}}, + {'$limit': limit}, + { + '$lookup': { + 'from': 'consumables', + 'localField': 'consumable_barcode', + 'foreignField': 'barcode', + 'as': 'consumable_info' + } + }, + {'$unwind': '$consumable_info'}, + { + '$lookup': { + 'from': 'workers', + 'localField': 'worker_barcode', + 'foreignField': 'barcode', + 'as': 'worker_info' + } + }, + {'$unwind': '$worker_info'} + ] + + results = mongodb.aggregate('consumable_usages', pipeline) - # Erweitere mit Consumable- und Worker-Informationen enriched_usages = [] - for usage in recent_usages: - consumable = mongodb.find_one('consumables', {'barcode': usage['consumable_barcode']}) - worker = mongodb.find_one('workers', {'barcode': usage['worker_barcode']}) + for usage in results: + consumable = usage.get('consumable_info') + worker = usage.get('worker_info') - if consumable and worker: - enriched_usages.append({ - 'consumable_name': consumable['name'], - 'quantity': usage['quantity'], - 'worker_name': f"{worker['firstname']} {worker['lastname']}", - 'used_at': usage['used_at'] - }) + enriched_usages.append({ + 'consumable_name': consumable.get('name', 'Unbekannt'), + 'consumable_barcode': consumable.get('barcode', ''), + 'quantity': usage.get('quantity', 0), + 'worker_name': f"{worker.get('firstname', '')} {worker.get('lastname', '')}".strip() or 'Unbekannt', + 'worker_barcode': worker.get('barcode', ''), + 'used_at': usage.get('used_at') + }) return enriched_usages