Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 25 additions & 49 deletions app/routes/admin/system.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,62 +121,38 @@ def manual_lending():
# Verbrauchsmaterialien laden
consumables = mongodb.find('consumables', {'deleted': {'$ne': True}}, sort=[('name', 1)])

# Hole aktuelle Ausleihen
# Hole aktuelle Ausleihen (optimiert via LendingService Bolt ⚡)
from app.services.lending_service import LendingService
current_lendings = []

# Aktuelle Werkzeug-Ausleihen
active_tool_lendings = mongodb.find('lendings', {'returned_at': None})
# Aktuelle Werkzeug-Ausleihen (Bolt ⚡ N+1 Fix)
active_tool_lendings = LendingService.get_active_lendings()
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
})

# 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
})
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 (Bolt ⚡ N+1 Fix)
recent_consumable_usages = LendingService.get_recent_consumable_usage(limit=100, days=30, only_outputs=True)
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']
})
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):
action_date = lending.get('action_date')
if isinstance(action_date, str):
try:
return datetime.strptime(action_date, '%Y-%m-%d %H:%M:%S')
except (ValueError, TypeError):
return datetime.min
elif isinstance(action_date, datetime):
return action_date
else:
return datetime.min

current_lendings.sort(key=safe_date_key, reverse=True)
current_lendings.sort(key=lambda x: x.get('action_date') if isinstance(x.get('action_date'), datetime) else datetime.min, reverse=True)

return render_template('admin/manual_lending.html',
tools=tools,
Expand Down
122 changes: 88 additions & 34 deletions app/services/lending_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Alle Ausleihe/Rückgabe-Logik an einem Ort
"""
from typing import Dict, Any, Tuple, Optional, List
from datetime import datetime
from datetime import datetime, timedelta
from app.models.mongodb_database import mongodb
import logging

Expand Down Expand Up @@ -341,54 +341,108 @@ 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 via Aggregation Bolt ⚡)"""
try:
active_lendings = mongodb.find('lendings', {'returned_at': None})
# Aggregation-Pipeline zur Vermeidung von N+1 Lookups
pipeline = [
{'$match': {'returned_at': None}},
{
'$lookup': {
'from': 'tools',
'localField': 'tool_barcode',
'foreignField': 'barcode',
'as': 'tool_info'
}
},
{'$unwind': {'path': '$tool_info', 'preserveNullAndEmptyArrays': False}},
{
'$lookup': {
'from': 'workers',
'localField': 'worker_barcode',
'foreignField': 'barcode',
'as': 'worker_info'
}
},
{'$unwind': {'path': '$worker_info', 'preserveNullAndEmptyArrays': False}},
{'$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 r in results:
tool = r.get('tool_info', {})
worker = r.get('worker_info', {})

# Erweitere Dokument mit Namen für Template-Kompatibilität
r['tool_name'] = tool.get('name', 'Unbekannt')
r['worker_name'] = f"{worker.get('firstname', '')} {worker.get('lastname', '')}".strip() or 'Unbekannt'

# Aufräumen der temporären Aggregations-Felder
r.pop('tool_info', None)
r.pop('worker_info', None)

enriched_lendings.append(r)

if tool and worker:
enriched_lendings.append({
**lending,
'tool_name': tool['name'],
'worker_name': f"{worker['firstname']} {worker['lastname']}",
'lent_at': lending['lent_at']
})

# 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:
logger.error(f"Fehler beim Laden aktiver Ausleihen: [Interner Fehler]")
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 via Aggregation Bolt ⚡)"""
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 aufbauen
match_query = {}
if days:
thirty_days_ago = datetime.now() - timedelta(days=days)
match_query['used_at'] = {'$gte': thirty_days_ago}

if only_outputs:
match_query['quantity'] = {'$lt': 0}

# Aggregation-Pipeline zur Vermeidung von N+1 Lookups
pipeline = [
{'$match': match_query},
{'$sort': {'used_at': -1}},
{'$limit': limit},
{
'$lookup': {
'from': 'consumables',
'localField': 'consumable_barcode',
'foreignField': 'barcode',
'as': 'consumable_info'
}
},
{'$unwind': {'path': '$consumable_info', 'preserveNullAndEmptyArrays': False}},
{
'$lookup': {
'from': 'workers',
'localField': 'worker_barcode',
'foreignField': 'barcode',
'as': 'worker_info'
}
},
{'$unwind': {'path': '$worker_info', 'preserveNullAndEmptyArrays': False}}
]

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 r in results:
consumable = r.get('consumable_info', {})
worker = r.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': r.get('consumable_barcode'),
'quantity': r.get('quantity'),
'worker_name': f"{worker.get('firstname', '')} {worker.get('lastname', '')}".strip() or 'Unbekannt',
'worker_barcode': r.get('worker_barcode'),
'used_at': r.get('used_at')
})

return enriched_usages

Expand Down