Enhance login functionality in AuthController and AuthService; add optional action parameter to login method, execute corresponding actions post-login, and handle action warnings. Update frontend components to trigger data refresh on successful login and display warnings if actions fail. Adjust SQL query in TimeEntryService for improved grouping.
This commit is contained in:
55
backend/MIGRATION_timewish_dates.md
Normal file
55
backend/MIGRATION_timewish_dates.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# Migration: Timewish-Tabelle erweitern
|
||||
|
||||
## Problem
|
||||
Die Produktions-Datenbank fehlen die Spalten `start_date` und `end_date` in der `timewish` Tabelle.
|
||||
Dies führt zu Fehler 500 bei `/api/time-entries/stats/summary`.
|
||||
|
||||
## Fehler-Log
|
||||
```
|
||||
ER_BAD_FIELD_ERROR: Unknown column 'start_date' in 'field list'
|
||||
```
|
||||
|
||||
## Lösung
|
||||
Führe das Migrations-Skript auf dem Produktionsserver aus:
|
||||
|
||||
```bash
|
||||
# 1. Auf den Server verbinden
|
||||
ssh ihr-server
|
||||
|
||||
# 2. Zum Backend-Verzeichnis wechseln
|
||||
cd /var/www/timeclock/backend
|
||||
|
||||
# 3. Migration ausführen
|
||||
mysql -u timeclock -p timeclock < add-timewish-dates.sql
|
||||
|
||||
# Oder mit root-User:
|
||||
sudo mysql timeclock < add-timewish-dates.sql
|
||||
```
|
||||
|
||||
## Was macht das Skript?
|
||||
1. Prüft ob die Spalten bereits existieren
|
||||
2. Fügt `start_date` hinzu (DEFAULT '2000-01-01')
|
||||
3. Fügt `end_date` hinzu (DEFAULT NULL = unbegrenzt gültig)
|
||||
4. Zeigt die neue Tabellenstruktur an
|
||||
|
||||
## Nach der Migration
|
||||
Alle bestehenden timewish-Einträge erhalten:
|
||||
- `start_date` = '2000-01-01' (gilt seit langem)
|
||||
- `end_date` = NULL (gilt unbegrenzt)
|
||||
|
||||
Das entspricht dem bisherigen Verhalten.
|
||||
|
||||
## Testen
|
||||
Nach der Migration sollte dieser API-Call erfolgreich sein:
|
||||
```bash
|
||||
curl http://localhost:3010/api/time-entries/stats/summary \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
```
|
||||
|
||||
## Rollback (falls nötig)
|
||||
Falls Probleme auftreten:
|
||||
```sql
|
||||
ALTER TABLE timewish DROP COLUMN start_date;
|
||||
ALTER TABLE timewish DROP COLUMN end_date;
|
||||
```
|
||||
|
||||
48
backend/add-timewish-dates.sql
Normal file
48
backend/add-timewish-dates.sql
Normal file
@@ -0,0 +1,48 @@
|
||||
-- Migration: Füge start_date und end_date zu timewish Tabelle hinzu
|
||||
-- Datum: 2025-10-20
|
||||
-- Beschreibung: Erweitert die timewish Tabelle um Gültigkeitsbereiche
|
||||
|
||||
-- Prüfe ob Spalten bereits existieren (für Sicherheit)
|
||||
SET @col_exists = (
|
||||
SELECT COUNT(*)
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'timewish'
|
||||
AND COLUMN_NAME = 'start_date'
|
||||
);
|
||||
|
||||
-- Füge start_date hinzu, falls nicht vorhanden
|
||||
SET @sql = IF(@col_exists = 0,
|
||||
'ALTER TABLE `timewish` ADD COLUMN `start_date` DATE NOT NULL DEFAULT "2000-01-01" COMMENT "Ab welchem Datum gilt dieser Timewish"',
|
||||
'SELECT "Spalte start_date existiert bereits" AS Info'
|
||||
);
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- Prüfe ob end_date bereits existiert
|
||||
SET @col_exists = (
|
||||
SELECT COUNT(*)
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'timewish'
|
||||
AND COLUMN_NAME = 'end_date'
|
||||
);
|
||||
|
||||
-- Füge end_date hinzu, falls nicht vorhanden
|
||||
SET @sql = IF(@col_exists = 0,
|
||||
'ALTER TABLE `timewish` ADD COLUMN `end_date` DATE DEFAULT NULL COMMENT "Bis welchem Datum gilt dieser Timewish (NULL = unbegrenzt)"',
|
||||
'SELECT "Spalte end_date existiert bereits" AS Info'
|
||||
);
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- Zeige Ergebnis
|
||||
SELECT
|
||||
'Migration abgeschlossen!' AS Status,
|
||||
'start_date und end_date zur timewish Tabelle hinzugefügt' AS Details;
|
||||
|
||||
-- Zeige aktuelle Tabellenstruktur
|
||||
DESCRIBE timewish;
|
||||
|
||||
@@ -40,7 +40,7 @@ class AuthController {
|
||||
*/
|
||||
async login(req, res) {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
const { email, password, action } = req.body;
|
||||
|
||||
if (!email || !password) {
|
||||
return res.status(400).json({
|
||||
@@ -49,14 +49,21 @@ class AuthController {
|
||||
});
|
||||
}
|
||||
|
||||
const result = await authService.login(email, password);
|
||||
const result = await authService.login(email, password, action);
|
||||
|
||||
res.json({
|
||||
const response = {
|
||||
success: true,
|
||||
message: 'Login erfolgreich',
|
||||
token: result.token,
|
||||
user: result.user
|
||||
});
|
||||
};
|
||||
|
||||
// Füge Warnung hinzu, falls vorhanden
|
||||
if (result.actionWarning) {
|
||||
response.actionWarning = result.actionWarning;
|
||||
}
|
||||
|
||||
res.json(response);
|
||||
} catch (error) {
|
||||
console.error('Login-Fehler:', error);
|
||||
|
||||
|
||||
@@ -124,9 +124,10 @@ class AuthService {
|
||||
* Benutzer einloggen
|
||||
* @param {string} email - E-Mail-Adresse
|
||||
* @param {string} password - Passwort
|
||||
* @param {string} action - Gewünschte Aktion nach Login (optional: '0', '1', '2', '3', '4')
|
||||
* @returns {Promise<Object>} Token und Benutzer-Info
|
||||
*/
|
||||
async login(email, password) {
|
||||
async login(email, password, action = '0') {
|
||||
const { User, AuthInfo, AuthToken } = database.getModels();
|
||||
|
||||
console.log('Login-Versuch für E-Mail:', email);
|
||||
@@ -211,7 +212,7 @@ class AuthService {
|
||||
version: 0
|
||||
});
|
||||
|
||||
return {
|
||||
const result = {
|
||||
token,
|
||||
user: {
|
||||
id: authInfo.user.id,
|
||||
@@ -220,6 +221,112 @@ class AuthService {
|
||||
role: authInfo.user.role
|
||||
}
|
||||
};
|
||||
|
||||
// Führe gewünschte Aktion aus (falls angegeben und gültig)
|
||||
if (action && action !== '0') {
|
||||
try {
|
||||
const actionWarning = await this._executeLoginAction(authInfo.user.id, action);
|
||||
if (actionWarning) {
|
||||
result.actionWarning = actionWarning;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Ausführen der Login-Aktion:', error);
|
||||
result.actionWarning = `Aktion konnte nicht ausgeführt werden: ${error.message}`;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Führt die gewünschte Aktion nach dem Login aus
|
||||
* @param {number} userId - Benutzer-ID
|
||||
* @param {string} action - Gewünschte Aktion ('1', '2', '3', '4')
|
||||
* @returns {Promise<string|null>} Warnung, falls Aktion nicht ausgeführt werden konnte
|
||||
* @private
|
||||
*/
|
||||
async _executeLoginAction(userId, action) {
|
||||
const timeEntryService = require('./TimeEntryService');
|
||||
|
||||
// Hole den aktuellen Status
|
||||
const currentState = await timeEntryService.getCurrentState(userId);
|
||||
|
||||
console.log(`Login-Aktion angefordert: ${action}, aktueller Status: ${currentState || 'null'}`);
|
||||
|
||||
// Mapping: action-Nummer -> Worklog-Action
|
||||
const actionMap = {
|
||||
'1': 'start work', // Arbeit beginnen
|
||||
'2': 'start pause', // Pause beginnen
|
||||
'3': 'stop pause', // Pause beenden
|
||||
'4': 'stop work' // Feierabend
|
||||
};
|
||||
|
||||
const worklogAction = actionMap[action];
|
||||
if (!worklogAction) {
|
||||
return 'Ungültige Aktion';
|
||||
}
|
||||
|
||||
// Validiere, ob die Aktion erlaubt ist
|
||||
const isValid = this._isValidLoginAction(currentState, worklogAction);
|
||||
|
||||
if (!isValid) {
|
||||
const actionNames = {
|
||||
'start work': 'Arbeit beginnen',
|
||||
'start pause': 'Pause beginnen',
|
||||
'stop pause': 'Pause beenden',
|
||||
'stop work': 'Feierabend'
|
||||
};
|
||||
|
||||
const currentStateNames = {
|
||||
'start work': 'Arbeit läuft',
|
||||
'stop work': 'Feierabend',
|
||||
'start pause': 'Pause läuft',
|
||||
'stop pause': 'Pause beendet',
|
||||
'null': 'Keine Aktion'
|
||||
};
|
||||
|
||||
const actionName = actionNames[worklogAction] || worklogAction;
|
||||
const currentStateName = currentStateNames[currentState || 'null'] || currentState;
|
||||
|
||||
return `Aktion "${actionName}" kann nicht ausgeführt werden (aktueller Status: ${currentStateName})`;
|
||||
}
|
||||
|
||||
// Führe die Aktion aus
|
||||
try {
|
||||
await timeEntryService.clock(userId, worklogAction);
|
||||
console.log(`Login-Aktion erfolgreich ausgeführt: ${worklogAction}`);
|
||||
return null; // Kein Fehler
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Ausführen der Login-Aktion:', error);
|
||||
return `Aktion konnte nicht ausgeführt werden: ${error.message}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validiert, ob eine Login-Aktion erlaubt ist
|
||||
* @param {string|null} currentState - Aktueller Status
|
||||
* @param {string} action - Gewünschte Aktion
|
||||
* @returns {boolean} True, wenn die Aktion erlaubt ist
|
||||
* @private
|
||||
*/
|
||||
_isValidLoginAction(currentState, action) {
|
||||
// Erlaubte Übergänge basierend auf den Anforderungen:
|
||||
// 1. Arbeit beginnen: user hat noch gar keine Aktion, oder letzte Aktion war stop work
|
||||
// 2. Pause beginnen: letzte Aktion "start work" oder "stop pause"
|
||||
// 3. Pause beenden: letzte Aktion "start pause"
|
||||
// 4. Feierabend: letzte Aktion "start work" oder "stop pause"
|
||||
|
||||
const allowedTransitions = {
|
||||
'start work': ['null', 'stop work'],
|
||||
'start pause': ['start work', 'stop pause'],
|
||||
'stop pause': ['start pause'],
|
||||
'stop work': ['start work', 'stop pause']
|
||||
};
|
||||
|
||||
const allowed = allowedTransitions[action] || [];
|
||||
const stateToCheck = currentState || 'null';
|
||||
|
||||
return allowed.includes(stateToCheck);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1069,7 +1069,7 @@ class TimeEntryService {
|
||||
AND w1.user_id = ?
|
||||
AND DATE(COALESCE(w1_fix.fix_date_time, w1.tstamp)) <= ?
|
||||
AND DAYOFWEEK(DATE(COALESCE(w1_fix.fix_date_time, w1.tstamp))) BETWEEN 2 AND 6
|
||||
GROUP BY DATE(COALESCE(w1_fix.fix_date_time, w1.tstamp))
|
||||
GROUP BY DATE(COALESCE(w1_fix.fix_date_time, w1.tstamp)), DAYOFWEEK(DATE(COALESCE(w1_fix.fix_date_time, w1.tstamp)))
|
||||
ORDER BY DATE(COALESCE(w1_fix.fix_date_time, w1.tstamp))
|
||||
`;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user