feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
"""
|
|
|
|
|
|
Club & Organization Management Endpoints for Shinkan Jinkendo
|
|
|
|
|
|
|
|
|
|
|
|
Handles CRUD operations for clubs, divisions, and training groups.
|
|
|
|
|
|
"""
|
2026-05-05 16:12:17 +02:00
|
|
|
|
from typing import Any, List, Optional
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
from fastapi import APIRouter, HTTPException, Depends, Query
|
2026-05-08 13:37:30 +02:00
|
|
|
|
from psycopg2.extras import Json
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
|
|
|
|
|
|
from db import get_db, get_cursor, r2d
|
2026-05-05 22:05:10 +02:00
|
|
|
|
from tenant_context import TenantContext, get_tenant_context
|
2026-05-05 16:12:17 +02:00
|
|
|
|
from club_tenancy import (
|
|
|
|
|
|
assert_club_member,
|
|
|
|
|
|
can_manage_club_org,
|
|
|
|
|
|
can_plan_in_club,
|
|
|
|
|
|
club_ids_for_profile,
|
|
|
|
|
|
is_platform_admin,
|
|
|
|
|
|
)
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
|
|
|
|
|
|
router = APIRouter(prefix="/api", tags=["clubs"])
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-05-08 13:27:15 +02:00
|
|
|
|
def _blank_to_none(value: Any) -> Any:
|
|
|
|
|
|
"""Frontend sendet häufig '' für optionale Felder — ungültig für PostgreSQL TIME/INTEGER."""
|
|
|
|
|
|
if value is None:
|
|
|
|
|
|
return None
|
|
|
|
|
|
if isinstance(value, str) and value.strip() == "":
|
|
|
|
|
|
return None
|
|
|
|
|
|
return value
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _optional_int(value: Any) -> Optional[int]:
|
|
|
|
|
|
v = _blank_to_none(value)
|
|
|
|
|
|
if v is None:
|
|
|
|
|
|
return None
|
|
|
|
|
|
try:
|
|
|
|
|
|
i = int(v)
|
|
|
|
|
|
except (TypeError, ValueError):
|
|
|
|
|
|
return None
|
|
|
|
|
|
return i if i > 0 else None
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-05-08 13:37:30 +02:00
|
|
|
|
def _normalize_co_trainer_ids(val: Any) -> List[int]:
|
|
|
|
|
|
"""Listen von Profil-IDs für JSONB; ungültige Einträge werden verworfen."""
|
|
|
|
|
|
if val is None:
|
|
|
|
|
|
raw: List[Any] = []
|
|
|
|
|
|
elif isinstance(val, list):
|
|
|
|
|
|
raw = val
|
|
|
|
|
|
else:
|
|
|
|
|
|
raw = []
|
|
|
|
|
|
out: List[int] = []
|
|
|
|
|
|
seen = set()
|
|
|
|
|
|
for x in raw:
|
|
|
|
|
|
try:
|
|
|
|
|
|
i = int(x)
|
|
|
|
|
|
except (TypeError, ValueError):
|
|
|
|
|
|
continue
|
|
|
|
|
|
if i <= 0 or i in seen:
|
|
|
|
|
|
continue
|
|
|
|
|
|
seen.add(i)
|
|
|
|
|
|
out.append(i)
|
|
|
|
|
|
return out
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _co_trainer_ids_jsonb(val: Any) -> Json:
|
|
|
|
|
|
"""psycopg2: reine list/dict für JSONB → ProgrammingError ohne Json()."""
|
|
|
|
|
|
return Json(_normalize_co_trainer_ids(val))
|
|
|
|
|
|
|
|
|
|
|
|
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
# ── List Clubs ────────────────────────────────────────────────────────
|
|
|
|
|
|
@router.get("/clubs")
|
|
|
|
|
|
def list_clubs(
|
|
|
|
|
|
status: Optional[str] = Query(default=None),
|
2026-05-05 22:05:10 +02:00
|
|
|
|
tenant: TenantContext = Depends(get_tenant_context),
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
):
|
|
|
|
|
|
"""
|
2026-05-05 16:12:17 +02:00
|
|
|
|
Vereine: für normale Nutzer nur Mitgliedschaft-Vereine; Plattform-Admins sehen alle.
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
"""
|
2026-05-05 22:05:10 +02:00
|
|
|
|
role = tenant.global_role
|
|
|
|
|
|
profile_id = tenant.profile_id
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
with get_db() as conn:
|
|
|
|
|
|
cur = get_cursor(conn)
|
|
|
|
|
|
query = "SELECT * FROM clubs"
|
2026-05-05 16:12:17 +02:00
|
|
|
|
params: List[Any] = []
|
|
|
|
|
|
conds = []
|
|
|
|
|
|
|
|
|
|
|
|
if not is_platform_admin(role):
|
|
|
|
|
|
cids = club_ids_for_profile(cur, profile_id)
|
|
|
|
|
|
if not cids:
|
|
|
|
|
|
return []
|
|
|
|
|
|
conds.append("id IN (" + ",".join(["%s"] * len(cids)) + ")")
|
|
|
|
|
|
params.extend(sorted(cids))
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
|
|
|
|
|
|
if status:
|
2026-05-05 16:12:17 +02:00
|
|
|
|
conds.append("status = %s")
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
params.append(status)
|
|
|
|
|
|
|
2026-05-05 16:12:17 +02:00
|
|
|
|
if conds:
|
|
|
|
|
|
query += " WHERE " + " AND ".join(conds)
|
|
|
|
|
|
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
query += " ORDER BY name"
|
|
|
|
|
|
|
|
|
|
|
|
cur.execute(query, params)
|
|
|
|
|
|
rows = cur.fetchall()
|
|
|
|
|
|
return [r2d(r) for r in rows]
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-05-05 16:40:49 +02:00
|
|
|
|
# ── Öffentliches Vereinsverzeichnis (Registrierung / Antrag ohne Mitgliedschaft) ──
|
|
|
|
|
|
@router.get("/clubs/public-directory")
|
|
|
|
|
|
def public_club_directory():
|
|
|
|
|
|
"""Aktive Vereine zur Auswahl bei Registrierung oder Beitrittsantrag (nur id, name, Kürzel)."""
|
|
|
|
|
|
with get_db() as conn:
|
|
|
|
|
|
cur = get_cursor(conn)
|
|
|
|
|
|
cur.execute(
|
|
|
|
|
|
"""
|
|
|
|
|
|
SELECT id, name, abbreviation
|
|
|
|
|
|
FROM clubs
|
|
|
|
|
|
WHERE status = 'active'
|
|
|
|
|
|
ORDER BY name
|
|
|
|
|
|
"""
|
|
|
|
|
|
)
|
|
|
|
|
|
return [r2d(r) for r in cur.fetchall()]
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-05-07 10:52:14 +02:00
|
|
|
|
# ── Aktive Mitglieder für Trainer-/Co-Trainer-Auswahl (Vereinsmitglied) ──
|
2026-05-05 16:40:49 +02:00
|
|
|
|
@router.get("/clubs/{club_id}/members/directory")
|
2026-05-05 22:05:10 +02:00
|
|
|
|
def club_members_directory(club_id: int, tenant: TenantContext = Depends(get_tenant_context)):
|
2026-05-07 10:52:14 +02:00
|
|
|
|
"""id + name für alle aktiven Mitglieder; E-Mail nur für Plattform-Admin oder Vereinsadmin (Org-Verwaltung)."""
|
2026-05-05 22:05:10 +02:00
|
|
|
|
profile_id = tenant.profile_id
|
|
|
|
|
|
role = tenant.global_role
|
2026-05-05 16:40:49 +02:00
|
|
|
|
with get_db() as conn:
|
|
|
|
|
|
cur = get_cursor(conn)
|
|
|
|
|
|
if not is_platform_admin(role):
|
|
|
|
|
|
assert_club_member(cur, profile_id, club_id)
|
|
|
|
|
|
cur.execute("SELECT 1 FROM clubs WHERE id = %s", (club_id,))
|
|
|
|
|
|
if not cur.fetchone():
|
|
|
|
|
|
raise HTTPException(404, "Verein nicht gefunden")
|
|
|
|
|
|
cur.execute(
|
|
|
|
|
|
"""
|
|
|
|
|
|
SELECT p.id, p.name, p.email
|
|
|
|
|
|
FROM club_members cm
|
|
|
|
|
|
INNER JOIN profiles p ON p.id = cm.profile_id
|
|
|
|
|
|
WHERE cm.club_id = %s AND cm.status = 'active'
|
|
|
|
|
|
ORDER BY COALESCE(p.name, ''), p.email
|
|
|
|
|
|
""",
|
|
|
|
|
|
(club_id,),
|
|
|
|
|
|
)
|
2026-05-07 10:52:14 +02:00
|
|
|
|
rows = [r2d(r) for r in cur.fetchall()]
|
|
|
|
|
|
show_email = is_platform_admin(role) or can_manage_club_org(cur, profile_id, club_id, role)
|
|
|
|
|
|
if not show_email:
|
|
|
|
|
|
for d in rows:
|
|
|
|
|
|
d["email"] = None
|
|
|
|
|
|
return rows
|
2026-05-05 16:40:49 +02:00
|
|
|
|
|
|
|
|
|
|
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
# ── Get Club ──────────────────────────────────────────────────────────
|
|
|
|
|
|
@router.get("/clubs/{club_id}")
|
2026-05-05 22:05:10 +02:00
|
|
|
|
def get_club(club_id: int, tenant: TenantContext = Depends(get_tenant_context)):
|
2026-05-05 16:12:17 +02:00
|
|
|
|
"""Get club by ID with divisions and groups – nur Mitglied oder Plattform-Admin."""
|
2026-05-05 22:05:10 +02:00
|
|
|
|
profile_id = tenant.profile_id
|
|
|
|
|
|
role = tenant.global_role
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
with get_db() as conn:
|
|
|
|
|
|
cur = get_cursor(conn)
|
2026-05-05 16:12:17 +02:00
|
|
|
|
if not is_platform_admin(role):
|
|
|
|
|
|
assert_club_member(cur, profile_id, club_id)
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
|
|
|
|
|
|
# Get club
|
|
|
|
|
|
cur.execute("SELECT * FROM clubs WHERE id = %s", (club_id,))
|
|
|
|
|
|
club = cur.fetchone()
|
|
|
|
|
|
|
|
|
|
|
|
if not club:
|
|
|
|
|
|
raise HTTPException(404, "Verein nicht gefunden")
|
|
|
|
|
|
|
|
|
|
|
|
club = r2d(club)
|
|
|
|
|
|
|
|
|
|
|
|
# Get divisions
|
2026-05-05 16:12:17 +02:00
|
|
|
|
cur.execute(
|
|
|
|
|
|
"""
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
SELECT * FROM divisions
|
|
|
|
|
|
WHERE club_id = %s
|
|
|
|
|
|
ORDER BY name
|
2026-05-05 16:12:17 +02:00
|
|
|
|
""",
|
|
|
|
|
|
(club_id,),
|
|
|
|
|
|
)
|
|
|
|
|
|
club["divisions"] = [r2d(r) for r in cur.fetchall()]
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
|
|
|
|
|
|
# Get training groups
|
2026-05-05 16:12:17 +02:00
|
|
|
|
cur.execute(
|
|
|
|
|
|
"""
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
SELECT g.*,
|
|
|
|
|
|
p.name as trainer_name
|
|
|
|
|
|
FROM training_groups g
|
|
|
|
|
|
LEFT JOIN profiles p ON g.trainer_id = p.id
|
|
|
|
|
|
WHERE g.club_id = %s
|
|
|
|
|
|
ORDER BY g.weekday, g.time_start
|
2026-05-05 16:12:17 +02:00
|
|
|
|
""",
|
|
|
|
|
|
(club_id,),
|
|
|
|
|
|
)
|
|
|
|
|
|
club["training_groups"] = [r2d(r) for r in cur.fetchall()]
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
|
|
|
|
|
|
return club
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ── Create Club ───────────────────────────────────────────────────────
|
|
|
|
|
|
@router.post("/clubs")
|
2026-05-05 22:05:10 +02:00
|
|
|
|
def create_club(data: dict, tenant: TenantContext = Depends(get_tenant_context)):
|
2026-05-05 16:12:17 +02:00
|
|
|
|
"""Neuen Verein anlegen – nur Plattform-Admin; Pflicht: primary_admin_profile_id (Hauptverwalter:in)."""
|
2026-05-05 22:05:10 +02:00
|
|
|
|
role = tenant.global_role
|
2026-05-05 16:12:17 +02:00
|
|
|
|
if not is_platform_admin(role):
|
|
|
|
|
|
raise HTTPException(403, "Nur Plattform-Administratoren dürfen neue Vereine anlegen")
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
|
2026-05-05 16:12:17 +02:00
|
|
|
|
name = data.get("name")
|
|
|
|
|
|
primary_admin_profile_id = data.get("primary_admin_profile_id")
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
if not name:
|
|
|
|
|
|
raise HTTPException(400, "Name ist Pflichtfeld")
|
2026-05-05 16:12:17 +02:00
|
|
|
|
if not primary_admin_profile_id:
|
|
|
|
|
|
raise HTTPException(400, "primary_admin_profile_id ist Pflichtfeld (Hauptverwalter:in)")
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
aid = int(primary_admin_profile_id)
|
|
|
|
|
|
except (TypeError, ValueError):
|
|
|
|
|
|
raise HTTPException(400, "primary_admin_profile_id ungültig")
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
|
|
|
|
|
|
with get_db() as conn:
|
|
|
|
|
|
cur = get_cursor(conn)
|
2026-05-05 16:12:17 +02:00
|
|
|
|
cur.execute("SELECT id FROM profiles WHERE id = %s", (aid,))
|
|
|
|
|
|
if not cur.fetchone():
|
|
|
|
|
|
raise HTTPException(404, "Profil für Hauptverwalter nicht gefunden")
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
|
2026-05-05 16:12:17 +02:00
|
|
|
|
cur.execute(
|
|
|
|
|
|
"""
|
|
|
|
|
|
INSERT INTO clubs (name, abbreviation, description, status, primary_admin_profile_id)
|
|
|
|
|
|
VALUES (%s, %s, %s, %s, %s)
|
|
|
|
|
|
RETURNING id
|
|
|
|
|
|
""",
|
|
|
|
|
|
(
|
|
|
|
|
|
name,
|
|
|
|
|
|
data.get("abbreviation"),
|
|
|
|
|
|
data.get("description"),
|
|
|
|
|
|
data.get("status", "active"),
|
|
|
|
|
|
aid,
|
|
|
|
|
|
),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
club_id = cur.fetchone()["id"]
|
|
|
|
|
|
|
|
|
|
|
|
cur.execute(
|
|
|
|
|
|
"""
|
|
|
|
|
|
INSERT INTO club_members (profile_id, club_id, status)
|
|
|
|
|
|
VALUES (%s, %s, 'active')
|
|
|
|
|
|
ON CONFLICT (profile_id, club_id)
|
|
|
|
|
|
DO UPDATE SET status = 'active', updated_at = NOW()
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
RETURNING id
|
2026-05-05 16:12:17 +02:00
|
|
|
|
""",
|
|
|
|
|
|
(aid, club_id),
|
|
|
|
|
|
)
|
|
|
|
|
|
cm_id = cur.fetchone()["id"]
|
|
|
|
|
|
for rc in ("club_admin", "trainer"):
|
|
|
|
|
|
cur.execute(
|
|
|
|
|
|
"""
|
|
|
|
|
|
INSERT INTO club_member_roles (club_member_id, role_code)
|
|
|
|
|
|
VALUES (%s, %s)
|
|
|
|
|
|
ON CONFLICT (club_member_id, role_code) DO NOTHING
|
|
|
|
|
|
""",
|
|
|
|
|
|
(cm_id, rc),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
2026-05-05 22:05:10 +02:00
|
|
|
|
return get_club(club_id, tenant)
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ── Update Club ───────────────────────────────────────────────────────
|
|
|
|
|
|
@router.put("/clubs/{club_id}")
|
2026-05-05 22:05:10 +02:00
|
|
|
|
def update_club(club_id: int, data: dict, tenant: TenantContext = Depends(get_tenant_context)):
|
2026-05-05 16:12:17 +02:00
|
|
|
|
"""Verein bearbeiten – Plattform-Admin oder Vereinsadmin."""
|
2026-05-05 22:05:10 +02:00
|
|
|
|
profile_id = tenant.profile_id
|
|
|
|
|
|
role = tenant.global_role
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
|
|
|
|
|
|
with get_db() as conn:
|
|
|
|
|
|
cur = get_cursor(conn)
|
|
|
|
|
|
|
|
|
|
|
|
# Check existence
|
|
|
|
|
|
cur.execute("SELECT id FROM clubs WHERE id = %s", (club_id,))
|
|
|
|
|
|
if not cur.fetchone():
|
|
|
|
|
|
raise HTTPException(404, "Verein nicht gefunden")
|
|
|
|
|
|
|
2026-05-05 16:12:17 +02:00
|
|
|
|
if not can_manage_club_org(cur, profile_id, club_id, role):
|
|
|
|
|
|
raise HTTPException(403, "Keine Berechtigung für diesen Verein")
|
|
|
|
|
|
|
|
|
|
|
|
# Nur Plattform-Admin darf primary_admin_profile_id ändern
|
|
|
|
|
|
if "primary_admin_profile_id" in data and data["primary_admin_profile_id"] is not None:
|
|
|
|
|
|
if not is_platform_admin(role):
|
|
|
|
|
|
raise HTTPException(403, "Nur Plattform-Admins dürfen den Hauptverwalter ändern")
|
|
|
|
|
|
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
# Update
|
2026-05-05 16:12:17 +02:00
|
|
|
|
cur.execute(
|
|
|
|
|
|
"""
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
UPDATE clubs SET
|
2026-05-05 16:12:17 +02:00
|
|
|
|
name = COALESCE(%s, name),
|
|
|
|
|
|
abbreviation = COALESCE(%s, abbreviation),
|
|
|
|
|
|
description = COALESCE(%s, description),
|
|
|
|
|
|
status = COALESCE(%s, status),
|
|
|
|
|
|
primary_admin_profile_id = COALESCE(%s, primary_admin_profile_id),
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
updated_at = NOW()
|
|
|
|
|
|
WHERE id = %s
|
2026-05-05 16:12:17 +02:00
|
|
|
|
""",
|
|
|
|
|
|
(
|
|
|
|
|
|
data.get("name"),
|
|
|
|
|
|
data.get("abbreviation"),
|
|
|
|
|
|
data.get("description"),
|
|
|
|
|
|
data.get("status"),
|
|
|
|
|
|
data.get("primary_admin_profile_id"),
|
|
|
|
|
|
club_id,
|
|
|
|
|
|
),
|
|
|
|
|
|
)
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
2026-05-05 22:05:10 +02:00
|
|
|
|
return get_club(club_id, tenant)
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ── Delete Club ───────────────────────────────────────────────────────
|
|
|
|
|
|
@router.delete("/clubs/{club_id}")
|
2026-05-05 22:05:10 +02:00
|
|
|
|
def delete_club(club_id: int, tenant: TenantContext = Depends(get_tenant_context)):
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
"""Delete club (superadmin only)."""
|
2026-05-05 22:05:10 +02:00
|
|
|
|
role = tenant.global_role
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
if role != 'superadmin':
|
|
|
|
|
|
raise HTTPException(403, "Nur Superadmins dürfen Vereine löschen")
|
|
|
|
|
|
|
|
|
|
|
|
with get_db() as conn:
|
|
|
|
|
|
cur = get_cursor(conn)
|
|
|
|
|
|
|
|
|
|
|
|
# Check existence
|
|
|
|
|
|
cur.execute("SELECT id FROM clubs WHERE id = %s", (club_id,))
|
|
|
|
|
|
if not cur.fetchone():
|
|
|
|
|
|
raise HTTPException(404, "Verein nicht gefunden")
|
|
|
|
|
|
|
2026-06-07 07:31:05 +02:00
|
|
|
|
# Gründungsanträge: Freigabe verliert Gültigkeit wenn Verein entfernt wird
|
|
|
|
|
|
cur.execute(
|
|
|
|
|
|
"""
|
|
|
|
|
|
UPDATE club_creation_requests
|
|
|
|
|
|
SET status = 'superseded', updated_at = NOW()
|
|
|
|
|
|
WHERE created_club_id = %s AND status = 'approved'
|
|
|
|
|
|
""",
|
|
|
|
|
|
(club_id,),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
# Delete (CASCADE handles divisions and groups)
|
|
|
|
|
|
cur.execute("DELETE FROM clubs WHERE id = %s", (club_id,))
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
|
|
|
|
|
return {"ok": True}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ── List Divisions ────────────────────────────────────────────────────
|
|
|
|
|
|
@router.get("/divisions")
|
|
|
|
|
|
def list_divisions(
|
|
|
|
|
|
club_id: Optional[int] = Query(default=None),
|
2026-05-05 22:05:10 +02:00
|
|
|
|
tenant: TenantContext = Depends(get_tenant_context),
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
):
|
2026-05-05 16:12:17 +02:00
|
|
|
|
"""Sparten – ohne Admin-Rechte nur in eigenen Vereinen sichtbar."""
|
2026-05-05 22:05:10 +02:00
|
|
|
|
profile_id = tenant.profile_id
|
|
|
|
|
|
role = tenant.global_role
|
2026-05-05 16:12:17 +02:00
|
|
|
|
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
with get_db() as conn:
|
|
|
|
|
|
cur = get_cursor(conn)
|
|
|
|
|
|
|
2026-05-05 16:12:17 +02:00
|
|
|
|
mine = club_ids_for_profile(cur, profile_id)
|
|
|
|
|
|
if not is_platform_admin(role) and not mine:
|
|
|
|
|
|
return []
|
|
|
|
|
|
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
query = """
|
|
|
|
|
|
SELECT d.*, c.name as club_name
|
|
|
|
|
|
FROM divisions d
|
|
|
|
|
|
LEFT JOIN clubs c ON d.club_id = c.id
|
|
|
|
|
|
"""
|
2026-05-05 16:12:17 +02:00
|
|
|
|
where = []
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
params = []
|
|
|
|
|
|
|
2026-05-05 16:12:17 +02:00
|
|
|
|
if club_id is not None:
|
|
|
|
|
|
where.append("d.club_id = %s")
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
params.append(club_id)
|
2026-05-05 16:12:17 +02:00
|
|
|
|
if not is_platform_admin(role) and club_id not in mine:
|
|
|
|
|
|
return []
|
|
|
|
|
|
|
|
|
|
|
|
if not is_platform_admin(role):
|
|
|
|
|
|
where.append(
|
|
|
|
|
|
"d.club_id IN (" + ",".join(["%s"] * len(mine)) + ")"
|
|
|
|
|
|
)
|
|
|
|
|
|
params.extend(sorted(mine))
|
|
|
|
|
|
|
|
|
|
|
|
if where:
|
|
|
|
|
|
query += " WHERE " + " AND ".join(where)
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
|
|
|
|
|
|
query += " ORDER BY d.name"
|
|
|
|
|
|
|
|
|
|
|
|
cur.execute(query, params)
|
|
|
|
|
|
rows = cur.fetchall()
|
|
|
|
|
|
return [r2d(r) for r in rows]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ── Create Division ───────────────────────────────────────────────────
|
|
|
|
|
|
@router.post("/divisions")
|
2026-05-05 22:05:10 +02:00
|
|
|
|
def create_division(data: dict, tenant: TenantContext = Depends(get_tenant_context)):
|
2026-05-05 16:12:17 +02:00
|
|
|
|
"""Create new division – Vereinsadmin / Plattform-Admin."""
|
2026-05-05 22:05:10 +02:00
|
|
|
|
profile_id = tenant.profile_id
|
|
|
|
|
|
role = tenant.global_role
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
|
2026-05-05 16:12:17 +02:00
|
|
|
|
club_id = data.get("club_id")
|
|
|
|
|
|
name = data.get("name")
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
|
|
|
|
|
|
if not club_id or not name:
|
|
|
|
|
|
raise HTTPException(400, "club_id und name sind Pflichtfelder")
|
|
|
|
|
|
|
|
|
|
|
|
with get_db() as conn:
|
|
|
|
|
|
cur = get_cursor(conn)
|
|
|
|
|
|
|
2026-05-05 16:12:17 +02:00
|
|
|
|
if not can_manage_club_org(cur, profile_id, int(club_id), role):
|
|
|
|
|
|
raise HTTPException(403, "Keine Berechtigung, Sparten in diesem Verein anzulegen")
|
|
|
|
|
|
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
# Check club exists
|
|
|
|
|
|
cur.execute("SELECT id FROM clubs WHERE id = %s", (club_id,))
|
|
|
|
|
|
if not cur.fetchone():
|
|
|
|
|
|
raise HTTPException(404, "Verein nicht gefunden")
|
|
|
|
|
|
|
|
|
|
|
|
# Insert
|
2026-05-05 16:12:17 +02:00
|
|
|
|
cur.execute(
|
|
|
|
|
|
"""
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
INSERT INTO divisions (club_id, name, focus_area)
|
|
|
|
|
|
VALUES (%s, %s, %s)
|
|
|
|
|
|
RETURNING id
|
2026-05-05 16:12:17 +02:00
|
|
|
|
""",
|
|
|
|
|
|
(
|
|
|
|
|
|
club_id,
|
|
|
|
|
|
name,
|
|
|
|
|
|
data.get("focus_area"),
|
|
|
|
|
|
),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
division_id = cur.fetchone()["id"]
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
|
|
|
|
|
# Return created division
|
2026-05-05 16:12:17 +02:00
|
|
|
|
cur.execute(
|
|
|
|
|
|
"""
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
SELECT d.*, c.name as club_name
|
|
|
|
|
|
FROM divisions d
|
|
|
|
|
|
LEFT JOIN clubs c ON d.club_id = c.id
|
|
|
|
|
|
WHERE d.id = %s
|
2026-05-05 16:12:17 +02:00
|
|
|
|
""",
|
|
|
|
|
|
(division_id,),
|
|
|
|
|
|
)
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
return r2d(cur.fetchone())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ── Update Division ───────────────────────────────────────────────────
|
|
|
|
|
|
@router.put("/divisions/{division_id}")
|
2026-05-05 22:05:10 +02:00
|
|
|
|
def update_division(division_id: int, data: dict, tenant: TenantContext = Depends(get_tenant_context)):
|
2026-05-05 16:12:17 +02:00
|
|
|
|
"""Update division – Vereinsadmin / Plattform-Admin."""
|
2026-05-05 22:05:10 +02:00
|
|
|
|
profile_id = tenant.profile_id
|
|
|
|
|
|
role = tenant.global_role
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
|
|
|
|
|
|
with get_db() as conn:
|
|
|
|
|
|
cur = get_cursor(conn)
|
|
|
|
|
|
|
2026-05-05 16:12:17 +02:00
|
|
|
|
cur.execute("SELECT id, club_id FROM divisions WHERE id = %s", (division_id,))
|
|
|
|
|
|
div = cur.fetchone()
|
|
|
|
|
|
if not div:
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
raise HTTPException(404, "Sparte nicht gefunden")
|
|
|
|
|
|
|
2026-05-05 16:12:17 +02:00
|
|
|
|
if not can_manage_club_org(cur, profile_id, div["club_id"], role):
|
|
|
|
|
|
raise HTTPException(403, "Keine Berechtigung")
|
|
|
|
|
|
|
|
|
|
|
|
cur.execute(
|
|
|
|
|
|
"""
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
UPDATE divisions SET
|
|
|
|
|
|
name = %s,
|
|
|
|
|
|
focus_area = %s,
|
|
|
|
|
|
updated_at = NOW()
|
|
|
|
|
|
WHERE id = %s
|
2026-05-05 16:12:17 +02:00
|
|
|
|
""",
|
|
|
|
|
|
(
|
|
|
|
|
|
data.get("name"),
|
|
|
|
|
|
data.get("focus_area"),
|
|
|
|
|
|
division_id,
|
|
|
|
|
|
),
|
|
|
|
|
|
)
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
2026-05-05 16:12:17 +02:00
|
|
|
|
cur.execute(
|
|
|
|
|
|
"""
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
SELECT d.*, c.name as club_name
|
|
|
|
|
|
FROM divisions d
|
|
|
|
|
|
LEFT JOIN clubs c ON d.club_id = c.id
|
|
|
|
|
|
WHERE d.id = %s
|
2026-05-05 16:12:17 +02:00
|
|
|
|
""",
|
|
|
|
|
|
(division_id,),
|
|
|
|
|
|
)
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
return r2d(cur.fetchone())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ── Delete Division ───────────────────────────────────────────────────
|
|
|
|
|
|
@router.delete("/divisions/{division_id}")
|
2026-05-05 22:05:10 +02:00
|
|
|
|
def delete_division(division_id: int, tenant: TenantContext = Depends(get_tenant_context)):
|
2026-05-05 16:12:17 +02:00
|
|
|
|
"""Delete division – Vereinsadmin / Plattform-Admin."""
|
2026-05-05 22:05:10 +02:00
|
|
|
|
profile_id = tenant.profile_id
|
|
|
|
|
|
role = tenant.global_role
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
|
|
|
|
|
|
with get_db() as conn:
|
|
|
|
|
|
cur = get_cursor(conn)
|
|
|
|
|
|
|
2026-05-05 16:12:17 +02:00
|
|
|
|
cur.execute("SELECT id, club_id FROM divisions WHERE id = %s", (division_id,))
|
|
|
|
|
|
div = cur.fetchone()
|
|
|
|
|
|
if not div:
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
raise HTTPException(404, "Sparte nicht gefunden")
|
|
|
|
|
|
|
2026-05-05 16:12:17 +02:00
|
|
|
|
if not can_manage_club_org(cur, profile_id, div["club_id"], role):
|
|
|
|
|
|
raise HTTPException(403, "Keine Berechtigung")
|
|
|
|
|
|
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
cur.execute("DELETE FROM divisions WHERE id = %s", (division_id,))
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
|
|
|
|
|
return {"ok": True}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ── List Training Groups ──────────────────────────────────────────────
|
|
|
|
|
|
@router.get("/groups")
|
|
|
|
|
|
def list_training_groups(
|
|
|
|
|
|
club_id: Optional[int] = Query(default=None),
|
|
|
|
|
|
division_id: Optional[int] = Query(default=None),
|
|
|
|
|
|
status: Optional[str] = Query(default=None),
|
2026-05-05 22:05:10 +02:00
|
|
|
|
tenant: TenantContext = Depends(get_tenant_context),
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
):
|
|
|
|
|
|
"""
|
2026-05-05 16:12:17 +02:00
|
|
|
|
Trainingsgruppen – ohne Plattform-Admin nur in eigenen Vereinen.
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
"""
|
2026-05-05 22:05:10 +02:00
|
|
|
|
profile_id = tenant.profile_id
|
|
|
|
|
|
role = tenant.global_role
|
2026-05-05 16:12:17 +02:00
|
|
|
|
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
with get_db() as conn:
|
|
|
|
|
|
cur = get_cursor(conn)
|
|
|
|
|
|
|
2026-05-05 16:12:17 +02:00
|
|
|
|
mine = club_ids_for_profile(cur, profile_id)
|
|
|
|
|
|
if not is_platform_admin(role) and not mine:
|
|
|
|
|
|
return []
|
|
|
|
|
|
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
query = """
|
|
|
|
|
|
SELECT g.*,
|
|
|
|
|
|
c.name as club_name,
|
|
|
|
|
|
d.name as division_name,
|
|
|
|
|
|
p.name as trainer_name
|
|
|
|
|
|
FROM training_groups g
|
|
|
|
|
|
LEFT JOIN clubs c ON g.club_id = c.id
|
|
|
|
|
|
LEFT JOIN divisions d ON g.division_id = d.id
|
|
|
|
|
|
LEFT JOIN profiles p ON g.trainer_id = p.id
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
where = []
|
|
|
|
|
|
params = []
|
|
|
|
|
|
|
2026-05-05 16:12:17 +02:00
|
|
|
|
if club_id is not None:
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
where.append("g.club_id = %s")
|
|
|
|
|
|
params.append(club_id)
|
2026-05-05 16:12:17 +02:00
|
|
|
|
if not is_platform_admin(role) and club_id not in mine:
|
|
|
|
|
|
return []
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
|
2026-05-05 16:12:17 +02:00
|
|
|
|
if division_id is not None:
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
where.append("g.division_id = %s")
|
|
|
|
|
|
params.append(division_id)
|
|
|
|
|
|
|
|
|
|
|
|
if status:
|
|
|
|
|
|
where.append("g.status = %s")
|
|
|
|
|
|
params.append(status)
|
|
|
|
|
|
|
2026-05-05 16:12:17 +02:00
|
|
|
|
if not is_platform_admin(role):
|
|
|
|
|
|
where.append("g.club_id IN (" + ",".join(["%s"] * len(mine)) + ")")
|
|
|
|
|
|
params.extend(sorted(mine))
|
|
|
|
|
|
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
if where:
|
|
|
|
|
|
query += " WHERE " + " AND ".join(where)
|
|
|
|
|
|
|
|
|
|
|
|
query += " ORDER BY g.weekday, g.time_start"
|
|
|
|
|
|
|
|
|
|
|
|
cur.execute(query, params)
|
|
|
|
|
|
rows = cur.fetchall()
|
|
|
|
|
|
return [r2d(r) for r in rows]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ── Get Training Group ────────────────────────────────────────────────
|
|
|
|
|
|
@router.get("/groups/{group_id}")
|
2026-05-05 22:05:10 +02:00
|
|
|
|
def get_training_group(group_id: int, tenant: TenantContext = Depends(get_tenant_context)):
|
2026-05-05 16:12:17 +02:00
|
|
|
|
"""Trainingsgruppe – nur mit Vereinszugriff."""
|
2026-05-05 22:05:10 +02:00
|
|
|
|
profile_id = tenant.profile_id
|
|
|
|
|
|
role = tenant.global_role
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
with get_db() as conn:
|
|
|
|
|
|
cur = get_cursor(conn)
|
|
|
|
|
|
|
2026-05-05 16:12:17 +02:00
|
|
|
|
cur.execute(
|
|
|
|
|
|
"""
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
SELECT g.*,
|
|
|
|
|
|
c.name as club_name,
|
|
|
|
|
|
d.name as division_name,
|
|
|
|
|
|
p.name as trainer_name
|
|
|
|
|
|
FROM training_groups g
|
|
|
|
|
|
LEFT JOIN clubs c ON g.club_id = c.id
|
|
|
|
|
|
LEFT JOIN divisions d ON g.division_id = d.id
|
|
|
|
|
|
LEFT JOIN profiles p ON g.trainer_id = p.id
|
|
|
|
|
|
WHERE g.id = %s
|
2026-05-05 16:12:17 +02:00
|
|
|
|
""",
|
|
|
|
|
|
(group_id,),
|
|
|
|
|
|
)
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
|
|
|
|
|
|
group = cur.fetchone()
|
|
|
|
|
|
|
|
|
|
|
|
if not group:
|
|
|
|
|
|
raise HTTPException(404, "Trainingsgruppe nicht gefunden")
|
|
|
|
|
|
|
2026-05-05 16:12:17 +02:00
|
|
|
|
cid = group["club_id"]
|
|
|
|
|
|
if not is_platform_admin(role):
|
|
|
|
|
|
assert_club_member(cur, profile_id, cid)
|
|
|
|
|
|
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
return r2d(group)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ── Create Training Group ─────────────────────────────────────────────
|
|
|
|
|
|
@router.post("/groups")
|
2026-05-05 22:05:10 +02:00
|
|
|
|
def create_training_group(data: dict, tenant: TenantContext = Depends(get_tenant_context)):
|
2026-05-05 16:12:17 +02:00
|
|
|
|
"""Trainingsgruppe anlegen – Mitglied mit Planungs-/Admin-Rolle im Verein."""
|
2026-05-05 22:05:10 +02:00
|
|
|
|
profile_id = tenant.profile_id
|
|
|
|
|
|
role = tenant.global_role
|
|
|
|
|
|
if role not in ("admin", "superadmin", "trainer", "user"):
|
2026-04-28 19:46:09 +02:00
|
|
|
|
raise HTTPException(403, "Keine Berechtigung, Trainingsgruppen anzulegen")
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
|
2026-05-05 16:12:17 +02:00
|
|
|
|
club_id = data.get("club_id")
|
|
|
|
|
|
name = data.get("name")
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
|
|
|
|
|
|
if not club_id or not name:
|
|
|
|
|
|
raise HTTPException(400, "club_id und name sind Pflichtfelder")
|
|
|
|
|
|
|
2026-05-08 13:27:15 +02:00
|
|
|
|
raw_tid = data.get("trainer_id")
|
|
|
|
|
|
if role in ("trainer", "user"):
|
|
|
|
|
|
parsed_tid = _optional_int(raw_tid)
|
|
|
|
|
|
trainer_id = parsed_tid if parsed_tid is not None else profile_id
|
|
|
|
|
|
else:
|
|
|
|
|
|
trainer_id = _optional_int(raw_tid)
|
|
|
|
|
|
|
|
|
|
|
|
division_id = _optional_int(data.get("division_id"))
|
2026-05-08 13:37:30 +02:00
|
|
|
|
co_json = _co_trainer_ids_jsonb(data.get("co_trainer_ids"))
|
2026-04-28 19:46:09 +02:00
|
|
|
|
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
with get_db() as conn:
|
|
|
|
|
|
cur = get_cursor(conn)
|
|
|
|
|
|
|
2026-05-05 16:12:17 +02:00
|
|
|
|
if not can_plan_in_club(cur, profile_id, int(club_id), role):
|
|
|
|
|
|
raise HTTPException(403, "Keine Berechtigung für diesen Verein")
|
|
|
|
|
|
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
# Check club exists
|
|
|
|
|
|
cur.execute("SELECT id FROM clubs WHERE id = %s", (club_id,))
|
|
|
|
|
|
if not cur.fetchone():
|
|
|
|
|
|
raise HTTPException(404, "Verein nicht gefunden")
|
|
|
|
|
|
|
|
|
|
|
|
# Insert
|
2026-05-05 16:12:17 +02:00
|
|
|
|
cur.execute(
|
|
|
|
|
|
"""
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
INSERT INTO training_groups (
|
|
|
|
|
|
club_id, division_id, name, focus, level, age_group,
|
|
|
|
|
|
weekday, time_start, time_end, location,
|
|
|
|
|
|
trainer_id, co_trainer_ids, status
|
|
|
|
|
|
) VALUES (
|
|
|
|
|
|
%s, %s, %s, %s, %s, %s,
|
|
|
|
|
|
%s, %s, %s, %s,
|
2026-05-08 13:37:30 +02:00
|
|
|
|
%s, %s::jsonb, %s
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
) RETURNING id
|
2026-05-05 16:12:17 +02:00
|
|
|
|
""",
|
|
|
|
|
|
(
|
2026-05-08 13:27:15 +02:00
|
|
|
|
int(club_id),
|
|
|
|
|
|
division_id,
|
2026-05-05 16:12:17 +02:00
|
|
|
|
name,
|
2026-05-08 13:27:15 +02:00
|
|
|
|
_blank_to_none(data.get("focus")),
|
|
|
|
|
|
_blank_to_none(data.get("level")),
|
|
|
|
|
|
_blank_to_none(data.get("age_group")),
|
|
|
|
|
|
_blank_to_none(data.get("weekday")),
|
|
|
|
|
|
_blank_to_none(data.get("time_start")),
|
|
|
|
|
|
_blank_to_none(data.get("time_end")),
|
|
|
|
|
|
_blank_to_none(data.get("location")),
|
2026-05-05 16:12:17 +02:00
|
|
|
|
trainer_id,
|
2026-05-08 13:37:30 +02:00
|
|
|
|
co_json,
|
2026-05-08 13:27:15 +02:00
|
|
|
|
data.get("status") or "active",
|
2026-05-05 16:12:17 +02:00
|
|
|
|
),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
group_id = cur.fetchone()["id"]
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
2026-05-05 22:05:10 +02:00
|
|
|
|
return get_training_group(group_id, tenant)
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ── Update Training Group ─────────────────────────────────────────────
|
|
|
|
|
|
@router.put("/groups/{group_id}")
|
2026-05-05 22:05:10 +02:00
|
|
|
|
def update_training_group(group_id: int, data: dict, tenant: TenantContext = Depends(get_tenant_context)):
|
2026-05-05 16:12:17 +02:00
|
|
|
|
"""Update training group – Vereinsadmin, Plattform-Admin oder zugewiesene Trainer."""
|
2026-05-05 22:05:10 +02:00
|
|
|
|
profile_id = tenant.profile_id
|
|
|
|
|
|
role = tenant.global_role
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
|
|
|
|
|
|
with get_db() as conn:
|
|
|
|
|
|
cur = get_cursor(conn)
|
|
|
|
|
|
|
2026-05-05 16:12:17 +02:00
|
|
|
|
cur.execute(
|
|
|
|
|
|
"SELECT trainer_id, co_trainer_ids, club_id FROM training_groups WHERE id = %s",
|
|
|
|
|
|
(group_id,),
|
|
|
|
|
|
)
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
row = cur.fetchone()
|
|
|
|
|
|
if not row:
|
|
|
|
|
|
raise HTTPException(404, "Trainingsgruppe nicht gefunden")
|
|
|
|
|
|
|
2026-05-05 16:12:17 +02:00
|
|
|
|
co_trainers = row["co_trainer_ids"] or []
|
|
|
|
|
|
club_id = row["club_id"]
|
|
|
|
|
|
|
2026-05-05 22:05:10 +02:00
|
|
|
|
allowed = role in ("admin", "superadmin")
|
2026-05-05 16:12:17 +02:00
|
|
|
|
if not allowed:
|
|
|
|
|
|
allowed = can_manage_club_org(cur, profile_id, club_id, role)
|
|
|
|
|
|
if not allowed:
|
|
|
|
|
|
allowed = row["trainer_id"] == profile_id or profile_id in co_trainers
|
|
|
|
|
|
|
|
|
|
|
|
if not allowed:
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
raise HTTPException(403, "Keine Berechtigung")
|
|
|
|
|
|
|
2026-05-08 13:37:30 +02:00
|
|
|
|
co_json = _co_trainer_ids_jsonb(data.get("co_trainer_ids"))
|
2026-05-08 13:27:15 +02:00
|
|
|
|
|
2026-05-05 16:12:17 +02:00
|
|
|
|
cur.execute(
|
|
|
|
|
|
"""
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
UPDATE training_groups SET
|
|
|
|
|
|
name = %s,
|
|
|
|
|
|
division_id = %s,
|
|
|
|
|
|
focus = %s,
|
|
|
|
|
|
level = %s,
|
|
|
|
|
|
age_group = %s,
|
|
|
|
|
|
weekday = %s,
|
|
|
|
|
|
time_start = %s,
|
|
|
|
|
|
time_end = %s,
|
|
|
|
|
|
location = %s,
|
|
|
|
|
|
trainer_id = %s,
|
2026-05-08 13:37:30 +02:00
|
|
|
|
co_trainer_ids = %s::jsonb,
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
status = %s,
|
|
|
|
|
|
updated_at = NOW()
|
|
|
|
|
|
WHERE id = %s
|
2026-05-05 16:12:17 +02:00
|
|
|
|
""",
|
|
|
|
|
|
(
|
|
|
|
|
|
data.get("name"),
|
2026-05-08 13:27:15 +02:00
|
|
|
|
_optional_int(data.get("division_id")),
|
|
|
|
|
|
_blank_to_none(data.get("focus")),
|
|
|
|
|
|
_blank_to_none(data.get("level")),
|
|
|
|
|
|
_blank_to_none(data.get("age_group")),
|
|
|
|
|
|
_blank_to_none(data.get("weekday")),
|
|
|
|
|
|
_blank_to_none(data.get("time_start")),
|
|
|
|
|
|
_blank_to_none(data.get("time_end")),
|
|
|
|
|
|
_blank_to_none(data.get("location")),
|
|
|
|
|
|
_optional_int(data.get("trainer_id")),
|
2026-05-08 13:37:30 +02:00
|
|
|
|
co_json,
|
2026-05-08 13:27:15 +02:00
|
|
|
|
data.get("status") or "active",
|
2026-05-05 16:12:17 +02:00
|
|
|
|
group_id,
|
|
|
|
|
|
),
|
|
|
|
|
|
)
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
2026-05-05 22:05:10 +02:00
|
|
|
|
return get_training_group(group_id, tenant)
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ── Delete Training Group ─────────────────────────────────────────────
|
|
|
|
|
|
@router.delete("/groups/{group_id}")
|
2026-05-05 22:05:10 +02:00
|
|
|
|
def delete_training_group(group_id: int, tenant: TenantContext = Depends(get_tenant_context)):
|
2026-05-05 16:12:17 +02:00
|
|
|
|
"""Delete training group – Vereinsadmin oder Plattform-Admin."""
|
2026-05-05 22:05:10 +02:00
|
|
|
|
profile_id = tenant.profile_id
|
|
|
|
|
|
role = tenant.global_role
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
|
|
|
|
|
|
with get_db() as conn:
|
|
|
|
|
|
cur = get_cursor(conn)
|
|
|
|
|
|
|
2026-05-05 16:12:17 +02:00
|
|
|
|
cur.execute("SELECT id, club_id FROM training_groups WHERE id = %s", (group_id,))
|
|
|
|
|
|
row = cur.fetchone()
|
|
|
|
|
|
if not row:
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
raise HTTPException(404, "Trainingsgruppe nicht gefunden")
|
|
|
|
|
|
|
2026-05-05 16:12:17 +02:00
|
|
|
|
if not can_manage_club_org(cur, profile_id, row["club_id"], role):
|
|
|
|
|
|
raise HTTPException(403, "Keine Berechtigung")
|
|
|
|
|
|
|
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
|
|
|
|
cur.execute("DELETE FROM training_groups WHERE id = %s", (group_id,))
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
|
|
|
|
|
return {"ok": True}
|