diff --git a/kimi.md b/kimi.md
index 3c384ff..86d9f77 100644
--- a/kimi.md
+++ b/kimi.md
@@ -189,6 +189,23 @@ tar -czf mp-dist.tar.gz -C dist .
## 8. Verlauf / offene Punkte
+**Erledigt 04.10. (v1.33):** Einträge in allen Listen löschbar. Neue
+adminOnly-DELETE-Endpunkte in server/src/index.js: /api/tickets/:id
+(kaskadiert threads + messages), /api/meters/:id (kaskadiert meter_readings),
+/api/belege/:id, /api/buildings/:name und /api/units/:id – letztere zwei
+verweigern mit 400 + deutscher Blocker-Liste (Haus: Wohnungen/Zähler/Kosten;
+Wohnung: Zähler/Dokumente/Kosten/Mieter), /api/users/:id (eigenes Konto und
+letzten aktiven Verwalter geschützt, sessions vorher gelöscht). Store:
+deleteTicket, deleteZaehler, deleteHaus, deleteWohnung, deleteBeleg,
+loescheBenutzer (apiDelete + lokaler State-Filter, threads bei Ticket-Delete
+mitgefiltert). Frontend: rote Trash2-Löschbuttons mit window.confirm +
+alert(err) in Bestand (Haus Karte+Liste, Wohnungszeile), Zaehler (Karte+Liste),
+Kosten BelegeListe, Tickets (admin, warnt vor Mitlöschung der Kommunikation),
+BenutzerVerwaltung (eigenes Konto deaktiviert); Buttons nur bei role=admin,
+in Mieter-sichtbaren Seiten (Zaehler, Tickets) zusätzlich gegated. E2E: 400-
+Blocker beim Haus mit Wohnung/Zähler, Kaskaden-Löschung bis Haus weg,
+Ticket anlegen+löschen, fremden Testbenutzer löschen, eigenes Konto → 400.
+
**Erledigt 04.10. (v1.32):** Deutsche Datumsformate in allen Modulen.
NEU src/lib/format.ts: fmtDatum (ISO YYYY-MM-DD → TT.MM.JJJJ, deutsche Werte
durchreichen) + fmtDatumZeit (mit Uhrzeit) – eingesetzt in Zaehler (Karte,
diff --git a/server/src/index.js b/server/src/index.js
index e85db40..676d580 100644
--- a/server/src/index.js
+++ b/server/src/index.js
@@ -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) => {
diff --git a/src/pages/BenutzerVerwaltung.tsx b/src/pages/BenutzerVerwaltung.tsx
index a4443a9..1865e6e 100644
--- a/src/pages/BenutzerVerwaltung.tsx
+++ b/src/pages/BenutzerVerwaltung.tsx
@@ -229,7 +229,7 @@ function EmailDialog({ benutzer: b, einheitLabel }: { benutzer: Benutzer; einhei
}
export default function BenutzerVerwaltung() {
- const { benutzer, codes, units, setAktiv, setPasswort, weiseEinheitZu, setzeBenutzerEmail, generiereCodes } = useStore()
+ const { benutzer, codes, units, setAktiv, setPasswort, weiseEinheitZu, setzeBenutzerEmail, generiereCodes, loescheBenutzer, me } = useStore()
const [suche, setSuche] = useState('')
const [gruppe, setGruppe] = useState<'alle' | BenutzerRolle>('alle')
const [anzahl, setAnzahl] = useState(5)
@@ -256,6 +256,12 @@ export default function BenutzerVerwaltung() {
await setPasswort(id, '0815')
}
+ const benutzerLoeschen = async (id: string, name: string) => {
+ if (!window.confirm(`Benutzer „${name}" wirklich löschen? Sitzungen werden entwertet, die Aktion kann nicht rückgängig gemacht werden.`)) return
+ const fehler = await loescheBenutzer(id)
+ if (fehler) window.alert(fehler)
+ }
+
const kopieren = (text: string) => {
navigator.clipboard?.writeText(text).catch(() => {})
setKopiert(text)
@@ -474,6 +480,15 @@ export default function BenutzerVerwaltung() {
>
{b.aktiv ? 'Deaktivieren' : 'Aktivieren'}
+
)
diff --git a/src/pages/Bestand.tsx b/src/pages/Bestand.tsx
index d968ef8..8fcc20b 100644
--- a/src/pages/Bestand.tsx
+++ b/src/pages/Bestand.tsx
@@ -1,7 +1,7 @@
import { Fragment, useMemo, useState } from 'react'
import {
Building2, Home, MapPin, CalendarDays, ArrowUpDown, LayoutGrid, Ruler, Pencil,
- Plus, Users, DoorOpen, Search, List, ChevronDown, ChevronRight, Flame,
+ Plus, Users, DoorOpen, Search, List, ChevronDown, ChevronRight, Flame, Trash2,
} from 'lucide-react'
import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge'
@@ -321,6 +321,12 @@ function sortiereWohnungen(liste: Unit[]): Unit[] {
/** Wohnungszeile, in Karten- und Listenansicht gleich. */
function WohnungsZeile({ u, gebaeude, festesHaus }: { u: Unit; gebaeude: string[]; festesHaus: string }) {
+ const { deleteWohnung, role } = useStore()
+ const loesche = async () => {
+ if (!window.confirm(`Wohnung ${u.building}, Whg. ${u.number} wirklich löschen?`)) return
+ const fehler = await deleteWohnung(u.id)
+ if (fehler) window.alert(fehler)
+ }
return (
@@ -344,6 +350,17 @@ function WohnungsZeile({ u, gebaeude, festesHaus }: { u: Unit; gebaeude: string[
{fmtEUR.format(u.warmRent)}
+ {role === 'admin' && (
+
+ )}
@@ -378,11 +395,18 @@ function BelegungsFortschritt({ angelegt, soll }: { angelegt: number; soll: numb
}
export default function Bestand() {
- const { buildings, units } = useStore()
+ const { buildings, units, deleteHaus, role } = useStore()
const [suche, setSuche] = useState('')
const [ansicht, setAnsicht] = useState<'karten' | 'liste'>('liste')
const [offen, setOffen] = useState(null)
+ const loescheHaus = async (name: string) => {
+ if (!window.confirm(`Haus „${name}" wirklich löschen?`)) return
+ const fehler = await deleteHaus(name)
+ if (fehler) window.alert(fehler)
+ if (offen === name) setOffen(null)
+ }
+
const gebaeudeNamen = useMemo(
() => buildings.map((b) => b.name).sort(),
[buildings],
@@ -509,7 +533,21 @@ export default function Bestand() {
-
+
+
+ {role === 'admin' && (
+
+ )}
+
@@ -618,7 +656,21 @@ export default function Bestand() {
e.stopPropagation()}>
-
+
+
+ {role === 'admin' && (
+
+ )}
+
|
{aufgeklappt && (
diff --git a/src/pages/Kosten.tsx b/src/pages/Kosten.tsx
index ff7a299..94591cc 100644
--- a/src/pages/Kosten.tsx
+++ b/src/pages/Kosten.tsx
@@ -316,10 +316,16 @@ function AbrechnungDialog() {
/** Eingegangene SparkReceipt-Belege (nur Verwaltung). */
function BelegeListe() {
- const { belege, setBelegZugeordnet, analysiereBeleg, units } = useStore()
+ const { belege, setBelegZugeordnet, analysiereBeleg, deleteBeleg, units } = useStore()
const [nurOffene, setNurOffene] = useState(false)
const [analysiere, setAnalysiere] = useState(null)
+ const loesche = async (b: Beleg) => {
+ if (!window.confirm(`Beleg „${b.haendler || b.externeId}" wirklich löschen?`)) return
+ const fehler = await deleteBeleg(b.id)
+ if (fehler) window.alert(fehler)
+ }
+
const zeilen = useMemo(
() => (nurOffene ? belege.filter((b) => !b.zugeordnet) : belege),
[belege, nurOffene],
@@ -477,6 +483,15 @@ function BelegeListe() {
Beleg
)}
+
diff --git a/src/pages/Tickets.tsx b/src/pages/Tickets.tsx
index 8d2f74e..6050ef5 100644
--- a/src/pages/Tickets.tsx
+++ b/src/pages/Tickets.tsx
@@ -1,6 +1,6 @@
import { useMemo, useState } from 'react'
import { Link } from 'react-router'
-import { Plus, Wrench, CalendarDays, MapPin, MessagesSquare } from 'lucide-react'
+import { Plus, Wrench, CalendarDays, MapPin, MessagesSquare, Trash2 } from 'lucide-react'
import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge'
import {
@@ -143,9 +143,15 @@ function NewTicketDialog() {
}
export default function Tickets() {
- const { tickets, units, role, setTicketStatus, meineEinheit, threads } = useStore()
+ const { tickets, units, role, setTicketStatus, meineEinheit, threads, deleteTicket } = useStore()
const [filter, setFilter] = useState<'alle' | TicketStatus>('alle')
+ const loescheTicket = async (id: string, titel: string) => {
+ if (!window.confirm(`Schadensmeldung „${titel}" wirklich löschen? Die zugehörige Kommunikation wird mitgelöscht.`)) return
+ const fehler = await deleteTicket(id)
+ if (fehler) window.alert(fehler)
+ }
+
const threadFuerTicket = (ticketId: string) => threads.find((th) => th.ticketId === ticketId)
const visible = useMemo(
@@ -242,6 +248,17 @@ export default function Tickets() {
) : (
{t.status}
)}
+ {role === 'admin' && (
+
+ )}
diff --git a/src/pages/Zaehler.tsx b/src/pages/Zaehler.tsx
index a1ed7f0..394c427 100644
--- a/src/pages/Zaehler.tsx
+++ b/src/pages/Zaehler.tsx
@@ -388,7 +388,7 @@ function StandEintragenDialog({ meter }: { meter: Meter }) {
}
export default function Zaehler() {
- const { meters, units, role } = useStore()
+ const { meters, units, role, deleteZaehler } = useStore()
const [filterHaus, setFilterHaus] = useState('alle')
const [filterArt, setFilterArt] = useState<'alle' | ZaehlerArt>('alle')
const [suche, setSuche] = useState('')
@@ -405,6 +405,12 @@ export default function Zaehler() {
return u ? `Whg. ${u.number}` : m.unitId
}
+ const loescheZaehler = async (m: Meter) => {
+ if (!window.confirm(`Zähler ${m.nummer} (${m.art}) wirklich löschen? Der gesamte Verlauf wird mitgelöscht.`)) return
+ const fehler = await deleteZaehler(m.id)
+ if (fehler) window.alert(fehler)
+ }
+
const visible = useMemo(() => {
const q = suche.trim().toLowerCase()
return meters.filter(
@@ -514,7 +520,21 @@ export default function Zaehler() {
)}
- {role === 'admin' && }
+ {role === 'admin' && (
+
+
+
+
+ )}
{fmtStand(m.stand)}
@@ -587,6 +607,18 @@ export default function Zaehler() {
{role === 'admin' && }
+ {role === 'admin' && (
+
+ )}
diff --git a/src/store/store.tsx b/src/store/store.tsx
index e664b64..61f8691 100644
--- a/src/store/store.tsx
+++ b/src/store/store.tsx
@@ -99,6 +99,8 @@ interface Store extends Bootstrap {
) => Promise
addTicket: (t: { title: string; description: string; category: string; priority: string; unitId?: string }) => Promise
setTicketStatus: (id: string, status: TicketStatus) => Promise
+ /** Schadensmeldung inkl. verknüpfter Kommunikation löschen (Verwaltung, v1.33) */
+ deleteTicket: (id: string) => Promise
newThread: (subject: string, text: string, unitId?: string) => Promise
addMessage: (threadId: string, text: string) => Promise
markThreadRead: (threadId: string) => void
@@ -111,6 +113,8 @@ interface Store extends Bootstrap {
einheitId: string | null,
) => Promise
setAktiv: (id: string, aktiv: boolean) => Promise
+ /** Benutzer endgültig löschen (Verwaltung, v1.33; eigenes Konto/geschützter Letzter ausgenommen) */
+ loescheBenutzer: (id: string) => Promise
setPasswort: (id: string, passwort: string) => Promise
weiseEinheitZu: (id: string, einheitId: string | null) => Promise
/** Direkt-E-Mail der Verwaltung an einen Mieter (immer mit CC an die Verwaltung, v1.22) */
@@ -131,6 +135,8 @@ interface Store extends Bootstrap {
id: string,
z: Partial<{ nummer: string; art: ZaehlerArt; stand: number; unitId: string | null; building: string }>,
) => Promise
+ /** Zähler inkl. Verlauf löschen (Verwaltung, v1.33) */
+ deleteZaehler: (id: string) => Promise
// ── Zählerverlauf (v1.25) ─────────────────────────────────
/** Verlauf eines Zählers laden (älteste zuerst) */
zaehlerVerlauf: (meterId: string) => Promise
@@ -153,6 +159,10 @@ interface Store extends Bootstrap {
heizungsart: string
}) => Promise
updateWohnung: (id: string, u: Partial) => Promise
+ /** Haus löschen (nur ohne Wohnungen/Zähler/Kosten; v1.33) */
+ deleteHaus: (name: string) => Promise
+ /** Wohnung löschen (nur ohne Referenzen; v1.33) */
+ deleteWohnung: (id: string) => Promise
// ── Dokumente (Verwaltung pflegt, Mieter lesen) ─────────
addDokument: (d: {
title: string
@@ -185,6 +195,8 @@ interface Store extends Bootstrap {
deleteKosten: (id: string) => Promise
// ── Belege (SparkReceipt-Import; Verwaltung ordnet zu) ──
setBelegZugeordnet: (id: string, zugeordnet: boolean) => Promise
+ /** Beleg endgültig löschen (Verwaltung, v1.33) */
+ deleteBeleg: (id: string) => Promise
// ── Nebenkostenabrechnung als PDF (v1.23) ─────────────────
erzeugeAbrechnung: (unitId: string, jahr: number) => Promise<{
fehler: string | null
@@ -310,6 +322,19 @@ export function StoreProvider({ children }: { children: ReactNode }) {
const { ticket } = await apiPatch<{ ticket: Ticket }>(`/api/tickets/${id}`, { status })
setDaten((d) => ({ ...d, tickets: d.tickets.map((t) => (t.id === id ? ticket : t)) }))
},
+ async deleteTicket(id) {
+ try {
+ await apiDelete(`/api/tickets/${id}`)
+ setDaten((d) => ({
+ ...d,
+ tickets: d.tickets.filter((t) => t.id !== id),
+ threads: d.threads.filter((t) => t.ticketId !== id),
+ }))
+ return null
+ } catch (e) {
+ return fehlerNachricht(e)
+ }
+ },
// ── Nachrichten ─────────────────────────────────────────
async newThread(subject, text, unitId) {
try {
@@ -360,6 +385,15 @@ export function StoreProvider({ children }: { children: ReactNode }) {
const { benutzer } = await apiPatch<{ benutzer: Benutzer }>(`/api/users/${id}`, { aktiv })
setDaten((d) => ({ ...d, benutzer: d.benutzer.map((b) => (b.id === id ? benutzer : b)) }))
},
+ async loescheBenutzer(id) {
+ try {
+ await apiDelete(`/api/users/${id}`)
+ setDaten((d) => ({ ...d, benutzer: d.benutzer.filter((b) => b.id !== id) }))
+ return null
+ } catch (e) {
+ return fehlerNachricht(e)
+ }
+ },
async setPasswort(id, passwort) {
const { benutzer } = await apiPatch<{ benutzer: Benutzer }>(`/api/users/${id}`, {
passwortHash: await sha256(passwort),
@@ -411,6 +445,15 @@ export function StoreProvider({ children }: { children: ReactNode }) {
return fehlerNachricht(e)
}
},
+ async deleteZaehler(id) {
+ try {
+ await apiDelete(`/api/meters/${id}`)
+ setDaten((d) => ({ ...d, meters: d.meters.filter((m) => m.id !== id) }))
+ return null
+ } catch (e) {
+ return fehlerNachricht(e)
+ }
+ },
// ── Zählerverlauf (v1.25) ───────────────────────────────
async zaehlerVerlauf(meterId) {
const { readings } = await apiGet<{ readings: MeterReading[] }>(`/api/meters/${meterId}/readings`)
@@ -454,6 +497,15 @@ export function StoreProvider({ children }: { children: ReactNode }) {
return fehlerNachricht(e)
}
},
+ async deleteHaus(name) {
+ try {
+ await apiDelete(`/api/buildings/${encodeURIComponent(name)}`)
+ setDaten((d) => ({ ...d, buildings: d.buildings.filter((b) => b.name !== name) }))
+ return null
+ } catch (e) {
+ return fehlerNachricht(e)
+ }
+ },
async addWohnung(u) {
try {
const { unit } = await apiPost<{ unit: Unit }>('/api/units', u)
@@ -472,6 +524,15 @@ export function StoreProvider({ children }: { children: ReactNode }) {
return fehlerNachricht(e)
}
},
+ async deleteWohnung(id) {
+ try {
+ await apiDelete(`/api/units/${id}`)
+ setDaten((d) => ({ ...d, units: d.units.filter((x) => x.id !== id) }))
+ return null
+ } catch (e) {
+ return fehlerNachricht(e)
+ }
+ },
// ── Dokumente ───────────────────────────────────────────
async addDokument(d) {
try {
@@ -555,6 +616,15 @@ export function StoreProvider({ children }: { children: ReactNode }) {
return fehlerNachricht(e)
}
},
+ async deleteBeleg(id) {
+ try {
+ await apiDelete(`/api/belege/${id}`)
+ setDaten((x) => ({ ...x, belege: x.belege.filter((b) => b.id !== id) }))
+ return null
+ } catch (e) {
+ return fehlerNachricht(e)
+ }
+ },
// ── Nebenkostenabrechnung (v1.23) ─────────────────────────
async erzeugeAbrechnung(unitId, jahr) {
try {