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_ 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 @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') if not all([title, start, end, printer_id]): return jsonify({"error": "Titel, Start, Ende und Drucker 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: # Drucker prüfen printer = db_session.query(Printer).filter_by(id=printer_id).first() if not printer: return jsonify({"error": "Drucker nicht gefunden"}), 404 # 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() logger.info(f"Neuer Kalendereintrag erstellt: ID {job.id}, Name: {title}") 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 Erstellen des Kalendereintrags: {str(e)}") return jsonify({"error": "Fehler beim Verarbeiten der Anfrage"}), 500 @calendar_blueprint.route('/api/calendar/event/', 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/', 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