v1.33: Eintraege in allen Listen loeschbar

This commit is contained in:
2026-10-04 19:13:36 +02:00
parent 20b5dab556
commit 2f873737c2
8 changed files with 325 additions and 10 deletions
+97
View File
@@ -553,6 +553,19 @@ app.patch('/api/tickets/:id', auth, adminOnly, async (req, res, next) => {
} catch (e) { next(e) }
})
// Schadensmeldung löschen inkl. verknüpfter Unterhaltung und Nachrichten (v1.33)
app.delete('/api/tickets/:id', auth, adminOnly, async (req, res, next) => {
try {
const { rows } = await pool.query('DELETE FROM tickets WHERE id = $1 RETURNING id', [req.params.id])
if (!rows.length) return res.status(404).json({ fehler: 'Schadensmeldung nicht gefunden.' })
const { rows: threads } = await pool.query('DELETE FROM threads WHERE ticket_id = $1 RETURNING id', [req.params.id])
for (const th of threads) {
await pool.query('DELETE FROM messages WHERE thread_id = $1', [th.id])
}
res.json({ ok: true })
} catch (e) { next(e) }
})
// ── Zählerstände (Strom / Wasser / Gas) ─────────────────────
const ZAEHLER_ARTEN = ['Strom', 'Wasser', 'Gas', 'Wärme', 'Sonstiges']
// Gängige Heizungsformen (v1.29) – Dropdown in Haus-/Wohnungsformular
@@ -707,6 +720,16 @@ app.delete('/api/meters/:id/readings/:rid', auth, adminOnly, async (req, res, ne
res.json({ ok: true })
} catch (e) { next(e) }
})
// Zähler komplett löschen inkl. Verlauf (v1.33)
app.delete('/api/meters/:id', auth, adminOnly, async (req, res, next) => {
try {
const { rows } = await pool.query('DELETE FROM meters WHERE id = $1 RETURNING id', [req.params.id])
if (!rows.length) return res.status(404).json({ fehler: 'Zähler nicht gefunden.' })
await pool.query('DELETE FROM meter_readings WHERE meter_id = $1', [req.params.id])
res.json({ ok: true })
} catch (e) { next(e) }
})
// ── Dokumente (Verwaltung) ───────────────────────────────────
const DOKUMENT_KATEGORIEN = ['Mietvertrag', 'Nebenkostenabrechnung', 'Hausordnung', 'Protokoll', 'Sonstiges']
@@ -1135,6 +1158,15 @@ app.post('/api/belege/:id/analysieren', auth, adminOnly, async (req, res, next)
} catch (e) { next(e) }
})
// Beleg löschen (v1.33)
app.delete('/api/belege/:id', auth, adminOnly, async (req, res, next) => {
try {
const { rows } = await pool.query('DELETE FROM belege WHERE id = $1 RETURNING id', [req.params.id])
if (!rows.length) return res.status(404).json({ fehler: 'Beleg nicht gefunden.' })
res.json({ ok: true })
} catch (e) { next(e) }
})
// ── Bestand: Häuser & Wohnungen (Verwaltung) ────────────────
/** Name/Anzeigeadresse werden aus den Einzelfeldern zusammengesetzt (v1.26). */
function hausNameAus(b) {
@@ -1237,6 +1269,28 @@ app.patch('/api/buildings/:name', auth, adminOnly, async (req, res, next) => {
} catch (e) { next(e) }
})
// Haus löschen – nur wenn keine Wohnungen, Zähler oder Kosten mehr darauf verweisen (v1.33)
app.delete('/api/buildings/:name', auth, adminOnly, async (req, res, next) => {
try {
const { rows } = await pool.query('SELECT 1 FROM buildings WHERE name = $1', [req.params.name])
if (!rows.length) return res.status(404).json({ fehler: 'Haus nicht gefunden.' })
const blocker = []
for (const [tabelle, spalte, label] of [
['units', 'building', 'Wohnungen'],
['meters', 'building', 'Zähler'],
['costs', 'building', 'Kostenpositionen'],
]) {
const { rows: r } = await pool.query(`SELECT COUNT(*)::int AS n FROM ${tabelle} WHERE ${spalte} = $1`, [req.params.name])
if (r[0].n > 0) blocker.push(`${r[0].n} ${label}`)
}
if (blocker.length) {
return res.status(400).json({ fehler: `Haus kann nicht gelöscht werden – bitte zuerst entfernen: ${blocker.join(', ')}.` })
}
await pool.query('DELETE FROM buildings WHERE name = $1', [req.params.name])
res.json({ ok: true })
} catch (e) { next(e) }
})
app.post('/api/units', auth, adminOnly, async (req, res, next) => {
try {
const u = req.body || {}
@@ -1297,6 +1351,29 @@ app.patch('/api/units/:id', auth, adminOnly, async (req, res, next) => {
} catch (e) { next(e) }
})
// Wohnung löschen – nur wenn keine Zähler, Dokumente, Kosten oder Mieter sie referenzieren (v1.33)
app.delete('/api/units/:id', auth, adminOnly, async (req, res, next) => {
try {
const { rows } = await pool.query('SELECT 1 FROM units WHERE id = $1', [req.params.id])
if (!rows.length) return res.status(404).json({ fehler: 'Wohnung nicht gefunden.' })
const blocker = []
for (const [tabelle, spalte, label] of [
['meters', 'unit_id', 'Zähler'],
['documents', 'unit_id', 'Dokumente'],
['costs', 'unit_id', 'Kostenpositionen'],
['users', 'einheit_id', 'zugeordnete Benutzer'],
]) {
const { rows: r } = await pool.query(`SELECT COUNT(*)::int AS n FROM ${tabelle} WHERE ${spalte} = $1`, [req.params.id])
if (r[0].n > 0) blocker.push(`${r[0].n} ${label}`)
}
if (blocker.length) {
return res.status(400).json({ fehler: `Wohnung kann nicht gelöscht werden – bitte zuerst entfernen/zuordnen: ${blocker.join(', ')}.` })
}
await pool.query('DELETE FROM units WHERE id = $1', [req.params.id])
res.json({ ok: true })
} catch (e) { next(e) }
})
// ── Nachrichten (Threads) ────────────────────────────────────
app.post('/api/threads', auth, async (req, res, next) => {
try {
@@ -1458,6 +1535,26 @@ app.patch('/api/users/:id', auth, adminOnly, async (req, res, next) => {
} catch (e) { next(e) }
})
// Benutzer löschen: eigenes Konto und den letzten aktiven Verwalter schützen (v1.33)
app.delete('/api/users/:id', auth, adminOnly, async (req, res, next) => {
try {
if (req.params.id === req.user.id)
return res.status(400).json({ fehler: 'Das eigene Konto kann nicht gelöscht werden.' })
const { rows } = await pool.query('SELECT * FROM users WHERE id = $1', [req.params.id])
if (!rows.length) return res.status(404).json({ fehler: 'Benutzer nicht gefunden.' })
if (rows[0].rolle === 'verwaltung' && rows[0].aktiv) {
const { rows: rest } = await pool.query(
`SELECT COUNT(*)::int AS n FROM users WHERE rolle = 'verwaltung' AND aktiv AND id <> $1`, [req.params.id],
)
if (rest[0].n === 0)
return res.status(400).json({ fehler: 'Der letzte aktive Verwaltungsbenutzer kann nicht gelöscht werden.' })
}
await pool.query('DELETE FROM sessions WHERE user_id = $1', [req.params.id])
await pool.query('DELETE FROM users WHERE id = $1', [req.params.id])
res.json({ ok: true })
} catch (e) { next(e) }
})
// Direkte E-Mail der Verwaltung an einen Mieter (immer mit CC an die
// Verwaltung, damit die Kommunikation dort vollständig nachvollziehbar ist)
app.post('/api/users/:id/email', auth, adminOnly, async (req, res, next) => {