"Refactor calendar blueprint and test printer creation"
This commit is contained in:
parent
ae74f4fc0c
commit
0b5a1f874d
@ -2,7 +2,7 @@ import json
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from flask import Blueprint, render_template, request, jsonify, redirect, url_for, abort
|
from flask import Blueprint, render_template, request, jsonify, redirect, url_for, abort
|
||||||
from flask_login import current_user, login_required
|
from flask_login import current_user, login_required
|
||||||
from sqlalchemy import and_, or_
|
from sqlalchemy import and_, or_, func
|
||||||
|
|
||||||
from models import Job, Printer, User, UserPermission, get_cached_session
|
from models import Job, Printer, User, UserPermission, get_cached_session
|
||||||
from utils.logging_config import get_logger
|
from utils.logging_config import get_logger
|
||||||
@ -21,6 +21,125 @@ def can_edit_events(user):
|
|||||||
return False
|
return False
|
||||||
return permission.can_approve_jobs
|
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'])
|
@calendar_blueprint.route('/calendar', methods=['GET'])
|
||||||
@login_required
|
@login_required
|
||||||
def calendar_view():
|
def calendar_view():
|
||||||
@ -137,10 +256,11 @@ def api_create_calendar_event():
|
|||||||
title = data.get('title')
|
title = data.get('title')
|
||||||
start = data.get('start')
|
start = data.get('start')
|
||||||
end = data.get('end')
|
end = data.get('end')
|
||||||
printer_id = data.get('printerId')
|
printer_id = data.get('printerId') # Jetzt optional
|
||||||
|
priority = data.get('priority', 'normal')
|
||||||
|
|
||||||
if not all([title, start, end, printer_id]):
|
if not all([title, start, end]):
|
||||||
return jsonify({"error": "Titel, Start, Ende und Drucker sind erforderlich"}), 400
|
return jsonify({"error": "Titel, Start und Ende sind erforderlich"}), 400
|
||||||
|
|
||||||
# Datumsfelder konvertieren
|
# Datumsfelder konvertieren
|
||||||
try:
|
try:
|
||||||
@ -153,11 +273,48 @@ def api_create_calendar_event():
|
|||||||
duration_minutes = int((end_date - start_date).total_seconds() / 60)
|
duration_minutes = int((end_date - start_date).total_seconds() / 60)
|
||||||
|
|
||||||
with get_cached_session() as db_session:
|
with get_cached_session() as db_session:
|
||||||
# Drucker prüfen
|
# 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()
|
printer = db_session.query(Printer).filter_by(id=printer_id).first()
|
||||||
if not printer:
|
if not printer:
|
||||||
return jsonify({"error": "Drucker nicht gefunden"}), 404
|
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
|
# Neuen Job erstellen
|
||||||
job = Job(
|
job = Job(
|
||||||
name=title,
|
name=title,
|
||||||
@ -174,7 +331,9 @@ def api_create_calendar_event():
|
|||||||
db_session.add(job)
|
db_session.add(job)
|
||||||
db_session.commit()
|
db_session.commit()
|
||||||
|
|
||||||
logger.info(f"Neuer Kalendereintrag erstellt: ID {job.id}, Name: {title}")
|
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({
|
return jsonify({
|
||||||
"success": True,
|
"success": True,
|
||||||
@ -182,7 +341,14 @@ def api_create_calendar_event():
|
|||||||
"title": job.name,
|
"title": job.name,
|
||||||
"start": job.start_at.isoformat(),
|
"start": job.start_at.isoformat(),
|
||||||
"end": job.end_at.isoformat(),
|
"end": job.end_at.isoformat(),
|
||||||
"status": job.status
|
"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:
|
except Exception as e:
|
||||||
@ -278,3 +444,107 @@ def api_delete_calendar_event(event_id):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Fehler beim Löschen des Kalendereintrags: {str(e)}")
|
logger.error(f"Fehler beim Löschen des Kalendereintrags: {str(e)}")
|
||||||
return jsonify({"error": "Fehler beim Verarbeiten der Anfrage"}), 500
|
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
|
@ -19,47 +19,54 @@ def create_test_printers():
|
|||||||
# Test-Drucker Daten
|
# Test-Drucker Daten
|
||||||
test_printers = [
|
test_printers = [
|
||||||
{
|
{
|
||||||
'name': 'Ultimaker S3 #01',
|
'name': 'Mercedes-Benz FDM Pro #01',
|
||||||
'model': 'Ultimaker S3',
|
'model': 'Ultimaker S5 Pro',
|
||||||
'location': 'Produktionshalle A',
|
'location': 'Werkhalle Sindelfingen',
|
||||||
'plug_ip': '192.168.1.100',
|
'plug_ip': '192.168.10.101',
|
||||||
'status': 'available',
|
'status': 'available',
|
||||||
'active': True
|
'active': True
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'Prusa MK3S+ #02',
|
'name': 'Mercedes-Benz FDM #02',
|
||||||
'model': 'Prusa MK3S+',
|
'model': 'Prusa MK3S+',
|
||||||
'location': 'Produktionshalle B',
|
'location': 'Entwicklungszentrum Stuttgart',
|
||||||
'plug_ip': '192.168.1.101',
|
'plug_ip': '192.168.10.102',
|
||||||
'status': 'offline',
|
'status': 'printing',
|
||||||
'active': True
|
'active': True
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'Bambu Lab X1 #03',
|
'name': 'Mercedes-Benz SLA #01',
|
||||||
'model': 'Bambu Lab X1 Carbon',
|
'model': 'Formlabs Form 3+',
|
||||||
'location': 'Labor R&D',
|
'location': 'Prototypenlabor',
|
||||||
'plug_ip': '192.168.1.102',
|
'plug_ip': '192.168.10.103',
|
||||||
'status': 'available',
|
'status': 'available',
|
||||||
'active': True
|
'active': True
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'Formlabs Form 3 #04',
|
'name': 'Mercedes-Benz Industrial #01',
|
||||||
'model': 'Formlabs Form 3',
|
'model': 'Stratasys F370',
|
||||||
'location': 'Prototyping Lab',
|
'location': 'Industriehalle Bremen',
|
||||||
'plug_ip': '192.168.1.103',
|
'plug_ip': '192.168.10.104',
|
||||||
'status': 'maintenance',
|
'status': 'maintenance',
|
||||||
'active': False
|
'active': False
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'Ender 3 V2 #05',
|
'name': 'Mercedes-Benz Rapid #01',
|
||||||
'model': 'Creality Ender 3 V2',
|
'model': 'Bambu Lab X1 Carbon',
|
||||||
'location': 'Testbereich',
|
'location': 'Designabteilung',
|
||||||
'plug_ip': '192.168.1.104',
|
'plug_ip': '192.168.10.105',
|
||||||
'status': 'offline',
|
'status': 'offline',
|
||||||
'active': True
|
'active': True
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'Mercedes-Benz SLS #01',
|
||||||
|
'model': 'HP Jet Fusion 5200',
|
||||||
|
'location': 'Produktionszentrum Berlin',
|
||||||
|
'plug_ip': '192.168.10.106',
|
||||||
|
'status': 'available',
|
||||||
|
'active': True
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
created_count = 0
|
created_count = 0
|
||||||
for printer_data in test_printers:
|
for printer_data in test_printers:
|
||||||
|
@ -1065,14 +1065,42 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
const priority = document.getElementById('eventPriority').value;
|
const priority = document.getElementById('eventPriority').value;
|
||||||
|
|
||||||
if (printerSelect.value === '' && startTime && endTime) {
|
if (printerSelect.value === '' && startTime && endTime) {
|
||||||
// Dynamische Empfehlung anzeigen
|
// Echte API-Empfehlung abrufen
|
||||||
showSmartRecommendation(startTime, endTime, priority);
|
fetchSmartRecommendation(startTime, endTime, priority);
|
||||||
} else {
|
} else {
|
||||||
hideSmartRecommendation();
|
hideSmartRecommendation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function showSmartRecommendation(start, end, priority) {
|
async function fetchSmartRecommendation(start, end, priority) {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/calendar/smart-recommendation', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRFToken': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
start: start,
|
||||||
|
end: end,
|
||||||
|
priority: priority
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.success && data.recommendation) {
|
||||||
|
showSmartRecommendation(data.recommendation);
|
||||||
|
} else {
|
||||||
|
showNoRecommendationMessage(data.message || 'Keine Empfehlung verfügbar');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Fehler beim Abrufen der Empfehlung:', error);
|
||||||
|
showNoRecommendationMessage('Fehler beim Abrufen der Empfehlung');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSmartRecommendation(recommendation) {
|
||||||
let existingRecommendation = document.getElementById('smart-recommendation');
|
let existingRecommendation = document.getElementById('smart-recommendation');
|
||||||
if (existingRecommendation) {
|
if (existingRecommendation) {
|
||||||
existingRecommendation.remove();
|
existingRecommendation.remove();
|
||||||
@ -1083,32 +1111,46 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
recommendationDiv.id = 'smart-recommendation';
|
recommendationDiv.id = 'smart-recommendation';
|
||||||
recommendationDiv.className = 'mt-3 p-4 bg-gradient-to-r from-green-50 to-blue-50 dark:from-green-900/20 dark:to-blue-900/20 rounded-lg border border-green-200 dark:border-green-800 transition-all duration-300';
|
recommendationDiv.className = 'mt-3 p-4 bg-gradient-to-r from-green-50 to-blue-50 dark:from-green-900/20 dark:to-blue-900/20 rounded-lg border border-green-200 dark:border-green-800 transition-all duration-300';
|
||||||
|
|
||||||
// Simuliere intelligente Empfehlung basierend auf Zeit und Priorität
|
// Optimierungsindikator
|
||||||
const recommendations = getSmartRecommendation(start, end, priority);
|
const optimizedBadge = recommendation.priority_optimized
|
||||||
|
? '<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-green-100 text-green-800 dark:bg-green-900/50 dark:text-green-300 ml-2">✨ Priorität optimiert</span>'
|
||||||
|
: '';
|
||||||
|
|
||||||
recommendationDiv.innerHTML = `
|
recommendationDiv.innerHTML = `
|
||||||
<div class="flex items-start gap-3">
|
<div class="flex items-start gap-3">
|
||||||
<div class="w-8 h-8 bg-gradient-to-br from-green-400 to-blue-500 rounded-full flex items-center justify-center">
|
<div class="w-8 h-8 bg-gradient-to-br from-green-400 to-blue-500 rounded-full flex items-center justify-center animate-pulse">
|
||||||
<svg class="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<h4 class="font-semibold text-green-800 dark:text-green-300 mb-2">🎯 Intelligente Empfehlung</h4>
|
<h4 class="font-semibold text-green-800 dark:text-green-300 mb-2 flex items-center">
|
||||||
|
🎯 Intelligente Empfehlung${optimizedBadge}
|
||||||
|
</h4>
|
||||||
<div class="text-sm text-green-700 dark:text-green-400">
|
<div class="text-sm text-green-700 dark:text-green-400">
|
||||||
<div class="flex items-center gap-2 mb-1">
|
<div class="flex items-center gap-2 mb-1">
|
||||||
<span class="font-medium">Empfohlener Drucker:</span>
|
<span class="font-medium">Empfohlener Drucker:</span>
|
||||||
<span class="px-2 py-1 bg-white dark:bg-slate-800 rounded-full border border-green-300 dark:border-green-600">
|
<span class="px-2 py-1 bg-white dark:bg-slate-800 rounded-full border border-green-300 dark:border-green-600 font-medium">
|
||||||
🖨️ ${recommendations.printer}
|
🖨️ ${recommendation.printer_name}
|
||||||
</span>
|
</span>
|
||||||
|
${recommendation.location ? `<span class="text-xs text-green-600 dark:text-green-500">(📍 ${recommendation.location})</span>` : ''}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs text-green-600 dark:text-green-500 mt-2">
|
<div class="text-xs text-green-600 dark:text-green-500 mt-2">
|
||||||
💡 ${recommendations.reason}
|
💡 ${recommendation.reason}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-4 mt-2 text-xs">
|
<div class="flex items-center gap-4 mt-2 text-xs">
|
||||||
<span>⚡ Verfügbarkeit: ${recommendations.availability}</span>
|
<span class="flex items-center gap-1">
|
||||||
<span>📊 Auslastung: ${recommendations.utilization}</span>
|
<span class="w-2 h-2 bg-green-500 rounded-full"></span>
|
||||||
<span>🎯 Eignung: ${recommendations.suitability}</span>
|
Verfügbarkeit: ${recommendation.availability}
|
||||||
|
</span>
|
||||||
|
<span class="flex items-center gap-1">
|
||||||
|
<span class="w-2 h-2 bg-blue-500 rounded-full"></span>
|
||||||
|
Auslastung: ${recommendation.utilization}
|
||||||
|
</span>
|
||||||
|
<span class="flex items-center gap-1">
|
||||||
|
<span class="w-2 h-2 bg-purple-500 rounded-full"></span>
|
||||||
|
Eignung: ${recommendation.suitability}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -1120,10 +1162,43 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
// Animation
|
// Animation
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
recommendationDiv.classList.add('animate-pulse');
|
recommendationDiv.classList.add('animate-pulse');
|
||||||
setTimeout(() => recommendationDiv.classList.remove('animate-pulse'), 1000);
|
setTimeout(() => recommendationDiv.classList.remove('animate-pulse'), 1500);
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showNoRecommendationMessage(message) {
|
||||||
|
let existingRecommendation = document.getElementById('smart-recommendation');
|
||||||
|
if (existingRecommendation) {
|
||||||
|
existingRecommendation.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
const printerContainer = document.getElementById('eventPrinter').parentElement;
|
||||||
|
const recommendationDiv = document.createElement('div');
|
||||||
|
recommendationDiv.id = 'smart-recommendation';
|
||||||
|
recommendationDiv.className = 'mt-3 p-4 bg-gradient-to-r from-yellow-50 to-orange-50 dark:from-yellow-900/20 dark:to-orange-900/20 rounded-lg border border-yellow-200 dark:border-yellow-800 transition-all duration-300';
|
||||||
|
|
||||||
|
recommendationDiv.innerHTML = `
|
||||||
|
<div class="flex items-start gap-3">
|
||||||
|
<div class="w-8 h-8 bg-gradient-to-br from-yellow-400 to-orange-500 rounded-full flex items-center justify-center">
|
||||||
|
<svg class="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.864-.833-2.634 0L4.18 16.5c-.77.833.192 2.5 1.732 2.5z"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<h4 class="font-semibold text-yellow-800 dark:text-yellow-300 mb-2">⚠️ Keine automatische Zuweisung möglich</h4>
|
||||||
|
<div class="text-sm text-yellow-700 dark:text-yellow-400">
|
||||||
|
${message}
|
||||||
|
<div class="mt-2 text-xs">
|
||||||
|
Bitte wählen Sie einen spezifischen Drucker aus der Liste oder ändern Sie den Zeitraum.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
printerContainer.appendChild(recommendationDiv);
|
||||||
|
}
|
||||||
|
|
||||||
function hideSmartRecommendation() {
|
function hideSmartRecommendation() {
|
||||||
const existingRecommendation = document.getElementById('smart-recommendation');
|
const existingRecommendation = document.getElementById('smart-recommendation');
|
||||||
if (existingRecommendation) {
|
if (existingRecommendation) {
|
||||||
@ -1133,48 +1208,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSmartRecommendation(start, end, priority) {
|
|
||||||
// Simuliere intelligente Logik basierend auf Zeit und Priorität
|
|
||||||
const startHour = new Date(start).getHours();
|
|
||||||
const duration = (new Date(end) - new Date(start)) / (1000 * 60 * 60); // Stunden
|
|
||||||
|
|
||||||
let recommendations = {
|
|
||||||
printer: "MYP-Drucker-01 (Halle A)",
|
|
||||||
reason: "Optimale Verfügbarkeit und geringe Auslastung im gewählten Zeitraum",
|
|
||||||
availability: "98%",
|
|
||||||
utilization: "24%",
|
|
||||||
suitability: "Ausgezeichnet"
|
|
||||||
};
|
|
||||||
|
|
||||||
if (priority === 'urgent') {
|
|
||||||
recommendations = {
|
|
||||||
printer: "MYP-Express-Drucker (Halle B)",
|
|
||||||
reason: "Schnellster verfügbarer Drucker für dringende Aufträge",
|
|
||||||
availability: "100%",
|
|
||||||
utilization: "15%",
|
|
||||||
suitability: "Perfekt"
|
|
||||||
};
|
|
||||||
} else if (startHour >= 18 || startHour <= 6) {
|
|
||||||
recommendations = {
|
|
||||||
printer: "MYP-Nacht-Drucker (Halle C)",
|
|
||||||
reason: "Speziell für Nachtschichten optimiert",
|
|
||||||
availability: "95%",
|
|
||||||
utilization: "12%",
|
|
||||||
suitability: "Optimal"
|
|
||||||
};
|
|
||||||
} else if (duration > 8) {
|
|
||||||
recommendations = {
|
|
||||||
printer: "MYP-Langzeit-Drucker (Halle A)",
|
|
||||||
reason: "Zuverlässig für lange Druckaufträge",
|
|
||||||
availability: "90%",
|
|
||||||
utilization: "35%",
|
|
||||||
suitability: "Sehr gut"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return recommendations;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Event Listeners für dynamische Empfehlung
|
// Event Listeners für dynamische Empfehlung
|
||||||
document.getElementById('eventStart').addEventListener('change', updatePrinterRecommendation);
|
document.getElementById('eventStart').addEventListener('change', updatePrinterRecommendation);
|
||||||
document.getElementById('eventEnd').addEventListener('change', updatePrinterRecommendation);
|
document.getElementById('eventEnd').addEventListener('change', updatePrinterRecommendation);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user