fix of the template name
Release Inventarsystem / release-docker (push) Successful in 2m15s

This commit is contained in:
2026-08-12 11:45:48 +02:00
parent dd3d8649a7
commit 96d45710ac
3 changed files with 83 additions and 61 deletions
+54 -38
View File
@@ -6366,11 +6366,25 @@ def edit_item(id):
return redirect(url_for('home_admin')) return redirect(url_for('home_admin'))
def is_library_item(item):
"""
Prüft, ob ein Artikel ein Bibliotheks-Item ist.
- 'other', None oder Leerstring -> Inventarsystem (False)
- Jeder andere Medientyp ('Buch', 'CD', etc.) -> Bibliothek (True)
"""
if not item:
return False
item_type = item.get('ItemType', 'other')
if not item_type:
return False
return item_type.strip().lower() != 'other'
@app.route('/item_edit/<id>', methods=['GET', 'POST']) @app.route('/item_edit/<id>', methods=['GET', 'POST'])
def item_edit(id): def item_edit(id):
""" """
Endpoint zum Bearbeiten von Artikeln (unterstützt Bibliotheks- und Inventar-Modus). Complete endpoint for editing items. Automatically detects whether the item
is a Library item (ItemType != 'other') or an Inventory item (ItemType == 'other').
""" """
if 'username' not in session: if 'username' not in session:
if request.method == 'POST' and request.is_json: if request.method == 'POST' and request.is_json:
@@ -6391,22 +6405,27 @@ def item_edit(id):
flash('Ungültige Element-ID.', 'error') flash('Ungültige Element-ID.', 'error')
return redirect(url_for('home_admin')) return redirect(url_for('home_admin'))
show_library = cfg.MODULES.is_enabled('library') current_item = it.get_item(obj_id)
if not current_item:
# --- GET: Bearbeitungsformular laden --- flash('Element in der Datenbank nicht gefunden.', 'error')
if request.method == 'GET':
item = it.get_item(id)
if not item:
flash('Element nicht gefunden.', 'error')
return redirect(url_for('home_admin')) return redirect(url_for('home_admin'))
item['_id'] = str(item['_id']) # Determine item type classification
library_module_active = cfg.MODULES.is_enabled('library')
is_lib_item = it.is_library_item(current_item)
show_library_features = library_module_active and is_lib_item
# Gruppen-Codes aufteilen (Basis-Code vs. Einzelcodes für die Textarea) # -------------------------------------------------------------------
base_code = item.get('Code_4', '') # GET METHOD: Render Form
# -------------------------------------------------------------------
if request.method == 'GET':
current_item['_id'] = str(current_item['_id'])
# Format individual group codes for the textarea
base_code = current_item.get('Code_4', '')
individual_codes = [] individual_codes = []
if item.get('SeriesGroupId'): if current_item.get('SeriesGroupId'):
group_ids = it.get_group_item_ids(str(item['_id'])) group_ids = it.get_group_item_ids(str(current_item['_id']))
if group_ids: if group_ids:
for gid in group_ids: for gid in group_ids:
g_item = it.get_item(gid) g_item = it.get_item(gid)
@@ -6414,26 +6433,23 @@ def item_edit(id):
if c4 and c4 != base_code: if c4 and c4 != base_code:
individual_codes.append(c4) individual_codes.append(c4)
item['IndividualCodes'] = '\n'.join(individual_codes) current_item['IndividualCodes'] = '\n'.join(individual_codes)
return render_template( return render_template(
'edit_library.html', 'item_edit.html',
username=session['username'], username=session['username'],
item=item, item=current_item,
show_library_features=show_library, show_library_features=show_library_features,
library_module_enabled=show_library, library_module_enabled=library_module_active,
page_title=f"Bearbeiten: {item.get('Name', '')}" page_title=f"Bearbeiten: {current_item.get('Name', '')}"
) )
# --- POST: Änderungen speichern --- # -------------------------------------------------------------------
# POST METHOD: Save Changes
# -------------------------------------------------------------------
redirect_target = request.referrer or url_for('home_admin') redirect_target = request.referrer or url_for('home_admin')
current_item = it.get_item(obj_id)
if not current_item: # Common fields
flash('Element in der Datenbank nicht gefunden.', 'error')
return redirect(redirect_target)
# Gemeinsame Felder auslesen & bereinigen
name = sanitize_form_value(request.form.get('name')) name = sanitize_form_value(request.form.get('name'))
ort = sanitize_form_value(request.form.get('ort')) ort = sanitize_form_value(request.form.get('ort'))
beschreibung = sanitize_form_value(request.form.get('beschreibung')) beschreibung = sanitize_form_value(request.form.get('beschreibung'))
@@ -6441,7 +6457,7 @@ def item_edit(id):
anschaffungs_kosten = sanitize_form_value(request.form.get('anschaffungskosten')) anschaffungs_kosten = sanitize_form_value(request.form.get('anschaffungskosten'))
reservierbar = 'reservierbar' in request.form reservierbar = 'reservierbar' in request.form
# Barcode & Gruppen-Codes # Barcodes
code_4 = sanitize_form_value(request.form.get('code_4')) code_4 = sanitize_form_value(request.form.get('code_4'))
individual_codes_raw = request.form.get('individual_codes', '') individual_codes_raw = request.form.get('individual_codes', '')
@@ -6453,7 +6469,7 @@ def item_edit(id):
all_codes_to_check = [code_4] + individual_codes all_codes_to_check = [code_4] + individual_codes
# Uniqueness-Check für alle übergebenen Barcodes # Barcode uniqueness check
current_group_id = current_item.get('SeriesGroupId') current_group_id = current_item.get('SeriesGroupId')
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
db_instance = client[cfg.MONGODB_DB] db_instance = client[cfg.MONGODB_DB]
@@ -6476,9 +6492,9 @@ def item_edit(id):
if has_code_error: if has_code_error:
return redirect(redirect_target) return redirect(redirect_target)
# --- Modi-spezifische Auswertung --- # Type-specific processing
if show_library: if show_library_features:
# 1. Bibliotheks-Modus # --- LIBRARY ITEM ---
isbn_raw = sanitize_form_value(request.form.get('isbn', '')) isbn_raw = sanitize_form_value(request.form.get('isbn', ''))
item_isbn = normalize_and_validate_isbn(isbn_raw) if isbn_raw else '' item_isbn = normalize_and_validate_isbn(isbn_raw) if isbn_raw else ''
item_type = sanitize_form_value(request.form.get('item_type_input', 'Buch')) item_type = sanitize_form_value(request.form.get('item_type_input', 'Buch'))
@@ -6489,16 +6505,16 @@ def item_edit(id):
filter3 = current_item.get('Filter3', []) filter3 = current_item.get('Filter3', [])
images = current_item.get('Images', []) images = current_item.get('Images', [])
else: else:
# 2. Inventar-Modus # --- INVENTORY ITEM ---
item_isbn = current_item.get('ISBN', '') item_isbn = current_item.get('ISBN', '')
item_type = current_item.get('ItemType', 'other') item_type = 'other' # Standard type for inventory items
library_category = current_item.get('library_category', '') library_category = current_item.get('library_category', '')
filter1 = expand_filter_selection(sanitize_form_value(request.form.getlist('filter')), 1) filter1 = expand_filter_selection(sanitize_form_value(request.form.getlist('filter')), 1)
filter2 = expand_filter_selection(sanitize_form_value(request.form.getlist('filter2')), 2) filter2 = expand_filter_selection(sanitize_form_value(request.form.getlist('filter2')), 2)
filter3 = sanitize_form_value(request.form.getlist('filter3')) filter3 = sanitize_form_value(request.form.getlist('filter3'))
# Bilderverarbeitung für den Inventar-Modus # Manage images for Inventory Mode
images_to_keep = request.form.getlist('existing_images') images_to_keep = request.form.getlist('existing_images')
original_images = current_item.get('Images', []) original_images = current_item.get('Images', [])
images = [img for img in original_images if img in images_to_keep] images = [img for img in original_images if img in images_to_keep]
@@ -6537,16 +6553,16 @@ def item_edit(id):
) )
images.append(new_filename) images.append(new_filename)
except Exception as e: except Exception as e:
app.logger.error(f"Bild-Fehler bei Item {id}: {e}") app.logger.error(f"Image error for item {id}: {e}")
# Standort bei Bedarf zu vordefinierten Orten hinzufügen # Auto-add new location to predefined locations list if applicable
if ort and ort not in it.get_predefined_locations(): if ort and ort not in it.get_predefined_locations():
it.add_predefined_location(ort) it.add_predefined_location(ort)
# 1. Gruppen-Codes synchronisieren # Sync series/group barcode IDs
it.sync_group_codes(str(id), code_4, individual_codes) it.sync_group_codes(str(id), code_4, individual_codes)
# 2. Datenbank-Eintrag aktualisieren # Update item in database
success = it.update_item( success = it.update_item(
id=str(id), id=str(id),
name=name, name=name,
+13
View File
@@ -27,6 +27,19 @@ from Web.modules.database.settings import MongoClient
import Web.modules.inventarsystem.data_protection as dp import Web.modules.inventarsystem.data_protection as dp
def is_library_item(item):
"""
Determines if an item belongs to the library system.
Returns False for 'other', None, or empty ItemType (Inventory item).
Returns True for any specific library type ('Buch', 'CD', 'DVD', etc.).
"""
if not item:
return False
item_type = item.get('ItemType', 'other')
if not item_type:
return False
return str(item_type).strip().lower() != 'other'
def safe_decrypt_user(encrypted_user): def safe_decrypt_user(encrypted_user):
""" """
Safely decrypt an encrypted username string. Safely decrypt an encrypted username string.
+16 -23
View File
@@ -103,7 +103,7 @@
background-color: #0056b3; background-color: #0056b3;
} }
/* Scanner Video Styles */ /* Scanner Video Canvas */
#code4-scanner video, #code4-scanner canvas, #code4-scanner video, #code4-scanner canvas,
#isbn-scanner video, #isbn-scanner canvas { #isbn-scanner video, #isbn-scanner canvas {
width: 100%; width: 100%;
@@ -124,7 +124,7 @@
overflow: hidden; overflow: hidden;
} }
/* Image management grid */ /* Image Management */
.existing-images-grid { .existing-images-grid {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
@@ -197,7 +197,7 @@
<input type="hidden" name="item_id" value="{{ item._id }}"> <input type="hidden" name="item_id" value="{{ item._id }}">
{% if show_library_features %} {% if show_library_features %}
<!-- BIBLIOTHEKS-MODUS: ISBN / Barcode --> <!-- ================= LIBRARY MODE FIELDS ================= -->
<div class="form-group"> <div class="form-group">
<label for="isbn">ISBN / Barcode:</label> <label for="isbn">ISBN / Barcode:</label>
<div class="isbn-input-group"> <div class="isbn-input-group">
@@ -211,13 +211,12 @@
</div> </div>
{% endif %} {% endif %}
<!-- Allgemein: Name --> <!-- ================= COMMON FIELDS ================= -->
<div class="form-group"> <div class="form-group">
<label for="name">Name / Titel:</label> <label for="name">Name / Titel:</label>
<input type="text" id="name" name="name" value="{{ item.Name|default('') }}" required> <input type="text" id="name" name="name" value="{{ item.Name|default('') }}" required>
</div> </div>
<!-- Allgemein: Ort -->
<div class="form-group"> <div class="form-group">
<label for="ort">Ort / Standort:</label> <label for="ort">Ort / Standort:</label>
<select id="ort" name="ort" data-selected="{{ item.Ort|default('') }}" required> <select id="ort" name="ort" data-selected="{{ item.Ort|default('') }}" required>
@@ -234,13 +233,11 @@
</div> </div>
</div> </div>
<!-- Allgemein: Beschreibung -->
<div class="form-group"> <div class="form-group">
<label for="beschreibung">Beschreibung:</label> <label for="beschreibung">Beschreibung:</label>
<textarea id="beschreibung" name="beschreibung" required>{{ item.Beschreibung|default('') }}</textarea> <textarea id="beschreibung" name="beschreibung" required>{{ item.Beschreibung|default('') }}</textarea>
</div> </div>
<!-- Barcode / Basis-Code -->
<div class="form-group" id="primary_code_group"> <div class="form-group" id="primary_code_group">
<label for="code_4">Basis-Code (Haupt-Barcode)</label> <label for="code_4">Basis-Code (Haupt-Barcode)</label>
<div style="display: flex; gap: 10px;"> <div style="display: flex; gap: 10px;">
@@ -251,7 +248,6 @@
<small id="code4-scan-status" class="form-text text-muted"></small> <small id="code4-scan-status" class="form-text text-muted"></small>
</div> </div>
<!-- Weitere Einzelcodes der Gruppe -->
<div class="form-group" id="individual_codes_group"> <div class="form-group" id="individual_codes_group">
<label for="individual_codes">Weitere Einzelcodes der Gruppe (je Zeile ein Code)</label> <label for="individual_codes">Weitere Einzelcodes der Gruppe (je Zeile ein Code)</label>
<textarea id="individual_codes" name="individual_codes" rows="4" class="form-control" placeholder="z.B.&#10;ABC-001&#10;ABC-002">{{ item.IndividualCodes|default('') }}</textarea> <textarea id="individual_codes" name="individual_codes" rows="4" class="form-control" placeholder="z.B.&#10;ABC-001&#10;ABC-002">{{ item.IndividualCodes|default('') }}</textarea>
@@ -259,7 +255,7 @@
</div> </div>
{% if show_library_features %} {% if show_library_features %}
<!-- BIBLIOTHEKS-MODUS: Spezifische Filter --> <!-- ================= LIBRARY CATEGORIZATION ================= -->
<div class="filter-inputs"> <div class="filter-inputs">
<h3>Medientyp</h3> <h3>Medientyp</h3>
<div class="form-group"> <div class="form-group">
@@ -268,7 +264,7 @@
<option value="Schulbuch" {% if item.ItemType == 'Schulbuch' %}selected{% endif %}>Schulbuch</option> <option value="Schulbuch" {% if item.ItemType == 'Schulbuch' %}selected{% endif %}>Schulbuch</option>
<option value="CD" {% if item.ItemType == 'CD' %}selected{% endif %}>CD</option> <option value="CD" {% if item.ItemType == 'CD' %}selected{% endif %}>CD</option>
<option value="DVD" {% if item.ItemType == 'DVD' %}selected{% endif %}>DVD</option> <option value="DVD" {% if item.ItemType == 'DVD' %}selected{% endif %}>DVD</option>
<option value="Sonstiges" {% if item.ItemType == 'Sonstiges' or not item.ItemType %}selected{% endif %}>Sonstiges</option> <option value="Sonstiges" {% if item.ItemType == 'Sonstiges' %}selected{% endif %}>Sonstiges</option>
</select> </select>
</div> </div>
<h3>Kategorie / Fach:</h3> <h3>Kategorie / Fach:</h3>
@@ -277,7 +273,7 @@
</div> </div>
</div> </div>
{% else %} {% else %}
<!-- INVENTAR-MODUS: Standard 3-Stufen-Filter --> <!-- ================= INVENTORY FILTERS (1-3) ================= -->
<div class="filter-inputs"> <div class="filter-inputs">
<h3>Unterrichtsfach (Filter 1):</h3> <h3>Unterrichtsfach (Filter 1):</h3>
<div class="multi-filter"> <div class="multi-filter">
@@ -315,7 +311,7 @@
</div> </div>
{% endif %} {% endif %}
<!-- Finanzen & Datum --> <!-- ================= DATES & FINANCIALS ================= -->
<div class="form-group"> <div class="form-group">
<label for="anschaffungsjahr">Anschaffungsjahr:</label> <label for="anschaffungsjahr">Anschaffungsjahr:</label>
<input type="date" id="anschaffungsjahr" name="anschaffungsjahr" value="{{ item.Anschaffungsjahr|default('') }}"> <input type="date" id="anschaffungsjahr" name="anschaffungsjahr" value="{{ item.Anschaffungsjahr|default('') }}">
@@ -326,7 +322,7 @@
</div> </div>
{% if not show_library_features %} {% if not show_library_features %}
<!-- INVENTAR-MODUS: Bilder & Medien verwalten --> <!-- ================= INVENTORY IMAGE MANAGEMENT ================= -->
<div class="form-group"> <div class="form-group">
<label>Bestehende Bilder behalten:</label> <label>Bestehende Bilder behalten:</label>
{% if item.Images and item.Images|length > 0 %} {% if item.Images and item.Images|length > 0 %}
@@ -350,7 +346,6 @@
</div> </div>
{% endif %} {% endif %}
<!-- Reservierbar -->
<div class="form-group"> <div class="form-group">
<label for="reservierbar" style="display:inline-block; width:auto; margin-right:10px;">Reservierbar:</label> <label for="reservierbar" style="display:inline-block; width:auto; margin-right:10px;">Reservierbar:</label>
<input type="checkbox" id="reservierbar" name="reservierbar" style="width:auto;" {% if item.Reservierbar %}checked{% endif %}> <input type="checkbox" id="reservierbar" name="reservierbar" style="width:auto;" {% if item.Reservierbar %}checked{% endif %}>
@@ -369,7 +364,7 @@
let code4LastScanned = ''; let code4LastScanned = '';
let code4LastScannedAt = 0; let code4LastScannedAt = 0;
// Filter-Optionen vom Server laden & bestehende Werte vorauswählen // Load filter dropdown options and pre-select current item values
function loadAndSelectFilterValues(filterNumber) { function loadAndSelectFilterValues(filterNumber) {
fetch(`/get_predefined_filter_values/${filterNumber}`) fetch(`/get_predefined_filter_values/${filterNumber}`)
.then(res => res.json()) .then(res => res.json())
@@ -380,7 +375,6 @@
const selectedValue = select.getAttribute('data-selected') || ''; const selectedValue = select.getAttribute('data-selected') || '';
// Optionen befüllen
data.values.forEach(val => { data.values.forEach(val => {
if (!val || String(val).trim() === '') return; if (!val || String(val).trim() === '') return;
const opt = document.createElement('option'); const opt = document.createElement('option');
@@ -389,7 +383,7 @@
select.appendChild(opt); select.appendChild(opt);
}); });
// Falls der gewählte Wert nicht in den Vorgaben war, als Option ergänzen // Add custom option if the item has a value not in predefined list
if (selectedValue && !Array.from(select.options).some(o => o.value === selectedValue)) { if (selectedValue && !Array.from(select.options).some(o => o.value === selectedValue)) {
const customOpt = document.createElement('option'); const customOpt = document.createElement('option');
customOpt.value = selectedValue; customOpt.value = selectedValue;
@@ -400,10 +394,10 @@
select.value = selectedValue; select.value = selectedValue;
} }
}) })
.catch(err => console.error(`Fehler beim Laden von Filter ${filterNumber}:`, err)); .catch(err => console.error(`Error loading Filter ${filterNumber}:`, err));
} }
// Orte vom Server laden // Load locations
function loadLocationOptions() { function loadLocationOptions() {
fetch('/get_predefined_locations') fetch('/get_predefined_locations')
.then(res => res.json()) .then(res => res.json())
@@ -423,10 +417,10 @@
select.value = currentVal; select.value = currentVal;
}) })
.catch(err => console.error('Fehler beim Laden der Orte:', err)); .catch(err => console.error('Error loading locations:', err));
} }
// Ort hinzufügen UI // New location handler
function addNewLocation() { function addNewLocation() {
const input = document.getElementById('new-location-input'); const input = document.getElementById('new-location-input');
const val = input.value.trim(); const val = input.value.trim();
@@ -446,7 +440,7 @@
document.getElementById('new-location-input').value = ''; document.getElementById('new-location-input').value = '';
} }
// Quagga Barcode Scanner Initialisierung // Quagga Barcode Scanner Engine
function runEngineInitialization(targetSelector, activeCallback, completionMsg, errorStatusSetter) { function runEngineInitialization(targetSelector, activeCallback, completionMsg, errorStatusSetter) {
if (scannerRunning) { Quagga.stop(); scannerRunning = false; } if (scannerRunning) { Quagga.stop(); scannerRunning = false; }
activeScannerCallback = activeCallback; activeScannerCallback = activeCallback;
@@ -470,7 +464,6 @@
if (typeof activeScannerCallback === "function") activeScannerCallback(String(data.codeResult.code).trim()); if (typeof activeScannerCallback === "function") activeScannerCallback(String(data.codeResult.code).trim());
}); });
// Barcode Scanner für Basis-Code
function startCode4Scanner() { function startCode4Scanner() {
const scannerBox = document.getElementById('code4-scanner'); const scannerBox = document.getElementById('code4-scanner');
const scanBtn = document.getElementById('scan-code4-btn'); const scanBtn = document.getElementById('scan-code4-btn');