/** * Seed: EIU-Ablagestruktur aus der Excel des Auftraggebers + Demo-Projekt. * * Der Katalog (FilingNode / DocumentRequirement) wird bei jedem Start * idempotent aktualisiert. Demo-Daten werden nur angelegt, wenn die * Datenbank noch leer ist. */ import { PrismaClient, type Akte, type Medium, type Uebergabezeitpunkt } from "@prisma/client"; import bcrypt from "bcryptjs"; import { readFileSync } from "fs"; import path from "path"; import { slotsErzeugen } from "../src/lib/projekte"; const prisma = new PrismaClient(); type RohKnoten = { code: string; parent: string | null; level: number; title: string; ril: string; akte: string }; type RohItem = { seq: number; folder: string; akte: string; title: string; bullet: boolean; ril: string; netz: boolean; sus: boolean; fbNetz: string[]; fbSuS: string[]; aufbNutzungsdauer: boolean; aufbJahre: string; medien: string[]; uebergabe: string[]; bemerkung: string; }; const tage = (d: number) => new Date(Date.now() + d * 864e5); async function ablagestrukturImportieren() { const datei = path.join(process.cwd(), "prisma", "data", "ablagestruktur.json"); const roh = JSON.parse(readFileSync(datei, "utf-8")) as { nodes: RohKnoten[]; items: RohItem[] }; // Knoten nach Ebene, damit Elternknoten zuerst existieren const sortiert = [...roh.nodes].sort((a, b) => a.level - b.level); let i = 0; for (const k of sortiert) { await prisma.filingNode.upsert({ where: { code: k.code }, create: { code: k.code, parentCode: k.parent, akte: k.akte as Akte, level: k.level, titel: k.title, ril: k.ril || null, sortIndex: i++, }, update: { titel: k.title, ril: k.ril || null, parentCode: k.parent, level: k.level, sortIndex: i }, }); } for (const it of roh.items) { const fachbereiche = [...new Set([...it.fbNetz, ...it.fbSuS])]; await prisma.documentRequirement.upsert({ where: { ordnung: it.seq }, create: { ordnung: it.seq, nodeCode: it.folder, titel: it.title, ril: it.ril || null, istUnterpunkt: it.bullet, relevantNetz: it.netz, relevantBahnhof: it.sus, fachbereiche, aufbewahrungNutzungsdauer: it.aufbNutzungsdauer, aufbewahrungJahre: it.aufbJahre || null, medien: it.medien as Medium[], uebergabe: it.uebergabe as Uebergabezeitpunkt[], bemerkung: it.bemerkung || null, }, update: { nodeCode: it.folder, titel: it.title, ril: it.ril || null, relevantNetz: it.netz, relevantBahnhof: it.sus, fachbereiche, medien: it.medien as Medium[], uebergabe: it.uebergabe as Uebergabezeitpunkt[], }, }); } console.log(` Ablagestruktur: ${sortiert.length} Ordner, ${roh.items.length} Soll-Unterlagen.`); } async function demodatenAnlegen() { const pw = await bcrypt.hash("baudoc2026", 11); // ── Firmen ──────────────────────────────────────────────────────── const eiu = await prisma.company.create({ data: { name: "DB InfraGO AG – Regionalbereich Nord", shortName: "DB InfraGO", type: "EIU", city: "Hannover" }, }); const anGleisbau = await prisma.company.create({ data: { name: "Nordbahn Gleisbau GmbH", shortName: "Nordbahn", type: "AUFTRAGNEHMER", city: "Lüneburg", kreditorNr: "K-100482", ustId: "DE812345678" }, }); const anElt = await prisma.company.create({ data: { name: "Elektro Bahntechnik Weser GmbH", shortName: "EBW", type: "AUFTRAGNEHMER", city: "Bremen", kreditorNr: "K-100731" }, }); const planer = await prisma.company.create({ data: { name: "Ingenieurbüro Bahnplan Nord GmbH", shortName: "IB Bahnplan", type: "PLANUNGSBUERO", city: "Hamburg" }, }); const sicherung = await prisma.company.create({ data: { name: "RailSafe Sicherungsdienste GmbH", shortName: "RailSafe", type: "SICHERUNGSUNTERNEHMEN", city: "Celle" }, }); await prisma.company.create({ data: { name: "Eisenbahn-Bundesamt, Außenstelle Hannover", shortName: "EBA", type: "BEHOERDE", city: "Hannover" }, }); // ── Benutzer ────────────────────────────────────────────────────── const mk = (email: string, name: string, companyId: string, position: string, isAdmin = false) => prisma.user.create({ data: { email, name, passwordHash: pw, companyId, position, isAdmin } }); const admin = await mk("admin@baudoc.local", "System Administration", eiu.id, "IT-Administration", true); const pl = await mk("m.kruse@infrago.local", "Martina Kruse", eiu.id, "Projektleitung (PL)"); const bhv = await mk("t.albrecht@infrago.local", "Thorben Albrecht", eiu.id, "Bauherrenvertretung (BHV)"); const buew = await mk("j.hoffmann@infrago.local", "Jens Hoffmann", eiu.id, "Bauüberwacher Bahn – Oberbau/KIB"); const fbue = await mk("s.linnemann@infrago.local", "Sabine Linnemann", eiu.id, "Fachbauüberwacherin – Elektrotechnik/OLA"); const kfm = await mk("r.pauls@infrago.local", "Rieke Pauls", eiu.id, "Kaufmännische Projektsteuerung"); const plan = await mk("d.wegener@bahnplan.local", "Dominik Wegener", planer.id, "Projektingenieur Planung"); const anUser = await mk("bauleitung@nordbahn.local", "Kai Petersen", anGleisbau.id, "Bauleitung Nordbahn Gleisbau"); const anUser2 = await mk("technik@ebw.local", "Yasemin Öztürk", anElt.id, "Bauleitung EBW"); const sich = await mk("dispo@railsafe.local", "Marco Dennig", sicherung.id, "Disposition Sicherungsleistungen"); // ── Projekt ─────────────────────────────────────────────────────── const fachbereiche = [ "Oberbau", "Tiefbau", "Konstruktiver Ingenieurbau", "Elektrotechnik 50 Hz", "LST", "Oberleitung 16,7 Hz", ]; const heute = new Date(); const projekt = await prisma.project.create({ data: { nummer: "P-2026-0147", name: "Streckenertüchtigung Lüneburg – Uelzen, Abschnitt Bad Bevensen", kurzname: "SE Bad Bevensen", bauherr: "DB_INFRAGO_FAHRWEG", status: "BAU", regionalbereich: "Nord", fachbereiche, budgetPlan: 14_850_000, beschreibung: "Erneuerung Oberbau km 42,300 – 46,900 inkl. Weichen 1–6, Ersatzneubau EÜ Ilmenau (km 44,120), " + "Anpassung Oberleitung 16,7 Hz, Kabeltiefbau und LST-Anpassung im Zuge der ESTW-Anbindung.", startgespraech: tage(-420), baubeginn: tage(-180), bauende: tage(120), inbetriebnahme: tage(95), bauschlussmeldung: tage(150), projektabschluss: tage(330), abschnitte: { create: [ { streckenNr: "1720", streckenName: "Lehrte – Cuxhaven", kmVon: "42,300", kmBis: "46,900", betriebsstelle: "Bad Bevensen (ABBV)" }, { streckenNr: "1720", streckenName: "Lehrte – Cuxhaven", kmVon: "44,100", kmBis: "44,150", betriebsstelle: "EÜ Ilmenau" }, ], }, bauwerke: { create: [ { bauwerksNr: "1720-044-120", bezeichnung: "EÜ Ilmenau", typ: "Eisenbahnüberführung", fachbereich: "Konstruktiver Ingenieurbau", kmLage: "44,120", ueberwachungspflichtig: true }, { bauwerksNr: "1720-045-880", bezeichnung: "Stützbauwerk Nordrampe", typ: "Stützbauwerk", fachbereich: "Konstruktiver Ingenieurbau", kmLage: "45,880", ueberwachungspflichtig: true }, { bezeichnung: "Weichen W1 – W6 Bad Bevensen", typ: "Gleisanlage", fachbereich: "Oberbau", kmLage: "43,100 – 43,650" }, { bezeichnung: "Oberleitungsanlage Abschnitt Nord", typ: "Oberleitung", fachbereich: "Oberleitung 16,7 Hz", kmLage: "42,300 – 46,900" }, ], }, milestones: { create: [ { typ: "STARTGESPRAECH", titel: "Startgespräch Bauüberwachung", geplant: tage(-420), ist: tage(-418) }, { typ: "BAUBEGINN", titel: "Baubeginn / Bauanlaufberatung", geplant: tage(-180), ist: tage(-180) }, { typ: "ZWISCHEN_IBN", titel: "Zwischeninbetriebnahme Gleis 1", geplant: tage(-30), ist: tage(-28) }, { typ: "INBETRIEBNAHME", titel: "Inbetriebnahme Gesamtmaßnahme", geplant: tage(95) }, { typ: "BAUENDE", titel: "Bauende", geplant: tage(120) }, { typ: "BAUSCHLUSSMELDUNG", titel: "Bauschlussmeldung", geplant: tage(150) }, { typ: "PROJEKTABSCHLUSS", titel: "Projektabschluss (PA)", geplant: tage(330) }, ], }, members: { create: [ { userId: admin.id, role: "ADMIN" }, { userId: pl.id, role: "PROJEKTLEITER" }, { userId: bhv.id, role: "BAUHERRENVERTRETER" }, { userId: buew.id, role: "BAUUEBERWACHER" }, { userId: fbue.id, role: "FACHBAUUEBERWACHER", fachbereiche: ["Elektrotechnik 50 Hz", "Oberleitung 16,7 Hz", "LST"] }, { userId: kfm.id, role: "KAUFMANN" }, { userId: plan.id, role: "PLANER" }, { userId: anUser.id, role: "AUFTRAGNEHMER" }, { userId: anUser2.id, role: "AUFTRAGNEHMER" }, { userId: sich.id, role: "AUFTRAGNEHMER" }, ], }, }, }); // ── Soll-Dokumentenkatalog für das Projekt erzeugen ──────────────── const { neu } = await slotsErzeugen(projekt.id); console.log(` Soll-Dokumentenkatalog: ${neu} Positionen für ${projekt.nummer}.`); // Realistischer Erfüllungsstand: Planungsakte weitgehend erledigt, // Bauakte teilweise, Betreiberakte noch offen. const slots = await prisma.projectDocumentSlot.findMany({ where: { projectId: projekt.id }, include: { requirement: { select: { nodeCode: true } } }, orderBy: { requirement: { ordnung: "asc" } }, }); let idx = 0; for (const s of slots) { const akte = s.requirement.nodeCode[0]; const wurf = (idx * 37) % 100; idx++; let status: "OFFEN" | "IN_ARBEIT" | "EINGEREICHT" | "IN_PRUEFUNG" | "FREIGEGEBEN" | "ENTFAELLT" = "OFFEN"; if (akte === "P") status = wurf < 78 ? "FREIGEGEBEN" : wurf < 88 ? "IN_PRUEFUNG" : wurf < 94 ? "ENTFAELLT" : "OFFEN"; else if (akte === "K") status = wurf < 46 ? "FREIGEGEBEN" : wurf < 60 ? "EINGEREICHT" : wurf < 70 ? "IN_ARBEIT" : "OFFEN"; else status = wurf < 34 ? "FREIGEGEBEN" : wurf < 46 ? "IN_PRUEFUNG" : wurf < 58 ? "IN_ARBEIT" : "OFFEN"; if (status !== "OFFEN") { await prisma.projectDocumentSlot.update({ where: { id: s.id }, data: { status, eingangAm: status === "FREIGEGEBEN" || status === "EINGEREICHT" ? tage(-((wurf % 120) + 5)) : null, responsibleId: akte === "K" ? kfm.id : akte === "P" ? plan.id : buew.id, entfalltGrund: status === "ENTFAELLT" ? "Im Projektumfang nicht einschlägig (Vermerk PL)." : null, }, }); } } // ── Vertrag Oberbau/KIB mit LV ──────────────────────────────────── const vertrag = await prisma.contract.create({ data: { projectId: projekt.id, companyId: anGleisbau.id, nummer: "BV-2026-0147-01", bezeichnung: "Oberbau- und Ingenieurbauleistungen Bad Bevensen", typ: "BAUVERTRAG", status: "IN_ABWICKLUNG", vergabeart: "Offenes Verfahren (VOB/A EU)", auftragssumme: 7_420_000, beauftragtAm: tage(-210), ausfuehrungVon: tage(-180), ausfuehrungBis: tage(110), gewaehrleistungJahre: 4, sicherheitseinbehaltProzent: 5, skontoProzent: 2, skontoTage: 14, zahlungszielTage: 30, vertragsverantwortlich: "Rieke Pauls", positionen: { create: [ { oz: "01.01.0010", kurztext: "Gleis ausbauen, Schotteroberbau", einheit: "m", menge: 4600, einheitspreis: 38.5 }, { oz: "01.01.0020", kurztext: "Planumsschutzschicht herstellen, d = 30 cm", einheit: "m²", menge: 21800, einheitspreis: 24.9 }, { oz: "01.02.0010", kurztext: "Gleis neu bauen, S 54, Betonschwelle B 70", einheit: "m", menge: 4600, einheitspreis: 412.0 }, { oz: "01.02.0040", kurztext: "Weiche EW 60-500-1:12 einbauen", einheit: "St", menge: 6, einheitspreis: 148500.0 }, { oz: "02.01.0010", kurztext: "EÜ Ilmenau – Überbau ausbauen", einheit: "psch", menge: 1, einheitspreis: 96000.0 }, { oz: "02.01.0030", kurztext: "EÜ Ilmenau – Überbau einheben und lagern", einheit: "psch", menge: 1, einheitspreis: 384000.0 }, { oz: "03.01.0010", kurztext: "Kabeltiefbau, Kabeltrog beidseitig", einheit: "m", menge: 4600, einheitspreis: 62.4 }, { oz: "09.01.0010", kurztext: "Stundenlohnarbeiten Facharbeiter", einheit: "h", menge: 800, einheitspreis: 58.5, istStundenlohn: true }, { oz: "09.01.0020", kurztext: "Stundenlohnarbeiten Polier", einheit: "h", menge: 200, einheitspreis: 74.0, istStundenlohn: true }, { oz: "09.02.0010", kurztext: "Vorhaltung Zweiwegebagger im Stundenlohn", einheit: "h", menge: 300, einheitspreis: 132.0, istStundenlohn: true }, ], }, }, include: { positionen: true }, }); const p = (oz: string) => vertrag.positionen.find((x) => x.oz === oz)!; await prisma.contract.create({ data: { projectId: projekt.id, companyId: anElt.id, nummer: "BV-2026-0147-02", bezeichnung: "Oberleitungs- und 50-Hz-Anlagen", typ: "BAUVERTRAG", status: "IN_ABWICKLUNG", auftragssumme: 3_180_000, beauftragtAm: tage(-195), ausfuehrungVon: tage(-150), ausfuehrungBis: tage(100), positionen: { create: [ { oz: "05.01.0010", kurztext: "Oberleitungsmast setzen inkl. Fundament", einheit: "St", menge: 118, einheitspreis: 8940.0 }, { oz: "05.02.0010", kurztext: "Kettenwerk Re200 montieren und regulieren", einheit: "m", menge: 4600, einheitspreis: 148.0 }, ], }, }, }); const nachtrag = await prisma.changeOrder.create({ data: { contractId: vertrag.id, nummer: "N-01", titel: "Mehraufwand Bodenaustausch km 45,100 – 45,400 (kontaminiertes Material)", begruendung: "Bei Aushub wurde nicht ausgeschriebenes, mit Altöl belastetes Material angetroffen (Zuordnung Z 2). " + "Entsorgung und Ersatz durch Frostschutzschicht erforderlich.", rechtsgrundlage: "§ 2 Abs. 6 VOB/B", angebotssumme: 214_800, anerkannteSumme: 186_400, status: "BEAUFTRAGT", angemeldetAm: tage(-96), geprueftAm: tage(-84), beauftragtAm: tage(-72), stellungnahme: "Dem Grunde nach anerkannt. Mengenvordersätze auf Basis Abrechnungszeichnung geprüft. " + "Einheitspreis Entsorgung auf marktübliches Niveau gekürzt (Nachtragsbewertungsmatrix).", }, }); await prisma.changeOrder.create({ data: { contractId: vertrag.id, nummer: "N-02", titel: "Zusätzliche Sperrpause wegen verschobener Betra KW 18", begruendung: "Verschiebung der Betra durch Kapazitätsmanagement, Mehrkosten Vorhaltung und Sicherung.", rechtsgrundlage: "§ 6 Abs. 6 VOB/B", angebotssumme: 88_400, status: "IN_PRUEFUNG_BUEW", angemeldetAm: tage(-21), }, }); // ── Aufmaße ─────────────────────────────────────────────────────── const aufmass1 = await prisma.measurement.create({ data: { projectId: projekt.id, contractId: vertrag.id, nummer: "AM-2026-014", art: "ABRECHNUNGSZEICHNUNG", datum: tage(-38), ortBauteil: "km 42,300 – 44,100", status: "ANERKANNT", createdById: buew.id, geprueftAm: tage(-34), bemerkung: "Mengenermittlung nach REB 23.003, gemeinsam mit AN festgestellt.", zeilen: { create: [ { contractItemId: p("01.01.0010").id, beschreibung: "Gleis 1 Ausbau", formel: "1800,00", menge: 1800 }, { contractItemId: p("01.02.0010").id, beschreibung: "Gleis 1 Neubau", formel: "1800,00", menge: 1800 }, { contractItemId: p("01.01.0020").id, beschreibung: "PSS Gleis 1", formel: "1800,00 * 4,70", menge: 8460 }, { contractItemId: p("01.02.0040").id, beschreibung: "W1, W2", formel: "2", menge: 2 }, ], }, }, }); await prisma.measurement.create({ data: { projectId: projekt.id, contractId: vertrag.id, nummer: "AM-2026-021", art: "OERTLICHES_AUFMASS", datum: tage(-12), ortBauteil: "EÜ Ilmenau km 44,120", status: "TEILWEISE_ANERKANNT", createdById: buew.id, geprueftAm: tage(-9), bemerkung: "Überbau Ausbau anerkannt. Einheben noch nicht erbracht – Position zurückgestellt.", zeilen: { create: [ { contractItemId: p("02.01.0010").id, beschreibung: "Überbau Ausbau", formel: "1", menge: 1, mengeAnerkannt: 1 }, { contractItemId: p("02.01.0030").id, beschreibung: "Überbau einheben", formel: "1", menge: 1, mengeAnerkannt: 0 }, { contractItemId: p("03.01.0010").id, beschreibung: "Kabeltrog Nordseite", formel: "1800,00", menge: 1800, mengeAnerkannt: 1650 }, ], }, }, }); // ── Stundenlohnzettel ───────────────────────────────────────────── // Zettel 1: vollständig anerkannt const zettel1 = await prisma.timeSheet.create({ data: { projectId: projekt.id, contractId: vertrag.id, nummer: "SLZ-2026-088", datum: tage(-26), leistung: "Freilegen und Sichern eines nicht dokumentierten Kabels km 43,480", ortKm: "43,480", bezugPosition: "09.01.0010", status: "ANERKANNT", filedById: anUser.id, checkedById: buew.id, geprueftAm: tage(-24), pruefkommentar: "Leistung war angeordnet (AvL vom 12.), Stunden vor Ort bestätigt.", zeilen: { create: [ { name: "M. Sobczak", art: "FACHARBEITER", stundenGemeldet: 8, stundenAnerkannt: 8, stundensatz: 58.5 }, { name: "T. Ehlert", art: "FACHARBEITER", stundenGemeldet: 8, stundenAnerkannt: 8, stundensatz: 58.5 }, { name: "R. Danneberg", art: "POLIER", stundenGemeldet: 4, stundenAnerkannt: 4, stundensatz: 74.0 }, ], }, }, }); // Zettel 2: gekürzt (An- und Abfahrt nicht anerkannt) – klassischer Streitfall const zettel2 = await prisma.timeSheet.create({ data: { projectId: projekt.id, contractId: vertrag.id, nummer: "SLZ-2026-091", datum: tage(-19), leistung: "Handschachtung im Bereich Bestandskabel W4, Sperrpause 22:00 – 05:00", ortKm: "43,610", bezugPosition: "09.01.0010", status: "TEILWEISE_ANERKANNT", filedById: anUser.id, checkedById: buew.id, geprueftAm: tage(-17), pruefkommentar: "An- und Abfahrtszeiten sind mit den Einheitspreisen abgegolten und wurden abgesetzt. " + "Nachtzuschlag 25 % für die Sperrpause anerkannt.", zeilen: { create: [ { name: "M. Sobczak", art: "FACHARBEITER", stundenGemeldet: 9, stundenAnerkannt: 7, stundensatz: 58.5, zuschlagProzent: 25, zuschlagGrund: "Nachtarbeit in Sperrpause" }, { name: "T. Ehlert", art: "FACHARBEITER", stundenGemeldet: 9, stundenAnerkannt: 7, stundensatz: 58.5, zuschlagProzent: 25, zuschlagGrund: "Nachtarbeit in Sperrpause" }, { name: "Zweiwegebagger ZW-14", art: "GERAET", stundenGemeldet: 7, stundenAnerkannt: 7, stundensatz: 132.0 }, ], }, }, }); // Zettel 3: noch nicht geprüft – darf nicht abgerechnet werden const zettel3 = await prisma.timeSheet.create({ data: { projectId: projekt.id, contractId: vertrag.id, nummer: "SLZ-2026-097", datum: tage(-6), leistung: "Nacharbeiten Entwässerungsrinne km 45,900", ortKm: "45,900", status: "EINGEREICHT", filedById: anUser.id, zeilen: { create: [ { name: "K. Wiese", art: "FACHARBEITER", stundenGemeldet: 6, stundensatz: 58.5 }, { name: "L. Brandt", art: "HELFER", stundenGemeldet: 6, stundensatz: 46.0 }, ], }, }, }); // ── Rechnung mit bewusst eingebauten Prüfbefunden ───────────────── const rechnung = await prisma.invoice.create({ data: { projectId: projekt.id, contractId: vertrag.id, companyId: anGleisbau.id, rechnungsNr: "2026-AR-0431", typ: "ABSCHLAGSRECHNUNG", status: "EINGEGANGEN", rechnungsdatum: tage(-4), eingangsdatum: tage(-3), leistungVon: tage(-60), leistungBis: tage(-5), nettoBetrag: 2_166_283.0, ustSatz: 19, ustBetrag: 411_593.77, bruttoBetrag: 2_577_876.77, zahlbarBis: tage(27), zeilen: { create: [ // korrekt { position: 1, art: "LV_POSITION", oz: "01.01.0010", bezeichnung: "Gleis ausbauen, Schotteroberbau", einheit: "m", menge: 1800, einheitspreis: 38.5, betrag: 69_300, contractItemId: p("01.01.0010").id }, // korrekt { position: 2, art: "LV_POSITION", oz: "01.02.0010", bezeichnung: "Gleis neu bauen S 54", einheit: "m", menge: 1800, einheitspreis: 412.0, betrag: 741_600, contractItemId: p("01.02.0010").id }, // Befund: Einheitspreis über Vertrag { position: 3, art: "LV_POSITION", oz: "01.01.0020", bezeichnung: "Planumsschutzschicht d = 30 cm", einheit: "m²", menge: 8460, einheitspreis: 26.4, betrag: 223_344, contractItemId: p("01.01.0020").id }, // Befund: Menge über anerkanntem Aufmaß (3 statt 2 Weichen) { position: 4, art: "LV_POSITION", oz: "01.02.0040", bezeichnung: "Weiche EW 60-500-1:12 einbauen", einheit: "St", menge: 3, einheitspreis: 148_500, betrag: 445_500, contractItemId: p("01.02.0040").id }, // Befund: Position ohne anerkanntes Aufmaß (Überbau noch nicht eingehoben) { position: 5, art: "LV_POSITION", oz: "02.01.0030", bezeichnung: "EÜ Ilmenau – Überbau einheben", einheit: "psch", menge: 1, einheitspreis: 384_000, betrag: 384_000, contractItemId: p("02.01.0030").id }, // korrekt { position: 6, art: "STUNDENLOHN", oz: "SLZ-2026-088", bezeichnung: "Stundenlohn SLZ-2026-088", einheit: "h", menge: 20, einheitspreis: 61.6, betrag: 1_232, timeSheetId: zettel1.id }, // Befund: gemeldete statt anerkannte Stunden { position: 7, art: "STUNDENLOHN", oz: "SLZ-2026-091", bezeichnung: "Stundenlohn SLZ-2026-091", einheit: "h", menge: 25, einheitspreis: 78.4, betrag: 1_960, timeSheetId: zettel2.id }, // Befund: Zettel noch nicht bestätigt { position: 8, art: "STUNDENLOHN", oz: "SLZ-2026-097", bezeichnung: "Stundenlohn SLZ-2026-097", einheit: "h", menge: 12, einheitspreis: 52.25, betrag: 627, timeSheetId: zettel3.id }, // Befund: Nachtragsposition ohne beauftragten Nachtrag ist hier korrekt zugeordnet { position: 9, art: "NACHTRAG", bezeichnung: "N-01 Bodenaustausch km 45,100 – 45,400", einheit: "psch", menge: 1, einheitspreis: 186_400, betrag: 186_400, changeOrderId: nachtrag.id }, // Befund: Kabeltrog über anerkanntem Aufmaß (1800 statt 1650 m) { position: 10, art: "LV_POSITION", oz: "03.01.0010", bezeichnung: "Kabeltiefbau, Kabeltrog", einheit: "m", menge: 1800, einheitspreis: 62.4, betrag: 112_320, contractItemId: p("03.01.0010").id }, ], }, }, }); // ── Bautagebuch ─────────────────────────────────────────────────── const wetter = [ ["heiter", "sonnig", "bewölkt", 8, 19], ["Nebel", "bedeckt", "Regen", 5, 12], ["bedeckt", "Regen", "Regen", 6, 11], ["klar", "heiter", "klar", 3, 16], ["bewölkt", "bewölkt", "heiter", 7, 17], ]; for (let i = 0; i < 5; i++) { const w = wetter[i]; const eintrag = await prisma.siteDiaryEntry.create({ data: { projectId: projekt.id, datum: tage(-(i + 1)), lfdNr: 178 - i, authorId: buew.id, wetterFrueh: String(w[0]), wetterMittag: String(w[1]), wetterAbend: String(w[2]), tempMin: Number(w[3]), tempMax: Number(w[4]), arbeitszeitVon: "06:30", arbeitszeitBis: "16:00", betra: i < 2 ? "Betra 2026/0412, Gleis 2 gesperrt 22:00 – 05:00" : "Betra 2026/0398", sperrpause: i < 2 ? "22:00 – 05:00, Gleis 2" : "keine", leistungen: i === 0 ? "Gleisneubau km 43,900 – 44,050 (150 m), Schotterverteilung und erstes Stopfen. Kabeltrogmontage Nordseite fortgesetzt.\nEÜ Ilmenau: Vorbereitung Lagerversetzung, Traggerüst geprüft." : "Gleisneubau fortgesetzt, PSS eingebaut und verdichtet. Weiche W4 vormontiert.", besondereVorkommnisse: i === 0 ? "Bei Aushub km 43,480 nicht dokumentiertes Kabel angetroffen. Arbeiten örtlich unterbrochen, DB Kommunikationstechnik verständigt (14:20 Uhr, Hr. Lembke)." : i === 2 ? "Starkregen ab 11:30, Verdichtungsarbeiten eingestellt." : null, einweisungen: i === 4 ? "Ersteinweisung 6 Mitarbeiter Nordbahn Gleisbau, Baustellenbelehrung nach Ril 132.0108V11." : null, sicherungsmassnahmen: "Sicherung durch automatisches Warnsystem (AWS), Sicherungsplan Rev. 3, RailSafe.", status: i === 0 ? "ENTWURF" : "ABGESCHLOSSEN", signedById: i === 0 ? null : buew.id, signedAt: i === 0 ? null : tage(-(i + 1)), gesperrtAm: i > 1 ? tage(-(i)) : null, personal: { create: [ { companyId: anGleisbau.id, gewerk: "Oberbau", taetigkeit: "Gleisbaukolonne", anzahl: 12, stunden: 108 }, { companyId: anGleisbau.id, gewerk: "Konstruktiver Ingenieurbau", taetigkeit: "EÜ Ilmenau", anzahl: 5, stunden: 45 }, { companyId: sicherung.id, gewerk: "Sicherung", taetigkeit: "Sicherungsposten / AWS", anzahl: 3, stunden: 27 }, ], }, geraete: { create: [ { bezeichnung: "Zweiwegebagger ZW-14", anzahl: 1, stunden: 9 }, { bezeichnung: "Stopfmaschine 09-32", anzahl: 1, stunden: 6 }, { bezeichnung: "Autokran 200 t", anzahl: i === 0 ? 1 : 0, stunden: i === 0 ? 8 : 0 }, ], }, }, }); if (i === 0) { await prisma.timeSheet.update({ where: { id: zettel1.id }, data: { diaryEntryId: eintrag.id } }); } } // ── Meldungen / Vorgänge ────────────────────────────────────────── await prisma.issue.createMany({ data: [ { projectId: projekt.id, nr: 1, typ: "BEHINDERUNGSANZEIGE", titel: "Nicht dokumentiertes Kabel km 43,480 – Behinderung Gleisbau", beschreibung: "Beim Aushub wurde ein im Bestandsplan nicht enthaltenes Kabel angetroffen. Der Gleisbau in diesem Bereich ruht " + "bis zur Freigabe durch DB Kommunikationstechnik. Bauzeitverzug wird angezeigt.", status: "IN_BEARBEITUNG", schwere: "HOCH", ortKm: "43,480", fachbereich: "Oberbau", raisedById: anUser.id, assigneeId: buew.id, gegenFirmaId: null, contractId: vertrag.id, gemeldetAm: tage(-26), fristAm: tage(2), kostenrelevant: true, terminrelevant: true, geschaetzteKosten: 42_000, }, { projectId: projekt.id, nr: 2, typ: "MANGELANZEIGE", titel: "Schweißnaht SK 41 – Ultraschallprüfung ohne Nachweis", beschreibung: "Für die Schweißstelle SK 41 (km 43,215) liegt kein Prüfprotokoll nach Ril 824.9006 vor. " + "Nachweis oder Nacharbeit ist bis zum Fristende beizubringen.", status: "OFFEN", schwere: "SICHERHEITSRELEVANT", ortKm: "43,215", fachbereich: "Oberbau", raisedById: buew.id, assigneeId: anUser.id, gegenFirmaId: anGleisbau.id, contractId: vertrag.id, gemeldetAm: tage(-8), fristAm: tage(-1), }, { projectId: projekt.id, nr: 3, typ: "BEDENKENANZEIGE", titel: "Bedenken gegen vorgesehene Verdichtungsleistung bei anhaltender Nässe", beschreibung: "Nach Starkregen ist die geforderte Verdichtung der PSS im Abschnitt km 45,2 – 45,6 nach unserer Einschätzung " + "nicht erreichbar. Bedenken werden gemäß § 4 Abs. 3 VOB/B angemeldet.", status: "ERLEDIGT", schwere: "MITTEL", ortKm: "45,200 – 45,600", fachbereich: "Tiefbau", raisedById: anUser.id, assigneeId: buew.id, contractId: vertrag.id, gemeldetAm: tage(-40), erledigtAm: tage(-31), }, { projectId: projekt.id, nr: 4, typ: "MEHRKOSTEN_AVL", titel: "Anordnung von Leistungen: zusätzliche Kabelsuchschachtungen", beschreibung: "Anordnung des BÜB vom 12., Ausführung im Stundenlohn nach SLZ-2026-088.", status: "ERLEDIGT", schwere: "GERING", raisedById: buew.id, contractId: vertrag.id, gemeldetAm: tage(-27), erledigtAm: tage(-24), kostenrelevant: true, }, { projectId: projekt.id, nr: 5, typ: "VERTRAGSABWEICHUNG", titel: "Abweichende Schwellenlieferung – Zulassung prüfen", beschreibung: "Angeliefert wurden Betonschwellen einer nicht im Vertrag benannten Bauart. " + "Verwendbarkeitsnachweis und Zulassung sind vor Einbau vorzulegen.", status: "IN_BEARBEITUNG", schwere: "HOCH", fachbereich: "Oberbau", raisedById: fbue.id, assigneeId: anUser.id, gegenFirmaId: anGleisbau.id, contractId: vertrag.id, gemeldetAm: tage(-3), fristAm: tage(4), }, ], }); await prisma.warranty.createMany({ data: [ { projectId: projekt.id, contractId: vertrag.id, gegenstand: "Zwischeninbetriebnahme Gleis 1, km 42,300 – 44,100", abnahmeAm: tage(-28), fristJahre: 4, fristEndeAm: tage(-28 + 4 * 365), verantwortlich: "Jens Hoffmann (BÜB)", buergschaft: 371_000, status: "LAUFEND", }, ], }); // ── Kommunikation ───────────────────────────────────────────────── const kanalAllgemein = await prisma.channel.create({ data: { projectId: projekt.id, name: "Baustelle allgemein", kind: "PROJEKT", beschreibung: "Tagesabstimmung aller Projektbeteiligten.", mitglieder: { create: [pl, bhv, buew, fbue, kfm, plan, anUser, anUser2, sich].map((u) => ({ userId: u.id })) }, }, }); const kanalAbrechnung = await prisma.channel.create({ data: { projectId: projekt.id, name: "Abrechnung & Nachträge", kind: "THEMA", privat: true, beschreibung: "Nur Bauherrenseite: Aufmaße, Stundenzettel, Rechnungsprüfung.", mitglieder: { create: [pl, bhv, buew, kfm].map((u) => ({ userId: u.id })) }, }, }); await prisma.channel.create({ data: { projectId: projekt.id, name: "Oberleitung / 50 Hz", kind: "FACHBEREICH", beschreibung: "Fachliche Abstimmung Elektrotechnik.", mitglieder: { create: [fbue, pl, anUser2, buew].map((u) => ({ userId: u.id })) }, }, }); await prisma.message.createMany({ data: [ { channelId: kanalAllgemein.id, authorId: buew.id, createdAt: tage(-1.4), text: "Betra 2026/0412 ist für heute Nacht bestätigt, Sperrung Gleis 2 von 22:00 bis 05:00. Einweisung um 21:30 am Container Nord." }, { channelId: kanalAllgemein.id, authorId: anUser.id, createdAt: tage(-1.3), text: "Verstanden. Wir sind mit 12 Mann und dem Zweiwegebagger vor Ort. @Hoffmann bitte kurz bestätigen, dass der Autokran bis 06:00 stehen bleiben darf." }, { channelId: kanalAllgemein.id, authorId: buew.id, createdAt: tage(-1.25), text: "Bestätigt, der Kran kann bis 06:00 stehen bleiben. Standsicherheitsnachweis liegt in der Ablage unter B.I.1.3." }, { channelId: kanalAllgemein.id, authorId: fbue.id, createdAt: tage(-0.6), text: "Hinweis für alle: ab Montag ist der Abschnitt km 45,2 – 45,6 unter Spannung. Arbeiten im Gefahrenbereich nur nach Schalt- und Erdungsprotokoll." }, { channelId: kanalAbrechnung.id, authorId: kfm.id, createdAt: tage(-0.4), text: "Die Abschlagsrechnung 2026-AR-0431 von Nordbahn ist eingegangen. Automatische Prüfung hat mehrere kritische Befunde ausgeworfen – @Hoffmann magst du sachlich draufschauen, bevor ich rechnerisch prüfe?" }, { channelId: kanalAbrechnung.id, authorId: buew.id, createdAt: tage(-0.3), text: "Schaue ich mir heute Nachmittag an. Position 5 (Überbau einheben) kann definitiv noch nicht abgerechnet werden, der Überbau liegt noch auf dem Lagerplatz." }, ], }); await prisma.task.createMany({ data: [ { projectId: projekt.id, titel: "Prüfprotokoll Schweißstelle SK 41 nachfordern", assigneeId: buew.id, faelligAm: tage(1), status: "OFFEN" }, { projectId: projekt.id, titel: "Nachtrag N-02 bewerten (Nachtragsbewertungsmatrix)", assigneeId: buew.id, faelligAm: tage(5), status: "IN_ARBEIT" }, { projectId: projekt.id, titel: "Bestandsunterlagen Kabeltrasse in DB-GIS einarbeiten lassen", assigneeId: plan.id, faelligAm: tage(30), status: "OFFEN" }, { projectId: projekt.id, titel: "Abschlagsrechnung 2026-AR-0431 rechnerisch prüfen", assigneeId: kfm.id, faelligAm: tage(6), status: "OFFEN" }, ], }); return { projekt, rechnung }; } async function main() { console.log("[seed] Ablagestruktur importieren ..."); await ablagestrukturImportieren(); const vorhanden = await prisma.project.count(); if (vorhanden > 0) { console.log("[seed] Projekte vorhanden – Demo-Daten werden übersprungen."); return; } console.log("[seed] Demo-Daten anlegen ..."); const { rechnung } = await demodatenAnlegen(); console.log("[seed] Rechnungsprüfung ausführen ..."); const { pruefeRechnung } = await import("../src/lib/rechnungspruefung"); const bericht = await pruefeRechnung(rechnung.id); console.log( ` ${bericht.befunde} Befunde (${bericht.kritisch} kritisch), ` + `Kürzung ${bericht.kuerzungNetto.toLocaleString("de-DE")} EUR.`, ); console.log("\n[seed] Fertig. Zugänge (Kennwort jeweils: baudoc2026)"); console.log(" admin@baudoc.local Administration"); console.log(" m.kruse@infrago.local Projektleitung"); console.log(" j.hoffmann@infrago.local Bauüberwacher Bahn"); console.log(" r.pauls@infrago.local Kaufmännische Steuerung"); console.log(" bauleitung@nordbahn.local Auftragnehmer"); } main() .catch((e) => { console.error(e); process.exit(1); }) .finally(() => prisma.$disconnect());