diff --git a/GPU25_26_mit_Herbstferien.TXT b/GPU25_26_mit_Herbstferien.TXT new file mode 100644 index 0000000..2138b05 --- /dev/null +++ b/GPU25_26_mit_Herbstferien.TXT @@ -0,0 +1,18 @@ +"2.11.","Allerseelen",20251102,20251102, +"Ferien2","Weihnachtsferien",20251224,20260106, +"Ferien3","Semesterferien",20260216,20260222, +"Ferien4_2","Osterferien",20260328,20260406, +"Ferien4","Hl. Florian",20260504,20260504, +"26.10.","Nationalfeiertag",20251026,20251026,"F" +"27.10.","Herbstferien",20251027,20251027,"F" +"28.10.","Herbstferien",20251028,20251028,"F" +"29.10.","Herbstferien",20251029,20251029,"F" +"30.10.","Herbstferien",20251030,20251030,"F" +"31.10.","Herbstferien",20251031,20251031,"F" +"1.11.","Allerheiligen",20251101,20251101,"F" +"8.12.","Mari Empfngnis",20251208,20251208,"F" +"1.5.","Staatsfeiertag",20260501,20260501,"F" +"14.5.","Christi Himmelfahrt",20260514,20260514,"F" +"24.5.","Pfingstsonntag",20260524,20260524,"F" +"25.5.","Pfingstmontag",20260525,20260525,"F" +"4.6.","Fronleichnam",20260604,20260604,"F" diff --git a/dashboard/public/program-info.json b/dashboard/public/program-info.json index e4eebb6..3fda5e8 100644 --- a/dashboard/public/program-info.json +++ b/dashboard/public/program-info.json @@ -1,6 +1,6 @@ { "appName": "Infoscreen-Management", - "version": "2025.1.0-alpha.5", + "version": "2025.1.0-alpha.6", "copyright": "© 2025 Third-Age-Applications", "supportContact": "support@third-age-applications.com", "description": "Eine zentrale Verwaltungsoberfläche für digitale Informationsbildschirme.", @@ -26,10 +26,22 @@ ] }, "buildInfo": { - "buildDate": "2025-08-30T12:00:00Z", - "commitId": "a1b2c3d4e5f6" + "buildDate": "2025-09-20T11:00:00Z", + "commitId": "8d1df7199cb7" }, "changelog": [ + { + "version": "2025.1.0-alpha.6", + "date": "2025-09-20", + "changes": [ + "🗓️ NEU: Akademische Perioden System - Unterstützung für Schuljahre, Semester und Trimester", + "🏗️ DATENBANK: Neue 'academic_periods' Tabelle für zeitbasierte Organisation", + "🔗 ERWEITERT: Events und Medien können jetzt optional einer akademischen Periode zugeordnet werden", + "📊 ARCHITEKTUR: Vollständig rückwärtskompatible Implementierung für schrittweise Einführung", + "🎯 BILDUNG: Fokus auf Schulumgebung mit Erweiterbarkeit für Hochschulen", + "⚙️ TOOLS: Automatische Erstellung von Standard-Schuljahren für österreichische Schulen" + ] + }, { "version": "2025.1.0-alpha.5", "date": "2025-09-14", diff --git a/dashboard/src/apiHolidays.ts b/dashboard/src/apiHolidays.ts new file mode 100644 index 0000000..0298d0f --- /dev/null +++ b/dashboard/src/apiHolidays.ts @@ -0,0 +1,26 @@ +export type Holiday = { + id: number; + name: string; + start_date: string; + end_date: string; + region?: string | null; + source_file_name?: string | null; + imported_at?: string | null; +}; + +export async function listHolidays(region?: string) { + const url = region ? `/api/holidays?region=${encodeURIComponent(region)}` : '/api/holidays'; + const res = await fetch(url); + const data = await res.json(); + if (!res.ok || data.error) throw new Error(data.error || 'Fehler beim Laden der Ferien'); + return data as { holidays: Holiday[] }; +} + +export async function uploadHolidaysCsv(file: File) { + const form = new FormData(); + form.append('file', file); + const res = await fetch('/api/holidays/upload', { method: 'POST', body: form }); + const data = await res.json(); + if (!res.ok || data.error) throw new Error(data.error || 'Fehler beim Import der Ferien'); + return data as { success: boolean; inserted: number; updated: number }; +} diff --git a/dashboard/src/appointments.tsx b/dashboard/src/appointments.tsx index 855a69d..9ae0b7b 100644 --- a/dashboard/src/appointments.tsx +++ b/dashboard/src/appointments.tsx @@ -144,7 +144,7 @@ const Appointments: React.FC = () => { const [modalInitialData, setModalInitialData] = useState({}); const [schedulerKey, setSchedulerKey] = useState(0); const [editMode, setEditMode] = useState(false); // NEU: Editiermodus - const [showInactive, setShowInactive] = React.useState(false); + const [showInactive, setShowInactive] = React.useState(true); // Gruppen laden useEffect(() => { @@ -413,7 +413,7 @@ const Appointments: React.FC = () => { } // Events nach Löschen neu laden if (selectedGroupId) { - fetchEvents(selectedGroupId) + fetchEvents(selectedGroupId, showInactive) .then((data: RawEvent[]) => { const mapped: Event[] = data.map((e: RawEvent) => ({ Id: e.Id, diff --git a/dashboard/src/einstellungen.tsx b/dashboard/src/einstellungen.tsx index c8ffcec..dc0461d 100644 --- a/dashboard/src/einstellungen.tsx +++ b/dashboard/src/einstellungen.tsx @@ -1,8 +1,83 @@ import React from 'react'; -const Einstellungen: React.FC = () => ( -
-

Einstellungen

-

Willkommen im Infoscreen-Management Einstellungen.

-
-); +import { listHolidays, uploadHolidaysCsv, type Holiday } from './apiHolidays'; + +const Einstellungen: React.FC = () => { + const [file, setFile] = React.useState(null); + const [busy, setBusy] = React.useState(false); + const [message, setMessage] = React.useState(null); + const [holidays, setHolidays] = React.useState([]); + + const refresh = React.useCallback(async () => { + try { + const data = await listHolidays(); + setHolidays(data.holidays); + } catch (e) { + const msg = e instanceof Error ? e.message : 'Fehler beim Laden der Ferien'; + setMessage(msg); + } + }, []); + + React.useEffect(() => { + refresh(); + }, [refresh]); + + const onUpload = async () => { + if (!file) return; + setBusy(true); + setMessage(null); + try { + const res = await uploadHolidaysCsv(file); + setMessage(`Import erfolgreich: ${res.inserted} neu, ${res.updated} aktualisiert.`); + await refresh(); + } catch (e) { + const msg = e instanceof Error ? e.message : 'Fehler beim Import.'; + setMessage(msg); + } finally { + setBusy(false); + } + }; + + return ( +
+

Einstellungen

+
+
+

Schulferien importieren

+

+ Laden Sie eine CSV-Datei mit den Spalten: name, start_date,{' '} + end_date, optional region. +

+
+ setFile(e.target.files?.[0] ?? null)} + /> + +
+ {message &&
{message}
} +
+ +
+

Importierte Ferien

+ {holidays.length === 0 ? ( +
Keine Einträge vorhanden.
+ ) : ( +
    + {holidays.slice(0, 20).map(h => ( +
  • + {h.name}: {h.start_date} – {h.end_date} + {h.region ? ` (${h.region})` : ''} +
  • + ))} +
+ )} +
+
+
+ ); +}; + export default Einstellungen; diff --git a/models/models.py b/models/models.py index 55011ae..3f14e17 100644 --- a/models/models.py +++ b/models/models.py @@ -1,5 +1,5 @@ from sqlalchemy import ( - Column, Integer, String, Enum, TIMESTAMP, func, Boolean, ForeignKey, Float, Text, Index, DateTime + Column, Integer, String, Enum, TIMESTAMP, func, Boolean, ForeignKey, Float, Text, Index, DateTime, Date, UniqueConstraint ) from sqlalchemy.orm import declarative_base, relationship import enum @@ -14,6 +14,12 @@ class UserRole(enum.Enum): superadmin = "superadmin" +class AcademicPeriodType(enum.Enum): + schuljahr = "schuljahr" + semester = "semester" + trimester = "trimester" + + class User(Base): __tablename__ = 'users' id = Column(Integer, primary_key=True, autoincrement=True) @@ -27,6 +33,42 @@ class User(Base): ), onupdate=func.current_timestamp()) +class AcademicPeriod(Base): + __tablename__ = 'academic_periods' + id = Column(Integer, primary_key=True, autoincrement=True) + name = Column(String(100), nullable=False) # "Schuljahr 2024/25" + display_name = Column(String(50), nullable=True) # "SJ 24/25" (kurz) + start_date = Column(Date, nullable=False, index=True) + end_date = Column(Date, nullable=False, index=True) + period_type = Column(Enum(AcademicPeriodType), + nullable=False, default=AcademicPeriodType.schuljahr) + # nur eine aktive Periode zur Zeit + is_active = Column(Boolean, default=False, nullable=False) + created_at = Column(TIMESTAMP(timezone=True), + server_default=func.current_timestamp()) + updated_at = Column(TIMESTAMP(timezone=True), server_default=func.current_timestamp( + ), onupdate=func.current_timestamp()) + + # Constraint: nur eine aktive Periode zur Zeit + __table_args__ = ( + Index('ix_academic_periods_active', 'is_active'), + UniqueConstraint('name', name='uq_academic_periods_name'), + ) + + def to_dict(self): + return { + "id": self.id, + "name": self.name, + "display_name": self.display_name, + "start_date": self.start_date.isoformat() if self.start_date else None, + "end_date": self.end_date.isoformat() if self.end_date else None, + "period_type": self.period_type.value if self.period_type else None, + "is_active": self.is_active, + "created_at": self.created_at.isoformat() if self.created_at else None, + "updated_at": self.updated_at.isoformat() if self.updated_at else None, + } + + class ClientGroup(Base): __tablename__ = 'client_groups' id = Column(Integer, primary_key=True, autoincrement=True) @@ -103,6 +145,9 @@ class Event(Base): id = Column(Integer, primary_key=True, autoincrement=True) group_id = Column(Integer, ForeignKey( 'client_groups.id'), nullable=False, index=True) + academic_period_id = Column(Integer, ForeignKey( + # Optional für Rückwärtskompatibilität + 'academic_periods.id'), nullable=True, index=True) title = Column(String(100), nullable=False) description = Column(Text, nullable=True) start = Column(TIMESTAMP(timezone=True), nullable=False, index=True) @@ -122,13 +167,18 @@ class Event(Base): updated_by = Column(Integer, ForeignKey('users.id'), nullable=True) is_active = Column(Boolean, default=True, nullable=False) - # Add relationship to EventMedia + # Add relationships + academic_period = relationship( + "AcademicPeriod", foreign_keys=[academic_period_id]) event_media = relationship("EventMedia", foreign_keys=[event_media_id]) class EventMedia(Base): __tablename__ = 'event_media' id = Column(Integer, primary_key=True, autoincrement=True) + academic_period_id = Column(Integer, ForeignKey( + # Optional für bessere Organisation + 'academic_periods.id'), nullable=True, index=True) media_type = Column(Enum(MediaType), nullable=False) url = Column(String(255), nullable=False) file_path = Column(String(255), nullable=True) @@ -136,11 +186,44 @@ class EventMedia(Base): uploaded_at = Column(TIMESTAMP, nullable=False, default=lambda: datetime.now(timezone.utc)) + # Add relationship + academic_period = relationship( + "AcademicPeriod", foreign_keys=[academic_period_id]) + def to_dict(self): return { "id": self.id, + "academic_period_id": self.academic_period_id, "media_type": self.media_type.value if self.media_type else None, "url": self.url, "file_path": self.file_path, "message_content": self.message_content, } + + +class SchoolHoliday(Base): + __tablename__ = 'school_holidays' + id = Column(Integer, primary_key=True, autoincrement=True) + name = Column(String(150), nullable=False) + start_date = Column(Date, nullable=False, index=True) + end_date = Column(Date, nullable=False, index=True) + region = Column(String(100), nullable=True, index=True) + source_file_name = Column(String(255), nullable=True) + imported_at = Column(TIMESTAMP(timezone=True), + server_default=func.current_timestamp()) + + __table_args__ = ( + UniqueConstraint('name', 'start_date', 'end_date', + 'region', name='uq_school_holidays_unique'), + ) + + def to_dict(self): + return { + "id": self.id, + "name": self.name, + "start_date": self.start_date.isoformat() if self.start_date else None, + "end_date": self.end_date.isoformat() if self.end_date else None, + "region": self.region, + "source_file_name": self.source_file_name, + "imported_at": self.imported_at.isoformat() if self.imported_at else None, + } diff --git a/server/alembic/versions/71ba7ab08d84_merge_heads_after_holidays_table.py b/server/alembic/versions/71ba7ab08d84_merge_heads_after_holidays_table.py new file mode 100644 index 0000000..008240c --- /dev/null +++ b/server/alembic/versions/71ba7ab08d84_merge_heads_after_holidays_table.py @@ -0,0 +1,28 @@ +"""merge heads after holidays table + +Revision ID: 71ba7ab08d84 +Revises: 216402147826, 9b7a1f2a4d2b +Create Date: 2025-09-18 19:04:12.755422 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = '71ba7ab08d84' +down_revision: Union[str, None] = ('216402147826', '9b7a1f2a4d2b') +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema.""" + pass + + +def downgrade() -> None: + """Downgrade schema.""" + pass diff --git a/server/alembic/versions/8d1df7199cb7_add_academic_periods_system.py b/server/alembic/versions/8d1df7199cb7_add_academic_periods_system.py new file mode 100644 index 0000000..d8ddc6d --- /dev/null +++ b/server/alembic/versions/8d1df7199cb7_add_academic_periods_system.py @@ -0,0 +1,62 @@ +"""add academic periods system + +Revision ID: 8d1df7199cb7 +Revises: 71ba7ab08d84 +Create Date: 2025-09-20 11:07:08.059374 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = '8d1df7199cb7' +down_revision: Union[str, None] = '71ba7ab08d84' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('academic_periods', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('name', sa.String(length=100), nullable=False), + sa.Column('display_name', sa.String(length=50), nullable=True), + sa.Column('start_date', sa.Date(), nullable=False), + sa.Column('end_date', sa.Date(), nullable=False), + sa.Column('period_type', sa.Enum('schuljahr', 'semester', 'trimester', name='academicperiodtype'), nullable=False), + sa.Column('is_active', sa.Boolean(), nullable=False), + sa.Column('created_at', sa.TIMESTAMP(timezone=True), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True), + sa.Column('updated_at', sa.TIMESTAMP(timezone=True), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('name', name='uq_academic_periods_name') + ) + op.create_index('ix_academic_periods_active', 'academic_periods', ['is_active'], unique=False) + op.create_index(op.f('ix_academic_periods_end_date'), 'academic_periods', ['end_date'], unique=False) + op.create_index(op.f('ix_academic_periods_start_date'), 'academic_periods', ['start_date'], unique=False) + op.add_column('event_media', sa.Column('academic_period_id', sa.Integer(), nullable=True)) + op.create_index(op.f('ix_event_media_academic_period_id'), 'event_media', ['academic_period_id'], unique=False) + op.create_foreign_key(None, 'event_media', 'academic_periods', ['academic_period_id'], ['id']) + op.add_column('events', sa.Column('academic_period_id', sa.Integer(), nullable=True)) + op.create_index(op.f('ix_events_academic_period_id'), 'events', ['academic_period_id'], unique=False) + op.create_foreign_key(None, 'events', 'academic_periods', ['academic_period_id'], ['id']) + # ### end Alembic commands ### + + +def downgrade() -> None: + """Downgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint(None, 'events', type_='foreignkey') + op.drop_index(op.f('ix_events_academic_period_id'), table_name='events') + op.drop_column('events', 'academic_period_id') + op.drop_constraint(None, 'event_media', type_='foreignkey') + op.drop_index(op.f('ix_event_media_academic_period_id'), table_name='event_media') + op.drop_column('event_media', 'academic_period_id') + op.drop_index(op.f('ix_academic_periods_start_date'), table_name='academic_periods') + op.drop_index(op.f('ix_academic_periods_end_date'), table_name='academic_periods') + op.drop_index('ix_academic_periods_active', table_name='academic_periods') + op.drop_table('academic_periods') + # ### end Alembic commands ### diff --git a/server/alembic/versions/9b7a1f2a4d2b_add_school_holidays_table.py b/server/alembic/versions/9b7a1f2a4d2b_add_school_holidays_table.py new file mode 100644 index 0000000..2f8e000 --- /dev/null +++ b/server/alembic/versions/9b7a1f2a4d2b_add_school_holidays_table.py @@ -0,0 +1,47 @@ +"""add school holidays table + +Revision ID: 9b7a1f2a4d2b +Revises: e6eaede720aa +Create Date: 2025-09-18 00:00:00.000000 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '9b7a1f2a4d2b' +down_revision = 'e6eaede720aa' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.create_table( + 'school_holidays', + sa.Column('id', sa.Integer(), primary_key=True, autoincrement=True), + sa.Column('name', sa.String(length=150), nullable=False), + sa.Column('start_date', sa.Date(), nullable=False), + sa.Column('end_date', sa.Date(), nullable=False), + sa.Column('region', sa.String(length=100), nullable=True), + sa.Column('source_file_name', sa.String(length=255), nullable=True), + sa.Column('imported_at', sa.TIMESTAMP(timezone=True), + server_default=sa.text('CURRENT_TIMESTAMP')), + ) + op.create_index('ix_school_holidays_start_date', + 'school_holidays', ['start_date']) + op.create_index('ix_school_holidays_end_date', + 'school_holidays', ['end_date']) + op.create_index('ix_school_holidays_region', 'school_holidays', ['region']) + op.create_unique_constraint('uq_school_holidays_unique', 'school_holidays', [ + 'name', 'start_date', 'end_date', 'region']) + + +def downgrade() -> None: + op.drop_constraint('uq_school_holidays_unique', + 'school_holidays', type_='unique') + op.drop_index('ix_school_holidays_region', table_name='school_holidays') + op.drop_index('ix_school_holidays_end_date', table_name='school_holidays') + op.drop_index('ix_school_holidays_start_date', + table_name='school_holidays') + op.drop_table('school_holidays') diff --git a/server/init_academic_periods.py b/server/init_academic_periods.py new file mode 100644 index 0000000..3afa1a8 --- /dev/null +++ b/server/init_academic_periods.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 +""" +Erstellt Standard-Schuljahre für österreichische Schulen +Führe dieses Skript nach der Migration aus, um Standard-Perioden zu erstellen. +""" + +from datetime import date +from models.models import AcademicPeriod, AcademicPeriodType +from server.database import Session +import sys +sys.path.append('/workspace') + + +def create_default_academic_periods(): + """Erstellt Standard-Schuljahre für österreichische Schulen""" + session = Session() + + try: + # Prüfe ob bereits Perioden existieren + existing = session.query(AcademicPeriod).first() + if existing: + print("Academic periods already exist. Skipping creation.") + return + + # Standard Schuljahre erstellen + periods = [ + { + 'name': 'Schuljahr 2024/25', + 'display_name': 'SJ 24/25', + 'start_date': date(2024, 9, 2), + 'end_date': date(2025, 7, 4), + 'period_type': AcademicPeriodType.schuljahr, + 'is_active': True # Aktuelles Schuljahr + }, + { + 'name': 'Schuljahr 2025/26', + 'display_name': 'SJ 25/26', + 'start_date': date(2025, 9, 1), + 'end_date': date(2026, 7, 3), + 'period_type': AcademicPeriodType.schuljahr, + 'is_active': False + }, + { + 'name': 'Schuljahr 2026/27', + 'display_name': 'SJ 26/27', + 'start_date': date(2026, 9, 7), + 'end_date': date(2027, 7, 2), + 'period_type': AcademicPeriodType.schuljahr, + 'is_active': False + } + ] + + for period_data in periods: + period = AcademicPeriod(**period_data) + session.add(period) + + session.commit() + print(f"Successfully created {len(periods)} academic periods") + + # Zeige erstellte Perioden + for period in session.query(AcademicPeriod).all(): + status = "AKTIV" if period.is_active else "inaktiv" + print( + f" - {period.name} ({period.start_date} - {period.end_date}) [{status}]") + + except Exception as e: + session.rollback() + print(f"Error creating academic periods: {e}") + finally: + session.close() + + +if __name__ == "__main__": + create_default_academic_periods() diff --git a/server/routes/holidays.py b/server/routes/holidays.py new file mode 100644 index 0000000..e0b1b7d --- /dev/null +++ b/server/routes/holidays.py @@ -0,0 +1,109 @@ +from flask import Blueprint, request, jsonify +from server.database import Session +from models.models import SchoolHoliday +from datetime import datetime +import csv +import io + +holidays_bp = Blueprint("holidays", __name__, url_prefix="/api/holidays") + + +@holidays_bp.route("", methods=["GET"]) +def list_holidays(): + session = Session() + region = request.args.get("region") + q = session.query(SchoolHoliday) + if region: + q = q.filter(SchoolHoliday.region == region) + rows = q.order_by(SchoolHoliday.start_date.asc()).all() + data = [r.to_dict() for r in rows] + session.close() + return jsonify({"holidays": data}) + + +@holidays_bp.route("/upload", methods=["POST"]) +def upload_holidays(): + """ + Accepts a CSV file upload (multipart/form-data) with columns like: + name,start_date,end_date,region + Dates can be in ISO (YYYY-MM-DD) or common European format (DD.MM.YYYY). + """ + if "file" not in request.files: + return jsonify({"error": "No file part"}), 400 + file = request.files["file"] + if file.filename == "": + return jsonify({"error": "No selected file"}), 400 + + try: + content = file.read().decode("utf-8", errors="ignore") + # Try to auto-detect delimiter; default ',' + sniffer = csv.Sniffer() + dialect = None + try: + dialect = sniffer.sniff(content[:1024]) + except Exception: + pass + reader = csv.DictReader(io.StringIO( + content), dialect=dialect) if dialect else csv.DictReader(io.StringIO(content)) + + required = {"name", "start_date", "end_date"} + if not required.issubset(set(h.lower() for h in reader.fieldnames or [])): + return jsonify({"error": "CSV must contain headers: name, start_date, end_date"}), 400 + + def parse_date(s: str): + s = (s or "").strip() + if not s: + return None + # Try ISO first + for fmt in ("%Y-%m-%d", "%d.%m.%Y", "%Y/%m/%d"): + try: + return datetime.strptime(s, fmt).date() + except ValueError: + continue + raise ValueError(f"Unsupported date format: {s}") + + session = Session() + inserted = 0 + updated = 0 + for row in reader: + # Normalize headers to lower-case keys + norm = {k.lower(): (v or "").strip() for k, v in row.items()} + name = norm.get("name") + start_date = parse_date(norm.get("start_date")) + end_date = parse_date(norm.get("end_date")) + region = norm.get("region") or None + if not name or not start_date or not end_date: + continue + + existing = ( + session.query(SchoolHoliday) + .filter( + SchoolHoliday.name == name, + SchoolHoliday.start_date == start_date, + SchoolHoliday.end_date == end_date, + SchoolHoliday.region.is_( + region) if region is None else SchoolHoliday.region == region, + ) + .first() + ) + + if existing: + # Optionally update region or source_file_name + existing.region = region + existing.source_file_name = file.filename + updated += 1 + else: + session.add(SchoolHoliday( + name=name, + start_date=start_date, + end_date=end_date, + region=region, + source_file_name=file.filename, + )) + inserted += 1 + + session.commit() + session.close() + return jsonify({"success": True, "inserted": inserted, "updated": updated}) + except Exception as e: + return jsonify({"error": str(e)}), 400 diff --git a/server/wsgi.py b/server/wsgi.py index 4fc7fc5..ff53ca6 100644 --- a/server/wsgi.py +++ b/server/wsgi.py @@ -2,6 +2,7 @@ from server.routes.eventmedia import eventmedia_bp from server.routes.files import files_bp from server.routes.events import events_bp +from server.routes.holidays import holidays_bp from server.routes.groups import groups_bp from server.routes.clients import clients_bp from server.database import Session, engine @@ -20,6 +21,7 @@ app.register_blueprint(groups_bp) app.register_blueprint(events_bp) app.register_blueprint(eventmedia_bp) app.register_blueprint(files_bp) +app.register_blueprint(holidays_bp) @app.route("/health")