diff --git a/backend/__pycache__/models.cpython-313.pyc b/backend/__pycache__/models.cpython-313.pyc index 1e98837a0..a7e16c713 100644 Binary files a/backend/__pycache__/models.cpython-313.pyc and b/backend/__pycache__/models.cpython-313.pyc differ diff --git a/backend/blueprints/__pycache__/calendar.cpython-313.pyc b/backend/blueprints/__pycache__/calendar.cpython-313.pyc index 11a6dc11f..7cf8c8606 100644 Binary files a/backend/blueprints/__pycache__/calendar.cpython-313.pyc and b/backend/blueprints/__pycache__/calendar.cpython-313.pyc differ diff --git a/backend/logs/app/app.log b/backend/logs/app/app.log index 0e50bae62..5f6c5e149 100644 --- a/backend/logs/app/app.log +++ b/backend/logs/app/app.log @@ -2765,3 +2765,4 @@ WHERE jobs.status = ?) AS anon_1] 2025-06-02 10:56:52 - [app] app - [INFO] INFO - Dashboard-Refresh angefordert von User 1 2025-06-02 10:56:52 - [app] app - [INFO] INFO - Dashboard-Refresh erfolgreich: {'active_jobs': 0, 'available_printers': 2, 'total_jobs': 16, 'pending_jobs': 0, 'success_rate': 0.0, 'completed_jobs': 0, 'failed_jobs': 0, 'cancelled_jobs': None, 'total_users': 1, 'online_printers': 0, 'offline_printers': 2} 2025-06-02 10:56:52 - [app] app - [INFO] INFO - Dashboard-Refresh erfolgreich: {'active_jobs': 0, 'available_printers': None, 'total_jobs': 16, 'pending_jobs': 0, 'success_rate': 0.0, 'completed_jobs': 0, 'failed_jobs': 0, 'cancelled_jobs': 0, 'total_users': 1, 'online_printers': 0, 'offline_printers': 2} +2025-06-02 14:25:35 - [app] app - [INFO] INFO - Optimierte SQLite-Engine erstellt: C:\Users\TTOMCZA.EMEA\Dev\Projektarbeit-MYP\backend\database\myp.db diff --git a/backend/logs/scheduler/scheduler.log b/backend/logs/scheduler/scheduler.log index a1f4e925f..71ccb375f 100644 --- a/backend/logs/scheduler/scheduler.log +++ b/backend/logs/scheduler/scheduler.log @@ -27181,3 +27181,4 @@ 2025-06-02 10:57:11 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(, 'Connection to 192.168.0.103 timed out. (connect timeout=2)')) 2025-06-02 10:57:11 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 14 nicht einschalten 2025-06-02 10:57:11 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 15: test +2025-06-02 14:25:36 - [scheduler] scheduler - [INFO] INFO - Task check_jobs registriert: Intervall 30s, Enabled: True diff --git a/backend/logs/windows_fixes/windows_fixes.log b/backend/logs/windows_fixes/windows_fixes.log index 8954ea6dd..ee665b564 100644 --- a/backend/logs/windows_fixes/windows_fixes.log +++ b/backend/logs/windows_fixes/windows_fixes.log @@ -451,3 +451,7 @@ 2025-06-02 10:02:51 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Subprocess automatisch gepatcht für UTF-8 Encoding (run + Popen) 2025-06-02 10:02:51 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Globaler subprocess-Patch angewendet 2025-06-02 10:02:51 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Alle Windows-Fixes erfolgreich angewendet +2025-06-02 14:25:35 - [windows_fixes] windows_fixes - [INFO] INFO - 🔧 Wende Windows-spezifische Fixes an... +2025-06-02 14:25:35 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Subprocess automatisch gepatcht für UTF-8 Encoding (run + Popen) +2025-06-02 14:25:35 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Globaler subprocess-Patch angewendet +2025-06-02 14:25:35 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Alle Windows-Fixes erfolgreich angewendet diff --git a/backend/setup.sh b/backend/setup.sh index 0b08e8003..b8cb2bae9 100644 --- a/backend/setup.sh +++ b/backend/setup.sh @@ -117,17 +117,23 @@ check_system_resources() { local ram_mb=$(free -m | awk '/^Mem:/{print $2}') progress "Verfügbarer RAM: ${ram_mb}MB" - if [ "$ram_mb" -lt 512 ]; then + if [ -n "$ram_mb" ] && [ "$ram_mb" -eq "$ram_mb" ] 2>/dev/null && [ "$ram_mb" -lt 512 ]; then warning "⚠️ Wenig RAM verfügbar (${ram_mb}MB) - Installation könnte langsam sein" - else + elif [ -n "$ram_mb" ] && [ "$ram_mb" -eq "$ram_mb" ] 2>/dev/null; then success "✅ Ausreichend RAM verfügbar (${ram_mb}MB)" + else + warning "⚠️ RAM-Größe konnte nicht ermittelt werden" fi # Festplattenplatz prüfen local disk_free_gb=$(df / | awk 'NR==2{printf "%.1f", $4/1024/1024}') progress "Verfügbarer Festplattenplatz: ${disk_free_gb}GB" - if [ "$(echo "$disk_free_gb < 2.0" | bc 2>/dev/null || echo "0")" -eq 1 ]; then + # Konvertiere zu Ganzzahl für Vergleich (GB * 10 für eine Dezimalstelle) + local disk_free_int=$(echo "$disk_free_gb * 10" | bc 2>/dev/null | cut -d. -f1) + local min_required_int=20 # 2.0 GB * 10 + + if [ -z "$disk_free_int" ] || [ "$disk_free_int" -lt "$min_required_int" ]; then warning "⚠️ Wenig Festplattenplatz verfügbar (${disk_free_gb}GB)" else success "✅ Ausreichend Festplattenplatz verfügbar (${disk_free_gb}GB)" diff --git a/backend/static/js/conflict-manager.js b/backend/static/js/conflict-manager.js new file mode 100644 index 000000000..11c835a66 --- /dev/null +++ b/backend/static/js/conflict-manager.js @@ -0,0 +1,741 @@ +/** + * Erweiterte Druckerkonflikt-Management-Engine - Frontend + * MYP Platform + * + * Behandelt Konflikte zwischen Druckerreservierungen mit + * intelligenten Lösungsvorschlägen und Benutzerführung. + */ + +class ConflictManager { + constructor() { + this.lastConflictCheck = null; + this.currentRecommendation = null; + this.autoCheckEnabled = true; + this.checkTimeout = null; + this.debounceDelay = 500; // ms + + this.init(); + } + + init() { + this.createConflictModal(); + this.createAvailabilityPanel(); + this.createSmartRecommendationWidget(); + this.attachEventListeners(); + + console.log('🔧 ConflictManager initialisiert'); + } + + // =================== MODAL CREATION =================== + + createConflictModal() { + const modalHTML = ` + `; + + document.body.insertAdjacentHTML('beforeend', modalHTML); + } + + createAvailabilityPanel() { + const panelHTML = ` + `; + + // Panel nach dem Kalender-Container einfügen + const calendarContainer = document.querySelector('.container'); + if (calendarContainer) { + calendarContainer.insertAdjacentHTML('afterbegin', panelHTML); + } + } + + createSmartRecommendationWidget() { + const widgetHTML = ` + `; + + // Widget nach dem Kalender-Container einfügen + const calendarContainer = document.querySelector('.container'); + if (calendarContainer) { + calendarContainer.insertAdjacentHTML('afterbegin', widgetHTML); + } + } + + // =================== EVENT LISTENERS =================== + + attachEventListeners() { + // Konflikt-Modal Events + document.getElementById('closeConflictModal')?.addEventListener('click', () => { + this.hideConflictModal(); + }); + + document.getElementById('applyAutoFix')?.addEventListener('click', () => { + this.applyAutoFix(); + }); + + document.getElementById('ignoreConflicts')?.addEventListener('click', () => { + this.ignoreConflicts(); + }); + + // Smart-Empfehlung Events + document.getElementById('acceptRecommendation')?.addEventListener('click', () => { + this.acceptRecommendation(); + }); + + document.getElementById('dismissRecommendation')?.addEventListener('click', () => { + this.dismissRecommendation(); + }); + + // Formular-Validierung Events + this.attachFormValidation(); + + // Verfügbarkeits-Button (falls vorhanden) + document.getElementById('refreshAvailability')?.addEventListener('click', () => { + this.refreshAvailability(); + }); + } + + attachFormValidation() { + const formFields = ['eventStart', 'eventEnd', 'eventPrinter', 'eventPriority']; + + formFields.forEach(fieldId => { + const field = document.getElementById(fieldId); + if (field) { + field.addEventListener('change', () => { + this.scheduleValidation(); + }); + + field.addEventListener('input', () => { + this.scheduleValidation(); + }); + } + }); + } + + // =================== API CALLS =================== + + async checkConflicts(eventData) { + try { + const response = await fetch('/api/calendar/check-conflicts', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(eventData) + }); + + if (!response.ok) { + throw new Error(`HTTP ${response.status}`); + } + + const result = await response.json(); + this.lastConflictCheck = result; + return result; + } catch (error) { + console.error('❌ Fehler bei Konfliktprüfung:', error); + this.showError('Konfliktprüfung fehlgeschlagen'); + return null; + } + } + + async resolveConflictsAndCreate(eventData) { + try { + const response = await fetch('/api/calendar/resolve-conflicts', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({...eventData, auto_resolve: true}) + }); + + if (!response.ok) { + const errorData = await response.json(); + if (response.status === 409) { + this.showConflictModal(errorData); + return null; + } else { + throw new Error(errorData.error || `HTTP ${response.status}`); + } + } + + return await response.json(); + } catch (error) { + console.error('❌ Fehler bei Konfliktlösung:', error); + this.showError('Automatische Konfliktlösung fehlgeschlagen'); + return null; + } + } + + async loadPrinterAvailability(startTime, endTime) { + try { + const params = new URLSearchParams({ + start: startTime, + end: endTime + }); + + const response = await fetch(`/api/calendar/printer-availability?${params}`); + if (!response.ok) { + throw new Error(`HTTP ${response.status}`); + } + + const data = await response.json(); + this.displayAvailability(data); + return data; + } catch (error) { + console.error('❌ Fehler beim Laden der Verfügbarkeit:', error); + return null; + } + } + + async getSmartRecommendation(eventData) { + try { + const response = await fetch('/api/calendar/smart-recommendation', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(eventData) + }); + + if (!response.ok) return null; + + const recommendation = await response.json(); + if (recommendation.success) { + this.displayRecommendation(recommendation.recommendation); + this.currentRecommendation = recommendation.recommendation; + return recommendation.recommendation; + } + return null; + } catch (error) { + console.error('❌ Fehler bei Smart-Empfehlung:', error); + return null; + } + } + + // =================== UI DISPLAY METHODS =================== + + showConflictModal(conflictData) { + const modal = document.getElementById('conflictNotificationModal'); + if (!modal) return; + + this.updateConflictSummary(conflictData); + this.updateConflictDetails(conflictData); + this.updateConflictRecommendations(conflictData); + + modal.classList.remove('hidden'); + } + + hideConflictModal() { + const modal = document.getElementById('conflictNotificationModal'); + if (modal) { + modal.classList.add('hidden'); + } + } + + updateConflictSummary(conflictData) { + const title = document.getElementById('conflictTitle'); + const description = document.getElementById('conflictDescription'); + const icon = document.getElementById('conflictIcon'); + const summary = document.getElementById('conflictSummary'); + + if (!title || !description || !icon || !summary) return; + + if (conflictData.severity_score >= 3) { + icon.textContent = '🚨'; + title.textContent = 'Kritische Konflikte'; + summary.className = summary.className.replace(/amber/g, 'red'); + } else { + icon.textContent = '⚠️'; + title.textContent = 'Konflikte erkannt'; + } + + description.textContent = `${conflictData.conflict_count} Konflikt(e) gefunden. ${conflictData.can_proceed ? 'Automatische Lösung möglich.' : 'Manuelle Anpassung erforderlich.'}`; + } + + updateConflictDetails(conflictData) { + const details = document.getElementById('conflictDetails'); + if (!details) return; + + details.innerHTML = ''; + if (conflictData.conflicts) { + conflictData.conflicts.forEach(conflict => { + const conflictEl = this.createConflictElement(conflict); + details.appendChild(conflictEl); + }); + } + } + + updateConflictRecommendations(conflictData) { + const recommendations = document.getElementById('smartRecommendations'); + if (!recommendations) return; + + recommendations.innerHTML = ''; + if (conflictData.recommendations) { + conflictData.recommendations.forEach(rec => { + const recEl = this.createRecommendationElement(rec); + recommendations.appendChild(recEl); + }); + } + + // Auto-Fix Button aktivieren/deaktivieren + const autoFixBtn = document.getElementById('applyAutoFix'); + if (autoFixBtn) { + autoFixBtn.disabled = !conflictData.can_proceed; + } + } + + createConflictElement(conflict) { + const div = document.createElement('div'); + div.className = 'p-3 border border-slate-200 dark:border-slate-600 rounded-lg'; + + const severityColors = { + 'kritisch': 'red', + 'hoch': 'orange', + 'mittel': 'amber', + 'niedrig': 'blue', + 'information': 'slate' + }; + + const color = severityColors[conflict.severity] || 'slate'; + + div.innerHTML = ` +
+ +
+

${conflict.description}

+

${conflict.estimated_impact}

+ ${conflict.conflicting_jobs?.length > 0 ? ` +
+ Betroffene Jobs: ${conflict.conflicting_jobs.map(job => job.name).join(', ')} +
+ ` : ''} +
+
+ `; + + return div; + } + + createRecommendationElement(recommendation) { + const div = document.createElement('div'); + div.className = 'p-3 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg'; + + let content = ` +
+ 💡 +
+

${recommendation.message}

+ `; + + if (recommendation.suggestions) { + content += '
'; + recommendation.suggestions.forEach(suggestion => { + content += ` +
+ ${suggestion.description || suggestion.printer_name} + ${suggestion.confidence ? `${Math.round(suggestion.confidence * 100)}%` : ''} +
+ `; + }); + content += '
'; + } + + content += ` +
+
+ `; + + div.innerHTML = content; + return div; + } + + displayAvailability(data) { + const panel = document.getElementById('printerAvailabilityPanel'); + if (!panel) return; + + const summary = data.summary; + + // Zusammenfassung aktualisieren + this.updateElement('totalPrinters', summary.total_printers); + this.updateElement('availablePrinters', summary.available_printers); + this.updateElement('optimalPrinters', summary.optimal_printers); + this.updateElement('availabilityRate', `${summary.availability_rate}%`); + this.updateElement('availabilityTimestamp', `Aktualisiert: ${new Date().toLocaleTimeString()}`); + + // Drucker-Details aktualisieren + const detailsList = document.getElementById('printerDetailsList'); + if (detailsList) { + detailsList.innerHTML = ''; + + data.printers.forEach(printer => { + const printerEl = document.createElement('div'); + printerEl.className = 'flex items-center justify-between p-2 bg-slate-50 dark:bg-slate-700 rounded'; + + printerEl.innerHTML = ` +
+ ${printer.availability_icon} +
+
${printer.printer_name}
+
${printer.location || 'Kein Standort'}
+
+
+
+
${printer.status_description}
+
${printer.recent_jobs_24h} Jobs (24h)
+
+ `; + + detailsList.appendChild(printerEl); + }); + } + + panel.classList.remove('hidden'); + } + + displayRecommendation(recommendation) { + const widget = document.getElementById('smartRecommendationWidget'); + if (!widget) return; + + const text = document.getElementById('recommendationText'); + const details = document.getElementById('recommendationDetails'); + + if (text) { + text.textContent = `Drucker: ${recommendation.printer_name}`; + } + + if (details) { + details.innerHTML = ` +
📍 Standort: ${recommendation.location}
+
📊 Verfügbarkeit: ${recommendation.availability}
+
⚡ Auslastung: ${recommendation.utilization}
+
🎯 Eignung: ${recommendation.suitability}
+
💡 ${recommendation.reason}
+ `; + } + + widget.classList.remove('hidden'); + } + + // =================== FORM VALIDATION =================== + + scheduleValidation() { + if (this.checkTimeout) { + clearTimeout(this.checkTimeout); + } + + this.checkTimeout = setTimeout(() => { + this.validateFormRealtime(); + }, this.debounceDelay); + } + + async validateFormRealtime() { + if (!this.autoCheckEnabled) return; + + const formData = this.getFormData(); + if (!formData.start_time || !formData.end_time) return; + + // Formular-Hash für Debouncing + const formHash = JSON.stringify(formData); + if (this.lastFormHash === formHash) return; + this.lastFormHash = formHash; + + try { + // Konflikte prüfen + const conflicts = await this.checkConflicts(formData); + if (conflicts) { + this.showInlineConflictWarning(conflicts); + } + + // Smart-Empfehlung anzeigen (wenn kein Drucker ausgewählt) + if (!formData.printer_id) { + await this.getSmartRecommendation(formData); + } + } catch (error) { + console.error('❌ Fehler bei Echzeit-Validierung:', error); + } + } + + showInlineConflictWarning(conflictData) { + // Inline-Warnung anzeigen (falls im Template vorhanden) + const warning = document.getElementById('conflictWarning'); + if (!warning) return; + + if (conflictData.has_conflicts) { + // Warnung anzeigen + warning.classList.remove('hidden'); + + // Nachrichten aktualisieren + const messages = document.getElementById('conflictMessages'); + if (messages) { + messages.innerHTML = ''; + conflictData.conflicts.forEach(conflict => { + const msgEl = document.createElement('div'); + msgEl.textContent = `${conflict.severity}: ${conflict.description}`; + messages.appendChild(msgEl); + }); + } + } else { + warning.classList.add('hidden'); + } + } + + // =================== USER ACTIONS =================== + + async applyAutoFix() { + if (!this.lastConflictCheck) return; + + const formData = this.getFormData(); + const result = await this.resolveConflictsAndCreate(formData); + + if (result && result.success) { + this.hideConflictModal(); + this.showSuccess('Konflikte automatisch gelöst und Auftrag erstellt! ✅'); + + // Kalender aktualisieren + if (window.calendar) { + window.calendar.refetchEvents(); + } + + // Modal schließen + if (window.closeEventModal) { + window.closeEventModal(); + } + } + } + + ignoreConflicts() { + this.hideConflictModal(); + this.showWarning('Konflikte werden ignoriert. Bitte manuell prüfen.'); + } + + acceptRecommendation() { + if (this.currentRecommendation) { + const printerSelect = document.getElementById('eventPrinter'); + if (printerSelect) { + printerSelect.value = this.currentRecommendation.printer_id; + this.dismissRecommendation(); + this.showSuccess('Empfehlung angenommen! 🎯'); + } + } + } + + dismissRecommendation() { + const widget = document.getElementById('smartRecommendationWidget'); + if (widget) { + widget.classList.add('hidden'); + } + this.currentRecommendation = null; + } + + async refreshAvailability() { + const now = new Date(); + const endTime = new Date(now.getTime() + 24 * 60 * 60 * 1000); // +24h + + await this.loadPrinterAvailability( + now.toISOString(), + endTime.toISOString() + ); + } + + // =================== HELPER METHODS =================== + + getFormData() { + return { + title: this.getFieldValue('eventTitle'), + description: this.getFieldValue('eventDescription'), + printer_id: this.getFieldValue('eventPrinter') || null, + start_time: this.getFieldValue('eventStart'), + end_time: this.getFieldValue('eventEnd'), + priority: this.getFieldValue('eventPriority') || 'normal' + }; + } + + getFieldValue(fieldId) { + const field = document.getElementById(fieldId); + return field ? field.value : ''; + } + + updateElement(elementId, content) { + const element = document.getElementById(elementId); + if (element) { + element.textContent = content; + } + } + + // =================== NOTIFICATIONS =================== + + showSuccess(message) { + this.showNotification(message, 'success'); + } + + showError(message) { + this.showNotification(message, 'error'); + } + + showWarning(message) { + this.showNotification(message, 'warning'); + } + + showNotification(message, type = 'info') { + const colors = { + success: 'from-green-500 to-green-600', + error: 'from-red-500 to-red-600', + warning: 'from-amber-500 to-amber-600', + info: 'from-blue-500 to-blue-600' + }; + + const icons = { + success: '', + error: '', + warning: '', + info: '' + }; + + const toast = document.createElement('div'); + toast.className = `fixed top-4 right-4 bg-gradient-to-r ${colors[type]} text-white px-6 py-4 rounded-lg shadow-xl z-50 transform transition-all duration-300 translate-x-full`; + toast.innerHTML = ` +
+
+ + ${icons[type]} + +
+ ${message} +
+ `; + + document.body.appendChild(toast); + setTimeout(() => toast.classList.remove('translate-x-full'), 100); + setTimeout(() => { + toast.classList.add('translate-x-full'); + setTimeout(() => toast.remove(), 300); + }, 4000); + } +} + +// Globale Instanz für einfache Nutzung +window.conflictManager = new ConflictManager(); + +// Integration mit bestehenden Kalender-Funktionen +document.addEventListener('DOMContentLoaded', function() { + console.log('✅ ConflictManager erfolgreich geladen'); + + // Integration mit bestehendem Formular + const existingForm = document.getElementById('eventForm'); + if (existingForm) { + // Existing form handler erweitern + existingForm.addEventListener('submit', async function(e) { + const formData = window.conflictManager.getFormData(); + + // Konflikte vor Submit prüfen + const conflicts = await window.conflictManager.checkConflicts(formData); + if (conflicts && conflicts.has_conflicts && !conflicts.can_proceed) { + e.preventDefault(); + window.conflictManager.showConflictModal(conflicts); + return false; + } + }); + } +}); \ No newline at end of file diff --git a/backend/templates/calendar.html b/backend/templates/calendar.html index 0a21abf84..999238089 100644 --- a/backend/templates/calendar.html +++ b/backend/templates/calendar.html @@ -818,6 +818,11 @@ Produktionseinheit +