feat: add Buchhaltung dashboard widget, CSV export, Bestellungen linking, recurring bookings, and approval workflow
This commit is contained in:
@@ -810,6 +810,196 @@ async function getOverview(haushaltsjahrId: number) {
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Wiederkehrend (Recurring Bookings)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function getAllWiederkehrend() {
|
||||
try {
|
||||
const result = await pool.query(
|
||||
`SELECT w.*,
|
||||
k.bezeichnung as konto_bezeichnung,
|
||||
k.kontonummer as konto_kontonummer,
|
||||
bk.bezeichnung as bankkonto_bezeichnung
|
||||
FROM buchhaltung_wiederkehrend w
|
||||
LEFT JOIN buchhaltung_konten k ON w.konto_id = k.id
|
||||
LEFT JOIN buchhaltung_bankkonten bk ON w.bankkonto_id = bk.id
|
||||
ORDER BY w.naechste_ausfuehrung, w.bezeichnung`
|
||||
);
|
||||
return result.rows;
|
||||
} catch (error) {
|
||||
logger.error('BuchhaltungService.getAllWiederkehrend failed', { error });
|
||||
throw new Error('Wiederkehrende Buchungen konnten nicht geladen werden');
|
||||
}
|
||||
}
|
||||
|
||||
async function getWiederkehrendById(id: number) {
|
||||
try {
|
||||
const result = await pool.query(
|
||||
`SELECT * FROM buchhaltung_wiederkehrend WHERE id = $1`,
|
||||
[id]
|
||||
);
|
||||
return result.rows[0] || null;
|
||||
} catch (error) {
|
||||
logger.error('BuchhaltungService.getWiederkehrendById failed', { error, id });
|
||||
throw new Error('Wiederkehrende Buchung konnte nicht geladen werden');
|
||||
}
|
||||
}
|
||||
|
||||
async function createWiederkehrend(
|
||||
data: {
|
||||
bezeichnung: string;
|
||||
konto_id?: number | null;
|
||||
bankkonto_id?: number | null;
|
||||
typ: 'einnahme' | 'ausgabe';
|
||||
betrag: number;
|
||||
beschreibung?: string;
|
||||
empfaenger_auftraggeber?: string;
|
||||
intervall: string;
|
||||
naechste_ausfuehrung: string;
|
||||
aktiv?: boolean;
|
||||
},
|
||||
userId: string
|
||||
) {
|
||||
try {
|
||||
const result = await pool.query(
|
||||
`INSERT INTO buchhaltung_wiederkehrend
|
||||
(bezeichnung, konto_id, bankkonto_id, typ, betrag, beschreibung, empfaenger_auftraggeber, intervall, naechste_ausfuehrung, aktiv, erstellt_von)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
||||
RETURNING *`,
|
||||
[
|
||||
data.bezeichnung,
|
||||
data.konto_id || null,
|
||||
data.bankkonto_id || null,
|
||||
data.typ,
|
||||
data.betrag,
|
||||
data.beschreibung || null,
|
||||
data.empfaenger_auftraggeber || null,
|
||||
data.intervall,
|
||||
data.naechste_ausfuehrung,
|
||||
data.aktiv !== false,
|
||||
userId,
|
||||
]
|
||||
);
|
||||
return result.rows[0];
|
||||
} catch (error) {
|
||||
logger.error('BuchhaltungService.createWiederkehrend failed', { error });
|
||||
throw new Error('Wiederkehrende Buchung konnte nicht erstellt werden');
|
||||
}
|
||||
}
|
||||
|
||||
async function updateWiederkehrend(
|
||||
id: number,
|
||||
data: {
|
||||
bezeichnung?: string;
|
||||
konto_id?: number | null;
|
||||
bankkonto_id?: number | null;
|
||||
typ?: string;
|
||||
betrag?: number;
|
||||
beschreibung?: string;
|
||||
empfaenger_auftraggeber?: string;
|
||||
intervall?: string;
|
||||
naechste_ausfuehrung?: string;
|
||||
aktiv?: boolean;
|
||||
}
|
||||
) {
|
||||
try {
|
||||
const fields: string[] = [];
|
||||
const values: unknown[] = [];
|
||||
let idx = 1;
|
||||
if (data.bezeichnung !== undefined) { fields.push(`bezeichnung = $${idx++}`); values.push(data.bezeichnung); }
|
||||
if (data.konto_id !== undefined) { fields.push(`konto_id = $${idx++}`); values.push(data.konto_id); }
|
||||
if (data.bankkonto_id !== undefined) { fields.push(`bankkonto_id = $${idx++}`); values.push(data.bankkonto_id); }
|
||||
if (data.typ !== undefined) { fields.push(`typ = $${idx++}`); values.push(data.typ); }
|
||||
if (data.betrag !== undefined) { fields.push(`betrag = $${idx++}`); values.push(data.betrag); }
|
||||
if (data.beschreibung !== undefined) { fields.push(`beschreibung = $${idx++}`); values.push(data.beschreibung || null); }
|
||||
if (data.empfaenger_auftraggeber !== undefined) { fields.push(`empfaenger_auftraggeber = $${idx++}`); values.push(data.empfaenger_auftraggeber || null); }
|
||||
if (data.intervall !== undefined) { fields.push(`intervall = $${idx++}`); values.push(data.intervall); }
|
||||
if (data.naechste_ausfuehrung !== undefined) { fields.push(`naechste_ausfuehrung = $${idx++}`); values.push(data.naechste_ausfuehrung); }
|
||||
if (data.aktiv !== undefined) { fields.push(`aktiv = $${idx++}`); values.push(data.aktiv); }
|
||||
if (fields.length === 0) throw new Error('Keine Felder zum Aktualisieren');
|
||||
values.push(id);
|
||||
const result = await pool.query(
|
||||
`UPDATE buchhaltung_wiederkehrend SET ${fields.join(', ')} WHERE id = $${idx} RETURNING *`,
|
||||
values
|
||||
);
|
||||
return result.rows[0] || null;
|
||||
} catch (error) {
|
||||
logger.error('BuchhaltungService.updateWiederkehrend failed', { error, id });
|
||||
throw new Error('Wiederkehrende Buchung konnte nicht aktualisiert werden');
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteWiederkehrend(id: number) {
|
||||
try {
|
||||
await pool.query(`DELETE FROM buchhaltung_wiederkehrend WHERE id = $1`, [id]);
|
||||
} catch (error) {
|
||||
logger.error('BuchhaltungService.deleteWiederkehrend failed', { error, id });
|
||||
throw new Error('Wiederkehrende Buchung konnte nicht gelöscht werden');
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// CSV Export
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function exportTransaktionenCsv(haushaltsjahrId: number): Promise<string> {
|
||||
try {
|
||||
const result = await pool.query(
|
||||
`SELECT t.*,
|
||||
k.bezeichnung as konto_bezeichnung,
|
||||
k.kontonummer as konto_kontonummer,
|
||||
bk.bezeichnung as bankkonto_bezeichnung
|
||||
FROM buchhaltung_transaktionen t
|
||||
LEFT JOIN buchhaltung_konten k ON t.konto_id = k.id
|
||||
LEFT JOIN buchhaltung_bankkonten bk ON t.bankkonto_id = bk.id
|
||||
WHERE t.haushaltsjahr_id = $1
|
||||
ORDER BY t.datum DESC, t.id DESC`,
|
||||
[haushaltsjahrId]
|
||||
);
|
||||
|
||||
const statusLabels: Record<string, string> = {
|
||||
entwurf: 'Entwurf',
|
||||
gebucht: 'Gebucht',
|
||||
freigegeben: 'Freigegeben',
|
||||
storniert: 'Storniert',
|
||||
};
|
||||
|
||||
const header = [
|
||||
'Nr.', 'Datum', 'Typ', 'Beschreibung', 'Empfänger/Auftraggeber',
|
||||
'Verwendungszweck', 'Belegnummer', 'Konto', 'Bankkonto', 'Betrag', 'Status',
|
||||
].join(';');
|
||||
|
||||
const rows = result.rows.map(row => {
|
||||
const betrag = (row.typ === 'ausgabe' ? '-' : '') + parseFloat(row.betrag).toFixed(2).replace('.', ',');
|
||||
const konto = row.konto_kontonummer ? `${row.konto_kontonummer} ${row.konto_bezeichnung}` : '';
|
||||
const datum = row.datum ? new Date(row.datum).toLocaleDateString('de-DE') : '';
|
||||
const escCsv = (val: unknown) => {
|
||||
const s = String(val ?? '');
|
||||
return s.includes(';') || s.includes('"') || s.includes('\n') ? `"${s.replace(/"/g, '""')}"` : s;
|
||||
};
|
||||
return [
|
||||
row.laufende_nummer ?? `E${row.id}`,
|
||||
datum,
|
||||
row.typ === 'einnahme' ? 'Einnahme' : 'Ausgabe',
|
||||
escCsv(row.beschreibung),
|
||||
escCsv(row.empfaenger_auftraggeber),
|
||||
escCsv(row.verwendungszweck),
|
||||
escCsv(row.beleg_nr),
|
||||
escCsv(konto),
|
||||
escCsv(row.bankkonto_bezeichnung),
|
||||
betrag,
|
||||
statusLabels[row.status] || row.status,
|
||||
].join(';');
|
||||
});
|
||||
|
||||
return '\uFEFF' + header + '\r\n' + rows.join('\r\n');
|
||||
} catch (error) {
|
||||
logger.error('BuchhaltungService.exportTransaktionenCsv failed', { error });
|
||||
throw new Error('CSV-Export fehlgeschlagen');
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Export
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -852,6 +1042,12 @@ const buchhaltungService = {
|
||||
logAudit,
|
||||
getAuditByTransaktion,
|
||||
getOverview,
|
||||
getAllWiederkehrend,
|
||||
getWiederkehrendById,
|
||||
createWiederkehrend,
|
||||
updateWiederkehrend,
|
||||
deleteWiederkehrend,
|
||||
exportTransaktionenCsv,
|
||||
};
|
||||
|
||||
export default buchhaltungService;
|
||||
|
||||
Reference in New Issue
Block a user