Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 74e11b5370 | |||
| c0d6ad7b72 | |||
| 7da7152332 | |||
| 3c9ab099ba | |||
| a81a8775a5 | |||
| 1633118d7f | |||
| a4b900c986 | |||
| a06393a915 | |||
| 1b9bfe1b14 | |||
| 3cdd1552d9 |
+73
-41
@@ -5207,55 +5207,87 @@ def upload_item():
|
||||
flash(error_msg, 'error')
|
||||
return redirect(url_for(success_redirect_endpoint))
|
||||
|
||||
# Check if base code is unique for single-item uploads
|
||||
if code_4 and item_count == 1 and not it.is_code_unique(code_4[0]):
|
||||
existing_item = it._get_items_collection().find_one({"code_4": code_4[0]})
|
||||
if existing_item:
|
||||
error_msg = 'Der Code wird bereits verwendet. Umleitung zum Eintrag.'
|
||||
if is_mobile:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': error_msg,
|
||||
'existing_item_id': str(existing_item['_id']),
|
||||
'redirect_to_item': True
|
||||
}), 400
|
||||
|
||||
flash(error_msg, 'info')
|
||||
return redirect(url_for(success_redirect_endpoint, open_item=str(existing_item['_id'])))
|
||||
# -------------------------------------------------------------------
|
||||
# 1. PARSE AND UNIFY CODES
|
||||
# -------------------------------------------------------------------
|
||||
# Assuming `code_4` might come in as a single string or a list depending on your form setup
|
||||
primary_code_raw = request.form.get('code_4', '').strip()
|
||||
individual_codes_raw = request.form.get('individual_codes', '').strip()
|
||||
|
||||
all_item_codes = []
|
||||
|
||||
if primary_code_raw:
|
||||
all_item_codes.append(primary_code_raw)
|
||||
|
||||
if individual_codes_raw:
|
||||
# Split textarea by newlines, clean whitespace, and ignore empty lines
|
||||
extra_codes = [c.strip() for c in individual_codes_raw.replace('\r', '').split('\n') if c.strip()]
|
||||
|
||||
# Add to master list, ensuring no duplicates within the submitted list itself
|
||||
for c in extra_codes:
|
||||
if c not in all_item_codes:
|
||||
all_item_codes.append(c)
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# 2. OVERRIDE ITEM COUNT
|
||||
# -------------------------------------------------------------------
|
||||
# If the user scanned/generated codes, the amount of codes IS the item count.
|
||||
# Otherwise, fallback to a manual item_count field if it exists.
|
||||
if len(all_item_codes) > 0:
|
||||
item_count = len(all_item_codes)
|
||||
else:
|
||||
try:
|
||||
item_count = int(request.form.get('item_count', 1))
|
||||
except ValueError:
|
||||
item_count = 1
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# 3. IMAGE VALIDATION
|
||||
# -------------------------------------------------------------------
|
||||
if upload_mode != 'library' and not is_duplicating and not images and not duplicate_images and not book_cover_image:
|
||||
error_msg = 'Bitte laden Sie mindestens ein Bild hoch'
|
||||
if is_mobile:
|
||||
return jsonify({'success': False, 'message': error_msg}), 400
|
||||
else:
|
||||
error_msg = 'Der Code wird bereits verwendet. Bitte wählen Sie einen anderen Code.'
|
||||
flash(error_msg, 'error')
|
||||
return redirect(url_for(success_redirect_endpoint))
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# 4. UNIFIED DB UNIQUENESS VALIDATION
|
||||
# -------------------------------------------------------------------
|
||||
# Validate every code in our master list against the database
|
||||
for code in all_item_codes:
|
||||
if not it.is_code_unique(code):
|
||||
|
||||
# Special case: If they only uploaded ONE item, redirect them to that existing item
|
||||
if item_count == 1:
|
||||
existing_item = it._get_items_collection().find_one({"code_4": code})
|
||||
if existing_item:
|
||||
error_msg = 'Der Code wird bereits verwendet. Umleitung zum Eintrag.'
|
||||
if is_mobile:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': error_msg,
|
||||
'existing_item_id': str(existing_item['_id']),
|
||||
'redirect_to_item': True
|
||||
}), 400
|
||||
|
||||
flash(error_msg, 'info')
|
||||
return redirect(url_for(success_redirect_endpoint, open_item=str(existing_item['_id'])))
|
||||
|
||||
# If multiple items are being uploaded, just throw a standard error
|
||||
error_msg = f'Der Code "{code}" wird bereits von einem anderen Artikel verwendet. Bitte überprüfen Sie Ihre Eingaben.'
|
||||
if is_mobile:
|
||||
return jsonify({'success': False, 'message': error_msg}), 400
|
||||
else:
|
||||
flash(error_msg, 'error')
|
||||
return redirect(url_for(success_redirect_endpoint))
|
||||
|
||||
# Validate optional per-item codes
|
||||
if individual_codes:
|
||||
if len(individual_codes) > item_count:
|
||||
error_msg = f'Zu viele Einzelcodes angegeben ({len(individual_codes)}), erlaubt sind maximal {item_count}.'
|
||||
if is_mobile:
|
||||
return jsonify({'success': False, 'message': error_msg}), 400
|
||||
flash(error_msg, 'error')
|
||||
return redirect(url_for(success_redirect_endpoint))
|
||||
|
||||
if len(set(individual_codes)) != len(individual_codes):
|
||||
error_msg = 'Doppelte Einzelcodes erkannt. Bitte alle Codes eindeutig eintragen.'
|
||||
if is_mobile:
|
||||
return jsonify({'success': False, 'message': error_msg}), 400
|
||||
flash(error_msg, 'error')
|
||||
return redirect(url_for(success_redirect_endpoint))
|
||||
|
||||
for specific_code in individual_codes:
|
||||
if not it.is_code_unique(specific_code):
|
||||
error_msg = f'Der Einzelcode "{specific_code}" wird bereits verwendet.'
|
||||
if is_mobile:
|
||||
return jsonify({'success': False, 'message': error_msg}), 400
|
||||
flash(error_msg, 'error')
|
||||
return redirect(url_for(success_redirect_endpoint))
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# 5. BATCH CODE GENERATOR (Remains mostly unchanged)
|
||||
# -------------------------------------------------------------------
|
||||
def generate_unique_batch_code(base_code, position):
|
||||
"""Generate a unique code for every item in a batch."""
|
||||
"""Generate a unique code for every item in a batch if no specific code is provided."""
|
||||
if not base_code:
|
||||
return None
|
||||
|
||||
|
||||
@@ -1295,11 +1295,6 @@
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if 'username' in session %}
|
||||
{% if current_permissions.pages.get('my_borrowed_items', True) %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link quick-link-pill {% if current_path == url_for('my_borrowed_items') %}nav-active{% endif %}" href="{{ url_for('my_borrowed_items') }}" data-tutorial-tip="Hier sehen Sie Ihre bibliotheksbezogenen Ausleihen.">Meine Medien</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if current_permissions.pages.get('tutorial_page', True) %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link quick-link-pill {% if current_path == url_for('tutorial_page') %}nav-active{% endif %}" href="{{ url_for('tutorial_page') }}" data-tutorial-tip="Nutzen Sie das Tutorial, um die Bibliotheksfunktionen kennenzulernen.">Tutorial</a>
|
||||
|
||||
@@ -1057,7 +1057,7 @@
|
||||
document.getElementById('detailModal').style.display = 'none';
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// =========================================================================
|
||||
// 5. EVENT LISTENERS INITIALIZATION & ON-LOAD INITIALIZER
|
||||
// =========================================================================
|
||||
function wireScannerUi() {
|
||||
@@ -1093,43 +1093,129 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Connect standard Filters and Search inputs
|
||||
document.getElementById('filterToggleBtn').addEventListener('click', () => {
|
||||
filterPanelOpen = !filterPanelOpen;
|
||||
document.getElementById('filterPanel').classList.toggle('open');
|
||||
document.getElementById('filterToggleBtn').classList.toggle('active');
|
||||
});
|
||||
|
||||
document.getElementById('applyFilterBtn').addEventListener('click', () => {
|
||||
activeFilters.isbn = document.getElementById('filterISBN').value.toLowerCase();
|
||||
activeFilters.type = document.getElementById('filterType').value;
|
||||
activeFilters.status = document.getElementById('filterStatus').value;
|
||||
applyFiltersAndSearch(true);
|
||||
});
|
||||
|
||||
document.getElementById('clearFilterBtn').addEventListener('click', () => {
|
||||
document.getElementById('filterISBN').value = '';
|
||||
document.getElementById('filterType').value = '';
|
||||
document.getElementById('filterStatus').value = '';
|
||||
activeFilters = { isbn: '', type: '', status: '' };
|
||||
applyFiltersAndSearch(true);
|
||||
});
|
||||
|
||||
document.getElementById('librarySearch').addEventListener('input', (e) => {
|
||||
currentSearchTerm = (e.target.value || '').toLowerCase();
|
||||
applyFiltersAndSearch(true);
|
||||
});
|
||||
|
||||
document.getElementById('loadMoreBtn').addEventListener('click', () => {
|
||||
renderedCount = Math.min(renderedCount + RENDER_BATCH_COUNT, visibleItems.length);
|
||||
renderItems();
|
||||
});
|
||||
|
||||
// Run when DOM structure is entirely ready
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
wireScannerUi(); // Setup scanner control buttons
|
||||
loadLibraryItems(); // Fetch your database items right away!
|
||||
|
||||
// Safely connect standard Filters and Search inputs inside DOMContentLoaded
|
||||
const filterToggleBtn = document.getElementById('filterToggleBtn');
|
||||
if (filterToggleBtn) {
|
||||
filterToggleBtn.addEventListener('click', () => {
|
||||
filterPanelOpen = !filterPanelOpen;
|
||||
document.getElementById('filterPanel').classList.toggle('open');
|
||||
filterToggleBtn.classList.toggle('active');
|
||||
});
|
||||
}
|
||||
|
||||
const applyFilterBtn = document.getElementById('applyFilterBtn');
|
||||
if (applyFilterBtn) {
|
||||
applyFilterBtn.addEventListener('click', () => {
|
||||
activeFilters.isbn = document.getElementById('filterISBN').value.toLowerCase();
|
||||
activeFilters.type = document.getElementById('filterType').value;
|
||||
activeFilters.status = document.getElementById('filterStatus').value;
|
||||
applyFiltersAndSearch(true);
|
||||
});
|
||||
}
|
||||
|
||||
const clearFilterBtn = document.getElementById('clearFilterBtn');
|
||||
if (clearFilterBtn) {
|
||||
clearFilterBtn.addEventListener('click', () => {
|
||||
document.getElementById('filterISBN').value = '';
|
||||
document.getElementById('filterType').value = '';
|
||||
document.getElementById('filterStatus').value = '';
|
||||
activeFilters = { isbn: '', type: '', status: '' };
|
||||
applyFiltersAndSearch(true);
|
||||
});
|
||||
}
|
||||
|
||||
const librarySearch = document.getElementById('librarySearch');
|
||||
if (librarySearch) {
|
||||
librarySearch.addEventListener('input', (e) => {
|
||||
currentSearchTerm = (e.target.value || '').toLowerCase();
|
||||
applyFiltersAndSearch(true);
|
||||
});
|
||||
}
|
||||
|
||||
const loadMoreBtn = document.getElementById('loadMoreBtn');
|
||||
if (loadMoreBtn) {
|
||||
loadMoreBtn.addEventListener('click', () => {
|
||||
renderedCount = Math.min(renderedCount + RENDER_BATCH_COUNT, visibleItems.length);
|
||||
renderItems();
|
||||
});
|
||||
}
|
||||
|
||||
// Edit Modal Form processing
|
||||
const editForm = document.getElementById('editLibraryForm');
|
||||
if (editForm) {
|
||||
editForm.addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const itemId = document.getElementById('editLibraryItemId').value;
|
||||
|
||||
const updatedData = {
|
||||
name: document.getElementById('editLibraryName').value,
|
||||
item_type: document.getElementById('editLibraryType').value,
|
||||
isbn: document.getElementById('editLibraryIsbn').value,
|
||||
code_4: document.getElementById('editLibraryCode4').value,
|
||||
ort: document.getElementById('editLibraryLocation').value,
|
||||
beschreibung: document.getElementById('editLibraryDescription').value
|
||||
};
|
||||
|
||||
try {
|
||||
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(updatedData)
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok && result.ok) {
|
||||
alert(result.message || 'Medium erfolgreich aktualisiert!');
|
||||
closeEditLibraryModal();
|
||||
|
||||
pagingState.loading = false;
|
||||
loadLibraryItems();
|
||||
} else {
|
||||
alert(result.message || 'Fehler beim Speichern der Änderungen.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Update failed:', error);
|
||||
alert('Netzwerkfehler beim Aktualisieren des Mediums.');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Modal Control Functions (accessible globally from table buttons)
|
||||
function openEditLibraryItem(itemId) {
|
||||
const item = libraryItems.find(i => i._id === itemId);
|
||||
|
||||
if (!item) {
|
||||
alert('Fehler: Das Medium konnte nicht gefunden werden.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Populate the form fields - Using correct German properties from MongoDB fields
|
||||
document.getElementById('editLibraryItemId').value = item._id || '';
|
||||
document.getElementById('editLibraryName').value = item.Name || '';
|
||||
document.getElementById('editLibraryType').value = item.ItemType || 'book';
|
||||
document.getElementById('editLibraryIsbn').value = item.ISBN || '';
|
||||
document.getElementById('editLibraryCode4').value = item.Code_4 || item.Code4 || '';
|
||||
document.getElementById('editLibraryLocation').value = item.Ort || '';
|
||||
document.getElementById('editLibraryDescription').value = item.Beschreibung || '';
|
||||
|
||||
document.getElementById('editLibraryModal').style.display = 'flex';
|
||||
}
|
||||
|
||||
function closeEditLibraryModal() {
|
||||
document.getElementById('editLibraryModal').style.display = 'none';
|
||||
}
|
||||
</script>
|
||||
|
||||
<div id="editLibraryModal" class="modal" style="display:none;">
|
||||
@@ -1177,4 +1263,4 @@
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
@@ -373,7 +373,7 @@
|
||||
|
||||
<div class="filter-group">
|
||||
<div class="filter-header">
|
||||
<label>Thema:</label>
|
||||
<label>Schlagwort:</label>
|
||||
<button type="button" class="filter-toggle" onclick="toggleFilterDropdown('filter3-dropdown')">▼</button>
|
||||
<button type="button" class="clear-filter" onclick="clearFilter(3)">Clear</button>
|
||||
</div>
|
||||
@@ -420,7 +420,7 @@
|
||||
<span>Ort</span>
|
||||
<span>Unterrichtsfach</span>
|
||||
<span>Jahrgangsstufe</span>
|
||||
<span>Thema</span>
|
||||
<span>Schlagwort</span>
|
||||
<span>Barcode</span>
|
||||
</div>
|
||||
<div id="items-container" class="items-container">
|
||||
@@ -1057,7 +1057,7 @@
|
||||
<p class="item-col-location"><strong>Ort:</strong> ${item.Ort || '-'}</p>
|
||||
<p class="item-col-filter1"><strong>Unterrichtsfach:</strong> ${filter1Display}${filter1More}</p>
|
||||
<p class="item-col-filter2"><strong>Jahrgangsstufe:</strong> ${filter2Display}${filter2More}</p>
|
||||
<p class="item-col-filter3"><strong>Thema:</strong> ${filter3Display}${filter3More}</p>
|
||||
<p class="item-col-filter3"><strong>Schlagwort:</strong> ${filter3Display}${filter3More}</p>
|
||||
<p class="item-col-code"><strong>Barcode:</strong> ${item.Code_4 || '-'}</p>
|
||||
<p class="item-col-count"><strong>Anzahl:</strong> ${groupedCount}</p>
|
||||
${isGroupedItem ? `<p class="item-col-count"><strong>Verfügbar:</strong> ${availableGroupedCount}</p>` : ''}
|
||||
@@ -1682,7 +1682,7 @@
|
||||
</div>
|
||||
|
||||
<div class="detail-group">
|
||||
<div class="detail-label">Thema:</div>
|
||||
<div class="detail-label">Schlagwort:</div>
|
||||
<div class="detail-value">${filter3Array.join(', ') || '-'}</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -2395,7 +2395,7 @@ document.addEventListener('DOMContentLoaded', ()=>{
|
||||
|
||||
<div class="filter-group">
|
||||
<div class="filter-header">
|
||||
<label>Thema:</label>
|
||||
<label>Schlagwort:</label>
|
||||
<button type="button" class="filter-toggle" onclick="toggleFilterDropdown('filter3-dropdown')">▼</button>
|
||||
<button type="button" class="clear-filter" onclick="clearFilter(3)">Clear</button>
|
||||
</div>
|
||||
@@ -2446,7 +2446,7 @@ document.addEventListener('DOMContentLoaded', ()=>{
|
||||
<span>Ort</span>
|
||||
<span>Unterrichtsfach</span>
|
||||
<span>Jahrgangsstufe</span>
|
||||
<span>Thema</span>
|
||||
<span>Schlagwort</span>
|
||||
<span>Barcode</span>
|
||||
<span>Anzahl</span>
|
||||
</div>
|
||||
@@ -2567,7 +2567,7 @@ document.addEventListener('DOMContentLoaded', ()=>{
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>Thema:</h3>
|
||||
<h3>Schlagwort:</h3>
|
||||
<div class="multi-filter">
|
||||
<div class="form-group">
|
||||
<label for="edit-filter3-1">Wert 1:</label>
|
||||
@@ -3686,7 +3686,7 @@ document.addEventListener('DOMContentLoaded', ()=>{
|
||||
<p class="item-col-location"><strong>Ort:</strong> ${item.Ort || '-'}</p>
|
||||
<p class="item-col-filter1"><strong>Unterrichtsfach:</strong> ${filter1Display}${filter1More}</p>
|
||||
<p class="item-col-filter2"><strong>Jahrgangsstufe:</strong> ${filter2Display}${filter2More}</p>
|
||||
<p class="item-col-filter3"><strong>Thema:</strong> ${filter3Display}${filter3More}</p>
|
||||
<p class="item-col-filter3"><strong>Schlagwort:</strong> ${filter3Display}${filter3More}</p>
|
||||
<p class="item-col-code"><strong>Barcode:</strong> ${item.Code_4 || '-'}</p>
|
||||
<p class="item-col-count"><strong>Anzahl:</strong> ${groupedCount}</p>
|
||||
${hasDamage ? `<div class="damage-badge">${damageCount > 0 ? `Schäden gemeldet: ${damageCount}` : 'Schäden gemeldet'}</div>` : ''}
|
||||
@@ -4229,7 +4229,7 @@ document.addEventListener('DOMContentLoaded', ()=>{
|
||||
document.getElementById('edit-filter2-3').value = filter2Array[2] || '';
|
||||
document.getElementById('edit-filter2-4').value = filter2Array[3] || '';
|
||||
|
||||
// Fill in filter 3 (Thema)
|
||||
// Fill in filter 3 (Schlagwort)
|
||||
const filter3Array = Array.isArray(item.Filter3) ? item.Filter3 : (item.Filter3 ? [item.Filter3] : []);
|
||||
document.getElementById('edit-filter3-1').value = filter3Array[0] || '';
|
||||
document.getElementById('edit-filter3-2').value = filter3Array[1] || '';
|
||||
@@ -4543,7 +4543,7 @@ document.addEventListener('DOMContentLoaded', ()=>{
|
||||
</div>
|
||||
|
||||
<div class="detail-group">
|
||||
<div class="detail-label">Thema:</div>
|
||||
<div class="detail-label">Schlagwort:</div>
|
||||
<div class="detail-value">${filter3Array.join(', ') || '-'}</div>
|
||||
</div>
|
||||
|
||||
|
||||
+238
-71
@@ -155,6 +155,27 @@
|
||||
background-color: #1e7e34;
|
||||
}
|
||||
|
||||
/* Quagga Scanner Video Styles */
|
||||
#code4-scanner video, #code4-scanner canvas,
|
||||
#isbn-scanner video, #isbn-scanner canvas {
|
||||
width: 100%;
|
||||
max-width: 640px;
|
||||
height: auto;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
#code4-scanner canvas, #isbn-scanner canvas {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
#code4-scanner, #isbn-scanner {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* ISBN input group styling */
|
||||
.isbn-input-group {
|
||||
display: flex;
|
||||
@@ -831,7 +852,7 @@
|
||||
<!-- Options will be loaded by JavaScript -->
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="formupload_admin-group">
|
||||
<label for="filter2-4">Wert 4:</label>
|
||||
<select id="filter2-4" name="filter2" class="filter-dropdown-select">
|
||||
<option value="">-- Optional --</option>
|
||||
@@ -840,7 +861,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>Thema:</h3>
|
||||
<h3>Schlagwort:</h3>
|
||||
<div class="multi-filter">
|
||||
<div class="form-group">
|
||||
<label for="filter3-1">Wert 1:</label>
|
||||
@@ -869,25 +890,41 @@
|
||||
<label for="anschaffungskosten">Anschaffungskosten (€)</label>
|
||||
<input id="anschaffungskosten" name="anschaffungskosten">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="code_4">Code</label>
|
||||
<div style="display:flex; gap:8px; flex-wrap:wrap; align-items:center;">
|
||||
<input id="code_4" name="code_4">
|
||||
<button type="button" id="scan-code4-btn" class="fetch-isbn-button">Barcode scannen</button>
|
||||
<div class="form-group">
|
||||
<label for="scan_mode">Erfassungs-Modus:</label>
|
||||
<select id="scan_mode" name="scan_mode" class="form-control" onchange="toggleScanMode()">
|
||||
<option value="single">Einzel-Code (Scanner stoppt nach 1 Scan)</option>
|
||||
<option value="continuous">Fortlaufend scannen (Mehrere Codes nacheinander)</option>
|
||||
<option value="range">Code-Bereich generieren (z.B. ABC-001 bis ABC-010)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div id="range_generator_group" class="form-group" style="display:none; background: #f9f9f9; padding: 15px; border-radius: 5px; border: 1px solid #ddd; margin-bottom: 15px;">
|
||||
<label style="font-weight: bold;">Code-Bereich automatisch generieren:</label>
|
||||
<div style="display: flex; gap: 10px; margin-bottom: 10px; align-items: center;">
|
||||
<input type="text" id="range_prefix" placeholder="Präfix (z.B. IT-)" class="form-control" style="flex: 2;">
|
||||
<input type="number" id="range_start" placeholder="Start (z.B. 1)" class="form-control" style="flex: 1;">
|
||||
<span style="font-weight: bold;">bis</span>
|
||||
<input type="text" id="range_end" placeholder="Ende (z.B. 020)" class="form-control" style="flex: 1;">
|
||||
</div>
|
||||
<small style="display:block; color:#666;">Einzelcode manuell setzen oder per Scanner erfassen.</small>
|
||||
<div id="code4-scanner" style="width:100%; max-width:520px; display:none; margin-top:10px;"></div>
|
||||
<small id="code4-scan-status" style="display:block; color:#666; margin-top:6px;"></small>
|
||||
<small style="display:block; color:#666; margin-bottom: 10px;">Tipp: Die Anzahl der Ziffern im "Ende"-Feld bestimmt die führenden Nullen (z.B. Ende "050" macht aus Start "1" einen "001").</small>
|
||||
<button type="button" class="btn btn-secondary" onclick="generateCodeRange()">Bereich generieren & einfügen</button>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="item_count">Anzahl dieser Sorte</label>
|
||||
<input type="number" id="item_count" name="item_count" min="1" max="100" value="1">
|
||||
<small style="display:block; color:#666;">Es werden eigene Items erstellt, aber in der Übersicht als ein Artikel gebündelt angezeigt.</small>
|
||||
|
||||
<div class="form-group" id="primary_code_group">
|
||||
<label for="code_4">Basis-Code</label>
|
||||
<div style="display: flex; gap: 10px;">
|
||||
<input type="text" id="code_4" name="code_4" class="form-control" placeholder="Haupt-Barcode" required>
|
||||
<button type="button" id="scan-code4-btn" class="btn btn-primary">Barcode scannen</button>
|
||||
</div>
|
||||
<div id="code4-scanner" style="display:none; margin-top: 10px;"></div>
|
||||
<small id="code4-scan-status" class="form-text text-muted"></small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="individual_codes">Einzelcodes (optional, je Zeile ein Code)</label>
|
||||
<textarea id="individual_codes" name="individual_codes" rows="4" placeholder="z.B.\nABC-001\nABC-002"></textarea>
|
||||
<small style="display:block; color:#666;">Bei Anzahl > 1 können hier individuelle Codes pro Item gesetzt werden. Der Scanner setzt immer zuerst den Basis-Code im Feld oben; weitere Einzelcodes können hier bei Bedarf manuell ergänzt werden.</small>
|
||||
|
||||
<div class="form-group" id="individual_codes_group">
|
||||
<label for="individual_codes">Weitere Einzelcodes (je Zeile ein Code)</label>
|
||||
<textarea id="individual_codes" name="individual_codes" rows="6" placeholder="z.B. ABC-001 ABC-002" class="form-control"></textarea>
|
||||
<small style="display:block; color:#666; margin-top: 5px;">Bei Anzahl > 1 können hier individuelle Codes pro Item gesetzt werden. Der Scanner setzt immer zuerst den Basis-Code im Feld oben; weitere Einzelcodes werden hier angehängt.</small>
|
||||
</div>
|
||||
<!-- Image upload (hidden for library mode) -->
|
||||
<div class="form-group" {% if show_library_features %}style="display:none;"{% endif %}>
|
||||
@@ -987,8 +1024,8 @@
|
||||
// Quagga2 engine state management properties
|
||||
let scannerRunning = false;
|
||||
let activeScannerCallback = null; // Dynamically handles routing data to the active caller field
|
||||
let code4LastScanned = '';
|
||||
let code4LastScannedAt = 0;
|
||||
var code4LastScanned = '';
|
||||
var code4LastScannedAt = 0;
|
||||
|
||||
function setCode4ScanStatus(message, isError = false) {
|
||||
const statusEl = document.getElementById('code4-scan-status');
|
||||
@@ -1087,7 +1124,7 @@
|
||||
// Unified tracking capture listener
|
||||
Quagga.onDetected(function(data) {
|
||||
if (!data || !data.codeResult || !data.codeResult.code) return;
|
||||
|
||||
|
||||
const rawBarcode = String(data.codeResult.code || '').trim();
|
||||
const currentTargetCallback = activeScannerCallback;
|
||||
|
||||
@@ -1096,59 +1133,175 @@
|
||||
}
|
||||
});
|
||||
|
||||
// Globale Variablen für den Scanner-Status
|
||||
var code4LastScanned = '';
|
||||
var code4LastScannedAt = 0;
|
||||
|
||||
/**
|
||||
* Schaltet das UI basierend auf dem ausgewählten Modus um
|
||||
*/
|
||||
function toggleScanMode() {
|
||||
const mode = document.getElementById('scan_mode').value;
|
||||
const rangeGroup = document.getElementById('range_generator_group');
|
||||
const scanButton = document.getElementById('scan-code4-btn');
|
||||
const scannerBox = document.getElementById('code4-scanner');
|
||||
|
||||
if (scannerBox && scannerBox.style.display !== 'none') {
|
||||
if (typeof killScannerHardware === 'function') killScannerHardware();
|
||||
scannerBox.style.display = 'none';
|
||||
if (scanButton) scanButton.textContent = 'Barcode scannen';
|
||||
}
|
||||
|
||||
if (mode === 'range') {
|
||||
if(rangeGroup) rangeGroup.style.display = 'block';
|
||||
if(scanButton) scanButton.style.display = 'none';
|
||||
} else {
|
||||
if(rangeGroup) rangeGroup.style.display = 'none'; // Hier wurde der Fehler korrigiert
|
||||
if(scanButton) scanButton.style.display = 'inline-block';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generiert eine Liste von Codes basierend auf den Eingaben
|
||||
*/
|
||||
function generateCodeRange() {
|
||||
const prefix = document.getElementById('range_prefix').value.trim();
|
||||
const startStr = document.getElementById('range_start').value.trim();
|
||||
const endStr = document.getElementById('range_end').value.trim();
|
||||
|
||||
const start = parseInt(startStr, 10);
|
||||
const end = parseInt(endStr, 10);
|
||||
|
||||
const paddingLength = endStr.length;
|
||||
|
||||
if (isNaN(start) || isNaN(end) || start > end) {
|
||||
alert("Bitte gültige Zahlen für Start und Ende eingeben. Der Startwert darf nicht größer als der Endwert sein.");
|
||||
return;
|
||||
}
|
||||
|
||||
let generatedCodes = [];
|
||||
for (let i = start; i <= end; i++) {
|
||||
let numStr = i.toString().padStart(paddingLength, '0');
|
||||
generatedCodes.push(`${prefix}${numStr}`);
|
||||
}
|
||||
|
||||
const codeField = document.getElementById('code_4');
|
||||
const individualCodesArea = document.getElementById('individual_codes');
|
||||
|
||||
if (generatedCodes.length > 0) {
|
||||
if (!codeField.value || confirm("Soll der aktuelle Basis-Code überschrieben werden?")) {
|
||||
codeField.value = generatedCodes[0];
|
||||
if (typeof validateCodeField === 'function') validateCodeField(codeField);
|
||||
generatedCodes.shift();
|
||||
}
|
||||
|
||||
if (generatedCodes.length > 0) {
|
||||
let existing = individualCodesArea.value.trim();
|
||||
individualCodesArea.value = existing ? existing + '\n' + generatedCodes.join('\n') : generatedCodes.join('\n');
|
||||
}
|
||||
|
||||
alert(`Bereich erfolgreich generiert! (${end - start + 1} Codes)`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scanner-Funktion (Single & Continuous Modus)
|
||||
*/
|
||||
function startCode4Scanner() {
|
||||
const scannerBox = document.getElementById('code4-scanner');
|
||||
const scanButton = document.getElementById('scan-code4-btn');
|
||||
const codeField = document.getElementById('code_4');
|
||||
const individualCodesArea = document.getElementById('individual_codes');
|
||||
const scanMode = document.getElementById('scan_mode').value;
|
||||
|
||||
if (!scannerBox || !scanButton || !codeField) return;
|
||||
|
||||
// Automatically clean up running ISBN scanner to prevent track multi-binding
|
||||
// Beende evtl. laufenden ISBN Scanner
|
||||
const isbnScannerBox = document.getElementById('isbn-scanner');
|
||||
const isbnScanButton = document.getElementById('scan-isbn-btn');
|
||||
if (isbnScannerBox && isbnScannerBox.style.display !== 'none') {
|
||||
killScannerHardware();
|
||||
if (isbnScannerBox) isbnScannerBox.style.display = 'none';
|
||||
if (isbnScanButton) isbnScanButton.textContent = 'ISBN scannen';
|
||||
setIsbnScanStatus('Scanner gestoppt.');
|
||||
isbnScannerBox.style.display = 'none';
|
||||
const isbnScanButton = document.getElementById('scan-isbn-btn');
|
||||
if (isbnScanButton) isbnScanButton.textContent = 'Barcode scannen';
|
||||
if (typeof setIsbnScanStatus === 'function') setIsbnScanStatus('Scanner gestoppt.');
|
||||
}
|
||||
|
||||
// Scanner ausschalten, wenn er bereits läuft
|
||||
if (scannerBox.style.display !== 'none') {
|
||||
killScannerHardware();
|
||||
scannerBox.style.display = 'none';
|
||||
scanButton.textContent = 'Barcode scannen';
|
||||
setCode4ScanStatus('Scanner gestoppt.');
|
||||
if (typeof setCode4ScanStatus === 'function') setCode4ScanStatus('Scanner gestoppt.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Scanner starten
|
||||
scannerBox.style.display = 'block';
|
||||
scanButton.textContent = 'Scanner stoppen';
|
||||
scanButton.textContent = scanMode === 'continuous' ? 'Fortlaufenden Scan stoppen' : 'Scanner stoppen';
|
||||
if (typeof setCode4ScanStatus === 'function') setCode4ScanStatus('Scanner initialisiert. Richte die Kamera auf den Barcode...');
|
||||
|
||||
runEngineInitialization(
|
||||
'#code4-scanner',
|
||||
function(decodedText) {
|
||||
const now = Date.now();
|
||||
if (decodedText === code4LastScanned && (now - code4LastScannedAt) < 1500) {
|
||||
return;
|
||||
}
|
||||
code4LastScanned = decodedText;
|
||||
code4LastScannedAt = now;
|
||||
if (typeof runEngineInitialization === 'function') {
|
||||
runEngineInitialization(
|
||||
'#code4-scanner',
|
||||
function(decodedText) {
|
||||
const now = Date.now();
|
||||
if (decodedText === code4LastScanned && (now - code4LastScannedAt) < 1500) {
|
||||
return;
|
||||
}
|
||||
code4LastScanned = decodedText;
|
||||
code4LastScannedAt = now;
|
||||
|
||||
if (scanMode === 'single') {
|
||||
killScannerHardware();
|
||||
scannerBox.style.display = 'none';
|
||||
scanButton.textContent = 'Barcode scannen';
|
||||
|
||||
codeField.value = decodedText;
|
||||
if (typeof validateCodeField === 'function') validateCodeField(codeField);
|
||||
if (typeof setCode4ScanStatus === 'function') setCode4ScanStatus(`Code gesetzt: ${decodedText}`);
|
||||
|
||||
killScannerHardware();
|
||||
scannerBox.style.display = 'none';
|
||||
scanButton.textContent = 'Barcode scannen';
|
||||
} else if (scanMode === 'continuous') {
|
||||
let currentAreaCodes = individualCodesArea.value.split('\n').map(c => c.trim()).filter(c => c !== '');
|
||||
|
||||
codeField.value = decodedText;
|
||||
validateCodeField(codeField);
|
||||
setCode4ScanStatus(`Code_4 gesetzt: ${decodedText}`);
|
||||
},
|
||||
'Scanner läuft. Der Scan wird im Basis-Codefeld übernommen.',
|
||||
setCode4ScanStatus
|
||||
);
|
||||
if (!codeField.value) {
|
||||
codeField.value = decodedText;
|
||||
if (typeof validateCodeField === 'function') validateCodeField(codeField);
|
||||
if (typeof setCode4ScanStatus === 'function') setCode4ScanStatus(`Basis-Code gesetzt: ${decodedText}`);
|
||||
} else if (!currentAreaCodes.includes(decodedText) && codeField.value !== decodedText) {
|
||||
individualCodesArea.value += (individualCodesArea.value ? '\n' : '') + decodedText;
|
||||
if (typeof setCode4ScanStatus === 'function') setCode4ScanStatus(`Zusatz-Code erfasst: ${decodedText}`);
|
||||
|
||||
try {
|
||||
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
||||
const oscillator = audioCtx.createOscillator();
|
||||
const gainNode = audioCtx.createGain();
|
||||
oscillator.connect(gainNode);
|
||||
gainNode.connect(audioCtx.destination);
|
||||
oscillator.type = 'sine';
|
||||
oscillator.frequency.value = 800;
|
||||
gainNode.gain.setValueAtTime(0.1, audioCtx.currentTime);
|
||||
oscillator.start();
|
||||
oscillator.stop(audioCtx.currentTime + 0.1);
|
||||
} catch (e) {
|
||||
console.log("Audio feedback not supported");
|
||||
}
|
||||
} else {
|
||||
if (typeof setCode4ScanStatus === 'function') setCode4ScanStatus(`Code ${decodedText} wurde bereits gescannt.`);
|
||||
}
|
||||
}
|
||||
},
|
||||
'Scanner läuft. Richte die Kamera auf den Barcode.',
|
||||
setCode4ScanStatus
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ISBN Scanner-Funktion
|
||||
*/
|
||||
function startIsbnScanner() {
|
||||
if (!libraryModuleEnabled) {
|
||||
setIsbnScanStatus('Bibliotheks-Modul ist deaktiviert.', true);
|
||||
if (typeof setIsbnScanStatus === 'function') setIsbnScanStatus('Bibliotheks-Modul ist deaktiviert.', true);
|
||||
return;
|
||||
}
|
||||
const scannerBox = document.getElementById('isbn-scanner');
|
||||
@@ -1156,21 +1309,20 @@
|
||||
const isbnField = document.getElementById('isbn');
|
||||
if (!scannerBox || !scanButton || !isbnField) return;
|
||||
|
||||
// Automatically clean up running Base Code_4 scanner to prevent track multi-binding
|
||||
const codeScannerBox = document.getElementById('code4-scanner');
|
||||
const codeScanButton = document.getElementById('scan-code4-btn');
|
||||
if (codeScannerBox && codeScannerBox.style.display !== 'none') {
|
||||
killScannerHardware();
|
||||
if (codeScannerBox) codeScannerBox.style.display = 'none';
|
||||
if (codeScanButton) codeScanButton.textContent = 'Barcode scannen';
|
||||
setCode4ScanStatus('Scanner gestoppt.');
|
||||
codeScannerBox.style.display = 'none';
|
||||
codeScanButton.textContent = 'Barcode scannen';
|
||||
if (typeof setCode4ScanStatus === 'function') setCode4ScanStatus('Scanner gestoppt.');
|
||||
}
|
||||
|
||||
if (scannerBox.style.display !== 'none') {
|
||||
killScannerHardware();
|
||||
scannerBox.style.display = 'none';
|
||||
scanButton.textContent = 'ISBN scannen';
|
||||
setIsbnScanStatus('Scanner gestoppt.');
|
||||
scanButton.textContent = 'Barcode scannen';
|
||||
if (typeof setIsbnScanStatus === 'function') setIsbnScanStatus('Scanner gestoppt.');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1182,18 +1334,17 @@
|
||||
function(decodedText) {
|
||||
killScannerHardware();
|
||||
scannerBox.style.display = 'none';
|
||||
scanButton.textContent = 'ISBN scannen';
|
||||
scanButton.textContent = 'Barcode scannen';
|
||||
|
||||
const normalizedIsbn = normalizeIsbnClient(decodedText);
|
||||
const normalizedIsbn = typeof normalizeIsbnClient === 'function' ? normalizeIsbnClient(decodedText) : decodedText;
|
||||
isbnField.value = normalizedIsbn || decodedText;
|
||||
updateIsbnLiveValidation();
|
||||
if (typeof updateIsbnLiveValidation === 'function') updateIsbnLiveValidation();
|
||||
|
||||
if (normalizedIsbn) {
|
||||
setIsbnScanStatus(`ISBN gesetzt: ${normalizedIsbn}`);
|
||||
// Automatically load book metadata after a valid ISBN scan.
|
||||
fetchBookInfo('upload');
|
||||
if (typeof setIsbnScanStatus === 'function') setIsbnScanStatus(`ISBN gesetzt: ${normalizedIsbn}`);
|
||||
if (typeof fetchBookInfo === 'function') fetchBookInfo('upload');
|
||||
} else {
|
||||
setIsbnScanStatus('Kein gültiges ISBN-Format erkannt, der gescannte Wert bleibt aber im Feld.', true);
|
||||
if (typeof setIsbnScanStatus === 'function') setIsbnScanStatus('Kein gültiges ISBN-Format erkannt, der gescannte Wert bleibt aber im Feld.', true);
|
||||
}
|
||||
},
|
||||
'Scanner läuft. Bitte ISBN-Code erfassen.',
|
||||
@@ -1201,6 +1352,32 @@
|
||||
);
|
||||
}
|
||||
|
||||
// --- WICHTIG: Event Listener Binden sobald DOM geladen ist ---
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 1. Basis-Code Scanner Button aktivieren
|
||||
const scanCode4Btn = document.getElementById('scan-code4-btn');
|
||||
if (scanCode4Btn) {
|
||||
scanCode4Btn.addEventListener('click', startCode4Scanner);
|
||||
}
|
||||
|
||||
// 2. ISBN Scanner Button aktivieren
|
||||
const scanIsbnBtn = document.getElementById('scan-isbn-btn');
|
||||
if (scanIsbnBtn) {
|
||||
scanIsbnBtn.addEventListener('click', startIsbnScanner);
|
||||
}
|
||||
|
||||
// 3. Dropdown-Modus Wechsel überwachen
|
||||
const scanModeSelect = document.getElementById('scan_mode');
|
||||
if (scanModeSelect) {
|
||||
scanModeSelect.addEventListener('change', toggleScanMode);
|
||||
}
|
||||
|
||||
// 4. ISBN Live-Validierung
|
||||
const isbnInput = document.getElementById('isbn');
|
||||
if (isbnInput && typeof updateIsbnLiveValidation === 'function') {
|
||||
isbnInput.addEventListener('input', updateIsbnLiveValidation);
|
||||
}
|
||||
});
|
||||
// Load predefined filter values for dropdowns
|
||||
function loadPredefinedFilterValues(filterNumber) {
|
||||
fetch(`/get_predefined_filter_values/${filterNumber}`)
|
||||
@@ -1726,16 +1903,6 @@
|
||||
});
|
||||
}
|
||||
|
||||
const scanCodeBtn = document.getElementById('scan-code4-btn');
|
||||
if (scanCodeBtn) {
|
||||
scanCodeBtn.addEventListener('click', startCode4Scanner);
|
||||
}
|
||||
|
||||
const scanIsbnBtn = document.getElementById('scan-isbn-btn');
|
||||
if (scanIsbnBtn) {
|
||||
scanIsbnBtn.addEventListener('click', startIsbnScanner);
|
||||
}
|
||||
|
||||
const isbnField = document.getElementById('isbn');
|
||||
if (isbnField) {
|
||||
isbnField.addEventListener('input', updateIsbnLiveValidation);
|
||||
|
||||
Reference in New Issue
Block a user