Changes so the edeting function can also adeit all the Codes and the entire mamunt of an itemgroup
Release Inventarsystem / release-docker (push) Successful in 2m14s

This commit is contained in:
2026-08-11 23:49:20 +02:00
parent 9164cd030d
commit 08bea97f0f
3 changed files with 161 additions and 27 deletions
+56 -19
View File
@@ -6372,7 +6372,6 @@ def item_edit(id):
""" """
Endpoint zum Laden und Aktualisieren eines Eintrags (item_edit). Endpoint zum Laden und Aktualisieren eines Eintrags (item_edit).
""" """
# 1. Rechte- & Auth-Check
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:
return jsonify({'success': False, 'message': 'Nicht angemeldet.'}), 401 return jsonify({'success': False, 'message': 'Nicht angemeldet.'}), 401
@@ -6392,7 +6391,6 @@ 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'))
# --- GET: Template anzeigen --- # --- GET: Template anzeigen ---
if request.method == 'GET': if request.method == 'GET':
item = it.get_item(id) item = it.get_item(id)
@@ -6401,10 +6399,24 @@ def item_edit(id):
return redirect(url_for('home_admin')) return redirect(url_for('home_admin'))
item['_id'] = str(item['_id']) item['_id'] = str(item['_id'])
# Gruppen-Codes aufteilen in Basis-Code und Einzelcodes für die Textarea
base_code = item.get('Code_4', '')
individual_codes = []
if item.get('SeriesGroupId'):
group_ids = it.get_group_item_ids(str(item['_id']))
if group_ids:
for gid in group_ids:
g_item = it.get_item(gid)
c4 = g_item.get('Code_4')
if c4 and c4 != base_code:
individual_codes.append(c4)
item['IndividualCodes'] = '\n'.join(individual_codes)
show_library = cfg.MODULES.is_enabled('library') show_library = cfg.MODULES.is_enabled('library')
return render_template( return render_template(
'edit_library.html', 'item_edit.html',
username=session['username'], username=session['username'],
item=item, item=item,
library_module_enabled=show_library, library_module_enabled=show_library,
@@ -6420,13 +6432,24 @@ def item_edit(id):
flash('Element in der Datenbank nicht gefunden.', 'error') flash('Element in der Datenbank nicht gefunden.', 'error')
return redirect(redirect_target) return redirect(redirect_target)
# Formulardaten auslesen und bereinigen # Formulardaten auslesen
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'))
code_4 = sanitize_form_value(request.form.get('code_4'))
isbn_raw = sanitize_form_value(request.form.get('isbn', ''))
# Basis-Code & Einzelcodes
code_4 = sanitize_form_value(request.form.get('code_4'))
individual_codes_raw = request.form.get('individual_codes', '')
individual_codes = []
for c in individual_codes_raw.replace('\r', '').split('\n'):
clean_c = sanitize_form_value(c)
if clean_c and clean_c != code_4 and clean_c not in individual_codes:
individual_codes.append(clean_c)
all_codes_to_check = [code_4] + individual_codes
isbn_raw = sanitize_form_value(request.form.get('isbn', ''))
anschaffungs_jahr = sanitize_form_value(request.form.get('anschaffungsjahr')) anschaffungs_jahr = sanitize_form_value(request.form.get('anschaffungsjahr'))
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
@@ -6438,15 +6461,32 @@ def item_edit(id):
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'))
# WICHTIG: Den aktuellen Verfügbarkeitsstatus aus der Datenbank übernehmen
verfuegbar = current_item.get('Verfuegbar', True) verfuegbar = current_item.get('Verfuegbar', True)
# Barcode Prüfen # Sicherer Uniqueness-Check für alle Codes über eine lokale DB-Verbindung
if code_4 and not it.is_code_unique(code_4, exclude_id=str(id)): current_group_id = current_item.get('SeriesGroupId')
flash(f'Der Code "{code_4}" wird bereits verwendet.', 'error') client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
db_instance = client[cfg.MONGODB_DB]
items_col = db_instance['items']
has_code_error = False
for code in all_codes_to_check:
if not code:
continue
existing = items_col.find_one({'Code_4': code, 'Deleted': {'$ne': True}})
if existing:
is_same_item = str(existing['_id']) == str(id)
is_in_same_group = current_group_id and existing.get('SeriesGroupId') == current_group_id
if not is_same_item and not is_in_same_group:
flash(f'Der Code "{code}" wird bereits von einem anderen Artikel verwendet.', 'error')
has_code_error = True
break
client.close()
if has_code_error:
return redirect(redirect_target) return redirect(redirect_target)
# ISBN und Medientyp verarbeiten # ISBN validieren
item_isbn = '' item_isbn = ''
item_type = item_type_input or current_item.get('ItemType', 'general') item_type = item_type_input or current_item.get('ItemType', 'general')
if cfg.MODULES.is_enabled('library') and isbn_raw: if cfg.MODULES.is_enabled('library') and isbn_raw:
@@ -6455,7 +6495,7 @@ def item_edit(id):
flash('Ungültiges ISBN-Format.', 'error') flash('Ungültiges ISBN-Format.', 'error')
return redirect(redirect_target) return redirect(redirect_target)
# Bilder verarbeiten (GridFS) # Bilder verarbeiten
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]
@@ -6469,30 +6509,23 @@ def item_edit(id):
if not is_allowed: if not is_allowed:
flash(error_msg, 'error') flash(error_msg, 'error')
return redirect(redirect_target) return redirect(redirect_target)
try: try:
secure_name = secure_filename(file.filename) secure_name = secure_filename(file.filename)
file.seek(0) file.seek(0)
image_bytes = file.read() image_bytes = file.read()
if not image_bytes: if not image_bytes:
continue continue
optimized_io = io.BytesIO() optimized_io = io.BytesIO()
with Image.open(io.BytesIO(image_bytes)) as img: with Image.open(io.BytesIO(image_bytes)) as img:
if img.mode not in ('RGB', 'RGBA'): if img.mode not in ('RGB', 'RGBA'):
img = img.convert('RGBA') img = img.convert('RGBA')
max_width = 800 max_width = 800
if img.width > max_width: if img.width > max_width:
ratio = max_width / img.width ratio = max_width / img.width
img = img.resize((max_width, int(img.height * ratio)), Image.Resampling.LANCZOS) img = img.resize((max_width, int(img.height * ratio)), Image.Resampling.LANCZOS)
img.save(optimized_io, format='WEBP', quality=85, optimize=True) img.save(optimized_io, format='WEBP', quality=85, optimize=True)
optimized_io.seek(0) optimized_io.seek(0)
new_filename = f"{uuid.uuid4().hex}_{int(time.time())}.webp" new_filename = f"{uuid.uuid4().hex}_{int(time.time())}.webp"
fs.put( fs.put(
optimized_io, optimized_io,
filename=new_filename, filename=new_filename,
@@ -6506,6 +6539,10 @@ def item_edit(id):
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-Bestand über die neue Helfer-Funktion synchronisieren
it.sync_group_codes(str(id), code_4, individual_codes)
# 2. Deine zentrale update_item Funktion aufrufen
success = it.update_item( success = it.update_item(
id=str(id), id=str(id),
name=name, name=name,
+74
View File
@@ -20,6 +20,7 @@ Collection Structure:
""" """
from bson.objectid import ObjectId from bson.objectid import ObjectId
from bson.errors import InvalidId from bson.errors import InvalidId
import uuid
import datetime import datetime
import Web.modules.database.settings as cfg import Web.modules.database.settings as cfg
from Web.modules.database.settings import MongoClient from Web.modules.database.settings import MongoClient
@@ -1148,3 +1149,76 @@ def get_current_status(item_id, decrypt=True):
except Exception as e: except Exception as e:
print(f"Error retrieving current status for item {item_id}: {e}") print(f"Error retrieving current status for item {item_id}: {e}")
return None return None
def sync_group_codes(primary_obj_id, base_code, individual_codes_list):
"""
Synchronisiert die Barcodes einer Gruppe beim Bearbeiten (abwärtskompatibel).
Trennt sauber zwischen 'Basis-Code' und 'weiteren Einzelcodes'.
"""
if not base_code:
return False
# Alle Codes zusammenführen und Duplikate filtern
all_target_codes = [base_code]
for c in individual_codes_list:
if c and c not in all_target_codes:
all_target_codes.append(c)
try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
db = client[cfg.MONGODB_DB]
items = db['items']
primary_item = items.find_one({'_id': ObjectId(primary_obj_id)})
if not primary_item:
client.close()
return False
group_id = primary_item.get('SeriesGroupId')
# 1. Wenn mehrere Codes existieren, aber noch keine Gruppe: Gruppe erstellen
if not group_id and len(all_target_codes) > 1:
group_id = str(uuid.uuid4())
items.update_one({'_id': primary_item['_id']}, {'$set': {'SeriesGroupId': group_id}})
primary_item['SeriesGroupId'] = group_id
# 2. Wenn es weiterhin keine Gruppe gibt (nur 1 Code), updaten und beenden
if not group_id:
items.update_one({'_id': primary_item['_id']}, {'$set': {'Code_4': base_code}})
client.close()
return True
# 3. Bestand abgleichen
existing_items = list(items.find({'SeriesGroupId': group_id}))
existing_codes_map = {item.get('Code_4'): item['_id'] for item in existing_items if item.get('Code_4')}
existing_codes_set = set(existing_codes_map.keys())
target_codes_set = set(all_target_codes)
codes_to_add = target_codes_set - existing_codes_set
codes_to_remove = existing_codes_set - target_codes_set
# 4. Entfernte Codes löschen (Haupt-Item wird niemals gelöscht)
for code in codes_to_remove:
item_id_to_delete = existing_codes_map.get(code)
if item_id_to_delete and item_id_to_delete != primary_item['_id']:
items.delete_one({'_id': item_id_to_delete})
# 5. Neue Items für neu eingetragene Codes als Klone erstellen
for code in codes_to_add:
new_item = primary_item.copy()
if '_id' in new_item:
del new_item['_id']
new_item['Code_4'] = code
new_item['is_grouped_sub_item'] = True
new_item['parent_item_id'] = str(primary_item['_id'])
items.insert_one(new_item)
# 6. Sicherstellen, dass das Haupt-Item den Basis-Code behält
if primary_item.get('Code_4') != base_code:
items.update_one({'_id': primary_item['_id']}, {'$set': {'Code_4': base_code}})
client.close()
return True
except Exception as e:
print(f"Error syncing group codes: {e}")
return False
+30 -7
View File
@@ -763,17 +763,24 @@
<textarea id="beschreibung" name="beschreibung" rows="4" class="form-control" required>{{ item.Beschreibung if item.Beschreibung else '' }}</textarea> <textarea id="beschreibung" name="beschreibung" rows="4" class="form-control" required>{{ item.Beschreibung if item.Beschreibung else '' }}</textarea>
</div> </div>
<!-- Barcode / Code_4 --> <!-- Barcode / Basis-Code -->
<div class="form-group" id="primary_code_group"> <div class="form-group" id="primary_code_group">
<label for="code_4">Barcode / Artikel-Code:</label> <label for="code_4">Basis-Code (Haupt-Barcode)</label>
<div style="display: flex; gap: 10px;"> <div style="display: flex; gap: 10px;">
<input type="text" id="code_4" name="code_4" class="form-control" value="{{ item.Code_4 if item.Code_4 else '' }}" required> <input type="text" id="code_4" name="code_4" class="form-control" value="{{ item.Code_4 if item.Code_4 else '' }}" required>
<button type="button" id="scan-code4-btn" class="btn btn-primary">Barcode scannen</button> <button type="button" id="scan-code4-btn" class="btn btn-primary" style="white-space: nowrap;">Barcode scannen</button>
</div> </div>
<div id="code4-scanner" style="display:none; margin-top: 10px;"></div> <div id="code4-scanner" style="display:none; margin-top: 10px;"></div>
<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 -->
<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="4" class="form-control">{{ item.IndividualCodes if item.IndividualCodes else '' }}</textarea>
<small style="display:block; color:#666; margin-top: 5px;">Der Scanner füllt zuerst den Basis-Code; weitere gescannte Codes werden hier automatisch zeilenweise angehängt.</small>
</div>
{% if show_library_features %} {% if show_library_features %}
<!-- Bibliotheks-Klassifizierung --> <!-- Bibliotheks-Klassifizierung -->
<div class="filter-inputs"> <div class="filter-inputs">
@@ -917,8 +924,10 @@
function startCode4Scanner() { function startCode4Scanner() {
const scannerBox = document.getElementById('code4-scanner'); const scannerBox = document.getElementById('code4-scanner');
const scanButton = document.getElementById('scan-code4-btn'); const scanButton = document.getElementById('scan-code4-btn');
const codeField = document.getElementById('code_4'); const baseCodeField = document.getElementById('code_4');
if (!scannerBox || !scanButton || !codeField) return; const individualCodesField = document.getElementById('individual_codes');
if (!scannerBox || !scanButton || !baseCodeField) return;
if (scannerBox.style.display !== 'none') { if (scannerBox.style.display !== 'none') {
killScannerHardware(); scannerBox.style.display = 'none'; scanButton.textContent = 'Barcode scannen'; return; killScannerHardware(); scannerBox.style.display = 'none'; scanButton.textContent = 'Barcode scannen'; return;
@@ -931,11 +940,25 @@
code4LastScanned = decodedText; code4LastScannedAt = now; code4LastScanned = decodedText; code4LastScannedAt = now;
killScannerHardware(); scannerBox.style.display = 'none'; scanButton.textContent = 'Barcode scannen'; killScannerHardware(); scannerBox.style.display = 'none'; scanButton.textContent = 'Barcode scannen';
codeField.value = decodedText;
validateCodeField(codeField, getItemId()); // Intelligentes Eintragen: Entweder Basis-Code oder Textarea
if (!baseCodeField.value.trim()) {
baseCodeField.value = decodedText;
} else {
let currentCodes = individualCodesField.value.split('\n').map(c => c.trim()).filter(c => c);
if (!currentCodes.includes(decodedText) && baseCodeField.value.trim() !== decodedText) {
currentCodes.push(decodedText);
individualCodesField.value = currentCodes.join('\n');
}
}
}, 'Scanner läuft...', (msg) => { document.getElementById('code4-scan-status').textContent = msg; }); }, 'Scanner läuft...', (msg) => { document.getElementById('code4-scan-status').textContent = msg; });
} }
document.addEventListener('DOMContentLoaded', function() {
const scanCodeBtn = document.getElementById('scan-code4-btn');
if (scanCodeBtn) scanCodeBtn.addEventListener('click', startCode4Scanner);
});
function validateCodeField(input, excludeId) { function validateCodeField(input, excludeId) {
const code = input.value.trim(); const code = input.value.trim();
if (!code) return; if (!code) return;