const express = require('express'); const router = express.Router(); const { getDb, logAudit } = require('../db'); const emailService = require('../email'); router.get('/', (req, res) => { const db = getDb(); const invoices = db.prepare(` SELECT i.*, c.name as client_name FROM invoices i JOIN clients c ON c.id = i.client_id ORDER BY i.created_at DESC `).all(); const totalOutstanding = invoices.filter(i => i.status === 'sent' || i.status === 'overdue').reduce((s, i) => s + i.total, 0); const totalPaid = invoices.filter(i => i.status === 'paid').reduce((s, i) => s + i.total, 0); res.render('invoice-list', { invoices, totalOutstanding, totalPaid }); }); router.get('/new', (req, res) => { const db = getDb(); const clients = db.prepare('SELECT id, name FROM clients ORDER BY name').all(); res.render('invoice-form', { invoice: null, clients, selectedClient: null, timeEntries: [], error: null }); }); router.get('/new/:clientId', (req, res) => { const db = getDb(); const clients = db.prepare('SELECT id, name FROM clients ORDER BY name').all(); const client = db.prepare('SELECT * FROM clients WHERE id = ?').get(req.params.clientId); if (!client) return res.redirect('/invoices/new'); const timeEntries = db.prepare(` SELECT t.*, e.title as engagement_title FROM time_entries t LEFT JOIN engagements e ON e.id = t.engagement_id WHERE t.client_id = ? AND t.billable = 1 ORDER BY t.date DESC `).all(req.params.clientId); res.render('invoice-form', { invoice: null, clients, selectedClient: parseInt(req.params.clientId), timeEntries, error: null }); }); router.post('/new', (req, res) => { const db = getDb(); const { client_id, date, due_date, notes, items } = req.body; if (!client_id) return res.redirect('/invoices'); const count = db.prepare('SELECT COUNT(*) as cnt FROM invoices').get().cnt + 1; const number = `FACT-${new Date().getFullYear()}-${String(count).padStart(4, '0')}`; const subtotal = parseFloat(req.body.subtotal) || 0; const tax = parseFloat(req.body.tax) || 0; const total = subtotal + tax; const result = db.prepare('INSERT INTO invoices (client_id, number, status, date, due_date, subtotal, tax, total, notes) VALUES (?,?,?,?,?,?,?,?,?)') .run(client_id, number, 'draft', date || new Date().toISOString().split('T')[0], due_date || '', subtotal, tax, total, notes || ''); const invoiceId = result.lastInsertRowid; if (items && Array.isArray(items)) { const insert = db.prepare('INSERT INTO invoice_items (invoice_id, description, quantity, unit_price, total) VALUES (?,?,?,?,?)'); for (const item of items) { if (item.description) { const itemTotal = (parseFloat(item.quantity) || 1) * (parseFloat(item.unit_price) || 0); insert.run(invoiceId, item.description, parseFloat(item.quantity) || 1, parseFloat(item.unit_price) || 0, itemTotal); } } } logAudit(req.session.userId, req.session.username, 'invoice_created', 'invoice', invoiceId, `Factuur ${number} aangemaakt`); res.redirect('/invoices/' + invoiceId); }); router.get('/:id', (req, res) => { const db = getDb(); const invoice = db.prepare('SELECT i.*, c.name as client_name, c.contact_name, c.contact_email, c.contact_phone, c.website FROM invoices i JOIN clients c ON c.id = i.client_id WHERE i.id = ?').get(req.params.id); if (!invoice) return res.redirect('/invoices'); const items = db.prepare('SELECT * FROM invoice_items WHERE invoice_id = ?').all(req.params.id); res.render('invoice-detail', { invoice, items, sent: req.query.sent === '1', error: req.query.error || null }); }); router.post('/:id/status', (req, res) => { const db = getDb(); db.prepare("UPDATE invoices SET status=?, updated_at=datetime('now') WHERE id=?").run(req.body.status, req.params.id); res.redirect('/invoices/' + req.params.id); }); router.post('/:id/send-email', async (req, res) => { const db = getDb(); const invoice = db.prepare('SELECT i.*, c.name as client_name, c.contact_email FROM invoices i JOIN clients c ON c.id = i.client_id WHERE i.id = ?').get(req.params.id); if (!invoice) return res.redirect('/invoices'); const account = db.prepare('SELECT * FROM email_accounts WHERE user_id = ? AND active = 1 LIMIT 1').get(req.session.userId); if (!account) return res.redirect('/invoices/' + req.params.id + '?error=noemail'); const items = db.prepare('SELECT * FROM invoice_items WHERE invoice_id = ?').all(req.params.id); let text = `Factuur ${invoice.number}\n\nCliënt: ${invoice.client_name}\nDatum: ${invoice.date}\nVervaldatum: ${invoice.due_date || '-'}\n\n`; items.forEach(item => { text += `${item.description} x${item.quantity} €${(item.unit_price||0).toFixed(2)} €${(item.total||0).toFixed(2)}\n`; }); text += `\nSubtotaal: €${(invoice.subtotal||0).toFixed(2)}\nBTW: €${(invoice.tax||0).toFixed(2)}\nTotaal: €${(invoice.total||0).toFixed(2)}\n\nMek-Tech Consulting`; const to = invoice.contact_email || (await emailService.getAccount(account.id)).email; try { await emailService.sendEmail(account.id, to, `Factuur ${invoice.number}`, text, text.replace(/\n/g, '
')); logAudit(req.session.userId, req.session.username, 'invoice_emailed', 'invoice', invoice.id, `Factuur ${invoice.number} gemaild naar ${to}`); db.prepare("UPDATE invoices SET status='sent', updated_at=datetime('now') WHERE id=?").run(req.params.id); res.redirect('/invoices/' + req.params.id + '?sent=1'); } catch (e) { res.redirect('/invoices/' + req.params.id + '?error=' + encodeURIComponent(e.message)); } }); router.post('/:id/delete', (req, res) => { const db = getDb(); db.prepare('DELETE FROM invoices WHERE id = ?').run(req.params.id); res.redirect('/invoices'); }); module.exports = router;