diff --git a/Web/app.py b/Web/app.py index 13463e2..fa91a3a 100755 --- a/Web/app.py +++ b/Web/app.py @@ -2659,6 +2659,12 @@ def upload_student_cards_excel(): """Bulk import student cards from Excel.""" return _upload_student_cards_excel() +@app.route('/api/is_student_card/', methods=['POST']) +def is_student_card(id): + user = sanitize_form_value(id) + exists = bool(us.student_card_exists(user)) + return jsonify({"is_student_card": exists}) + """-------------------------------------------------------------File Serving-----------------------------------------------------------------------------""" diff --git a/Web/templates/library_table.html b/Web/templates/library_table.html index 7c1da5b..eced068 100644 --- a/Web/templates/library_table.html +++ b/Web/templates/library_table.html @@ -495,12 +495,13 @@
+ +

📚 Bibliothek

Bücher, CDs, DVDs und weitere Medien

- - + - - - -
-
- - - - - - - - -
-
- Hinweis: Im Schnellmodus zuerst den Bibliotheksausweis scannen, danach den Buch-/Mediencode. -
- +
+ + + + + + +
@@ -946,6 +932,32 @@ setScanStatus('Scanner gestoppt.', 'warn'); } + async function isStudentCardBarcode(code) { + if (!code) return false; + + try { + const response = await fetch(`/api/is_student_card/${encodeURIComponent(code)}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + // Falls du in Flask CSRF-Protect nutzt, muss der Token mitgesendet werden: + 'X-CSRFToken': '{{ csrf_token }}', + 'X-CSRF-Token': '{{ csrf_token }}' + } + }); + + if (!response.ok) return false; + + // Wir parsen die Antwort. Flask jsonify({"is_student_card": true/false}) + const data = await response.json(); + return data === true || data.is_student_card === true; + + } catch (error) { + console.error('Fehler bei der API-Prüfung des Ausweises:', error); + return false; + } + } + Quagga.onDetected(function(data) { if (!data || !data.codeResult || !data.codeResult.code) return; @@ -955,13 +967,6 @@ const currentCallback = activeScannerCallback; stopScanner(); - const returnOnly = (document.getElementById('returnOnlyToggle') || {}).checked; - if (returnOnly) { - // direct return flow - returnByCode(barcode); - return; - } - if (typeof currentCallback === "function") { currentCallback(barcode); } else { @@ -988,14 +993,8 @@ keyboardScanBuffer = ''; keyboardLastKeyAt = 0; if (!code) return; - // If return-only mode is active, attempt direct return - const returnOnly = (document.getElementById('returnOnlyToggle') || {}).checked; - if (returnOnly) { - returnByCode(code); - return; - } - // Process exactly like a camera scan + // Process exactly like a camera scan - centralized logic handles the rest! handleScanSuccess(code); return; } @@ -1014,33 +1013,123 @@ } function handleScanSuccess(decodedText) { - const scannedCode = normalizeScannedCode(decodedText); + const scannedCode = typeof normalizeScannedCode === "function" ? normalizeScannedCode(decodedText) : decodedText; if (!scannedCode) return; const now = Date.now(); if (scannedCode === lastScanValue && (now - lastScanAt) < 1500) { - return; + return; // Verhindert doppeltes Scannen in kurzer Zeit } lastScanValue = scannedCode; lastScanAt = now; + // Den aktuellen Modus aus dem Dropdown auslesen const mode = (document.getElementById('scanModeSelect') || {}).value || 'card_only'; + + // 1. Modus: Nur Rückgabe + if (mode === 'return_only') { + returnByCode(scannedCode); + return; + } + + // 2. Modus: Nur Ausweis if (mode === 'card_only') { setActiveStudentCard(scannedCode); setScanStatus(`Ausweis gesetzt: ${activeStudentCardId}`, 'ok'); return; } - processQuickToggleScan(scannedCode); + // 3. Modus: Schnellmodus (1x Ausweis, 1x Buch) + if (mode === 'quick_toggle') { + processQuickToggleScan(scannedCode); + return; + } + + // 4. Modus: Dauermodus (1x Ausweis, Nx Bücher) + if (mode === 'continuous') { + processContinuousScan(scannedCode); + return; + } + } + + async function processContinuousScan(scannedCode) { + setScanStatus('Überprüfe Code-Typ...', 'warn'); + + const isCard = await isStudentCardBarcode(scannedCode); + + if (isCard) { + setActiveStudentCard(scannedCode); + setScanStatus(`Neuer Ausweis gesetzt: ${activeStudentCardId}. Bitte Medien scannen.`, 'ok'); + showSmallConfirm(`Benutzer gewechselt zu: ${activeStudentCardId}`, 'ok'); + + setTimeout(() => { + if (document.getElementById('scanModeSelect').value === 'continuous') { + startScanner(); + } + }, 1000); + return; + } + + if (!activeStudentCardId) { + setScanStatus('Kein Ausweis aktiv! Bitte zuerst einen Schülerausweis scannen.', 'error'); + showSmallConfirm('Bitte zuerst Ausweis scannen', 'error'); + + setTimeout(() => { + if (document.getElementById('scanModeSelect').value === 'continuous') { + startScanner(); + } + }, 2000); + return; + } + + try { + setScanStatus('Verarbeite Mediencode...', 'warn'); + const response = await fetch('/api/library_scan_action', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': '{{ csrf_token }}', + 'X-CSRF-Token': '{{ csrf_token }}' + }, + body: JSON.stringify({ + student_card_id: activeStudentCardId, + item_code: scannedCode + }) + }); + + const result = await response.json(); + if (!response.ok || !result.ok) { + setScanStatus(result.message || 'Scan-Aktion fehlgeschlagen.', 'error'); + showSmallConfirm(result.message || 'Aktion fehlgeschlagen.', 'error'); + } else if (result.action === 'borrowed') { + setScanStatus(`Ausgeliehen: ${result.item_name}`, 'ok'); + showSmallConfirm(`Ausgeliehen: ${result.item_name}`, 'ok'); + } else if (result.action === 'returned') { + setScanStatus(`Zurückgegeben: ${result.item_name}`, 'ok'); + showSmallConfirm(`Zurückgegeben: ${result.item_name}`, 'ok'); + } else { + setScanStatus(result.message || 'Aktion durchgeführt.', 'ok'); + showSmallConfirm(result.message || 'Erfolgreich', 'ok'); + } + + // Tabellen-Ansicht aktualisieren + if (typeof loadLibraryItems === "function") { + await loadLibraryItems(); + } + } catch (err) { + console.error('Continuous scan action failed:', err); + setScanStatus('Fehler beim Verarbeiten des Scans.', 'error'); + showSmallConfirm('Fehler im Netzwerk/System', 'error'); + } + + setTimeout(() => { + if (document.getElementById('scanModeSelect').value === 'continuous') { + startScanner(); + } + }, 1500); } async function processQuickToggleScan(scannedCode) { - // 1. Prüfen, ob "Nur Rückgabe"-Modus aktiv ist - const returnOnly = (document.getElementById('returnOnlyToggle') || {}).checked; - if (returnOnly) { - await returnByCode(scannedCode); - return; - } // 2. Wenn kein Ausweis gesetzt ist, wird der Code als Ausweis interpretiert if (!activeStudentCardId) { @@ -1434,111 +1523,30 @@ }); } - const manualReturnBtn = document.getElementById('manualReturnBtn'); + const manualActionBtn = document.getElementById('manualActionBtn'); const manualItemCode = document.getElementById('manualItemCode'); - if (manualReturnBtn && manualItemCode) { - manualReturnBtn.addEventListener('click', async () => { + + if (manualActionBtn && manualItemCode) { + manualActionBtn.addEventListener('click', () => { const code = (manualItemCode.value || '').trim(); if (!code) { - alert('Bitte einen Mediencode eingeben.'); + alert('Bitte einen Code eingeben.'); return; } - await returnByCode(code); + + // Leitet den manuellen Code an die zentrale Logik weiter, + // die prüft, welcher Modus im Dropdown aktiv ist. + handleScanSuccess(code); + + // Feld nach Eingabe leeren, um direkt den nächsten Code bereitzuhaben + manualItemCode.value = ''; }); - } - const editForm = document.getElementById('editLibraryForm'); - if (editForm) { - editForm.addEventListener('submit', async function (e) { - e.preventDefault(); - - const itemId = document.getElementById('editLibraryItemId').value; - const currentItem = libraryItems.find(i => i._id === itemId); - if (!currentItem) return; - - const codeInputs = Array.from(document.querySelectorAll('#editLibraryCodesContainer input[data-item-id]')); - - // Daten aus dem Formular sammeln - const sharedPayload = { - name: document.getElementById('editLibraryName').value, - item_type: document.getElementById('editLibraryType').value, - isbn: document.getElementById('editLibraryIsbn').value, - ort: document.getElementById('editLibraryLocation').value, - beschreibung: document.getElementById('editLibraryDescription').value, - ansch_jahr: currentItem.Anschaffungsjahr || '', - ansch_kost: currentItem.Anschaffungskosten || '', - reservierbar: currentItem.Reservierbar !== false, - }; - - const codeByItemId = new Map(codeInputs.map(input => [input.dataset.itemId, (input.value || '').trim()])); - const groupMembers = editLibraryState.groupMembers.length > 0 ? editLibraryState.groupMembers : [currentItem]; - const isGroupedEdit = Boolean(currentItem.SeriesGroupId) && groupMembers.length > 1; - - // API-Aufruf - try { - if (isGroupedEdit) { - const payload = { - series_group_id: currentItem.SeriesGroupId, - ...sharedPayload, - items: groupMembers.map(member => ({ - id: member._id, - code_4: codeByItemId.get(member._id) || '' - })) - }; - - const response = await fetch('/update_group', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'X-CSRFToken': '{{ csrf_token }}', - 'X-CSRF-Token': '{{ csrf_token }}' - }, - body: JSON.stringify(payload) - }); - - const result = await response.json(); - if (response.ok && result.success) { - alert(result.message || 'Gruppe erfolgreich aktualisiert!'); - closeEditLibraryModal(); - pagingState.loading = false; - await loadLibraryItems(); - } else { - alert(result.message || 'Fehler beim Speichern der Gruppenänderungen.'); - } - } else { - const primaryCodeInput = codeInputs[0]; - const payload = { - name: sharedPayload.name, - item_type: sharedPayload.item_type, - isbn: sharedPayload.isbn, - code_4: primaryCodeInput ? primaryCodeInput.value.trim() : (currentItem.Code_4 || currentItem.Code4 || '').trim(), - ort: sharedPayload.ort, - beschreibung: sharedPayload.beschreibung - }; - - const response = await fetch(`/api/library_item/${itemId}/update`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'X-CSRFToken': '{{ csrf_token }}', - 'X-CSRF-Token': '{{ csrf_token }}' - }, - body: JSON.stringify(payload) - }); - - const result = await response.json(); - if (response.ok && result.ok) { - alert(result.message || 'Medium erfolgreich aktualisiert!'); - closeEditLibraryModal(); - pagingState.loading = false; - await loadLibraryItems(); - } else { - alert(result.message || 'Fehler beim Speichern der Änderungen.'); - } - } - } catch (error) { - console.error('Update failed:', error); - alert('Netzwerkfehler beim Aktualisieren des Mediums.'); + // Optional: Auch auf "Enter" im Textfeld reagieren + manualItemCode.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + e.preventDefault(); + manualActionBtn.click(); } }); } @@ -1560,58 +1568,6 @@ } } - function renderLibraryGroupCodeFields(groupMembers, currentItemId) { - const codesContainer = document.getElementById('editLibraryCodesContainer'); - const groupWarning = document.getElementById('editLibraryGroupWarning'); - const groupCount = document.getElementById('editLibraryGroupCount'); - const groupHint = document.getElementById('editLibraryGroupHint'); - - if (!codesContainer) return; - - const items = Array.isArray(groupMembers) ? groupMembers.slice() : []; - items.sort((a, b) => (a.SeriesPosition || 0) - (b.SeriesPosition || 0) || String(a.Name || '').localeCompare(String(b.Name || ''))); - - editLibraryState.groupMembers = items; - - if (groupWarning) { - groupWarning.style.display = items.length > 1 ? 'block' : 'none'; - } - if (groupCount) { - const totalCount = items.length || 1; - const declaredCount = items[0]?.SeriesCount || totalCount; - groupCount.textContent = `${totalCount} / ${declaredCount}`; - } - if (groupHint) { - groupHint.textContent = items.length > 1 - ? 'Jeder Code gehört zu einem eigenen Exemplar. Änderungen werden für alle Codes gespeichert.' - : 'Einzelnes Exemplar. Der Code wird direkt gespeichert.'; - } - - if (!items.length) { - codesContainer.innerHTML = '
Keine Codes geladen.
'; - return; - } - - codesContainer.innerHTML = items.map((member, index) => { - const codeValue = member.Code_4 || member.Code4 || ''; - const labelParts = []; - if (member.SeriesPosition !== undefined && member.SeriesPosition !== null) { - labelParts.push(`Exemplar ${member.SeriesPosition}`); - } else { - labelParts.push(`Exemplar ${index + 1}`); - } - if (member._id === currentItemId) { - labelParts.push('aktuelles Medium'); - } - return ` -
- - -
- `; - }).join(''); - } - function openEditLibraryItem(itemId) { window.location.href = `/item_edit/${itemId}`; }