Time Tracking Modul
Zweck
Das Time Tracking Modul bietet eine vollständige Zeiterfassungslösung für Mitarbeiter. Es ermöglicht Clock In/Out, manuelle Zeiteinträge, Pausen-Management, Abwesenheitsverwaltung, Zeitkonto-Verwaltung und Genehmigungs-Workflows.
Verantwortlichkeiten
Was gehört zum Modul
- Zeiterfassung: Clock In/Out, manuelle Einträge, Live-Tracking
- Pausen-Management: Start/Ende von Pausen, ArbZG-konforme Pausenerfassung
- Abwesenheitsverwaltung: Urlaub, Krankheit, Sonderurlaub mit Kontingent-Verwaltung
- Zeitkonto: Zielstunden, Saldo-Berechnung, manuelle Anpassungen
- Genehmigungen: Überstunden-Genehmigungen, projektbezogene Zeiterfassung
- Kalender-Integration: Zeiteinträge werden als Events im Kalender angezeigt
Was gehört nicht zum Modul
- Schichtplanung: Siehe shift-pool Modul (noch nicht dokumentiert)
- Compliance-Prüfungen: Siehe work-time-compliance Modul (noch nicht dokumentiert)
- Projekt-Verwaltung: Siehe projects Modul (noch nicht dokumentiert)
Implementierung
Backend- und Frontend-API sind modular aufgebaut; Details siehe interne Entwickler-Dokumentation.
Konfiguration
Entitlement
Das Modul erfordert das Entitlement module.time_tracking. Dieses wird pro Tenant aktiviert/deaktiviert.
Entitlement-Key: module.time_tracking
Environment Variables
Keine modulspezifischen ENV-Variablen erforderlich.
Defaults
- Zielstunden pro Monat: 160 Stunden (40h/Woche × 4 Wochen)
- Arbeitszeit-Typ: Vollzeit (
FULL_TIME) - Wöchentliche Stunden: 40 Stunden
Abhängigkeiten
Optionale Abhängigkeiten
- work-time-compliance: Für automatische Compliance-Prüfungen (Ruhezeiten, Schichtdauer, Pausen)
- projects: Für projektbezogene Zeiterfassung (erfordert immer Genehmigung)
Core-Abhängigkeiten
- calendar-core: Zeiteinträge werden als Events im Kalender angezeigt
- members: Für Rollenprüfung (Admin/Manager für erweiterte Funktionen)
- notifications: Für Benachrichtigungen bei Genehmigungen
API-Endpoints
Authentifizierung
Alle Endpoints erfordern Authentifizierung. Zwei Methoden werden unterstützt:
- Firebase ID Token (Standard für Web-App)
- API-Token (für externe Nutzung)
Header: Authorization: Bearer <token>
User-Endpoints
Status
GET /api/time-tracking/status
Gibt den aktuellen Status zurück (laufender Entry, heute-Stats).
Response:
{
"isRunning": true,
"runningEntry": {
"id": "entry123",
"clockIn": "2024-01-15T08:00:00Z",
"status": "running",
"entryType": "work"
},
"today": {
"totalMinutes": 240,
"entriesCount": 2
}
}
Clock In
POST /api/time-tracking/clock-in
Startet die Zeiterfassung.
Request Body:
Response:
{
"message": "Clocked in successfully",
"entry": {
"id": "entry123",
"clockIn": "2024-01-15T08:00:00Z",
"status": "running",
"entryType": "work",
"projectId": "project123"
}
}
Hinweis: Wenn projectId angegeben wird, wird beim Clock-Out der Status automatisch auf PENDING_APPROVAL gesetzt.
Fehler:
- 409 ALREADY_CLOCKED_IN: Bereits eingestempelt
Clock Out
POST /api/time-tracking/clock-out
Beendet die Zeiterfassung.
Request Body:
Response:
{
"message": "Clocked out successfully",
"entry": {
"id": "entry123",
"clockIn": "2024-01-15T08:00:00Z",
"clockOut": "2024-01-15T17:00:00Z",
"status": "completed",
"durationMinutes": 540
}
}
Status-Verhalten:
- Ohne projectId: Status wird auf COMPLETED gesetzt
- Mit projectId: Status wird auf PENDING_APPROVAL gesetzt (erfordert Genehmigung)
Fehler:
- 409 NOT_CLOCKED_IN: Nicht eingestempelt
Time Entries auflisten
GET /api/time-tracking/entries?limit=50
Listet TimeEntries des aktuellen Users.
Query-Parameter:
- limit (optional): Maximale Anzahl (Standard: 50, Maximum: 100)
Response:
{
"entries": [
{
"id": "entry123",
"clockIn": "2024-01-15T08:00:00Z",
"clockOut": "2024-01-15T17:00:00Z",
"status": "completed",
"durationMinutes": 540,
"entryType": "work",
"note": "Arbeitszeit"
}
],
"count": 1
}
Laufenden Entry abrufen
GET /api/time-tracking/running
Gibt den aktuell laufenden Entry zurück (falls vorhanden).
Response:
{
"isRunning": true,
"entry": {
"id": "entry123",
"clockIn": "2024-01-15T08:00:00Z",
"clockOut": null,
"status": "running",
"entryType": "work"
}
}
Hinweis: Wenn kein laufender Entry vorhanden ist, wird isRunning: false und entry: null zurückgegeben.
Time Entry abrufen
GET /api/time-tracking/entries/:entryId
Lädt einen einzelnen TimeEntry.
Response:
{
"entry": {
"id": "entry123",
"clockIn": "2024-01-15T08:00:00Z",
"clockOut": "2024-01-15T17:00:00Z",
"status": "completed",
"durationMinutes": 540
}
}
Fehler:
- 404 NOT_FOUND: Eintrag nicht gefunden
Manuellen Time Entry erstellen
POST /api/time-tracking/entries
Erstellt einen manuellen TimeEntry.
Request Body:
{
"clockIn": "2024-01-15T08:00:00Z",
"clockOut": "2024-01-15T17:00:00Z",
"entryType": "work",
"projectId": "Optional: Projekt-ID",
"note": "Optional: Notiz"
}
Validierung:
- clockIn muss vor clockOut liegen
- Maximale Dauer: 24 Stunden
- Automatische Pausenabrechnung basierend auf ArbZG
Response:
{
"message": "Eintrag erstellt",
"entry": {
"id": "entry123",
"clockIn": "2024-01-15T08:00:00Z",
"clockOut": "2024-01-15T17:00:00Z",
"status": "completed",
"durationMinutes": 495
}
}
Status-Verhalten:
- Ohne projectId: Status wird auf COMPLETED gesetzt
- Mit projectId: Status wird auf PENDING_APPROVAL gesetzt (erfordert Genehmigung)
Fehler:
- 400 VALIDATION_ERROR: Ungültige Zeiten oder Dauer
Time Entry aktualisieren
PUT /api/time-tracking/entries/:entryId
Aktualisiert einen TimeEntry.
Request Body:
{
"clockIn": "2024-01-15T08:00:00Z",
"clockOut": "2024-01-15T17:00:00Z",
"entryType": "work",
"note": "Aktualisierte Notiz"
}
Einschränkungen: - Nur eigene Einträge können bearbeitet werden - Laufende Einträge können nicht bearbeitet werden
Fehler:
- 403 FORBIDDEN: Keine Berechtigung
- 400 VALIDATION_ERROR: Laufende Einträge können nicht bearbeitet werden
Time Entry löschen
DELETE /api/time-tracking/entries/:entryId
Löscht einen TimeEntry.
Einschränkungen: - Nur eigene Einträge können gelöscht werden - Laufende Einträge können nicht gelöscht werden
Fehler:
- 403 FORBIDDEN: Keine Berechtigung
- 400 VALIDATION_ERROR: Laufende Einträge können nicht gelöscht werden
Pausen-Management
Pausen-Status abrufen
GET /api/time-tracking/break/status
Gibt den aktuellen Pausen-Status zurück.
Response:
{
"isOnBreak": true,
"breakEntry": {
"id": "break123",
"clockIn": "2024-01-15T12:00:00Z",
"status": "running",
"entryType": "break"
},
"workEntryBeforeBreak": {
"id": "work123",
"clockIn": "2024-01-15T08:00:00Z",
"clockOut": "2024-01-15T12:00:00Z",
"status": "completed",
"entryType": "work"
}
}
Pause starten
POST /api/time-tracking/break/start
Startet eine Pause (stoppt laufenden WORK-Entry, startet BREAK-Entry).
Request Body:
Response:
{
"message": "Pause gestartet",
"workEntry": {
"id": "work123",
"clockOut": "2024-01-15T12:00:00Z",
"status": "completed"
},
"breakEntry": {
"id": "break123",
"clockIn": "2024-01-15T12:00:00Z",
"status": "running",
"entryType": "break"
}
}
Fehler:
- 409 BREAK_ALREADY_RUNNING: Pause läuft bereits
- 409 NO_WORK_ENTRY: Kein laufender Arbeitseintrag
Pause beenden
POST /api/time-tracking/break/end
Beendet eine Pause (stoppt BREAK-Entry, startet neuen WORK-Entry).
Request Body:
Response:
{
"message": "Pause beendet",
"breakEntry": {
"id": "break123",
"clockOut": "2024-01-15T12:30:00Z",
"status": "completed",
"durationMinutes": 30
},
"workEntry": {
"id": "work124",
"clockIn": "2024-01-15T12:30:00Z",
"status": "running",
"entryType": "work"
}
}
Fehler:
- 409 NO_BREAK_RUNNING: Keine laufende Pause gefunden
Pausen-Vorschlag
GET /api/time-tracking/break-suggestion?date=2024-01-15
Berechnet einen Pausen-Vorschlag basierend auf ArbZG.
Query-Parameter:
- date (optional): Datum für Berechnung (ISO 8601, Standard: heute)
Response:
{
"suggestion": {
"requiredMinutes": 30,
"reason": "Bei mehr als 6 Stunden Arbeitszeit sind mindestens 30 Minuten Pause erforderlich (ArbZG). Bereits erfasst: 0 Minuten."
}
}
ArbZG-Regeln: - > 6 Stunden: 30 Minuten Pause erforderlich - > 9 Stunden: 45 Minuten Pause erforderlich
Hinweis: Admin- und Manager-Endpoints sind verfügbar, erfordern jedoch entsprechende Rollenberechtigungen. Detaillierte Dokumentation ist nur für autorisierte Administratoren verfügbar.
Zeitkonto-Endpoints
Zeitkonto abrufen
GET /api/time-tracking/time-account/:year/:month
Lädt das Zeitkonto für einen Monat.
Path-Parameter:
- year: Jahr (z.B. 2024)
- month: Monat (1-12)
Response:
{
"account": {
"userId": "user123",
"year": 2024,
"month": 1,
"targetHours": 160,
"actualHours": 165.5,
"timeTrackingHours": 165.5,
"shiftHours": 0,
"balanceHours": 5.5,
"manualAdjustments": [],
"complianceAdjustments": [],
"createdAt": "2024-01-01T00:00:00Z",
"updatedAt": "2024-01-31T23:59:59Z"
}
}
Zeitkonto-Historie
GET /api/time-tracking/time-account/history?limit=12
Lädt die Historie der Zeitkonten.
Query-Parameter:
- limit (optional): Anzahl Monate (Standard: 12, Maximum: 24)
Zielstunden abrufen (Admin)
GET /api/time-tracking/time-account/target/:userId
Lädt die Zielstunden für einen User (nur Admin/Manager).
Response:
{
"target": {
"userId": "user123",
"monthlyTargetHours": 160,
"employmentType": "full_time",
"weeklyHours": 40,
"updatedAt": "2024-01-01T00:00:00Z",
"updatedBy": "admin123"
}
}
Zielstunden setzen (Admin)
PUT /api/time-tracking/time-account/target
Setzt die Zielstunden für einen User (nur Admin/Manager).
Request Body:
{
"userId": "user123",
"monthlyTargetHours": 160,
"employmentType": "full_time",
"weeklyHours": 40
}
Manuelle Anpassung hinzufügen (Admin)
POST /api/time-tracking/time-account/:year/:month/adjust
Fügt eine manuelle Anpassung hinzu (nur Admin/Manager).
Request Body:
Zeitkonto exportieren
GET /api/time-tracking/time-account/export?format=json&startYear=2024&startMonth=1&endYear=2024&endMonth=12
Exportiert Zeitkonto-Daten für DSGVO (Art. 15).
Query-Parameter:
- format (optional): json oder csv (Standard: json)
- startYear, startMonth (optional): Start-Zeitraum
- endYear, endMonth (optional): End-Zeitraum
Response (JSON):
Response (CSV): CSV-Datei mit UTF-8 BOM für Excel-Kompatibilität
Abwesenheits-Endpoints
Abwesenheit erstellen
POST /api/time-tracking/absences
Erstellt eine Abwesenheit.
Request Body:
{
"type": "vacation",
"startDate": "2024-07-01",
"endDate": "2024-07-14",
"note": "Optional: Notiz",
"attestDocumentId": "Optional; bei type=sick Pflicht: ID des hochgeladenen Mitglieder-Dokuments (Attest)",
"attestFileName": "Optional: Dateiname des Attests für Anzeige"
}
Typen: vacation, sick, holiday, special
Hinweis: Bei type=sick ist attestDocumentId erforderlich (Arbeitsunfähigkeitsbescheinigung muss zuvor als Mitglieder-Dokument hochgeladen und die ID hier angegeben werden).
Response:
{
"message": "Abwesenheit beantragt",
"absence": {
"id": "absence123",
"type": "vacation",
"startDate": "2024-07-01",
"endDate": "2024-07-14",
"days": 10,
"status": "pending"
}
}
Abwesenheiten auflisten
GET /api/time-tracking/absences?limit=100&startDate=2024-01-01&endDate=2024-12-31&status=pending&type=vacation
Query-Parameter:
- limit (optional): Maximale Anzahl (Standard: 100, Maximum: 200)
- startDate (optional): Start-Datum (ISO 8601)
- endDate (optional): End-Datum (ISO 8601)
- status (optional): pending, approved, rejected
- type (optional): vacation, sick, holiday, special
Abwesenheit abrufen
GET /api/time-tracking/absences/:absenceId
- Eigene Abwesenheiten immer; Admin/Manager können beliebige Abwesenheiten laden (z. B. für Genehmigungsansicht).
- Response kann optionale Felder
attestDocumentIdundattestFileNameenthalten (bei Krankmeldung mit Attest).
Attest herunterladen
GET /api/time-tracking/absences/:absenceId/attest/download
Liefert eine signierte Download-URL für das Attest (Arbeitsunfähigkeitsbescheinigung) einer Abwesenheit.
- Berechtigung: Antragsteller (eigene Abwesenheit) oder Admin/Manager.
- Response:
{ "downloadUrl": "...", "expiresAt": "..." }(URL ca. 1 Stunde gültig). - 404: Wenn keine Abwesenheit gefunden wird oder kein Attest zugeordnet ist.
Abwesenheit aktualisieren
PUT /api/time-tracking/absences/:absenceId
Einschränkungen: - Nur eigene Abwesenheiten können bearbeitet werden - Ausstehende Abwesenheiten können nicht bearbeitet werden
Abwesenheit löschen
DELETE /api/time-tracking/absences/:absenceId
Einschränkungen: - Nur eigene Abwesenheiten können gelöscht werden - Ausstehende Abwesenheiten können nicht gelöscht werden
Kontingent abrufen
GET /api/time-tracking/absences/quota/:year
Lädt Kontingente für ein Jahr.
Response:
{
"quota": {
"uid": "user123",
"year": 2024,
"vacationDays": 25,
"sickDays": 0,
"specialDays": 0,
"usedVacationDays": 10,
"usedSickDays": 0,
"usedSpecialDays": 0
}
}
Kontingent setzen (Admin)
PUT /api/time-tracking/absences/quota/:year
Setzt Kontingente für ein Jahr (nur Admin/Manager).
Request Body:
Kontingent-Verfügbarkeit prüfen
GET /api/time-tracking/absences/quota/:year/check?type=vacation&days=10
Prüft ob Kontingent verfügbar ist.
Query-Parameter:
- type (required): vacation, sick, special
- days (required): Anzahl Tage
Response:
Genehmigungs-Endpoints
Hinweis: Diese Endpoints erfordern Admin- oder Manager-Rolle (außer GET /approvals/entity/:type/:entityId).
Genehmigungen auflisten
GET /api/time-tracking/approvals?limit=100&status=pending&type=time_entry&requestedBy=user123
Query-Parameter:
- limit (optional): Maximale Anzahl (Standard: 100, Maximum: 200)
- status (optional): pending, approved, rejected
- type (optional): time_entry, absence, overtime, allowance
- requestedBy (optional): User-ID
Genehmigung abrufen
GET /api/time-tracking/approvals/:approvalId
Genehmigung für Objekt abrufen
GET /api/time-tracking/approvals/entity/:type/:entityId
Lädt Genehmigung für ein Objekt (z.B. TimeEntry).
Path-Parameter:
- type: time_entry, project_time_entry, etc.
- entityId: ID des Objekts
Genehmigung genehmigen
POST /api/time-tracking/approvals/:approvalId/approve
Genehmigt einen Antrag (nur Admin/Manager).
Request Body:
Verhalten bei projektbezogenen Zeiterfassungen:
- Wenn die Genehmigung für einen project_time_entry oder time_entry mit projectId genehmigt wird, wird der Status des TimeEntry automatisch von PENDING_APPROVAL auf COMPLETED gesetzt.
Response:
{
"message": "Genehmigung genehmigt",
"approval": {
"id": "approval123",
"type": "project_time_entry",
"entityId": "entry123",
"status": "approved",
"approvedBy": "admin123",
"approvedAt": "2024-01-15T10:00:00Z"
}
}
Genehmigung ablehnen
POST /api/time-tracking/approvals/:approvalId/reject
Lehnt einen Antrag ab (nur Admin/Manager).
Request Body:
Verhalten bei projektbezogenen Zeiterfassungen:
- Wenn die Genehmigung für einen project_time_entry oder time_entry mit projectId abgelehnt wird, wird der Status des TimeEntry automatisch von PENDING_APPROVAL auf REJECTED gesetzt.
Response:
{
"message": "Genehmigung abgelehnt",
"approval": {
"id": "approval123",
"type": "project_time_entry",
"entityId": "entry123",
"status": "rejected",
"rejectedBy": "admin123",
"rejectedAt": "2024-01-15T10:00:00Z",
"rejectionReason": "Ungültige Zeiten"
}
}
Externe API-Nutzung
API-Token erstellen
- Öffnen Sie die Einstellungen in der Web-App
- Gehen Sie zum Tab Sicherheit
- Klicken Sie auf Neuer Token unter "API-Tokens"
- Geben Sie einen Namen ein (z.B. "Production API", "Script Integration")
- Klicken Sie auf Token erstellen
- WICHTIG: Kopieren Sie den Token sofort – er wird nicht erneut angezeigt!
API-Token verwenden
Alle API-Endpoints können mit einem API-Token verwendet werden:
Header: Authorization: Bearer <api-token>
WICHTIG - Sicherheitshinweise: - API-Tokens sind wie Passwörter zu behandeln - Tokens niemals in Code committen oder in öffentlichen Repositories speichern - Tokens in Environment Variables oder Secret Managers speichern - Regelmäßige Token-Rotation empfohlen - Bei Kompromittierung sofort Token widerrufen und neu erstellen
Beispiele
Minimal: Clock In/Out
# API-Token aus Environment Variable laden
API_TOKEN="${TIMEAM_API_TOKEN}"
# Clock In
curl -X POST https://api.example.com/api/time-tracking/clock-in \
-H "Authorization: Bearer ${API_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"note": "Arbeitsbeginn"}'
# Clock Out
curl -X POST https://api.example.com/api/time-tracking/clock-out \
-H "Authorization: Bearer ${API_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"note": "Arbeitsende"}'
Status abrufen
curl -X GET https://api.example.com/api/time-tracking/status \
-H "Authorization: Bearer YOUR_API_TOKEN"
Manuellen Eintrag erstellen
API_TOKEN="${TIMEAM_API_TOKEN}"
curl -X POST https://api.example.com/api/time-tracking/entries \
-H "Authorization: Bearer ${API_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"clockIn": "2024-01-15T08:00:00Z",
"clockOut": "2024-01-15T17:00:00Z",
"entryType": "work",
"note": "Arbeitszeit"
}'
Pause starten/beenden
API_TOKEN="${TIMEAM_API_TOKEN}"
# Pause starten
curl -X POST https://api.example.com/api/time-tracking/break/start \
-H "Authorization: Bearer ${API_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"note": "Mittagspause"}'
# Pause beenden
curl -X POST https://api.example.com/api/time-tracking/break/end \
-H "Authorization: Bearer ${API_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"note": "Pause beendet"}'
Abwesenheit beantragen
API_TOKEN="${TIMEAM_API_TOKEN}"
curl -X POST https://api.example.com/api/time-tracking/absences \
-H "Authorization: Bearer ${API_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"type": "vacation",
"startDate": "2024-07-01",
"endDate": "2024-07-14",
"note": "Sommerurlaub"
}'
Zeitkonto abrufen
API_TOKEN="${TIMEAM_API_TOKEN}"
curl -X GET https://api.example.com/api/time-tracking/time-account/2024/1 \
-H "Authorization: Bearer ${API_TOKEN}"
Zeitkonto exportieren (CSV)
API_TOKEN="${TIMEAM_API_TOKEN}"
curl -X GET "https://api.example.com/api/time-tracking/time-account/export?format=csv&startYear=2024&startMonth=1&endYear=2024&endMonth=12" \
-H "Authorization: Bearer ${API_TOKEN}" \
-o zeitkonto-export.csv
Typischer Use-Case: Automatische Zeiterfassung
#!/bin/bash
# API-Token aus Environment Variable oder Secret Manager laden
# NIEMALS Token direkt im Code speichern!
API_TOKEN="${TIMEAM_API_TOKEN}"
API_BASE="https://api.example.com"
# Clock In um 8:00 Uhr
curl -X POST "$API_BASE/api/time-tracking/clock-in" \
-H "Authorization: Bearer $API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"note": "Automatisch eingestempelt"}'
# Pause um 12:00 Uhr
sleep 14400 # 4 Stunden warten
curl -X POST "$API_BASE/api/time-tracking/break/start" \
-H "Authorization: Bearer $API_TOKEN" \
-H "Content-Type: application/json"
# Pause beenden um 12:30 Uhr
sleep 1800 # 30 Minuten warten
curl -X POST "$API_BASE/api/time-tracking/break/end" \
-H "Authorization: Bearer $API_TOKEN" \
-H "Content-Type: application/json"
# Clock Out um 17:00 Uhr
sleep 16200 # 4,5 Stunden warten
curl -X POST "$API_BASE/api/time-tracking/clock-out" \
-H "Authorization: Bearer $API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"note": "Automatisch ausgestempelt"}'
Firestore Collections
Time Entries
Pfad: /tenants/{tenantId}/timeEntries/{entryId}
{
uid: string; // Firebase Auth UID
email: string; // E-Mail (für Anzeige)
clockIn: Timestamp; // Clock-In Zeitpunkt
clockOut: Timestamp | null; // Clock-Out Zeitpunkt (null wenn running)
status: 'running' | 'completed' | 'pending_approval' | 'rejected';
durationMinutes: number | null; // Dauer in Minuten
entryType?: 'work' | 'break'; // Typ (Standard: 'work')
projectId?: string; // Optional: Projekt-ID
note?: string; // Optional: Notiz
source?: 'clock' | 'manual' | 'shift'; // Quelle
createdAt: Timestamp;
updatedAt: Timestamp;
}
Status-Werte:
- running: Eintrag läuft noch (Clock-In ohne Clock-Out)
- completed: Eintrag abgeschlossen und genehmigt (ohne Projekt oder nach Genehmigung)
- pending_approval: Eintrag wartet auf Genehmigung (nur bei projektbezogenen Einträgen)
- rejected: Eintrag wurde abgelehnt (nur bei projektbezogenen Einträgen)
Time Accounts
Pfad: /tenants/{tenantId}/timeAccounts/{accountId}
Dokument-ID: {userId}_{year}_{month}
{
userId: string;
year: number;
month: number;
targetHours: number; // Zielstunden
actualHours: number; // Ist-Stunden (gesamt)
timeTrackingHours: number; // Stunden aus Zeiterfassung
shiftHours: number; // Stunden aus Schichten
balanceHours: number; // Saldo (Ist - Ziel)
manualAdjustments: Array<{...}>; // Manuelle Anpassungen
complianceAdjustments: Array<{...}>; // Compliance-Anpassungen
createdAt: Timestamp;
updatedAt: Timestamp;
}
Time Account Targets
Pfad: /tenants/{tenantId}/timeAccountTargets/{userId}
{
userId: string;
monthlyTargetHours: number; // Zielstunden pro Monat
employmentType?: 'full_time' | 'part_time';
weeklyHours?: number; // Wöchentliche Stunden
updatedAt: Timestamp;
updatedBy: string; // UID des Admins
}
Absences
Pfad: /tenants/{tenantId}/absences/{absenceId}
{
uid: string;
email: string;
type: 'vacation' | 'sick' | 'holiday' | 'special';
startDate: string; // ISO 8601 Datum
endDate: string; // ISO 8601 Datum
days: number; // Anzahl Tage
status: 'pending' | 'approved' | 'rejected';
requestedBy: string; // UID
approvedBy?: string; // UID (optional)
approvedAt?: Timestamp; // Optional
rejectionReason?: string; // Optional
note?: string; // Optional
createdAt: Timestamp;
updatedAt: Timestamp;
}
Approvals
Pfad: /tenants/{tenantId}/approvals/{approvalId}
{
type: 'time_entry' | 'absence' | 'overtime' | 'allowance' | 'project_time_entry';
entityId: string; // ID des Objekts
entityType: string; // Typ des Objekts
requestedBy: string; // UID
requestedAt: Timestamp;
approvedBy?: string; // UID (optional)
approvedAt?: Timestamp; // Optional
rejectedBy?: string; // UID (optional)
rejectedAt?: Timestamp; // Optional
rejectionReason?: string; // Optional
status: 'pending' | 'approved' | 'rejected';
metadata?: Record<string, unknown>; // Zusätzliche Daten
createdAt: Timestamp;
updatedAt: Timestamp;
}
Fehlerfälle
HTTP-Status-Codes
- 200 OK: Erfolgreich
- 201 Created: Ressource erstellt
- 400 Bad Request: Validierungsfehler
- 401 Unauthorized: Nicht authentifiziert
- 403 Forbidden: Keine Berechtigung
- 404 Not Found: Ressource nicht gefunden
- 409 Conflict: Konflikt (z.B. bereits eingestempelt)
- 500 Internal Server Error: Server-Fehler
Fehlercodes
ALREADY_CLOCKED_IN: Bereits eingestempeltNOT_CLOCKED_IN: Nicht eingestempeltBREAK_ALREADY_RUNNING: Pause läuft bereitsNO_BREAK_RUNNING: Keine laufende PauseNO_WORK_ENTRY: Kein laufender ArbeitseintragVALIDATION_ERROR: ValidierungsfehlerNOT_FOUND: Ressource nicht gefundenFORBIDDEN: Keine Berechtigung
Logging
Fehler werden protokolliert und können über ein Monitoring-System überwacht werden.
Tests
Status: Nicht im Repo gefunden
TODO: - Unit-Tests für Service-Funktionen - Integration-Tests für API-Endpoints - E2E-Tests für Frontend-Komponenten
Empfohlene Test-Struktur: Unit-Tests für Service, Integration-Tests für API, E2E-Tests für Frontend (Details siehe interne Entwickler-Dokumentation).
FAQ / Troubleshooting
Was passiert bei doppeltem Clock-In?
Wenn ein User bereits eingestempelt ist und erneut Clock-In aufruft, erhält er einen 409 ALREADY_CLOCKED_IN Fehler. Der laufende Entry bleibt unverändert.
Lösung: Zuerst Clock-Out aufrufen, dann erneut Clock-In.
Wie werden Pausen automatisch erfasst?
Bei Clock-Out oder manuellen Einträgen wird automatisch geprüft, ob eine Pause erforderlich ist (basierend auf ArbZG): - > 6 Stunden: 30 Minuten Pause - > 9 Stunden: 45 Minuten Pause
Falls erforderlich, wird automatisch ein BREAK-Entry erstellt.
Hinweis: Automatische Pausen werden nur erstellt, wenn noch keine automatische Pause für den Tag existiert.
Wie funktioniert das Zeitkonto?
Das Zeitkonto wird automatisch aktualisiert, wenn: - Ein TimeEntry erstellt/aktualisiert/gelöscht wird - Eine Schicht abgeschlossen wird (wenn shift-pool Modul aktiv)
Berechnung:
- actualHours = timeTrackingHours + shiftHours
- balanceHours = actualHours - targetHours
Manuelle Anpassungen: Admins können manuelle Anpassungen hinzufügen (z.B. Überstunden-Ausgleich).
Wie nutze ich die API extern mit Token?
- Token erstellen: In den Einstellungen → Sicherheit → API-Tokens → Neuer Token
- Token kopieren: Beim Erstellen sofort kopieren (wird nicht erneut angezeigt)
- Token verwenden: In jedem Request als
Authorization: Bearer <token>Header
Beispiel:
API_TOKEN="${TIMEAM_API_TOKEN}"
curl -X GET https://api.example.com/api/time-tracking/status \
-H "Authorization: Bearer ${API_TOKEN}"
Kann ich mehrere API-Tokens haben?
Ja, Sie können beliebig viele API-Tokens erstellen. Jeder Token hat einen eigenen Namen und kann unabhängig gelöscht werden.
Empfehlung: Erstellen Sie separate Tokens für verschiedene Anwendungen (z.B. "Production API", "Development Scripts").
Was passiert, wenn ich einen Token verliere?
Wenn Sie einen Token verlieren, können Sie ihn nicht wiederherstellen. Sie müssen: 1. Den alten Token löschen (falls Sie ihn noch sehen können) 2. Einen neuen Token erstellen
Sicherheit: Gelöschte Tokens funktionieren sofort nicht mehr.
Wie werden projektbezogene Zeiterfassungen genehmigt?
Projektbezogene Zeiterfassungen (mit projectId) erfordern immer eine Genehmigung, unabhängig von der Arbeitszeit.
Workflow:
1. User erstellt TimeEntry mit projectId (Clock-In/Out oder manuell)
2. Status wird automatisch auf PENDING_APPROVAL gesetzt (nicht COMPLETED)
3. System erstellt automatisch Approval-Request
4. Admin/Manager genehmigt oder lehnt ab
5. Bei Genehmigung: Status wird auf COMPLETED gesetzt
6. Bei Ablehnung: Status wird auf REJECTED gesetzt
7. User wird benachrichtigt
Wichtig: Einträge ohne projectId werden weiterhin direkt auf COMPLETED gesetzt und benötigen keine Genehmigung.
Wie funktioniert die Überstunden-Genehmigung?
Überstunden-Genehmigungen werden automatisch erstellt, wenn: - Die tägliche Arbeitszeit (nur WORK-Einträge) über 8 Stunden liegt - Der TimeEntry abgeschlossen wird (Clock-Out oder manueller Eintrag)
Workflow: 1. User arbeitet > 8 Stunden 2. System erstellt automatisch Approval-Request mit Überstunden 3. Admin/Manager genehmigt oder lehnt ab 4. User wird benachrichtigt
Wie werden Abwesenheiten genehmigt?
Abwesenheiten werden automatisch als pending erstellt. Admins/Manager können sie über die Genehmigungs-Endpoints genehmigen oder ablehnen.
Kontingent-Prüfung: Beim Erstellen wird automatisch geprüft, ob genügend Kontingent verfügbar ist.