382 lines
15 KiB
JavaScript
382 lines
15 KiB
JavaScript
const sqlite3 = require('sqlite3').verbose();
|
||
const bcrypt = require('bcryptjs');
|
||
const path = require('path');
|
||
|
||
// Datenbank-Pfad: Umgebungsvariable oder Standard-Pfad
|
||
const dbPath = process.env.DB_PATH || path.join(__dirname, 'stundenerfassung.db');
|
||
const db = new sqlite3.Database(dbPath);
|
||
|
||
// Datenbank initialisieren
|
||
function initDatabase() {
|
||
db.serialize(() => {
|
||
// Benutzer-Tabelle
|
||
db.run(`CREATE TABLE IF NOT EXISTS users (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
username TEXT UNIQUE NOT NULL,
|
||
password TEXT NOT NULL,
|
||
firstname TEXT NOT NULL,
|
||
lastname TEXT NOT NULL,
|
||
role TEXT NOT NULL DEFAULT 'mitarbeiter',
|
||
last_week_start TEXT,
|
||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||
)`);
|
||
|
||
// Migration: last_week_start Spalte hinzufügen falls sie nicht existiert
|
||
db.run(`ALTER TABLE users ADD COLUMN last_week_start TEXT`, (err) => {
|
||
// Fehler ignorieren wenn Spalte bereits existiert
|
||
});
|
||
|
||
// Migration: default_break_minutes (Standard-Pausenzeit pro Mitarbeiter)
|
||
db.run(`ALTER TABLE users ADD COLUMN default_break_minutes INTEGER DEFAULT 30`, (err) => {
|
||
if (err && !err.message.includes('duplicate column')) {
|
||
console.warn('Warnung beim Hinzufügen der Spalte default_break_minutes:', err.message);
|
||
}
|
||
});
|
||
|
||
// Stundenerfassung-Tabelle
|
||
db.run(`CREATE TABLE IF NOT EXISTS timesheet_entries (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
user_id INTEGER NOT NULL,
|
||
date TEXT NOT NULL,
|
||
start_time TEXT,
|
||
end_time TEXT,
|
||
break_minutes INTEGER DEFAULT 0,
|
||
total_hours REAL,
|
||
notes TEXT,
|
||
activity1_desc TEXT,
|
||
activity1_hours REAL,
|
||
activity2_desc TEXT,
|
||
activity2_hours REAL,
|
||
activity3_desc TEXT,
|
||
activity3_hours REAL,
|
||
activity4_desc TEXT,
|
||
activity4_hours REAL,
|
||
activity5_desc TEXT,
|
||
activity5_hours REAL,
|
||
status TEXT DEFAULT 'offen',
|
||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||
FOREIGN KEY (user_id) REFERENCES users(id)
|
||
)`);
|
||
|
||
// Migration: Tätigkeitsfelder hinzufügen falls sie nicht existieren
|
||
const activityColumns = [
|
||
'activity1_desc', 'activity1_hours',
|
||
'activity2_desc', 'activity2_hours',
|
||
'activity3_desc', 'activity3_hours',
|
||
'activity4_desc', 'activity4_hours',
|
||
'activity5_desc', 'activity5_hours'
|
||
];
|
||
|
||
activityColumns.forEach(col => {
|
||
const colType = col.includes('_hours') ? 'REAL' : 'TEXT';
|
||
db.run(`ALTER TABLE timesheet_entries ADD COLUMN ${col} ${colType}`, (err) => {
|
||
// Fehler ignorieren wenn Spalte bereits existiert
|
||
});
|
||
});
|
||
|
||
// Wöchentliche Stundenzettel-Tabelle
|
||
db.run(`CREATE TABLE IF NOT EXISTS weekly_timesheets (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
user_id INTEGER NOT NULL,
|
||
week_start TEXT NOT NULL,
|
||
week_end TEXT NOT NULL,
|
||
version INTEGER DEFAULT 1,
|
||
status TEXT DEFAULT 'eingereicht',
|
||
submitted_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||
reviewed_by INTEGER,
|
||
reviewed_at DATETIME,
|
||
pdf_downloaded_at DATETIME,
|
||
pdf_downloaded_by INTEGER,
|
||
FOREIGN KEY (user_id) REFERENCES users(id),
|
||
FOREIGN KEY (reviewed_by) REFERENCES users(id),
|
||
FOREIGN KEY (pdf_downloaded_by) REFERENCES users(id)
|
||
)`);
|
||
|
||
// Migration: version Spalte hinzufügen falls sie nicht existiert
|
||
db.run(`ALTER TABLE weekly_timesheets ADD COLUMN version INTEGER DEFAULT 1`, (err) => {
|
||
// Fehler ignorieren wenn Spalte bereits existiert
|
||
// Wenn Spalte neu erstellt wurde, bestehende Einträge haben automatisch version = 1
|
||
});
|
||
|
||
// Migration: pdf_downloaded_at Spalte hinzufügen falls sie nicht existiert
|
||
db.run(`ALTER TABLE weekly_timesheets ADD COLUMN pdf_downloaded_at DATETIME`, (err) => {
|
||
// Fehler ignorieren wenn Spalte bereits existiert
|
||
});
|
||
|
||
// Migration: pdf_downloaded_by Spalte hinzufügen falls sie nicht existiert
|
||
db.run(`ALTER TABLE weekly_timesheets ADD COLUMN pdf_downloaded_by INTEGER`, (err) => {
|
||
// Fehler ignorieren wenn Spalte bereits existiert
|
||
});
|
||
|
||
// Migration: version_reason Spalte hinzufügen
|
||
db.run(`ALTER TABLE weekly_timesheets ADD COLUMN version_reason TEXT`, (err) => {
|
||
// Fehler ignorieren wenn Spalte bereits existiert
|
||
if (err && !err.message.includes('duplicate column')) {
|
||
console.warn('Warnung beim Hinzufügen der Spalte version_reason:', err.message);
|
||
}
|
||
});
|
||
|
||
// Migration: admin_comment Spalte hinzufügen
|
||
db.run(`ALTER TABLE weekly_timesheets ADD COLUMN admin_comment TEXT`, (err) => {
|
||
// Fehler ignorieren wenn Spalte bereits existiert
|
||
if (err && !err.message.includes('duplicate column')) {
|
||
console.warn('Warnung beim Hinzufügen der Spalte admin_comment:', err.message);
|
||
}
|
||
});
|
||
|
||
// Migration: Projektnummern für Tätigkeiten hinzufügen
|
||
const projectNumberColumns = [
|
||
'activity1_project_number', 'activity2_project_number',
|
||
'activity3_project_number', 'activity4_project_number',
|
||
'activity5_project_number'
|
||
];
|
||
|
||
projectNumberColumns.forEach(col => {
|
||
db.run(`ALTER TABLE timesheet_entries ADD COLUMN ${col} TEXT`, (err) => {
|
||
// Fehler ignorieren wenn Spalte bereits existiert
|
||
if (err && !err.message.includes('duplicate column')) {
|
||
console.warn(`Warnung beim Hinzufügen der Spalte ${col}:`, err.message);
|
||
}
|
||
});
|
||
});
|
||
|
||
// Migration: Überstunden und Urlaub hinzufügen
|
||
db.run(`ALTER TABLE timesheet_entries ADD COLUMN overtime_taken_hours REAL`, (err) => {
|
||
// Fehler ignorieren wenn Spalte bereits existiert
|
||
if (err && !err.message.includes('duplicate column')) {
|
||
console.warn('Warnung beim Hinzufügen der Spalte overtime_taken_hours:', err.message);
|
||
}
|
||
});
|
||
|
||
db.run(`ALTER TABLE timesheet_entries ADD COLUMN vacation_type TEXT`, (err) => {
|
||
// Fehler ignorieren wenn Spalte bereits existiert
|
||
if (err && !err.message.includes('duplicate column')) {
|
||
console.warn('Warnung beim Hinzufügen der Spalte vacation_type:', err.message);
|
||
}
|
||
});
|
||
|
||
// Migration: Krank-Status hinzufügen
|
||
db.run(`ALTER TABLE timesheet_entries ADD COLUMN sick_status INTEGER DEFAULT 0`, (err) => {
|
||
// Fehler ignorieren wenn Spalte bereits existiert
|
||
if (err && !err.message.includes('duplicate column')) {
|
||
console.warn('Warnung beim Hinzufügen der Spalte sick_status:', err.message);
|
||
}
|
||
});
|
||
|
||
// Migration: Pausen-Zeiten für API-Zeiterfassung hinzufügen
|
||
db.run(`ALTER TABLE timesheet_entries ADD COLUMN pause_start_time TEXT`, (err) => {
|
||
// Fehler ignorieren wenn Spalte bereits existiert
|
||
if (err && !err.message.includes('duplicate column')) {
|
||
console.warn('Warnung beim Hinzufügen der Spalte pause_start_time:', err.message);
|
||
}
|
||
});
|
||
|
||
db.run(`ALTER TABLE timesheet_entries ADD COLUMN pause_end_time TEXT`, (err) => {
|
||
// Fehler ignorieren wenn Spalte bereits existiert
|
||
if (err && !err.message.includes('duplicate column')) {
|
||
console.warn('Warnung beim Hinzufügen der Spalte pause_end_time:', err.message);
|
||
}
|
||
});
|
||
|
||
// Migration: Wochenend-Reise und angewendeter Wochenend-Prozentsatz hinzufügen
|
||
db.run(`ALTER TABLE timesheet_entries ADD COLUMN weekend_travel INTEGER DEFAULT 0`, (err) => {
|
||
// Fehler ignorieren wenn Spalte bereits existiert
|
||
if (err && !err.message.includes('duplicate column')) {
|
||
console.warn('Warnung beim Hinzufügen der Spalte weekend_travel:', err.message);
|
||
}
|
||
});
|
||
|
||
db.run(`ALTER TABLE timesheet_entries ADD COLUMN applied_weekend_percentage REAL DEFAULT NULL`, (err) => {
|
||
// Fehler ignorieren wenn Spalte bereits existiert
|
||
if (err && !err.message.includes('duplicate column')) {
|
||
console.warn('Warnung beim Hinzufügen der Spalte applied_weekend_percentage:', err.message);
|
||
}
|
||
});
|
||
|
||
// Migration: User-Felder hinzufügen (Personalnummer, Wochenstunden, Urlaubstage)
|
||
db.run(`ALTER TABLE users ADD COLUMN personalnummer TEXT`, (err) => {
|
||
// Fehler ignorieren wenn Spalte bereits existiert
|
||
});
|
||
|
||
db.run(`ALTER TABLE users ADD COLUMN wochenstunden REAL`, (err) => {
|
||
// Fehler ignorieren wenn Spalte bereits existiert
|
||
});
|
||
|
||
db.run(`ALTER TABLE users ADD COLUMN urlaubstage REAL`, (err) => {
|
||
// Fehler ignorieren wenn Spalte bereits existiert
|
||
});
|
||
|
||
// Migration: Überstunden-Offset (manuelle Korrektur durch Verwaltung)
|
||
db.run(`ALTER TABLE users ADD COLUMN overtime_offset_hours REAL DEFAULT 0`, (err) => {
|
||
// Fehler ignorieren wenn Spalte bereits existiert
|
||
if (err && !err.message.includes('duplicate column')) {
|
||
console.warn('Warnung beim Hinzufügen der Spalte overtime_offset_hours:', err.message);
|
||
}
|
||
});
|
||
|
||
// Tabelle: Protokoll für Überstunden-Korrekturen durch Verwaltung
|
||
db.run(`CREATE TABLE IF NOT EXISTS overtime_corrections (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
user_id INTEGER NOT NULL,
|
||
correction_hours REAL NOT NULL,
|
||
reason TEXT,
|
||
corrected_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||
FOREIGN KEY (user_id) REFERENCES users(id)
|
||
)`, (err) => {
|
||
if (err) {
|
||
console.warn('Warnung beim Erstellen der overtime_corrections Tabelle:', err.message);
|
||
}
|
||
});
|
||
|
||
// Migration: reason Spalte für overtime_corrections hinzufügen (falls Tabelle bereits existiert)
|
||
db.run(`ALTER TABLE overtime_corrections ADD COLUMN reason TEXT`, (err) => {
|
||
// Fehler ignorieren wenn Spalte bereits existiert
|
||
if (err && !err.message.includes('duplicate column')) {
|
||
// "duplicate column" ist SQLite CLI wording; sqlite3 node liefert typischerweise "duplicate column name"
|
||
if (!err.message.includes('duplicate column name')) {
|
||
console.warn('Warnung beim Hinzufügen der Spalte reason (overtime_corrections):', err.message);
|
||
}
|
||
}
|
||
});
|
||
|
||
// Migration: Urlaubstage-Offset (manuelle Korrektur durch Verwaltung)
|
||
db.run(`ALTER TABLE users ADD COLUMN vacation_offset_days REAL DEFAULT 0`, (err) => {
|
||
// Fehler ignorieren wenn Spalte bereits existiert
|
||
if (err && !err.message.includes('duplicate column')) {
|
||
console.warn('Warnung beim Hinzufügen der Spalte vacation_offset_days:', err.message);
|
||
}
|
||
});
|
||
|
||
// Migration: Arbeitstage pro Woche hinzufügen
|
||
db.run(`ALTER TABLE users ADD COLUMN arbeitstage INTEGER DEFAULT 5`, (err) => {
|
||
// Fehler ignorieren wenn Spalte bereits existiert
|
||
if (err && !err.message.includes('duplicate column')) {
|
||
console.warn('Warnung beim Hinzufügen der Spalte arbeitstage:', err.message);
|
||
}
|
||
});
|
||
|
||
// Migration: ping_ip Spalte hinzufügen
|
||
db.run(`ALTER TABLE users ADD COLUMN ping_ip TEXT`, (err) => {
|
||
// Fehler ignorieren wenn Spalte bereits existiert
|
||
});
|
||
|
||
// Ping-Status-Tabelle für IP-basierte Zeiterfassung
|
||
db.run(`CREATE TABLE IF NOT EXISTS ping_status (
|
||
user_id INTEGER NOT NULL,
|
||
date TEXT NOT NULL,
|
||
last_successful_ping DATETIME,
|
||
failed_ping_count INTEGER DEFAULT 0,
|
||
start_time_set INTEGER DEFAULT 0,
|
||
first_failed_ping_time DATETIME,
|
||
PRIMARY KEY (user_id, date),
|
||
FOREIGN KEY (user_id) REFERENCES users(id)
|
||
)`, (err) => {
|
||
if (err && !err.message.includes('duplicate column')) {
|
||
console.warn('Warnung beim Erstellen der ping_status Tabelle:', err.message);
|
||
}
|
||
});
|
||
|
||
// LDAP-Konfiguration-Tabelle
|
||
db.run(`CREATE TABLE IF NOT EXISTS ldap_config (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
enabled INTEGER DEFAULT 0,
|
||
url TEXT,
|
||
bind_dn TEXT,
|
||
bind_password TEXT,
|
||
base_dn TEXT,
|
||
user_search_filter TEXT,
|
||
username_attribute TEXT DEFAULT 'sAMAccountName',
|
||
firstname_attribute TEXT DEFAULT 'givenName',
|
||
lastname_attribute TEXT DEFAULT 'sn',
|
||
sync_interval INTEGER DEFAULT 0,
|
||
last_sync DATETIME,
|
||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||
)`);
|
||
|
||
// LDAP-Sync-Log-Tabelle
|
||
db.run(`CREATE TABLE IF NOT EXISTS ldap_sync_log (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
sync_type TEXT NOT NULL,
|
||
status TEXT NOT NULL,
|
||
users_synced INTEGER DEFAULT 0,
|
||
error_message TEXT,
|
||
sync_started_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||
sync_completed_at DATETIME
|
||
)`);
|
||
|
||
// Feiertage (öffentliche Feiertage BW) – API wird nur 1x pro Jahr aufgerufen
|
||
db.run(`CREATE TABLE IF NOT EXISTS public_holidays (
|
||
date TEXT PRIMARY KEY,
|
||
name TEXT
|
||
)`);
|
||
|
||
// System-Optionen-Tabelle für Wochenend-Prozentsätze
|
||
db.run(`CREATE TABLE IF NOT EXISTS system_options (
|
||
id INTEGER PRIMARY KEY DEFAULT 1,
|
||
saturday_percentage REAL DEFAULT 100,
|
||
sunday_percentage REAL DEFAULT 100,
|
||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||
CHECK (id = 1)
|
||
)`);
|
||
|
||
// Standard-Eintrag für system_options erstellen falls nicht vorhanden
|
||
db.run(`INSERT OR IGNORE INTO system_options (id, saturday_percentage, sunday_percentage) VALUES (1, 100, 100)`, (err) => {
|
||
if (err && !err.message.includes('UNIQUE constraint')) {
|
||
console.warn('Warnung beim Erstellen des Standard-Eintrags für system_options:', err.message);
|
||
}
|
||
});
|
||
|
||
// Migration: checkin_root_url Spalte hinzufügen
|
||
db.run(`ALTER TABLE system_options ADD COLUMN checkin_root_url TEXT`, (err) => {
|
||
// Fehler ignorieren wenn Spalte bereits existiert
|
||
if (err && !err.message.includes('duplicate column')) {
|
||
console.warn('Warnung beim Hinzufügen der Spalte checkin_root_url:', err.message);
|
||
}
|
||
});
|
||
|
||
// Migration: Bestehende Rollen zu JSON-Arrays konvertieren
|
||
// Prüfe ob Rollen noch als einfache Strings gespeichert sind (nicht als JSON-Array)
|
||
db.all('SELECT id, role FROM users', (err, users) => {
|
||
if (!err && users) {
|
||
users.forEach(user => {
|
||
let roleValue = user.role;
|
||
// Prüfe ob es bereits ein JSON-Array ist
|
||
try {
|
||
const parsed = JSON.parse(roleValue);
|
||
// Wenn erfolgreich geparst und es ist ein Array, nichts tun
|
||
if (Array.isArray(parsed)) {
|
||
return; // Bereits JSON-Array
|
||
}
|
||
} catch (e) {
|
||
// Nicht JSON, konvertiere zu JSON-Array
|
||
}
|
||
|
||
// Konvertiere zu JSON-Array
|
||
const roleArray = JSON.stringify([roleValue]);
|
||
db.run('UPDATE users SET role = ? WHERE id = ?', [roleArray, user.id], (err) => {
|
||
if (err) {
|
||
console.warn(`Warnung beim Konvertieren der Rolle für User ${user.id}:`, err.message);
|
||
}
|
||
});
|
||
});
|
||
}
|
||
});
|
||
|
||
// Standard Admin-Benutzer erstellen
|
||
const adminPassword = bcrypt.hashSync('admin123', 10);
|
||
db.run(`INSERT OR IGNORE INTO users (id, username, password, firstname, lastname, role)
|
||
VALUES (1, 'admin', ?, 'System', 'Administrator', ?)`,
|
||
[adminPassword, JSON.stringify(['admin'])]);
|
||
|
||
// Standard Verwaltungs-Benutzer erstellen
|
||
const verwaltungPassword = bcrypt.hashSync('verwaltung123', 10);
|
||
db.run(`INSERT OR IGNORE INTO users (id, username, password, firstname, lastname, role)
|
||
VALUES (2, 'verwaltung', ?, 'Verwaltung', 'User', ?)`,
|
||
[verwaltungPassword, JSON.stringify(['verwaltung'])]);
|
||
});
|
||
}
|
||
|
||
module.exports = { db, initDatabase };
|