/** * routes/invoices.mjs — Invoice endpoints (revenue entries as invoices) */ function extractCustomer(description) { if (!description) return 'Okänd kund'; const parts = description.split(/[|]/); const raw = parts[0].trim(); return raw.split(' ').map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join(' '); } export default function registerInvoices(app, pool) { // GET /api/ledger/invoices — hämta verifikat med intäktsposter som "fakturor" app.get('/api/ledger/invoices', async (req, res) => { const tenant_id = req.auth?.tenant_id || req.headers['x-tenant-id'] || 'wavult-group'; const fiscal_year = req.query.fiscal_year ? parseInt(req.query.fiscal_year) : null; try { const conditions = ['e.tenant_id=$1', "e.status='posted'"]; const params = [tenant_id]; if (fiscal_year) { params.push(fiscal_year); conditions.push(`e.fiscal_year=$${params.length}`); } const { rows: entries } = await pool.query(` SELECT DISTINCT ON (e.id) e.id, e.entry_number, e.fiscal_year, e.period, e.entry_date, e.description, e.reference, e.status, e.metadata FROM ledger_journal_entries e JOIN ledger_journal_lines l ON l.entry_id = e.id AND l.tenant_id = e.tenant_id WHERE ${conditions.join(' AND ')} AND l.account_number LIKE '3%' AND l.credit > 0 ORDER BY e.id, e.entry_date DESC `, params); const entryIds = entries.map(e => e.id); let lines = []; if (entryIds.length > 0) { const placeholders = entryIds.map((_,i) => `$${i+2}`).join(','); const { rows } = await pool.query( `SELECT * FROM ledger_journal_lines WHERE tenant_id=$1 AND entry_id IN (${placeholders}) ORDER BY line_number`, [tenant_id, ...entryIds] ); lines = rows; } const invoices = entries.map(e => { const entryLines = lines.filter(l => l.entry_id === e.id); const revenueLines = entryLines.filter(l => (l.account_number||'').startsWith('3') && parseFloat(l.credit||0) > 0); const vatLines = entryLines.filter(l => ['2611','2614','2615','2616'].includes(l.account_number) && parseFloat(l.credit||0) > 0); const receivableLines = entryLines.filter(l => l.account_number === '1510' && parseFloat(l.debit||0) > 0); const cashLines = entryLines.filter(l => /^19[0-9]{2}$/.test(l.account_number||'') && parseFloat(l.debit||0) > 0); const amount = revenueLines.reduce((s,l) => s + parseFloat(l.credit||0), 0); const vatAmount = vatLines.reduce((s,l) => s + parseFloat(l.credit||0), 0); const total = amount + vatAmount; const isPaid = cashLines.length > 0; const isSent = receivableLines.length > 0; const date = e.entry_date ? new Date(e.entry_date).toISOString().split('T')[0] : ''; return { id: e.id, entry_number: e.entry_number, period: e.period, date, description: e.description, reference: e.reference, customer: extractCustomer(e.description), amount, vatAmount, total, status: isPaid ? 'paid' : isSent ? 'sent' : 'posted', lines: entryLines, revenueAccounts: revenueLines.map(l => l.account_number).join(', '), }; }); invoices.sort((a,b) => new Date(b.date) - new Date(a.date)); res.json({ ok: true, invoices, count: invoices.length }); } catch (e) { res.status(500).json({ ok: false, error: e.message }); } }); }