Dieser Commit ist enthalten in:
Claude Project Manager
2025-12-28 21:36:45 +00:00
Commit ab1e5be9a9
146 geänderte Dateien mit 65525 neuen und 0 gelöschten Zeilen

299
backend/routes/proposals.js Normale Datei
Datei anzeigen

@ -0,0 +1,299 @@
/**
* TASKMATE - Proposals Routes
* ===========================
* API-Endpunkte fuer Vorschlaege und Genehmigungen
*/
const express = require('express');
const router = express.Router();
const { getDb } = require('../database');
const { authenticateToken, requireRegularUser, checkPermission } = require('../middleware/auth');
const logger = require('../utils/logger');
const notificationService = require('../services/notificationService');
// Alle Proposals-Routes erfordern Authentifizierung und regulaeren User (kein Admin)
router.use(authenticateToken);
router.use(requireRegularUser);
/**
* GET /api/proposals - Alle Genehmigungen abrufen (projektbezogen)
* Query-Parameter: sort = 'date' | 'alpha', archived = '0' | '1', projectId = number
*/
router.get('/', (req, res) => {
try {
const db = getDb();
const sort = req.query.sort || 'date';
const archived = req.query.archived === '1' ? 1 : 0;
const projectId = req.query.projectId ? parseInt(req.query.projectId) : null;
let orderBy;
switch (sort) {
case 'alpha':
orderBy = 'p.title ASC';
break;
case 'date':
default:
orderBy = 'p.created_at DESC';
break;
}
// Nur Genehmigungen des aktuellen Projekts laden
let whereClause = 'p.archived = ?';
const params = [archived];
if (projectId) {
whereClause += ' AND p.project_id = ?';
params.push(projectId);
}
const proposals = db.prepare(`
SELECT
p.*,
u.display_name as created_by_name,
u.color as created_by_color,
ua.display_name as approved_by_name,
t.title as task_title,
t.id as linked_task_id
FROM proposals p
LEFT JOIN users u ON p.created_by = u.id
LEFT JOIN users ua ON p.approved_by = ua.id
LEFT JOIN tasks t ON p.task_id = t.id
WHERE ${whereClause}
ORDER BY ${orderBy}
`).all(...params);
res.json(proposals);
} catch (error) {
logger.error('Fehler beim Abrufen der Genehmigungen:', error);
res.status(500).json({ error: 'Fehler beim Abrufen der Genehmigungen' });
}
});
/**
* POST /api/proposals - Neue Genehmigung erstellen (projektbezogen)
*/
router.post('/', (req, res) => {
try {
const { title, description, taskId, projectId } = req.body;
if (!title || title.trim().length === 0) {
return res.status(400).json({ error: 'Titel erforderlich' });
}
if (!projectId) {
return res.status(400).json({ error: 'Projekt erforderlich' });
}
const db = getDb();
const result = db.prepare(`
INSERT INTO proposals (title, description, created_by, task_id, project_id)
VALUES (?, ?, ?, ?, ?)
`).run(title.trim(), description?.trim() || null, req.user.id, taskId || null, projectId);
const proposal = db.prepare(`
SELECT
p.*,
u.display_name as created_by_name,
u.color as created_by_color,
t.title as task_title,
t.id as linked_task_id
FROM proposals p
LEFT JOIN users u ON p.created_by = u.id
LEFT JOIN tasks t ON p.task_id = t.id
WHERE p.id = ?
`).get(result.lastInsertRowid);
logger.info(`Benutzer ${req.user.username} hat Genehmigung "${title}" erstellt`);
// Benachrichtigungen an User mit 'genehmigung'-Berechtigung senden (persistent)
const io = req.app.get('io');
const usersWithPermission = db.prepare(`
SELECT id FROM users
WHERE role = 'user'
AND permissions LIKE '%genehmigung%'
AND id != ?
`).all(req.user.id);
usersWithPermission.forEach(user => {
notificationService.create(user.id, 'approval:pending', {
proposalId: proposal.id,
proposalTitle: title.trim(),
projectId: projectId,
actorId: req.user.id,
actorName: req.user.display_name || req.user.username
}, io, true); // persistent = true
});
res.status(201).json(proposal);
} catch (error) {
logger.error('Fehler beim Erstellen der Genehmigung:', error);
res.status(500).json({ error: 'Fehler beim Erstellen der Genehmigung' });
}
});
/**
* PUT /api/proposals/:id/approve - Genehmigung erteilen (nur mit Berechtigung)
*/
router.put('/:id/approve', checkPermission('genehmigung'), (req, res) => {
try {
const proposalId = parseInt(req.params.id);
const { approved } = req.body;
const db = getDb();
// Genehmigung pruefen
const proposal = db.prepare('SELECT * FROM proposals WHERE id = ?').get(proposalId);
if (!proposal) {
return res.status(404).json({ error: 'Genehmigung nicht gefunden' });
}
if (approved) {
// Genehmigen
db.prepare(`
UPDATE proposals
SET approved = 1, approved_by = ?, approved_at = CURRENT_TIMESTAMP
WHERE id = ?
`).run(req.user.id, proposalId);
logger.info(`Benutzer ${req.user.username} hat Genehmigung ${proposalId} erteilt`);
} else {
// Genehmigung zurueckziehen
db.prepare(`
UPDATE proposals
SET approved = 0, approved_by = NULL, approved_at = NULL
WHERE id = ?
`).run(proposalId);
logger.info(`Benutzer ${req.user.username} hat Genehmigung ${proposalId} zurueckgezogen`);
}
// Aktualisierte Genehmigung zurueckgeben
const updatedProposal = db.prepare(`
SELECT
p.*,
u.display_name as created_by_name,
u.color as created_by_color,
ua.display_name as approved_by_name,
t.title as task_title,
t.id as linked_task_id
FROM proposals p
LEFT JOIN users u ON p.created_by = u.id
LEFT JOIN users ua ON p.approved_by = ua.id
LEFT JOIN tasks t ON p.task_id = t.id
WHERE p.id = ?
`).get(proposalId);
// Benachrichtigungen senden
const io = req.app.get('io');
if (approved) {
// Ersteller benachrichtigen dass genehmigt wurde
if (proposal.created_by !== req.user.id) {
notificationService.create(proposal.created_by, 'approval:granted', {
proposalId: proposalId,
proposalTitle: proposal.title,
projectId: proposal.project_id,
actorId: req.user.id,
actorName: req.user.display_name || req.user.username
}, io);
}
// Persistente Benachrichtigungen auflösen
notificationService.resolvePersistent(proposalId);
// Aktualisierte Zählung an alle User mit Berechtigung senden
const usersWithPermission = db.prepare(`
SELECT id FROM users
WHERE role = 'user'
AND permissions LIKE '%genehmigung%'
`).all();
usersWithPermission.forEach(user => {
const count = notificationService.getUnreadCount(user.id);
io.to(`user:${user.id}`).emit('notification:count', { count });
});
}
res.json(updatedProposal);
} catch (error) {
logger.error('Fehler beim Genehmigen:', error);
res.status(500).json({ error: 'Fehler beim Genehmigen' });
}
});
/**
* PUT /api/proposals/:id/archive - Genehmigung archivieren/wiederherstellen (nur mit Berechtigung)
*/
router.put('/:id/archive', checkPermission('genehmigung'), (req, res) => {
try {
const proposalId = parseInt(req.params.id);
const { archived } = req.body;
const db = getDb();
// Genehmigung pruefen
const proposal = db.prepare('SELECT * FROM proposals WHERE id = ?').get(proposalId);
if (!proposal) {
return res.status(404).json({ error: 'Genehmigung nicht gefunden' });
}
db.prepare(`
UPDATE proposals
SET archived = ?
WHERE id = ?
`).run(archived ? 1 : 0, proposalId);
logger.info(`Benutzer ${req.user.username} hat Genehmigung ${proposalId} ${archived ? 'archiviert' : 'wiederhergestellt'}`);
// Aktualisierte Genehmigung zurueckgeben
const updatedProposal = db.prepare(`
SELECT
p.*,
u.display_name as created_by_name,
u.color as created_by_color,
ua.display_name as approved_by_name,
t.title as task_title,
t.id as linked_task_id
FROM proposals p
LEFT JOIN users u ON p.created_by = u.id
LEFT JOIN users ua ON p.approved_by = ua.id
LEFT JOIN tasks t ON p.task_id = t.id
WHERE p.id = ?
`).get(proposalId);
res.json(updatedProposal);
} catch (error) {
logger.error('Fehler beim Archivieren:', error);
res.status(500).json({ error: 'Fehler beim Archivieren' });
}
});
/**
* DELETE /api/proposals/:id - Eigene Genehmigung loeschen
*/
router.delete('/:id', (req, res) => {
try {
const proposalId = parseInt(req.params.id);
const db = getDb();
// Genehmigung pruefen
const proposal = db.prepare('SELECT * FROM proposals WHERE id = ?').get(proposalId);
if (!proposal) {
return res.status(404).json({ error: 'Genehmigung nicht gefunden' });
}
// Nur eigene Genehmigungen loeschen (oder mit genehmigung-Berechtigung)
const permissions = req.user.permissions || [];
if (proposal.created_by !== req.user.id && !permissions.includes('genehmigung')) {
return res.status(403).json({ error: 'Nur eigene Genehmigungen koennen geloescht werden' });
}
db.prepare('DELETE FROM proposals WHERE id = ?').run(proposalId);
logger.info(`Benutzer ${req.user.username} hat Genehmigung ${proposalId} geloescht`);
res.json({ success: true });
} catch (error) {
logger.error('Fehler beim Loeschen der Genehmigung:', error);
res.status(500).json({ error: 'Fehler beim Loeschen der Genehmigung' });
}
});
module.exports = router;