v1.26: Haeuser ohne Namensfeld, Adresse in Einzelfelder, Flaechen/Lage, Suche + Listenansicht
This commit is contained in:
@@ -164,6 +164,14 @@ CREATE TABLE IF NOT EXISTS buildings (
|
||||
wohneinheiten INT NOT NULL, -- Anzahl Wohneinheiten (Soll)
|
||||
quadratmeter NUMERIC(9,1) NOT NULL -- Gesamtwohnfläche in m²
|
||||
);
|
||||
-- v1.26: Adresse in Einzelfelder aufgeteilt, Flächen aufgeteilt, optionale Lage
|
||||
ALTER TABLE buildings ADD COLUMN IF NOT EXISTS strasse TEXT NOT NULL DEFAULT '';
|
||||
ALTER TABLE buildings ADD COLUMN IF NOT EXISTS hausnummer TEXT NOT NULL DEFAULT '';
|
||||
ALTER TABLE buildings ADD COLUMN IF NOT EXISTS plz TEXT NOT NULL DEFAULT '';
|
||||
ALTER TABLE buildings ADD COLUMN IF NOT EXISTS ort TEXT NOT NULL DEFAULT '';
|
||||
ALTER TABLE buildings ADD COLUMN IF NOT EXISTS lage TEXT NOT NULL DEFAULT '';
|
||||
ALTER TABLE buildings ADD COLUMN IF NOT EXISTS wohnflaeche NUMERIC(9,1);
|
||||
ALTER TABLE buildings ADD COLUMN IF NOT EXISTS grundstuecksflaeche NUMERIC(9,1);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS costs (
|
||||
id TEXT PRIMARY KEY,
|
||||
|
||||
+77
-20
@@ -185,9 +185,15 @@ const mapMeter = (m) => ({
|
||||
letzteAnomalie: m.letzte_anomalie === undefined ? undefined : Boolean(m.letzte_anomalie),
|
||||
})
|
||||
const mapBuilding = (b) => ({
|
||||
name: b.name, adresse: b.adresse, baujahr: b.baujahr, stockwerke: b.stockwerke,
|
||||
name: b.name, adresse: b.adresse,
|
||||
strasse: b.strasse ?? '', hausnummer: b.hausnummer ?? '',
|
||||
plz: b.plz ?? '', ort: b.ort ?? '', lage: b.lage ?? '',
|
||||
baujahr: b.baujahr, stockwerke: b.stockwerke,
|
||||
etagenhoehe: Number(b.etagenhoehe), wohneinheiten: b.wohneinheiten,
|
||||
quadratmeter: Number(b.quadratmeter),
|
||||
// Fallback: Bestand vor v1.26 trägt die Wohnfläche noch in quadratmeter
|
||||
wohnflaeche: Number(b.wohnflaeche ?? b.quadratmeter),
|
||||
grundstuecksflaeche: b.grundstuecksflaeche === null || b.grundstuecksflaeche === undefined
|
||||
? null : Number(b.grundstuecksflaeche),
|
||||
})
|
||||
const mapKosten = (k) => ({
|
||||
id: k.id, building: k.building, unitId: k.unit_id, jahr: k.jahr,
|
||||
@@ -1047,20 +1053,33 @@ app.post('/api/belege/:id/analysieren', auth, adminOnly, async (req, res, next)
|
||||
})
|
||||
|
||||
// ── Bestand: Häuser & Wohnungen (Verwaltung) ────────────────
|
||||
/** Name/Anzeigeadresse werden aus den Einzelfeldern zusammengesetzt (v1.26). */
|
||||
function hausNameAus(b) {
|
||||
return `${String(b.strasse || '').trim()} ${String(b.hausnummer || '').trim()}`.trim()
|
||||
}
|
||||
function hausAdresseAus(b) {
|
||||
return `${hausNameAus(b)}, ${String(b.plz || '').trim()} ${String(b.ort || '').trim()}`.trim()
|
||||
}
|
||||
|
||||
function pruefeHaus(b) {
|
||||
const fehler = []
|
||||
const baujahr = Number(b.baujahr)
|
||||
const stockwerke = Number(b.stockwerke)
|
||||
const etagenhoehe = Number(b.etagenhoehe)
|
||||
const wohneinheiten = Number(b.wohneinheiten)
|
||||
const quadratmeter = Number(b.quadratmeter)
|
||||
if (!String(b.name || '').trim()) fehler.push('Hausname fehlt.')
|
||||
if (!String(b.adresse || '').trim()) fehler.push('Adresse fehlt.')
|
||||
const wohnflaeche = Number(b.wohnflaeche)
|
||||
const grund = b.grundstuecksflaeche === null || b.grundstuecksflaeche === undefined || b.grundstuecksflaeche === ''
|
||||
? null : Number(b.grundstuecksflaeche)
|
||||
if (!String(b.strasse || '').trim()) fehler.push('Straße fehlt.')
|
||||
if (!String(b.hausnummer || '').trim()) fehler.push('Hausnummer fehlt.')
|
||||
if (!/^\d{5}$/.test(String(b.plz || '').trim())) fehler.push('PLZ ungültig (5 Ziffern).')
|
||||
if (!String(b.ort || '').trim()) fehler.push('Ort fehlt.')
|
||||
if (!Number.isInteger(baujahr) || baujahr < 1500 || baujahr > new Date().getFullYear() + 2) fehler.push('Baujahr ungültig.')
|
||||
if (!Number.isInteger(stockwerke) || stockwerke < 0 || stockwerke > 50) fehler.push('Stockwerke ungültig.')
|
||||
if (!Number.isInteger(stockwerke) || stockwerke < 0 || stockwerke > 50) fehler.push('Anzahl Stockwerke ungültig.')
|
||||
if (!Number.isFinite(etagenhoehe) || etagenhoehe <= 0 || etagenhoehe > 10) fehler.push('Etagenhöhe ungültig.')
|
||||
if (!Number.isInteger(wohneinheiten) || wohneinheiten < 0) fehler.push('Wohneinheiten ungültig.')
|
||||
if (!Number.isFinite(quadratmeter) || quadratmeter <= 0) fehler.push('Quadratmeter ungültig.')
|
||||
if (!Number.isInteger(wohneinheiten) || wohneinheiten < 0) fehler.push('Anzahl Wohneinheiten ungültig.')
|
||||
if (!Number.isFinite(wohnflaeche) || wohnflaeche <= 0) fehler.push('Wohnfläche ungültig.')
|
||||
if (grund !== null && (!Number.isFinite(grund) || grund <= 0)) fehler.push('Grundstücksfläche ungültig.')
|
||||
return fehler
|
||||
}
|
||||
|
||||
@@ -1069,14 +1088,18 @@ app.post('/api/buildings', auth, adminOnly, async (req, res, next) => {
|
||||
const b = req.body || {}
|
||||
const fehler = pruefeHaus(b)
|
||||
if (fehler.length) return res.status(400).json({ fehler: fehler.join(' ') })
|
||||
const name = String(b.name).trim()
|
||||
const name = hausNameAus(b)
|
||||
const vorhanden = await pool.query('SELECT 1 FROM buildings WHERE name = $1', [name])
|
||||
if (vorhanden.rows.length) return res.status(400).json({ fehler: 'Dieses Haus existiert bereits.' })
|
||||
if (vorhanden.rows.length) return res.status(400).json({ fehler: 'Ein Haus mit dieser Straße und Hausnummer existiert bereits.' })
|
||||
const grund = b.grundstuecksflaeche ? Number(b.grundstuecksflaeche) : null
|
||||
const { rows } = await pool.query(
|
||||
`INSERT INTO buildings (name, adresse, baujahr, stockwerke, etagenhoehe, wohneinheiten, quadratmeter)
|
||||
VALUES ($1,$2,$3,$4,$5,$6,$7) RETURNING *`,
|
||||
[name, String(b.adresse).trim(), Number(b.baujahr), Number(b.stockwerke),
|
||||
Number(b.etagenhoehe), Number(b.wohneinheiten), Number(b.quadratmeter)],
|
||||
`INSERT INTO buildings (name, adresse, strasse, hausnummer, plz, ort, lage,
|
||||
baujahr, stockwerke, etagenhoehe, wohneinheiten, wohnflaeche, grundstuecksflaeche)
|
||||
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13) RETURNING *`,
|
||||
[name, hausAdresseAus(b), String(b.strasse).trim(), String(b.hausnummer).trim(),
|
||||
String(b.plz).trim(), String(b.ort).trim(), String(b.lage || '').trim(),
|
||||
Number(b.baujahr), Number(b.stockwerke), Number(b.etagenhoehe),
|
||||
Number(b.wohneinheiten), Number(b.wohnflaeche), grund],
|
||||
)
|
||||
res.json({ building: mapBuilding(rows[0]) })
|
||||
} catch (e) { next(e) }
|
||||
@@ -1090,19 +1113,25 @@ app.patch('/api/buildings/:name', auth, adminOnly, async (req, res, next) => {
|
||||
const b = { ...alt, ...(req.body || {}) }
|
||||
const fehler = pruefeHaus(b)
|
||||
if (fehler.length) return res.status(400).json({ fehler: fehler.join(' ') })
|
||||
const neuName = String(b.name).trim()
|
||||
// Name folgt immer Straße + Hausnummer; die Anzeigeadresse wird neu zusammengesetzt
|
||||
const neuName = hausNameAus(b)
|
||||
if (neuName !== alt.name) {
|
||||
const vorhanden = await pool.query('SELECT 1 FROM buildings WHERE name = $1', [neuName])
|
||||
if (vorhanden.rows.length) return res.status(400).json({ fehler: 'Dieser Hausname ist bereits vergeben.' })
|
||||
if (vorhanden.rows.length) return res.status(400).json({ fehler: 'Ein Haus mit dieser Straße und Hausnummer existiert bereits.' })
|
||||
}
|
||||
const grund = b.grundstuecksflaeche ? Number(b.grundstuecksflaeche) : null
|
||||
const client = await pool.connect()
|
||||
try {
|
||||
await client.query('BEGIN')
|
||||
await client.query(
|
||||
`UPDATE buildings SET name = $1, adresse = $2, baujahr = $3, stockwerke = $4,
|
||||
etagenhoehe = $5, wohneinheiten = $6, quadratmeter = $7 WHERE name = $8`,
|
||||
[neuName, String(b.adresse).trim(), Number(b.baujahr), Number(b.stockwerke),
|
||||
Number(b.etagenhoehe), Number(b.wohneinheiten), Number(b.quadratmeter), req.params.name],
|
||||
`UPDATE buildings SET name = $1, adresse = $2, strasse = $3, hausnummer = $4, plz = $5, ort = $6, lage = $7,
|
||||
baujahr = $8, stockwerke = $9, etagenhoehe = $10, wohneinheiten = $11,
|
||||
wohnflaeche = $12, grundstuecksflaeche = $13
|
||||
WHERE name = $14`,
|
||||
[neuName, hausAdresseAus(b), String(b.strasse).trim(), String(b.hausnummer).trim(),
|
||||
String(b.plz).trim(), String(b.ort).trim(), String(b.lage || '').trim(),
|
||||
Number(b.baujahr), Number(b.stockwerke), Number(b.etagenhoehe), Number(b.wohneinheiten),
|
||||
Number(b.wohnflaeche), grund, req.params.name],
|
||||
)
|
||||
// Haus umbenannt → Bezüge in Wohnungen und Zählern mitziehen
|
||||
if (neuName !== alt.name) {
|
||||
@@ -1471,6 +1500,33 @@ app.use((err, _req, res, _next) => {
|
||||
})
|
||||
|
||||
// ── Start ────────────────────────────────────────────────────
|
||||
/** Zerlegt „Straße Nr., PLZ Ort" in Einzelfelder (Best-Effort-Migration v1.26). */
|
||||
function adresseZerlegen(adresse) {
|
||||
const teile = { strasse: '', hausnummer: '', plz: '', ort: '' }
|
||||
const linksRechts = String(adresse).split(',')
|
||||
const rechts = (linksRechts[1] || '').trim()
|
||||
const plzMatch = /(\d{5})\s*(.*)/.exec(rechts)
|
||||
if (plzMatch) { teile.plz = plzMatch[1]; teile.ort = plzMatch[2].trim() }
|
||||
const links = (linksRechts[0] || '').trim().split(/\s+/)
|
||||
teile.hausnummer = links.length > 1 ? links.pop() : ''
|
||||
teile.strasse = links.join(' ')
|
||||
return teile
|
||||
}
|
||||
|
||||
async function haeuserFelderMigrieren() {
|
||||
const { rows } = await pool.query("SELECT * FROM buildings WHERE strasse = '' AND adresse <> ''")
|
||||
for (const b of rows) {
|
||||
const t = adresseZerlegen(b.adresse)
|
||||
await pool.query(
|
||||
'UPDATE buildings SET strasse = $1, hausnummer = $2, plz = $3, ort = $4 WHERE name = $5',
|
||||
[t.strasse, t.hausnummer, t.plz, t.ort, b.name],
|
||||
)
|
||||
}
|
||||
// Wohnfläche aus Alt-Feld quadratmeter nachziehen
|
||||
await pool.query('UPDATE buildings SET wohnflaeche = quadratmeter WHERE wohnflaeche IS NULL')
|
||||
if (rows.length) console.log(`[api] Häuser: ${rows.length} Adresse(n) in Einzelfelder zerlegt`)
|
||||
}
|
||||
|
||||
async function warteAufDatenbank() {
|
||||
let letzterFehler = null
|
||||
for (let versuch = 1; versuch <= 30; versuch++) {
|
||||
@@ -1498,6 +1554,7 @@ async function start() {
|
||||
if (haeuserGeseedet) console.log('[api] Haus-Demo-Daten eingespielt')
|
||||
const kostenGeseedet = await seedeKosten()
|
||||
if (kostenGeseedet) console.log('[api] Kosten-Demo-Daten eingespielt')
|
||||
await haeuserFelderMigrieren()
|
||||
setInterval(sessionsAufraeumen, 60 * 60 * 1000).unref()
|
||||
app.listen(PORT, () => console.log(`[api] mp-api lauscht auf :${PORT}`))
|
||||
}
|
||||
|
||||
+8
-3
@@ -139,10 +139,15 @@ async function seedeHaeuser() {
|
||||
const { rows } = await pool.query('SELECT COUNT(*)::int AS n FROM buildings')
|
||||
if (rows[0].n > 0 || !(await demoBestandVorhanden())) return false
|
||||
for (const h of buildings) {
|
||||
const teile = String(h.adresse).trim().split(/\s+/)
|
||||
const hausnummer = teile.length > 1 ? teile.pop() : ''
|
||||
const strasse = teile.join(' ')
|
||||
await pool.query(
|
||||
`INSERT INTO buildings (name, adresse, baujahr, stockwerke, etagenhoehe, wohneinheiten, quadratmeter)
|
||||
VALUES ($1,$2,$3,$4,$5,$6,$7)`,
|
||||
[h.name, h.adresse, h.baujahr, h.stockwerke, h.etagenhoehe, h.wohneinheiten, h.quadratmeter],
|
||||
`INSERT INTO buildings (name, adresse, strasse, hausnummer, plz, ort, lage,
|
||||
baujahr, stockwerke, etagenhoehe, wohneinheiten, quadratmeter, wohnflaeche)
|
||||
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13)`,
|
||||
[h.name, h.adresse, strasse, hausnummer, '', '', '',
|
||||
h.baujahr, h.stockwerke, h.etagenhoehe, h.wohneinheiten, h.quadratmeter, h.quadratmeter],
|
||||
)
|
||||
}
|
||||
return true
|
||||
|
||||
Reference in New Issue
Block a user