"Refactor global refresh functions and templates for better performance (feat)"

This commit is contained in:
2025-05-29 17:51:01 +02:00
parent e338c5835e
commit a473c06658
5 changed files with 958 additions and 3 deletions

View File

@@ -5102,4 +5102,548 @@ def refresh_dashboard():
return jsonify({
'success': False,
'error': 'Fehler beim Aktualisieren der Dashboard-Daten'
}), 500
}), 500
# ===== ADMIN GASTAUFTRÄGE API-ENDPUNKTE =====
@app.route('/api/admin/guest-requests', methods=['GET'])
@admin_required
def get_admin_guest_requests():
"""Gibt alle Gastaufträge für Admin-Verwaltung zurück"""
try:
db_session = get_db_session()
# Parameter auslesen
status = request.args.get('status', 'all')
limit = int(request.args.get('limit', 50))
offset = int(request.args.get('offset', 0))
search = request.args.get('search', '')
# Basis-Query
query = db_session.query(GuestRequest)
# Status-Filter
if status != 'all':
query = query.filter(GuestRequest.status == status)
# Suchfilter
if search:
search_term = f"%{search}%"
query = query.filter(
(GuestRequest.name.ilike(search_term)) |
(GuestRequest.email.ilike(search_term)) |
(GuestRequest.file_name.ilike(search_term)) |
(GuestRequest.reason.ilike(search_term))
)
# Gesamtanzahl vor Pagination
total = query.count()
# Sortierung und Pagination
requests = query.order_by(GuestRequest.created_at.desc()).offset(offset).limit(limit).all()
# Statistiken berechnen
stats = {
'total': db_session.query(GuestRequest).count(),
'pending': db_session.query(GuestRequest).filter(GuestRequest.status == 'pending').count(),
'approved': db_session.query(GuestRequest).filter(GuestRequest.status == 'approved').count(),
'rejected': db_session.query(GuestRequest).filter(GuestRequest.status == 'rejected').count(),
}
# Requests zu Dictionary konvertieren
requests_data = []
for req in requests:
# Priorität berechnen
now = datetime.now()
hours_old = (now - req.created_at).total_seconds() / 3600 if req.created_at else 0
is_urgent = hours_old > 24 and req.status == 'pending'
request_data = {
'id': req.id,
'name': req.name,
'email': req.email,
'file_name': req.file_name,
'file_path': req.file_path,
'duration_minutes': req.duration_minutes,
'copies': req.copies,
'reason': req.reason,
'status': req.status,
'created_at': req.created_at.isoformat() if req.created_at else None,
'updated_at': req.updated_at.isoformat() if req.updated_at else None,
'approved_at': req.approved_at.isoformat() if req.approved_at else None,
'rejected_at': req.rejected_at.isoformat() if req.rejected_at else None,
'approval_notes': req.approval_notes,
'rejection_reason': req.rejection_reason,
'is_urgent': is_urgent,
'hours_old': round(hours_old, 1)
}
requests_data.append(request_data)
db_session.close()
app_logger.info(f"Admin-Gastaufträge geladen: {len(requests_data)} von {total} (Status: {status})")
return jsonify({
'success': True,
'requests': requests_data,
'stats': stats,
'total': total,
'offset': offset,
'limit': limit,
'has_more': offset + limit < total
})
except Exception as e:
app_logger.error(f"Fehler beim Laden der Admin-Gastaufträge: {str(e)}")
return jsonify({
'success': False,
'message': f'Fehler beim Laden der Gastaufträge: {str(e)}'
}), 500
@app.route('/api/guest-requests/<int:request_id>/approve', methods=['POST'])
@admin_required
def approve_guest_request(request_id):
"""Genehmigt einen Gastauftrag"""
try:
db_session = get_db_session()
guest_request = db_session.query(GuestRequest).filter(GuestRequest.id == request_id).first()
if not guest_request:
db_session.close()
return jsonify({
'success': False,
'message': 'Gastauftrag nicht gefunden'
}), 404
if guest_request.status != 'pending':
db_session.close()
return jsonify({
'success': False,
'message': f'Gastauftrag kann im Status "{guest_request.status}" nicht genehmigt werden'
}), 400
# Daten aus Request Body
data = request.get_json() or {}
notes = data.get('notes', '')
printer_id = data.get('printer_id')
# Status aktualisieren
guest_request.status = 'approved'
guest_request.approved_at = datetime.now()
guest_request.approved_by = current_user.id
guest_request.approval_notes = notes
guest_request.updated_at = datetime.now()
# Falls Drucker zugewiesen werden soll
if printer_id:
printer = db_session.query(Printer).filter(Printer.id == printer_id).first()
if printer:
guest_request.assigned_printer_id = printer_id
# OTP-Code generieren für den Gast
import secrets
otp_code = ''.join([str(secrets.randbelow(10)) for _ in range(6)])
guest_request.otp_code = otp_code
guest_request.otp_expires_at = datetime.now() + timedelta(hours=24)
db_session.commit()
# Benachrichtigung an den Gast senden (falls E-Mail verfügbar)
if guest_request.email:
try:
# Hier würde normalerweise eine E-Mail gesendet werden
app_logger.info(f"E-Mail-Benachrichtigung würde an {guest_request.email} gesendet (OTP: {otp_code})")
except Exception as e:
app_logger.warning(f"Fehler beim Senden der E-Mail-Benachrichtigung: {str(e)}")
db_session.close()
app_logger.info(f"Gastauftrag {request_id} von Admin {current_user.id} genehmigt (OTP: {otp_code})")
return jsonify({
'success': True,
'message': 'Gastauftrag erfolgreich genehmigt',
'otp_code': otp_code,
'expires_at': (datetime.now() + timedelta(hours=24)).isoformat()
})
except Exception as e:
app_logger.error(f"Fehler beim Genehmigen des Gastauftrags {request_id}: {str(e)}")
return jsonify({
'success': False,
'message': f'Fehler beim Genehmigen: {str(e)}'
}), 500
@app.route('/api/guest-requests/<int:request_id>/reject', methods=['POST'])
@admin_required
def reject_guest_request(request_id):
"""Lehnt einen Gastauftrag ab"""
try:
db_session = get_db_session()
guest_request = db_session.query(GuestRequest).filter(GuestRequest.id == request_id).first()
if not guest_request:
db_session.close()
return jsonify({
'success': False,
'message': 'Gastauftrag nicht gefunden'
}), 404
if guest_request.status != 'pending':
db_session.close()
return jsonify({
'success': False,
'message': f'Gastauftrag kann im Status "{guest_request.status}" nicht abgelehnt werden'
}), 400
# Daten aus Request Body
data = request.get_json() or {}
reason = data.get('reason', '').strip()
if not reason:
db_session.close()
return jsonify({
'success': False,
'message': 'Ablehnungsgrund ist erforderlich'
}), 400
# Status aktualisieren
guest_request.status = 'rejected'
guest_request.rejected_at = datetime.now()
guest_request.rejected_by = current_user.id
guest_request.rejection_reason = reason
guest_request.updated_at = datetime.now()
db_session.commit()
# Benachrichtigung an den Gast senden (falls E-Mail verfügbar)
if guest_request.email:
try:
# Hier würde normalerweise eine E-Mail gesendet werden
app_logger.info(f"Ablehnungs-E-Mail würde an {guest_request.email} gesendet (Grund: {reason})")
except Exception as e:
app_logger.warning(f"Fehler beim Senden der Ablehnungs-E-Mail: {str(e)}")
db_session.close()
app_logger.info(f"Gastauftrag {request_id} von Admin {current_user.id} abgelehnt (Grund: {reason})")
return jsonify({
'success': True,
'message': 'Gastauftrag erfolgreich abgelehnt'
})
except Exception as e:
app_logger.error(f"Fehler beim Ablehnen des Gastauftrags {request_id}: {str(e)}")
return jsonify({
'success': False,
'message': f'Fehler beim Ablehnen: {str(e)}'
}), 500
@app.route('/api/guest-requests/<int:request_id>', methods=['DELETE'])
@admin_required
def delete_guest_request(request_id):
"""Löscht einen Gastauftrag"""
try:
db_session = get_db_session()
guest_request = db_session.query(GuestRequest).filter(GuestRequest.id == request_id).first()
if not guest_request:
db_session.close()
return jsonify({
'success': False,
'message': 'Gastauftrag nicht gefunden'
}), 404
# Datei löschen falls vorhanden
if guest_request.file_path and os.path.exists(guest_request.file_path):
try:
os.remove(guest_request.file_path)
app_logger.info(f"Datei {guest_request.file_path} für Gastauftrag {request_id} gelöscht")
except Exception as e:
app_logger.warning(f"Fehler beim Löschen der Datei: {str(e)}")
# Gastauftrag aus Datenbank löschen
request_name = guest_request.name
db_session.delete(guest_request)
db_session.commit()
db_session.close()
app_logger.info(f"Gastauftrag {request_id} ({request_name}) von Admin {current_user.id} gelöscht")
return jsonify({
'success': True,
'message': 'Gastauftrag erfolgreich gelöscht'
})
except Exception as e:
app_logger.error(f"Fehler beim Löschen des Gastauftrags {request_id}: {str(e)}")
return jsonify({
'success': False,
'message': f'Fehler beim Löschen: {str(e)}'
}), 500
@app.route('/api/guest-requests/<int:request_id>', methods=['GET'])
@admin_required
def get_guest_request_detail(request_id):
"""Gibt Details eines spezifischen Gastauftrags zurück"""
try:
db_session = get_db_session()
guest_request = db_session.query(GuestRequest).filter(GuestRequest.id == request_id).first()
if not guest_request:
db_session.close()
return jsonify({
'success': False,
'message': 'Gastauftrag nicht gefunden'
}), 404
# Detaildaten zusammenstellen
request_data = {
'id': guest_request.id,
'name': guest_request.name,
'email': guest_request.email,
'file_name': guest_request.file_name,
'file_path': guest_request.file_path,
'file_size': None,
'duration_minutes': guest_request.duration_minutes,
'copies': guest_request.copies,
'reason': guest_request.reason,
'status': guest_request.status,
'created_at': guest_request.created_at.isoformat() if guest_request.created_at else None,
'updated_at': guest_request.updated_at.isoformat() if guest_request.updated_at else None,
'approved_at': guest_request.approved_at.isoformat() if guest_request.approved_at else None,
'rejected_at': guest_request.rejected_at.isoformat() if guest_request.rejected_at else None,
'approval_notes': guest_request.approval_notes,
'rejection_reason': guest_request.rejection_reason,
'otp_code': guest_request.otp_code,
'otp_expires_at': guest_request.otp_expires_at.isoformat() if guest_request.otp_expires_at else None,
'author_ip': guest_request.author_ip
}
# Dateigröße ermitteln
if guest_request.file_path and os.path.exists(guest_request.file_path):
try:
file_size = os.path.getsize(guest_request.file_path)
request_data['file_size'] = file_size
request_data['file_size_mb'] = round(file_size / (1024 * 1024), 2)
except Exception as e:
app_logger.warning(f"Fehler beim Ermitteln der Dateigröße: {str(e)}")
# Bearbeiter-Informationen hinzufügen
if guest_request.approved_by:
approved_by_user = db_session.query(User).filter(User.id == guest_request.approved_by).first()
if approved_by_user:
request_data['approved_by_name'] = approved_by_user.name or approved_by_user.username
if guest_request.rejected_by:
rejected_by_user = db_session.query(User).filter(User.id == guest_request.rejected_by).first()
if rejected_by_user:
request_data['rejected_by_name'] = rejected_by_user.name or rejected_by_user.username
# Zugewiesener Drucker
if hasattr(guest_request, 'assigned_printer_id') and guest_request.assigned_printer_id:
assigned_printer = db_session.query(Printer).filter(Printer.id == guest_request.assigned_printer_id).first()
if assigned_printer:
request_data['assigned_printer'] = {
'id': assigned_printer.id,
'name': assigned_printer.name,
'location': assigned_printer.location,
'status': assigned_printer.status
}
db_session.close()
return jsonify({
'success': True,
'request': request_data
})
except Exception as e:
app_logger.error(f"Fehler beim Abrufen der Gastauftrag-Details {request_id}: {str(e)}")
return jsonify({
'success': False,
'message': f'Fehler beim Abrufen der Details: {str(e)}'
}), 500
@app.route('/api/admin/guest-requests/stats', methods=['GET'])
@admin_required
def get_guest_requests_stats():
"""Gibt detaillierte Statistiken zu Gastaufträgen zurück"""
try:
db_session = get_db_session()
# Basis-Statistiken
total = db_session.query(GuestRequest).count()
pending = db_session.query(GuestRequest).filter(GuestRequest.status == 'pending').count()
approved = db_session.query(GuestRequest).filter(GuestRequest.status == 'approved').count()
rejected = db_session.query(GuestRequest).filter(GuestRequest.status == 'rejected').count()
# Zeitbasierte Statistiken
today = datetime.now().date()
week_ago = datetime.now() - timedelta(days=7)
month_ago = datetime.now() - timedelta(days=30)
today_requests = db_session.query(GuestRequest).filter(
func.date(GuestRequest.created_at) == today
).count()
week_requests = db_session.query(GuestRequest).filter(
GuestRequest.created_at >= week_ago
).count()
month_requests = db_session.query(GuestRequest).filter(
GuestRequest.created_at >= month_ago
).count()
# Dringende Requests (älter als 24h und pending)
urgent_cutoff = datetime.now() - timedelta(hours=24)
urgent_requests = db_session.query(GuestRequest).filter(
GuestRequest.status == 'pending',
GuestRequest.created_at < urgent_cutoff
).count()
# Durchschnittliche Bearbeitungszeit
avg_processing_time = None
try:
processed_requests = db_session.query(GuestRequest).filter(
GuestRequest.status.in_(['approved', 'rejected']),
GuestRequest.updated_at.isnot(None)
).all()
if processed_requests:
total_time = sum([
(req.updated_at - req.created_at).total_seconds()
for req in processed_requests
if req.updated_at and req.created_at
])
avg_processing_time = round(total_time / len(processed_requests) / 3600, 2) # Stunden
except Exception as e:
app_logger.warning(f"Fehler beim Berechnen der durchschnittlichen Bearbeitungszeit: {str(e)}")
# Erfolgsrate
success_rate = 0
if approved + rejected > 0:
success_rate = round((approved / (approved + rejected)) * 100, 1)
stats = {
'total': total,
'pending': pending,
'approved': approved,
'rejected': rejected,
'urgent': urgent_requests,
'today': today_requests,
'week': week_requests,
'month': month_requests,
'success_rate': success_rate,
'avg_processing_time_hours': avg_processing_time,
'completion_rate': round(((approved + rejected) / total * 100), 1) if total > 0 else 0
}
db_session.close()
return jsonify({
'success': True,
'stats': stats,
'generated_at': datetime.now().isoformat()
})
except Exception as e:
app_logger.error(f"Fehler beim Abrufen der Gastauftrag-Statistiken: {str(e)}")
return jsonify({
'success': False,
'message': f'Fehler beim Abrufen der Statistiken: {str(e)}'
}), 500
@app.route('/api/admin/guest-requests/export', methods=['GET'])
@admin_required
def export_guest_requests():
"""Exportiert Gastaufträge als CSV"""
try:
db_session = get_db_session()
# Filter-Parameter
status = request.args.get('status', 'all')
start_date = request.args.get('start_date')
end_date = request.args.get('end_date')
# Query aufbauen
query = db_session.query(GuestRequest)
if status != 'all':
query = query.filter(GuestRequest.status == status)
if start_date:
try:
start_dt = datetime.fromisoformat(start_date)
query = query.filter(GuestRequest.created_at >= start_dt)
except ValueError:
pass
if end_date:
try:
end_dt = datetime.fromisoformat(end_date)
query = query.filter(GuestRequest.created_at <= end_dt)
except ValueError:
pass
requests = query.order_by(GuestRequest.created_at.desc()).all()
# CSV-Daten erstellen
import csv
import io
output = io.StringIO()
writer = csv.writer(output)
# Header
writer.writerow([
'ID', 'Name', 'E-Mail', 'Datei', 'Status', 'Erstellt am',
'Dauer (Min)', 'Kopien', 'Begründung', 'Genehmigt am',
'Abgelehnt am', 'Bearbeitungsnotizen', 'Ablehnungsgrund'
])
# Daten
for req in requests:
writer.writerow([
req.id,
req.name or '',
req.email or '',
req.file_name or '',
req.status,
req.created_at.strftime('%Y-%m-%d %H:%M:%S') if req.created_at else '',
req.duration_minutes or '',
req.copies or '',
req.reason or '',
req.approved_at.strftime('%Y-%m-%d %H:%M:%S') if req.approved_at else '',
req.rejected_at.strftime('%Y-%m-%d %H:%M:%S') if req.rejected_at else '',
req.approval_notes or '',
req.rejection_reason or ''
])
db_session.close()
# Response erstellen
output_value = output.getvalue()
output.close()
response = make_response(output_value)
response.headers["Content-Disposition"] = f"attachment; filename=gastauftraege_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
response.headers["Content-Type"] = "text/csv; charset=utf-8"
app_logger.info(f"Gastaufträge-Export erstellt: {len(requests)} Einträge")
return response
except Exception as e:
app_logger.error(f"Fehler beim Exportieren der Gastaufträge: {str(e)}")
return jsonify({
'success': False,
'message': f'Fehler beim Export: {str(e)}'
}), 500
# ===== ENDE ADMIN GASTAUFTRÄGE API-ENDPUNKTE =====