1815 lines
66 KiB
Markdown
1815 lines
66 KiB
Markdown
# Trustee Feature - Architektur und Implementierungsplan
|
||
|
||
## Executive Summary
|
||
|
||
Dieses Dokument beschreibt die Architektur und den Implementierungsplan für das **Trustee** Feature, welches das erste Feature in einem neuen Client-Management-System ist. Das Trustee Feature ermöglicht es Treuhandgesellschaften, die Spesenabrechnung und zugehörige Dokumente für ihre Kunden zentral zu verwalten.
|
||
|
||
## Inhaltsverzeichnis
|
||
|
||
1. [Übersicht](#overview)
|
||
2. [Architektur-Analyse](#architecture-analysis)
|
||
3. [Architektur-Entscheidungen](#architecture-decisions)
|
||
4. [System-Architektur](#system-architecture)
|
||
5. [Datenmodell](#data-model)
|
||
6. [RBAC-Integration](#rbac-integration)
|
||
7. [API-Design](#api-design)
|
||
8. [Implementierungsplan](#implementation-plan)
|
||
9. [Migrationsstrategie](#migration-strategy)
|
||
10. [Teststrategie](#testing-strategy)
|
||
11. [Implementierungsdetails](#implementation-details)
|
||
|
||
**Zugehörige Dokumente**:
|
||
- [UI-Spezifikation](./doc_trustee_feature_ui_specification.md) - Frontend-Implementierung (UI-Komponenten, FormGenerator-Pattern, React/TypeScript)
|
||
|
||
---
|
||
|
||
## Übersicht
|
||
|
||
### Zweck
|
||
|
||
Das Trustee Feature bietet ein zentralisiertes Spesenabrechnungssystem, bei dem:
|
||
- Benutzer ihre Spesen an ihre Treuhandgesellschaft melden können
|
||
- Treuhandgesellschaften Spesendaten zentral für alle ihre Kunden verwalten können
|
||
- Der Zugriff über ein feature-basiertes RBAC-System gesteuert wird
|
||
|
||
### Schlüsselkonzepte
|
||
|
||
1. **Feature-basiertes System**: Das System definiert Zugriffe für Features. "Trustee" ist das erste Feature.
|
||
2. **Datenbank-Interface**: Jedes Feature hat ein Datenbank-Interface (wie "chat") mit automatisierten Systemattributen (mandate, created, updated, etc.)
|
||
3. **RBAC-Integration**: Feature-Zugriff wird über RBAC-Ressourcen gesteuert
|
||
4. **Organisations-basierter Zugriff**: Der Zugriff wird auf Organisationsebene innerhalb des Trustee Features gesteuert
|
||
|
||
---
|
||
|
||
## Architektur-Analyse
|
||
|
||
### Verständnis des aktuellen Systems
|
||
|
||
Basierend auf der Codebase-Analyse:
|
||
|
||
1. **Interface Pattern**: Custom interfaces follow the pattern of `interfaceDbChatObjects.py`:
|
||
- Use `DatabaseConnector` for database operations
|
||
- Implement CRUD operations
|
||
- Integrate with RBAC through `interfaceRbac.py`
|
||
- Support standard attributes (mandate, _createdAt, _modifiedAt, _createdBy, _modifiedBy) - automatisch vom DatabaseConnector gesetzt
|
||
|
||
2. **RBAC System**:
|
||
- Uses `AccessRuleContext` enum: DATA, UI, RESOURCE
|
||
- Access levels: ALL, MY, GROUP, NONE
|
||
- Resources are defined as cascading strings (e.g., "ai.model.anthropic")
|
||
- UI elements use cascading strings (e.g., "playground.voice.settings")
|
||
- **Filterung auf DB-Ebene**: `getRecordsetWithRBAC()` filtert Daten automatisch basierend auf:
|
||
- `ALL`: Keine Filterung (alle Records)
|
||
- `MY`: Filter nach `_createdBy = userId` (nur eigene Records)
|
||
- `GROUP`: Filter nach `mandateId = user.mandateId` (nur Records der eigenen Gruppe)
|
||
- `NONE`: Keine Records sichtbar (`1 = 0` WHERE-Clause)
|
||
- **View-Permission**: Wird zuerst geprüft - wenn `view=false`, werden keine Records zurückgegeben
|
||
|
||
3. **Route Pattern**: Routes follow `routeData*.py` pattern:
|
||
- Use FastAPI routers
|
||
- Integrate with authentication via `getCurrentUser`
|
||
- Support pagination
|
||
- Use interface methods for data access
|
||
|
||
4. **Data Model Pattern**: Models in `datamodels/datamodel*.py`:
|
||
- Use Pydantic BaseModel
|
||
- Register labels with `registerModelLabels`
|
||
- Include frontend metadata in `json_schema_extra`
|
||
|
||
5. **DatabaseConnector Pattern**:
|
||
- **Automatische Tabellenerstellung**: `_ensureTableExists()` erstellt Tabellen automatisch aus Pydantic-Modellen
|
||
- **Automatische Spaltenerstellung**: Spalten werden aus Modell-Feldern generiert
|
||
- **Systemattribute**: Automatisch hinzugefügt: `_createdAt`, `_modifiedAt`, `_createdBy`, `_modifiedBy`
|
||
- **Index-Erstellung**: Automatische Indizes für Foreign Keys (Felder die auf `Id` enden)
|
||
- **Migration**: Fehlende Spalten werden automatisch hinzugefügt (additive Migration)
|
||
- **SQL-Typ-Mapping**: Automatische Konvertierung von Python-Typen zu PostgreSQL-Typen
|
||
|
||
### Architektur-Entscheidungen
|
||
|
||
1. **Feature-Registrierung**: Das Trustee Feature wird als neues Interface ähnlich wie Chat registriert
|
||
2. **RBAC-Ressourcen**:
|
||
- `ui.trustee` - UI-Zugriffskontrolle
|
||
- `resource.trustee` - Feature-Ressourcen-Zugriff
|
||
3. **Datenbank-Isolation**: Jedes Feature hat sein eigenes Datenbank-Interface, teilt aber dieselbe Datenbankinstanz
|
||
4. **Organisation vs Mandate**:
|
||
- **Mandate**: System-Level-Organisation (bestehendes Konzept)
|
||
- **Organisation**: Trustee-Feature-spezifische Firma (neues Konzept innerhalb des Trustee Features)
|
||
- Dies sind separate Konzepte - Organisation ist auf das Trustee Feature beschränkt
|
||
- **Beziehung**:
|
||
- Eine `mandate` kann mehrere `organisationId`s haben
|
||
- Eine `organisationId` gehört zu genau einer `mandate`
|
||
- `mandate` wird automatisch aus `currentUser.mandateId` gesetzt
|
||
- `organisationId` Dropdown zeigt alle gelieferten Organisationen (RBAC filtert automatisch)
|
||
- `organisationId`s können nicht über `mandate`-Grenzen hinweg geteilt werden
|
||
|
||
---
|
||
|
||
## System-Architektur
|
||
|
||
### Komponenten-Übersicht
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ Frontend (React/TS) │
|
||
│ - Trustee UI Components │
|
||
│ - Organisation Management │
|
||
│ - Contract Management │
|
||
│ - Expense Reporting (Position + Document) │
|
||
└──────────────────────┬──────────────────────────────────────┘
|
||
│
|
||
│ HTTP/REST API
|
||
│
|
||
┌──────────────────────▼──────────────────────────────────────┐
|
||
│ API Routes Layer │
|
||
│ - routeDataTrusteeOrganisations.py │
|
||
│ - routeDataTrusteeRoles.py │
|
||
│ - routeDataTrusteeAccess.py │
|
||
│ - routeDataTrusteeContracts.py │
|
||
│ - routeDataTrusteeDocuments.py │
|
||
│ - routeDataTrusteePositions.py │
|
||
│ - routeDataTrusteePositionDocuments.py │
|
||
└──────────────────────┬──────────────────────────────────────┘
|
||
│
|
||
│ Interface Methods
|
||
│
|
||
┌──────────────────────▼──────────────────────────────────────┐
|
||
│ Interface Layer (interfaceDbTrusteeObjects) │
|
||
│ - CRUD Operations │
|
||
│ - RBAC Filtering │
|
||
│ - Business Logic │
|
||
└──────────────────────┬──────────────────────────────────────┘
|
||
│
|
||
│ Database Connector
|
||
│
|
||
┌──────────────────────▼──────────────────────────────────────┐
|
||
│ Database Layer (PostgreSQL) │
|
||
│ - trustee.organisation │
|
||
│ - trustee.role │
|
||
│ - trustee.access │
|
||
│ - trustee.contract │
|
||
│ - trustee.document │
|
||
│ - trustee.position │
|
||
│ - trustee.xpositiondocument │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ RBAC System │
|
||
│ - resource.trustee (feature access) │
|
||
│ - ui.trustee (UI access) │
|
||
│ - Table-level access (DATA context) │
|
||
│ - Feature-level access (trustee.access table) │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### Interface-Struktur
|
||
|
||
Das Trustee-Interface folgt dem Muster von `interfaceDbChatObjects.py`:
|
||
|
||
```python
|
||
# gateway/modules/interfaces/interfaceDbTrusteeObjects.py
|
||
|
||
class TrusteeInterface:
|
||
"""
|
||
Interface for Trustee feature database operations.
|
||
Similar to ChatInterface but for trustee-specific data.
|
||
"""
|
||
|
||
def __init__(self, currentUser: User):
|
||
self.currentUser = currentUser
|
||
self.db = DatabaseConnector(...) # Trustee database
|
||
self.rbac = RbacClass(self.db, dbApp=dbApp)
|
||
|
||
# Organisation CRUD
|
||
def createOrganisation(...)
|
||
def getOrganisation(...)
|
||
def updateOrganisation(...)
|
||
def deleteOrganisation(...)
|
||
def getAllOrganisations(...)
|
||
|
||
# Role CRUD
|
||
def createRole(...)
|
||
def getAllRoles(...)
|
||
|
||
# Access CRUD
|
||
def createAccess(...)
|
||
def getUserAccessForOrganisation(...)
|
||
def checkUserPermission(...)
|
||
|
||
# Contract CRUD
|
||
def createContract(...)
|
||
def getContract(...)
|
||
# ... etc
|
||
|
||
# Document CRUD
|
||
def createDocument(...)
|
||
def getDocument(...)
|
||
def getDocumentData(...) # Binary data
|
||
|
||
# Position CRUD
|
||
def createPosition(...)
|
||
def getPosition(...)
|
||
# ... etc
|
||
|
||
# Cross-reference operations
|
||
def linkPositionDocument(...)
|
||
def unlinkPositionDocument(...)
|
||
def getDocumentsForPosition(...)
|
||
def getPositionsForDocument(...)
|
||
```
|
||
|
||
---
|
||
|
||
## Datenmodell
|
||
|
||
### Tabelle: `trustee.organisation`
|
||
|
||
Repräsentiert Treuhandgesellschaften (Organisationen) innerhalb des Trustee Features.
|
||
|
||
```python
|
||
class TrusteeOrganisation(BaseModel):
|
||
id: str = Field( # Unique string label (PK), not UUID
|
||
description="Unique organisation identifier (label)",
|
||
json_schema_extra={
|
||
"frontend_type": "text",
|
||
"frontend_readonly": False, # Änderbar bei Erstellung, danach readonly
|
||
"frontend_required": True
|
||
}
|
||
)
|
||
label: str = Field(
|
||
description="Company name",
|
||
json_schema_extra={
|
||
"frontend_type": "text",
|
||
"frontend_readonly": False,
|
||
"frontend_required": True
|
||
}
|
||
)
|
||
enabled: bool = Field(
|
||
default=True,
|
||
description="Whether the organisation is enabled",
|
||
json_schema_extra={
|
||
"frontend_type": "checkbox",
|
||
"frontend_readonly": False,
|
||
"frontend_required": False
|
||
}
|
||
)
|
||
mandate: str = Field( # System attribute
|
||
description="Mandate ID (system-level organisation)",
|
||
json_schema_extra={
|
||
"frontend_type": "text",
|
||
"frontend_readonly": True,
|
||
"frontend_required": False
|
||
}
|
||
)
|
||
# Systemattribute werden automatisch vom DatabaseConnector gesetzt:
|
||
# _createdAt, _modifiedAt, _createdBy, _modifiedBy
|
||
# Diese werden nicht im Modell definiert, aber im Backend automatisch verwaltet
|
||
# Systemattribute-Verwaltung:
|
||
# - _createdBy: Automatisch aus currentUser.id gesetzt
|
||
# - _createdAt: Automatisch beim Erstellen gesetzt (float, UTC timestamp in Sekunden)
|
||
# - _modifiedAt: Automatisch beim Update gesetzt (float, UTC timestamp in Sekunden)
|
||
# - _modifiedBy: Automatisch aus currentUser.id beim Update gesetzt
|
||
# - Frontend: Diese Felder werden als readonly angezeigt
|
||
# - Formatierung: Timestamps als float (UI rendert gemäß Zeitzoneneinstellungen), User-Namen statt User-ID
|
||
# - Sichtbarkeit: Kann in FormGeneric definiert werden, welche Felder angezeigt werden, aber sie müssen ans UI geliefert werden über die Routes
|
||
|
||
registerModelLabels(
|
||
"TrusteeOrganisation",
|
||
{"en": "Organisation", "fr": "Organisation"},
|
||
{
|
||
"id": {"en": "ID", "fr": "ID"},
|
||
"label": {"en": "Label", "fr": "Libellé"},
|
||
"enabled": {"en": "Enabled", "fr": "Activé"},
|
||
"mandate": {"en": "Mandate", "fr": "Mandat"},
|
||
},
|
||
)
|
||
```
|
||
|
||
**RBAC Rules**:
|
||
- `sysadmin`: Can manage all organisations
|
||
- `admin`: Can manage organisations for their group (mandate)
|
||
|
||
**Indexes**:
|
||
- Primary key on `id`
|
||
- Index on `mandate` for filtering
|
||
|
||
### Tabelle: `trustee.role`
|
||
|
||
Definiert Rollen innerhalb des Trustee Features.
|
||
|
||
```python
|
||
class TrusteeRole(BaseModel):
|
||
id: str = Field( # Unique string label (PK), not UUID
|
||
description="Unique role identifier (label)",
|
||
json_schema_extra={
|
||
"frontend_type": "text",
|
||
"frontend_readonly": False,
|
||
"frontend_required": True
|
||
}
|
||
)
|
||
desc: str = Field(
|
||
description="Role description",
|
||
json_schema_extra={
|
||
"frontend_type": "textarea",
|
||
"frontend_readonly": False,
|
||
"frontend_required": True
|
||
}
|
||
)
|
||
mandate: str = Field( # System attribute
|
||
description="Mandate ID",
|
||
json_schema_extra={
|
||
"frontend_type": "text",
|
||
"frontend_readonly": True,
|
||
"frontend_required": False
|
||
}
|
||
)
|
||
# Systemattribute werden automatisch vom DatabaseConnector gesetzt
|
||
|
||
registerModelLabels(
|
||
"TrusteeRole",
|
||
{"en": "Role", "fr": "Rôle"},
|
||
{
|
||
"id": {"en": "ID", "fr": "ID"},
|
||
"desc": {"en": "Description", "fr": "Description"},
|
||
"mandate": {"en": "Mandate", "fr": "Mandat"},
|
||
},
|
||
)
|
||
```
|
||
|
||
**Initiale Rollen**:
|
||
- `userreport`: Kann Benutzerdokumente an das System liefern
|
||
- `admin`: Kann den Zugriff administrieren
|
||
- `operate`: Kann Daten für Operationen verwenden
|
||
|
||
**RBAC Rules**:
|
||
- `sysadmin`: Can manage all roles
|
||
|
||
**Indexes**:
|
||
- Primary key on `id`
|
||
- Index on `mandate` for filtering
|
||
|
||
### Tabelle: `trustee.access`
|
||
|
||
Definiert Benutzerzugriff auf Organisationen mit spezifischen Rollen. Der Zugriff kann optional auf einen spezifischen Contract beschränkt werden.
|
||
|
||
```python
|
||
class TrusteeAccess(BaseModel):
|
||
id: str = Field( # UUID PK
|
||
default_factory=lambda: str(uuid.uuid4()),
|
||
json_schema_extra={
|
||
"frontend_type": "text",
|
||
"frontend_readonly": True,
|
||
"frontend_required": False
|
||
}
|
||
)
|
||
organisationId: str = Field(
|
||
description="Reference to trustee.organisation.id",
|
||
json_schema_extra={
|
||
"frontend_type": "select",
|
||
"frontend_readonly": False,
|
||
"frontend_required": True,
|
||
"frontend_options": "trustee.organisation" # String-Referenz für dynamische Options
|
||
}
|
||
)
|
||
roleId: str = Field(
|
||
description="Reference to trustee.role.id",
|
||
json_schema_extra={
|
||
"frontend_type": "select",
|
||
"frontend_readonly": False,
|
||
"frontend_required": True,
|
||
"frontend_options": "trustee.role" # String-Referenz für dynamische Options
|
||
}
|
||
)
|
||
userId: str = Field(
|
||
description="User ID assigned to this role",
|
||
json_schema_extra={
|
||
"frontend_type": "select",
|
||
"frontend_readonly": False,
|
||
"frontend_required": True,
|
||
"frontend_options": "user" # String-Referenz für User-Liste
|
||
}
|
||
)
|
||
contractId: Optional[str] = Field(
|
||
default=None,
|
||
description="Optional reference to trustee.contract.id. If None, access is for full organisation. If set, access is limited to this specific contract.",
|
||
json_schema_extra={
|
||
"frontend_type": "select",
|
||
"frontend_readonly": False,
|
||
"frontend_required": False,
|
||
"frontend_options": "trustee.contract", # String-Referenz, dynamisch gefiltert nach organisationId
|
||
"frontend_depends_on": "organisationId" # Dropdown wird aktualisiert wenn organisationId geändert wird
|
||
}
|
||
)
|
||
mandate: str = Field( # System attribute
|
||
description="Mandate ID",
|
||
json_schema_extra={
|
||
"frontend_type": "text",
|
||
"frontend_readonly": True,
|
||
"frontend_required": False
|
||
}
|
||
)
|
||
# Systemattribute werden automatisch vom DatabaseConnector gesetzt
|
||
|
||
registerModelLabels(
|
||
"TrusteeAccess",
|
||
{"en": "Access", "fr": "Accès"},
|
||
{
|
||
"id": {"en": "ID", "fr": "ID"},
|
||
"organisationId": {"en": "Organisation", "fr": "Organisation"},
|
||
"roleId": {"en": "Role", "fr": "Rôle"},
|
||
"userId": {"en": "User", "fr": "Utilisateur"},
|
||
"contractId": {"en": "Contract (optional)", "fr": "Contrat (optionnel)"},
|
||
"mandate": {"en": "Mandate", "fr": "Mandat"},
|
||
},
|
||
)
|
||
```
|
||
|
||
**Zugriffslogik**:
|
||
- **Ohne `contractId` (None)**: Zugriff gilt für die gesamte Organisation
|
||
- **Mit `contractId`**: Zugriff ist auf diesen spezifischen Contract beschränkt
|
||
- **RBAC-Filterung**: Bei der Datenfilterung wird geprüft:
|
||
- Hat User Access für Organisation (ohne Contract) → Zugriff auf alle Contracts dieser Organisation
|
||
- Hat User Access für Organisation + spezifischen Contract → Zugriff nur auf diesen Contract
|
||
|
||
**RBAC Rules**:
|
||
- `sysadmin`: Can manage all access records
|
||
- `admin`: Can manage access for their group (mandate)
|
||
- Users with `admin` role in trustee.access can manage access for their organisation
|
||
|
||
**Indexes**:
|
||
- Primary key on `id`
|
||
- Unique constraint on `(organisationId, roleId, userId, contractId)` to prevent duplicates (erlaubt aber mehrere Rollen pro Benutzer-Organisation durch separate Records)
|
||
- Index on `organisationId`
|
||
- Index on `userId`
|
||
- Index on `roleId`
|
||
- Index on `contractId`
|
||
- Index on `mandate`
|
||
|
||
### Tabelle: `trustee.contract`
|
||
|
||
Definiert Kundenverträge innerhalb von Organisationen.
|
||
|
||
```python
|
||
class TrusteeContract(BaseModel):
|
||
id: str = Field( # UUID PK
|
||
default_factory=lambda: str(uuid.uuid4()),
|
||
json_schema_extra={
|
||
"frontend_type": "text",
|
||
"frontend_readonly": True,
|
||
"frontend_required": False
|
||
}
|
||
)
|
||
organisationId: str = Field(
|
||
description="Reference to trustee.organisation.id",
|
||
json_schema_extra={
|
||
"frontend_type": "select",
|
||
"frontend_readonly": False, # Änderbar bei Erstellung, danach readonly (immutable)
|
||
"frontend_required": True,
|
||
"frontend_options": "trustee.organisation"
|
||
}
|
||
)
|
||
label: str = Field(
|
||
description="Label for the customer contract (e.g., 'Muster AG 2026')",
|
||
json_schema_extra={
|
||
"frontend_type": "text",
|
||
"frontend_readonly": False,
|
||
"frontend_required": True
|
||
}
|
||
)
|
||
enabled: bool = Field(
|
||
default=True,
|
||
description="Whether the contract is enabled",
|
||
json_schema_extra={
|
||
"frontend_type": "checkbox",
|
||
"frontend_readonly": False,
|
||
"frontend_required": False
|
||
}
|
||
)
|
||
mandate: str = Field( # System attribute
|
||
description="Mandate ID",
|
||
json_schema_extra={
|
||
"frontend_type": "text",
|
||
"frontend_readonly": True,
|
||
"frontend_required": False
|
||
}
|
||
)
|
||
# Systemattribute werden automatisch vom DatabaseConnector gesetzt
|
||
|
||
registerModelLabels(
|
||
"TrusteeContract",
|
||
{"en": "Contract", "fr": "Contrat"},
|
||
{
|
||
"id": {"en": "ID", "fr": "ID"},
|
||
"organisationId": {"en": "Organisation", "fr": "Organisation"},
|
||
"label": {"en": "Label", "fr": "Libellé"},
|
||
"enabled": {"en": "Enabled", "fr": "Activé"},
|
||
"mandate": {"en": "Mandate", "fr": "Mandat"},
|
||
},
|
||
)
|
||
```
|
||
|
||
**RBAC Rules**:
|
||
- `sysadmin`: Can manage all contracts
|
||
- `admin`: Can manage contracts for their group (mandate)
|
||
- Users with `admin` role in trustee.access: Can CRUD contracts for their organisationId
|
||
- Wenn `contractId` in trustee.access leer: Zugriff auf alle Contracts der Organisation
|
||
- Wenn `contractId` gesetzt: Zugriff nur auf diesen spezifischen Contract
|
||
- New records default to their own organisationId
|
||
- Dropdown to select from granted organisationIds (und optional Contracts)
|
||
|
||
**Wichtig**: Verträge sind unveränderlich bezüglich `organisationId` - kann nach der Erstellung nicht mehr geändert werden.
|
||
|
||
**Implementierung**:
|
||
- **Backend-Validierung**: In `updateContract()` prüfen: Wenn `organisationId` im Update vorhanden und unterschiedlich zum bestehenden Wert → Fehler
|
||
- **Frontend-Readonly**: `organisationId` wird auf readonly gesetzt wenn `id` vorhanden (non-blank) ist
|
||
- **Logik**: ID kann nur gespeichert werden, wenn non-blank. Eine non-blank ID kann nicht mehr geändert werden
|
||
|
||
**Indexes**:
|
||
- Primary key on `id`
|
||
- Index on `organisationId`
|
||
- Index on `mandate`
|
||
- **Wichtig**: `organisationId` ist immutable nach Erstellung - keine Updates erlaubt
|
||
|
||
**Validierung**:
|
||
- `id` Format: Alphanumerisch + Bindestrich/Unterstrich
|
||
- Länge: 3-50 Zeichen
|
||
- Validierung: Backend + Frontend
|
||
|
||
### Tabelle: `trustee.document`
|
||
|
||
Enthält Dokumentreferenzen und Belege für Buchungen.
|
||
|
||
```python
|
||
class TrusteeDocument(BaseModel):
|
||
id: str = Field( # UUID PK
|
||
default_factory=lambda: str(uuid.uuid4()),
|
||
json_schema_extra={
|
||
"frontend_type": "text",
|
||
"frontend_readonly": True,
|
||
"frontend_required": False
|
||
}
|
||
)
|
||
organisationId: str = Field(
|
||
description="Reference to trustee.organisation.id",
|
||
json_schema_extra={
|
||
"frontend_type": "select",
|
||
"frontend_readonly": False,
|
||
"frontend_required": True,
|
||
"frontend_options": "trustee.organisation"
|
||
}
|
||
)
|
||
contractId: str = Field(
|
||
description="Reference to trustee.contract.id",
|
||
json_schema_extra={
|
||
"frontend_type": "select",
|
||
"frontend_readonly": False,
|
||
"frontend_required": True,
|
||
"frontend_options": "trustee.contract" # Gefiltert nach organisationId
|
||
}
|
||
)
|
||
documentData: bytes = Field( # Binary data
|
||
description="The file content",
|
||
json_schema_extra={
|
||
"frontend_type": "file", # Für File Upload
|
||
"frontend_readonly": False,
|
||
"frontend_required": True
|
||
}
|
||
)
|
||
documentName: str = Field(
|
||
description="File name (e.g., 'Beleg.pdf')",
|
||
json_schema_extra={
|
||
"frontend_type": "text",
|
||
"frontend_readonly": False,
|
||
"frontend_required": True
|
||
}
|
||
)
|
||
documentMimeType: str = Field(
|
||
description="MIME type of the document",
|
||
json_schema_extra={
|
||
"frontend_type": "select",
|
||
"frontend_readonly": False,
|
||
"frontend_required": True,
|
||
"frontend_options": [
|
||
{"value": "application/pdf", "label": {"en": "PDF", "fr": "PDF"}},
|
||
{"value": "image/jpeg", "label": {"en": "JPEG", "fr": "JPEG"}},
|
||
{"value": "image/png", "label": {"en": "PNG", "fr": "PNG"}},
|
||
# ... weitere MIME-Types
|
||
]
|
||
}
|
||
)
|
||
mandate: str = Field( # System attribute
|
||
description="Mandate ID",
|
||
json_schema_extra={
|
||
"frontend_type": "text",
|
||
"frontend_readonly": True,
|
||
"frontend_required": False
|
||
}
|
||
)
|
||
# Systemattribute werden automatisch vom DatabaseConnector gesetzt
|
||
|
||
registerModelLabels(
|
||
"TrusteeDocument",
|
||
{"en": "Document", "fr": "Document"},
|
||
{
|
||
"id": {"en": "ID", "fr": "ID"},
|
||
"organisationId": {"en": "Organisation", "fr": "Organisation"},
|
||
"contractId": {"en": "Contract", "fr": "Contrat"},
|
||
"documentData": {"en": "Document Data", "fr": "Données du document"},
|
||
"documentName": {"en": "Document Name", "fr": "Nom du document"},
|
||
"documentMimeType": {"en": "MIME Type", "fr": "Type MIME"},
|
||
"mandate": {"en": "Mandate", "fr": "Mandat"},
|
||
},
|
||
)
|
||
```
|
||
|
||
**RBAC Rules**:
|
||
- `sysadmin`: Can manage all documents
|
||
- `admin`: Can manage documents for their group (mandate)
|
||
- Users with `operate` role in trustee.access: Can CRUD documents in their organisationId
|
||
- Users with `userreport` role in trustee.access: Can CRUD own documents (_createdBy = userId, automatisch über Systemattribute gesetzt)
|
||
|
||
**Speicherung**:
|
||
- Dokument-Binärdaten werden in PostgreSQL BYTEA-Spalte gespeichert
|
||
- Einfach, transaktional, einfaches Backup
|
||
|
||
**File Upload/Download**:
|
||
- **Nicht direkt integriert**: File Upload/Download erfolgt über das Workflow-System mit einer Action
|
||
- Die Action erstellt automatisch die Datensätze in `trustee.document`
|
||
- Dies ist nicht Teil der direkten Trustee-Feature-Implementierung
|
||
|
||
**Indexes**:
|
||
- Primary key on `id`
|
||
- Index on `organisationId`
|
||
- Index on `contractId`
|
||
- Index on `mandate`
|
||
- Index on `_created_at` (für userreport Filterung)
|
||
- Index auf `_created_by` (automatisch über Systemattribute, für userreport Filterung)
|
||
|
||
### Tabelle: `trustee.position`
|
||
|
||
Enthält Buchungspositionen (Speseneinträge).
|
||
|
||
```python
|
||
class TrusteePosition(BaseModel):
|
||
id: str = Field( # UUID PK
|
||
default_factory=lambda: str(uuid.uuid4()),
|
||
json_schema_extra={
|
||
"frontend_type": "text",
|
||
"frontend_readonly": True,
|
||
"frontend_required": False
|
||
}
|
||
)
|
||
organisationId: str = Field(
|
||
description="Reference to trustee.organisation.id",
|
||
json_schema_extra={
|
||
"frontend_type": "select",
|
||
"frontend_readonly": False,
|
||
"frontend_required": True,
|
||
"frontend_options": "trustee.organisation"
|
||
}
|
||
)
|
||
contractId: str = Field(
|
||
description="Reference to trustee.contract.id",
|
||
json_schema_extra={
|
||
"frontend_type": "select",
|
||
"frontend_readonly": False,
|
||
"frontend_required": True,
|
||
"frontend_options": "trustee.contract" # Gefiltert nach organisationId
|
||
}
|
||
)
|
||
valuta: date = Field(
|
||
description="Value date",
|
||
json_schema_extra={
|
||
"frontend_type": "date",
|
||
"frontend_readonly": False,
|
||
"frontend_required": True
|
||
}
|
||
)
|
||
transactionDateTime: datetime = Field(
|
||
description="Transaction timestamp",
|
||
json_schema_extra={
|
||
"frontend_type": "timestamp",
|
||
"frontend_readonly": False,
|
||
"frontend_required": True
|
||
}
|
||
)
|
||
company: str = Field(
|
||
default="",
|
||
description="Company name",
|
||
json_schema_extra={
|
||
"frontend_type": "text",
|
||
"frontend_readonly": False,
|
||
"frontend_required": False
|
||
}
|
||
)
|
||
desc: str = Field(
|
||
default="",
|
||
description="Description",
|
||
json_schema_extra={
|
||
"frontend_type": "textarea",
|
||
"frontend_readonly": False,
|
||
"frontend_required": False
|
||
}
|
||
)
|
||
tags: str = Field(
|
||
default="",
|
||
description="Tags (comma-separated or JSON)",
|
||
json_schema_extra={
|
||
"frontend_type": "text",
|
||
"frontend_readonly": False,
|
||
"frontend_required": False
|
||
}
|
||
)
|
||
bookingCurrency: str = Field(
|
||
description="Booking currency code",
|
||
json_schema_extra={
|
||
"frontend_type": "select",
|
||
"frontend_readonly": False,
|
||
"frontend_required": True,
|
||
"frontend_options": [
|
||
{"value": "CHF", "label": {"en": "CHF", "fr": "CHF"}},
|
||
{"value": "EUR", "label": {"en": "EUR", "fr": "EUR"}},
|
||
{"value": "USD", "label": {"en": "USD", "fr": "USD"}},
|
||
# ... weitere Währungen
|
||
]
|
||
}
|
||
)
|
||
bookingAmount: float = Field(
|
||
description="Booking amount",
|
||
json_schema_extra={
|
||
"frontend_type": "number",
|
||
"frontend_readonly": False,
|
||
"frontend_required": True
|
||
}
|
||
)
|
||
originalCurrency: str = Field(
|
||
description="Original currency code",
|
||
json_schema_extra={
|
||
"frontend_type": "select",
|
||
"frontend_readonly": False,
|
||
"frontend_required": True,
|
||
"frontend_options": [
|
||
{"value": "CHF", "label": {"en": "CHF", "fr": "CHF"}},
|
||
{"value": "EUR", "label": {"en": "EUR", "fr": "EUR"}},
|
||
{"value": "USD", "label": {"en": "USD", "fr": "USD"}},
|
||
]
|
||
}
|
||
)
|
||
originalAmount: float = Field(
|
||
description="Original amount (manuelle Eingabe in Phase 1, keine automatische Währungsumrechnung)",
|
||
json_schema_extra={
|
||
"frontend_type": "number",
|
||
"frontend_readonly": False,
|
||
"frontend_required": True
|
||
}
|
||
)
|
||
vatPercentage: float = Field(
|
||
default=0.0,
|
||
description="VAT percentage",
|
||
json_schema_extra={
|
||
"frontend_type": "number",
|
||
"frontend_readonly": False,
|
||
"frontend_required": False
|
||
}
|
||
)
|
||
vatAmount: float = Field(
|
||
default=0.0,
|
||
description="VAT amount (wird automatisch berechnet: bookingAmount * vatPercentage / 100, kann manuell überschrieben werden)",
|
||
json_schema_extra={
|
||
"frontend_type": "number",
|
||
"frontend_readonly": False, # Editierbar für manuelle Überschreibung
|
||
"frontend_required": False
|
||
}
|
||
)
|
||
# MwSt-Berechnung:
|
||
# - Automatisch beim Ändern von bookingAmount oder vatPercentage
|
||
# - Wenn vatAmount manuell geändert wird, wird automatische Berechnung erneut durchgeführt
|
||
# - Warnung (Toast) erscheint bereits beim Ändern, nicht erst beim Speichern
|
||
mandate: str = Field( # System attribute
|
||
description="Mandate ID",
|
||
json_schema_extra={
|
||
"frontend_type": "text",
|
||
"frontend_readonly": True,
|
||
"frontend_required": False
|
||
}
|
||
)
|
||
# Systemattribute werden automatisch vom DatabaseConnector gesetzt
|
||
|
||
registerModelLabels(
|
||
"TrusteePosition",
|
||
{"en": "Position", "fr": "Position"},
|
||
{
|
||
"id": {"en": "ID", "fr": "ID"},
|
||
"organisationId": {"en": "Organisation", "fr": "Organisation"},
|
||
"contractId": {"en": "Contract", "fr": "Contrat"},
|
||
"valuta": {"en": "Value Date", "fr": "Date de valeur"},
|
||
"transactionDateTime": {"en": "Transaction Date/Time", "fr": "Date/Heure de transaction"},
|
||
"company": {"en": "Company", "fr": "Entreprise"},
|
||
"desc": {"en": "Description", "fr": "Description"},
|
||
"tags": {"en": "Tags", "fr": "Tags"},
|
||
"bookingCurrency": {"en": "Booking Currency", "fr": "Devise de comptabilisation"},
|
||
"bookingAmount": {"en": "Booking Amount", "fr": "Montant de comptabilisation"},
|
||
"originalCurrency": {"en": "Original Currency", "fr": "Devise d'origine"},
|
||
"originalAmount": {"en": "Original Amount", "fr": "Montant d'origine"},
|
||
"vatPercentage": {"en": "VAT Percentage", "fr": "Pourcentage TVA"},
|
||
"vatAmount": {"en": "VAT Amount", "fr": "Montant TVA"},
|
||
"mandate": {"en": "Mandate", "fr": "Mandat"},
|
||
},
|
||
)
|
||
```
|
||
|
||
**RBAC Rules**:
|
||
- `sysadmin`: Can manage all positions
|
||
- `admin`: Can manage positions for their group (mandate)
|
||
- Users with `operate` role in trustee.access: Can CRUD positions in their organisationId
|
||
- Wenn `contractId` in trustee.access leer: Zugriff auf alle Positions der Organisation
|
||
- Wenn `contractId` gesetzt: Zugriff nur auf Positions dieses Contracts
|
||
- Users with `userreport` role in trustee.access: Can CRUD own positions (_createdBy = userId, automatisch über Systemattribute gesetzt)
|
||
- Contract-Filterung gilt auch für userreport (nur eigene Positions in erlaubten Contracts)
|
||
|
||
**Indexes**:
|
||
- Primary key on `id`
|
||
- Index on `organisationId`
|
||
- Index on `contractId`
|
||
- Index on `valuta` (for date-based queries)
|
||
- Index on `transactionDateTime`
|
||
- Index on `mandate`
|
||
- Index auf `_createdBy` (automatisch über Systemattribute, für userreport Filterung)
|
||
|
||
### Tabelle: `trustee.xpositiondocument`
|
||
|
||
Verknüpfungstabelle, die Positionen mit Dokumenten verknüpft (viele-zu-viele).
|
||
|
||
```python
|
||
class TrusteePositionDocument(BaseModel):
|
||
id: str = Field( # UUID PK
|
||
default_factory=lambda: str(uuid.uuid4()),
|
||
json_schema_extra={
|
||
"frontend_type": "text",
|
||
"frontend_readonly": True,
|
||
"frontend_required": False
|
||
}
|
||
)
|
||
organisationId: str = Field(
|
||
description="Reference to trustee.organisation.id",
|
||
json_schema_extra={
|
||
"frontend_type": "select",
|
||
"frontend_readonly": False,
|
||
"frontend_required": True,
|
||
"frontend_options": "trustee.organisation"
|
||
}
|
||
)
|
||
contractId: str = Field(
|
||
description="Reference to trustee.contract.id",
|
||
json_schema_extra={
|
||
"frontend_type": "select",
|
||
"frontend_readonly": False,
|
||
"frontend_required": True,
|
||
"frontend_options": "trustee.contract"
|
||
}
|
||
)
|
||
documentId: str = Field(
|
||
description="Reference to trustee.document.id",
|
||
json_schema_extra={
|
||
"frontend_type": "select",
|
||
"frontend_readonly": False,
|
||
"frontend_required": True,
|
||
"frontend_options": "trustee.document" # Gefiltert nach organisationId/contractId
|
||
}
|
||
)
|
||
positionId: str = Field(
|
||
description="Reference to trustee.position.id",
|
||
json_schema_extra={
|
||
"frontend_type": "select",
|
||
"frontend_readonly": False,
|
||
"frontend_required": True,
|
||
"frontend_options": "trustee.position" # Gefiltert nach organisationId/contractId
|
||
}
|
||
)
|
||
mandate: str = Field( # System attribute
|
||
description="Mandate ID",
|
||
json_schema_extra={
|
||
"frontend_type": "text",
|
||
"frontend_readonly": True,
|
||
"frontend_required": False
|
||
}
|
||
)
|
||
# Systemattribute werden automatisch vom DatabaseConnector gesetzt
|
||
|
||
registerModelLabels(
|
||
"TrusteePositionDocument",
|
||
{"en": "Position-Document Link", "fr": "Lien Position-Document"},
|
||
{
|
||
"id": {"en": "ID", "fr": "ID"},
|
||
"organisationId": {"en": "Organisation", "fr": "Organisation"},
|
||
"contractId": {"en": "Contract", "fr": "Contrat"},
|
||
"documentId": {"en": "Document", "fr": "Document"},
|
||
"positionId": {"en": "Position", "fr": "Position"},
|
||
"mandate": {"en": "Mandate", "fr": "Mandat"},
|
||
},
|
||
)
|
||
```
|
||
<|tool▁calls▁begin|><|tool▁call▁begin|>
|
||
read_file
|
||
|
||
**RBAC Rules**:
|
||
- `sysadmin`: Can manage all cross-references
|
||
- `admin`: Can manage cross-references for their group (mandate)
|
||
- Users with `operate` role in trustee.access: Can CRUD cross-references in their organisationId
|
||
- Wenn `contractId` in trustee.access leer: Zugriff auf alle Cross-References der Organisation
|
||
- Wenn `contractId` gesetzt: Zugriff nur auf Cross-References dieses Contracts
|
||
- Users with `userreport` role in trustee.access: Can CRUD cross-references for their own positions/documents (createdBy = userId, automatisch über Systemattribute gesetzt)
|
||
- Contract-Filterung gilt auch für userreport (nur eigene Cross-References in erlaubten Contracts)
|
||
|
||
**Wichtig**: Verknüpfungen sind optional - Positionen können ohne Dokumente existieren, Dokumente können ohne Positionen existieren.
|
||
|
||
**Indexes**:
|
||
- Primary key on `id`
|
||
- Unique constraint on `(positionId, documentId)` to prevent duplicates
|
||
- Index on `organisationId`
|
||
- Index on `contractId`
|
||
- Index on `documentId`
|
||
- Index on `positionId`
|
||
- Index on `mandate`
|
||
- Index auf `_created_by` (automatisch über Systemattribute, für userreport Filterung)
|
||
|
||
---
|
||
|
||
## RBAC-Integration
|
||
|
||
### System-Level RBAC
|
||
|
||
Das Trustee Feature integriert sich auf mehreren Ebenen mit dem bestehenden RBAC-System:
|
||
|
||
#### 1. Feature Access (`resource.trustee`)
|
||
|
||
**AccessRule**:
|
||
```python
|
||
AccessRule(
|
||
roleLabel="admin", # or appropriate system role
|
||
context=AccessRuleContext.RESOURCE,
|
||
item="trustee",
|
||
view=True,
|
||
# No read/create/update/delete for RESOURCE context
|
||
)
|
||
```
|
||
|
||
**Zweck**: Steuert, ob ein Benutzer überhaupt auf das Trustee Feature zugreifen kann.
|
||
|
||
#### 2. UI Access (`ui.trustee`)
|
||
|
||
**AccessRule**:
|
||
```python
|
||
AccessRule(
|
||
roleLabel="admin",
|
||
context=AccessRuleContext.UI,
|
||
item="trustee",
|
||
view=True,
|
||
)
|
||
```
|
||
|
||
**Zweck**: Steuert die UI-Sichtbarkeit für das Trustee Feature.
|
||
|
||
#### 3. Table-Level Access (DATA context)
|
||
|
||
Each trustee table needs DATA context access rules:
|
||
|
||
```python
|
||
# Example for trustee.organisation
|
||
AccessRule(
|
||
roleLabel="admin",
|
||
context=AccessRuleContext.DATA,
|
||
item="trustee.organisation",
|
||
view=True,
|
||
read=AccessLevel.GROUP, # Can read group records
|
||
create=AccessLevel.GROUP,
|
||
update=AccessLevel.GROUP,
|
||
delete=AccessLevel.GROUP,
|
||
)
|
||
```
|
||
|
||
### Feature-Level RBAC (trustee.access Tabelle)
|
||
|
||
Die `trustee.access` Tabelle erweitert System-RBAC mit Feature-spezifischen Berechtigungen:
|
||
|
||
1. **Rollen-basierter Zugriff**: Benutzern werden Rollen (`userreport`, `admin`, `operate`) für spezifische Organisationen zugewiesen
|
||
2. **Contract-basierter Zugriff (optional)**:
|
||
- Wenn `contractId` nicht gesetzt (None): Zugriff gilt für die gesamte Organisation
|
||
- Wenn `contractId` gesetzt: Zugriff ist auf diesen spezifischen Contract beschränkt
|
||
3. **Berechtigungsprüfung**: Bei der Durchführung von Operationen prüfen:
|
||
- System RBAC (Feature-Zugriff, Tabellen-Zugriff)
|
||
- Feature RBAC (trustee.access Tabelle für Organisation, Rolle und optional Contract)
|
||
|
||
### Permission Checking Logic
|
||
|
||
```python
|
||
def checkTrusteePermission(
|
||
user: User,
|
||
organisationId: str,
|
||
operation: str, # 'read', 'create', 'update', 'delete'
|
||
resource: str, # 'contract', 'document', 'position', etc.
|
||
recordId: Optional[str] = None,
|
||
contractId: Optional[str] = None # Optional: für Contract-basierte Filterung
|
||
) -> bool:
|
||
"""
|
||
Check if user has permission for trustee operation.
|
||
|
||
Steps:
|
||
1. Check system RBAC: resource.trustee access
|
||
2. Check system RBAC: table-level access (trustee.{resource})
|
||
3. Check feature RBAC: trustee.access table
|
||
- Get user's roles for organisationId (und optional contractId)
|
||
- Check if role allows operation on resource
|
||
- Wenn contractId angegeben: Prüfe ob User Access für diesen Contract hat
|
||
"""
|
||
|
||
# Step 1: Feature access
|
||
if not checkSystemRbac(user, "resource.trustee"):
|
||
return False
|
||
|
||
# Step 2: Table access
|
||
tableName = f"trustee.{resource}"
|
||
if not checkSystemRbac(user, tableName, operation):
|
||
return False
|
||
|
||
# Step 3: Feature-level access
|
||
# Get user's access records for this organisation
|
||
userAccessRecords = getTrusteeAccessForUser(user.id, organisationId)
|
||
|
||
# Filter by contract if contractId is specified
|
||
if contractId:
|
||
# User muss Access für diesen spezifischen Contract haben
|
||
# Oder Access ohne contractId (full organisation access)
|
||
userAccessRecords = [
|
||
acc for acc in userAccessRecords
|
||
if acc.contractId is None or acc.contractId == contractId
|
||
]
|
||
else:
|
||
# Wenn kein contractId angegeben: User muss Access haben (mit oder ohne contractId)
|
||
pass
|
||
|
||
userRoles = [acc.roleId for acc in userAccessRecords]
|
||
|
||
# Check role permissions
|
||
if "admin" in userRoles:
|
||
# Admin can do everything for their organisation (or specific contract)
|
||
return True
|
||
|
||
if operation in ["read", "create", "update", "delete"]:
|
||
if "operate" in userRoles:
|
||
# Operate can CRUD for organisation (or specific contract)
|
||
if resource in ["contract", "document", "position", "xpositiondocument"]:
|
||
return True
|
||
|
||
if "userreport" in userRoles:
|
||
# Userreport can CRUD own records
|
||
if recordId:
|
||
record = getRecord(resource, recordId)
|
||
if record._createdBy == user.id: # _createdBy wird automatisch vom DatabaseConnector gesetzt
|
||
return True
|
||
else:
|
||
# Creating new record - allowed
|
||
return True
|
||
|
||
return False
|
||
```
|
||
|
||
### Rollen-Berechtigungs-Matrix
|
||
|
||
| Role | Organisation | Contract | Document | Position | PositionDocument |
|
||
|------|--------------|----------|-----------|----------|------------------|
|
||
| **admin** (system) | CRUD (group) | CRUD (group) | CRUD (group) | CRUD (group) | CRUD (group) |
|
||
| **admin** (trustee.access) | CRUD (org) | CRUD (org) | CRUD (org) | CRUD (org) | CRUD (org) |
|
||
| **operate** | Read (org) | CRUD (org) | CRUD (org) | CRUD (org) | CRUD (org) |
|
||
| **userreport** | Read (org) | Read (org) | CRUD (own) | CRUD (own) | CRUD (own) |
|
||
|
||
**Legend**:
|
||
- CRUD: Create, Read, Update, Delete
|
||
- (group): Within user's mandate/group
|
||
- (org): Within assigned organisation
|
||
- (own): Only records created by the user
|
||
|
||
---
|
||
|
||
## API-Design
|
||
|
||
### RBAC-Berechtigungsabfrage
|
||
|
||
Das UI benötigt Berechtigungsinformationen, um die richtigen Komponenten zu rendern. Hierfür stehen folgende RBAC-Routen zur Verfügung:
|
||
|
||
#### Einzelne Berechtigung abfragen
|
||
```python
|
||
GET /api/rbac/permissions?context=UI&item=trustee
|
||
GET /api/rbac/permissions?context=RESOURCE&item=trustee
|
||
```
|
||
Gibt `UserPermissions` für einen spezifischen Kontext und Item zurück.
|
||
|
||
#### Alle Berechtigungen abrufen (für UI-Initialisierung)
|
||
```python
|
||
GET /api/rbac/permissions/all # Alle UI- und RESOURCE-Berechtigungen
|
||
GET /api/rbac/permissions/all?context=UI # Nur UI-Berechtigungen
|
||
GET /api/rbac/permissions/all?context=RESOURCE # Nur RESOURCE-Berechtigungen
|
||
```
|
||
Gibt alle Berechtigungen des aktuellen Users zurück:
|
||
```json
|
||
{
|
||
"ui": {
|
||
"trustee": {"view": true, "read": null, "create": null, "update": null, "delete": null},
|
||
"trustee.organisation": {"view": true, ...},
|
||
"trustee.role": {"view": true, ...},
|
||
...
|
||
},
|
||
"resource": {
|
||
"trustee": {"view": true, ...},
|
||
...
|
||
}
|
||
}
|
||
```
|
||
|
||
**Verwendung**:
|
||
- **UI-Initialisierung**: Einmaliger Call zu `/api/rbac/permissions/all` beim Laden der Anwendung
|
||
- **Spezifische Prüfung**: `/api/rbac/permissions?context=UI&item=trustee.organisation` für dynamische Berechtigungsprüfungen
|
||
|
||
### Route-Struktur
|
||
|
||
Alle Trustee-Routen folgen dem Muster `/api/trustee/{resource}`:
|
||
|
||
#### Organisation Routes (`routeDataTrusteeOrganisations.py`)
|
||
|
||
```python
|
||
GET /api/trustee/organisations/ # List organisations (filtered by RBAC)
|
||
GET /api/trustee/organisations/{id} # Get organisation
|
||
POST /api/trustee/organisations/ # Create organisation
|
||
PUT /api/trustee/organisations/{id} # Update organisation
|
||
DELETE /api/trustee/organisations/{id} # Delete organisation
|
||
```
|
||
|
||
#### Role Routes (`routeDataTrusteeRoles.py`)
|
||
|
||
```python
|
||
GET /api/trustee/roles/ # List roles
|
||
GET /api/trustee/roles/{id} # Get role
|
||
POST /api/trustee/roles/ # Create role (sysadmin only)
|
||
PUT /api/trustee/roles/{id} # Update role (sysadmin only)
|
||
DELETE /api/trustee/roles/{id} # Delete role (sysadmin only)
|
||
```
|
||
|
||
#### Access Routes (`routeDataTrusteeAccess.py`)
|
||
|
||
```python
|
||
GET /api/trustee/access/ # List access records (filtered)
|
||
GET /api/trustee/access/{id} # Get access record
|
||
POST /api/trustee/access/ # Create access record
|
||
PUT /api/trustee/access/{id} # Update access record
|
||
DELETE /api/trustee/access/{id} # Delete access record
|
||
GET /api/trustee/access/organisation/{orgId} # Get access for organisation
|
||
GET /api/trustee/access/user/{userId} # Get access for user
|
||
```
|
||
|
||
#### Contract Routes (`routeDataTrusteeContracts.py`)
|
||
|
||
```python
|
||
GET /api/trustee/contracts/ # List contracts (filtered)
|
||
GET /api/trustee/contracts/{id} # Get contract
|
||
POST /api/trustee/contracts/ # Create contract
|
||
PUT /api/trustee/contracts/{id} # Update contract
|
||
DELETE /api/trustee/contracts/{id} # Delete contract
|
||
GET /api/trustee/contracts/organisation/{orgId} # Get contracts for organisation
|
||
```
|
||
|
||
#### Document Routes (`routeDataTrusteeDocuments.py`)
|
||
|
||
```python
|
||
GET /api/trustee/documents/ # List documents (filtered)
|
||
GET /api/trustee/documents/{id} # Get document metadata
|
||
GET /api/trustee/documents/{id}/data # Get document binary data
|
||
POST /api/trustee/documents/ # Create document (with upload)
|
||
PUT /api/trustee/documents/{id} # Update document metadata
|
||
DELETE /api/trustee/documents/{id} # Delete document
|
||
GET /api/trustee/documents/contract/{contractId} # Get documents for contract
|
||
```
|
||
|
||
#### Position Routes (`routeDataTrusteePositions.py`)
|
||
|
||
```python
|
||
GET /api/trustee/positions/ # List positions (filtered)
|
||
GET /api/trustee/positions/{id} # Get position
|
||
POST /api/trustee/positions/ # Create position
|
||
PUT /api/trustee/positions/{id} # Update position
|
||
DELETE /api/trustee/positions/{id} # Delete position
|
||
GET /api/trustee/positions/contract/{contractId} # Get positions for contract
|
||
GET /api/trustee/positions/organisation/{orgId} # Get positions for organisation
|
||
```
|
||
|
||
#### Position-Document Cross-Reference Routes (`routeDataTrusteePositionDocuments.py`)
|
||
|
||
```python
|
||
GET /api/trustee/position-documents/ # List cross-references
|
||
GET /api/trustee/position-documents/{id} # Get cross-reference
|
||
POST /api/trustee/position-documents/ # Link position to document
|
||
DELETE /api/trustee/position-documents/{id} # Unlink position from document
|
||
GET /api/trustee/position-documents/position/{positionId} # Get documents for position
|
||
GET /api/trustee/position-documents/document/{documentId} # Get positions for document
|
||
```
|
||
|
||
### Request/Response Examples
|
||
|
||
#### Create Organisation
|
||
|
||
```http
|
||
POST /api/trustee/organisations/
|
||
Content-Type: application/json
|
||
|
||
{
|
||
"id": "acme-corp",
|
||
"label": "ACME Corporation",
|
||
"enabled": true
|
||
}
|
||
```
|
||
|
||
Response:
|
||
```json
|
||
{
|
||
"id": "acme-corp",
|
||
"label": "ACME Corporation",
|
||
"enabled": true,
|
||
"mandate": "mandate-123",
|
||
"created": 1704067200.0,
|
||
"updated": 1704067200.0
|
||
}
|
||
```
|
||
|
||
#### Create Access Record
|
||
|
||
```http
|
||
POST /api/trustee/access/
|
||
Content-Type: application/json
|
||
|
||
{
|
||
"organisationId": "acme-corp",
|
||
"roleId": "operate",
|
||
"userId": "user-456"
|
||
}
|
||
```
|
||
|
||
#### Upload Document
|
||
|
||
```http
|
||
POST /api/trustee/documents/
|
||
Content-Type: multipart/form-data
|
||
|
||
organisationId: acme-corp
|
||
contractId: contract-789
|
||
documentName: receipt.pdf
|
||
documentMimeType: application/pdf
|
||
file: <binary data>
|
||
```
|
||
|
||
---
|
||
|
||
## Implementierungsplan
|
||
|
||
### Phase 1: Grundlagen (Woche 1-2)
|
||
|
||
#### 1.1 Datenmodelle
|
||
- [ ] Create `datamodelTrustee.py` with all model classes
|
||
- [ ] Register model labels
|
||
- [ ] Add frontend metadata
|
||
- [ ] Define validation rules
|
||
|
||
#### 1.2 Datenbank-Interface
|
||
- [ ] Create `interfaceDbTrusteeObjects.py`
|
||
- [ ] Implement database connector initialization
|
||
- [ ] Implement table creation/initialization
|
||
- [ ] Implement basic CRUD operations for all tables
|
||
- [ ] Add RBAC integration helpers
|
||
|
||
#### 1.3 RBAC Setup
|
||
- [ ] Create initial AccessRules for:
|
||
- `resource.trustee`
|
||
- `ui.trustee`
|
||
- All trustee tables (DATA context)
|
||
- [ ] Create bootstrap script to initialize roles
|
||
- [ ] Document RBAC configuration
|
||
|
||
### Phase 2: Core API (Woche 3-4)
|
||
|
||
#### 2.1 Route Implementation
|
||
- [ ] `routeDataTrusteeOrganisations.py`
|
||
- [ ] `routeDataTrusteeRoles.py`
|
||
- [ ] `routeDataTrusteeAccess.py`
|
||
- [ ] `routeDataTrusteeContracts.py`
|
||
- [ ] `routeDataTrusteeDocuments.py`
|
||
- [ ] `routeDataTrusteePositions.py`
|
||
- [ ] `routeDataTrusteePositionDocuments.py`
|
||
|
||
#### 2.2 Berechtigungsprüfung
|
||
- [ ] Implement `checkTrusteePermission()` helper
|
||
- [ ] Integrate permission checks in all routes
|
||
- [ ] Add permission checks in interface methods
|
||
- [ ] Test permission scenarios
|
||
|
||
#### 2.3 Route Registration
|
||
- [ ] Register routes in main app
|
||
- [ ] Add route documentation
|
||
- [ ] Test API endpoints
|
||
|
||
### Phase 3: Advanced Features (Week 5-6)
|
||
|
||
#### 3.1 Dokumentenspeicherung
|
||
- [ ] Decide on storage approach (DB vs file system)
|
||
- [ ] Implement document upload/download
|
||
- [ ] Add file size limits
|
||
- [ ] Add MIME type validation
|
||
|
||
#### 3.2 Query Optimization
|
||
- [ ] Add database indexes
|
||
- [ ] Optimize RBAC filtering queries
|
||
- [ ] Add pagination support
|
||
- [ ] Add filtering and sorting
|
||
|
||
#### 3.3 Validierung & Fehlerbehandlung
|
||
- [ ] Add input validation
|
||
- [ ] Add foreign key validation
|
||
- [ ] Improve error messages
|
||
- [ ] Add audit logging
|
||
|
||
### Phase 4: Frontend-Integration (Woche 7-8)
|
||
|
||
#### 4.1 UI Components
|
||
- [ ] Organisation management UI
|
||
- [ ] Role management UI (sysadmin only)
|
||
- [ ] Access management UI
|
||
- [ ] Contract management UI
|
||
- [ ] Document upload/management UI
|
||
- [ ] Position entry/management UI
|
||
- [ ] Position-document linking UI
|
||
|
||
#### 4.2 RBAC UI Integration
|
||
- [ ] Hide UI elements based on `ui.trustee` access
|
||
- [ ] Show/hide features based on roles
|
||
- [ ] Add permission error messages
|
||
|
||
### Phase 5: Testing & Dokumentation (Woche 9-10)
|
||
|
||
#### 5.1 Unit Tests
|
||
- [ ] Test data models
|
||
- [ ] Test interface methods
|
||
- [ ] Test permission checking
|
||
- [ ] Test RBAC integration
|
||
|
||
#### 5.2 Integration Tests
|
||
- [ ] Test API endpoints
|
||
- [ ] Test RBAC scenarios
|
||
- [ ] Test document upload/download
|
||
- [ ] Test cross-reference operations
|
||
|
||
#### 5.3 Documentation
|
||
- [ ] API documentation
|
||
- [ ] User guide
|
||
- [ ] Admin guide
|
||
- [ ] Developer guide
|
||
|
||
---
|
||
|
||
## Migrationsstrategie
|
||
|
||
### Datenbank-Migration
|
||
|
||
1. **Tabellen erstellen**: Interface-Initialisierung verwenden, um Tabellen zu erstellen
|
||
2. **Initialdaten**: Bootstrap-Skript zum Erstellen initialer Rollen
|
||
3. **RBAC-Regeln**: Migrationsskript zum Erstellen von AccessRules
|
||
4. **Datenmigration**: Falls Migration von bestehendem System, Migrationsskript erstellen
|
||
|
||
### Rollout-Plan
|
||
|
||
1. **Entwicklung**: Implementierung in Entwicklungsumgebung
|
||
2. **Testing**: Deployment in Testumgebung, Integrationstests durchführen
|
||
3. **Staging**: Deployment in Staging, User Acceptance Testing
|
||
4. **Produktion**: Schrittweiser Rollout mit Feature-Flag
|
||
|
||
### Rückwärtskompatibilität
|
||
|
||
- Keine Breaking Changes am bestehenden System
|
||
- Trustee Feature ist additiv
|
||
- Bestehendes RBAC-System bleibt unverändert
|
||
|
||
---
|
||
|
||
## Testing Strategy
|
||
|
||
### Unit Tests
|
||
|
||
```python
|
||
# Test data models
|
||
def test_trustee_organisation_model():
|
||
org = TrusteeOrganisation(
|
||
id="test-org",
|
||
label="Test Organisation",
|
||
enabled=True
|
||
)
|
||
assert org.id == "test-org"
|
||
|
||
# Test interface methods
|
||
def test_create_organisation():
|
||
interface = TrusteeInterface(user)
|
||
org = interface.createOrganisation(...)
|
||
assert org.id is not None
|
||
|
||
# Test permission checking
|
||
def test_check_trustee_permission():
|
||
assert checkTrusteePermission(user, orgId, "read", "contract") == True
|
||
```
|
||
|
||
### Integrations-Tests
|
||
|
||
```python
|
||
# Test API endpoints
|
||
def test_create_organisation_api():
|
||
response = client.post("/api/trustee/organisations/", json={...})
|
||
assert response.status_code == 201
|
||
|
||
# Test RBAC filtering
|
||
def test_organisation_list_filtered_by_rbac():
|
||
# User should only see organisations they have access to
|
||
response = client.get("/api/trustee/organisations/")
|
||
assert all(org in allowed_orgs for org in response.json())
|
||
```
|
||
|
||
### RBAC-Test-Szenarien
|
||
|
||
1. **Sysadmin**: Kann auf alle Organisationen zugreifen
|
||
2. **Admin (Gruppe)**: Kann auf Organisationen in ihrer Gruppe zugreifen
|
||
3. **Admin (Trustee)**: Kann auf Organisationen zugreifen, denen sie zugewiesen sind
|
||
4. **Operate**: Kann CRUD für Verträge/Dokumente/Positionen in zugewiesenen Organisationen
|
||
5. **Userreport**: Kann nur eigene Datensätze CRUD
|
||
6. **Kein Zugriff**: Kann nicht auf Trustee Feature zugreifen
|
||
|
||
---
|
||
|
||
## Implementierungsdetails
|
||
|
||
### DatabaseConnector - Automatische Tabellenerstellung
|
||
|
||
Der `DatabaseConnector` erstellt Tabellen automatisch aus Pydantic-Modellen:
|
||
|
||
#### Prozess
|
||
|
||
1. **Tabellen-Erstellung**: `_ensureTableExists(model_class)` wird bei jedem Datenbankzugriff aufgerufen
|
||
2. **Spalten-Generierung**: Spalten werden aus Modell-Feldern generiert:
|
||
- `id`: VARCHAR(255) PRIMARY KEY
|
||
- Andere Felder: Automatische Typ-Konvertierung (str → TEXT, int → INTEGER, float → DOUBLE PRECISION, bool → BOOLEAN, date → DATE, datetime → TIMESTAMP, bytes → BYTEA)
|
||
- Systemattribute: Automatisch hinzugefügt (`_createdAt`, `_modifiedAt`, `_createdBy`, `_modifiedBy`)
|
||
3. **Index-Erstellung**: Automatische Indizes für Foreign Keys (Felder die auf `Id` enden)
|
||
4. **Migration**: Fehlende Spalten werden automatisch hinzugefügt (additive Migration, keine Spalten-Löschung)
|
||
|
||
#### Beispiel Trustee-Modell
|
||
|
||
```python
|
||
class TrusteeOrganisation(BaseModel):
|
||
id: str = Field(...) # VARCHAR(255) PRIMARY KEY
|
||
label: str = Field(...) # TEXT
|
||
enabled: bool = Field(...) # BOOLEAN
|
||
mandate: str = Field(...) # TEXT
|
||
# Systemattribute werden automatisch hinzugefügt:
|
||
# _createdAt: DOUBLE PRECISION
|
||
# _modifiedAt: DOUBLE PRECISION
|
||
# _createdBy: VARCHAR(255)
|
||
# _modifiedBy: VARCHAR(255)
|
||
```
|
||
|
||
**Ergebnis**: Tabelle `TrusteeOrganisation` wird automatisch erstellt mit allen Spalten.
|
||
|
||
### RBAC-Filterung auf DB-Ebene
|
||
|
||
Das RBAC-System filtert Daten automatisch auf Datenbank-Ebene:
|
||
|
||
#### Funktion: `getRecordsetWithRBAC()`
|
||
|
||
```python
|
||
records = getRecordsetWithRBAC(
|
||
connector=db,
|
||
modelClass=TrusteeOrganisation,
|
||
currentUser=user,
|
||
recordFilter={"enabled": True} # Zusätzliche Filter
|
||
)
|
||
```
|
||
|
||
#### Filterung basierend auf AccessLevel
|
||
|
||
1. **View-Permission prüfen**: Wenn `permissions.view = false`, werden keine Records zurückgegeben
|
||
2. **Read-Level Filterung**:
|
||
- `ALL`: Keine WHERE-Clause (alle Records)
|
||
- `MY`: `WHERE "_createdBy" = %s` (nur eigene Records)
|
||
- `GROUP`: `WHERE "mandateId" = %s` (nur Records der eigenen Gruppe)
|
||
- `NONE`: `WHERE 1 = 0` (keine Records)
|
||
|
||
#### Trustee-spezifische Filterung
|
||
|
||
Für Trustee-Feature müssen zusätzliche Filter implementiert werden:
|
||
|
||
1. **Organisation-Filterung**: Basierend auf `trustee.access` Tabelle
|
||
- User mit `admin` Rolle: Nur Records mit `organisationId` aus eigenen Zugriffen
|
||
- User mit `operate` Rolle: Nur Records mit `organisationId` aus eigenen Zugriffen
|
||
- User mit `userreport` Rolle: Nur eigene Records (`_createdBy = userId`)
|
||
|
||
2. **Contract-Filterung**: Basierend auf `contractId` in `trustee.access` Tabelle
|
||
- **Ohne `contractId` (None)**: User hat Zugriff auf alle Contracts der Organisation
|
||
- **Mit `contractId`**: User hat Zugriff nur auf diesen spezifischen Contract
|
||
- **Filter-Logik**:
|
||
- Wenn User Access ohne `contractId` hat → Zugriff auf alle Contracts der Organisation
|
||
- Wenn User Access mit spezifischem `contractId` hat → Zugriff nur auf diesen Contract
|
||
- Wenn User beide hat → Zugriff auf alle Contracts (weil Access ohne contractId übergeordnet ist)
|
||
- **Anwendung**: Bei Queries für `contract`, `document`, `position` wird zusätzlich nach `contractId` gefiltert:
|
||
- Records mit `contractId` werden nur angezeigt wenn User Access für diesen Contract hat (oder Access ohne contractId)
|
||
- Records ohne `contractId` werden angezeigt wenn User Access ohne contractId hat
|
||
|
||
**Implementierung**:
|
||
- **Zwei-Stufen-Filterung**: Zuerst System-RBAC (`getRecordsetWithRBAC()`), dann zusätzliche Trustee-Filterung in der Interface-Schicht
|
||
- Diese Filterung muss in der Trustee-Interface-Schicht implementiert werden, da sie feature-spezifisch ist
|
||
- **Wichtig**: Für das UI ist diese Logik nicht relevant, da RBAC automatisch alle Daten korrekt gefiltert liefert
|
||
|
||
### FormGenerator - Automatische UI-Generierung
|
||
|
||
**FormGenerator** bietet folgende automatische Features:
|
||
|
||
#### Tabellen-Features (automatisch)
|
||
- ✅ Auto-Spalten-Erkennung (wenn keine `columns` übergeben)
|
||
- ✅ Sortierung (Klick auf Header: asc → desc → keine)
|
||
- ✅ Filter (Text, Boolean, Enum, Date)
|
||
- ✅ Pagination (konfigurierbare `pageSize`)
|
||
- ✅ Spalten-Resize (Drag & Drop)
|
||
- ✅ Row Selection (Checkboxen)
|
||
- ✅ Custom Actions (Buttons pro Zeile)
|
||
- ✅ Custom Formatter (pro Spalte)
|
||
- ✅ Loading State
|
||
|
||
#### Formular-Features (automatisch)
|
||
- ✅ Feld-Typen basierend auf `frontend_type`
|
||
- ✅ Validierung basierend auf `frontend_required`
|
||
- ✅ Readonly-Felder basierend auf `frontend_readonly`
|
||
- ✅ Select-Optionen aus `frontend_options`
|
||
- ✅ Floating Labels
|
||
|
||
#### Custom Logic (manuell implementieren)
|
||
- 🔧 Custom Validierungen (z.B. MwSt-Berechnung)
|
||
- 🔧 Custom Formatter (z.B. Links zu verknüpften Records)
|
||
- 🔧 Custom Actions (z.B. Download-Button)
|
||
- 🔧 Custom Business Logic
|
||
|
||
## Implementierungsdetails
|
||
|
||
### Dokumentenspeicherung
|
||
|
||
**Entscheidung**: Dokument-Binärdaten werden in PostgreSQL BYTEA-Spalte gespeichert.
|
||
- Einfach, transaktional, einfaches Backup
|
||
- Für Phase 1 ausreichend
|
||
|
||
### Organisation ID Format
|
||
|
||
**Entscheidung**: String-Label (z.B. "acme-corp")
|
||
- Menschenlesbar, benutzerfreundlich
|
||
- Validierung: Alphanumerisch, Bindestriche, Unterstriche
|
||
- Längenbegrenzung: 3-50 Zeichen
|
||
- Groß-/Kleinschreibung-unabhängige Eindeutigkeitsprüfung
|
||
|
||
### Rollen-Management
|
||
|
||
**Entscheidung**: Rollen sind bearbeitbar
|
||
- Rollen werden in `trustee.role` Tabelle gespeichert
|
||
- Initiale Rollen werden beim Bootstrap erstellt
|
||
- Rollen können bearbeitet werden, aber nicht gelöscht werden, wenn sie in Verwendung sind
|
||
|
||
### Access-Record Eindeutigkeit
|
||
|
||
**Entscheidung**: Mehrere Access-Records erlaubt
|
||
- Ein Benutzer kann mehrere Rollen für dieselbe Organisation haben
|
||
- Unique Constraint auf `(organisationId, roleId, userId)` verhindert Duplikate
|
||
- Separate Records für jede Rollenkombination
|
||
|
||
### Vertrags-Organisations-Beziehung
|
||
|
||
**Entscheidung**: Verträge sind unveränderlich
|
||
- `contract.organisationId` kann nach der Erstellung NICHT mehr geändert werden
|
||
- Datenintegrität und Audit-Trail werden dadurch gewährleistet
|
||
|
||
### Position-Dokument-Beziehung
|
||
|
||
**Entscheidung**: Verknüpfungen sind optional
|
||
- Positionen können ohne Dokumente existieren
|
||
- Dokumente können ohne Positionen existieren
|
||
- Viele-zu-viele-Beziehung über `xpositiondocument` Tabelle
|
||
|
||
### Währungsumrechnung
|
||
|
||
**Entscheidung**: Phase 1 - Manuelle Eingabe, keine automatische Umrechnung
|
||
- Beide Währungen werden gespeichert: `bookingCurrency/bookingAmount` und `originalCurrency/originalAmount`
|
||
- Manuelle Eingabe durch Benutzer
|
||
- Wechselkurstabelle und automatische Umrechnung können in Phase 2 hinzugefügt werden
|
||
|
||
### MwSt-Berechnung
|
||
|
||
**Entscheidung**: Automatische Berechnung mit manueller Überschreibung
|
||
- `vatAmount = bookingAmount * vatPercentage / 100` wird automatisch berechnet
|
||
- Manuelle Überschreibung erlaubt, falls erforderlich
|
||
- Validierungswarnung, wenn Werte nicht übereinstimmen
|
||
|
||
---
|
||
|
||
## Nächste Schritte
|
||
|
||
1. **Dokument mit Stakeholdern final überprüfen**
|
||
2. **Detaillierte technische Spezifikationen** für jede Komponente erstellen
|
||
3. **Entwicklungsumgebung** und Projektstruktur einrichten
|
||
4. **Phase 1 Implementierung beginnen**
|
||
|
||
---
|
||
|
||
## Anhang
|
||
|
||
### A. Datenbank-Schema SQL
|
||
|
||
```sql
|
||
-- Organisation table
|
||
CREATE TABLE trustee_organisation (
|
||
id VARCHAR(255) PRIMARY KEY,
|
||
label VARCHAR(255) NOT NULL,
|
||
enabled BOOLEAN DEFAULT TRUE,
|
||
mandate VARCHAR(255) NOT NULL,
|
||
_created_at FLOAT NOT NULL, -- Systemattribut
|
||
_modified_at FLOAT NOT NULL, -- Systemattribut
|
||
_created_by VARCHAR(255), -- Wird automatisch über Systemattribute vom DatabaseConnector gesetzt
|
||
_modified_by VARCHAR(255) -- Wird automatisch über Systemattribute vom DatabaseConnector gesetzt
|
||
);
|
||
|
||
CREATE INDEX idx_trustee_organisation_mandate ON trustee_organisation(mandate);
|
||
|
||
-- Role table (Rollen sind bearbeitbar)
|
||
CREATE TABLE trustee_role (
|
||
id VARCHAR(255) PRIMARY KEY, -- String-Label (z.B. "userreport", "admin", "operate")
|
||
desc TEXT NOT NULL,
|
||
mandate VARCHAR(255) NOT NULL,
|
||
_created_at FLOAT NOT NULL, -- Systemattribut
|
||
_modified_at FLOAT NOT NULL, -- Systemattribut
|
||
_created_by VARCHAR(255), -- Wird automatisch über Systemattribute vom DatabaseConnector gesetzt
|
||
_modified_by VARCHAR(255) -- Wird automatisch über Systemattribute vom DatabaseConnector gesetzt
|
||
);
|
||
|
||
CREATE INDEX idx_trustee_role_mandate ON trustee_role(mandate);
|
||
|
||
-- Access table (ein Benutzer kann mehrere Rollen für dieselbe Organisation haben)
|
||
CREATE TABLE trustee_access (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
organisation_id VARCHAR(255) NOT NULL REFERENCES trustee_organisation(id),
|
||
role_id VARCHAR(255) NOT NULL REFERENCES trustee_role(id),
|
||
user_id VARCHAR(255) NOT NULL,
|
||
mandate VARCHAR(255) NOT NULL,
|
||
_created_at FLOAT NOT NULL, -- Systemattribut
|
||
_modified_at FLOAT NOT NULL, -- Systemattribut
|
||
_created_by VARCHAR(255), -- Wird automatisch über Systemattribute vom DatabaseConnector gesetzt
|
||
_modified_by VARCHAR(255), -- Wird automatisch über Systemattribute vom DatabaseConnector gesetzt
|
||
UNIQUE(organisation_id, role_id, user_id) -- Verhindert Duplikate, erlaubt aber mehrere Rollen pro Benutzer-Organisation
|
||
);
|
||
|
||
CREATE INDEX idx_trustee_access_organisation ON trustee_access(organisation_id);
|
||
CREATE INDEX idx_trustee_access_user ON trustee_access(user_id);
|
||
CREATE INDEX idx_trustee_access_role ON trustee_access(role_id);
|
||
CREATE INDEX idx_trustee_access_mandate ON trustee_access(mandate);
|
||
|
||
-- Contract table (Verträge sind unveränderlich: organisation_id kann nicht geändert werden)
|
||
CREATE TABLE trustee_contract (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
organisation_id VARCHAR(255) NOT NULL REFERENCES trustee_organisation(id), -- Immutable nach Erstellung
|
||
label VARCHAR(255) NOT NULL,
|
||
enabled BOOLEAN DEFAULT TRUE,
|
||
mandate VARCHAR(255) NOT NULL,
|
||
_created_at FLOAT NOT NULL, -- Systemattribut
|
||
_modified_at FLOAT NOT NULL, -- Systemattribut
|
||
_created_by VARCHAR(255), -- Wird automatisch über Systemattribute vom DatabaseConnector gesetzt
|
||
_modified_by VARCHAR(255) -- Wird automatisch über Systemattribute vom DatabaseConnector gesetzt
|
||
);
|
||
|
||
CREATE INDEX idx_trustee_contract_organisation ON trustee_contract(organisation_id);
|
||
CREATE INDEX idx_trustee_contract_mandate ON trustee_contract(mandate);
|
||
|
||
-- Document table
|
||
CREATE TABLE trustee_document (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
organisation_id VARCHAR(255) NOT NULL REFERENCES trustee_organisation(id),
|
||
contract_id UUID NOT NULL REFERENCES trustee_contract(id),
|
||
document_data BYTEA NOT NULL, -- Binärdaten werden direkt in Datenbank gespeichert
|
||
document_name VARCHAR(255) NOT NULL,
|
||
document_mime_type VARCHAR(100) NOT NULL,
|
||
mandate VARCHAR(255) NOT NULL,
|
||
created FLOAT NOT NULL,
|
||
updated FLOAT NOT NULL,
|
||
created_by VARCHAR(255) NOT NULL
|
||
);
|
||
|
||
CREATE INDEX idx_trustee_document_organisation ON trustee_document(organisation_id);
|
||
CREATE INDEX idx_trustee_document_contract ON trustee_document(contract_id);
|
||
CREATE INDEX idx_trustee_document_mandate ON trustee_document(mandate);
|
||
CREATE INDEX idx_trustee_document_created_by ON trustee_document(_created_by);
|
||
|
||
-- Position table
|
||
CREATE TABLE trustee_position (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
organisation_id VARCHAR(255) NOT NULL REFERENCES trustee_organisation(id),
|
||
contract_id UUID NOT NULL REFERENCES trustee_contract(id),
|
||
valuta DATE NOT NULL,
|
||
transaction_date_time TIMESTAMP NOT NULL,
|
||
company VARCHAR(255),
|
||
desc TEXT,
|
||
tags VARCHAR(255),
|
||
booking_currency VARCHAR(10) NOT NULL,
|
||
booking_amount FLOAT NOT NULL,
|
||
original_currency VARCHAR(10) NOT NULL,
|
||
original_amount FLOAT NOT NULL,
|
||
vat_percentage FLOAT DEFAULT 0.0,
|
||
vat_amount FLOAT DEFAULT 0.0,
|
||
mandate VARCHAR(255) NOT NULL,
|
||
created FLOAT NOT NULL,
|
||
updated FLOAT NOT NULL,
|
||
created_by VARCHAR(255) NOT NULL
|
||
);
|
||
|
||
CREATE INDEX idx_trustee_position_organisation ON trustee_position(organisation_id);
|
||
CREATE INDEX idx_trustee_position_contract ON trustee_position(contract_id);
|
||
CREATE INDEX idx_trustee_position_valuta ON trustee_position(valuta);
|
||
CREATE INDEX idx_trustee_position_transaction_date_time ON trustee_position(transaction_date_time);
|
||
CREATE INDEX idx_trustee_position_mandate ON trustee_position(mandate);
|
||
CREATE INDEX idx_trustee_position_created_by ON trustee_position(_created_by); -- Für userreport Filterung
|
||
|
||
-- Position-Document cross-reference table (Tabellenname in Kleinbuchstaben: xpositiondocument)
|
||
CREATE TABLE trustee_xpositiondocument (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
organisation_id VARCHAR(255) NOT NULL REFERENCES trustee_organisation(id),
|
||
contract_id UUID NOT NULL REFERENCES trustee_contract(id),
|
||
document_id UUID NOT NULL REFERENCES trustee_document(id),
|
||
position_id UUID NOT NULL REFERENCES trustee_position(id),
|
||
mandate VARCHAR(255) NOT NULL,
|
||
_created_at FLOAT NOT NULL, -- Systemattribut
|
||
_modified_at FLOAT NOT NULL, -- Systemattribut
|
||
_created_by VARCHAR(255), -- Wird automatisch über Systemattribute vom DatabaseConnector gesetzt
|
||
_modified_by VARCHAR(255), -- Wird automatisch über Systemattribute vom DatabaseConnector gesetzt
|
||
UNIQUE(position_id, document_id) -- Verknüpfungen sind optional: Positionen/Dokumente können unabhängig existieren
|
||
);
|
||
|
||
CREATE INDEX idx_trustee_xpd_organisation ON trustee_xpositiondocument(organisation_id);
|
||
CREATE INDEX idx_trustee_xpd_contract ON trustee_xpositiondocument(contract_id);
|
||
CREATE INDEX idx_trustee_xpd_document ON trustee_xpositiondocument(document_id);
|
||
CREATE INDEX idx_trustee_xpd_position ON trustee_xpositiondocument(position_id);
|
||
CREATE INDEX idx_trustee_xpd_mandate ON trustee_xpositiondocument(mandate);
|
||
CREATE INDEX idx_trustee_xpd_created_by ON trustee_xpositiondocument(_created_by); -- Für userreport Filterung
|
||
```
|
||
|
||
### B. Initial Roles Bootstrap
|
||
|
||
**Implementierung**: Bootstrap-Script erstellt initiale Rollen automatisch beim ersten Start des Trustee-Features (ähnlich wie `initBootstrap()` für andere System-Tabellen).
|
||
|
||
```python
|
||
# Bootstrap script to create initial roles
|
||
initial_roles = [
|
||
{
|
||
"id": "userreport",
|
||
"desc": "Can deliver user documents to the system"
|
||
},
|
||
{
|
||
"id": "admin",
|
||
"desc": "Can administrate the access"
|
||
},
|
||
{
|
||
"id": "operate",
|
||
"desc": "Can use data for operations"
|
||
}
|
||
]
|
||
```
|
||
|
||
### C. RBAC Access Rules Bootstrap
|
||
|
||
```python
|
||
# Bootstrap script to create initial AccessRules
|
||
initial_access_rules = [
|
||
# Feature access
|
||
{
|
||
"roleLabel": "sysadmin",
|
||
"context": "RESOURCE",
|
||
"item": "trustee",
|
||
"view": True
|
||
},
|
||
# UI access
|
||
{
|
||
"roleLabel": "sysadmin",
|
||
"context": "UI",
|
||
"item": "trustee",
|
||
"view": True
|
||
},
|
||
# Table access rules for each table...
|
||
]
|
||
```
|
||
|
||
---
|
||
|
||
**Dokumentversion**: 1.0
|
||
**Letzte Aktualisierung**: 2025-01-03
|
||
**Autor**: Architektur-Review
|
||
**Status**: Ausstehende Überprüfung
|