Rechnungen: Überblick über fällige/überfällige Rechnungen und Verträge

This commit is contained in:
Kundencenter 2026-09-27 21:19:48 +02:00
parent 5efe37ecc0
commit 3a2864ee76
5 changed files with 125 additions and 4 deletions

View file

@ -96,6 +96,25 @@ export const invoicesModule: KcModule = {
return rows.map(invoiceView);
});
/** Mahnwesen-Überblick: überfällige Rechnungen und aktive Verträge, die vermutlich neu in Rechnung gestellt werden müssen
* (letzte Rechnung liegt länger zurück als die Verlängerungslaufzeit des Vertrags). Nur Personal. */
app.get('/invoices/reminders', async (req) => {
requirePermission(req, 'invoices.read');
const overdue = (await query(`${INVOICE_SQL} WHERE v.status = 'open' AND v.due_date < CURDATE() ORDER BY v.due_date`)).map(invoiceView);
const contracts = await query(`
SELECT c.id, c.number, c.org_id, c.started_at, c.renewal_term_months, g.name AS org_name, g.customer_number,
JSON_VALUE(c.price_snapshot_json, '$.name') AS product_name, JSON_VALUE(c.price_snapshot_json, '$.recurring.gross') AS last_gross_cents,
(SELECT MAX(i.issue_date) FROM invoices i JOIN invoice_items ii ON ii.invoice_id = i.id WHERE ii.contract_id = c.id AND i.status IN ('open', 'paid')) AS last_invoiced
FROM contracts c JOIN organizations g ON g.id = c.org_id
WHERE c.status = 'active' AND c.renewal = 'auto' AND c.renewal_term_months > 0`);
const dueContracts = contracts
.map((c) => { const since = c.last_invoiced ?? c.started_at; const due = new Date(since); due.setMonth(due.getMonth() + c.renewal_term_months);
return { id: c.id, number: c.number, orgId: c.org_id, orgName: c.org_name, customerNumber: c.customer_number, productName: c.product_name, lastInvoicedAt: c.last_invoiced, dueSince: due.toISOString().slice(0, 10), lastGrossCents: c.last_gross_cents === null ? null : Number(c.last_gross_cents) }; })
.filter((c) => new Date(c.dueSince) <= new Date())
.sort((a, b) => a.dueSince.localeCompare(b.dueSince));
return { overdueInvoices: overdue, dueContracts };
});
app.post('/invoices', async (req) => {
const a = requirePermission(req, 'invoices.write');
const b = z.object({ orgId: z.string().uuid(), note: z.string().trim().max(500).optional(), paymentMethod: z.string().max(50).optional() }).parse(req.body);