/** Idempotenter Seed: füllt die Datenbank beim ersten Start mit den * Demo-Daten (50 Wohnungen, 552 Buchungen, Tickets, Dokumente, Threads, * Ankündigungen, Demo-Benutzer, Registrierungscode). Läuft nur, wenn die * users-Tabelle leer ist – bestehende Daten werden nie überschrieben. */ const { pool } = require('./db') const { users, registrationCodes, tickets, documents, threads, announcements, meters, buildings, kosten } = require('./seed-data') const generated = require('../seed/generated.json') async function seed() { const { rows } = await pool.query('SELECT COUNT(*)::int AS n FROM users') if (rows[0].n > 0) return false const client = await pool.connect() try { await client.query('BEGIN') for (const u of generated.units) { // v1.36: Wohnung schlank (kein Mieter/Miete mehr am Objekt); Mieter // werden als eigene Datensätze (Tabelle mieter) angelegt. const status = u.status === 'vermietet' ? 'vermietet' : 'leerstand' await client.query( `INSERT INTO units (id, building, number, floor, size, status) VALUES ($1,$2,$3,$4,$5,$6)`, [u.id, u.building, u.number, u.floor, u.size, status], ) if (u.tenant) { const teile = String(u.tenant).trim().split(/\s+/) const vorname = teile.length > 1 ? teile.slice(0, -1).join(' ') : '' const nachname = teile.length > 1 ? teile[teile.length - 1] : teile[0] await client.query( `INSERT INTO mieter (id, unit_id, name, vorname, kaltmiete, warmmiete, vertragsstatus, mietbeginn_am, erstellt_am, geaendert_am) VALUES ($1,$2,$3,$4,$5,$6,'aktiv','',$7,$7)`, [`mi-${u.id}`, u.id, nachname, vorname, Number(u.coldRent ?? 0), Number(u.warmRent ?? 0), new Date().toLocaleDateString('de-DE')], ) } } for (const t of generated.transactions) { await client.query( `INSERT INTO transactions (id, unit_id, tenant, month, year, amount, type, status, paid_at) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)`, [t.id, t.unitId, t.tenant, t.month, t.year, t.amount, t.type, t.status, t.paidAt ?? null], ) } // Mieter-Namen in Tickets/Threads aus den Seed-Daten nachziehen const mieterVon = Object.fromEntries( generated.units.filter((u) => u.tenant).map((u) => [u.id, u.tenant]), ) for (const t of tickets) { await client.query( `INSERT INTO tickets (id, unit_id, tenant, title, description, category, priority, status, created_at) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)`, [t.id, t.unit_id, mieterVon[t.unit_id] ?? t.tenant, t.title, t.description, t.category, t.priority, t.status, t.created_at], ) } for (const d of documents) { await client.query( `INSERT INTO documents (id, unit_id, title, category, size, date) VALUES ($1,$2,$3,$4,$5,$6)`, [d.id, d.unit_id, d.title, d.category, d.size, d.date], ) } for (const th of threads) { await client.query( `INSERT INTO threads (id, unit_id, tenant, subject, unread_by_admin, unread_by_tenant) VALUES ($1,$2,$3,$4,$5,$6)`, [th.id, th.unit_id, mieterVon[th.unit_id] ?? th.tenant, th.subject, th.unread_by_admin, th.unread_by_tenant], ) for (const m of th.messages) { await client.query( `INSERT INTO messages (id, thread_id, from_role, author, text, at) VALUES ($1,$2,$3,$4,$5,$6)`, [m.id, th.id, m.from_role, m.author, m.text, m.at], ) } } for (const a of announcements) { await client.query( `INSERT INTO announcements (id, title, text, date, tag) VALUES ($1,$2,$3,$4,$5)`, [a.id, a.title, a.text, a.date, a.tag], ) } for (const b of users) { await client.query( `INSERT INTO users (id, benutzername, name, passwort_hash, rolle, email, einheit_id, aktiv, erstellt_am, letzter_login) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10)`, [b.id, b.benutzername, b.name, b.passwort_hash, b.rolle, b.email, b.einheit_id, b.aktiv, b.erstellt_am, b.letzter_login ?? null], ) } for (const c of registrationCodes) { await client.query( `INSERT INTO registration_codes (code, einheit_id, erstellt_am, verwendet_am) VALUES ($1,$2,$3,$4)`, [c.code, c.einheit_id, c.erstellt_am, c.verwendet_am], ) } for (const z of meters) { await client.query( `INSERT INTO meters (id, unit_id, building, nummer, art, stand, stand_am, geaendert_am) VALUES ($1,$2,$3,$4,$5,$6,$7,$8)`, [z.id, z.unit_id, z.building, z.nummer, z.art, z.stand, z.stand_am, z.geaendert_am], ) } await client.query('COMMIT') return true } catch (e) { await client.query('ROLLBACK') throw e } finally { client.release() } } module.exports = { seed } /** Der Demo-Bestand (Einheit hs-7) muss existieren, sonst gehören die * Demo-Nachseeds (Zähler/Häuser/Kosten) nicht in diese Datenbank – z. B. nach * dem Live-Import (v1.21) darf nichts mehr nachgeseedet werden. */ async function demoBestandVorhanden() { const { rows } = await pool.query(`SELECT 1 FROM units WHERE id = 'hs-7'`) return rows.length > 0 } /** Zähler-Demo-Daten: idempotent – läuft bei jedem Start und füllt die * Zähler-Tabelle nur, wenn sie leer ist (auch nachträglich auf bestehenden * Datenbanken, z. B. nach dem v1.9-Update). */ async function seedeZaehler() { const { rows } = await pool.query('SELECT COUNT(*)::int AS n FROM meters') if (rows[0].n > 0 || !(await demoBestandVorhanden())) return false for (const z of meters) { await pool.query( `INSERT INTO meters (id, unit_id, building, nummer, art, stand, stand_am, geaendert_am) VALUES ($1,$2,$3,$4,$5,$6,$7,$8)`, [z.id, z.unit_id, z.building, z.nummer, z.art, z.stand, z.stand_am, z.geaendert_am], ) } return true } module.exports = { seed, seedeZaehler } /** Haus-Demo-Daten: idempotent – füllt die buildings-Tabelle nur, wenn sie * leer ist (auch nachträglich auf bestehenden Datenbanken, z. B. v1.10). */ 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, 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 } module.exports = { seed, seedeZaehler, seedeHaeuser } /** Kosten-Demo-Daten: idempotent – füllt die costs-Tabelle nur, wenn sie * leer ist (auch nachträglich auf bestehenden Datenbanken, z. B. v1.14). */ async function seedeKosten() { const { rows } = await pool.query('SELECT COUNT(*)::int AS n FROM costs') if (rows[0].n > 0 || !(await demoBestandVorhanden())) return false for (const k of kosten) { 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)`, [k.id, k.building, k.unit_id, k.jahr, k.kostenart, k.betrag, k.umlegbar, k.notiz, k.erfasst_am], ) } return true } module.exports = { seed, seedeZaehler, seedeHaeuser, seedeKosten }