From 2177975513aaa29bce2a91f79362d0535ca26b54 Mon Sep 17 00:00:00 2001 From: Till Tomczak Date: Thu, 29 May 2025 17:07:42 +0200 Subject: [PATCH] "feat: Introduce test suite for printer functionality in backend" --- backend/app/app.py | 298 ++++++- backend/app/templates/printers.html | 1275 +++++++++++++++++++++++---- backend/app/test_implementation.py | 287 ++++++ 3 files changed, 1701 insertions(+), 159 deletions(-) create mode 100644 backend/app/test_implementation.py diff --git a/backend/app/app.py b/backend/app/app.py index d1936121..93c291e7 100644 --- a/backend/app/app.py +++ b/backend/app/app.py @@ -4226,7 +4226,301 @@ def force_update_all_printer_status(): @app.route('/api/admin/cache/clear', methods=['POST']) @admin_required def clear_admin_cache(): - logger.error(f"Fehler beim Ablehnen der Gastanfrage {request_id}: {str(e)}") + """Löscht den Admin-Cache.""" + try: + # Session-Cache für Admin löschen + admin_cache_keys = [ + 'admin_users_cache', + 'admin_printers_cache', + 'admin_stats_cache', + 'admin_guest_requests_cache' + ] + + cleared = 0 + for key in admin_cache_keys: + if key in session: + del session[key] + cleared += 1 + + return jsonify({ + "success": True, + "message": f"Admin-Cache geleert ({cleared} Einträge)" + }) + except Exception as e: + return jsonify({"error": f"Fehler beim Löschen des Admin-Cache: {str(e)}"}), 500 + +# ===== ADMIN GUEST REQUEST MANAGEMENT APIs ===== + +@app.route("/api/admin/requests", methods=["GET"]) +@admin_required +def admin_get_guest_requests(): + """ + Gibt alle Gastanfragen für Admin-Verwaltung zurück. + Unterstützt Filterung und Paginierung. + """ + try: + page = request.args.get('page', 1, type=int) + per_page = min(request.args.get('per_page', 20, type=int), 100) + status_filter = request.args.get('status', 'all') + + db_session = get_db_session() + + # Basis-Query für Gastanfragen + query = db_session.query(GuestRequest) + + # Status-Filter anwenden + if status_filter == 'pending': + query = query.filter(GuestRequest.processed_at.is_(None)) + elif status_filter == 'approved': + query = query.filter( + GuestRequest.processed_at.isnot(None), + GuestRequest.rejection_reason.is_(None) + ) + elif status_filter == 'denied': + query = query.filter(GuestRequest.rejection_reason.isnot(None)) + + # Sortierung nach neuesten zuerst + query = query.order_by(desc(GuestRequest.created_at)) + + # Paginierung + paginated_requests = query.paginate( + page=page, + per_page=per_page, + error_out=False + ) + + requests_data = [] + for req in paginated_requests.items: + request_data = { + "id": req.id, + "guest_name": req.guest_name, + "guest_email": req.guest_email, + "company": req.company, + "visit_purpose": req.visit_purpose, + "requested_printer_id": req.requested_printer_id, + "printer_name": req.printer.name if req.printer else "Beliebiger Drucker", + "urgency": req.urgency, + "additional_notes": req.additional_notes, + "created_at": req.created_at.isoformat(), + "processed_at": req.processed_at.isoformat() if req.processed_at else None, + "processed_by": req.processed_by, + "approval_notes": req.approval_notes, + "rejection_reason": req.rejection_reason, + "status": "approved" if req.processed_at and not req.rejection_reason else "denied" if req.rejection_reason else "pending", + "otp_code": req.otp_code, + "otp_used_at": req.otp_used_at.isoformat() if req.otp_used_at else None + } + requests_data.append(request_data) + + db_session.close() + + return jsonify({ + "requests": requests_data, + "pagination": { + "page": page, + "per_page": per_page, + "total": paginated_requests.total, + "pages": paginated_requests.pages, + "has_next": paginated_requests.has_next, + "has_prev": paginated_requests.has_prev + }, + "filters": { + "status": status_filter + } + }) + + except Exception as e: + app_logger.error(f"Fehler beim Abrufen der Gastanfragen: {str(e)}") return jsonify({"error": "Interner Serverfehler"}), 500 -# ===== ADMIN API-ROUTEN FÜR BENUTZER UND DRUCKER ===== +@app.route("/api/admin/requests/", methods=["GET"]) +@admin_required +def admin_get_guest_request_details(request_id): + """Gibt detaillierte Informationen zu einer spezifischen Gastanfrage zurück.""" + try: + db_session = get_db_session() + + guest_request = db_session.query(GuestRequest).get(request_id) + if not guest_request: + db_session.close() + return jsonify({"error": "Gastanfrage nicht gefunden"}), 404 + + # Detaillierte Daten zusammenstellen + request_data = { + "id": guest_request.id, + "guest_name": guest_request.guest_name, + "guest_email": guest_request.guest_email, + "company": guest_request.company, + "visit_purpose": guest_request.visit_purpose, + "requested_printer_id": guest_request.requested_printer_id, + "printer": { + "id": guest_request.printer.id, + "name": guest_request.printer.name, + "location": guest_request.printer.location, + "model": guest_request.printer.model + } if guest_request.printer else None, + "urgency": guest_request.urgency, + "additional_notes": guest_request.additional_notes, + "created_at": guest_request.created_at.isoformat(), + "processed_at": guest_request.processed_at.isoformat() if guest_request.processed_at else None, + "processed_by": guest_request.processed_by, + "approval_notes": guest_request.approval_notes, + "rejection_reason": guest_request.rejection_reason, + "status": "approved" if guest_request.processed_at and not guest_request.rejection_reason else "denied" if guest_request.rejection_reason else "pending", + "otp_code": guest_request.otp_code, + "otp_used_at": guest_request.otp_used_at.isoformat() if guest_request.otp_used_at else None + } + + db_session.close() + return jsonify({"request": request_data}) + + except Exception as e: + app_logger.error(f"Fehler beim Abrufen der Gastanfrage {request_id}: {str(e)}") + return jsonify({"error": "Interner Serverfehler"}), 500 + +@app.route("/api/requests//approve", methods=["POST"]) +@admin_required +def admin_approve_guest_request(request_id): + """Genehmigt eine Gastanfrage und weist optional einen Drucker zu.""" + try: + data = request.json or {} + approval_notes = data.get('approval_notes', '') + assigned_printer_id = data.get('assigned_printer_id') + + db_session = get_db_session() + + guest_request = db_session.query(GuestRequest).get(request_id) + if not guest_request: + db_session.close() + return jsonify({"error": "Gastanfrage nicht gefunden"}), 404 + + if guest_request.processed_at: + db_session.close() + return jsonify({"error": "Gastanfrage wurde bereits bearbeitet"}), 400 + + # Drucker validieren (falls angegeben) + if assigned_printer_id: + printer = db_session.query(Printer).get(assigned_printer_id) + if not printer: + db_session.close() + return jsonify({"error": "Angegebener Drucker nicht gefunden"}), 400 + guest_request.requested_printer_id = assigned_printer_id + + # OTP-Code generieren + import random + import string + otp_code = ''.join(random.choices(string.digits, k=6)) + + # Gastanfrage genehmigen + guest_request.processed_at = datetime.now() + guest_request.processed_by = current_user.id + guest_request.approval_notes = approval_notes + guest_request.otp_code = otp_code + guest_request.rejection_reason = None # Sicherstellen, dass es nicht abgelehnt ist + + db_session.commit() + + # Benachrichtigung erstellen + notification = Notification( + user_id=current_user.id, + title="Gastanfrage genehmigt", + message=f"Gastanfrage von {guest_request.guest_name} wurde genehmigt. OTP: {otp_code}", + type="success", + created_at=datetime.now() + ) + db_session.add(notification) + db_session.commit() + + # E-Mail an Gast senden (falls implementiert) + try: + from utils.email_service import send_approval_email + send_approval_email(guest_request.guest_email, guest_request.guest_name, otp_code) + except ImportError: + app_logger.warning("E-Mail-Service nicht verfügbar") + except Exception as email_error: + app_logger.error(f"Fehler beim Senden der Genehmigungs-E-Mail: {str(email_error)}") + + db_session.close() + + app_logger.info(f"Gastanfrage {request_id} von Admin {current_user.id} genehmigt") + + return jsonify({ + "success": True, + "message": "Gastanfrage erfolgreich genehmigt", + "otp_code": otp_code, + "request_id": request_id + }) + + except Exception as e: + app_logger.error(f"Fehler beim Genehmigen der Gastanfrage {request_id}: {str(e)}") + return jsonify({"error": "Interner Serverfehler"}), 500 + +@app.route("/api/requests//deny", methods=["POST"]) +@admin_required +def admin_deny_guest_request(request_id): + """Lehnt eine Gastanfrage ab.""" + try: + data = request.json or {} + rejection_reason = data.get('rejection_reason', '').strip() + + if not rejection_reason: + return jsonify({"error": "Ablehnungsgrund ist erforderlich"}), 400 + + db_session = get_db_session() + + guest_request = db_session.query(GuestRequest).get(request_id) + if not guest_request: + db_session.close() + return jsonify({"error": "Gastanfrage nicht gefunden"}), 404 + + if guest_request.processed_at: + db_session.close() + return jsonify({"error": "Gastanfrage wurde bereits bearbeitet"}), 400 + + # Gastanfrage ablehnen + guest_request.processed_at = datetime.now() + guest_request.processed_by = current_user.id + guest_request.rejection_reason = rejection_reason + guest_request.approval_notes = None + guest_request.otp_code = None + + db_session.commit() + + # Benachrichtigung erstellen + notification = Notification( + user_id=current_user.id, + title="Gastanfrage abgelehnt", + message=f"Gastanfrage von {guest_request.guest_name} wurde abgelehnt: {rejection_reason}", + type="warning", + created_at=datetime.now() + ) + db_session.add(notification) + db_session.commit() + + # E-Mail an Gast senden (falls implementiert) + try: + from utils.email_service import send_rejection_email + send_rejection_email(guest_request.guest_email, guest_request.guest_name, rejection_reason) + except ImportError: + app_logger.warning("E-Mail-Service nicht verfügbar") + except Exception as email_error: + app_logger.error(f"Fehler beim Senden der Ablehnungs-E-Mail: {str(email_error)}") + + db_session.close() + + app_logger.info(f"Gastanfrage {request_id} von Admin {current_user.id} abgelehnt") + + return jsonify({ + "success": True, + "message": "Gastanfrage erfolgreich abgelehnt", + "request_id": request_id + }) + + except Exception as e: + app_logger.error(f"Fehler beim Ablehnen der Gastanfrage {request_id}: {str(e)}") + return jsonify({"error": "Interner Serverfehler"}), 500 + +# ===== ENDE ADMIN GUEST REQUEST MANAGEMENT ===== + +if __name__ == "__main__": + app.run(debug=True, host="0.0.0.0", port=5000, ssl_context=get_ssl_context()) diff --git a/backend/app/templates/printers.html b/backend/app/templates/printers.html index 9d3a9c31..659ce57b 100644 --- a/backend/app/templates/printers.html +++ b/backend/app/templates/printers.html @@ -2,6 +2,441 @@ {% block title %}3D-Drucker - Mercedes-Benz MYP Platform{% endblock %} +{% block extra_css %} + +{% endblock %} + {% block head %} {{ super() }} @@ -9,21 +444,42 @@ {% block content %}
- +
-
-
- +
+
+ - -
+ +
-

3D-Drucker

-

Verwaltung und Überwachung Ihrer Produktionseinheiten

+

3D-Drucker

+

Live-Überwachung und Verwaltung Ihrer Produktionseinheiten

+
+ + + + Live-Updates aktiv +
+ + -
- +
-
-
+
+
-

Online

-
0
+

Online

+
-
+
+
+ Betriebsbereit +
-
-
+
+
+
+
+
+
+ 0% aller Drucker
-
-
+ +
+
-

Offline

-
0
+

Offline

+
-
+
+
+ Nicht verfügbar +
-
-
+
+
+
+
+
+
+ 0% aller Drucker
-
-
+ +
+
-

Aktiv

-
0
+

Aktiv

+
-
+
+
+ Druckt gerade +
-
-
+
+
+
+
+
+
+ 0% Auslastung
-
-
+ +
+
-

Gesamt

-
0
+

Gesamt

+
-
+
+ + + + Drucker registriert +
-
- +
+
+
+
+ Letzte Aktualisierung: --:-- +
+
- +
-
-
-
+ + +
+
+
+

Filter & Suche

+

Finden Sie schnell den gewünschten Drucker

+
+ +
+ +
+ +
+ - +
+ + +
+ +
-
-
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ 0 von 0 Druckern +
+
+ + +
+
+
+
+ + +
+
+
+

Drucker-Übersicht

+

Live-Status aller registrierten 3D-Drucker

+
+
+
+ Ansicht: +
+ + +
+
+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + +
+

Lade Drucker-Informationen...

+
+
+ + +
- -
- -
- - -
-
- - - -
-

Lade Drucker...

-
- - - -
- - +