Mandantenfähigkeit ausgebaut: - Neue Rolle RESELLER (company_id NULL); companies.reseller_id + is_active - RLS-Erweiterung (Migration 0034): companies/users zusätzlich auf app.reseller_id gefenced → Reseller sieht/verwaltet DB-seitig nur eigene Firmen, keine personenbezogenen Zeit-/Abwesenheitsdaten (DSGVO: nur Verwaltung) - get_current_user setzt app.reseller_id + Bypass aus für RESELLER - tenant_service: Firma + Erst-Admin (Einladung), Übersicht mit Kennzahlen - Router /reseller/* (Self-Service) und /admin/* (SUPER_ADMIN: Mandanten + Reseller) - Login-Sperre bei deaktiviertem Mandanten - Frontend: TenantsPage (/admin/tenants), eigene ResellerCompaniesPage (/reseller), rollenbasierte Login-Weiterleitung, Nav "Mandanten" für SUPER_ADMIN - 4 neue Tests inkl. Cross-Reseller-RLS-Isolation; 172/172 grün Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
70 lines
2.0 KiB
Python
70 lines
2.0 KiB
Python
"""Schemas für Reseller-Selbstverwaltung und SUPER_ADMIN-Mandantenübersicht.
|
||
|
||
Wichtig (DSGVO): Reseller sehen ausschließlich Firmen-Stammdaten und Kennzahlen
|
||
(z.B. Nutzeranzahl) – KEINE personenbezogenen Zeit-/Abwesenheitsdaten.
|
||
"""
|
||
import uuid
|
||
from datetime import datetime
|
||
|
||
from pydantic import BaseModel, EmailStr, Field
|
||
|
||
|
||
class TenantOut(BaseModel):
|
||
"""Eine Firma als Mandant – für Reseller- und SUPER_ADMIN-Übersicht."""
|
||
model_config = {"from_attributes": True}
|
||
|
||
id: uuid.UUID
|
||
name: str
|
||
slug: str
|
||
plan: str
|
||
is_active: bool
|
||
country: str
|
||
reseller_id: uuid.UUID | None = None
|
||
reseller_name: str | None = None
|
||
user_count: int = 0
|
||
active_user_count: int = 0
|
||
admin_email: str | None = None
|
||
created_at: datetime | None = None
|
||
|
||
|
||
class TenantCreate(BaseModel):
|
||
"""Neue Firma anlegen (durch Reseller oder SUPER_ADMIN).
|
||
|
||
Es wird automatisch ein erster COMPANY_ADMIN per Einladung angelegt.
|
||
"""
|
||
name: str = Field(min_length=2, max_length=255)
|
||
country: str = Field(default="DE", max_length=10)
|
||
plan: str = Field(default="trial", max_length=50)
|
||
admin_email: EmailStr
|
||
admin_first_name: str = Field(min_length=1, max_length=100)
|
||
admin_last_name: str = Field(min_length=1, max_length=100)
|
||
|
||
|
||
class TenantUpdate(BaseModel):
|
||
name: str | None = Field(None, min_length=2, max_length=255)
|
||
plan: str | None = Field(None, max_length=50)
|
||
is_active: bool | None = None
|
||
|
||
|
||
class TenantAssignReseller(BaseModel):
|
||
"""SUPER_ADMIN: Firma einem Reseller zuordnen (oder lösen mit null)."""
|
||
reseller_id: uuid.UUID | None = None
|
||
|
||
|
||
class ResellerOut(BaseModel):
|
||
model_config = {"from_attributes": True}
|
||
|
||
id: uuid.UUID
|
||
email: str
|
||
first_name: str
|
||
last_name: str
|
||
is_active: bool
|
||
created_at: datetime | None = None
|
||
company_count: int = 0
|
||
|
||
|
||
class ResellerCreate(BaseModel):
|
||
email: EmailStr
|
||
first_name: str = Field(min_length=1, max_length=100)
|
||
last_name: str = Field(min_length=1, max_length=100)
|