Files
boc/aamos-ledger-refactor/routes/invoices.mjs
T
Bernt bae705aa97 ARCHITECTURE: NFC roadmap, edge AI, audit logging
- Add NFC ePassport roadmap (ICAO 9303, eIDAS)
- Add TensorFlow.js edge face detection (BlazeFace)
- Add structured audit logger (GDPR-compliant)
- Risk scoring support

Part of KYC Apple Native UX v1.1.0
2026-06-29 16:24:48 +00:00

80 lines
3.5 KiB
JavaScript

/**
* 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 });
}
});
}