|
|
|
|
@@ -471,18 +471,28 @@ async function validateSubPotBudget(
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function createKonto(
|
|
|
|
|
data: { haushaltsjahr_id: number; konto_typ_id?: number; kontonummer: string; bezeichnung: string; parent_id?: number | null; budget_gwg?: number; budget_anlagen?: number; budget_instandhaltung?: number; notizen?: string },
|
|
|
|
|
data: { haushaltsjahr_id: number; konto_typ_id?: number; kontonummer: string; bezeichnung: string; parent_id?: number | null; budget_gwg?: number; budget_anlagen?: number; budget_instandhaltung?: number; notizen?: string; kategorie_id?: number | null; budget_typ?: string; budget_gesamt?: number },
|
|
|
|
|
userId: string
|
|
|
|
|
) {
|
|
|
|
|
try {
|
|
|
|
|
if (data.parent_id && (data.budget_gwg || data.budget_anlagen || data.budget_instandhaltung)) {
|
|
|
|
|
await validateSubPotBudget(data.parent_id, data.budget_gwg || 0, data.budget_anlagen || 0, data.budget_instandhaltung || 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Child konten inherit parent's budget_typ
|
|
|
|
|
let budgetTyp = data.budget_typ || 'detailliert';
|
|
|
|
|
if (data.parent_id) {
|
|
|
|
|
const parentRow = await pool.query(`SELECT budget_typ FROM buchhaltung_konten WHERE id = $1`, [data.parent_id]);
|
|
|
|
|
if (parentRow.rows[0]) {
|
|
|
|
|
budgetTyp = parentRow.rows[0].budget_typ;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const result = await pool.query(
|
|
|
|
|
`INSERT INTO buchhaltung_konten (haushaltsjahr_id, konto_typ_id, kontonummer, bezeichnung, parent_id, budget_gwg, budget_anlagen, budget_instandhaltung, notizen, erstellt_von)
|
|
|
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
|
|
|
|
`INSERT INTO buchhaltung_konten (haushaltsjahr_id, konto_typ_id, kontonummer, bezeichnung, parent_id, budget_gwg, budget_anlagen, budget_instandhaltung, notizen, erstellt_von, kategorie_id, budget_typ, budget_gesamt)
|
|
|
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
|
|
|
|
|
RETURNING *`,
|
|
|
|
|
[data.haushaltsjahr_id, data.konto_typ_id || null, data.kontonummer, data.bezeichnung, data.parent_id || null, data.budget_gwg || 0, data.budget_anlagen || 0, data.budget_instandhaltung || 0, data.notizen || null, userId]
|
|
|
|
|
[data.haushaltsjahr_id, data.konto_typ_id || null, data.kontonummer, data.bezeichnung, data.parent_id || null, data.budget_gwg || 0, data.budget_anlagen || 0, data.budget_instandhaltung || 0, data.notizen || null, userId, data.kategorie_id ?? null, budgetTyp, data.budget_gesamt || 0]
|
|
|
|
|
);
|
|
|
|
|
return result.rows[0];
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
@@ -497,7 +507,7 @@ async function createKonto(
|
|
|
|
|
|
|
|
|
|
async function updateKonto(
|
|
|
|
|
id: number,
|
|
|
|
|
data: { konto_typ_id?: number; kontonummer?: string; bezeichnung?: string; parent_id?: number | null; budget_gwg?: number; budget_anlagen?: number; budget_instandhaltung?: number; notizen?: string }
|
|
|
|
|
data: { konto_typ_id?: number; kontonummer?: string; bezeichnung?: string; parent_id?: number | null; budget_gwg?: number; budget_anlagen?: number; budget_instandhaltung?: number; notizen?: string; kategorie_id?: number | null; budget_typ?: string; budget_gesamt?: number }
|
|
|
|
|
) {
|
|
|
|
|
try {
|
|
|
|
|
// Budget validation for sub-pots
|
|
|
|
|
@@ -527,7 +537,27 @@ async function updateKonto(
|
|
|
|
|
if (data.budget_anlagen !== undefined) { fields.push(`budget_anlagen = $${idx++}`); values.push(data.budget_anlagen); }
|
|
|
|
|
if (data.budget_instandhaltung !== undefined) { fields.push(`budget_instandhaltung = $${idx++}`); values.push(data.budget_instandhaltung); }
|
|
|
|
|
if (data.notizen !== undefined) { fields.push(`notizen = $${idx++}`); values.push(data.notizen || null); }
|
|
|
|
|
if ('kategorie_id' in data) { fields.push(`kategorie_id = $${idx++}`); values.push(data.kategorie_id ?? null); }
|
|
|
|
|
if (data.budget_typ !== undefined) { fields.push(`budget_typ = $${idx++}`); values.push(data.budget_typ); }
|
|
|
|
|
if (data.budget_gesamt !== undefined) { fields.push(`budget_gesamt = $${idx++}`); values.push(data.budget_gesamt); }
|
|
|
|
|
if (fields.length === 0) throw new Error('Keine Felder zum Aktualisieren');
|
|
|
|
|
|
|
|
|
|
// Child konten must inherit parent's budget_typ
|
|
|
|
|
if (data.budget_typ !== undefined) {
|
|
|
|
|
const currentRow = await pool.query(`SELECT parent_id FROM buchhaltung_konten WHERE id = $1`, [id]);
|
|
|
|
|
const parentId = data.parent_id !== undefined ? data.parent_id : currentRow.rows[0]?.parent_id;
|
|
|
|
|
if (parentId) {
|
|
|
|
|
const parentRow = await pool.query(`SELECT budget_typ FROM buchhaltung_konten WHERE id = $1`, [parentId]);
|
|
|
|
|
if (parentRow.rows[0]) {
|
|
|
|
|
// Override budget_typ with parent's value
|
|
|
|
|
const btIdx = fields.findIndex(f => f.startsWith('budget_typ'));
|
|
|
|
|
if (btIdx !== -1) {
|
|
|
|
|
values[btIdx] = parentRow.rows[0].budget_typ;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
values.push(id);
|
|
|
|
|
const result = await pool.query(
|
|
|
|
|
`UPDATE buchhaltung_konten SET ${fields.join(', ')} WHERE id = $${idx} RETURNING *`,
|
|
|
|
|
@@ -1231,6 +1261,94 @@ async function exportTransaktionenCsv(haushaltsjahrId: number): Promise<string>
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// Erstattungen (Reimbursements)
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
interface Transaktion {
|
|
|
|
|
id: number;
|
|
|
|
|
[key: string]: unknown;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function createErstattung(data: {
|
|
|
|
|
konto_id: number;
|
|
|
|
|
bankkonto_id: number;
|
|
|
|
|
betrag: number;
|
|
|
|
|
datum: string;
|
|
|
|
|
beschreibung?: string;
|
|
|
|
|
empfaenger_auftraggeber?: string;
|
|
|
|
|
verwendungszweck?: string;
|
|
|
|
|
ausgabe_ids: number[];
|
|
|
|
|
erstellt_von?: number;
|
|
|
|
|
}): Promise<Transaktion> {
|
|
|
|
|
const client = await pool.connect();
|
|
|
|
|
try {
|
|
|
|
|
await client.query('BEGIN');
|
|
|
|
|
|
|
|
|
|
// Look up haushaltsjahr_id from the konto
|
|
|
|
|
const kontoResult = await client.query(
|
|
|
|
|
`SELECT haushaltsjahr_id FROM buchhaltung_konten WHERE id = $1`,
|
|
|
|
|
[data.konto_id]
|
|
|
|
|
);
|
|
|
|
|
const haushaltsjahrId = kontoResult.rows[0]?.haushaltsjahr_id;
|
|
|
|
|
|
|
|
|
|
const txResult = await client.query(
|
|
|
|
|
`INSERT INTO buchhaltung_transaktionen (typ, konto_id, bankkonto_id, betrag, datum, beschreibung, empfaenger_auftraggeber, verwendungszweck, erstellt_von, haushaltsjahr_id, status)
|
|
|
|
|
VALUES ('einnahme', $1, $2, $3, $4, $5, $6, $7, $8, $9, 'entwurf')
|
|
|
|
|
RETURNING *`,
|
|
|
|
|
[data.konto_id, data.bankkonto_id, data.betrag, data.datum, data.beschreibung || null, data.empfaenger_auftraggeber || null, data.verwendungszweck || null, data.erstellt_von || null, haushaltsjahrId]
|
|
|
|
|
);
|
|
|
|
|
const tx = txResult.rows[0];
|
|
|
|
|
|
|
|
|
|
for (const ausgabeId of data.ausgabe_ids) {
|
|
|
|
|
await client.query(
|
|
|
|
|
`INSERT INTO buchhaltung_erstattung_zuordnungen (erstattung_transaktion_id, ausgabe_transaktion_id) VALUES ($1, $2)`,
|
|
|
|
|
[tx.id, ausgabeId]
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await client.query('COMMIT');
|
|
|
|
|
return tx;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
await client.query('ROLLBACK');
|
|
|
|
|
logger.error('BuchhaltungService.createErstattung failed', { error });
|
|
|
|
|
throw new Error('Erstattung konnte nicht erstellt werden');
|
|
|
|
|
} finally {
|
|
|
|
|
client.release();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function getErstattungLinks(transaktionId: number): Promise<{
|
|
|
|
|
erstattung?: any;
|
|
|
|
|
ausgaben?: any[];
|
|
|
|
|
}> {
|
|
|
|
|
try {
|
|
|
|
|
// If this transaction is an Ausgabe, find its reimbursement
|
|
|
|
|
const erstattungResult = await pool.query(
|
|
|
|
|
`SELECT t.* FROM buchhaltung_transaktionen t
|
|
|
|
|
JOIN buchhaltung_erstattung_zuordnungen ez ON t.id = ez.erstattung_transaktion_id
|
|
|
|
|
WHERE ez.ausgabe_transaktion_id = $1`,
|
|
|
|
|
[transaktionId]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// If this transaction is an Erstattung, find its linked Ausgaben
|
|
|
|
|
const ausgabenResult = await pool.query(
|
|
|
|
|
`SELECT t.* FROM buchhaltung_transaktionen t
|
|
|
|
|
JOIN buchhaltung_erstattung_zuordnungen ez ON t.id = ez.ausgabe_transaktion_id
|
|
|
|
|
WHERE ez.erstattung_transaktion_id = $1`,
|
|
|
|
|
[transaktionId]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
erstattung: erstattungResult.rows[0] || undefined,
|
|
|
|
|
ausgaben: ausgabenResult.rows,
|
|
|
|
|
};
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logger.error('BuchhaltungService.getErstattungLinks failed', { error, transaktionId });
|
|
|
|
|
throw new Error('Erstattungsverknüpfungen konnten nicht geladen werden');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// Export
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
@@ -1286,6 +1404,8 @@ const buchhaltungService = {
|
|
|
|
|
updateWiederkehrend,
|
|
|
|
|
deleteWiederkehrend,
|
|
|
|
|
exportTransaktionenCsv,
|
|
|
|
|
createErstattung,
|
|
|
|
|
getErstattungLinks,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export default buchhaltungService;
|
|
|
|
|
|