Initial commit: Ressourcenplanung-App
Vue 3 + Vite Frontend und Node/Express + SQLite Backend für Projekt-, Aufgaben-, Mitarbeiter- und Teamverwaltung inkl. Gantt-Zeitplan und Auslastungs-Heatmap.
This commit is contained in:
+103
@@ -0,0 +1,103 @@
|
||||
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);
|
||||
|
||||
if (isNew) {
|
||||
seed();
|
||||
}
|
||||
|
||||
function seed() {
|
||||
const insertEmployee = db.prepare(
|
||||
'INSERT INTO employees (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;
|
||||
+345
@@ -0,0 +1,345 @@
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const db = require('./db');
|
||||
|
||||
const app = express();
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
|
||||
const PORT = process.env.PORT || 8080;
|
||||
|
||||
// ---------- helpers ----------
|
||||
|
||||
function isoWeekKey(date) {
|
||||
const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
|
||||
const dayNum = (d.getUTCDay() + 6) % 7; // Monday = 0
|
||||
d.setUTCDate(d.getUTCDate() - dayNum + 3);
|
||||
const firstThursday = new Date(Date.UTC(d.getUTCFullYear(), 0, 4));
|
||||
const week =
|
||||
1 + Math.round(((d - firstThursday) / 86400000 - 3 + ((firstThursday.getUTCDay() + 6) % 7)) / 7);
|
||||
return `${d.getUTCFullYear()}-W${String(week).padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
function mondayOf(date) {
|
||||
const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
|
||||
const dayNum = (d.getUTCDay() + 6) % 7;
|
||||
d.setUTCDate(d.getUTCDate() - dayNum);
|
||||
return d;
|
||||
}
|
||||
|
||||
function addDays(date, days) {
|
||||
const d = new Date(date);
|
||||
d.setUTCDate(d.getUTCDate() + days);
|
||||
return d;
|
||||
}
|
||||
|
||||
function parseDate(s) {
|
||||
const [y, m, d] = s.split('-').map(Number);
|
||||
return new Date(Date.UTC(y, m - 1, d));
|
||||
}
|
||||
|
||||
// ---------- employees ----------
|
||||
|
||||
app.get('/api/employees', (req, res) => {
|
||||
res.json(db.prepare('SELECT * FROM employees ORDER BY name').all());
|
||||
});
|
||||
|
||||
app.post('/api/employees', (req, res) => {
|
||||
const { name, email, role, weekly_capacity_hours } = req.body;
|
||||
if (!name) return res.status(400).json({ error: 'name ist erforderlich' });
|
||||
const result = db
|
||||
.prepare(
|
||||
'INSERT INTO employees (name, email, role, weekly_capacity_hours) VALUES (?, ?, ?, ?)'
|
||||
)
|
||||
.run(name, email || null, role || null, weekly_capacity_hours ?? 40);
|
||||
res.status(201).json(db.prepare('SELECT * FROM employees WHERE id = ?').get(result.lastInsertRowid));
|
||||
});
|
||||
|
||||
app.put('/api/employees/:id', (req, res) => {
|
||||
const { name, email, role, weekly_capacity_hours } = req.body;
|
||||
const existing = db.prepare('SELECT * FROM employees WHERE id = ?').get(req.params.id);
|
||||
if (!existing) return res.status(404).json({ error: 'nicht gefunden' });
|
||||
db.prepare(
|
||||
'UPDATE employees SET name = ?, email = ?, role = ?, weekly_capacity_hours = ? WHERE id = ?'
|
||||
).run(
|
||||
name ?? existing.name,
|
||||
email ?? existing.email,
|
||||
role ?? existing.role,
|
||||
weekly_capacity_hours ?? existing.weekly_capacity_hours,
|
||||
req.params.id
|
||||
);
|
||||
res.json(db.prepare('SELECT * FROM employees WHERE id = ?').get(req.params.id));
|
||||
});
|
||||
|
||||
app.delete('/api/employees/:id', (req, res) => {
|
||||
db.prepare('DELETE FROM employees WHERE id = ?').run(req.params.id);
|
||||
res.status(204).end();
|
||||
});
|
||||
|
||||
// ---------- teams ----------
|
||||
|
||||
app.get('/api/teams', (req, res) => {
|
||||
const teams = db.prepare('SELECT * FROM teams ORDER BY name').all();
|
||||
const members = db
|
||||
.prepare(
|
||||
`SELECT tm.team_id, e.* FROM team_members tm
|
||||
JOIN employees e ON e.id = tm.employee_id`
|
||||
)
|
||||
.all();
|
||||
res.json(
|
||||
teams.map((t) => ({
|
||||
...t,
|
||||
members: members.filter((m) => m.team_id === t.id).map(({ team_id, ...rest }) => rest),
|
||||
}))
|
||||
);
|
||||
});
|
||||
|
||||
app.post('/api/teams', (req, res) => {
|
||||
const { name, description } = req.body;
|
||||
if (!name) return res.status(400).json({ error: 'name ist erforderlich' });
|
||||
const result = db
|
||||
.prepare('INSERT INTO teams (name, description) VALUES (?, ?)')
|
||||
.run(name, description || null);
|
||||
res.status(201).json({ ...db.prepare('SELECT * FROM teams WHERE id = ?').get(result.lastInsertRowid), members: [] });
|
||||
});
|
||||
|
||||
app.put('/api/teams/:id', (req, res) => {
|
||||
const { name, description } = req.body;
|
||||
const existing = db.prepare('SELECT * FROM teams WHERE id = ?').get(req.params.id);
|
||||
if (!existing) return res.status(404).json({ error: 'nicht gefunden' });
|
||||
db.prepare('UPDATE teams SET name = ?, description = ? WHERE id = ?').run(
|
||||
name ?? existing.name,
|
||||
description ?? existing.description,
|
||||
req.params.id
|
||||
);
|
||||
res.json(db.prepare('SELECT * FROM teams WHERE id = ?').get(req.params.id));
|
||||
});
|
||||
|
||||
app.delete('/api/teams/:id', (req, res) => {
|
||||
db.prepare('DELETE FROM teams WHERE id = ?').run(req.params.id);
|
||||
res.status(204).end();
|
||||
});
|
||||
|
||||
app.post('/api/teams/:id/members', (req, res) => {
|
||||
const { employee_id } = req.body;
|
||||
db.prepare('INSERT OR IGNORE INTO team_members (team_id, employee_id) VALUES (?, ?)').run(
|
||||
req.params.id,
|
||||
employee_id
|
||||
);
|
||||
res.status(201).end();
|
||||
});
|
||||
|
||||
app.delete('/api/teams/:id/members/:employeeId', (req, res) => {
|
||||
db.prepare('DELETE FROM team_members WHERE team_id = ? AND employee_id = ?').run(
|
||||
req.params.id,
|
||||
req.params.employeeId
|
||||
);
|
||||
res.status(204).end();
|
||||
});
|
||||
|
||||
// ---------- projects & tasks ----------
|
||||
|
||||
app.get('/api/projects', (req, res) => {
|
||||
const projects = db.prepare('SELECT * FROM projects ORDER BY start_date').all();
|
||||
const tasks = db.prepare('SELECT * FROM tasks ORDER BY start_date').all();
|
||||
const assignments = db
|
||||
.prepare(
|
||||
`SELECT a.*, e.name AS employee_name FROM assignments a
|
||||
JOIN employees e ON e.id = a.employee_id`
|
||||
)
|
||||
.all();
|
||||
const deps = db.prepare('SELECT * FROM task_dependencies').all();
|
||||
|
||||
const tasksByProject = projects.map((p) => ({
|
||||
...p,
|
||||
tasks: tasks
|
||||
.filter((t) => t.project_id === p.id)
|
||||
.map((t) => ({
|
||||
...t,
|
||||
assignments: assignments.filter((a) => a.task_id === t.id),
|
||||
depends_on: deps.filter((d) => d.task_id === t.id).map((d) => d.depends_on_task_id),
|
||||
})),
|
||||
}));
|
||||
res.json(tasksByProject);
|
||||
});
|
||||
|
||||
app.post('/api/projects', (req, res) => {
|
||||
const { name, description, start_date, end_date, color } = req.body;
|
||||
if (!name) return res.status(400).json({ error: 'name ist erforderlich' });
|
||||
const result = db
|
||||
.prepare(
|
||||
'INSERT INTO projects (name, description, start_date, end_date, color) VALUES (?, ?, ?, ?, ?)'
|
||||
)
|
||||
.run(name, description || null, start_date || null, end_date || null, color || '#4f46e5');
|
||||
res.status(201).json(db.prepare('SELECT * FROM projects WHERE id = ?').get(result.lastInsertRowid));
|
||||
});
|
||||
|
||||
app.put('/api/projects/:id', (req, res) => {
|
||||
const existing = db.prepare('SELECT * FROM projects WHERE id = ?').get(req.params.id);
|
||||
if (!existing) return res.status(404).json({ error: 'nicht gefunden' });
|
||||
const { name, description, start_date, end_date, color } = req.body;
|
||||
db.prepare(
|
||||
'UPDATE projects SET name = ?, description = ?, start_date = ?, end_date = ?, color = ? WHERE id = ?'
|
||||
).run(
|
||||
name ?? existing.name,
|
||||
description ?? existing.description,
|
||||
start_date ?? existing.start_date,
|
||||
end_date ?? existing.end_date,
|
||||
color ?? existing.color,
|
||||
req.params.id
|
||||
);
|
||||
res.json(db.prepare('SELECT * FROM projects WHERE id = ?').get(req.params.id));
|
||||
});
|
||||
|
||||
app.delete('/api/projects/:id', (req, res) => {
|
||||
db.prepare('DELETE FROM projects WHERE id = ?').run(req.params.id);
|
||||
res.status(204).end();
|
||||
});
|
||||
|
||||
app.post('/api/tasks', (req, res) => {
|
||||
const { project_id, name, start_date, end_date, estimated_hours, status } = req.body;
|
||||
if (!project_id || !name || !start_date || !end_date) {
|
||||
return res.status(400).json({ error: 'project_id, name, start_date, end_date sind erforderlich' });
|
||||
}
|
||||
const result = db
|
||||
.prepare(
|
||||
'INSERT INTO tasks (project_id, name, start_date, end_date, estimated_hours, status) VALUES (?, ?, ?, ?, ?, ?)'
|
||||
)
|
||||
.run(project_id, name, start_date, end_date, estimated_hours || 0, status || 'geplant');
|
||||
res.status(201).json(db.prepare('SELECT * FROM tasks WHERE id = ?').get(result.lastInsertRowid));
|
||||
});
|
||||
|
||||
app.put('/api/tasks/:id', (req, res) => {
|
||||
const existing = db.prepare('SELECT * FROM tasks WHERE id = ?').get(req.params.id);
|
||||
if (!existing) return res.status(404).json({ error: 'nicht gefunden' });
|
||||
const { name, start_date, end_date, estimated_hours, status } = req.body;
|
||||
db.prepare(
|
||||
'UPDATE tasks SET name = ?, start_date = ?, end_date = ?, estimated_hours = ?, status = ? WHERE id = ?'
|
||||
).run(
|
||||
name ?? existing.name,
|
||||
start_date ?? existing.start_date,
|
||||
end_date ?? existing.end_date,
|
||||
estimated_hours ?? existing.estimated_hours,
|
||||
status ?? existing.status,
|
||||
req.params.id
|
||||
);
|
||||
res.json(db.prepare('SELECT * FROM tasks WHERE id = ?').get(req.params.id));
|
||||
});
|
||||
|
||||
app.delete('/api/tasks/:id', (req, res) => {
|
||||
db.prepare('DELETE FROM tasks WHERE id = ?').run(req.params.id);
|
||||
res.status(204).end();
|
||||
});
|
||||
|
||||
app.post('/api/tasks/:id/dependencies', (req, res) => {
|
||||
const { depends_on_task_id } = req.body;
|
||||
db.prepare(
|
||||
'INSERT OR IGNORE INTO task_dependencies (task_id, depends_on_task_id) VALUES (?, ?)'
|
||||
).run(req.params.id, depends_on_task_id);
|
||||
res.status(201).end();
|
||||
});
|
||||
|
||||
app.delete('/api/tasks/:id/dependencies/:dependsOnId', (req, res) => {
|
||||
db.prepare(
|
||||
'DELETE FROM task_dependencies WHERE task_id = ? AND depends_on_task_id = ?'
|
||||
).run(req.params.id, req.params.dependsOnId);
|
||||
res.status(204).end();
|
||||
});
|
||||
|
||||
// ---------- assignments ----------
|
||||
|
||||
app.post('/api/assignments', (req, res) => {
|
||||
const { task_id, employee_id, allocated_hours_per_week } = req.body;
|
||||
if (!task_id || !employee_id) {
|
||||
return res.status(400).json({ error: 'task_id und employee_id sind erforderlich' });
|
||||
}
|
||||
const result = db
|
||||
.prepare(
|
||||
`INSERT INTO assignments (task_id, employee_id, allocated_hours_per_week) VALUES (?, ?, ?)
|
||||
ON CONFLICT (task_id, employee_id) DO UPDATE SET allocated_hours_per_week = excluded.allocated_hours_per_week`
|
||||
)
|
||||
.run(task_id, employee_id, allocated_hours_per_week || 0);
|
||||
const id = result.lastInsertRowid || db.prepare(
|
||||
'SELECT id FROM assignments WHERE task_id = ? AND employee_id = ?'
|
||||
).get(task_id, employee_id).id;
|
||||
res.status(201).json(db.prepare('SELECT * FROM assignments WHERE id = ?').get(id));
|
||||
});
|
||||
|
||||
app.put('/api/assignments/:id', (req, res) => {
|
||||
const { allocated_hours_per_week } = req.body;
|
||||
db.prepare('UPDATE assignments SET allocated_hours_per_week = ? WHERE id = ?').run(
|
||||
allocated_hours_per_week,
|
||||
req.params.id
|
||||
);
|
||||
res.json(db.prepare('SELECT * FROM assignments WHERE id = ?').get(req.params.id));
|
||||
});
|
||||
|
||||
app.delete('/api/assignments/:id', (req, res) => {
|
||||
db.prepare('DELETE FROM assignments WHERE id = ?').run(req.params.id);
|
||||
res.status(204).end();
|
||||
});
|
||||
|
||||
// ---------- utilization heatmap ----------
|
||||
|
||||
app.get('/api/utilization', (req, res) => {
|
||||
const from = req.query.from ? parseDate(req.query.from) : mondayOf(new Date());
|
||||
const weekCount = Math.min(Number(req.query.weeks) || 12, 52);
|
||||
|
||||
const weeks = [];
|
||||
for (let i = 0; i < weekCount; i++) {
|
||||
const weekStart = addDays(mondayOf(from), i * 7);
|
||||
weeks.push({ key: isoWeekKey(weekStart), start: weekStart });
|
||||
}
|
||||
|
||||
const employees = db.prepare('SELECT * FROM employees ORDER BY name').all();
|
||||
const rows = db
|
||||
.prepare(
|
||||
`SELECT a.employee_id, a.allocated_hours_per_week, t.start_date, t.end_date
|
||||
FROM assignments a JOIN tasks t ON t.id = a.task_id`
|
||||
)
|
||||
.all();
|
||||
|
||||
const data = {};
|
||||
for (const e of employees) data[e.id] = {};
|
||||
|
||||
for (const row of rows) {
|
||||
const taskStart = parseDate(row.start_date);
|
||||
const taskEnd = parseDate(row.end_date);
|
||||
for (const week of weeks) {
|
||||
const weekEnd = addDays(week.start, 6);
|
||||
const overlapStart = taskStart > week.start ? taskStart : week.start;
|
||||
const overlapEnd = taskEnd < weekEnd ? taskEnd : weekEnd;
|
||||
const overlapDays = Math.max(0, Math.round((overlapEnd - overlapStart) / 86400000) + 1);
|
||||
if (overlapDays <= 0) continue;
|
||||
const hours = row.allocated_hours_per_week * (overlapDays / 7);
|
||||
data[row.employee_id][week.key] = (data[row.employee_id][week.key] || 0) + hours;
|
||||
}
|
||||
}
|
||||
|
||||
res.json({
|
||||
weeks: weeks.map((w) => w.key),
|
||||
employees: employees.map((e) => ({
|
||||
id: e.id,
|
||||
name: e.name,
|
||||
weekly_capacity_hours: e.weekly_capacity_hours,
|
||||
hours: weeks.map((w) => Math.round((data[e.id][w.key] || 0) * 10) / 10),
|
||||
})),
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/health', (req, res) => res.json({ ok: true }));
|
||||
|
||||
// serve built frontend in production
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const clientDist = path.join(__dirname, '..', 'client', 'dist');
|
||||
if (fs.existsSync(clientDist)) {
|
||||
app.use(express.static(clientDist));
|
||||
app.get(/^(?!\/api|\/health).*/, (req, res) => {
|
||||
res.sendFile(path.join(clientDist, 'index.html'));
|
||||
});
|
||||
}
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Ressourcenplanung-API läuft auf http://localhost:${PORT}`);
|
||||
});
|
||||
Generated
+1279
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "ressourcenplanung-server",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"type": "commonjs",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"dev": "node index.js",
|
||||
"start": "node index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"better-sqlite3": "^11.3.0",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.19.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
PRAGMA foreign_keys = ON;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS employees (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
email TEXT,
|
||||
role TEXT,
|
||||
weekly_capacity_hours REAL NOT NULL DEFAULT 40
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS teams (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS team_members (
|
||||
team_id INTEGER NOT NULL REFERENCES teams(id) ON DELETE CASCADE,
|
||||
employee_id INTEGER NOT NULL REFERENCES employees(id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (team_id, employee_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS projects (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
start_date TEXT,
|
||||
end_date TEXT,
|
||||
color TEXT DEFAULT '#4f46e5'
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tasks (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
project_id INTEGER NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
start_date TEXT NOT NULL,
|
||||
end_date TEXT NOT NULL,
|
||||
estimated_hours REAL NOT NULL DEFAULT 0,
|
||||
status TEXT NOT NULL DEFAULT 'geplant'
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS task_dependencies (
|
||||
task_id INTEGER NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
||||
depends_on_task_id INTEGER NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (task_id, depends_on_task_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS assignments (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
task_id INTEGER NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
||||
employee_id INTEGER NOT NULL REFERENCES employees(id) ON DELETE CASCADE,
|
||||
allocated_hours_per_week REAL NOT NULL DEFAULT 0,
|
||||
UNIQUE (task_id, employee_id)
|
||||
);
|
||||
Reference in New Issue
Block a user