- Migration 074: convert checklist vorlage single FK fields to junction tables (vorlage_fahrzeug_typen, vorlage_fahrzeuge, vorlage_ausruestung_typen, vorlage_ausruestungen) - Backend checklist service: multi-type create/update/query with array fields - Backend cleanup service: add checklist-history and reset-checklist-history targets - Frontend types/service: singular FK fields replaced with arrays (fahrzeug_typ_ids, etc.) - Frontend Checklisten.tsx: multi-select Autocomplete pickers for all assignment types - Fahrzeuge.tsx/Ausruestung.tsx: add tab layout (Uebersicht + Einstellungen), inline type CRUD - FahrzeugEinstellungen/AusruestungEinstellungen: replaced with redirects to tab URLs - Sidebar: add Uebersicht sub-items, update Einstellungen paths to tab URLs - DataManagementTab: add checklist-history cleanup and reset sections Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
203 lines
8.5 KiB
TypeScript
203 lines
8.5 KiB
TypeScript
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<CleanupResult> {
|
|
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<CleanupResult> {
|
|
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<CleanupResult> {
|
|
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<CleanupResult> {
|
|
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<CleanupResult> {
|
|
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<CleanupResult> {
|
|
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<CleanupResult> {
|
|
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<CleanupResult> {
|
|
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<CleanupResult> {
|
|
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<CleanupResult> {
|
|
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<CleanupResult> {
|
|
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<CleanupResult> {
|
|
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 };
|
|
}
|
|
}
|
|
|
|
export default new CleanupService();
|