Compare commits

...

9 Commits

Author SHA1 Message Date
Aiirondev_dev 1af2a2be06 implementation of the image
Release Inventarsystem / release-docker (push) Successful in 2m14s
2026-08-12 00:32:00 +02:00
Aiirondev_dev 8e5e434116 fix of the template name
Release Inventarsystem / release-docker (push) Successful in 2m15s
2026-08-12 00:24:25 +02:00
Aiirondev_dev 2f9a93ee65 fix of the template name
Release Inventarsystem / release-docker (push) Successful in 2m14s
2026-08-12 00:20:48 +02:00
Aiirondev_dev 91467a1e76 fix of the template name
Release Inventarsystem / release-docker (push) Successful in 2m14s
2026-08-12 00:19:19 +02:00
Aiirondev_dev 3840348a2d Changes to the formating and processing of the group codes
Release Inventarsystem / release-docker (push) Successful in 2m15s
2026-08-12 00:12:06 +02:00
Aiirondev_dev cdb7319c56 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
2026-08-11 23:58:23 +02:00
Aiirondev_dev 08bea97f0f 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
2026-08-11 23:49:20 +02:00
Aiirondev_dev 9164cd030d Debug changes
Release Inventarsystem / release-docker (push) Successful in 2m14s
2026-08-11 23:07:13 +02:00
Aiirondev_dev 9227392787 Debug changes
Release Inventarsystem / release-docker (push) Successful in 2m15s
2026-08-11 22:56:40 +02:00
3 changed files with 212 additions and 34 deletions
+59 -25
View File
@@ -3313,7 +3313,7 @@ def api_library_items():
ausleihungen_db = db['ausleihungen'] ausleihungen_db = db['ausleihungen']
query = { query = {
'ItemType': {'$in': ['book', 'cd', 'dvd', 'schoolbook', 'schulbuch', 'Buch', 'Schulbuch']}, 'ItemType': {'$in': ['book', 'cd', 'CD', 'DVD', 'dvd', 'schoolbook', 'schulbuch', 'Buch', 'Schulbuch']},
'IsGroupedSubItem': {'$ne': True}, 'IsGroupedSubItem': {'$ne': True},
'Deleted': {'$ne': True} 'Deleted': {'$ne': True}
} }
@@ -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,6 +6399,22 @@ 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'])
# Codes für die Ansicht trennen (Basis-Code vs. Einzelcodes)
base_code = item.get('Code_4', '')
individual_codes = []
group_id = item.get('SeriesGroupId')
if group_id:
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(
@@ -6412,7 +6426,7 @@ def item_edit(id):
page_title=f"Bearbeiten: {item.get('Name', '')}" page_title=f"Bearbeiten: {item.get('Name', '')}"
) )
# --- POST: Speichern --- # --- POST: Speichern ---
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) current_item = it.get_item(obj_id)
@@ -6420,13 +6434,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 aus der Textarea
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,22 +6463,13 @@ 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 # Uniqueness-Check über alle Codes
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)
return redirect(redirect_target) db_instance = client[cfg.MONGODB_DB]
items_col = db_instance['items']
# ISBN und Medientyp verarbeiten
item_isbn = ''
item_type = item_type_input or current_item.get('ItemType', 'general')
if cfg.MODULES.is_enabled('library') and isbn_raw:
item_isbn = normalize_and_validate_isbn(isbn_raw)
if not item_isbn:
flash('Ungültiges ISBN-Format.', 'error')
return redirect(redirect_target)
# Bilder verarbeiten (GridFS) # Bilder verarbeiten (GridFS)
images_to_keep = request.form.getlist('existing_images') images_to_keep = request.form.getlist('existing_images')
@@ -6503,9 +6519,27 @@ def item_edit(id):
except Exception as e: except Exception as e:
app.logger.error(f"Bild-Fehler bei Item {id}: {e}") app.logger.error(f"Bild-Fehler bei Item {id}: {e}")
if ort and ort not in it.get_predefined_locations(): has_code_error = False
it.add_predefined_location(ort) 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)
# 1. Gruppen-Bestand über die korrigierte Helfer-Funktion synchronisieren
it.sync_group_codes(str(id), code_4, individual_codes)
# 2. Deine bestehende update_item Funktion aufrufen
success = it.update_item( success = it.update_item(
id=str(id), id=str(id),
name=name, name=name,
@@ -6520,8 +6554,8 @@ def item_edit(id):
ansch_kost=anschaffungs_kosten, ansch_kost=anschaffungs_kosten,
code_4=code_4, code_4=code_4,
reservierbar=reservierbar, reservierbar=reservierbar,
isbn=item_isbn, isbn=isbn_raw,
item_type=item_type item_type=item_type_input
) )
if success: if success:
+120 -1
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
@@ -1147,4 +1148,122 @@ def get_current_status(item_id, decrypt=True):
return None return None
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 im korrekten Schema
(angelehnt an das 'Augenmodell groß'-Vorbild).
"""
if not base_code:
return False
# Alle Ziel-Codes zusammenführen (Basis-Code an erster Stelle)
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)
item_count = len(all_target_codes)
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')
# Wenn es mehr als 1 Item gibt und noch keine Gruppe existiert -> Neue GroupID erzeugen
if not group_id and item_count > 1:
group_id = str(uuid.uuid4())
# Wenn es nun eine Gruppe gibt (item_count > 1)
if item_count > 1:
# 1. Haupt-Item (Parent) aktualisieren
items.update_one(
{'_id': primary_item['_id']},
{'$set': {
'Code_4': base_code,
'SeriesGroupId': group_id,
'SeriesCount': item_count,
'SeriesPosition': 1,
'IsGroupedSubItem': False,
'ParentItemId': None
}}
)
# Bestehende Gruppenmitglieder laden
existing_items = list(items.find({'SeriesGroupId': group_id}))
existing_map = {it.get('Code_4'): it for it in existing_items if
it.get('Code_4') and str(it['_id']) != str(primary_item['_id'])}
# Alle verbleibenden Sub-Codes ab Position 2 abarbeiten
processed_sub_ids = []
for idx, code in enumerate(all_target_codes[1:], start=2):
if code in existing_map:
# Existiert bereits in der Gruppe -> Nur Position und Count aktualisieren
sub_item = existing_map[code]
processed_sub_ids.append(sub_item['_id'])
items.update_one(
{'_id': sub_item['_id']},
{'$set': {
'SeriesCount': item_count,
'SeriesPosition': idx,
'IsGroupedSubItem': True,
'ParentItemId': str(primary_item['_id'])
}}
)
else:
# Neu hinzukommender Code -> Als Klon (Sub-Item) erstellen
new_sub = primary_item.copy()
if '_id' in new_sub:
del new_sub['_id']
new_sub.update({
'Code_4': code,
'SeriesGroupId': group_id,
'SeriesCount': item_count,
'SeriesPosition': idx,
'IsGroupedSubItem': True,
'ParentItemId': str(primary_item['_id']),
'LastUpdated': primary_item.get('LastUpdated')
})
inserted_res = items.insert_one(new_sub)
processed_sub_ids.append(inserted_res.inserted_id)
# Nicht mehr benötigte Sub-Items aus dieser Gruppe entfernen
for code, sub_item in existing_map.items():
if sub_item['_id'] not in processed_sub_ids:
items.delete_one({'_id': sub_item['_id']})
else:
# Fall: Nur 1 einziges Item (keine Gruppe / Gruppe aufgelöst)
# Eventuelle alte Sub-Items dieser Gruppe löschen
if group_id:
items.delete_many({
'SeriesGroupId': group_id,
'_id': {'$ne': primary_item['_id']}
})
items.update_one(
{'_id': primary_item['_id']},
{'$set': {
'Code_4': base_code,
'SeriesGroupId': None,
'SeriesCount': 1,
'SeriesPosition': 1,
'IsGroupedSubItem': False,
'ParentItemId': None
}}
)
client.close()
return True
except Exception as e:
print(f"Error syncing group codes: {e}")
return False
+33 -8
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;
@@ -1001,4 +1024,6 @@
const codeField = document.getElementById('code_4'); const codeField = document.getElementById('code_4');
if (codeField) codeField.addEventListener('blur', function() { validateCodeField(this, getItemId()); }); if (codeField) codeField.addEventListener('blur', function() { validateCodeField(this, getItemId()); });
}); });
</script> </script>
{% endblock %}