06ea149d41
Führt Login mit vier Rollen (Admin, Teamleiter, Mitarbeiter, Beobachter) ein, über die Teammitglieder eigenen Urlaub inkl. Pflicht-Vertreter eintragen können. Die Auslastungsberechnung berücksichtigt Urlaub jetzt als reduzierte Kapazität. Neue Konten vergeben ihr Passwort selbst beim ersten Login. Migrationsskript für bestehende Mitarbeiter aus der Vorversion inklusive. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
163 lines
6.5 KiB
JavaScript
163 lines
6.5 KiB
JavaScript
const path = require('path');
|
|
const fs = require('fs');
|
|
const Database = require('better-sqlite3');
|
|
|
|
const dataDir = path.join(__dirname, 'data');
|
|
if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir, { recursive: true });
|
|
|
|
const dbPath = path.join(dataDir, 'data.db');
|
|
const isNew = !fs.existsSync(dbPath);
|
|
|
|
const db = new Database(dbPath);
|
|
db.pragma('foreign_keys = ON');
|
|
|
|
const schema = fs.readFileSync(path.join(__dirname, 'schema.sql'), 'utf8');
|
|
db.exec(schema);
|
|
|
|
migrateEmployeeName();
|
|
seedInitialAdmin();
|
|
|
|
if (isNew) {
|
|
seed();
|
|
}
|
|
|
|
// Ältere Deployments hatten ein einzelnes "name"-Feld statt first_name/last_name.
|
|
// Bestehende Daten werden erhalten, indem der Name gesplittet statt die Tabelle neu angelegt wird.
|
|
function migrateEmployeeName() {
|
|
const columns = db.prepare('PRAGMA table_info(employees)').all().map((c) => c.name);
|
|
if (!columns.includes('name') || columns.includes('first_name')) return;
|
|
|
|
db.exec('ALTER TABLE employees ADD COLUMN first_name TEXT');
|
|
db.exec('ALTER TABLE employees ADD COLUMN last_name TEXT');
|
|
|
|
const rows = db.prepare('SELECT id, name FROM employees').all();
|
|
const update = db.prepare('UPDATE employees SET first_name = ?, last_name = ? WHERE id = ?');
|
|
for (const row of rows) {
|
|
const parts = (row.name || '').trim().split(/\s+/);
|
|
const first_name = parts.shift() || '';
|
|
const last_name = parts.join(' ');
|
|
update.run(first_name, last_name, row.id);
|
|
}
|
|
|
|
db.exec('ALTER TABLE employees DROP COLUMN name');
|
|
}
|
|
|
|
// Legt beim allerersten Start einen Admin-Zugang für einen BESTEHENDEN Mitarbeiter an,
|
|
// dessen employees.email mit ADMIN_EMAIL übereinstimmt. Es wird bewusst kein Passwort
|
|
// gesetzt: wie bei allen über die Benutzerverwaltung angelegten Konten wird das
|
|
// Passwort erst beim allerersten Login vom Nutzer selbst vergeben (siehe /api/auth/login).
|
|
function seedInitialAdmin() {
|
|
const userCount = db.prepare('SELECT COUNT(*) AS c FROM users').get().c;
|
|
if (userCount > 0) return;
|
|
|
|
const adminEmail = process.env.ADMIN_EMAIL;
|
|
if (!adminEmail) {
|
|
console.log(
|
|
'Hinweis: ADMIN_EMAIL ist nicht gesetzt, es wurde kein Start-Admin angelegt. ' +
|
|
'Bitte ADMIN_EMAIL auf die E-Mail eines bestehenden Mitarbeiters setzen und neu starten, ' +
|
|
'oder scripts/reset-admin.js verwenden.'
|
|
);
|
|
return;
|
|
}
|
|
|
|
const employee = db.prepare('SELECT id FROM employees WHERE email = ?').get(adminEmail);
|
|
if (!employee) {
|
|
console.log(
|
|
`Hinweis: ADMIN_EMAIL (${adminEmail}) passt zu keinem bestehenden Mitarbeiter, ` +
|
|
'es wurde kein Start-Admin angelegt.'
|
|
);
|
|
return;
|
|
}
|
|
|
|
db.prepare('INSERT INTO users (employee_id, email, password_hash, role) VALUES (?, ?, NULL, ?)').run(
|
|
employee.id,
|
|
adminEmail,
|
|
'admin'
|
|
);
|
|
console.log(`Start-Admin für ${adminEmail} angelegt. Das Passwort wird beim ersten Login vergeben.`);
|
|
}
|
|
|
|
function seed() {
|
|
const insertEmployee = db.prepare(
|
|
'INSERT INTO employees (first_name, last_name, email, role, weekly_capacity_hours) VALUES (?, ?, ?, ?, ?)'
|
|
);
|
|
const employees = [
|
|
['Anna', 'Keller', 'anna.keller@example.com', 'Frontend-Entwicklerin', 40],
|
|
['Jonas', 'Weber', 'jonas.weber@example.com', 'Backend-Entwickler', 40],
|
|
['Mira', 'Schulz', 'mira.schulz@example.com', 'UX-Designerin', 32],
|
|
['Tom', 'Fischer', 'tom.fischer@example.com', 'Projektleiter', 40],
|
|
['Lea', 'Hoffmann', 'lea.hoffmann@example.com', 'QA-Ingenieurin', 30],
|
|
];
|
|
const employeeIds = employees.map((e) => insertEmployee.run(...e).lastInsertRowid);
|
|
|
|
const insertTeam = db.prepare('INSERT INTO teams (name, description) VALUES (?, ?)');
|
|
const teamWeb = insertTeam.run('Web-Team', 'Frontend & Backend fürs Kundenportal').lastInsertRowid;
|
|
const teamDesign = insertTeam.run('Design-Team', 'UX/UI und Prototyping').lastInsertRowid;
|
|
|
|
const insertMember = db.prepare(
|
|
'INSERT INTO team_members (team_id, employee_id) VALUES (?, ?)'
|
|
);
|
|
insertMember.run(teamWeb, employeeIds[0]);
|
|
insertMember.run(teamWeb, employeeIds[1]);
|
|
insertMember.run(teamWeb, employeeIds[3]);
|
|
insertMember.run(teamDesign, employeeIds[2]);
|
|
insertMember.run(teamDesign, employeeIds[3]);
|
|
|
|
const insertProject = db.prepare(
|
|
'INSERT INTO projects (name, description, start_date, end_date, color) VALUES (?, ?, ?, ?, ?)'
|
|
);
|
|
const projectPortal = insertProject.run(
|
|
'Kundenportal Relaunch',
|
|
'Neugestaltung des Self-Service-Portals',
|
|
'2026-07-01',
|
|
'2026-08-28',
|
|
'#4f46e5'
|
|
).lastInsertRowid;
|
|
const projectApp = insertProject.run(
|
|
'Mobile App v2',
|
|
'Neue Funktionen für die mobile App',
|
|
'2026-07-06',
|
|
'2026-09-18',
|
|
'#059669'
|
|
).lastInsertRowid;
|
|
|
|
const insertTask = db.prepare(
|
|
'INSERT INTO tasks (project_id, name, start_date, end_date, estimated_hours, status) VALUES (?, ?, ?, ?, ?, ?)'
|
|
);
|
|
const t1 = insertTask.run(projectPortal, 'Konzept & Wireframes', '2026-07-01', '2026-07-10', 40, 'in Arbeit').lastInsertRowid;
|
|
const t2 = insertTask.run(projectPortal, 'Frontend-Umsetzung', '2026-07-13', '2026-08-07', 120, 'geplant').lastInsertRowid;
|
|
const t3 = insertTask.run(projectPortal, 'Backend-API', '2026-07-13', '2026-08-07', 100, 'geplant').lastInsertRowid;
|
|
const t4 = insertTask.run(projectPortal, 'QA & Rollout', '2026-08-10', '2026-08-28', 60, 'geplant').lastInsertRowid;
|
|
|
|
const t5 = insertTask.run(projectApp, 'Anforderungsanalyse', '2026-07-06', '2026-07-17', 30, 'in Arbeit').lastInsertRowid;
|
|
const t6 = insertTask.run(projectApp, 'Umsetzung Feature-Set', '2026-07-20', '2026-09-04', 150, 'geplant').lastInsertRowid;
|
|
const t7 = insertTask.run(projectApp, 'Test & Release', '2026-09-07', '2026-09-18', 50, 'geplant').lastInsertRowid;
|
|
|
|
const insertDependency = db.prepare(
|
|
'INSERT INTO task_dependencies (task_id, depends_on_task_id) VALUES (?, ?)'
|
|
);
|
|
insertDependency.run(t2, t1);
|
|
insertDependency.run(t3, t1);
|
|
insertDependency.run(t4, t2);
|
|
insertDependency.run(t4, t3);
|
|
insertDependency.run(t6, t5);
|
|
insertDependency.run(t7, t6);
|
|
|
|
const insertAssignment = db.prepare(
|
|
'INSERT INTO assignments (task_id, employee_id, allocated_hours_per_week) VALUES (?, ?, ?)'
|
|
);
|
|
insertAssignment.run(t1, employeeIds[2], 20);
|
|
insertAssignment.run(t1, employeeIds[3], 10);
|
|
insertAssignment.run(t2, employeeIds[0], 32);
|
|
insertAssignment.run(t3, employeeIds[1], 32);
|
|
insertAssignment.run(t4, employeeIds[4], 24);
|
|
insertAssignment.run(t4, employeeIds[3], 8);
|
|
insertAssignment.run(t5, employeeIds[3], 16);
|
|
insertAssignment.run(t5, employeeIds[2], 12);
|
|
insertAssignment.run(t6, employeeIds[0], 20);
|
|
insertAssignment.run(t6, employeeIds[1], 24);
|
|
insertAssignment.run(t7, employeeIds[4], 24);
|
|
}
|
|
|
|
module.exports = db;
|