From 7ab8f841d0b7cad6a2885d573d6bd82ee7a41443 Mon Sep 17 00:00:00 2001 From: Aiirondev Date: Sun, 31 May 2026 16:13:11 +0200 Subject: [PATCH] feat: Enhance tenant management and appointment deletion functionality --- Web/modules/database/termine.py | 6 ++- Web/modules/terminplaner/backend_server.py | 35 ++++++++++++++--- Web/modules/terminplaner/blueprint.py | 44 ++++++++++++++++++++++ Web/templates/termin_client.html | 4 +- Web/templates/terminplaner.html | 11 ++++-- Web/tenant.py | 18 +++++++++ 6 files changed, 105 insertions(+), 13 deletions(-) diff --git a/Web/modules/database/termine.py b/Web/modules/database/termine.py index 01024ac..61e61f3 100644 --- a/Web/modules/database/termine.py +++ b/Web/modules/database/termine.py @@ -166,7 +166,7 @@ def remove(id): result = items.delete_one({'_id': ObjectId(id)}) client.close() - return result.modified_count > 0 + return result.deleted_count > 0 except Exception as e: print(f"Error removing appointment: {e}") return False @@ -179,6 +179,7 @@ def remove_done(): items = db['appointments'] today = datetime.date.today().strftime('%Y-%m-%d') + removed_count = 0 cursor = items.find( _active_record_query( @@ -191,9 +192,10 @@ def remove_done(): for item in cursor: item['_id'] = str(item.get('_id')) result = items.delete_one({'_id': ObjectId(item['_id'])}) + removed_count += result.deleted_count client.close() - return result.modified_count > 0 + return removed_count > 0 except Exception as e: print(f"Error removing appointment: {e}") return False diff --git a/Web/modules/terminplaner/backend_server.py b/Web/modules/terminplaner/backend_server.py index 84e0857..120f4c8 100644 --- a/Web/modules/terminplaner/backend_server.py +++ b/Web/modules/terminplaner/backend_server.py @@ -24,6 +24,18 @@ def _resolve_public_base_url() -> str: return f"https://{subdomain}.invario.eu" if subdomain else "https://invario.eu" +def _current_tenant_id() -> str: + tenant_context = get_tenant_context() + if tenant_context and getattr(tenant_context, 'tenant_id', None): + return str(tenant_context.tenant_id) + if has_request_context(): + try: + return str(request.args.get('tenant', '') or request.args.get('tenant_id', '') or '').strip() + except Exception: + return '' + return '' + + def _normalize_time_span(time_span): if isinstance(time_span, list): return [str(entry).strip() for entry in time_span if str(entry).strip()] @@ -70,11 +82,14 @@ def build_calendar_ics(appointment_id: str) -> str | None: time_span = item.get('time_span', []) or [] creator = item.get('user', 'Terminplaner') note = item.get('note', '') or '' + tenant_id = _current_tenant_id() try: - link = url_for('terminplaner.client', appointment_id=str(appointment_id), _external=True) + link = url_for('terminplaner.client', appointment_id=str(appointment_id), tenant=tenant_id or None, _external=True) except Exception: host = _resolve_public_base_url() link = host + "/terminplaner/client/" + str(appointment_id) + if tenant_id: + link += f"?tenant={tenant_id}" try: start_date = datetime.datetime.strptime(str(date_start), '%Y-%m-%d').date() @@ -133,21 +148,26 @@ def new(date_start: str, date_end: str, time_span: list, slots: int, slot_lenght normalized_mail = _normalize_mail_list(mail) id = termin.add(date_start, date_end, normalized_time_span, slots, slot_lenght, user, normalized_mail, note, calendar_enabled=calendar_enabled) id_str = str(id) + tenant_id = _current_tenant_id() try: - link = url_for('terminplaner.client', appointment_id=id_str, _external=True) + link = url_for('terminplaner.client', appointment_id=id_str, tenant=tenant_id or None, _external=True) except Exception: host = _resolve_public_base_url() link = host + "/terminplaner/client/" + id_str + if tenant_id: + link += f"?tenant={tenant_id}" subject = f"Terminanfrage von {user}" note_link = note + f"Bitte klicken sie auf den folgenden Link um einen Termin zu vereinbaren: {link}" calendar_link = None if calendar_enabled: try: - calendar_link = url_for('terminplaner.calendar_export', appointment_id=id_str, _external=True) + calendar_link = url_for('terminplaner.calendar_export', appointment_id=id_str, tenant=tenant_id or None, _external=True) except Exception: host = _resolve_public_base_url() calendar_link = host + "/terminplaner/calendar/" + id_str + ".ics" + if tenant_id: + calendar_link += f"?tenant={tenant_id}" email_body = note_link if calendar_link: @@ -324,6 +344,7 @@ def get_user_upcoming_events(user: str, limit: int = 25) -> list[dict]: appointments = termin.get_upcoming_for_user(user_name, limit=limit) host = _resolve_public_base_url() + tenant_id = _current_tenant_id() result = [] for item in appointments: @@ -332,14 +353,18 @@ def get_user_upcoming_events(user: str, limit: int = 25) -> list[dict]: continue try: - link = url_for('terminplaner.client', appointment_id=appointment_id, _external=True) + link = url_for('terminplaner.client', appointment_id=appointment_id, tenant=tenant_id or None, _external=True) except Exception: link = host + '/terminplaner/client/' + appointment_id + if tenant_id: + link += f'?tenant={tenant_id}' try: - calendar_link = url_for('terminplaner.calendar_export', appointment_id=appointment_id, _external=True) + calendar_link = url_for('terminplaner.calendar_export', appointment_id=appointment_id, tenant=tenant_id or None, _external=True) except Exception: calendar_link = host + '/terminplaner/calendar/' + appointment_id + '.ics' + if tenant_id: + calendar_link += f'?tenant={tenant_id}' slots_total = int(item.get('slots', 0) or 0) slots_booked = item.get('slots_booked', []) or [] diff --git a/Web/modules/terminplaner/blueprint.py b/Web/modules/terminplaner/blueprint.py index 8c53ed5..68237a8 100644 --- a/Web/modules/terminplaner/blueprint.py +++ b/Web/modules/terminplaner/blueprint.py @@ -2,6 +2,8 @@ from flask import Blueprint, render_template, request, session, url_for, redirec from flask import Response import Web.modules.terminplaner.backend_server as appointment_service import Web.modules.database.settings as cfg +import Web.modules.database.termine as termin +import Web.modules.database.user as us # Create a blueprint instance appoint_bp = Blueprint('terminplaner', __name__) @@ -21,6 +23,17 @@ def _appointment_not_found_response(): error_message='Der Termin wurde nicht gefunden.', ), 404 + +def _current_tenant_id(): + try: + from Web.tenant import get_tenant_context + ctx = get_tenant_context() + if ctx and getattr(ctx, 'tenant_id', None): + return str(ctx.tenant_id) + except Exception: + pass + return str(session.get('tenant_id', '') or '').strip() + @appoint_bp.route('/client/', methods=['POST', 'GET']) def client(appointment_id): """ @@ -57,8 +70,37 @@ def client(appointment_id): appointment_id=appointment_id, available=available, current_user=session.get('username', ''), + tenant_id=_current_tenant_id(), ) + +@appoint_bp.route('/delete/', methods=['POST']) +def delete_appointment(appointment_id): + guard = _require_module_enabled() + if guard: + return guard + + if 'username' not in session: + flash('Bitte mit einem Konto anmelden.', 'error') + return redirect(url_for('login')) + + appointment = termin.get_item(appointment_id) + if not appointment: + return _appointment_not_found_response() + + current_user = str(session.get('username', '')).strip() + appointment_user = str(appointment.get('user', '')).strip() + if not us.check_admin(current_user) and appointment_user != current_user: + flash('Sie dürfen diesen Termin nicht löschen.', 'error') + return redirect(url_for('terminplaner.main', tenant=_current_tenant_id() or None)) + + if termin.remove(appointment_id): + flash('Der Terminplan wurde gelöscht.', 'success') + else: + flash('Der Terminplan konnte nicht gelöscht werden.', 'error') + + return redirect(url_for('terminplaner.main', tenant=_current_tenant_id() or None)) + @appoint_bp.route('/configure', methods=['GET', 'POST']) def configure(): """ @@ -134,10 +176,12 @@ def main(): current_user = session.get('username', '') upcoming_events = appointment_service.get_user_upcoming_events(current_user) if current_user else [] + tenant_id = _current_tenant_id() return render_template( 'terminplaner.html', school_periods=cfg.SCHOOL_PERIODS, current_user=current_user, upcoming_events=upcoming_events, + tenant_id=tenant_id, ) \ No newline at end of file diff --git a/Web/templates/termin_client.html b/Web/templates/termin_client.html index 46ed419..73b81ad 100644 --- a/Web/templates/termin_client.html +++ b/Web/templates/termin_client.html @@ -44,7 +44,7 @@

Buchung absenden

-
+
@@ -56,7 +56,7 @@
- Zur Übersicht + Zur Übersicht
diff --git a/Web/templates/terminplaner.html b/Web/templates/terminplaner.html index 7420ed1..16da811 100644 --- a/Web/templates/terminplaner.html +++ b/Web/templates/terminplaner.html @@ -14,8 +14,8 @@

Erstellen Sie neue Terminreihen, teilen Sie Buchungslinks und öffnen Sie den Kalender für bestehende Reservierungen.

@@ -27,7 +27,7 @@
🗓️

Kalender

Sehen Sie vorhandene Termine, ihre Auslastung und die aktuellen Reservierungen im Kalender.

- Zum Kalender + Zum Kalender @@ -37,7 +37,7 @@
✍️

Neue Planung

Erstellen Sie einen neuen Terminplan und verschicken Sie den Buchungslink an Ihre Zielgruppe.

- Konfiguration öffnen + Konfiguration öffnen @@ -87,6 +87,9 @@ {% if event.calendar_link %} .ics {% endif %} +
+ +
diff --git a/Web/tenant.py b/Web/tenant.py index e75504e..da2cd0f 100644 --- a/Web/tenant.py +++ b/Web/tenant.py @@ -473,6 +473,20 @@ class TenantContext: if not has_request_context(): return None + # Query parameters are useful for public links that must open a specific tenant + # even when the host/subdomain cannot be mapped reliably. + tenant_from_query = ( + request.args.get('tenant', '').strip() + or request.args.get('tenant_id', '').strip() + or request.args.get('tenantId', '').strip() + ) + if tenant_from_query: + matched_tenant = _find_registered_tenant_id(tenant_from_query) or tenant_from_query + self.tenant_id = matched_tenant + self.config = get_tenant_config(matched_tenant) + session['tenant_id'] = matched_tenant + return self._get_db_name(matched_tenant) + # Priority 1: X-Tenant-ID header (for testing/internal APIs) tenant_from_header = request.headers.get('X-Tenant-ID', '').strip() if tenant_from_header: @@ -531,6 +545,10 @@ class TenantContext: potential_subdomain = parts[0] if potential_subdomain not in ('www', 'api', 'admin', 'app', 'mail'): matched_tenant = _find_registered_tenant_id(potential_subdomain) + if not matched_tenant and potential_subdomain.startswith('school'): + matched_tenant = _find_registered_tenant_id('schule' + potential_subdomain[len('school'):]) + elif not matched_tenant and potential_subdomain.startswith('schule'): + matched_tenant = _find_registered_tenant_id('school' + potential_subdomain[len('schule'):]) if matched_tenant: self.subdomain = potential_subdomain self.tenant_id = matched_tenant