import pool from '../config/database'; import logger from '../utils/logger'; export interface CleanupResult { count: number; deleted: boolean; } class CleanupService { async cleanupNotifications(olderThanDays: number, confirm: boolean): Promise { const cutoff = `${olderThanDays} days`; if (!confirm) { const { rows } = await pool.query( `SELECT COUNT(*)::int AS count FROM benachrichtigungen WHERE erstellt_am < NOW() - $1::interval`, [cutoff] ); return { count: rows[0].count, deleted: false }; } const { rowCount } = await pool.query( `DELETE FROM benachrichtigungen WHERE erstellt_am < NOW() - $1::interval`, [cutoff] ); logger.info(`Cleanup: deleted ${rowCount} notifications older than ${olderThanDays} days`); return { count: rowCount ?? 0, deleted: true }; } async cleanupAuditLog(olderThanDays: number, confirm: boolean): Promise { const cutoff = `${olderThanDays} days`; if (!confirm) { const { rows } = await pool.query( `SELECT COUNT(*)::int AS count FROM audit_log WHERE created_at < NOW() - $1::interval`, [cutoff] ); return { count: rows[0].count, deleted: false }; } const { rowCount } = await pool.query( `DELETE FROM audit_log WHERE created_at < NOW() - $1::interval`, [cutoff] ); logger.info(`Cleanup: deleted ${rowCount} audit_log entries older than ${olderThanDays} days`); return { count: rowCount ?? 0, deleted: true }; } async cleanupEvents(olderThanDays: number, confirm: boolean): Promise { const cutoff = `${olderThanDays} days`; if (!confirm) { const { rows } = await pool.query( `SELECT COUNT(*)::int AS count FROM events WHERE end_date < NOW() - $1::interval`, [cutoff] ); return { count: rows[0].count, deleted: false }; } const { rowCount } = await pool.query( `DELETE FROM events WHERE end_date < NOW() - $1::interval`, [cutoff] ); logger.info(`Cleanup: deleted ${rowCount} events older than ${olderThanDays} days`); return { count: rowCount ?? 0, deleted: true }; } async cleanupBookings(olderThanDays: number, confirm: boolean): Promise { const cutoff = `${olderThanDays} days`; if (!confirm) { const { rows } = await pool.query( `SELECT COUNT(*)::int AS count FROM fahrzeug_buchungen WHERE end_date < NOW() - $1::interval AND status IN ('completed', 'cancelled')`, [cutoff] ); return { count: rows[0].count, deleted: false }; } const { rowCount } = await pool.query( `DELETE FROM fahrzeug_buchungen WHERE end_date < NOW() - $1::interval AND status IN ('completed', 'cancelled')`, [cutoff] ); logger.info(`Cleanup: deleted ${rowCount} bookings older than ${olderThanDays} days`); return { count: rowCount ?? 0, deleted: true }; } async cleanupOrders(olderThanDays: number, confirm: boolean): Promise { const cutoff = `${olderThanDays} days`; if (!confirm) { const { rows } = await pool.query( `SELECT COUNT(*)::int AS count FROM bestellungen WHERE updated_at < NOW() - $1::interval AND status = 'abgeschlossen'`, [cutoff] ); return { count: rows[0].count, deleted: false }; } const { rowCount } = await pool.query( `DELETE FROM bestellungen WHERE updated_at < NOW() - $1::interval AND status = 'abgeschlossen'`, [cutoff] ); logger.info(`Cleanup: deleted ${rowCount} orders older than ${olderThanDays} days`); return { count: rowCount ?? 0, deleted: true }; } async cleanupVehicleHistory(olderThanDays: number, confirm: boolean): Promise { const cutoff = `${olderThanDays} days`; if (!confirm) { const { rows } = await pool.query( `SELECT COUNT(*)::int AS count FROM fahrzeug_wartungslog WHERE datum < NOW() - $1::interval`, [cutoff] ); return { count: rows[0].count, deleted: false }; } const { rowCount } = await pool.query( `DELETE FROM fahrzeug_wartungslog WHERE datum < NOW() - $1::interval`, [cutoff] ); logger.info(`Cleanup: deleted ${rowCount} vehicle history entries older than ${olderThanDays} days`); return { count: rowCount ?? 0, deleted: true }; } async cleanupEquipmentHistory(olderThanDays: number, confirm: boolean): Promise { const cutoff = `${olderThanDays} days`; if (!confirm) { const { rows } = await pool.query( `SELECT COUNT(*)::int AS count FROM ausruestung_wartungslog WHERE datum < NOW() - $1::interval`, [cutoff] ); return { count: rows[0].count, deleted: false }; } const { rowCount } = await pool.query( `DELETE FROM ausruestung_wartungslog WHERE datum < NOW() - $1::interval`, [cutoff] ); logger.info(`Cleanup: deleted ${rowCount} equipment history entries older than ${olderThanDays} days`); return { count: rowCount ?? 0, deleted: true }; } async resetBestellungenSequence(confirm: boolean): Promise { if (!confirm) { const { rows } = await pool.query('SELECT COUNT(*)::int AS count FROM bestellungen'); return { count: rows[0].count, deleted: false }; } const { rows } = await pool.query('SELECT COUNT(*)::int AS count FROM bestellungen'); const count = rows[0].count; // Delete related linking tables first, then main table await pool.query('DELETE FROM ausruestung_anfrage_bestellung WHERE bestellung_id IN (SELECT id FROM bestellungen)'); await pool.query('TRUNCATE bestellungen CASCADE'); try { await pool.query('ALTER SEQUENCE bestellungen_id_seq RESTART WITH 1'); } catch { /* sequence may not exist */ } logger.info(`Cleanup: truncated bestellungen (${count} rows) and reset sequence`); return { count, deleted: true }; } async resetAusruestungAnfragenSequence(confirm: boolean): Promise { if (!confirm) { const { rows } = await pool.query('SELECT COUNT(*)::int AS count FROM ausruestung_anfragen'); return { count: rows[0].count, deleted: false }; } const { rows } = await pool.query('SELECT COUNT(*)::int AS count FROM ausruestung_anfragen'); const count = rows[0].count; // Delete linking table first await pool.query('DELETE FROM ausruestung_anfrage_bestellung WHERE anfrage_id IN (SELECT id FROM ausruestung_anfragen)'); await pool.query('TRUNCATE ausruestung_anfragen CASCADE'); try { await pool.query('ALTER SEQUENCE ausruestung_anfragen_id_seq RESTART WITH 1'); } catch { /* sequence may not exist */ } logger.info(`Cleanup: truncated ausruestung_anfragen (${count} rows) and reset sequence`); return { count, deleted: true }; } async cleanupChecklistHistory(olderThanDays: number, confirm: boolean): Promise { const cutoff = `${olderThanDays} days`; if (!confirm) { const { rows } = await pool.query( `SELECT COUNT(*)::int AS count FROM checklist_ausfuehrungen WHERE ausgefuehrt_am < NOW() - $1::interval`, [cutoff] ); return { count: rows[0].count, deleted: false }; } const { rowCount } = await pool.query( `DELETE FROM checklist_ausfuehrungen WHERE ausgefuehrt_am < NOW() - $1::interval`, [cutoff] ); logger.info(`Cleanup: deleted ${rowCount} checklist executions older than ${olderThanDays} days`); return { count: rowCount ?? 0, deleted: true }; } async resetChecklistHistory(confirm: boolean): Promise { if (!confirm) { const { rows } = await pool.query(`SELECT COUNT(*)::int AS count FROM checklist_ausfuehrungen`); return { count: rows[0].count, deleted: false }; } await pool.query(`TRUNCATE checklist_ausfuehrungen CASCADE`); await pool.query(`TRUNCATE checklist_faelligkeit CASCADE`); logger.info('Cleanup: reset all checklist history'); return { count: 0, deleted: true }; } async resetIssuesSequence(confirm: boolean): Promise { if (!confirm) { const { rows } = await pool.query('SELECT COUNT(*)::int AS count FROM issues'); return { count: rows[0].count, deleted: false }; } const { rows } = await pool.query('SELECT COUNT(*)::int AS count FROM issues'); const count = rows[0].count; await pool.query('TRUNCATE issues CASCADE'); try { await pool.query('ALTER SEQUENCE issues_id_seq RESTART WITH 1'); } catch { /* sequence may not exist */ } logger.info(`Cleanup: truncated issues (${count} rows) and reset sequence`); return { count, deleted: true }; } async resetBuchhaltungTransaktionen(confirm: boolean): Promise { if (!confirm) { const { rows } = await pool.query('SELECT COUNT(*)::int AS count FROM buchhaltung_transaktionen'); return { count: rows[0].count, deleted: false }; } const { rows } = await pool.query('SELECT COUNT(*)::int AS count FROM buchhaltung_transaktionen'); const count = rows[0].count; await pool.query('TRUNCATE buchhaltung_transaktionen CASCADE'); try { await pool.query('ALTER SEQUENCE buchhaltung_transaktionen_id_seq RESTART WITH 1'); } catch { /* sequence may not exist */ } logger.info(`Cleanup: truncated buchhaltung_transaktionen (${count} rows) and reset sequence`); return { count, deleted: true }; } async resetBuchhaltungKonten(confirm: boolean): Promise { if (!confirm) { const { rows } = await pool.query('SELECT COUNT(*)::int AS count FROM buchhaltung_konten'); return { count: rows[0].count, deleted: false }; } const { rows } = await pool.query('SELECT COUNT(*)::int AS count FROM buchhaltung_konten'); const count = rows[0].count; await pool.query('TRUNCATE buchhaltung_transaktionen CASCADE'); await pool.query('TRUNCATE buchhaltung_konten CASCADE'); try { await pool.query('ALTER SEQUENCE buchhaltung_konten_id_seq RESTART WITH 1'); } catch { /* sequence may not exist */ } try { await pool.query('ALTER SEQUENCE buchhaltung_transaktionen_id_seq RESTART WITH 1'); } catch { /* sequence may not exist */ } logger.info(`Cleanup: truncated buchhaltung_konten (${count} rows) and reset sequence`); return { count, deleted: true }; } async resetBuchhaltungBankkonten(confirm: boolean): Promise { if (!confirm) { const { rows } = await pool.query('SELECT COUNT(*)::int AS count FROM buchhaltung_bankkonten'); return { count: rows[0].count, deleted: false }; } const { rows } = await pool.query('SELECT COUNT(*)::int AS count FROM buchhaltung_bankkonten'); const count = rows[0].count; await pool.query('TRUNCATE buchhaltung_bankkonten CASCADE'); try { await pool.query('ALTER SEQUENCE buchhaltung_bankkonten_id_seq RESTART WITH 1'); } catch { /* sequence may not exist */ } logger.info(`Cleanup: truncated buchhaltung_bankkonten (${count} rows) and reset sequence`); return { count, deleted: true }; } async resetPersoenlicheAusruestung(confirm: boolean): Promise { if (!confirm) { const { rows } = await pool.query('SELECT COUNT(*)::int AS count FROM persoenliche_ausruestung WHERE geloescht_am IS NULL'); return { count: rows[0].count, deleted: false }; } const { rows } = await pool.query('SELECT COUNT(*)::int AS count FROM persoenliche_ausruestung'); const count = rows[0].count; // Clear FK references from request positions before truncating await pool.query(`UPDATE ausruestung_anfrage_positionen SET zuweisung_persoenlich_id = NULL, zuweisung_typ = 'keine' WHERE zuweisung_persoenlich_id IS NOT NULL`); // CASCADE removes persoenliche_ausruestung_eigenschaften rows automatically await pool.query('TRUNCATE persoenliche_ausruestung CASCADE'); logger.info(`Cleanup: truncated persoenliche_ausruestung (${count} rows)`); return { count, deleted: true }; } } export default new CleanupService();