📝 Commit Details:
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,550 @@
|
||||
import json
|
||||
from datetime import datetime, timedelta
|
||||
from flask import Blueprint, render_template, request, jsonify, redirect, url_for, abort
|
||||
from flask_login import current_user, login_required
|
||||
from sqlalchemy import and_, or_, func
|
||||
|
||||
from models import Job, Printer, User, UserPermission, get_cached_session
|
||||
from utils.logging_config import get_logger
|
||||
|
||||
calendar_blueprint = Blueprint('calendar', __name__)
|
||||
logger = get_logger("calendar")
|
||||
|
||||
def can_edit_events(user):
|
||||
"""Prüft, ob ein Benutzer Kalendereinträge bearbeiten darf."""
|
||||
if user.is_admin:
|
||||
return True
|
||||
|
||||
with get_cached_session() as db_session:
|
||||
permission = db_session.query(UserPermission).filter_by(user_id=user.id).first()
|
||||
if not permission:
|
||||
return False
|
||||
return permission.can_approve_jobs
|
||||
|
||||
def get_smart_printer_assignment(start_date, end_date, priority="normal", db_session=None):
|
||||
"""
|
||||
Intelligente Druckerzuweisung basierend auf verschiedenen Faktoren.
|
||||
|
||||
Args:
|
||||
start_date: Startzeit des Jobs
|
||||
end_date: Endzeit des Jobs
|
||||
priority: Prioritätsstufe ('urgent', 'high', 'normal', 'low')
|
||||
db_session: Datenbankverbindung
|
||||
|
||||
Returns:
|
||||
printer_id: ID des empfohlenen Druckers oder None
|
||||
"""
|
||||
if not db_session:
|
||||
return None
|
||||
|
||||
try:
|
||||
# Verfügbare Drucker ermitteln
|
||||
available_printers = db_session.query(Printer).filter(
|
||||
Printer.active == True
|
||||
).all()
|
||||
|
||||
if not available_printers:
|
||||
logger.warning("Keine aktiven Drucker für automatische Zuweisung gefunden")
|
||||
return None
|
||||
|
||||
printer_scores = []
|
||||
|
||||
for printer in available_printers:
|
||||
score = 0
|
||||
|
||||
# 1. Verfügbarkeit prüfen - Jobs im gleichen Zeitraum
|
||||
conflicting_jobs = db_session.query(Job).filter(
|
||||
Job.printer_id == printer.id,
|
||||
Job.status.in_(["scheduled", "running"]),
|
||||
or_(
|
||||
and_(Job.start_at >= start_date, Job.start_at < end_date),
|
||||
and_(Job.end_at > start_date, Job.end_at <= end_date),
|
||||
and_(Job.start_at <= start_date, Job.end_at >= end_date)
|
||||
)
|
||||
).count()
|
||||
|
||||
if conflicting_jobs > 0:
|
||||
continue # Drucker ist nicht verfügbar
|
||||
|
||||
score += 100 # Grundpunkte für Verfügbarkeit
|
||||
|
||||
# 2. Auslastung in den letzten 24 Stunden bewerten
|
||||
last_24h = datetime.now() - timedelta(hours=24)
|
||||
recent_jobs = db_session.query(Job).filter(
|
||||
Job.printer_id == printer.id,
|
||||
Job.start_at >= last_24h,
|
||||
Job.status.in_(["scheduled", "running", "finished"])
|
||||
).count()
|
||||
|
||||
# Weniger Auslastung = höhere Punktzahl
|
||||
score += max(0, 50 - (recent_jobs * 10))
|
||||
|
||||
# 3. Prioritätsbasierte Zuweisung
|
||||
if priority == "urgent":
|
||||
# Für dringende Jobs: Express-Drucker bevorzugen
|
||||
if "express" in printer.name.lower() or "schnell" in printer.name.lower():
|
||||
score += 30
|
||||
elif priority == "high":
|
||||
# Für hohe Priorität: Weniger belastete Drucker
|
||||
if recent_jobs <= 2:
|
||||
score += 20
|
||||
|
||||
# 4. Zeitfenster-basierte Zuweisung
|
||||
start_hour = start_date.hour
|
||||
if start_hour >= 18 or start_hour <= 6: # Nachtschicht
|
||||
if "nacht" in printer.name.lower() or printer.location and "c" in printer.location.lower():
|
||||
score += 25
|
||||
elif start_hour >= 8 and start_hour <= 17: # Tagschicht
|
||||
if "tag" in printer.name.lower() or printer.location and "a" in printer.location.lower():
|
||||
score += 15
|
||||
|
||||
# 5. Standort-basierte Bewertung (Round-Robin ähnlich)
|
||||
if printer.location:
|
||||
location_penalty = hash(printer.location) % 10 # Verteilung basierend auf Standort
|
||||
score += location_penalty
|
||||
|
||||
# 6. Druckerdauer-Eignung
|
||||
job_duration_hours = (end_date - start_date).total_seconds() / 3600
|
||||
if job_duration_hours > 8: # Lange Jobs
|
||||
if "langzeit" in printer.name.lower() or "marathon" in printer.name.lower():
|
||||
score += 20
|
||||
elif job_duration_hours <= 2: # Kurze Jobs
|
||||
if "express" in printer.name.lower() or "schnell" in printer.name.lower():
|
||||
score += 15
|
||||
|
||||
# 7. Wartungszyklen berücksichtigen
|
||||
# Neuere Drucker (falls last_maintenance_date verfügbar) bevorzugen
|
||||
# TODO: Implementierung abhängig von Printer-Model-Erweiterungen
|
||||
|
||||
printer_scores.append({
|
||||
'printer': printer,
|
||||
'score': score,
|
||||
'conflicts': conflicting_jobs,
|
||||
'recent_load': recent_jobs
|
||||
})
|
||||
|
||||
# Nach Punktzahl sortieren und besten Drucker auswählen
|
||||
if not printer_scores:
|
||||
logger.warning("Keine verfügbaren Drucker für den gewünschten Zeitraum gefunden")
|
||||
return None
|
||||
|
||||
printer_scores.sort(key=lambda x: x['score'], reverse=True)
|
||||
best_printer = printer_scores[0]
|
||||
|
||||
logger.info(f"Automatische Druckerzuweisung: {best_printer['printer'].name} "
|
||||
f"(Score: {best_printer['score']}, Load: {best_printer['recent_load']})")
|
||||
|
||||
return best_printer['printer'].id
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler bei automatischer Druckerzuweisung: {str(e)}")
|
||||
return None
|
||||
|
||||
@calendar_blueprint.route('/calendar', methods=['GET'])
|
||||
@login_required
|
||||
def calendar_view():
|
||||
"""Kalender-Ansicht anzeigen."""
|
||||
can_edit = can_edit_events(current_user)
|
||||
|
||||
with get_cached_session() as db_session:
|
||||
printers = db_session.query(Printer).filter_by(active=True).all()
|
||||
|
||||
return render_template('calendar.html',
|
||||
printers=printers,
|
||||
can_edit=can_edit)
|
||||
|
||||
@calendar_blueprint.route('/api/calendar', methods=['GET'])
|
||||
@login_required
|
||||
def api_get_calendar_events():
|
||||
"""Kalendereinträge als JSON für FullCalendar zurückgeben."""
|
||||
try:
|
||||
# Datumsbereich aus Anfrage
|
||||
start_str = request.args.get('from')
|
||||
end_str = request.args.get('to')
|
||||
|
||||
if not start_str or not end_str:
|
||||
# Standardmäßig eine Woche anzeigen
|
||||
start_date = datetime.now()
|
||||
end_date = start_date + timedelta(days=7)
|
||||
else:
|
||||
try:
|
||||
start_date = datetime.fromisoformat(start_str)
|
||||
end_date = datetime.fromisoformat(end_str)
|
||||
except ValueError:
|
||||
return jsonify({"error": "Ungültiges Datumsformat"}), 400
|
||||
|
||||
# Optional: Filter nach Druckern
|
||||
printer_id = request.args.get('printer_id')
|
||||
|
||||
with get_cached_session() as db_session:
|
||||
# Jobs im angegebenen Zeitraum abfragen
|
||||
query = db_session.query(Job).filter(
|
||||
or_(
|
||||
# Jobs, die im Zeitraum beginnen
|
||||
and_(Job.start_at >= start_date, Job.start_at <= end_date),
|
||||
# Jobs, die im Zeitraum enden
|
||||
and_(Job.end_at >= start_date, Job.end_at <= end_date),
|
||||
# Jobs, die den Zeitraum komplett umfassen
|
||||
and_(Job.start_at <= start_date, Job.end_at >= end_date)
|
||||
)
|
||||
)
|
||||
|
||||
if printer_id:
|
||||
query = query.filter(Job.printer_id == printer_id)
|
||||
|
||||
jobs = query.all()
|
||||
|
||||
# Jobs in FullCalendar-Event-Format umwandeln
|
||||
events = []
|
||||
for job in jobs:
|
||||
# Farbe basierend auf Status bestimmen
|
||||
color = "#6B7280" # Grau für pending
|
||||
if job.status == "running":
|
||||
color = "#3B82F6" # Blau für running
|
||||
elif job.status == "finished":
|
||||
color = "#10B981" # Grün für finished
|
||||
elif job.status == "scheduled":
|
||||
color = "#10B981" # Grün für approved
|
||||
elif job.status == "cancelled" or job.status == "failed":
|
||||
color = "#EF4444" # Rot für abgebrochen/fehlgeschlagen
|
||||
|
||||
# Benutzerinformationen laden
|
||||
user = db_session.query(User).filter_by(id=job.user_id).first()
|
||||
user_name = user.name if user else "Unbekannt"
|
||||
|
||||
# Druckerinformationen laden
|
||||
printer = db_session.query(Printer).filter_by(id=job.printer_id).first()
|
||||
printer_name = printer.name if printer else "Unbekannt"
|
||||
|
||||
event = {
|
||||
"id": job.id,
|
||||
"title": job.name,
|
||||
"start": job.start_at.isoformat(),
|
||||
"end": job.end_at.isoformat(),
|
||||
"color": color,
|
||||
"extendedProps": {
|
||||
"status": job.status,
|
||||
"description": job.description,
|
||||
"userName": user_name,
|
||||
"printerId": job.printer_id,
|
||||
"printerName": printer_name
|
||||
}
|
||||
}
|
||||
|
||||
events.append(event)
|
||||
|
||||
return jsonify(events)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Abrufen der Kalendereinträge: {str(e)}")
|
||||
return jsonify({"error": "Fehler beim Verarbeiten der Anfrage"}), 500
|
||||
|
||||
@calendar_blueprint.route('/api/calendar/event', methods=['POST'])
|
||||
@login_required
|
||||
def api_create_calendar_event():
|
||||
"""Neuen Kalendereintrag (Job) erstellen."""
|
||||
# Nur Admins und Benutzer mit can_approve_jobs dürfen Einträge erstellen
|
||||
if not can_edit_events(current_user):
|
||||
return jsonify({"error": "Keine Berechtigung zum Erstellen von Kalendereinträgen"}), 403
|
||||
|
||||
try:
|
||||
data = request.get_json()
|
||||
if not data:
|
||||
return jsonify({"error": "Keine Daten erhalten"}), 400
|
||||
|
||||
# Pflichtfelder prüfen
|
||||
title = data.get('title')
|
||||
start = data.get('start')
|
||||
end = data.get('end')
|
||||
printer_id = data.get('printerId') # Jetzt optional
|
||||
priority = data.get('priority', 'normal')
|
||||
|
||||
if not all([title, start, end]):
|
||||
return jsonify({"error": "Titel, Start und Ende sind erforderlich"}), 400
|
||||
|
||||
# Datumsfelder konvertieren
|
||||
try:
|
||||
start_date = datetime.fromisoformat(start)
|
||||
end_date = datetime.fromisoformat(end)
|
||||
except ValueError:
|
||||
return jsonify({"error": "Ungültiges Datumsformat"}), 400
|
||||
|
||||
# Dauer in Minuten berechnen
|
||||
duration_minutes = int((end_date - start_date).total_seconds() / 60)
|
||||
|
||||
with get_cached_session() as db_session:
|
||||
# Intelligente Druckerzuweisung falls kein Drucker angegeben
|
||||
if not printer_id:
|
||||
logger.info(f"Automatische Druckerzuweisung wird verwendet für Job '{title}'")
|
||||
printer_id = get_smart_printer_assignment(
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
priority=priority,
|
||||
db_session=db_session
|
||||
)
|
||||
|
||||
if not printer_id:
|
||||
return jsonify({
|
||||
"error": "Keine verfügbaren Drucker für den gewünschten Zeitraum gefunden. "
|
||||
"Bitte wählen Sie einen spezifischen Drucker oder einen anderen Zeitraum."
|
||||
}), 409
|
||||
|
||||
# Drucker prüfen/validieren
|
||||
printer = db_session.query(Printer).filter_by(id=printer_id).first()
|
||||
if not printer:
|
||||
return jsonify({"error": "Drucker nicht gefunden"}), 404
|
||||
|
||||
if not printer.active:
|
||||
return jsonify({"error": f"Drucker '{printer.name}' ist nicht aktiv"}), 400
|
||||
|
||||
# Nochmals Verfügbarkeit prüfen bei automatischer Zuweisung
|
||||
if not data.get('printerId'): # War automatische Zuweisung
|
||||
conflicting_jobs = db_session.query(Job).filter(
|
||||
Job.printer_id == printer_id,
|
||||
Job.status.in_(["scheduled", "running"]),
|
||||
or_(
|
||||
and_(Job.start_at >= start_date, Job.start_at < end_date),
|
||||
and_(Job.end_at > start_date, Job.end_at <= end_date),
|
||||
and_(Job.start_at <= start_date, Job.end_at >= end_date)
|
||||
)
|
||||
).first()
|
||||
|
||||
if conflicting_jobs:
|
||||
return jsonify({
|
||||
"error": f"Automatisch zugewiesener Drucker '{printer.name}' ist nicht mehr verfügbar. "
|
||||
"Bitte wählen Sie einen anderen Zeitraum oder spezifischen Drucker."
|
||||
}), 409
|
||||
|
||||
# Neuen Job erstellen
|
||||
job = Job(
|
||||
name=title,
|
||||
description=data.get('description', ''),
|
||||
user_id=current_user.id,
|
||||
printer_id=printer_id,
|
||||
start_at=start_date,
|
||||
end_at=end_date,
|
||||
status="scheduled",
|
||||
duration_minutes=duration_minutes,
|
||||
owner_id=current_user.id
|
||||
)
|
||||
|
||||
db_session.add(job)
|
||||
db_session.commit()
|
||||
|
||||
assignment_type = "automatisch" if not data.get('printerId') else "manuell"
|
||||
logger.info(f"Neuer Kalendereintrag erstellt: ID {job.id}, Name: {title}, "
|
||||
f"Drucker: {printer.name} ({assignment_type} zugewiesen)")
|
||||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"id": job.id,
|
||||
"title": job.name,
|
||||
"start": job.start_at.isoformat(),
|
||||
"end": job.end_at.isoformat(),
|
||||
"status": job.status,
|
||||
"printer": {
|
||||
"id": printer.id,
|
||||
"name": printer.name,
|
||||
"location": printer.location,
|
||||
"assignment_type": assignment_type
|
||||
},
|
||||
"message": f"Auftrag erfolgreich erstellt und {assignment_type} dem Drucker '{printer.name}' zugewiesen."
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Erstellen des Kalendereintrags: {str(e)}")
|
||||
return jsonify({"error": "Fehler beim Verarbeiten der Anfrage"}), 500
|
||||
|
||||
@calendar_blueprint.route('/api/calendar/event/<int:event_id>', methods=['PUT'])
|
||||
@login_required
|
||||
def api_update_calendar_event(event_id):
|
||||
"""Kalendereintrag (Job) aktualisieren."""
|
||||
# Nur Admins und Benutzer mit can_approve_jobs dürfen Einträge bearbeiten
|
||||
if not can_edit_events(current_user):
|
||||
return jsonify({"error": "Keine Berechtigung zum Bearbeiten von Kalendereinträgen"}), 403
|
||||
|
||||
try:
|
||||
data = request.get_json()
|
||||
if not data:
|
||||
return jsonify({"error": "Keine Daten erhalten"}), 400
|
||||
|
||||
with get_cached_session() as db_session:
|
||||
job = db_session.query(Job).filter_by(id=event_id).first()
|
||||
if not job:
|
||||
return jsonify({"error": "Kalendereintrag nicht gefunden"}), 404
|
||||
|
||||
# Felder aktualisieren, die im Request enthalten sind
|
||||
if 'title' in data:
|
||||
job.name = data['title']
|
||||
|
||||
if 'description' in data:
|
||||
job.description = data['description']
|
||||
|
||||
if 'start' in data and 'end' in data:
|
||||
try:
|
||||
start_date = datetime.fromisoformat(data['start'])
|
||||
end_date = datetime.fromisoformat(data['end'])
|
||||
|
||||
job.start_at = start_date
|
||||
job.end_at = end_date
|
||||
job.duration_minutes = int((end_date - start_date).total_seconds() / 60)
|
||||
except ValueError:
|
||||
return jsonify({"error": "Ungültiges Datumsformat"}), 400
|
||||
|
||||
if 'printerId' in data:
|
||||
printer = db_session.query(Printer).filter_by(id=data['printerId']).first()
|
||||
if not printer:
|
||||
return jsonify({"error": "Drucker nicht gefunden"}), 404
|
||||
job.printer_id = data['printerId']
|
||||
|
||||
if 'status' in data:
|
||||
# Status nur ändern, wenn er gültig ist
|
||||
valid_statuses = ["scheduled", "running", "finished", "cancelled"]
|
||||
if data['status'] in valid_statuses:
|
||||
job.status = data['status']
|
||||
|
||||
db_session.commit()
|
||||
|
||||
logger.info(f"Kalendereintrag aktualisiert: ID {job.id}")
|
||||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"id": job.id,
|
||||
"title": job.name,
|
||||
"start": job.start_at.isoformat(),
|
||||
"end": job.end_at.isoformat(),
|
||||
"status": job.status
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Aktualisieren des Kalendereintrags: {str(e)}")
|
||||
return jsonify({"error": "Fehler beim Verarbeiten der Anfrage"}), 500
|
||||
|
||||
@calendar_blueprint.route('/api/calendar/event/<int:event_id>', methods=['DELETE'])
|
||||
@login_required
|
||||
def api_delete_calendar_event(event_id):
|
||||
"""Kalendereintrag (Job) löschen."""
|
||||
# Nur Admins und Benutzer mit can_approve_jobs dürfen Einträge löschen
|
||||
if not can_edit_events(current_user):
|
||||
return jsonify({"error": "Keine Berechtigung zum Löschen von Kalendereinträgen"}), 403
|
||||
|
||||
try:
|
||||
with get_cached_session() as db_session:
|
||||
job = db_session.query(Job).filter_by(id=event_id).first()
|
||||
if not job:
|
||||
return jsonify({"error": "Kalendereintrag nicht gefunden"}), 404
|
||||
|
||||
db_session.delete(job)
|
||||
db_session.commit()
|
||||
|
||||
logger.info(f"Kalendereintrag gelöscht: ID {event_id}")
|
||||
|
||||
return jsonify({"success": True})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Löschen des Kalendereintrags: {str(e)}")
|
||||
return jsonify({"error": "Fehler beim Verarbeiten der Anfrage"}), 500
|
||||
|
||||
@calendar_blueprint.route('/api/calendar/smart-recommendation', methods=['POST'])
|
||||
@login_required
|
||||
def api_get_smart_recommendation():
|
||||
"""Intelligente Druckerempfehlung für gegebenes Zeitfenster abrufen."""
|
||||
try:
|
||||
data = request.get_json()
|
||||
if not data:
|
||||
return jsonify({"error": "Keine Daten erhalten"}), 400
|
||||
|
||||
start = data.get('start')
|
||||
end = data.get('end')
|
||||
priority = data.get('priority', 'normal')
|
||||
|
||||
if not all([start, end]):
|
||||
return jsonify({"error": "Start und Ende sind erforderlich"}), 400
|
||||
|
||||
# Datumsfelder konvertieren
|
||||
try:
|
||||
start_date = datetime.fromisoformat(start)
|
||||
end_date = datetime.fromisoformat(end)
|
||||
except ValueError:
|
||||
return jsonify({"error": "Ungültiges Datumsformat"}), 400
|
||||
|
||||
with get_cached_session() as db_session:
|
||||
# Empfohlenen Drucker ermitteln
|
||||
recommended_printer_id = get_smart_printer_assignment(
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
priority=priority,
|
||||
db_session=db_session
|
||||
)
|
||||
|
||||
if not recommended_printer_id:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"message": "Keine verfügbaren Drucker für den gewünschten Zeitraum gefunden."
|
||||
})
|
||||
|
||||
# Drucker-Details abrufen
|
||||
printer = db_session.query(Printer).filter_by(id=recommended_printer_id).first()
|
||||
if not printer:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"message": "Empfohlener Drucker nicht mehr verfügbar."
|
||||
})
|
||||
|
||||
# Zusätzliche Statistiken für die Empfehlung berechnen
|
||||
last_24h = datetime.now() - timedelta(hours=24)
|
||||
recent_jobs_count = db_session.query(Job).filter(
|
||||
Job.printer_id == printer.id,
|
||||
Job.start_at >= last_24h,
|
||||
Job.status.in_(["scheduled", "running", "finished"])
|
||||
).count()
|
||||
|
||||
# Verfügbarkeit als Prozentsatz berechnen
|
||||
total_time_slots = 24 # Stunden pro Tag
|
||||
availability_percent = max(0, 100 - (recent_jobs_count * 4)) # Grobe Schätzung
|
||||
|
||||
# Auslastung berechnen
|
||||
utilization_percent = min(100, recent_jobs_count * 8) # Grobe Schätzung
|
||||
|
||||
# Eignung basierend auf Priorität und Zeitfenster bestimmen
|
||||
suitability = "Gut"
|
||||
if priority == "urgent" and ("express" in printer.name.lower() or "schnell" in printer.name.lower()):
|
||||
suitability = "Perfekt"
|
||||
elif priority == "high" and recent_jobs_count <= 2:
|
||||
suitability = "Ausgezeichnet"
|
||||
elif recent_jobs_count == 0:
|
||||
suitability = "Optimal"
|
||||
|
||||
# Begründung generieren
|
||||
reason = f"Optimale Verfügbarkeit und geringe Auslastung im gewählten Zeitraum"
|
||||
|
||||
job_duration_hours = (end_date - start_date).total_seconds() / 3600
|
||||
start_hour = start_date.hour
|
||||
|
||||
if priority == "urgent":
|
||||
reason = "Schnellster verfügbarer Drucker für dringende Aufträge"
|
||||
elif start_hour >= 18 or start_hour <= 6:
|
||||
reason = "Speziell für Nachtschichten optimiert"
|
||||
elif job_duration_hours > 8:
|
||||
reason = "Zuverlässig für lange Druckaufträge"
|
||||
elif job_duration_hours <= 2:
|
||||
reason = "Optimal für schnelle Druckaufträge"
|
||||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"recommendation": {
|
||||
"printer_id": printer.id,
|
||||
"printer_name": f"{printer.name}",
|
||||
"location": printer.location or "Haupthalle",
|
||||
"reason": reason,
|
||||
"availability": f"{availability_percent}%",
|
||||
"utilization": f"{utilization_percent}%",
|
||||
"suitability": suitability,
|
||||
"recent_jobs": recent_jobs_count,
|
||||
"priority_optimized": priority in ["urgent", "high"] and suitability in ["Perfekt", "Ausgezeichnet"]
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Abrufen der intelligenten Empfehlung: {str(e)}")
|
||||
return jsonify({"error": "Fehler beim Verarbeiten der Anfrage"}), 500
|
||||
@@ -0,0 +1,855 @@
|
||||
import json
|
||||
import secrets
|
||||
import bcrypt
|
||||
from datetime import datetime, timedelta
|
||||
from flask import Blueprint, render_template, request, jsonify, redirect, url_for, abort, session, flash
|
||||
from flask_login import current_user, login_required
|
||||
from flask_wtf import FlaskForm
|
||||
from flask_wtf.file import FileField, FileAllowed
|
||||
from wtforms import StringField, TextAreaField, IntegerField, SelectField
|
||||
from wtforms.validators import DataRequired, Email, Optional, NumberRange
|
||||
from functools import wraps
|
||||
from sqlalchemy import desc
|
||||
from sqlalchemy.orm import joinedload
|
||||
|
||||
from models import GuestRequest, Job, Printer, User, UserPermission, Notification, get_cached_session
|
||||
from utils.logging_config import get_logger
|
||||
|
||||
guest_blueprint = Blueprint('guest', __name__)
|
||||
logger = get_logger("guest")
|
||||
|
||||
# Flask-WTF Formular für Gastanfragen
|
||||
class GuestRequestForm(FlaskForm):
|
||||
name = StringField('Vollständiger Name', validators=[DataRequired()])
|
||||
email = StringField('E-Mail-Adresse', validators=[DataRequired(), Email()])
|
||||
printer_id = SelectField('Drucker auswählen', coerce=int, validators=[Optional()])
|
||||
duration_min = IntegerField('Geschätzte Dauer (Minuten)',
|
||||
validators=[DataRequired(), NumberRange(min=1, max=1440)],
|
||||
default=60)
|
||||
reason = TextAreaField('Projektbeschreibung', validators=[Optional()])
|
||||
file = FileField('3D-Datei hochladen',
|
||||
validators=[Optional(), FileAllowed(['stl', 'obj', '3mf', 'amf', 'gcode'],
|
||||
'3D-Dateien sind erlaubt!')])
|
||||
|
||||
# Hilfsfunktionen
|
||||
def can_approve_jobs(user_id):
|
||||
"""Prüft, ob ein Benutzer Anfragen genehmigen darf."""
|
||||
with get_cached_session() as db_session:
|
||||
permission = db_session.query(UserPermission).filter_by(user_id=user_id).first()
|
||||
if not permission:
|
||||
return False
|
||||
return permission.can_approve_jobs
|
||||
|
||||
def approver_required(f):
|
||||
"""Decorator zur Prüfung der Genehmigungsberechtigung."""
|
||||
@wraps(f)
|
||||
@login_required
|
||||
def decorated_function(*args, **kwargs):
|
||||
if not can_approve_jobs(current_user.id):
|
||||
abort(403, "Keine Berechtigung zum Genehmigen von Anfragen")
|
||||
return f(*args, **kwargs)
|
||||
return decorated_function
|
||||
|
||||
# Gast-Routen
|
||||
@guest_blueprint.route('/request', methods=['GET', 'POST'])
|
||||
def guest_request_form():
|
||||
"""Formular für Gastanfragen anzeigen und verarbeiten."""
|
||||
with get_cached_session() as db_session:
|
||||
# Aktive Drucker für SelectField laden
|
||||
printers = db_session.query(Printer).filter_by(active=True).all()
|
||||
|
||||
# Formular erstellen
|
||||
form = GuestRequestForm()
|
||||
|
||||
# Drucker-Optionen für SelectField setzen
|
||||
printer_choices = [(0, 'Keinen spezifischen Drucker auswählen')]
|
||||
printer_choices.extend([(p.id, f"{p.name} ({p.location or 'Kein Standort'})") for p in printers])
|
||||
form.printer_id.choices = printer_choices
|
||||
|
||||
if form.validate_on_submit():
|
||||
try:
|
||||
# Daten aus dem Formular extrahieren
|
||||
name = form.name.data
|
||||
email = form.email.data
|
||||
reason = form.reason.data
|
||||
duration_min = form.duration_min.data
|
||||
printer_id = form.printer_id.data if form.printer_id.data != 0 else None
|
||||
|
||||
# IP-Adresse erfassen
|
||||
author_ip = request.remote_addr
|
||||
|
||||
# Drucker validieren, falls angegeben
|
||||
if printer_id:
|
||||
printer = db_session.query(Printer).filter_by(id=printer_id, active=True).first()
|
||||
if not printer:
|
||||
flash("Ungültiger Drucker ausgewählt.", "error")
|
||||
return render_template('guest_request.html', form=form, printers=printers)
|
||||
|
||||
# Neue Anfrage erstellen
|
||||
guest_request = GuestRequest(
|
||||
name=name,
|
||||
email=email,
|
||||
reason=reason,
|
||||
duration_min=duration_min,
|
||||
printer_id=printer_id,
|
||||
author_ip=author_ip
|
||||
)
|
||||
|
||||
db_session.add(guest_request)
|
||||
db_session.commit()
|
||||
|
||||
# Benachrichtigung für Genehmiger erstellen
|
||||
Notification.create_for_approvers(
|
||||
notification_type="guest_request",
|
||||
payload={
|
||||
"request_id": guest_request.id,
|
||||
"name": guest_request.name,
|
||||
"created_at": guest_request.created_at.isoformat(),
|
||||
"status": guest_request.status
|
||||
}
|
||||
)
|
||||
|
||||
logger.info(f"Neue Gastanfrage erstellt: ID {guest_request.id}, Name: {name}")
|
||||
flash("Ihr Antrag wurde erfolgreich eingereicht!", "success")
|
||||
|
||||
# Weiterleitung zur Status-Seite
|
||||
return redirect(url_for('guest.guest_request_status', request_id=guest_request.id))
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Erstellen der Gastanfrage: {str(e)}")
|
||||
flash("Fehler beim Verarbeiten Ihres Antrags. Bitte versuchen Sie es erneut.", "error")
|
||||
|
||||
# Drucker-Liste von der Session trennen für Template-Verwendung
|
||||
db_session.expunge_all()
|
||||
|
||||
return render_template('guest_request.html', form=form, printers=printers)
|
||||
|
||||
@guest_blueprint.route('/start-job', methods=['GET'])
|
||||
def guest_start_job_form():
|
||||
"""Code-Eingabe-Formular für Gäste anzeigen."""
|
||||
return render_template('guest_start_job.html')
|
||||
|
||||
@guest_blueprint.route('/job/<int:job_id>/status', methods=['GET'])
|
||||
def guest_job_status(job_id):
|
||||
"""Job-Status-Seite für Gäste anzeigen."""
|
||||
with get_cached_session() as db_session:
|
||||
# Job mit eager loading des printer-Relationships laden
|
||||
job = db_session.query(Job).options(
|
||||
joinedload(Job.printer),
|
||||
joinedload(Job.user)
|
||||
).filter_by(id=job_id).first()
|
||||
|
||||
if not job:
|
||||
abort(404, "Job nicht gefunden")
|
||||
|
||||
# Zugehörige Gastanfrage finden
|
||||
guest_request = db_session.query(GuestRequest).filter_by(job_id=job_id).first()
|
||||
|
||||
# Objekte explizit von der Session trennen, um sie außerhalb verwenden zu können
|
||||
db_session.expunge(job)
|
||||
if guest_request:
|
||||
db_session.expunge(guest_request)
|
||||
|
||||
return render_template('guest_job_status.html',
|
||||
job=job,
|
||||
guest_request=guest_request)
|
||||
|
||||
@guest_blueprint.route('/requests/overview', methods=['GET'])
|
||||
def guest_requests_overview():
|
||||
"""Öffentliche Übersicht aller Druckanträge mit zensierten persönlichen Daten."""
|
||||
try:
|
||||
with get_cached_session() as db_session:
|
||||
# Alle Gastanfragen mit eager loading des printer-Relationships laden
|
||||
guest_requests = db_session.query(GuestRequest).options(
|
||||
joinedload(GuestRequest.printer)
|
||||
).order_by(desc(GuestRequest.created_at)).all()
|
||||
|
||||
# Daten für Gäste aufbereiten (persönliche Daten zensieren)
|
||||
public_requests = []
|
||||
for req in guest_requests:
|
||||
# Name zensieren: Nur ersten Buchstaben und letzten Buchstaben anzeigen
|
||||
censored_name = "***"
|
||||
if req.name and len(req.name) > 0:
|
||||
if len(req.name) == 1:
|
||||
censored_name = req.name[0] + "***"
|
||||
elif len(req.name) == 2:
|
||||
censored_name = req.name[0] + "***" + req.name[-1]
|
||||
else:
|
||||
censored_name = req.name[0] + "***" + req.name[-1]
|
||||
|
||||
# E-Mail zensieren
|
||||
censored_email = "***@***.***"
|
||||
if req.email and "@" in req.email:
|
||||
email_parts = req.email.split("@")
|
||||
if len(email_parts[0]) > 2:
|
||||
censored_email = email_parts[0][:2] + "***@" + email_parts[1]
|
||||
else:
|
||||
censored_email = "***@" + email_parts[1]
|
||||
|
||||
# Grund zensieren (nur erste 20 Zeichen anzeigen)
|
||||
censored_reason = "***"
|
||||
if req.reason and len(req.reason) > 20:
|
||||
censored_reason = req.reason[:20] + "..."
|
||||
elif req.reason:
|
||||
censored_reason = req.reason
|
||||
|
||||
public_requests.append({
|
||||
"id": req.id,
|
||||
"name": censored_name,
|
||||
"email": censored_email,
|
||||
"reason": censored_reason,
|
||||
"duration_min": req.duration_min,
|
||||
"created_at": req.created_at,
|
||||
"status": req.status,
|
||||
"printer": req.printer.to_dict() if req.printer else None
|
||||
})
|
||||
|
||||
# Objekte explizit von der Session trennen
|
||||
db_session.expunge_all()
|
||||
|
||||
return render_template('guest_requests_overview.html', requests=public_requests)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Laden der öffentlichen Gastanfragen: {str(e)}")
|
||||
return render_template('guest_requests_overview.html', requests=[], error="Fehler beim Laden der Anfragen")
|
||||
|
||||
@guest_blueprint.route('/request/<int:request_id>', methods=['GET'])
|
||||
def guest_request_status(request_id):
|
||||
"""Status einer Gastanfrage anzeigen."""
|
||||
with get_cached_session() as db_session:
|
||||
# Guest Request mit eager loading des printer-Relationships laden
|
||||
guest_request = db_session.query(GuestRequest).options(
|
||||
joinedload(GuestRequest.printer)
|
||||
).filter_by(id=request_id).first()
|
||||
|
||||
if not guest_request:
|
||||
abort(404, "Anfrage nicht gefunden")
|
||||
|
||||
# OTP-Code nur anzeigen, wenn Anfrage genehmigt wurde
|
||||
otp_code = None
|
||||
show_start_link = False
|
||||
|
||||
if guest_request.status == "approved":
|
||||
if not guest_request.otp_code:
|
||||
# OTP generieren falls noch nicht vorhanden
|
||||
otp_code = guest_request.generate_otp()
|
||||
db_session.commit()
|
||||
else:
|
||||
# OTP existiert bereits - prüfen ob noch nicht verwendet
|
||||
show_start_link = guest_request.otp_used_at is None
|
||||
|
||||
# Zugehörigen Job laden, falls vorhanden
|
||||
job = None
|
||||
if guest_request.job_id:
|
||||
job = db_session.query(Job).filter_by(id=guest_request.job_id).first()
|
||||
|
||||
# Objekte explizit von der Session trennen, um sie außerhalb verwenden zu können
|
||||
db_session.expunge(guest_request)
|
||||
if job:
|
||||
db_session.expunge(job)
|
||||
|
||||
return render_template('guest_status.html',
|
||||
request=guest_request,
|
||||
job=job,
|
||||
otp_code=otp_code,
|
||||
show_start_link=show_start_link)
|
||||
|
||||
# API-Endpunkte
|
||||
@guest_blueprint.route('/api/guest/requests', methods=['POST'])
|
||||
def api_create_guest_request():
|
||||
"""Neue Gastanfrage erstellen."""
|
||||
data = request.get_json()
|
||||
if not data:
|
||||
return jsonify({"error": "Keine Daten erhalten"}), 400
|
||||
|
||||
# Pflichtfelder prüfen
|
||||
name = data.get('name')
|
||||
if not name:
|
||||
return jsonify({"error": "Name ist erforderlich"}), 400
|
||||
|
||||
# Optionale Felder
|
||||
email = data.get('email')
|
||||
reason = data.get('reason')
|
||||
duration_min = data.get('duration_min', 60) # Standard: 1 Stunde
|
||||
printer_id = data.get('printer_id')
|
||||
|
||||
# IP-Adresse erfassen
|
||||
author_ip = request.remote_addr
|
||||
|
||||
try:
|
||||
with get_cached_session() as db_session:
|
||||
# Drucker prüfen
|
||||
if printer_id:
|
||||
printer = db_session.query(Printer).filter_by(id=printer_id, active=True).first()
|
||||
if not printer:
|
||||
return jsonify({"error": "Ungültiger Drucker ausgewählt"}), 400
|
||||
|
||||
# Neue Anfrage erstellen
|
||||
guest_request = GuestRequest(
|
||||
name=name,
|
||||
email=email,
|
||||
reason=reason,
|
||||
duration_min=duration_min,
|
||||
printer_id=printer_id,
|
||||
author_ip=author_ip
|
||||
)
|
||||
|
||||
db_session.add(guest_request)
|
||||
db_session.commit()
|
||||
|
||||
# Benachrichtigung für Genehmiger erstellen
|
||||
Notification.create_for_approvers(
|
||||
notification_type="guest_request",
|
||||
payload={
|
||||
"request_id": guest_request.id,
|
||||
"name": guest_request.name,
|
||||
"created_at": guest_request.created_at.isoformat(),
|
||||
"status": guest_request.status
|
||||
}
|
||||
)
|
||||
|
||||
logger.info(f"Neue Gastanfrage erstellt: ID {guest_request.id}, Name: {name}")
|
||||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"request_id": guest_request.id,
|
||||
"status": guest_request.status,
|
||||
"redirect_url": url_for('guest.guest_request_status', request_id=guest_request.id)
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Erstellen der Gastanfrage: {str(e)}")
|
||||
return jsonify({"error": "Fehler beim Verarbeiten der Anfrage"}), 500
|
||||
|
||||
@guest_blueprint.route('/api/guest/start-job', methods=['POST'])
|
||||
def api_start_job_with_code():
|
||||
"""Job mit OTP-Code starten."""
|
||||
try:
|
||||
data = request.get_json()
|
||||
if not data or 'code' not in data:
|
||||
return jsonify({"error": "Code ist erforderlich"}), 400
|
||||
|
||||
code = data['code'].strip().upper()
|
||||
if len(code) != 6:
|
||||
return jsonify({"error": "Code muss 6 Zeichen lang sein"}), 400
|
||||
|
||||
with get_cached_session() as db_session:
|
||||
# Alle genehmigten Gastanfragen mit OTP-Codes finden
|
||||
guest_requests = db_session.query(GuestRequest).filter(
|
||||
GuestRequest.status == "approved",
|
||||
GuestRequest.otp_code.isnot(None),
|
||||
GuestRequest.otp_used_at.is_(None) # Noch nicht verwendet
|
||||
).all()
|
||||
|
||||
matching_request = None
|
||||
for req in guest_requests:
|
||||
# Code validieren
|
||||
if req.verify_otp(code):
|
||||
matching_request = req
|
||||
break
|
||||
|
||||
if not matching_request:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "Ungültiger oder bereits verwendeter Code"
|
||||
}), 400
|
||||
|
||||
# Prüfen ob zugehöriger Job existiert
|
||||
if not matching_request.job_id:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "Kein zugehöriger Job gefunden"
|
||||
}), 400
|
||||
|
||||
job = db_session.query(Job).options(
|
||||
joinedload(Job.printer)
|
||||
).filter_by(id=matching_request.job_id).first()
|
||||
|
||||
if not job:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "Job nicht gefunden"
|
||||
}), 400
|
||||
|
||||
# Prüfen ob Job noch startbar ist
|
||||
if job.status not in ["scheduled", "waiting_for_printer"]:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": f"Job kann im Status '{job.status}' nicht gestartet werden"
|
||||
}), 400
|
||||
|
||||
# Job starten
|
||||
now = datetime.now()
|
||||
job.status = "running"
|
||||
job.start_at = now
|
||||
job.end_at = now + timedelta(minutes=matching_request.duration_min)
|
||||
job.actual_start_time = now
|
||||
|
||||
# OTP als verwendet markieren
|
||||
matching_request.otp_used_at = now
|
||||
|
||||
# Drucker einschalten (falls implementiert)
|
||||
if job.printer and job.printer.plug_ip:
|
||||
try:
|
||||
from utils.job_scheduler import toggle_plug
|
||||
toggle_plug(job.printer_id, True)
|
||||
except Exception as e:
|
||||
logger.warning(f"Fehler beim Einschalten des Druckers: {str(e)}")
|
||||
|
||||
db_session.commit()
|
||||
|
||||
logger.info(f"Job {job.id} mit OTP-Code gestartet für Gastanfrage {matching_request.id}")
|
||||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"job_id": job.id,
|
||||
"job_name": job.name,
|
||||
"start_time": job.start_at.strftime("%H:%M"),
|
||||
"end_time": job.end_at.strftime("%H:%M"),
|
||||
"duration_minutes": matching_request.duration_min,
|
||||
"printer_name": job.printer.name if job.printer else "Unbekannt",
|
||||
"message": f"Job '{job.name}' erfolgreich gestartet"
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Starten des Jobs mit Code: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "Fehler beim Starten des Jobs"
|
||||
}), 500
|
||||
|
||||
@guest_blueprint.route('/api/guest/requests/<int:request_id>', methods=['GET'])
|
||||
def api_get_guest_request(request_id):
|
||||
"""Status einer Gastanfrage abrufen."""
|
||||
try:
|
||||
with get_cached_session() as db_session:
|
||||
guest_request = db_session.query(GuestRequest).filter_by(id=request_id).first()
|
||||
if not guest_request:
|
||||
return jsonify({"error": "Anfrage nicht gefunden"}), 404
|
||||
|
||||
# OTP wird nie über die API zurückgegeben
|
||||
response_data = guest_request.to_dict()
|
||||
response_data.pop("otp_code", None)
|
||||
|
||||
return jsonify(response_data)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Abrufen der Gastanfrage: {str(e)}")
|
||||
return jsonify({"error": "Fehler beim Verarbeiten der Anfrage"}), 500
|
||||
|
||||
@guest_blueprint.route('/api/guest/job/<int:job_id>/status', methods=['GET'])
|
||||
def api_get_guest_job_status(job_id):
|
||||
"""Job-Status für Gäste abrufen."""
|
||||
try:
|
||||
with get_cached_session() as db_session:
|
||||
# Job mit Drucker-Information laden
|
||||
job = db_session.query(Job).options(
|
||||
joinedload(Job.printer)
|
||||
).filter_by(id=job_id).first()
|
||||
|
||||
if not job:
|
||||
return jsonify({"error": "Job nicht gefunden"}), 404
|
||||
|
||||
# Zugehörige Gastanfrage prüfen
|
||||
guest_request = db_session.query(GuestRequest).filter_by(job_id=job_id).first()
|
||||
if not guest_request:
|
||||
return jsonify({"error": "Kein Gastjob"}), 403
|
||||
|
||||
# Aktuelle Zeit für Berechnungen
|
||||
now = datetime.now()
|
||||
|
||||
# Restzeit berechnen
|
||||
remaining_minutes = 0
|
||||
if job.status == "running" and job.end_at:
|
||||
remaining_seconds = (job.end_at - now).total_seconds()
|
||||
remaining_minutes = max(0, int(remaining_seconds / 60))
|
||||
|
||||
# Fortschritt berechnen
|
||||
progress_percent = 0
|
||||
if job.status == "running" and job.start_at and job.end_at:
|
||||
total_duration = (job.end_at - job.start_at).total_seconds()
|
||||
elapsed_duration = (now - job.start_at).total_seconds()
|
||||
progress_percent = min(100, max(0, int((elapsed_duration / total_duration) * 100)))
|
||||
elif job.status in ["completed", "finished"]:
|
||||
progress_percent = 100
|
||||
|
||||
job_data = {
|
||||
"id": job.id,
|
||||
"name": job.name,
|
||||
"status": job.status,
|
||||
"start_at": job.start_at.isoformat() if job.start_at else None,
|
||||
"end_at": job.end_at.isoformat() if job.end_at else None,
|
||||
"duration_minutes": job.duration_minutes,
|
||||
"remaining_minutes": remaining_minutes,
|
||||
"progress_percent": progress_percent,
|
||||
"printer": {
|
||||
"id": job.printer.id,
|
||||
"name": job.printer.name,
|
||||
"location": job.printer.location
|
||||
} if job.printer else None,
|
||||
"guest_request": {
|
||||
"id": guest_request.id,
|
||||
"name": guest_request.name,
|
||||
"created_at": guest_request.created_at.isoformat()
|
||||
},
|
||||
"is_active": job.status in ["scheduled", "running"],
|
||||
"is_completed": job.status in ["completed", "finished"],
|
||||
"is_failed": job.status in ["failed", "cancelled"]
|
||||
}
|
||||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"job": job_data
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Abrufen des Job-Status: {str(e)}")
|
||||
return jsonify({"error": "Fehler beim Verarbeiten der Anfrage"}), 500
|
||||
|
||||
@guest_blueprint.route('/api/notifications', methods=['GET'])
|
||||
@login_required
|
||||
def api_get_notifications():
|
||||
"""Benachrichtigungen für den aktuellen Benutzer abrufen."""
|
||||
try:
|
||||
# Zeitstempel für Filter (nur neue Benachrichtigungen)
|
||||
since = request.args.get('since')
|
||||
if since:
|
||||
try:
|
||||
since_date = datetime.fromisoformat(since)
|
||||
except ValueError:
|
||||
return jsonify({"error": "Ungültiges Datumsformat"}), 400
|
||||
else:
|
||||
since_date = None
|
||||
|
||||
with get_cached_session() as db_session:
|
||||
query = db_session.query(Notification).filter_by(
|
||||
user_id=current_user.id,
|
||||
read=False
|
||||
)
|
||||
|
||||
if since_date:
|
||||
query = query.filter(Notification.created_at > since_date)
|
||||
|
||||
notifications = query.order_by(desc(Notification.created_at)).all()
|
||||
|
||||
return jsonify({
|
||||
"count": len(notifications),
|
||||
"notifications": [n.to_dict() for n in notifications]
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Abrufen der Benachrichtigungen: {str(e)}")
|
||||
return jsonify({"error": "Fehler beim Verarbeiten der Anfrage"}), 500
|
||||
|
||||
@guest_blueprint.route('/api/notifications/<int:notification_id>/read', methods=['POST'])
|
||||
@login_required
|
||||
def api_mark_notification_read(notification_id):
|
||||
"""Benachrichtigung als gelesen markieren."""
|
||||
try:
|
||||
with get_cached_session() as db_session:
|
||||
notification = db_session.query(Notification).filter_by(
|
||||
id=notification_id,
|
||||
user_id=current_user.id
|
||||
).first()
|
||||
|
||||
if not notification:
|
||||
return jsonify({"error": "Benachrichtigung nicht gefunden"}), 404
|
||||
|
||||
notification.read = True
|
||||
db_session.commit()
|
||||
|
||||
return jsonify({"success": True})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Markieren der Benachrichtigung als gelesen: {str(e)}")
|
||||
return jsonify({"error": "Fehler beim Verarbeiten der Anfrage"}), 500
|
||||
|
||||
@guest_blueprint.route('/api/admin/requests', methods=['GET'])
|
||||
@approver_required
|
||||
def api_get_all_requests():
|
||||
"""Alle Gastanfragen für Admins abrufen."""
|
||||
try:
|
||||
# Filter-Parameter
|
||||
status_filter = request.args.get('status', 'all') # all, pending, approved, denied
|
||||
limit = int(request.args.get('limit', 50))
|
||||
offset = int(request.args.get('offset', 0))
|
||||
|
||||
with get_cached_session() as db_session:
|
||||
# Query mit eager loading
|
||||
query = db_session.query(GuestRequest).options(
|
||||
joinedload(GuestRequest.printer),
|
||||
joinedload(GuestRequest.job),
|
||||
joinedload(GuestRequest.processed_by_user)
|
||||
)
|
||||
|
||||
# Status-Filter anwenden
|
||||
if status_filter != 'all':
|
||||
query = query.filter(GuestRequest.status == status_filter)
|
||||
|
||||
# Sortierung: Pending zuerst, dann nach Erstellungsdatum
|
||||
query = query.order_by(
|
||||
desc(GuestRequest.status == 'pending'),
|
||||
desc(GuestRequest.created_at)
|
||||
)
|
||||
|
||||
# Pagination
|
||||
total_count = query.count()
|
||||
requests = query.offset(offset).limit(limit).all()
|
||||
|
||||
# Daten für Admin aufbereiten
|
||||
admin_requests = []
|
||||
for req in requests:
|
||||
request_data = req.to_dict()
|
||||
|
||||
# Zusätzliche Admin-Informationen
|
||||
request_data.update({
|
||||
"can_be_processed": req.status == "pending",
|
||||
"is_overdue": (
|
||||
req.status == "approved" and
|
||||
req.job and
|
||||
req.job.end_at < datetime.now()
|
||||
) if req.job else False,
|
||||
"time_since_creation": (datetime.now() - req.created_at).total_seconds() / 3600 if req.created_at else 0 # Stunden
|
||||
})
|
||||
|
||||
admin_requests.append(request_data)
|
||||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"requests": admin_requests,
|
||||
"pagination": {
|
||||
"total": total_count,
|
||||
"limit": limit,
|
||||
"offset": offset,
|
||||
"has_more": (offset + limit) < total_count
|
||||
},
|
||||
"stats": {
|
||||
"total": total_count,
|
||||
"pending": db_session.query(GuestRequest).filter_by(status='pending').count(),
|
||||
"approved": db_session.query(GuestRequest).filter_by(status='approved').count(),
|
||||
"denied": db_session.query(GuestRequest).filter_by(status='denied').count()
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Abrufen der Admin-Gastanfragen: {str(e)}")
|
||||
return jsonify({"error": "Fehler beim Verarbeiten der Anfrage"}), 500
|
||||
|
||||
@guest_blueprint.route('/api/admin/requests/<int:request_id>', methods=['GET'])
|
||||
@approver_required
|
||||
def api_get_request_details(request_id):
|
||||
"""Detaillierte Informationen zu einer Gastanfrage für Admins abrufen."""
|
||||
try:
|
||||
with get_cached_session() as db_session:
|
||||
guest_request = db_session.query(GuestRequest).options(
|
||||
joinedload(GuestRequest.printer),
|
||||
joinedload(GuestRequest.job),
|
||||
joinedload(GuestRequest.processed_by_user)
|
||||
).filter_by(id=request_id).first()
|
||||
|
||||
if not guest_request:
|
||||
return jsonify({"error": "Anfrage nicht gefunden"}), 404
|
||||
|
||||
# Vollständige Admin-Informationen
|
||||
request_data = guest_request.to_dict()
|
||||
|
||||
# Verfügbare Drucker für Zuweisung
|
||||
available_printers = db_session.query(Printer).filter_by(active=True).all()
|
||||
request_data["available_printers"] = [p.to_dict() for p in available_printers]
|
||||
|
||||
# Job-Historie falls vorhanden
|
||||
if guest_request.job:
|
||||
job_data = guest_request.job.to_dict()
|
||||
job_data["is_active"] = guest_request.job.status in ["scheduled", "running"]
|
||||
job_data["is_overdue"] = guest_request.job.end_at < datetime.now() if guest_request.job.end_at else False
|
||||
request_data["job_details"] = job_data
|
||||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"request": request_data
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Abrufen der Gastanfrage-Details: {str(e)}")
|
||||
return jsonify({"error": "Fehler beim Verarbeiten der Anfrage"}), 500
|
||||
|
||||
@guest_blueprint.route('/api/admin/requests/<int:request_id>/update', methods=['PUT'])
|
||||
@approver_required
|
||||
def api_update_request(request_id):
|
||||
"""Gastanfrage aktualisieren (nur für Admins)."""
|
||||
try:
|
||||
data = request.get_json()
|
||||
if not data:
|
||||
return jsonify({"error": "Keine Daten erhalten"}), 400
|
||||
|
||||
with get_cached_session() as db_session:
|
||||
guest_request = db_session.query(GuestRequest).filter_by(id=request_id).first()
|
||||
if not guest_request:
|
||||
return jsonify({"error": "Anfrage nicht gefunden"}), 404
|
||||
|
||||
# Erlaubte Felder für Updates
|
||||
allowed_fields = ['printer_id', 'duration_min', 'approval_notes', 'rejection_reason']
|
||||
changes_made = False
|
||||
|
||||
for field in allowed_fields:
|
||||
if field in data:
|
||||
if field == 'printer_id' and data[field]:
|
||||
# Drucker validieren
|
||||
printer = db_session.query(Printer).filter_by(id=data[field], active=True).first()
|
||||
if not printer:
|
||||
return jsonify({"error": "Ungültiger Drucker ausgewählt"}), 400
|
||||
|
||||
setattr(guest_request, field, data[field])
|
||||
changes_made = True
|
||||
|
||||
if changes_made:
|
||||
guest_request.processed_by = current_user.id
|
||||
guest_request.processed_at = datetime.now()
|
||||
db_session.commit()
|
||||
|
||||
logger.info(f"Gastanfrage {request_id} aktualisiert von Admin {current_user.id}")
|
||||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"message": "Anfrage erfolgreich aktualisiert",
|
||||
"updated_by": current_user.name,
|
||||
"updated_at": guest_request.processed_at.isoformat()
|
||||
})
|
||||
else:
|
||||
return jsonify({"error": "Keine Änderungen vorgenommen"}), 400
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Aktualisieren der Gastanfrage: {str(e)}")
|
||||
return jsonify({"error": "Fehler beim Verarbeiten der Anfrage"}), 500
|
||||
|
||||
# Admin-Routen
|
||||
@guest_blueprint.route('/admin/requests', methods=['GET'])
|
||||
@approver_required
|
||||
def admin_requests_management():
|
||||
"""Admin-Oberfläche für die Verwaltung von Gastanfragen."""
|
||||
return render_template('admin_guest_requests.html')
|
||||
|
||||
@guest_blueprint.route('/api/requests/<int:request_id>/approve', methods=['POST'])
|
||||
@approver_required
|
||||
def api_approve_request(request_id):
|
||||
"""Gastanfrage genehmigen."""
|
||||
try:
|
||||
data = request.get_json() or {}
|
||||
approval_notes = data.get('notes', '')
|
||||
printer_id = data.get('printer_id') # Optional: Drucker zuweisen/ändern
|
||||
|
||||
with get_cached_session() as db_session:
|
||||
guest_request = db_session.query(GuestRequest).filter_by(id=request_id).first()
|
||||
if not guest_request:
|
||||
return jsonify({"error": "Anfrage nicht gefunden"}), 404
|
||||
|
||||
if guest_request.status != "pending":
|
||||
return jsonify({"error": "Anfrage wurde bereits bearbeitet"}), 400
|
||||
|
||||
# Drucker validieren, falls angegeben
|
||||
if printer_id:
|
||||
printer = db_session.query(Printer).filter_by(id=printer_id, active=True).first()
|
||||
if not printer:
|
||||
return jsonify({"error": "Ungültiger Drucker ausgewählt"}), 400
|
||||
guest_request.printer_id = printer_id
|
||||
|
||||
# Sicherstellen, dass ein Drucker zugewiesen ist
|
||||
if not guest_request.printer_id:
|
||||
return jsonify({"error": "Kein Drucker zugewiesen. Bitte wählen Sie einen Drucker aus."}), 400
|
||||
|
||||
# Anfrage genehmigen
|
||||
guest_request.status = "approved"
|
||||
guest_request.processed_by = current_user.id
|
||||
guest_request.processed_at = datetime.now()
|
||||
guest_request.approval_notes = approval_notes
|
||||
|
||||
# OTP generieren
|
||||
otp_plain = guest_request.generate_otp()
|
||||
|
||||
# Zugehörigen Job erstellen
|
||||
start_time = datetime.now() + timedelta(minutes=5) # Start in 5 Minuten
|
||||
end_time = start_time + timedelta(minutes=guest_request.duration_min)
|
||||
|
||||
# Admin-Benutzer als Eigentümer verwenden
|
||||
admin_user = db_session.query(User).filter_by(is_admin=True).first()
|
||||
if not admin_user:
|
||||
admin_user = current_user # Fallback auf aktuellen Benutzer
|
||||
|
||||
job = Job(
|
||||
name=f"Gastauftrag: {guest_request.name}",
|
||||
description=guest_request.reason or "Gastauftrag",
|
||||
user_id=admin_user.id,
|
||||
printer_id=guest_request.printer_id,
|
||||
start_at=start_time,
|
||||
end_at=end_time,
|
||||
status="scheduled",
|
||||
duration_minutes=guest_request.duration_min,
|
||||
owner_id=admin_user.id
|
||||
)
|
||||
|
||||
db_session.add(job)
|
||||
db_session.flush() # ID generieren
|
||||
|
||||
# Job-ID in Gastanfrage speichern
|
||||
guest_request.job_id = job.id
|
||||
|
||||
db_session.commit()
|
||||
|
||||
logger.info(f"Gastanfrage {request_id} genehmigt von Admin {current_user.id} ({current_user.username})")
|
||||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"status": "approved",
|
||||
"job_id": job.id,
|
||||
"otp": otp_plain, # Nur in dieser Antwort wird der OTP-Klartext zurückgegeben
|
||||
"approved_by": current_user.username,
|
||||
"approved_at": guest_request.processed_at.isoformat(),
|
||||
"notes": approval_notes,
|
||||
"message": f"Anfrage genehmigt. Zugangscode: {otp_plain}"
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Genehmigen der Gastanfrage: {str(e)}")
|
||||
return jsonify({"error": "Fehler beim Verarbeiten der Anfrage"}), 500
|
||||
|
||||
@guest_blueprint.route('/api/requests/<int:request_id>/deny', methods=['POST'])
|
||||
@approver_required
|
||||
def api_deny_request(request_id):
|
||||
"""Gastanfrage ablehnen."""
|
||||
try:
|
||||
data = request.get_json() or {}
|
||||
rejection_reason = data.get('reason', '')
|
||||
|
||||
if not rejection_reason.strip():
|
||||
return jsonify({"error": "Ablehnungsgrund ist erforderlich"}), 400
|
||||
|
||||
with get_cached_session() as db_session:
|
||||
guest_request = db_session.query(GuestRequest).filter_by(id=request_id).first()
|
||||
if not guest_request:
|
||||
return jsonify({"error": "Anfrage nicht gefunden"}), 404
|
||||
|
||||
if guest_request.status != "pending":
|
||||
return jsonify({"error": "Anfrage wurde bereits bearbeitet"}), 400
|
||||
|
||||
# Anfrage ablehnen
|
||||
guest_request.status = "denied"
|
||||
guest_request.processed_by = current_user.id
|
||||
guest_request.processed_at = datetime.now()
|
||||
guest_request.rejection_reason = rejection_reason
|
||||
|
||||
db_session.commit()
|
||||
|
||||
logger.info(f"Gastanfrage {request_id} abgelehnt von Admin {current_user.id} ({current_user.username}): {rejection_reason}")
|
||||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"status": "denied",
|
||||
"rejected_by": current_user.username,
|
||||
"rejected_at": guest_request.processed_at.isoformat(),
|
||||
"reason": rejection_reason,
|
||||
"message": "Anfrage wurde abgelehnt"
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Ablehnen der Gastanfrage: {str(e)}")
|
||||
return jsonify({"error": "Fehler beim Verarbeiten der Anfrage"}), 500
|
||||
@@ -0,0 +1,181 @@
|
||||
"""
|
||||
Drucker-Blueprint für MYP Platform
|
||||
Enthält alle Routen und Funktionen zur Druckerverwaltung, Statusüberwachung und Steuerung.
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
from flask import Blueprint, request, jsonify, current_app, abort, Response
|
||||
from flask_login import login_required, current_user
|
||||
from werkzeug.utils import secure_filename
|
||||
from werkzeug.exceptions import NotFound, BadRequest
|
||||
from sqlalchemy import func, desc, asc
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
from typing import Dict, List, Tuple, Any, Optional
|
||||
|
||||
from models import Printer, User, Job, get_db_session
|
||||
from utils.logging_config import get_logger, measure_execution_time
|
||||
from utils.permissions import require_permission, Permission, check_permission
|
||||
from utils.printer_monitor import printer_monitor
|
||||
|
||||
# Logger initialisieren
|
||||
printers_logger = get_logger("printers")
|
||||
|
||||
# Blueprint erstellen
|
||||
printers_blueprint = Blueprint("printers", __name__, url_prefix="/api/printers")
|
||||
|
||||
@printers_blueprint.route("/monitor/live-status", methods=["GET"])
|
||||
@login_required
|
||||
@measure_execution_time(logger=printers_logger, task_name="API-Live-Drucker-Status-Abfrage")
|
||||
def get_live_printer_status():
|
||||
"""
|
||||
Liefert den aktuellen Live-Status aller Drucker.
|
||||
|
||||
Query-Parameter:
|
||||
- use_cache: ob Cache verwendet werden soll (default: true)
|
||||
|
||||
Returns:
|
||||
JSON mit Live-Status aller Drucker
|
||||
"""
|
||||
printers_logger.info(f"🔄 Live-Status-Abfrage von Benutzer {current_user.name} (ID: {current_user.id})")
|
||||
|
||||
# Parameter auslesen
|
||||
use_cache_param = request.args.get("use_cache", "true").lower()
|
||||
use_cache = use_cache_param == "true"
|
||||
|
||||
try:
|
||||
# Live-Status über den PrinterMonitor abrufen
|
||||
status_data = printer_monitor.get_live_printer_status(use_session_cache=use_cache)
|
||||
|
||||
# Zusammenfassung der Druckerstatus erstellen
|
||||
summary = printer_monitor.get_printer_summary()
|
||||
|
||||
# Antwort mit Status und Zusammenfassung
|
||||
response = {
|
||||
"success": True,
|
||||
"status": status_data,
|
||||
"summary": summary,
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"cache_used": use_cache
|
||||
}
|
||||
|
||||
printers_logger.info(f"✅ Live-Status-Abfrage erfolgreich: {len(status_data)} Drucker")
|
||||
return jsonify(response)
|
||||
|
||||
except Exception as e:
|
||||
printers_logger.error(f"❌ Fehler bei Live-Status-Abfrage: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "Fehler bei Abfrage des Druckerstatus",
|
||||
"message": str(e)
|
||||
}), 500
|
||||
|
||||
@printers_blueprint.route("/control/<int:printer_id>/power", methods=["POST"])
|
||||
@login_required
|
||||
@require_permission(Permission.CONTROL_PRINTER) # Verwende die bereits vorhandene Berechtigung
|
||||
@measure_execution_time(logger=printers_logger, task_name="API-Drucker-Stromversorgung-Steuerung")
|
||||
def control_printer_power(printer_id):
|
||||
"""
|
||||
Steuert die Stromversorgung eines Druckers (ein-/ausschalten).
|
||||
|
||||
Args:
|
||||
printer_id: ID des zu steuernden Druckers
|
||||
|
||||
JSON-Parameter:
|
||||
- action: "on" oder "off"
|
||||
|
||||
Returns:
|
||||
JSON mit Ergebnis der Steuerungsaktion
|
||||
"""
|
||||
printers_logger.info(f"🔌 Stromsteuerung für Drucker {printer_id} von Benutzer {current_user.name}")
|
||||
|
||||
# Parameter validieren
|
||||
data = request.get_json()
|
||||
if not data or "action" not in data:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "Parameter 'action' fehlt"
|
||||
}), 400
|
||||
|
||||
action = data["action"]
|
||||
if action not in ["on", "off"]:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "Ungültige Aktion. Erlaubt sind 'on' oder 'off'."
|
||||
}), 400
|
||||
|
||||
try:
|
||||
# Drucker aus Datenbank holen
|
||||
db_session = get_db_session()
|
||||
printer = db_session.query(Printer).filter(Printer.id == printer_id).first()
|
||||
|
||||
if not printer:
|
||||
db_session.close()
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": f"Drucker mit ID {printer_id} nicht gefunden"
|
||||
}), 404
|
||||
|
||||
# Prüfen, ob Drucker eine Steckdose konfiguriert hat
|
||||
if not printer.plug_ip or not printer.plug_username or not printer.plug_password:
|
||||
db_session.close()
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": f"Drucker {printer.name} hat keine Steckdose konfiguriert"
|
||||
}), 400
|
||||
|
||||
# Steckdose steuern
|
||||
from PyP100 import PyP110
|
||||
try:
|
||||
# TP-Link Tapo P110 Verbindung herstellen
|
||||
p110 = PyP110.P110(printer.plug_ip, printer.plug_username, printer.plug_password)
|
||||
p110.handshake() # Authentifizierung
|
||||
p110.login() # Login
|
||||
|
||||
# Steckdose ein- oder ausschalten
|
||||
if action == "on":
|
||||
p110.turnOn()
|
||||
success = True
|
||||
message = "Steckdose erfolgreich eingeschaltet"
|
||||
printer.status = "starting" # Status aktualisieren
|
||||
else:
|
||||
p110.turnOff()
|
||||
success = True
|
||||
message = "Steckdose erfolgreich ausgeschaltet"
|
||||
printer.status = "offline" # Status aktualisieren
|
||||
|
||||
# Zeitpunkt der letzten Prüfung aktualisieren
|
||||
printer.last_checked = datetime.now()
|
||||
db_session.commit()
|
||||
|
||||
# Cache leeren, damit neue Status-Abfragen aktuell sind
|
||||
printer_monitor.clear_all_caches()
|
||||
|
||||
printers_logger.info(f"✅ {action.upper()}: Drucker {printer.name} erfolgreich {message}")
|
||||
|
||||
except Exception as e:
|
||||
printers_logger.error(f"❌ Fehler bei Steckdosensteuerung für {printer.name}: {str(e)}")
|
||||
db_session.close()
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": f"Fehler bei Steckdosensteuerung: {str(e)}"
|
||||
}), 500
|
||||
|
||||
db_session.close()
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"message": message,
|
||||
"printer_id": printer_id,
|
||||
"printer_name": printer.name,
|
||||
"action": action,
|
||||
"timestamp": datetime.now().isoformat()
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
printers_logger.error(f"❌ Allgemeiner Fehler bei Stromsteuerung: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": f"Allgemeiner Fehler: {str(e)}"
|
||||
}), 500
|
||||
@@ -0,0 +1,168 @@
|
||||
from flask import Blueprint, render_template, request, jsonify, redirect, url_for, abort
|
||||
from flask_login import current_user, login_required
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
from functools import wraps
|
||||
|
||||
from models import User, UserPermission, get_cached_session
|
||||
from utils.logging_config import get_logger
|
||||
|
||||
users_blueprint = Blueprint('users', __name__)
|
||||
logger = get_logger("users")
|
||||
|
||||
def users_admin_required(f):
|
||||
"""Decorator zur Prüfung der Admin-Berechtigung für Users Blueprint."""
|
||||
@wraps(f)
|
||||
@login_required
|
||||
def users_decorated_function(*args, **kwargs):
|
||||
if not current_user.is_admin:
|
||||
abort(403, "Nur Administratoren haben Zugriff auf diese Seite")
|
||||
return f(*args, **kwargs)
|
||||
return users_decorated_function
|
||||
|
||||
@users_blueprint.route('/admin/users/<int:user_id>/permissions', methods=['GET'])
|
||||
@users_admin_required
|
||||
def admin_user_permissions(user_id):
|
||||
"""Benutzerberechtigungen anzeigen und bearbeiten."""
|
||||
with get_cached_session() as db_session:
|
||||
user = db_session.query(User).filter_by(id=user_id).first()
|
||||
if not user:
|
||||
abort(404, "Benutzer nicht gefunden")
|
||||
|
||||
# Berechtigungen laden oder erstellen, falls nicht vorhanden
|
||||
permission = db_session.query(UserPermission).filter_by(user_id=user_id).first()
|
||||
if not permission:
|
||||
permission = UserPermission(user_id=user_id)
|
||||
db_session.add(permission)
|
||||
db_session.commit()
|
||||
|
||||
return render_template('admin_user_permissions.html', user=user, permission=permission)
|
||||
|
||||
@users_blueprint.route('/api/users/<int:user_id>/permissions', methods=['GET'])
|
||||
@login_required
|
||||
def api_get_user_permissions(user_id):
|
||||
"""Benutzerberechtigungen als JSON zurückgeben."""
|
||||
# Nur Admins oder der Benutzer selbst darf seine Berechtigungen sehen
|
||||
if not current_user.is_admin and current_user.id != user_id:
|
||||
return jsonify({"error": "Keine Berechtigung"}), 403
|
||||
|
||||
try:
|
||||
with get_cached_session() as db_session:
|
||||
permission = db_session.query(UserPermission).filter_by(user_id=user_id).first()
|
||||
|
||||
if not permission:
|
||||
# Falls keine Berechtigungen existieren, Standard-Werte zurückgeben
|
||||
return jsonify({
|
||||
"user_id": user_id,
|
||||
"can_start_jobs": False,
|
||||
"needs_approval": True,
|
||||
"can_approve_jobs": False
|
||||
})
|
||||
|
||||
return jsonify(permission.to_dict())
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Abrufen der Benutzerberechtigungen: {str(e)}")
|
||||
return jsonify({"error": "Fehler beim Verarbeiten der Anfrage"}), 500
|
||||
|
||||
@users_blueprint.route('/api/users/<int:user_id>/permissions', methods=['PUT'])
|
||||
@users_admin_required
|
||||
def api_update_user_permissions(user_id):
|
||||
"""Benutzerberechtigungen aktualisieren."""
|
||||
try:
|
||||
data = request.get_json()
|
||||
if not data:
|
||||
return jsonify({"error": "Keine Daten erhalten"}), 400
|
||||
|
||||
with get_cached_session() as db_session:
|
||||
# Benutzer prüfen
|
||||
user = db_session.query(User).filter_by(id=user_id).first()
|
||||
if not user:
|
||||
return jsonify({"error": "Benutzer nicht gefunden"}), 404
|
||||
|
||||
# Berechtigungen laden oder erstellen
|
||||
permission = db_session.query(UserPermission).filter_by(user_id=user_id).first()
|
||||
if not permission:
|
||||
permission = UserPermission(user_id=user_id)
|
||||
db_session.add(permission)
|
||||
|
||||
# Berechtigungen aktualisieren
|
||||
if 'can_start_jobs' in data:
|
||||
permission.can_start_jobs = bool(data['can_start_jobs'])
|
||||
|
||||
if 'needs_approval' in data:
|
||||
permission.needs_approval = bool(data['needs_approval'])
|
||||
|
||||
if 'can_approve_jobs' in data:
|
||||
permission.can_approve_jobs = bool(data['can_approve_jobs'])
|
||||
|
||||
db_session.commit()
|
||||
|
||||
logger.info(f"Berechtigungen für Benutzer {user_id} aktualisiert durch Admin {current_user.id}")
|
||||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"permissions": permission.to_dict()
|
||||
})
|
||||
|
||||
except SQLAlchemyError as e:
|
||||
logger.error(f"Datenbankfehler beim Aktualisieren der Benutzerberechtigungen: {str(e)}")
|
||||
return jsonify({"error": "Datenbankfehler beim Verarbeiten der Anfrage"}), 500
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Aktualisieren der Benutzerberechtigungen: {str(e)}")
|
||||
return jsonify({"error": "Fehler beim Verarbeiten der Anfrage"}), 500
|
||||
|
||||
@users_blueprint.route('/admin/users/<int:user_id>/permissions/update', methods=['POST'])
|
||||
@users_admin_required
|
||||
def admin_update_user_permissions(user_id):
|
||||
"""Benutzerberechtigungen über Formular aktualisieren."""
|
||||
try:
|
||||
# Formularfelder auslesen
|
||||
can_start_jobs = request.form.get('can_start_jobs') == 'on'
|
||||
needs_approval = request.form.get('needs_approval') == 'on'
|
||||
can_approve_jobs = request.form.get('can_approve_jobs') == 'on'
|
||||
|
||||
with get_cached_session() as db_session:
|
||||
# Benutzer prüfen
|
||||
user = db_session.query(User).filter_by(id=user_id).first()
|
||||
if not user:
|
||||
abort(404, "Benutzer nicht gefunden")
|
||||
|
||||
# Berechtigungen laden oder erstellen
|
||||
permission = db_session.query(UserPermission).filter_by(user_id=user_id).first()
|
||||
if not permission:
|
||||
permission = UserPermission(user_id=user_id)
|
||||
db_session.add(permission)
|
||||
|
||||
# Berechtigungen aktualisieren
|
||||
permission.can_start_jobs = can_start_jobs
|
||||
permission.needs_approval = needs_approval
|
||||
permission.can_approve_jobs = can_approve_jobs
|
||||
|
||||
db_session.commit()
|
||||
|
||||
logger.info(f"Berechtigungen für Benutzer {user_id} aktualisiert durch Admin {current_user.id} (Formular)")
|
||||
|
||||
return redirect(url_for('users.admin_user_permissions', user_id=user_id))
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Aktualisieren der Benutzerberechtigungen: {str(e)}")
|
||||
abort(500, "Fehler beim Verarbeiten der Anfrage")
|
||||
|
||||
# Erweiterung des bestehenden Benutzer-Bearbeitungsformulars
|
||||
@users_blueprint.route('/admin/users/<int:user_id>/edit/permissions', methods=['GET'])
|
||||
@users_admin_required
|
||||
def admin_edit_user_permissions_section(user_id):
|
||||
"""Rendert nur den Berechtigungsteil für das Benutzer-Edit-Formular."""
|
||||
with get_cached_session() as db_session:
|
||||
user = db_session.query(User).filter_by(id=user_id).first()
|
||||
if not user:
|
||||
abort(404, "Benutzer nicht gefunden")
|
||||
|
||||
# Berechtigungen laden oder erstellen, falls nicht vorhanden
|
||||
permission = db_session.query(UserPermission).filter_by(user_id=user_id).first()
|
||||
if not permission:
|
||||
permission = UserPermission(user_id=user_id)
|
||||
db_session.add(permission)
|
||||
db_session.commit()
|
||||
|
||||
return render_template('_user_permissions_form.html', user=user, permission=permission)
|
||||
Reference in New Issue
Block a user