Compare commits

...

3 Commits

Author SHA1 Message Date
Aiirondev_dev 9452743660 Changes to reflekt the right inventar and bibliothek item fields
Release Inventarsystem / release-docker (push) Successful in 2m17s
2026-08-12 11:59:57 +02:00
Aiirondev_dev 6a3865ef24 fix of the template name
Release Inventarsystem / release-docker (push) Successful in 2m16s
2026-08-12 11:46:59 +02:00
Aiirondev_dev 96d45710ac fix of the template name
Release Inventarsystem / release-docker (push) Successful in 2m15s
2026-08-12 11:45:48 +02:00
3 changed files with 103 additions and 91 deletions
+60 -45
View File
@@ -6366,11 +6366,25 @@ def edit_item(id):
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'])
def item_edit(id):
"""
Endpoint zum Bearbeiten von Artikeln (unterstützt Bibliotheks- und Inventar-Modus).
Complete endpoint for editing items. Processes Filters 1-3 for ALL items,
and applies Library-specific fields when the item is a Library item.
"""
if 'username' not in session:
if request.method == 'POST' and request.is_json:
@@ -6391,22 +6405,27 @@ def item_edit(id):
flash('Ungültige Element-ID.', 'error')
return redirect(url_for('home_admin'))
show_library = cfg.MODULES.is_enabled('library')
current_item = it.get_item(obj_id)
if not current_item:
flash('Element in der Datenbank nicht gefunden.', 'error')
return redirect(url_for('home_admin'))
# --- GET: Bearbeitungsformular laden ---
# Determine item 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
# -------------------------------------------------------------------
# GET METHOD: Render Form
# -------------------------------------------------------------------
if request.method == 'GET':
item = it.get_item(id)
if not item:
flash('Element nicht gefunden.', 'error')
return redirect(url_for('home_admin'))
current_item['_id'] = str(current_item['_id'])
item['_id'] = str(item['_id'])
# Gruppen-Codes aufteilen (Basis-Code vs. Einzelcodes für die Textarea)
base_code = item.get('Code_4', '')
# Format individual group codes for the textarea
base_code = current_item.get('Code_4', '')
individual_codes = []
if item.get('SeriesGroupId'):
group_ids = it.get_group_item_ids(str(item['_id']))
if current_item.get('SeriesGroupId'):
group_ids = it.get_group_item_ids(str(current_item['_id']))
if group_ids:
for gid in group_ids:
g_item = it.get_item(gid)
@@ -6414,26 +6433,23 @@ def item_edit(id):
if c4 and c4 != base_code:
individual_codes.append(c4)
item['IndividualCodes'] = '\n'.join(individual_codes)
current_item['IndividualCodes'] = '\n'.join(individual_codes)
return render_template(
'edit_library.html',
username=session['username'],
item=item,
show_library_features=show_library,
library_module_enabled=show_library,
page_title=f"Bearbeiten: {item.get('Name', '')}"
item=current_item,
show_library_features=show_library_features,
library_module_enabled=library_module_active,
page_title=f"Bearbeiten: {current_item.get('Name', '')}"
)
# --- POST: Änderungen speichern ---
# -------------------------------------------------------------------
# POST METHOD: Save Changes
# -------------------------------------------------------------------
redirect_target = request.referrer or url_for('home_admin')
current_item = it.get_item(obj_id)
if not current_item:
flash('Element in der Datenbank nicht gefunden.', 'error')
return redirect(redirect_target)
# Gemeinsame Felder auslesen & bereinigen
# Common fields
name = sanitize_form_value(request.form.get('name'))
ort = sanitize_form_value(request.form.get('ort'))
beschreibung = sanitize_form_value(request.form.get('beschreibung'))
@@ -6441,7 +6457,12 @@ def item_edit(id):
anschaffungs_kosten = sanitize_form_value(request.form.get('anschaffungskosten'))
reservierbar = 'reservierbar' in request.form
# Barcode & Gruppen-Codes
# System Filters 1, 2, and 3 (Processed for ALL items)
filter1 = expand_filter_selection(sanitize_form_value(request.form.getlist('filter')), 1)
filter2 = expand_filter_selection(sanitize_form_value(request.form.getlist('filter2')), 2)
filter3 = sanitize_form_value(request.form.getlist('filter3'))
# Barcodes & Group Codes
code_4 = sanitize_form_value(request.form.get('code_4'))
individual_codes_raw = request.form.get('individual_codes', '')
@@ -6453,7 +6474,7 @@ def item_edit(id):
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')
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
db_instance = client[cfg.MONGODB_DB]
@@ -6476,29 +6497,21 @@ def item_edit(id):
if has_code_error:
return redirect(redirect_target)
# --- Modi-spezifische Auswertung ---
if show_library:
# 1. Bibliotheks-Modus
# Type-specific processing
if show_library_features:
# --- LIBRARY ITEM SPECIFICS ---
isbn_raw = sanitize_form_value(request.form.get('isbn', ''))
item_isbn = normalize_and_validate_isbn(isbn_raw) if isbn_raw else ''
item_type = sanitize_form_value(request.form.get('item_type_input', 'Buch'))
library_category = sanitize_form_value(request.form.get('library_category', ''))
filter1 = current_item.get('Filter', [])
filter2 = current_item.get('Filter2', [])
filter3 = current_item.get('Filter3', [])
images = current_item.get('Images', [])
else:
# 2. Inventar-Modus
# --- INVENTORY ITEM SPECIFICS ---
item_isbn = current_item.get('ISBN', '')
item_type = current_item.get('ItemType', 'other')
item_type = 'other'
library_category = current_item.get('library_category', '')
filter1 = expand_filter_selection(sanitize_form_value(request.form.getlist('filter')), 1)
filter2 = expand_filter_selection(sanitize_form_value(request.form.getlist('filter2')), 2)
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')
original_images = current_item.get('Images', [])
images = [img for img in original_images if img in images_to_keep]
@@ -6537,16 +6550,16 @@ def item_edit(id):
)
images.append(new_filename)
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 location to predefined locations list if applicable
if ort and ort not in it.get_predefined_locations():
it.add_predefined_location(ort)
# 1. Gruppen-Codes synchronisieren
# Sync group barcodes
it.sync_group_codes(str(id), code_4, individual_codes)
# 2. Datenbank-Eintrag aktualisieren
# Save to MongoDB
success = it.update_item(
id=str(id),
name=name,
@@ -6572,6 +6585,8 @@ def item_edit(id):
flash('Fehler beim Aktualisieren des Artikels.', 'error')
return redirect(redirect_target)
@app.route('/update_group', methods=['POST'])
def update_group():
+13
View File
@@ -27,6 +27,19 @@ from Web.modules.database.settings import MongoClient
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):
"""
Safely decrypt an encrypted username string.
+30 -46
View File
@@ -103,7 +103,7 @@
background-color: #0056b3;
}
/* Scanner Video Styles */
/* Scanner Elements */
#code4-scanner video, #code4-scanner canvas,
#isbn-scanner video, #isbn-scanner canvas {
width: 100%;
@@ -124,7 +124,7 @@
overflow: hidden;
}
/* Image management grid */
/* Image Management */
.existing-images-grid {
display: flex;
flex-wrap: wrap;
@@ -197,7 +197,7 @@
<input type="hidden" name="item_id" value="{{ item._id }}">
{% if show_library_features %}
<!-- BIBLIOTHEKS-MODUS: ISBN / Barcode -->
<!-- ================= LIBRARY SPECIFIC FIELDS ================= -->
<div class="form-group">
<label for="isbn">ISBN / Barcode:</label>
<div class="isbn-input-group">
@@ -209,15 +209,31 @@
<small id="isbn-scan-status" style="display:block; color:#666; margin-top:6px;"></small>
<div id="book-info-container"></div>
</div>
<div class="filter-inputs" style="margin-bottom: 20px;">
<h3>Medientyp</h3>
<div class="form-group">
<select name="item_type_input" id="item_type_input">
<option value="Buch" {% if item.ItemType == 'Buch' %}selected{% endif %}>Buch</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="DVD" {% if item.ItemType == 'DVD' %}selected{% endif %}>DVD</option>
<option value="Sonstiges" {% if item.ItemType == 'Sonstiges' %}selected{% endif %}>Sonstiges</option>
</select>
</div>
<h3>Bibliotheks-Kategorie:</h3>
<div class="form-group">
<input type="text" name="library_category" id="library_category" value="{{ item.library_category|default('') }}" placeholder="z.B. Belletristik, Sachbücher, etc.">
</div>
</div>
{% endif %}
<!-- Allgemein: Name -->
<!-- ================= COMMON CORE FIELDS ================= -->
<div class="form-group">
<label for="name">Name / Titel:</label>
<input type="text" id="name" name="name" value="{{ item.Name|default('') }}" required>
</div>
<!-- Allgemein: Ort -->
<div class="form-group">
<label for="ort">Ort / Standort:</label>
<select id="ort" name="ort" data-selected="{{ item.Ort|default('') }}" required>
@@ -234,13 +250,11 @@
</div>
</div>
<!-- Allgemein: Beschreibung -->
<div class="form-group">
<label for="beschreibung">Beschreibung:</label>
<textarea id="beschreibung" name="beschreibung" required>{{ item.Beschreibung|default('') }}</textarea>
</div>
<!-- Barcode / Basis-Code -->
<div class="form-group" id="primary_code_group">
<label for="code_4">Basis-Code (Haupt-Barcode)</label>
<div style="display: flex; gap: 10px;">
@@ -251,33 +265,13 @@
<small id="code4-scan-status" class="form-text text-muted"></small>
</div>
<!-- Weitere Einzelcodes der Gruppe -->
<div class="form-group" id="individual_codes_group">
<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>
<small style="display:block; color:#666; margin-top: 5px;">Der Basis-Code steht oben. Alle weiteren Gruppenmitglieder werden hier untereinander aufgeführt.</small>
</div>
{% if show_library_features %}
<!-- BIBLIOTHEKS-MODUS: Spezifische Filter -->
<div class="filter-inputs">
<h3>Medientyp</h3>
<div class="form-group">
<select name="item_type_input" id="item_type_input">
<option value="Buch" {% if item.ItemType == 'Buch' %}selected{% endif %}>Buch</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="DVD" {% if item.ItemType == 'DVD' %}selected{% endif %}>DVD</option>
<option value="Sonstiges" {% if item.ItemType == 'Sonstiges' or not item.ItemType %}selected{% endif %}>Sonstiges</option>
</select>
</div>
<h3>Kategorie / Fach:</h3>
<div class="form-group">
<input type="text" name="library_category" id="library_category" value="{{ item.library_category|default('') }}" placeholder="z.B. Belletristik, Sachbücher, etc.">
</div>
</div>
{% else %}
<!-- INVENTAR-MODUS: Standard 3-Stufen-Filter -->
<!-- ================= SYSTEM FILTERS 1-3 (ALWAYS AVAILABLE) ================= -->
<div class="filter-inputs">
<h3>Unterrichtsfach (Filter 1):</h3>
<div class="multi-filter">
@@ -313,9 +307,8 @@
{% endfor %}
</div>
</div>
{% endif %}
<!-- Finanzen & Datum -->
<!-- ================= DATES & FINANCIALS ================= -->
<div class="form-group">
<label for="anschaffungsjahr">Anschaffungsjahr:</label>
<input type="date" id="anschaffungsjahr" name="anschaffungsjahr" value="{{ item.Anschaffungsjahr|default('') }}">
@@ -326,7 +319,7 @@
</div>
{% if not show_library_features %}
<!-- INVENTAR-MODUS: Bilder & Medien verwalten -->
<!-- ================= INVENTORY IMAGE MANAGEMENT ================= -->
<div class="form-group">
<label>Bestehende Bilder behalten:</label>
{% if item.Images and item.Images|length > 0 %}
@@ -350,7 +343,6 @@
</div>
{% endif %}
<!-- Reservierbar -->
<div class="form-group">
<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 %}>
@@ -363,13 +355,12 @@
<script src="https://cdn.jsdelivr.net/npm/@ericblade/quagga2/dist/quagga.js"></script>
<script>
const libraryModuleEnabled = {{ 'true' if show_library_features else 'false' }};
let scannerRunning = false;
let activeScannerCallback = null;
let code4LastScanned = '';
let code4LastScannedAt = 0;
// Filter-Optionen vom Server laden & bestehende Werte vorauswählen
// Load filter dropdown options and select current values
function loadAndSelectFilterValues(filterNumber) {
fetch(`/get_predefined_filter_values/${filterNumber}`)
.then(res => res.json())
@@ -380,7 +371,6 @@
const selectedValue = select.getAttribute('data-selected') || '';
// Optionen befüllen
data.values.forEach(val => {
if (!val || String(val).trim() === '') return;
const opt = document.createElement('option');
@@ -389,7 +379,6 @@
select.appendChild(opt);
});
// Falls der gewählte Wert nicht in den Vorgaben war, als Option ergänzen
if (selectedValue && !Array.from(select.options).some(o => o.value === selectedValue)) {
const customOpt = document.createElement('option');
customOpt.value = selectedValue;
@@ -400,10 +389,9 @@
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
function loadLocationOptions() {
fetch('/get_predefined_locations')
.then(res => res.json())
@@ -423,10 +411,9 @@
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
function addNewLocation() {
const input = document.getElementById('new-location-input');
const val = input.value.trim();
@@ -446,7 +433,6 @@
document.getElementById('new-location-input').value = '';
}
// Quagga Barcode Scanner Initialisierung
function runEngineInitialization(targetSelector, activeCallback, completionMsg, errorStatusSetter) {
if (scannerRunning) { Quagga.stop(); scannerRunning = false; }
activeScannerCallback = activeCallback;
@@ -470,7 +456,6 @@
if (typeof activeScannerCallback === "function") activeScannerCallback(String(data.codeResult.code).trim());
});
// Barcode Scanner für Basis-Code
function startCode4Scanner() {
const scannerBox = document.getElementById('code4-scanner');
const scanBtn = document.getElementById('scan-code4-btn');
@@ -504,10 +489,9 @@
document.addEventListener('DOMContentLoaded', function() {
loadLocationOptions();
if (!libraryModuleEnabled) {
loadAndSelectFilterValues(1);
loadAndSelectFilterValues(2);
}
// Always load Filter 1 and Filter 2 options for all items
loadAndSelectFilterValues(1);
loadAndSelectFilterValues(2);
const scanCodeBtn = document.getElementById('scan-code4-btn');
if (scanCodeBtn) scanCodeBtn.addEventListener('click', startCode4Scanner);