feat: Reseller-Rolle + SUPER_ADMIN-Mandantenübersicht
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>
This commit is contained in:
@@ -3,7 +3,7 @@ import uuid
|
||||
from datetime import datetime
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from sqlalchemy import Boolean, DateTime, Integer, String, Text
|
||||
from sqlalchemy import Boolean, DateTime, ForeignKey, Integer, String, Text
|
||||
from sqlalchemy.dialects.postgresql import JSONB, UUID
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
@@ -26,6 +26,17 @@ class Company(Base):
|
||||
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||
slug: Mapped[str] = mapped_column(String(100), unique=True, nullable=False)
|
||||
plan: Mapped[str] = mapped_column(String(50), default="trial")
|
||||
|
||||
# Mandanten-Status: deaktivierte Firmen können sich nicht mehr einloggen.
|
||||
# Von Reseller (eigene Firmen) bzw. SUPER_ADMIN umschaltbar.
|
||||
is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
|
||||
|
||||
# Reseller-Besitz: Firma wurde von diesem Reseller angelegt und wird von ihm
|
||||
# verwaltet (nur Firmen-Ebene, kein Zugriff auf personenbezogene Daten).
|
||||
# NULL = direkt von der Plattform (SUPER_ADMIN) verwaltet.
|
||||
reseller_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
UUID(as_uuid=True), ForeignKey("users.id", ondelete="SET NULL"), nullable=True, index=True
|
||||
)
|
||||
logo_url: Mapped[str | None] = mapped_column(Text)
|
||||
country: Mapped[str] = mapped_column(String(10), default="DE")
|
||||
state: Mapped[str | None] = mapped_column(String(10))
|
||||
@@ -71,7 +82,10 @@ class Company(Base):
|
||||
overtime_max_carryover_hours: Mapped[int | None] = mapped_column(Integer, nullable=True) # None = alles
|
||||
|
||||
# Relationships
|
||||
users: Mapped[list["User"]] = relationship("User", back_populates="company", lazy="noload")
|
||||
users: Mapped[list["User"]] = relationship(
|
||||
"User", back_populates="company", lazy="noload",
|
||||
foreign_keys="User.company_id",
|
||||
)
|
||||
departments: Mapped[list["Department"]] = relationship("Department", back_populates="company", lazy="noload")
|
||||
|
||||
def __repr__(self) -> str:
|
||||
|
||||
Reference in New Issue
Block a user