Zum Inhalt

Members Modul

Zweck

Das Members Modul bietet eine umfassende Mitarbeiterverwaltung mit Rollen (Admin, Planer, Mitarbeiter), Berechtigungen, Einladungssystem per E-Mail, Profilverwaltung und Übersicht aller Organisationsmitglieder.

Erweitert zu einem vollständigen Personalwesen-Modul mit: - Dokumentenverwaltung (Verträge, Zeugnisse, Qualifikationen, Ausweise) - Vertragsverwaltung (Arbeitsverträge mit Laufzeiten) - Qualifikationsverwaltung (mit Ablaufdaten und Warnungen) - Abwesenheitsverwaltung (Urlaub, Krankheit, etc.) - Notizenverwaltung (Personalnotizen) - Team-Verwaltung (Teams erstellen, bearbeiten, löschen, Mitglieder zuweisen) - ERP-Integration (DATEV, SAP, Custom) - Erweiterte Personalwesen-Felder (Geburtsdatum, Steuer-ID, Bankverbindung, etc.)

Verantwortlichkeiten

Was gehört zum Modul

  • Mitarbeiter-Verwaltung: Übersicht aller Organisationsmitglieder
  • Rollen-Management: Rollen zuweisen (Admin, Planer, Mitarbeiter)
  • Berechtigungen: Berechtigungen pro Rolle verwalten
  • Einladungssystem: Mitarbeiter per E-Mail einladen
  • Profilverwaltung: Profil-Informationen verwalten
  • Mitglieder-Status: Aktiv/Inaktiv verwalten
  • Dokumentenverwaltung: Dokumente hochladen, verwalten und herunterladen
  • Vertragsverwaltung: Arbeitsverträge erstellen, verwalten und verfolgen
  • Qualifikationsverwaltung: Qualifikationen mit Ablaufdaten verwalten
  • Abwesenheitsverwaltung: Urlaub, Krankheit und andere Abwesenheiten verwalten
  • Notizenverwaltung: Personalnotizen erstellen und verwalten
  • Team-Verwaltung: Teams erstellen, bearbeiten und löschen, Mitglieder zu Teams hinzufügen/entfernen, Team-Leader zuweisen
  • ERP-Integration: Export für DATEV, SAP und andere ERP-Systeme

Was gehört nicht zum Modul

  • Authentifizierung: Siehe Core Auth
  • Tenant-Verwaltung: Siehe Core Tenancy
  • Benachrichtigungen: Siehe notifications Modul

Implementierung

Backend- und Frontend-API sind modular aufgebaut; Details siehe interne Entwickler-Dokumentation.

Konfiguration

Entitlement

Das Modul ist ein Core-Modul und erfordert kein spezielles Entitlement. Es ist immer aktiv.

Entitlement-Key: Keiner (Core-Modul)

Environment Variables

Keine modulspezifischen ENV-Variablen erforderlich.

Defaults

  • Standard-Rolle: MEMBER (Mitarbeiter)
  • Einladungs-E-Mail: Wird automatisch versendet

Abhängigkeiten

Optionale Abhängigkeiten

Keine.

Core-Abhängigkeiten

  • notifications: Für Benachrichtigungen bei Einladungen

API-Endpoints

Authentifizierung

Alle Endpoints erfordern Authentifizierung. Zwei Methoden werden unterstützt:

  1. Firebase ID Token (Standard für Web-App)
  2. API-Token (für externe Nutzung)

Header: Authorization: Bearer <token>

Mitglieder auflisten

GET /api/members

Lädt alle Mitglieder des Tenants.

Response:

{
  "members": [
    {
      "id": "member123",
      "uid": "user123",
      "email": "user@example.com",
      "role": "ADMIN",
      "status": "active",
      "displayName": "Max Mustermann"
    }
  ],
  "count": 1
}

Eigenes Profil abrufen

GET /api/members/me

Lädt das eigene Profil (für Admins/Manager).

Response:

{
  "member": {
    "id": "member123",
    "uid": "user123",
    "email": "user@example.com",
    "role": "ADMIN",
    "status": "active",
    "displayName": "Max Mustermann"
  }
}

Fehler: - 404 NOT_FOUND: Profil nicht gefunden - 403 ACCESS_DENIED: Keine Berechtigung

Mitglied-Details abrufen

GET /api/members/:memberId

Lädt Details eines Mitglieds.

Response:

{
  "member": {
    "id": "member123",
    "uid": "user123",
    "email": "user@example.com",
    "role": "MEMBER",
    "status": "active",
    "displayName": "Max Mustermann",
    "firstName": "Max",
    "lastName": "Mustermann",
    "phone": "+49 123 456789",
    "department": "Sicherheit",
    "position": "Wachmann"
  }
}

Fehler: - 404 NOT_FOUND: Mitglied nicht gefunden - 403 ACCESS_DENIED: Keine Berechtigung

Mitglied einladen

POST /api/members

Lädt ein Mitglied per E-Mail ein. Erstellt einen Firebase Auth User und gibt optional einen Password Reset Link zurück.

Request Body:

{
  "email": "newuser@example.com",
  "role": "MEMBER",
  "displayName": "Optional: Anzeigename",
  "firstName": "Optional: Vorname",
  "lastName": "Optional: Nachname"
}

Response:

{
  "member": {
    "id": "member123",
    "uid": "user123",
    "email": "newuser@example.com",
    "role": "MEMBER",
    "status": "active"
  },
  "passwordResetLink": "https://...",
  "message": "Member invited successfully. User can set their password via the reset link."
}

Fehler: - 422 VALIDATION_ERROR: Ungültige Eingabedaten (z.B. ungültige E-Mail) - 409 DUPLICATE: Mitglied existiert bereits - 500 AUTH_ERROR: Fehler beim Erstellen des User-Accounts - 403 SUBSCRIPTION_USER_LIMIT_REACHED: Abonnement-Limit erreicht

Eigenes Profil aktualisieren

PATCH /api/members/me

Aktualisiert das eigene Profil (für Admins/Manager). Nur bestimmte Felder können aktualisiert werden (keine Rolle/Status-Änderung).

Request Body:

{
  "displayName": "Max Mustermann",
  "firstName": "Max",
  "lastName": "Mustermann",
  "phone": "+49 123 456789",
  "address": {
    "street": "Musterstraße 1",
    "zip": "12345",
    "city": "Berlin"
  },
  "employeeNumber": "EMP001",
  "department": "Sicherheit",
  "position": "Wachmann",
  "hourlyRate": 15.50,
  "skills": ["Sicherheit", "Erste Hilfe"],
  "notes": "Optional: Notizen",
  "hasSachkunde": true,
  "hasFuehrerschein": true,
  "hasUnterweisung": true,
  "securityQualifications": ["Sachkunde §34a"]
}

Response:

{
  "member": {
    "id": "member123",
    "displayName": "Max Mustermann",
    "firstName": "Max",
    "lastName": "Mustermann"
  },
  "message": "Profile updated successfully"
}

Fehler: - 404 NOT_FOUND: Profil nicht gefunden - 403 ACCESS_DENIED: Keine Berechtigung

Mitglied aktualisieren

PUT /api/members/:memberId

Aktualisiert ein Mitglied (nur Admin/Manager).

Request Body:

{
  "role": "PLANNER",
  "displayName": "Max Mustermann",
  "firstName": "Max",
  "lastName": "Mustermann",
  "phone": "+49 123 456789",
  "department": "Sicherheit",
  "position": "Wachmann",
  "hourlyRate": 15.50,
  "skills": ["Sicherheit"],
  "status": "active"
}

Response:

{
  "member": {
    "id": "member123",
    "role": "PLANNER",
    "displayName": "Max Mustermann",
    "status": "active"
  },
  "message": "Member updated successfully"
}

Fehler: - 404 NOT_FOUND: Mitglied nicht gefunden - 403 ACCESS_DENIED: Keine Berechtigung

Mitglied löschen

DELETE /api/members/:memberId

Löscht ein Mitglied (nur Admin).

Response:

{
  "success": true,
  "message": "Member deleted successfully"
}

Fehler: - 404 NOT_FOUND: Mitglied nicht gefunden - 403 FORBIDDEN: Kann sich nicht selbst löschen

Mitglied aktivieren

POST /api/members/:memberId/activate

Aktiviert ein deaktiviertes Mitglied (nur Admin/Manager).

Response:

{
  "member": {
    "id": "member123",
    "status": "active"
  },
  "message": "Member activated successfully"
}

Fehler: - 404 NOT_FOUND: Mitglied nicht gefunden - 403 ACCESS_DENIED: Keine Berechtigung

Mitglied deaktivieren

POST /api/members/:memberId/deactivate

Deaktiviert ein Mitglied (nur Admin/Manager).

Response:

{
  "member": {
    "id": "member123",
    "status": "inactive"
  },
  "message": "Member deactivated successfully"
}

Fehler: - 404 NOT_FOUND: Mitglied nicht gefunden - 403 ACCESS_DENIED: Keine Berechtigung

Schichten eines Mitglieds abrufen

GET /api/members/:memberId/shifts?includeCompleted=false

Lädt alle Schichten eines Mitglieds (angenommen + zugewiesen).

Query-Parameter: - includeCompleted (optional): Auch abgeschlossene Schichten einbeziehen (Standard: false)

Response:

{
  "shifts": [
    {
      "id": "shift123",
      "title": "Schicht-Titel",
      "status": "PUBLISHED",
      "startsAt": "2024-01-15T08:00:00Z",
      "endsAt": "2024-01-15T17:00:00Z"
    }
  ],
  "count": 1
}

Fehler: - 404 NOT_FOUND: Mitglied nicht gefunden - 403 ACCESS_DENIED: Keine Berechtigung

POST /api/members/:memberId/generate-invite-link

Sendet eine Password Reset E-Mail an einen Mitarbeiter. Die E-Mail wird automatisch gesendet (kein Link wird zurückgegeben aus Sicherheitsgründen).

Response:

{
  "success": true,
  "message": "Password reset email sent successfully"
}

Fehler: - 404 NOT_FOUND: Mitglied nicht gefunden - 403 ACCESS_DENIED: Keine Berechtigung - 500 EMAIL_SEND_FAILED: Fehler beim E-Mail-Versand

Personalwesen-Erweiterungen

Dokumentenverwaltung

Dokumente abrufen

GET /api/members/:memberId/documents?type=contract

Lädt alle Dokumente eines Mitarbeiters.

Query-Parameter: - type (optional): Filter nach Dokumententyp (contract, certificate, qualification, identification, other)

Response:

{
  "documents": [
    {
      "id": "doc123",
      "memberId": "member123",
      "type": "contract",
      "fileName": "arbeitsvertrag.pdf",
      "fileSize": 245678,
      "fileType": "application/pdf",
      "filePath": "[storage-path]/[filename]",
      "uploadedByUid": "user123",
      "uploadedAt": "2024-01-15T10:00:00Z",
      "description": "Arbeitsvertrag vom 01.01.2024"
    }
  ],
  "count": 1
}

Dokument hochladen

POST /api/members/:memberId/documents

Lädt ein Dokument hoch (multipart/form-data).

Request Body (FormData): - file: Datei (PDF, JPG, PNG, max. 10MB) - type: Dokumententyp (contract, certificate, qualification, identification, other) - description (optional): Beschreibung - metadata (optional): JSON-String mit Metadaten

Response:

{
  "document": {
    "id": "doc123",
    "memberId": "member123",
    "type": "contract",
    "fileName": "arbeitsvertrag.pdf",
    "fileSize": 245678,
    "fileType": "application/pdf",
    "uploadedAt": "2024-01-15T10:00:00Z"
  },
  "message": "Document uploaded successfully"
}

Fehler: - 404 NOT_FOUND: Mitglied nicht gefunden - 422 VALIDATION_ERROR: Ungültiger Dateityp oder Dateigröße überschritten - 403 ACCESS_DENIED: Keine Berechtigung

Dokument-Download-URL abrufen

GET /api/members/:memberId/documents/:documentId/download

Generiert eine temporäre Download-URL (1 Stunde gültig).

Response:

{
  "downloadUrl": "https://storage.googleapis.com/...",
  "expiresAt": "2024-01-15T11:00:00Z"
}

Dokument löschen

DELETE /api/members/:memberId/documents/:documentId

Löscht ein Dokument (nur Admin/Manager).

Response:

{
  "success": true,
  "message": "Document deleted successfully"
}

Vertragsverwaltung

Verträge abrufen

GET /api/members/:memberId/contracts

Lädt alle Verträge eines Mitarbeiters.

Response:

{
  "contracts": [
    {
      "id": "contract123",
      "memberId": "member123",
      "type": "permanent",
      "startDate": "2024-01-01",
      "endDate": null,
      "employmentModel": "full_time",
      "salary": 3500.00,
      "hourlyRate": 20.00,
      "weeklyHours": 40,
      "noticePeriod": 30,
      "createdAt": "2024-01-01T10:00:00Z"
    }
  ],
  "count": 1
}

Aktiven Vertrag abrufen

GET /api/members/:memberId/contracts/active

Lädt den aktuell aktiven Vertrag eines Mitarbeiters.

Response:

{
  "contract": {
    "id": "contract123",
    "type": "permanent",
    "startDate": "2024-01-01",
    "endDate": null,
    "employmentModel": "full_time"
  }
}

Vertrag erstellen

POST /api/members/:memberId/contracts

Erstellt einen neuen Vertrag.

Request Body:

{
  "type": "permanent",
  "startDate": "2024-01-01",
  "endDate": null,
  "employmentModel": "full_time",
  "salary": 3500.00,
  "hourlyRate": 20.00,
  "weeklyHours": 40,
  "noticePeriod": 30,
  "documentId": "doc123",
  "notes": "Unbefristeter Arbeitsvertrag"
}

Vertrag aktualisieren

PUT /api/members/:memberId/contracts/:contractId

Aktualisiert einen Vertrag.

Vertrag löschen

DELETE /api/members/:memberId/contracts/:contractId

Löscht einen Vertrag.

Qualifikationsverwaltung

Qualifikationen abrufen

GET /api/members/:memberId/qualifications

Lädt alle Qualifikationen eines Mitarbeiters.

Response:

{
  "qualifications": [
    {
      "id": "qual123",
      "memberId": "member123",
      "name": "Führerschein Klasse B",
      "type": "Führerschein",
      "issuer": "Straßenverkehrsamt",
      "issueDate": "2020-01-15",
      "expiryDate": null,
      "documentId": "doc456",
      "notes": "Gültig unbefristet"
    }
  ],
  "count": 1
}

Ablaufende Qualifikationen abrufen

GET /api/members/:memberId/qualifications/expiring?daysAhead=30

Lädt Qualifikationen, die in den nächsten Tagen ablaufen.

Query-Parameter: - daysAhead (optional): Anzahl der Tage im Voraus (Standard: 30)

Response:

{
  "qualifications": [
    {
      "id": "qual123",
      "name": "Erste-Hilfe-Kurs",
      "expiryDate": "2024-02-15"
    }
  ],
  "count": 1,
  "daysAhead": 30
}

Qualifikation erstellen

POST /api/members/:memberId/qualifications

Request Body:

{
  "name": "Erste-Hilfe-Kurs",
  "type": "Zertifikat",
  "issuer": "DRK",
  "issueDate": "2023-01-15",
  "expiryDate": "2025-01-15",
  "documentId": "doc789",
  "notes": "Jährliche Auffrischung erforderlich"
}

Abwesenheitsverwaltung

Abwesenheiten abrufen

GET /api/members/:memberId/absences?startDate=2024-01-01&endDate=2024-12-31

Lädt alle Abwesenheiten eines Mitarbeiters.

Query-Parameter: - startDate (optional): Startdatum für Filter - endDate (optional): Enddatum für Filter

Response:

{
  "absences": [
    {
      "id": "absence123",
      "memberId": "member123",
      "type": "vacation",
      "startDate": "2024-07-01",
      "endDate": "2024-07-14",
      "reason": "Sommerurlaub",
      "approvalStatus": "approved",
      "approvedByUid": "admin123",
      "approvedAt": "2024-06-15T10:00:00Z",
      "createdAt": "2024-06-01T10:00:00Z"
    }
  ],
  "count": 1
}

Abwesenheit erstellen

POST /api/members/:memberId/absences

Request Body:

{
  "type": "vacation",
  "startDate": "2024-07-01",
  "endDate": "2024-07-14",
  "reason": "Sommerurlaub",
  "notes": "Genehmigt durch Vorgesetzten"
}

Abwesenheit aktualisieren

PUT /api/members/:memberId/absences/:absenceId

Kann auch zur Genehmigung/Ablehnung verwendet werden:

{
  "approvalStatus": "approved",
  "approvedByUid": "admin123"
}

Notizenverwaltung

Notizen abrufen

GET /api/members/:memberId/notes

Lädt alle Notizen eines Mitarbeiters.

Response:

{
  "notes": [
    {
      "id": "note123",
      "memberId": "member123",
      "title": "Mitarbeitergespräch",
      "content": "Sehr gute Leistung, Weiterentwicklung geplant",
      "category": "Mitarbeitergespräch",
      "isConfidential": false,
      "createdAt": "2024-01-15T10:00:00Z",
      "createdByUid": "admin123"
    }
  ],
  "count": 1
}

Notiz erstellen

POST /api/members/:memberId/notes

Request Body:

{
  "title": "Mitarbeitergespräch",
  "content": "Sehr gute Leistung",
  "category": "Mitarbeitergespräch",
  "isConfidential": false
}

ERP-Export

Vollständige Mitarbeiterdaten für ERP-Export

GET /api/members/:memberId/erp-export?format=json

Lädt vollständige Mitarbeiterdaten für ERP-Export (DATEV, SAP, etc.).

Query-Parameter: - format (optional): Export-Format (json oder csv, Standard: json)

Response:

{
  "export": {
    "member": {
      "id": "member123",
      "email": "user@example.com",
      "firstName": "Max",
      "lastName": "Mustermann",
      "externalEmployeeId": "EMP001",
      "erpSystem": "datev",
      "erpSyncStatus": "synced",
      "dateOfBirth": "1990-01-15",
      "taxId": "12345678901",
      "bankAccount": {
        "iban": "DE89370400440532013000",
        "bic": "COBADEFFXXX"
      }
    },
    "contracts": [...],
    "qualifications": [...],
    "absences": [...],
    "documents": [...],
    "notes": [...],
    "exportedAt": "2024-01-15T10:00:00Z",
    "exportedBy": "admin123"
  },
  "format": "json"
}

Verwendung für DATEV-Integration:

// Beispiel: DATEV-Export
const exportData = await getMemberERPExport(memberId, 'json');
// Konvertiere zu DATEV-Format
const datevFormat = convertToDatevFormat(exportData.export);

Verwendung für SAP-Integration:

// Beispiel: SAP-Export
const exportData = await getMemberERPExport(memberId, 'json');
// Sende an SAP-System
await sendToSAP(exportData.export);

Team-Verwaltung

Teams auflisten

GET /api/members/teams

Lädt alle Teams des Tenants.

Response:

{
  "teams": [
    {
      "id": "team123",
      "name": "Vertriebsteam",
      "description": "Team für Vertrieb und Kundenbetreuung",
      "leaderId": "member123",
      "memberIds": ["member123", "member456"],
      "createdAt": "2024-01-15T10:00:00Z",
      "updatedAt": "2024-01-15T10:00:00Z",
      "createdByUid": "user123"
    }
  ],
  "count": 1
}

Fehler: - 403 ACCESS_DENIED: Keine Berechtigung

Team-Details abrufen

GET /api/members/teams/:teamId

Lädt Details eines Teams.

Response:

{
  "team": {
    "id": "team123",
    "name": "Vertriebsteam",
    "description": "Team für Vertrieb und Kundenbetreuung",
    "leaderId": "member123",
    "memberIds": ["member123", "member456"],
    "createdAt": "2024-01-15T10:00:00Z",
    "updatedAt": "2024-01-15T10:00:00Z",
    "createdByUid": "user123"
  }
}

Fehler: - 404 NOT_FOUND: Team nicht gefunden - 403 ACCESS_DENIED: Keine Berechtigung

Team erstellen

POST /api/members/teams

Erstellt ein neues Team.

Request Body:

{
  "name": "Vertriebsteam",
  "description": "Team für Vertrieb und Kundenbetreuung",
  "leaderId": "member123",
  "memberIds": ["member123", "member456"]
}

Response:

{
  "team": {
    "id": "team123",
    "name": "Vertriebsteam",
    "description": "Team für Vertrieb und Kundenbetreuung",
    "leaderId": "member123",
    "memberIds": ["member123", "member456"],
    "createdAt": "2024-01-15T10:00:00Z",
    "updatedAt": "2024-01-15T10:00:00Z",
    "createdByUid": "user123"
  },
  "message": "Team created successfully"
}

Fehler: - 422 VALIDATION_ERROR: Validierungsfehler (z.B. fehlender Name) - 409 DUPLICATE: Team existiert bereits - 403 ACCESS_DENIED: Keine Berechtigung

Team aktualisieren

PUT /api/members/teams/:teamId

Aktualisiert ein bestehendes Team.

Request Body:

{
  "name": "Vertriebsteam (Aktualisiert)",
  "description": "Aktualisierte Beschreibung",
  "leaderId": "member789",
  "memberIds": ["member123", "member456", "member789"]
}

Alle Felder sind optional.

Response:

{
  "team": {
    "id": "team123",
    "name": "Vertriebsteam (Aktualisiert)",
    "description": "Aktualisierte Beschreibung",
    "leaderId": "member789",
    "memberIds": ["member123", "member456", "member789"],
    "createdAt": "2024-01-15T10:00:00Z",
    "updatedAt": "2024-01-16T10:00:00Z",
    "createdByUid": "user123"
  },
  "message": "Team updated successfully"
}

Fehler: - 404 NOT_FOUND: Team nicht gefunden - 409 DUPLICATE: Team-Name existiert bereits - 403 ACCESS_DENIED: Keine Berechtigung

Team löschen

DELETE /api/members/teams/:teamId

Löscht ein Team.

Response:

{
  "success": true,
  "message": "Team deleted successfully"
}

Fehler: - 404 NOT_FOUND: Team nicht gefunden - 403 ACCESS_DENIED: Keine Berechtigung

Mitglied zu Team hinzufügen

POST /api/members/teams/:teamId/members/:memberId

Fügt ein Mitglied zu einem Team hinzu.

Response:

{
  "team": {
    "id": "team123",
    "name": "Vertriebsteam",
    "memberIds": ["member123", "member456", "member789"],
    "updatedAt": "2024-01-16T10:00:00Z"
  },
  "message": "Member added to team successfully"
}

Fehler: - 404 NOT_FOUND: Team nicht gefunden - 409 DUPLICATE: Mitglied ist bereits im Team - 403 ACCESS_DENIED: Keine Berechtigung

Mitglied aus Team entfernen

DELETE /api/members/teams/:teamId/members/:memberId

Entfernt ein Mitglied aus einem Team.

Response:

{
  "team": {
    "id": "team123",
    "name": "Vertriebsteam",
    "memberIds": ["member123", "member456"],
    "updatedAt": "2024-01-16T10:00:00Z"
  },
  "message": "Member removed from team successfully"
}

Fehler: - 404 NOT_FOUND: Team nicht gefunden oder Mitglied nicht im Team - 403 ACCESS_DENIED: Keine Berechtigung

Team-Mitglieder abrufen

GET /api/members/teams/:teamId/members

Lädt alle Mitglieder eines Teams.

Response:

{
  "members": [
    {
      "id": "member123",
      "uid": "user123",
      "email": "user@example.com",
      "displayName": "Max Mustermann",
      "role": "MANAGER",
      "status": "ACTIVE"
    }
  ],
  "count": 1
}

Fehler: - 404 NOT_FOUND: Team nicht gefunden - 403 ACCESS_DENIED: Keine Berechtigung

Team-Statistiken abrufen

GET /api/members/teams/:teamId/stats

Lädt Statistiken für ein Team.

Response:

{
  "stats": {
    "totalMembers": 5,
    "activeMembers": 4,
    "inactiveMembers": 1,
    "pendingMembers": 0,
    "adminCount": 1,
    "managerCount": 2,
    "employeeCount": 2
  }
}

Fehler: - 404 NOT_FOUND: Team nicht gefunden - 403 ACCESS_DENIED: Keine Berechtigung

Firestore Collections

Members

Pfad: /tenants/{tenantId}/members/{memberId}

{
  uid: string; // Firebase Auth UID
  email: string;
  role: 'ADMIN' | 'MANAGER' | 'EMPLOYEE';
  status: 'ACTIVE' | 'INACTIVE' | 'PENDING';
  displayName?: string;
  firstName?: string;
  lastName?: string;
  address?: string; // Legacy: einfache Adresse
  employeeNumber?: string;
  phone?: string;
  department?: string;
  position?: string;
  hourlyRate?: number;
  skills?: string[];
  notes?: string;
  hasSachkunde?: boolean;
  hasFuehrerschein?: boolean;
  hasUnterweisung?: boolean;
  securityQualifications?: string[];
  customFields?: Record<string, CustomFieldValue>;

  // Personalwesen-Erweiterungen
  dateOfBirth?: string; // ISO 8601 Date
  nationality?: string;
  maritalStatus?: 'single' | 'married' | 'divorced' | 'widowed' | 'partnership';
  placeOfBirth?: string;
  residentialAddress?: {
    street: string;
    zipCode: string;
    city: string;
    country?: string;
    state?: string;
  };
  postalAddress?: {
    street: string;
    zipCode: string;
    city: string;
    country?: string;
    state?: string;
  };
  emergencyContact?: {
    name: string;
    phone: string;
    relationship?: string;
    email?: string;
  };
  relatives?: string[];
  bankAccount?: {
    iban: string;
    bic?: string;
    bankName?: string;
    accountHolder?: string;
  };
  taxClass?: number; // 1-6
  taxId?: string;
  socialSecurityNumber?: string;
  employmentModel?: 'full_time' | 'part_time' | 'mini_job' | 'internship' | 'freelance';
  weeklyHours?: number;
  workingTimeDistribution?: string;
  externalEmployeeId?: string; // Für DATEV/SAP
  erpSyncStatus?: 'not_synced' | 'synced' | 'error';
  erpLastSyncAt?: Timestamp;
  erpSystem?: 'datev' | 'sap' | 'custom';
  teamId?: string;
  supervisorId?: string; // Member-ID
  costCenter?: string;
  costCenterCode?: string;

  invitedByUid?: string;
  createdAt: Timestamp;
  updatedAt: Timestamp;
  lastActiveAt?: Timestamp;
  lastLoginAt?: Timestamp;
  ssoConfigId?: string;
}

Member Documents

Pfad: /tenants/{tenantId}/members/{memberId}/documents/{documentId}

{
  memberId: string;
  type: 'contract' | 'certificate' | 'qualification' | 'identification' | 'other';
  fileName: string;
  fileSize: number;
  fileType: string;
  filePath: string; // Storage-Pfad
  uploadedByUid: string;
  uploadedAt: Timestamp;
  description?: string;
  metadata?: Record<string, string | number | boolean>;
}

Member Contracts

Pfad: /tenants/{tenantId}/members/{memberId}/contracts/{contractId}

{
  memberId: string;
  type: 'permanent' | 'temporary' | 'internship' | 'freelance';
  startDate: Timestamp;
  endDate?: Timestamp;
  employmentModel: 'full_time' | 'part_time' | 'mini_job' | 'internship' | 'freelance';
  salary?: number; // Bruttogehalt (monatlich)
  hourlyRate?: number;
  weeklyHours?: number;
  noticePeriod?: number; // Kündigungsfrist in Tagen
  documentId?: string; // Referenz zu MemberDocument
  notes?: string;
  createdAt: Timestamp;
  updatedAt: Timestamp;
  createdByUid: string;
}

Member Qualifications

Pfad: /tenants/{tenantId}/members/{memberId}/qualifications/{qualificationId}

{
  memberId: string;
  name: string;
  type: string; // z.B. "Führerschein", "Sachkunde", "Zertifikat"
  issuer?: string;
  issueDate?: Timestamp;
  expiryDate?: Timestamp;
  documentId?: string;
  notes?: string;
  createdAt: Timestamp;
  updatedAt: Timestamp;
  createdByUid: string;
}

Member Absences

Pfad: /tenants/{tenantId}/members/{memberId}/absences/{absenceId}

{
  memberId: string;
  type: 'vacation' | 'sick' | 'special_leave' | 'unpaid_leave' | 'other';
  startDate: Timestamp;
  endDate: Timestamp;
  reason?: string;
  approvalStatus: 'pending' | 'approved' | 'rejected';
  approvedByUid?: string;
  approvedAt?: Timestamp;
  notes?: string;
  createdAt: Timestamp;
  updatedAt: Timestamp;
  createdByUid: string;
}

Member Notes

Pfad: /tenants/{tenantId}/members/{memberId}/notes/{noteId}

{
  memberId: string;
  title: string;
  content: string;
  category?: string; // z.B. "Mitarbeitergespräch", "Leistungsbewertung"
  isConfidential?: boolean; // Nur für Admin sichtbar
  createdAt: Timestamp;
  updatedAt: Timestamp;
  createdByUid: string;
}

Teams

Pfad: /tenants/{tenantId}/teams/{teamId}

{
  name: string;
  description?: string;
  leaderId?: string; // Member-ID des Team-Leaders
  memberIds: string[]; // Array von Member-IDs
  createdAt: Timestamp;
  updatedAt: Timestamp;
  createdByUid: string;
}

Firebase Storage Struktur

Dokumente werden in Firebase Storage gespeichert:

Dokumente werden in einer sicheren, hierarchischen Struktur organisiert nach Dokumenttyp gespeichert.
Unterstützte Typen: contract, certificate, qualification, identification, other

Unterstützte Dateitypen: PDF, JPG, PNG (max. 10MB)

Fehlerfälle

HTTP-Status-Codes

  • 200 OK: Erfolgreich
  • 201 Created: Ressource erstellt
  • 400 Bad Request: Validierungsfehler
  • 401 Unauthorized: Nicht authentifiziert
  • 403 Forbidden: Keine Berechtigung (nur Admin)
  • 404 Not Found: Ressource nicht gefunden
  • 500 Internal Server Error: Server-Fehler

Fehlercodes

Die folgenden Fehlercodes werden von den API-Endpunkten zurückgegeben:

  • NOT_FOUND: Ressource nicht gefunden (Mitglied, Team, etc.)
  • ACCESS_DENIED: Keine Berechtigung für die Operation
  • VALIDATION_ERROR: Validierungsfehler bei Eingabedaten
  • DUPLICATE: Ressource existiert bereits (Mitglied, Team-Name) oder Mitglied bereits im Team
  • AUTH_ERROR: Fehler beim Erstellen des User-Accounts
  • SUBSCRIPTION_USER_LIMIT_REACHED: Abonnement-Limit erreicht (mit currentCount und maxCount)
  • FORBIDDEN: Operation nicht erlaubt (z.B. sich selbst löschen)
  • EMAIL_SEND_FAILED: Fehler beim E-Mail-Versand

ERP-Integration

DATEV-Integration

Das Modul unterstützt den Export von Mitarbeiterdaten für DATEV:

// Beispiel: DATEV-Export
const exportData = await getMemberERPExport(memberId, 'json');

// Konvertiere zu DATEV-Format
const datevData = {
  Personalnummer: exportData.export.member.externalEmployeeId || exportData.export.member.employeeNumber,
  Vorname: exportData.export.member.firstName,
  Nachname: exportData.export.member.lastName,
  Geburtsdatum: exportData.export.member.dateOfBirth,
  Steuerklasse: exportData.export.member.taxClass,
  SteuerID: exportData.export.member.taxId,
  IBAN: exportData.export.member.bankAccount?.iban,
  BIC: exportData.export.member.bankAccount?.bic,
  // ... weitere Felder
};

// Sende an DATEV
await sendToDatev(datevData);

SAP-Integration

Für SAP-Integration kann der JSON-Export verwendet werden:

// Beispiel: SAP-Export
const exportData = await getMemberERPExport(memberId, 'json');

// Konvertiere zu SAP-Format
const sapData = {
  PERNR: exportData.export.member.externalEmployeeId,
  VORNA: exportData.export.member.firstName,
  NACHN: exportData.export.member.lastName,
  GBDAT: exportData.export.member.dateOfBirth,
  // ... weitere Felder
};

// Sende an SAP
await sendToSAP(sapData);

Custom ERP-Integration

Für andere ERP-Systeme kann der generische JSON-Export verwendet werden:

const exportData = await getMemberERPExport(memberId, 'json');
// Verarbeite exportData.export nach Bedarf

Rückwärtskompatibilität

Alle Erweiterungen sind rückwärtskompatibel:

  • ✅ Alle neuen Felder sind optional
  • ✅ Bestehende API-Endpoints bleiben unverändert
  • ✅ Bestehende Daten werden automatisch unterstützt
  • ✅ Alte Clients können weiterhin ohne neue Felder arbeiten
  • ✅ Neue Sub-Collections werden nur erstellt, wenn benötigt

Sicherheit

Berechtigungen

  • Dokumente: Nur Admin/Manager können hochladen/löschen
  • Verträge: Nur Admin/Manager können erstellen/bearbeiten
  • Qualifikationen: Nur Admin/Manager können erstellen/bearbeiten
  • Abwesenheiten: Admin/Manager können alle sehen, Mitarbeiter nur eigene
  • Notizen: Nur Admin/Manager können erstellen/bearbeiten
  • ERP-Export: Nur Admin/Manager können exportieren

Validierung

  • Alle Uploads werden validiert (Dateityp, Größe)
  • Alle Eingaben werden mit Zod-Schemas validiert
  • Tenant-Scope wird bei allen Operationen geprüft
  • Alle Sub-Collections sind tenant-scoped

FAQ / Troubleshooting

Wie lade ich ein Dokument hoch?

  1. Öffne die Mitglieder-Detailansicht
  2. Wechsle zum Tab "Dokumente"
  3. Klicke auf "Dokument hochladen"
  4. Wähle Datei und Dokumententyp
  5. Optional: Beschreibung hinzufügen
  6. Klicke auf "Hochladen"

Wie exportiere ich Daten für DATEV?

  1. Öffne die Mitglieder-Detailansicht
  2. Klicke auf "ERP-Export"
  3. Die JSON-Datei wird heruntergeladen
  4. Konvertiere die Daten nach DATEV-Format (siehe ERP-Integration)

Wie verwalte ich ablaufende Qualifikationen?

  1. Öffne die Mitglieder-Detailansicht
  2. Wechsle zum Tab "Qualifikationen"
  3. Ablaufende Qualifikationen werden automatisch hervorgehoben
  4. Verwende den Endpoint /api/members/:memberId/qualifications/expiring für automatisierte Warnungen

Wie genehmige ich eine Abwesenheit?

  1. Öffne die Mitglieder-Detailansicht
  2. Wechsle zum Tab "Abwesenheiten"
  3. Klicke auf "Genehmigen" oder "Ablehnen" bei ausstehenden Abwesenheiten