v1.14: Steuermodul - umlegbare und nicht umlegbare Kosten nach Haus und Wohnung
This commit is contained in:
+91
-2
@@ -12,7 +12,7 @@ const fs = require('fs')
|
||||
const path = require('path')
|
||||
const multer = require('multer')
|
||||
const { pool, init, sessionsAufraeumen } = require('./db')
|
||||
const { seed, seedeZaehler, seedeHaeuser } = require('./seed')
|
||||
const { seed, seedeZaehler, seedeHaeuser, seedeKosten } = require('./seed')
|
||||
const mail = require('./mail')
|
||||
|
||||
const PORT = Number(process.env.PORT || 3000)
|
||||
@@ -177,6 +177,11 @@ const mapBuilding = (b) => ({
|
||||
etagenhoehe: Number(b.etagenhoehe), wohneinheiten: b.wohneinheiten,
|
||||
quadratmeter: Number(b.quadratmeter),
|
||||
})
|
||||
const mapKosten = (k) => ({
|
||||
id: k.id, building: k.building, unitId: k.unit_id, jahr: k.jahr,
|
||||
kostenart: k.kostenart, betrag: Number(k.betrag), umlegbar: k.umlegbar,
|
||||
notiz: k.notiz, erfasstAm: k.erfasst_am,
|
||||
})
|
||||
|
||||
const deutschesDatum = () => new Date().toLocaleDateString('de-DE')
|
||||
const deutschesDatumZeit = () => new Date().toLocaleString('de-DE')
|
||||
@@ -280,7 +285,7 @@ app.get('/api/bootstrap', auth, async (req, res, next) => {
|
||||
try {
|
||||
const istAdmin = req.user.rolle === 'verwaltung'
|
||||
const einheit = req.user.einheit_id
|
||||
const [units, transactions, documents, tickets, threads, messages, announcements, users, codes, meters, buildings] =
|
||||
const [units, transactions, documents, tickets, threads, messages, announcements, users, codes, meters, buildings, costs] =
|
||||
await Promise.all([
|
||||
pool.query('SELECT * FROM units ORDER BY id'),
|
||||
istAdmin
|
||||
@@ -311,6 +316,9 @@ app.get('/api/bootstrap', auth, async (req, res, next) => {
|
||||
istAdmin
|
||||
? pool.query('SELECT * FROM buildings ORDER BY name')
|
||||
: { rows: [] },
|
||||
istAdmin
|
||||
? pool.query('SELECT * FROM costs ORDER BY jahr DESC, building, kostenart')
|
||||
: pool.query('SELECT * FROM costs WHERE unit_id = $1 ORDER BY jahr DESC, kostenart', [einheit]),
|
||||
])
|
||||
// Threads mit ihren Nachrichten zusammenführen
|
||||
const nachrichtenProThread = {}
|
||||
@@ -332,6 +340,7 @@ app.get('/api/bootstrap', auth, async (req, res, next) => {
|
||||
codes: codes.rows.map(mapCode),
|
||||
meters: meters.rows.map(mapMeter),
|
||||
buildings: buildings.rows.map(mapBuilding),
|
||||
kosten: costs.rows.map(mapKosten),
|
||||
})
|
||||
} catch (e) { next(e) }
|
||||
})
|
||||
@@ -625,6 +634,84 @@ app.get('/api/documents/:id/download', auth, async (req, res, next) => {
|
||||
} catch (e) { next(e) }
|
||||
})
|
||||
|
||||
// ── Steuer & Kosten (Verwaltung) ─────────────────────────────
|
||||
const KOSTEN_ARTEN = [
|
||||
'Wasser/Abwasser', 'Müllabfuhr', 'Straßenreinigung/Winterdienst', 'Gartenpflege',
|
||||
'Hausreinigung', 'Hauswart', 'Aufzug', 'Versicherung', 'Grundsteuer',
|
||||
'Heizung/Warmwasser', 'Kabelanschluss', 'Verwaltung', 'Instandhaltung',
|
||||
'Mietausfall', 'Sonstiges',
|
||||
]
|
||||
|
||||
async function kostenGueltig(b, res) {
|
||||
const kostenart = String(b.kostenart || '').trim()
|
||||
if (!KOSTEN_ARTEN.includes(kostenart)) { res.status(400).json({ fehler: 'Ungültige Kostenart.' }); return null }
|
||||
const jahr = Number(b.jahr)
|
||||
if (!Number.isInteger(jahr) || jahr < 2000 || jahr > new Date().getFullYear() + 1) { res.status(400).json({ fehler: 'Jahr ungültig.' }); return null }
|
||||
const betrag = Number(b.betrag)
|
||||
if (!Number.isFinite(betrag) || betrag < 0) { res.status(400).json({ fehler: 'Betrag ungültig.' }); return null }
|
||||
// Haus folgt der Wohnung: unitId gesetzt → Wohnung muss existieren, Haus wird übernommen
|
||||
let einheitId = b.unitId !== undefined ? (b.unitId || null) : undefined
|
||||
let haus = String(b.building || '').trim()
|
||||
if (einheitId) {
|
||||
const { rows: u } = await pool.query('SELECT building FROM units WHERE id = $1', [einheitId])
|
||||
if (!u.length) { res.status(400).json({ fehler: 'Wohnung unbekannt.' }); return null }
|
||||
haus = u[0].building
|
||||
}
|
||||
if (!haus) { res.status(400).json({ fehler: 'Haus fehlt.' }); return null }
|
||||
const { rows: h } = await pool.query('SELECT name FROM buildings WHERE name = $1', [haus])
|
||||
if (!h.length) { res.status(400).json({ fehler: 'Haus unbekannt.' }); return null }
|
||||
return { kostenart, jahr, betrag, einheitId, haus, notiz: String(b.notiz || '').trim() }
|
||||
}
|
||||
|
||||
app.post('/api/costs', auth, adminOnly, async (req, res, next) => {
|
||||
try {
|
||||
const g = await kostenGueltig(req.body || {}, res)
|
||||
if (!g) return
|
||||
const { rows } = await pool.query(
|
||||
`INSERT INTO costs (id, building, unit_id, jahr, kostenart, betrag, umlegbar, notiz, erfasst_am)
|
||||
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9) RETURNING *`,
|
||||
[`k-${Date.now()}`, g.haus, g.einheitId ?? null, g.jahr, g.kostenart, g.betrag,
|
||||
req.body.umlegbar !== false, g.notiz, deutschesDatumZeit()],
|
||||
)
|
||||
res.json({ kosten: mapKosten(rows[0]) })
|
||||
} catch (e) { next(e) }
|
||||
})
|
||||
|
||||
app.patch('/api/costs/:id', auth, adminOnly, async (req, res, next) => {
|
||||
try {
|
||||
const { rows } = await pool.query('SELECT * FROM costs WHERE id = $1', [req.params.id])
|
||||
if (!rows.length) return res.status(404).json({ fehler: 'Kostenposition nicht gefunden.' })
|
||||
const k = rows[0]
|
||||
const b = req.body || {}
|
||||
const kandidat = {
|
||||
kostenart: b.kostenart !== undefined ? b.kostenart : k.kostenart,
|
||||
jahr: b.jahr !== undefined ? b.jahr : k.jahr,
|
||||
betrag: b.betrag !== undefined ? b.betrag : k.betrag,
|
||||
unitId: b.unitId !== undefined ? b.unitId : k.unit_id,
|
||||
building: b.building !== undefined ? b.building : k.building,
|
||||
notiz: b.notiz !== undefined ? b.notiz : k.notiz,
|
||||
}
|
||||
const g = await kostenGueltig(kandidat, res)
|
||||
if (!g) return
|
||||
const einheitId = g.einheitId === undefined ? k.unit_id : g.einheitId
|
||||
const { rows: aktualisiert } = await pool.query(
|
||||
`UPDATE costs SET building = $1, unit_id = $2, jahr = $3, kostenart = $4, betrag = $5, umlegbar = $6, notiz = $7
|
||||
WHERE id = $8 RETURNING *`,
|
||||
[g.haus, einheitId, g.jahr, g.kostenart, g.betrag,
|
||||
b.umlegbar !== undefined ? Boolean(b.umlegbar) : k.umlegbar, g.notiz, req.params.id],
|
||||
)
|
||||
res.json({ kosten: mapKosten(aktualisiert[0]) })
|
||||
} catch (e) { next(e) }
|
||||
})
|
||||
|
||||
app.delete('/api/costs/:id', auth, adminOnly, async (req, res, next) => {
|
||||
try {
|
||||
const { rowCount } = await pool.query('DELETE FROM costs WHERE id = $1', [req.params.id])
|
||||
if (!rowCount) return res.status(404).json({ fehler: 'Kostenposition nicht gefunden.' })
|
||||
res.json({ ok: true })
|
||||
} catch (e) { next(e) }
|
||||
})
|
||||
|
||||
// ── Bestand: Häuser & Wohnungen (Verwaltung) ────────────────
|
||||
function pruefeHaus(b) {
|
||||
const fehler = []
|
||||
@@ -1006,6 +1093,8 @@ async function start() {
|
||||
if (zaehlerGeseedet) console.log('[api] Zähler-Demo-Daten eingespielt')
|
||||
const haeuserGeseedet = await seedeHaeuser()
|
||||
if (haeuserGeseedet) console.log('[api] Haus-Demo-Daten eingespielt')
|
||||
const kostenGeseedet = await seedeKosten()
|
||||
if (kostenGeseedet) console.log('[api] Kosten-Demo-Daten eingespielt')
|
||||
setInterval(sessionsAufraeumen, 60 * 60 * 1000).unref()
|
||||
app.listen(PORT, () => console.log(`[api] mp-api lauscht auf :${PORT}`))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user