Compare commits

...

15 Commits

Author SHA1 Message Date
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
Aiirondev_dev 4917c22ae3 Debug changes
Release Inventarsystem / release-docker (push) Successful in 2m14s
2026-08-11 22:49:40 +02:00
Aiirondev_dev a2f2dd5a9e Debug changes
Release Inventarsystem / release-docker (push) Successful in 2m14s
2026-08-11 22:41:30 +02:00
Aiirondev_dev faf270ff93 Debug changes
Release Inventarsystem / release-docker (push) Successful in 2m15s
2026-08-11 22:35:43 +02:00
Aiirondev_dev beeb562ac4 Dubvug changes
Release Inventarsystem / release-docker (push) Successful in 2m32s
2026-08-11 22:21:28 +02:00
Aiirondev_dev 0199957545 Dubvug changes
Release Inventarsystem / release-docker (push) Successful in 2m14s
2026-08-11 21:35:08 +02:00
Aiirondev_dev a518adb054 Dubvug changes
Release Inventarsystem / release-docker (push) Successful in 2m15s
2026-08-11 21:27:39 +02:00
Aiirondev_dev 6a94d50d28 Additiopn to filter out the iduvidual items and
Release Inventarsystem / release-docker (push) Has been cancelled
2026-08-11 21:16:37 +02:00
Aiirondev_dev c90cef6dcf Addition of ort preloading for the edeting modal
Release Inventarsystem / release-docker (push) Successful in 2m14s
2026-08-11 15:30:26 +02:00
4 changed files with 1305 additions and 95 deletions
+153 -2
View File
@@ -46,7 +46,7 @@ import Web.modules.inventarsystem.pdf_export as pdf_export
import Web.modules.inventarsystem.excel_export as excel_export import Web.modules.inventarsystem.excel_export as excel_export
import datetime import datetime
from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.schedulers.background import BackgroundScheduler
from bson.objectid import ObjectId from bson.objectid import ObjectId, InvalidId
from urllib.parse import urlparse, urlunparse from urllib.parse import urlparse, urlunparse
import requests import requests
import csv import csv
@@ -3313,7 +3313,8 @@ 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},
'Deleted': {'$ne': True} 'Deleted': {'$ne': True}
} }
@@ -6366,6 +6367,156 @@ def edit_item(id):
return redirect(url_for('home_admin')) return redirect(url_for('home_admin'))
@app.route('/item_edit/<id>', methods=['GET', 'POST'])
def item_edit(id):
"""
Endpoint zum Laden und Aktualisieren eines Eintrags (item_edit).
"""
if 'username' not in session:
if request.method == 'POST' and request.is_json:
return jsonify({'success': False, 'message': 'Nicht angemeldet.'}), 401
flash('Bitte melden Sie sich an.', 'error')
return redirect(url_for('login'))
current_permissions = us.get_effective_permissions(session['username'])
if not current_permissions['actions'].get('can_edit', False):
if request.method == 'POST' and request.is_json:
return jsonify({'success': False, 'message': 'Keine Berechtigung zum Bearbeiten.'}), 403
flash('Keine Berechtigung zum Bearbeiten.', 'error')
return redirect(url_for('home_admin'))
try:
obj_id = ObjectId(id)
except InvalidId:
flash('Ungültige Element-ID.', 'error')
return redirect(url_for('home_admin'))
# --- GET: Template anzeigen ---
if request.method == 'GET':
item = it.get_item(id)
if not item:
flash('Element nicht gefunden.', 'error')
return redirect(url_for('home_admin'))
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')
return render_template(
'edit_library.html',
username=session['username'],
item=item,
library_module_enabled=show_library,
show_library_features=show_library,
page_title=f"Bearbeiten: {item.get('Name', '')}"
)
# --- POST: Speichern ---
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)
# Formulardaten auslesen
name = sanitize_form_value(request.form.get('name'))
ort = sanitize_form_value(request.form.get('ort'))
beschreibung = sanitize_form_value(request.form.get('beschreibung'))
# 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_kosten = sanitize_form_value(request.form.get('anschaffungskosten'))
reservierbar = 'reservierbar' in request.form
item_type_input = sanitize_form_value(request.form.get('item_type_input'))
library_category = sanitize_form_value(request.form.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'))
verfuegbar = current_item.get('Verfuegbar', True)
# Uniqueness-Check über alle Codes
current_group_id = current_item.get('SeriesGroupId')
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)
# 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(
id=str(id),
name=name,
ort=ort,
beschreibung=beschreibung,
images=images,
verfuegbar=verfuegbar,
filter1=filter1,
filter2=filter2,
filter3=filter3,
ansch_jahr=anschaffungs_jahr,
ansch_kost=anschaffungs_kosten,
code_4=code_4,
reservierbar=reservierbar,
isbn=item_isbn,
item_type=item_type
)
if success:
flash('Artikel erfolgreich aktualisiert.', 'success')
else:
flash('Fehler beim Aktualisieren des Artikels.', 'error')
return redirect(redirect_target)
@app.route('/update_group', methods=['POST']) @app.route('/update_group', methods=['POST'])
def update_group(): def update_group():
+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
File diff suppressed because it is too large Load Diff
+3 -92
View File
@@ -800,7 +800,7 @@
return ` return `
<tr> <tr>
<td class="table-title">${escapeHtml(item.Name || 'Untitled')}</td> <td class="table-title">${escapeHtml(item.Name || 'Untitled')}</td>
<td>${escapeHtml(item.Code_4 || item.Code4 || '-')}</td> <td>${escapeHtml(item.ISBN || '-')}</td>
<td>${getItemTypeLabel(item.ItemType || 'book')}</td> <td>${getItemTypeLabel(item.ItemType || 'book')}</td>
<td style="font-weight:600; text-align:center;">${item.Quantity || item.GroupedDisplayCount || 1}</td> <td style="font-weight:600; text-align:center;">${item.Quantity || item.GroupedDisplayCount || 1}</td>
<td> <td>
@@ -1565,97 +1565,8 @@
}).join(''); }).join('');
} }
window.openEditLibraryItem = async function (itemId) { function openEditLibraryItem(itemId) {
const item = libraryItems.find(i => i._id === itemId); window.location.href = `/item_edit/${itemId}`;
if (!item) return;
editLibraryState.itemId = item._id;
editLibraryState.seriesGroupId = item.SeriesGroupId || '';
editLibraryState.groupMembers = [];
// Felder befüllen
document.getElementById('editLibraryItemId').value = item._id;
document.getElementById('editLibraryName').value = item.Name;
document.getElementById('editLibraryType').value = item.ItemType;
document.getElementById('editLibraryIsbn').value = item.ISBN || '';
document.getElementById('editLibraryLocation').value = item.Ort;
document.getElementById('editLibraryDescription').value = item.Beschreibung;
const codesContainer = document.getElementById('editLibraryCodesContainer');
if (codesContainer) {
codesContainer.innerHTML = '<div style="padding:10px 0; color:#6b7280;">Lade Codes...</div>';
}
const groupMembers = item.SeriesGroupId ? await fetchLibraryGroupMembers(item.SeriesGroupId) : [item];
renderLibraryGroupCodeFields(groupMembers.length > 0 ? groupMembers : [item], item._id);
document.getElementById('editLibraryModal').style.display = 'flex';
};
function closeEditLibraryModal() {
document.getElementById('editLibraryModal').style.display = 'none';
} }
</script> </script>
<div id="editLibraryModal" class="modal" style="display:none;">
<div class="modal-content" style="max-width: 760px; padding: 25px; border-radius: 8px;">
<span class="close" onclick="closeEditLibraryModal()" style="cursor: pointer; float: right; font-size: 24px;">&times;</span>
<h3 style="margin-top:0;">Bibliotheksmedium bearbeiten</h3>
<!-- Bereich für Gruppen-Informationen (Hier konsolidiert!) -->
<div id="editLibraryGroupWarning" style="display:none; background-color: #fff; padding: 15px; border-radius: 6px; margin-bottom: 20px; border: 1px solid #0ea5e9;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; border-bottom: 1px solid #eee; padding-bottom: 10px;">
<strong style="color: #0ea5e9;">Gruppen-Range (Total: <span id="editLibraryGroupCount"></span>)</strong>
</div>
<p id="editLibraryGroupHint" style="margin: 5px 0; font-size: 12px; color: #555;">
Änderungen an Titel, Ort und Beschreibung werden auf alle Exemplare der Gruppe übertragen.
</p>
<div style="margin-top: 15px; font-size: 11px; background: #e0f2fe; padding: 8px; border-radius: 4px;">
<strong>Hinweis:</strong> Jeder Mediencode wird einzeln gespeichert, damit alle Exemplare der Gruppe korrekt bleiben.
</div>
</div>
<form id="editLibraryForm">
<input type="hidden" id="editLibraryItemId">
<div class="edit-grid">
<div class="full">
<label for="editLibraryName">Titel</label>
<input id="editLibraryName" required style="width: 100%;">
</div>
<div>
<label for="editLibraryType">Medientyp</label>
<select id="editLibraryType" style="width: 100%;">
<option value="Buch">Buch</option>
<option value="Schulbuch">Schulbuch</option>
<option value="cd">CD</option>
<option value="dvd">DVD</option>
<option value="other">Sonstige Medien</option>
</select>
</div>
<div>
<label for="editLibraryIsbn">ISBN</label>
<input id="editLibraryIsbn" placeholder="optional ISBN-10/13" style="width: 100%;">
</div>
<div>
<label>Mediencodes</label>
<div id="editLibraryCodesContainer"></div>
</div>
<div class="full">
<label for="editLibraryLocation">Ort</label>
<input id="editLibraryLocation" required style="width: 100%;">
</div>
<div class="full">
<label for="editLibraryDescription">Beschreibung</label>
<textarea id="editLibraryDescription" rows="4" required style="width: 100%;"></textarea>
</div>
</div>
<div class="edit-actions" style="margin-top:20px;">
<button type="submit" class="button" style="background:#0ea5e9;color:#fff;">Speichern & Synchronisieren</button>
<button type="button" class="button" onclick="closeEditLibraryModal()">Abbrechen</button>
</div>
</form>
</div>
</div>
{% endblock %} {% endblock %}