🎉 Refactor & Update Backend Files, Documentation 📚

This commit is contained in:
2025-06-01 00:05:09 +02:00
parent 193164964e
commit b2bdc2d123
12 changed files with 4178 additions and 1868 deletions

View File

@@ -21,7 +21,7 @@ from flask import request, jsonify, current_app
from flask_login import current_user
from utils.logging_config import get_logger
from models import Job, Printer, get_db_session
from models import Job, Printer, JobOrder, get_db_session
from utils.file_manager import save_job_file, save_temp_file
from config.settings import ALLOWED_EXTENSIONS, MAX_FILE_SIZE, UPLOAD_FOLDER
@@ -119,34 +119,282 @@ class DragDropManager:
def update_job_order(self, printer_id: int, job_ids: List[int]) -> bool:
"""Aktualisiert die Job-Reihenfolge für einen Drucker"""
try:
with get_db_session() as db_session:
# Validiere dass alle Jobs existieren und zum Drucker gehören
jobs = db_session.query(Job).filter(
Job.id.in_(job_ids),
Job.printer_id == printer_id,
Job.status.in_(['scheduled', 'paused'])
).all()
if len(jobs) != len(job_ids):
logger.warning(f"Nicht alle Jobs gefunden oder gehören zu Drucker {printer_id}")
return False
# Aktuelle Benutzer-ID für Audit-Trail
user_id = current_user.id if current_user.is_authenticated else None
# Validierung der Eingaben
if not isinstance(printer_id, int) or printer_id <= 0:
logger.error(f"Ungültige Drucker-ID: {printer_id}")
return False
if not isinstance(job_ids, list) or not job_ids:
logger.error(f"Ungültige Job-IDs Liste: {job_ids}")
return False
# Duplikate entfernen und Reihenfolge beibehalten
unique_job_ids = []
seen = set()
for job_id in job_ids:
if job_id not in seen:
unique_job_ids.append(job_id)
seen.add(job_id)
if len(unique_job_ids) != len(job_ids):
logger.warning(f"Duplikate in Job-IDs entfernt: {job_ids} -> {unique_job_ids}")
job_ids = unique_job_ids
# Datenbank-Implementierung mit JobOrder-Tabelle
success = JobOrder.update_printer_order(
printer_id=printer_id,
job_ids=job_ids,
modified_by_user_id=user_id
)
if success:
# Cache aktualisieren
self.job_order_cache[printer_id] = job_ids
# Optional: In Datenbank speichern (erweiterte Implementierung)
# Hier könnte man ein separates Job-Order-Table verwenden
logger.info(f"Job-Reihenfolge für Drucker {printer_id} erfolgreich aktualisiert: {job_ids}")
logger.info(f"Aktualisiert von Benutzer: {user_id}")
# Optional: Bereinigung ungültiger Einträge im Hintergrund
self._schedule_cleanup()
logger.info(f"Job-Reihenfolge für Drucker {printer_id} aktualisiert: {job_ids}")
return True
else:
logger.error(f"Fehler beim Speichern der Job-Reihenfolge in der Datenbank")
return False
except Exception as e:
logger.error(f"Fehler beim Aktualisieren der Job-Reihenfolge: {str(e)}")
logger.error(f"Unerwarteter Fehler beim Aktualisieren der Job-Reihenfolge: {str(e)}")
return False
def get_job_order(self, printer_id: int) -> List[int]:
"""Holt die aktuelle Job-Reihenfolge für einen Drucker"""
return self.job_order_cache.get(printer_id, [])
try:
# Erst aus Cache versuchen
if printer_id in self.job_order_cache:
cached_order = self.job_order_cache[printer_id]
logger.debug(f"Job-Reihenfolge aus Cache für Drucker {printer_id}: {cached_order}")
return cached_order
# Aus Datenbank laden
job_ids = JobOrder.get_ordered_job_ids(printer_id)
# Cache aktualisieren
self.job_order_cache[printer_id] = job_ids
logger.debug(f"Job-Reihenfolge aus Datenbank geladen für Drucker {printer_id}: {job_ids}")
return job_ids
except Exception as e:
logger.error(f"Fehler beim Laden der Job-Reihenfolge für Drucker {printer_id}: {str(e)}")
return []
def get_ordered_jobs_for_printer(self, printer_id: int) -> List[Job]:
"""
Holt die Jobs für einen Drucker in der korrekten Reihenfolge.
Args:
printer_id: ID des Druckers
Returns:
List[Job]: Jobs sortiert nach der benutzerdefinierten Reihenfolge
"""
try:
# Job-IDs in der korrekten Reihenfolge holen
ordered_job_ids = self.get_job_order(printer_id)
if not ordered_job_ids:
# Fallback: Jobs nach Standard-Kriterien sortieren
with get_db_session() as db_session:
jobs = db_session.query(Job).filter(
Job.printer_id == printer_id,
Job.status.in_(['scheduled', 'paused'])
).order_by(Job.created_at).all()
return jobs
# Jobs in der definierten Reihenfolge laden
with get_db_session() as db_session:
# Alle relevanten Jobs laden
all_jobs = db_session.query(Job).filter(
Job.printer_id == printer_id,
Job.status.in_(['scheduled', 'paused'])
).all()
# Dictionary für schnelle Zugriffe
jobs_dict = {job.id: job for job in all_jobs}
# Jobs in der korrekten Reihenfolge zusammenstellen
ordered_jobs = []
for job_id in ordered_job_ids:
if job_id in jobs_dict:
ordered_jobs.append(jobs_dict[job_id])
# Jobs hinzufügen, die nicht in der Reihenfolge sind (neue Jobs)
ordered_job_ids_set = set(ordered_job_ids)
unordered_jobs = [job for job in all_jobs if job.id not in ordered_job_ids_set]
if unordered_jobs:
# Neue Jobs nach Erstellungsdatum sortieren und anhängen
unordered_jobs.sort(key=lambda x: x.created_at)
ordered_jobs.extend(unordered_jobs)
# Reihenfolge automatisch aktualisieren für neue Jobs
new_order = [job.id for job in ordered_jobs]
self.update_job_order(printer_id, new_order)
logger.debug(f"Jobs für Drucker {printer_id} in Reihenfolge geladen: {len(ordered_jobs)} Jobs")
return ordered_jobs
except Exception as e:
logger.error(f"Fehler beim Laden der sortierten Jobs für Drucker {printer_id}: {str(e)}")
# Fallback: Unsortierte Jobs zurückgeben
try:
with get_db_session() as db_session:
jobs = db_session.query(Job).filter(
Job.printer_id == printer_id,
Job.status.in_(['scheduled', 'paused'])
).order_by(Job.created_at).all()
return jobs
except Exception as fallback_error:
logger.error(f"Auch Fallback fehlgeschlagen: {str(fallback_error)}")
return []
def remove_job_from_order(self, job_id: int) -> bool:
"""
Entfernt einen Job aus allen Drucker-Reihenfolgen.
Args:
job_id: ID des zu entfernenden Jobs
Returns:
bool: True wenn erfolgreich
"""
try:
# Aus Datenbank entfernen
JobOrder.remove_job_from_orders(job_id)
# Cache aktualisieren: Job aus allen Caches entfernen
for printer_id in list(self.job_order_cache.keys()):
if job_id in self.job_order_cache[printer_id]:
self.job_order_cache[printer_id].remove(job_id)
logger.debug(f"Job {job_id} aus Cache für Drucker {printer_id} entfernt")
logger.info(f"Job {job_id} erfolgreich aus allen Reihenfolgen entfernt")
return True
except Exception as e:
logger.error(f"Fehler beim Entfernen des Jobs {job_id} aus Reihenfolgen: {str(e)}")
return False
def cleanup_invalid_orders(self):
"""Bereinigt ungültige Job-Reihenfolgen"""
try:
# Datenbank-Bereinigung
JobOrder.cleanup_invalid_orders()
# Cache komplett leeren (wird bei Bedarf neu geladen)
self.job_order_cache.clear()
logger.info("Job-Reihenfolgen bereinigt")
except Exception as e:
logger.error(f"Fehler bei der Bereinigung der Job-Reihenfolgen: {str(e)}")
def _schedule_cleanup(self):
"""Plant eine Bereinigung für später (non-blocking)"""
try:
# In produktiver Umgebung könnte hier ein Background-Task gestartet werden
# Für jetzt führen wir eine schnelle Bereinigung durch
import threading
def cleanup_worker():
try:
self.cleanup_invalid_orders()
except Exception as e:
logger.error(f"Hintergrund-Bereinigung fehlgeschlagen: {str(e)}")
cleanup_thread = threading.Thread(target=cleanup_worker, daemon=True)
cleanup_thread.start()
except Exception as e:
logger.debug(f"Konnte Hintergrund-Bereinigung nicht starten: {str(e)}")
def get_printer_summary(self, printer_id: int) -> Dict[str, Any]:
"""
Erstellt eine Zusammenfassung der Job-Reihenfolge für einen Drucker.
Args:
printer_id: ID des Druckers
Returns:
Dict: Zusammenfassung mit Jobs, Reihenfolge, Statistiken
"""
try:
ordered_jobs = self.get_ordered_jobs_for_printer(printer_id)
# Statistiken berechnen
total_duration = sum(job.duration_minutes for job in ordered_jobs)
total_jobs = len(ordered_jobs)
# Nächster Job
next_job = ordered_jobs[0] if ordered_jobs else None
# Job-Details für die Ausgabe
job_details = []
for position, job in enumerate(ordered_jobs):
job_details.append({
'position': position,
'job_id': job.id,
'name': job.name,
'duration_minutes': job.duration_minutes,
'user_name': job.user.name if job.user else 'Unbekannt',
'created_at': job.created_at.isoformat() if job.created_at else None,
'status': job.status
})
return {
'printer_id': printer_id,
'total_jobs': total_jobs,
'total_duration_minutes': total_duration,
'estimated_completion': self._calculate_completion_time(ordered_jobs),
'next_job': {
'id': next_job.id,
'name': next_job.name,
'user': next_job.user.name if next_job and next_job.user else None
} if next_job else None,
'jobs': job_details,
'last_updated': datetime.now().isoformat()
}
except Exception as e:
logger.error(f"Fehler beim Erstellen der Drucker-Zusammenfassung für {printer_id}: {str(e)}")
return {
'printer_id': printer_id,
'total_jobs': 0,
'total_duration_minutes': 0,
'error': str(e)
}
def _calculate_completion_time(self, jobs: List[Job]) -> Optional[str]:
"""Berechnet die geschätzte Fertigstellungszeit"""
try:
if not jobs:
return None
total_minutes = sum(job.duration_minutes for job in jobs)
completion_time = datetime.now()
completion_time = completion_time.replace(
minute=(completion_time.minute + total_minutes) % 60,
hour=(completion_time.hour + (completion_time.minute + total_minutes) // 60) % 24
)
return completion_time.isoformat()
except Exception:
return None
# Globale Instanz
drag_drop_manager = DragDropManager()