🎉 Refactor & Update Backend Files, Documentation 📚
This commit is contained in:
@@ -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()
|
||||
|
Reference in New Issue
Block a user