feat(buchhaltung): recurring job, budget alerts, audit endpoint, konto-typen CRUD

This commit is contained in:
Matthias Hochmeister
2026-03-30 15:04:06 +02:00
parent bbbfc8eaaa
commit d833b3c224
7 changed files with 306 additions and 1 deletions

View File

@@ -5,6 +5,8 @@
import pool from '../config/database';
import logger from '../utils/logger';
import fs from 'fs';
import { permissionService } from './permission.service';
import notificationService from './notification.service';
// ---------------------------------------------------------------------------
// Kategorien (Categories)
@@ -197,6 +199,58 @@ async function getAllKontoTypen() {
}
}
async function createKontoTyp(data: { bezeichnung: string; art: string; sort_order?: number }) {
try {
const result = await pool.query(
`INSERT INTO buchhaltung_konto_typen (bezeichnung, art, sort_order) VALUES ($1, $2, $3) RETURNING *`,
[data.bezeichnung, data.art, data.sort_order ?? 0]
);
return result.rows[0];
} catch (error) {
logger.error('BuchhaltungService.createKontoTyp failed', { error });
throw new Error('Kontotyp konnte nicht erstellt werden');
}
}
async function updateKontoTyp(id: number, data: { bezeichnung?: string; art?: string; sort_order?: number }) {
try {
const fields: string[] = [];
const values: unknown[] = [];
let idx = 1;
if (data.bezeichnung !== undefined) { fields.push(`bezeichnung = $${idx++}`); values.push(data.bezeichnung); }
if (data.art !== undefined) { fields.push(`art = $${idx++}`); values.push(data.art); }
if (data.sort_order !== undefined) { fields.push(`sort_order = $${idx++}`); values.push(data.sort_order); }
if (fields.length === 0) throw new Error('Keine Felder zum Aktualisieren');
values.push(id);
const result = await pool.query(
`UPDATE buchhaltung_konto_typen SET ${fields.join(', ')} WHERE id = $${idx} RETURNING *`,
values
);
return result.rows[0] || null;
} catch (error) {
logger.error('BuchhaltungService.updateKontoTyp failed', { error, id });
throw new Error('Kontotyp konnte nicht aktualisiert werden');
}
}
async function deleteKontoTyp(id: number) {
try {
const check = await pool.query(
`SELECT COUNT(*) FROM buchhaltung_bankkonten WHERE konto_typ_id = $1`,
[id]
);
if (parseInt(check.rows[0].count) > 0) {
const err = new Error('Kontotyp wird noch verwendet');
(err as any).statusCode = 409;
throw err;
}
await pool.query(`DELETE FROM buchhaltung_konto_typen WHERE id = $1`, [id]);
} catch (error) {
logger.error('BuchhaltungService.deleteKontoTyp failed', { error, id });
throw error;
}
}
// ---------------------------------------------------------------------------
// Bankkonten (Bank Accounts)
// ---------------------------------------------------------------------------
@@ -785,6 +839,38 @@ async function bookTransaktion(id: number, userId: string) {
[id, userId]
);
if (result.rows[0]) await logAudit(id, 'gebucht', {}, userId);
// Budget alert check (non-fatal)
try {
const tx = result.rows[0];
if (tx && tx.konto_id) {
const budget = await getBudgetUtilisation(tx.konto_id);
if (budget && budget.auslastung_prozent > 0) {
const settings = await getEinstellungen();
const globalThreshold = parseInt(settings.default_alert_threshold as string) || 80;
const threshold = budget.alert_threshold ?? globalThreshold;
if (budget.auslastung_prozent >= threshold) {
const users = await permissionService.getUsersWithPermission('buchhaltung:manage_accounts');
for (const user of users) {
await notificationService.createNotification({
user_id: user.id,
typ: 'buchhaltung_budget_warnung',
titel: 'Budget-Warnung',
nachricht: `${budget.bezeichnung}: ${budget.auslastung_prozent}% ausgelastet`,
schwere: 'warnung',
link: `/buchhaltung/konto/${tx.konto_id}`,
quell_id: `budget-alert-${tx.konto_id}`,
quell_typ: 'buchhaltung_budget_warnung',
});
}
}
}
}
} catch (alertErr) {
logger.warn('BuchhaltungService.bookTransaktion budget alert failed (non-fatal)', { alertErr });
}
return result.rows[0] || null;
} catch (error) {
logger.error('BuchhaltungService.bookTransaktion failed', { error, id });
@@ -1365,6 +1451,9 @@ const buchhaltungService = {
updateHaushaltsjahr,
closeHaushaltsjahr,
getAllKontoTypen,
createKontoTyp,
updateKontoTyp,
deleteKontoTyp,
getAllBankkonten,
getBankkontoById,
createBankkonto,