169 lines
8.4 KiB
JavaScript
169 lines
8.4 KiB
JavaScript
|
|
const express = require('express');
|
||
|
|
const router = express.Router();
|
||
|
|
const path = require('path');
|
||
|
|
const fs = require('fs');
|
||
|
|
const multer = require('multer');
|
||
|
|
const { getDb, logAudit } = require('../db');
|
||
|
|
|
||
|
|
const UPLOAD_DIR = path.join(__dirname, '..', 'uploads');
|
||
|
|
if (!fs.existsSync(UPLOAD_DIR)) fs.mkdirSync(UPLOAD_DIR, { recursive: true });
|
||
|
|
|
||
|
|
const storage = multer.diskStorage({
|
||
|
|
destination: (req, file, cb) => cb(null, UPLOAD_DIR),
|
||
|
|
filename: (req, file, cb) => cb(null, Date.now() + '-' + Math.round(Math.random() * 1e9) + path.extname(file.originalname))
|
||
|
|
});
|
||
|
|
const upload = multer({ storage, limits: { fileSize: 100 * 1024 * 1024 } });
|
||
|
|
|
||
|
|
router.get('/', (req, res) => {
|
||
|
|
const db = getDb();
|
||
|
|
const { status, search } = req.query;
|
||
|
|
let sql = `SELECT c.*,
|
||
|
|
(SELECT COUNT(*) FROM engagements e WHERE e.client_id = c.id) as engagement_count,
|
||
|
|
(SELECT MAX(a.created_at) FROM assessments a WHERE a.client_id = c.id) as last_assessment
|
||
|
|
FROM clients c WHERE 1=1`;
|
||
|
|
const params = [];
|
||
|
|
if (status) { sql += ' AND c.status = ?'; params.push(status); }
|
||
|
|
if (search) { sql += ' AND (c.name LIKE ? OR c.contact_name LIKE ? OR c.industry LIKE ?)'; params.push(`%${search}%`, `%${search}%`, `%${search}%`); }
|
||
|
|
sql += ' ORDER BY c.updated_at DESC';
|
||
|
|
const clients = db.prepare(sql).all(...params);
|
||
|
|
res.render('client-list', { clients, filters: req.query });
|
||
|
|
});
|
||
|
|
|
||
|
|
router.get('/new', (req, res) => {
|
||
|
|
res.render('client-form', { client: null, error: null });
|
||
|
|
});
|
||
|
|
|
||
|
|
router.post('/new', (req, res) => {
|
||
|
|
const db = getDb();
|
||
|
|
const { name, industry, website, contact_name, contact_email, contact_phone, status, source, notes } = req.body;
|
||
|
|
if (!name || !name.trim()) {
|
||
|
|
return res.render('client-form', { client: null, error: 'Bedrijfsnaam is verplicht' });
|
||
|
|
}
|
||
|
|
const result = db.prepare(`INSERT INTO clients (name, industry, website, contact_name, contact_email, contact_phone, status, source, notes)
|
||
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(name, industry, website, contact_name, contact_email, contact_phone, status || 'lead', source, notes);
|
||
|
|
logAudit(req.session.userId, req.session.username, 'client_created', 'client', result.lastInsertRowid, `Cliënt ${name} aangemaakt`);
|
||
|
|
res.redirect('/clients');
|
||
|
|
});
|
||
|
|
|
||
|
|
router.get('/:id', (req, res) => {
|
||
|
|
const db = getDb();
|
||
|
|
const client = db.prepare('SELECT * FROM clients WHERE id = ?').get(req.params.id);
|
||
|
|
if (!client) return res.redirect('/clients');
|
||
|
|
const engagements = db.prepare('SELECT * FROM engagements WHERE client_id = ? ORDER BY created_at DESC').all(req.params.id);
|
||
|
|
const assessments = db.prepare('SELECT * FROM assessments WHERE client_id = ? ORDER BY created_at DESC').all(req.params.id);
|
||
|
|
const advice = db.prepare('SELECT * FROM advice_log WHERE client_id = ? AND dismissed = 0 ORDER BY created_at DESC').all(req.params.id);
|
||
|
|
const diagrams = db.prepare('SELECT * FROM data_diagrams WHERE client_id = ? ORDER BY updated_at DESC').all(req.params.id);
|
||
|
|
const notes = db.prepare('SELECT * FROM client_notes WHERE client_id = ? ORDER BY created_at DESC').all(req.params.id);
|
||
|
|
const files = db.prepare('SELECT * FROM client_files WHERE client_id = ? ORDER BY created_at DESC').all(req.params.id);
|
||
|
|
res.render('client-detail', { client, engagements, assessments, advice, diagrams, notes, files });
|
||
|
|
});
|
||
|
|
|
||
|
|
router.get('/:id/edit', (req, res) => {
|
||
|
|
const db = getDb();
|
||
|
|
const client = db.prepare('SELECT * FROM clients WHERE id = ?').get(req.params.id);
|
||
|
|
if (!client) return res.redirect('/clients');
|
||
|
|
res.render('client-form', { client, error: null });
|
||
|
|
});
|
||
|
|
|
||
|
|
router.post('/:id/edit', (req, res) => {
|
||
|
|
const db = getDb();
|
||
|
|
const { name, industry, website, contact_name, contact_email, contact_phone, status, source, notes } = req.body;
|
||
|
|
if (!name || !name.trim()) {
|
||
|
|
const client = db.prepare('SELECT * FROM clients WHERE id = ?').get(req.params.id);
|
||
|
|
return res.render('client-form', { client, error: 'Bedrijfsnaam is verplicht' });
|
||
|
|
}
|
||
|
|
db.prepare(`UPDATE clients SET name=?, industry=?, website=?, contact_name=?, contact_email=?, contact_phone=?, status=?, source=?, notes=?, updated_at=datetime('now') WHERE id=?`)
|
||
|
|
.run(name, industry, website, contact_name, contact_email, contact_phone, status, source, notes, req.params.id);
|
||
|
|
logAudit(req.session.userId, req.session.username, 'client_updated', 'client', req.params.id, `Cliënt ${name} bijgewerkt`);
|
||
|
|
res.redirect('/clients/' + req.params.id);
|
||
|
|
});
|
||
|
|
|
||
|
|
router.post('/:id/delete', (req, res) => {
|
||
|
|
const db = getDb();
|
||
|
|
db.prepare('DELETE FROM clients WHERE id = ?').run(req.params.id);
|
||
|
|
res.redirect('/clients');
|
||
|
|
});
|
||
|
|
|
||
|
|
router.post('/:id/advice/:adviceId/dismiss', (req, res) => {
|
||
|
|
const db = getDb();
|
||
|
|
db.prepare("UPDATE advice_log SET dismissed = 1 WHERE id = ? AND client_id = ?").run(req.params.adviceId, req.params.id);
|
||
|
|
res.redirect('/clients/' + req.params.id);
|
||
|
|
});
|
||
|
|
|
||
|
|
router.post('/:id/notes', (req, res) => {
|
||
|
|
const db = getDb();
|
||
|
|
const { content } = req.body;
|
||
|
|
if (content && content.trim()) {
|
||
|
|
db.prepare('INSERT INTO client_notes (client_id, content, created_by) VALUES (?, ?, ?)')
|
||
|
|
.run(req.params.id, content.trim(), req.session.username || '');
|
||
|
|
}
|
||
|
|
res.redirect('/clients/' + req.params.id + '#notes');
|
||
|
|
});
|
||
|
|
|
||
|
|
router.post('/:id/notes/:noteId/delete', (req, res) => {
|
||
|
|
const db = getDb();
|
||
|
|
db.prepare('DELETE FROM client_notes WHERE id = ? AND client_id = ?').run(req.params.noteId, req.params.id);
|
||
|
|
res.redirect('/clients/' + req.params.id + '#notes');
|
||
|
|
});
|
||
|
|
|
||
|
|
router.post('/:id/upload', upload.single('file'), (req, res) => {
|
||
|
|
if (!req.file) return res.redirect('/clients/' + req.params.id + '#files');
|
||
|
|
const db = getDb();
|
||
|
|
db.prepare('INSERT INTO client_files (client_id, original_name, stored_name, mime_type, file_size) VALUES (?, ?, ?, ?, ?)')
|
||
|
|
.run(req.params.id, req.file.originalname, req.file.filename, req.file.mimetype, req.file.size);
|
||
|
|
res.redirect('/clients/' + req.params.id + '#files');
|
||
|
|
});
|
||
|
|
|
||
|
|
router.get('/:id/files/:fileId/view', (req, res) => {
|
||
|
|
const db = getDb();
|
||
|
|
const file = db.prepare('SELECT * FROM client_files WHERE id = ? AND client_id = ?').get(req.params.fileId, req.params.id);
|
||
|
|
if (!file) return res.status(404).send('Bestand niet gevonden');
|
||
|
|
const filePath = path.join(UPLOAD_DIR, file.stored_name);
|
||
|
|
if (!fs.existsSync(filePath)) return res.status(404).send('Bestand niet gevonden op schijf');
|
||
|
|
if (file.mime_type === 'application/pdf') {
|
||
|
|
res.render('pdf-viewer', { file, clientId: req.params.id });
|
||
|
|
} else {
|
||
|
|
res.download(filePath, file.original_name);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
router.get('/:id/files/:fileId/download', (req, res) => {
|
||
|
|
const db = getDb();
|
||
|
|
const file = db.prepare('SELECT * FROM client_files WHERE id = ? AND client_id = ?').get(req.params.fileId, req.params.id);
|
||
|
|
if (!file) return res.status(404).send('Bestand niet gevonden');
|
||
|
|
const filePath = path.join(UPLOAD_DIR, file.stored_name);
|
||
|
|
if (!fs.existsSync(filePath)) return res.status(404).send('Bestand niet gevonden op schijf');
|
||
|
|
res.download(filePath, file.original_name);
|
||
|
|
});
|
||
|
|
|
||
|
|
router.post('/:id/files/:fileId/delete', (req, res) => {
|
||
|
|
const db = getDb();
|
||
|
|
const file = db.prepare('SELECT * FROM client_files WHERE id = ? AND client_id = ?').get(req.params.fileId, req.params.id);
|
||
|
|
if (file) {
|
||
|
|
const filePath = path.join(UPLOAD_DIR, file.stored_name);
|
||
|
|
if (fs.existsSync(filePath)) fs.unlinkSync(filePath);
|
||
|
|
db.prepare('DELETE FROM client_files WHERE id = ?').run(req.params.fileId);
|
||
|
|
}
|
||
|
|
res.redirect('/clients/' + req.params.id + '#files');
|
||
|
|
});
|
||
|
|
|
||
|
|
router.post('/:id/email/send', upload.array('attachments', 5), async (req, res) => {
|
||
|
|
const db = getDb();
|
||
|
|
const client = db.prepare('SELECT * FROM clients WHERE id = ?').get(req.params.id);
|
||
|
|
if (!client) return res.status(404).json({ error: 'Client not found' });
|
||
|
|
const { to, cc, bcc, subject, text } = req.body;
|
||
|
|
if (!to) return res.status(400).json({ error: 'Ontvanger is verplicht' });
|
||
|
|
const emailService = require('../email');
|
||
|
|
const account = db.prepare('SELECT * FROM email_accounts WHERE user_id = ? AND active = 1 LIMIT 1').get(req.session.userId);
|
||
|
|
if (!account) return res.status(400).json({ error: 'Geen email account geconfigureerd. Ga naar /email/settings' });
|
||
|
|
try {
|
||
|
|
await emailService.sendEmail(account.id, to, subject || `Mek-Tech — ${client.name}`, text || '', '', cc, bcc);
|
||
|
|
logAudit(req.session.userId, req.session.username, 'email_sent_to_client', 'client', client.id, `Email naar ${to}: ${subject}`);
|
||
|
|
res.redirect('/clients/' + req.params.id + '?email=sent');
|
||
|
|
} catch (e) {
|
||
|
|
res.redirect('/clients/' + req.params.id + '?email=error&msg=' + encodeURIComponent(e.message));
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
module.exports = router;
|