feat(buchhaltung): recurring job, budget alerts, audit endpoint, konto-typen CRUD
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user