"Refactor global refresh functions and templates for better performance (feat)"
This commit is contained in:
@@ -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 =====
|
Reference in New Issue
Block a user