Compare commits

...

3 Commits

2 changed files with 117 additions and 87 deletions
+58 -52
View File
@@ -556,7 +556,7 @@ def _enforce_module_access():
flash(msg, 'info')
if name != 'inventory' and cfg.MODULES.is_enabled('inventory'):
return redirect(url_for('home'))
return redirect(url_for('home_admin'))
elif name != 'library' and cfg.MODULES.is_enabled('library'):
return redirect('/library')
@@ -630,7 +630,7 @@ def handle_build_error(e):
return redirect('/library')
if 'username' in session:
return redirect(url_for('home'))
return redirect(url_for('home_admin'))
return redirect(url_for('login'))
@@ -708,10 +708,10 @@ def _permission_denied_fallback_endpoint(permissions, current_endpoint=None):
is_admin_user = bool(username and us.check_admin(username))
admin_home_allowed = _page_access_allowed(permissions, 'home_admin') and _action_access_allowed(permissions, 'can_manage_settings')
for candidate in ('my_borrowed_items', 'tutorial_page', 'notifications_view', 'impressum', 'home'):
for candidate in ('my_borrowed_items', 'tutorial_page', 'notifications_view', 'impressum', 'home_admin'):
if current_endpoint and candidate == current_endpoint:
continue
if candidate == 'home' and is_admin_user and not admin_home_allowed:
if candidate == 'home_admin' and is_admin_user and not admin_home_allowed:
continue
if _page_access_allowed(permissions, candidate):
return candidate
@@ -2178,7 +2178,7 @@ def _upload_excel_items(scope='inventory'):
if is_library_scope:
if not cfg.MODULES.is_enabled('library'):
flash('Bibliotheks-Modul ist deaktiviert.', 'error')
return redirect(url_for('home'))
return redirect(url_for('home_admin'))
excel_file = request.files.get(file_field)
if not excel_file or not excel_file.filename:
@@ -2194,7 +2194,7 @@ def _upload_excel_items(scope='inventory'):
# Allow CSV imports for authenticated non-admin users only for inventory (non-library)
if not (is_csv and not is_library_scope and 'username' in session):
flash('Einfüge-Rechte erforderlich.', 'error')
return redirect(url_for('home'))
return redirect(url_for('home_admin'))
filename_lower = excel_file.filename.lower()
if not filename_lower.endswith(('.xlsx', '.csv')):
@@ -2882,15 +2882,16 @@ def user_status():
##################################################### changes to be made to account for the new account permison managment system ##############################
""""
@app.route('/')
def home():
"""
""""""
Main route for the application homepage.
Redirects to the appropriate view based on user role.
Returns:
flask.Response: Rendered template or redirect
"""
""""""
if 'username' not in session:
flash('Bitte mit registriertem Konto anmelden!', 'error')
return redirect(url_for('login'))
@@ -2921,7 +2922,7 @@ def home():
if fallback_endpoint == 'logout':
flash('Für diesen Benutzer sind aktuell keine Seiten freigegeben.', 'error')
return redirect(url_for(fallback_endpoint))
"""
@app.route('/home_admin')
def home_admin():
@@ -2942,9 +2943,12 @@ def home_admin():
else:
return "Weder Inventar- noch Bibliotheks-Modul sind aktiviert.", 403
if not us.check_admin(session['username']):
current_permissions = us.get_effective_permissions(session['username'])
if not current_permissions['pages'].get('home', False):
flash('Ihnen ist es nicht gestattet auf dieser Internetanwendung, die eben besuchte Adrrese zu nutzen, versuchen sie es erneut nach dem sie sich mit einem berechtigten Nutzer angemeldet haben!', 'error')
return redirect(url_for('login'))
return redirect(url_for('library_view'))
return render_template(
'main_admin.html',
username=session['username'],
@@ -3052,7 +3056,7 @@ def library_view():
return redirect(url_for('login'))
if not cfg.MODULES.is_enabled('library'):
flash('Bibliotheks-Modul ist deaktiviert.', 'error')
return redirect(url_for('home'))
return redirect(url_for('home_admin'))
return render_template(
'library_table.html',
@@ -3080,7 +3084,7 @@ def library_loans_admin():
if not cfg.MODULES.is_enabled('library'):
flash('Bibliotheks-Modul ist deaktiviert.', 'error')
return redirect(url_for('home'))
return redirect(url_for('home_admin'))
_ensure_audit_indexes_once()
@@ -4478,7 +4482,7 @@ def login():
flask.Response: Rendered template or redirect
"""
if 'username' in session:
return redirect(url_for('home'))
return redirect(url_for('home_admin'))
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
@@ -4514,7 +4518,7 @@ def login():
fallback_endpoint = _permission_denied_fallback_endpoint(permissions, current_endpoint='login')
return redirect(url_for(fallback_endpoint))
else:
return redirect(url_for('home'))
return redirect(url_for('home_admin'))
else:
app.logger.warning(f"Login failed: username={encrypt_text(username)!r} tenant={current_tenant_id or 'default'} db={current_tenant_db} host={request.host} ip={encrypt_text(request.remote_addr)}")
flash('Ungültige Anmeldedaten', 'error')
@@ -4584,7 +4588,7 @@ def change_password():
# Update the password
if us.update_password(session['username'], new_password):
flash('Ihr Passwort wurde erfolgreich geändert.', 'success')
return redirect(url_for('home'))
return redirect(url_for('home_admin'))
else:
flash('Fehler beim Ändern des Passworts. Bitte versuchen Sie es später erneut.', 'error')
@@ -5178,7 +5182,7 @@ def upload_item():
elif cfg.MODULES.is_enabled('library') and _page_access_allowed(permissions, 'home_library'):
success_redirect_endpoint = 'home_library'
else:
success_redirect_endpoint = 'home'
success_redirect_endpoint = 'home_admin'
# Detect if request is from mobile device
is_mobile = 'Mobile' in request.headers.get('User-Agent', '')
@@ -6384,7 +6388,7 @@ def delete_item(id):
if not current_permissions['actions'].get('can_delete', False):
flash('Ihnen fehlen die nötigen Berechtigungen, um diese Aktion (Löschen) auszuführen.', 'error')
return redirect(url_for('home'))
return redirect(url_for('home_admin'))
if not cfg.MODULES.is_enabled('inventory'):
flash('Bibliotheks-Modul ist deaktiviert.', 'error')
@@ -6448,11 +6452,11 @@ def delete_library_item(id):
if not current_permissions['actions'].get('can_delete', False):
flash('Ihnen fehlen die nötigen Berechtigungen, um diese Aktion (Löschen) auszuführen.', 'error')
return redirect(url_for('home'))
return redirect(url_for('home_admin'))
if not cfg.MODULES.is_enabled('library'):
flash('Bibliotheks-Modul ist deaktiviert.', 'error')
return redirect(url_for('home'))
return redirect(url_for('home_admin'))
# Verify item exists and is a library item
try:
@@ -6522,7 +6526,7 @@ def bulk_delete_items():
if not current_permissions['actions'].get('can_delete', False):
flash('Ihnen fehlen die nötigen Berechtigungen, um diese Aktion (Löschen) auszuführen.', 'error')
return redirect(url_for('home'))
return redirect(url_for('home_admin'))
if not cfg.MODULES.is_enabled('inventory'):
flash('Bibliotheks-Modul ist deaktiviert.', 'error')
@@ -6587,7 +6591,7 @@ def edit_item(id):
if not current_permissions['actions'].get('can_edit', False):
flash('Ihnen fehlen die nötigen Berechtigungen, um diese Aktion (Löschen) auszuführen.', 'error')
return redirect(url_for('home'))
return redirect(url_for('home_admin'))
if not cfg.MODULES.is_enabled('inventory'):
flash('Bibliotheks-Modul ist deaktiviert.', 'error')
@@ -7005,7 +7009,7 @@ def ausleihen(id):
if requested_return_to == 'library' and cfg.MODULES.is_enabled('library'):
redirect_target = 'library_view'
else:
redirect_target = 'home_admin' if us.check_admin(username) else 'home' # check for plausability
redirect_target = 'home_admin'
item = it.get_item(id)
if not item:
@@ -7071,7 +7075,7 @@ def ausleihen(id):
student_user = us.get_user_by_student_card(student_card_id)
if not student_user:
flash('Keine Schülerin/kein Schüler mit dieser Ausweis-ID gefunden.', 'error')
return redirect(url_for('home_admin'))
return redirect(url_for('library'))
effective_borrower = student_user.get('Username') or student_user.get('username') or username
if borrow_duration_days is None:
try:
@@ -7321,7 +7325,7 @@ def zurueckgeben(id):
item = it.get_item(id)
if not item:
flash('Element nicht gefunden', 'error')
return redirect(url_for('home'))
return redirect(url_for('home_admin'))
username = session['username']
@@ -7903,20 +7907,20 @@ def terminplan():
if not cfg.MODULES.is_enabled('terminplan'):
flash('Der Terminplaner ist deaktiviert.', 'info')
return redirect(url_for('home'))
return redirect(url_for('home_admin'))
# Make sure the template exists
template_path = os.path.join(BASE_DIR, 'templates', 'terminplan.html')
if not os.path.exists(template_path):
print(f"Template file not found: {template_path}")
flash('Vorlage nicht gefunden. Bitte kontaktieren Sie den Administrator.', 'error')
return redirect(url_for('home'))
return redirect(url_for('home_admin'))
return render_template('terminplan.html', school_periods=SCHOOL_PERIODS)
except Exception as e:
app.logger.error(f"Error rendering terminplan: {e}")
flash('Ein Fehler ist beim Anzeigen des Kalenders aufgetreten.', 'error')
return redirect(url_for('home'))
return redirect(url_for('home_admin'))
'''-------------------------------------------------------------------------------------------------------------ADMIN ROUTES------------------------------------------------------------------------------------------------------------------'''
@@ -7973,7 +7977,7 @@ def register():
action_permissions=action_permissions,
page_permissions=page_permissions,
)
return redirect(url_for('home'))
return redirect(url_for('home_admin'))
return render_template(
'register.html',
library_module_enabled=cfg.MODULES.is_enabled('library'),
@@ -8620,34 +8624,36 @@ def admin_create_invoice(borrow_id):
flash('Für diese Ausleihe existiert bereits eine Rechnung. Bitte Korrekturbuchung verwenden.', 'warning')
return redirect(url_for('admin_borrowings'))
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
db = client[cfg.MONGODB_DB]
student_cards = db['student_cards']
student_id = borrow_doc.get('AusweisId')
if not student_id:
client.close()
flash('Dieser Ausleihe ist kein Schülerausweis zugeordnet.', 'error')
return redirect(url_for('admin_borrowings'))
borrower = "Unbekannt"
# 2. Mit der korrekten ID in student_cards suchen
card = student_cards.find_one({'_id': ObjectId(student_id)})
if not card:
client.close()
flash('Der zugehörige Schülerausweis wurde in der Datenbank nicht gefunden.', 'error')
return redirect(url_for('admin_borrowings'))
# 1. Prüfen: Gibt es eine AusweisId?
if student_id:
try:
card = student_cards.find_one({'_id': ObjectId(student_id)})
except Exception:
card = student_cards.find_one({'_id': student_id})
if not card:
flash('Der zugehörige Schülerausweis wurde in der Datenbank nicht gefunden.', 'error')
return redirect(url_for('admin_borrowings'))
card = _decrypt_student_card_doc(card)
client.close()
# 3. Sicherer Zugriff mit .get() als zusätzliche Schutzmaßnahme
borrower = card.get('SchülerName', 'Unbekannt')
card = _decrypt_student_card_doc(card)
borrower = card.get('SchülerName', 'Unbekannt')
# 2. Fallback: Wenn keine AusweisId da ist, schauen wir, ob ein Name unter 'User' gespeichert wurde
else:
fallback_user = borrow_doc.get('User')
if fallback_user:
borrower = str(fallback_user)
else:
flash('Dieser Ausleihe ist weder eine AusweisId noch ein Benutzername zugeordnet.', 'error')
return redirect(url_for('admin_borrowings'))
invoice_number = existing_invoice.get('invoice_number') or _build_invoice_number(borrow_doc['_id'], now)
item_name = item_doc.get('Name', '')
item_code = item_doc.get('Code_4', '')
item_code = item_doc.get('Code_4', '')
invoice_data = {
'invoice_number': invoice_number,
@@ -8656,7 +8662,7 @@ def admin_create_invoice(borrow_id):
'damage_reason': damage_reason,
'created_at': now,
'created_by': session.get('username', ''),
'borrower': borrower,
'borrower': decrypt_text(borrower),
'item_id': str(item_doc['_id']),
'item_name': item_name,
'item_code': item_code,
@@ -10436,7 +10442,7 @@ def notifications_view():
except Exception as exc:
app.logger.error(f"Error loading notifications: {exc}")
flash('Fehler beim Laden der Benachrichtigungen.', 'error')
return redirect(url_for('home'))
return redirect(url_for('home_admin'))
finally:
if client:
client.close()
+59 -35
View File
@@ -2309,6 +2309,7 @@ document.addEventListener('DOMContentLoaded', ()=>{
</script>
<div class="admin-content-container">
{% if current_permissions.actions.get('can_edit', False) %}
<!-- Admin conflict warning banner (populated by JS) -->
<div id="conflict-banner" style="display:none; background:#fff3cd; border:1px solid #ffc107; border-left:4px solid #fd7e14; color:#856404; padding:12px 16px; border-radius:4px; margin-bottom:16px; position:relative;">
<strong>⚠ Buchungskonflikte erkannt:</strong>
@@ -2335,6 +2336,8 @@ document.addEventListener('DOMContentLoaded', ()=>{
.catch(() => {});
});
</script>
{% endif %}
<div class="content">
<h1 style="position:relative;">Inventar Objekte
<div class="view-switch-group">
@@ -2344,14 +2347,17 @@ document.addEventListener('DOMContentLoaded', ()=>{
<button id="toggle-favorites-view" class="view-toggle-btn" title="Nur Merkliste anzeigen">
<span id="favorites-view-icon">🔖</span>
</button>
{% if current_permissions.actions.get('can_delete', False) %}
<div class="bulk-delete-inline-wrap">
<button type="button" id="bulk-delete-drawer-toggle" class="view-toggle-btn bulk-delete-drawer-toggle" aria-expanded="false" title="Massenlöschung" onclick="toggleBulkDeleteDrawer()">
<span class="drawer-icon"></span>
</button>
</div>
{% endif %}
</div>
</h1>
<div id="items-indicator" class="items-indicator">Objekte im System: 0</div>
{% if current_permissions.actions.get('can_delete', False) %}
<div id="bulk-delete-drawer" class="bulk-delete-drawer" aria-hidden="true">
<div class="bulk-delete-content">
<strong>Massenlöschung nach Kriterien</strong>
@@ -2364,6 +2370,7 @@ document.addEventListener('DOMContentLoaded', ()=>{
<button type="button" id="bulk-delete-button" class="danger" onclick="deleteSelectedItems()" disabled>Auswahl löschen</button>
</div>
</div>
{% endif %}
<div class="filter-container">
<div class="filter-group">
<div class="filter-header">
@@ -2469,6 +2476,7 @@ document.addEventListener('DOMContentLoaded', ()=>{
</div>
<!-- Add the edit modal form -->
{% if current_permissions.actions.get('can_edit', False) %}
<div id="edit-modal" class="item-modal">
<div class="modal-content">
<span class="close-modal" onclick="closeEditModal()">&times;</span>
@@ -2646,6 +2654,7 @@ document.addEventListener('DOMContentLoaded', ()=>{
</form>
</div>
</div>
{% endif %}
</div>
<!-- Schedule Appointment Modal -->
@@ -2724,7 +2733,6 @@ document.addEventListener('DOMContentLoaded', ()=>{
</div>
</div>
<!-- Initialize template variables before any JavaScript code -->
<script>
// Create a global object to hold server-side template values
window.serverVars = {};
@@ -3239,7 +3247,7 @@ document.addEventListener('DOMContentLoaded', ()=>{
return;
}
fetch('{{ url_for('bulk_delete_items') }}', {
fetch("{{ url_for('bulk_delete_items') }}", {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -3703,6 +3711,7 @@ document.addEventListener('DOMContentLoaded', ()=>{
Für Löschung markieren
</label>
</div>
{% if current_permissions.actions.get('can_borrow', False) %}
${isAvailableForBorrow && !item.BlockedNow ?
`<form method="POST" action="{{ url_for('ausleihen', id='') }}${item._id}">
${isGroupedItem ? `
@@ -3724,12 +3733,21 @@ document.addEventListener('DOMContentLoaded', ()=>{
:
`<button class="ausleihen disabled-button" disabled>${item.BlockedNow ? 'Reserviert' : 'Ausgeliehen'}</button>`
}
{% endif %}
{% if current_permissions.actions.get('can_delete', False) %}
<form method="POST" action="{{ url_for('delete_item', id='') }}${item._id}" style="display:inline;" onsubmit="return confirm('Sind Sie sicher, dass Sie dieses Objekt löschen möchten?')">
<button class="delete-button" type="submit">Löschen</button>
</form>
{% endif %}
{% if current_permissions.actions.get('can_edit', False) %}
<button class="edit-button" onclick="openEditModalForSelectedUnit('${item._id}', 'specific-item-card-${item._id}')">Bearbeiten</button>
{% endif %}
{% if current_permissions.actions.get('can_insert', False) %}
<button class="duplicate-button" onclick="duplicateItem('${item._id}')">Duplizieren</button>
{% endif %}
{% if current_permissions.pages.get('admin_school_settings', False) %}
${canScheduleItem ? `<button class="schedule-button" onclick="openScheduleModal('${item._id}')">Reservieren</button>` : ''}
{% endif %}
</div>
`;
itemsContainer.appendChild(card);
@@ -4506,10 +4524,10 @@ document.addEventListener('DOMContentLoaded', ()=>{
: '<div style="font-size:0.92rem;color:#64748b;">Keine Beschädigungs-Historie vorhanden.</div>';
modalContent.innerHTML = `
<h2>${item.Name}</h2>
<h2>${escapeHtml(item.Name || '')}</h2>
${borrowerInfoHtml}
${appointmentInfoHtml}
<div class="modal-image-container">
${imagesHtml}
${item.Images && item.Images.length > 1 ? `
@@ -4518,66 +4536,67 @@ document.addEventListener('DOMContentLoaded', ()=>{
<div class="image-counter">1/${item.Images.length}</div>
` : ''}
</div>
<div class="modal-details">
<div class="detail-group">
<div class="detail-label">Ort:</div>
<div class="detail-value">${item.Ort || '-'}</div>
<div class="detail-value">${escapeHtml(item.Ort || '-')}</div>
</div>
<div class="detail-group">
<div class="detail-label">Status:</div>
<div class="detail-value ${isBorrowed ? 'status-borrowed' : ''}">
${isBorrowed ? 'Ausgeliehen' : 'Verfügbar'}
</div>
</div>
<div class="detail-group">
<div class="detail-label">Unterrichtsfach:</div>
<div class="detail-value">${filter1Array.join(', ') || '-'}</div>
<div class="detail-value">${escapeHtml(filter1Array.join(', ') || '-')}</div>
</div>
<div class="detail-group">
<div class="detail-label">Jahrgangsstufe:</div>
<div class="detail-value">${filter2Array.join(', ') || '-'}</div>
<div class="detail-value">${escapeHtml(filter2Array.join(', ') || '-')}</div>
</div>
<div class="detail-group">
<div class="detail-label">Schlagwort:</div>
<div class="detail-value">${filter3Array.join(', ') || '-'}</div>
<div class="detail-value">${escapeHtml(filter3Array.join(', ') || '-')}</div>
</div>
<div class="detail-group">
<div class="detail-label">Code:</div>
<div class="detail-value">${item.Code_4 || '-'}</div>
<div class="detail-value">${escapeHtml(item.Code_4 || '-')}</div>
</div>
<div class="detail-group">
<div class="detail-label">Anzahl:</div>
<div class="detail-value">${item.GroupedDisplayCount || 1}</div>
<div class="detail-value">${escapeHtml(String(item.GroupedDisplayCount || 1))}</div>
</div>
${isGroupedItem ? `
<div class="detail-group">
<div class="detail-label">Verfügbar:</div>
<div class="detail-value">${availableGroupedCount}</div>
<div class="detail-value">${escapeHtml(String(availableGroupedCount))}</div>
</div>` : ''}
<div class="detail-group">
<div class="detail-label">Anschaffungsjahr:</div>
<div class="detail-value">${item.Anschaffungsjahr || '-'}</div>
</div>
<div class="detail-group">
<div class="detail-label">Anschaffungskosten:</div>
<div class="detail-value">${item.Anschaffungskosten ? item.Anschaffungskosten + ' €' : '-'}</div>
</div>
<div class="detail-group full-width">
<div class="detail-label">Beschreibung:</div>
<div class="detail-value">${item.Beschreibung || '-'}</div>
<div class="detail-value">${escapeHtml(String(item.Anschaffungsjahr || '-'))}</div>
</div>
<div class="detail-group">
<div class="detail-label">Anschaffungskosten:</div>
<div class="detail-value">${item.Anschaffungskosten ? escapeHtml(String(item.Anschaffungskosten)) + ' €' : '-'}</div>
</div>
<div class="detail-group full-width">
<div class="detail-label">Beschreibung:</div>
<div class="detail-value">${escapeHtml(item.Beschreibung || '-')}</div>
</div>
{% if current_permissions.actions.get('can_view_logs', False) %}
<div class="detail-group full-width" style="margin-top:12px;">
<div class="detail-label" style="font-weight:600; color:#374151;">Beschädigungs-Historie</div>
<div class="detail-value">
@@ -4590,7 +4609,8 @@ document.addEventListener('DOMContentLoaded', ()=>{
</div>
</div>
</div>
{% endif %}
<div class="detail-group full-width" style="margin-top:12px; padding:10px; border:1px solid #e3e3e3; border-radius:8px;">
<div class="detail-label">Verfügbarkeit prüfen:</div>
<div class="detail-value">
@@ -4611,7 +4631,7 @@ document.addEventListener('DOMContentLoaded', ()=>{
</div>
</div>
</div>
<div class="detail-group full-width" style="margin-top:15px;">
<div class="detail-label">Geplante Ausleihen:</div>
<div class="detail-value">
@@ -4635,9 +4655,10 @@ document.addEventListener('DOMContentLoaded', ()=>{
</div>
</div>
</div>
<div class="modal-actions">
${!isBorrowed ?
{% if current_permissions.actions.get('can_borrow', False) %}
`<form method="POST" action="/ausleihen/${item._id}">
${isGroupedItem ? `
<div style="display:flex; gap:8px; flex-wrap:wrap; margin-bottom:8px; align-items:center;">
@@ -4651,6 +4672,9 @@ document.addEventListener('DOMContentLoaded', ()=>{
</div>` : ''}
<button class="ausleihen" type="submit">Ausleihen</button>
</form>`
{% else %}
`<button class="ausleihen disabled-button" disabled title="Keine Berechtigung">Ausleihen nicht möglich</button>`
{% endif %}
: (!isGroupedItem && item.User === currentUsername) ?
`<form method="POST" action="/zurueckgeben/${item._id}">
<button class="ausleihen" type="submit">Zurückgeben</button>
@@ -4659,7 +4683,7 @@ document.addEventListener('DOMContentLoaded', ()=>{
}
<button class="edit-button" onclick="openEditModalForSelectedUnit('${item._id}', 'specific-item-modal-${item._id}')">Bearbeiten</button>
<button class="duplicate-button" onclick="duplicateItem('${item._id}')">Duplizieren</button>
${damageReports.length > 0 ? `<button class="damage-button" onclick="markDamageAsRepaired('${item._id}')">Repariert</button>` : `<button class="damage-button" onclick="registerDamage('${item._id}')">Schaden melden</button>`}
${damageReports && damageReports.length > 0 ? `<button class="damage-button" onclick="markDamageAsRepaired('${item._id}')">Repariert</button>` : `<button class="damage-button" onclick="registerDamage('${item._id}')">Schaden melden</button>`}
${canScheduleItem ? `<button class="schedule-button" onclick="openScheduleModal('${item._id}')">Reservieren</button>` : ''}
<form method="POST" action="/delete_item/${item._id}" style="display:inline;" onsubmit="return confirm('Sind Sie sicher?')">
<button class="delete-button" type="submit">Löschen</button>