/** * Korrigiert einzelne fachrechtliche Zitate, die gegen den geltenden * Normbestand geprüft wurden. * * node scripts/fix-legal-citations.js --dry-run * node scripts/fix-legal-citations.js * * Geändert wird ausschließlich, wofür eine belegte, eindeutige Fassung * vorliegt. Alles Übrige – nicht zitierfähige Satzungen, unklare Zuordnungen, * fehlende Fristen – bleibt unangetastet und geht an die Datenschutz- * beauftragte. * * Der Lauf ist wiederholbar und setzt nur an, wo der alte Wortlaut noch steht. */ const fs = require("fs") const path = require("path") const { PrismaClient } = require("@prisma/client") const prisma = new PrismaClient() const JSON_DIR = path.join(process.cwd(), "data", "policies") /** Trennzeile, hinter der das Fachrecht in `legalBasis` steht. */ const MARKER = "Konkrete Rechtsgrundlage: " /** * Belegte Korrekturen. `von` muss exakt dem heutigen Fachrechts-Text * entsprechen, sonst wird der Datensatz übersprungen. */ const KORREKTUREN = [ // --- Landesbauordnung SH, neu gefasst am 6.12.2021, gültig ab 01.09.2022 --- // Die Paragraphen wurden dabei umnummeriert. { titel: "Bauantrag", von: "§ 64 LBO", nach: "§§ 64, 68 LBO", grund: "§ 64 LBO regelt das Baugenehmigungsverfahren, der Bauantrag selbst steht in § 68 LBO (Bauantrag, Bauvorlagen). Ergänzt statt ersetzt, da beide einschlägig sind.", }, { titel: "Bauvoranfrage", von: "§ 61 LBO", nach: "§ 75 LBO", grund: "§ 61 LBO ist seit 01.09.2022 „Verfahrensfreie Bauvorhaben“. Der Vorbescheid – die Bauvoranfrage – steht in § 75 LBO.", }, { titel: "Befreiungsantrag /Abweichungsantrag/Ausnahmeantrag", von: "§ 61 LBO", nach: "§ 67 LBO, § 31 BauGB", grund: "Abweichungen, Ausnahmen und Befreiungen stehen in § 67 LBO; bauplanungsrechtliche Befreiungen zusätzlich in § 31 BauGB.", }, { titel: "Stellungnahme Bauantrag", von: "§ 61 LBO", nach: "§ 36 BauGB, § 71 LBO", grund: "Das gemeindliche Einvernehmen regelt § 36 BauGB, seine Ersetzung § 71 LBO. § 61 LBO betrifft verfahrensfreie Vorhaben.", }, // --- Wohnberechtigungsschein --- { titel: "Antrag auf Ausstellung eines Wohnberechtigungsscheines", von: "Wohnberechtigungsschein", nach: "§ 27 WoFG i. V. m. § 5 WoBindG", grund: "„Wohnberechtigungsschein“ benennt das Dokument, keine Norm. Der Schein wird nach § 27 WoFG i. V. m. § 5 WoBindG erteilt.", }, // --- Bildung und Teilhabe --- { titel: "Antrag auf Hilfe für Bildung u. Teilhabe", von: "SGB XII, BKKG", nach: "§ 34 SGB XII, § 6b BKGG", grund: "„BKKG“ ist ein Schreibfehler für BKGG. Leistungen für Bildung und Teilhabe stehen in § 34 SGB XII bzw. § 6b BKGG.", }, { titel: "Antrag auf Hilfe für Bildung u. Teilhabe: Hilfe f. mehrtägige Klassenfahrten", von: "SGB XII", nach: "§ 34 SGB XII", grund: "Präzisierung: Leistungen für Bildung und Teilhabe stehen in § 34 SGB XII.", }, { titel: "Änderungsmitteilung auf Hilfe für Bildung u. Teilhabe", von: "SGB XII", nach: "§ 34 SGB XII", grund: "Präzisierung: Leistungen für Bildung und Teilhabe stehen in § 34 SGB XII.", }, // --- Vergaberecht --- { titel: "Ausschreibungsunterlagen", von: "VOL/VgV/UVGO/VGO S-H", nach: "VgV/UVgO/VGO S-H", grund: "Die VOL/A ist für Liefer- und Dienstleistungen durch die UVgO abgelöst. Nur das überholte Kürzel entfernt; die Landesvorschrift sollte noch mit vollem Titel benannt werden.", }, ] /** * Zusätzliche Erlaubnisnorm, wo besondere Kategorien personenbezogener Daten * verarbeitet werden. Art. 9 Abs. 1 DSGVO verbietet das grundsätzlich; die * Verarbeitung braucht deshalb eine eigene Grundlage nach Art. 9 Abs. 2. */ const ART9_ZUSATZ = "Soweit Gesundheitsdaten verarbeitet werden: **Art. 9 Abs. 2 lit. b DSGVO** i. V. m. **§ 15 LDSG SH** – Verarbeitung besonderer Kategorien personenbezogener Daten zur Erfüllung von Pflichten aus dem Arbeits- und Sozialrecht." const ART9_TITEL = [ "Beihilferecht", "Mutterschutz, Elternzeit", "Arbeitsmedizinische Untersuchungen, Unfallfürsorge", ] function fachrechtErsetzen(legalBasis, von, nach) { const stelle = legalBasis.indexOf(MARKER) if (stelle === -1) return null const kopf = legalBasis.slice(0, stelle + MARKER.length) const rest = legalBasis.slice(stelle + MARKER.length) if (rest.trim() !== von) return null return kopf + nach } async function main() { const dryRun = process.argv.includes("--dry-run") const policies = await prisma.privacyPolicy.findMany() const nachTitel = new Map(policies.map((p) => [p.title, p])) const geplant = [] const uebersprungen = [] for (const korrektur of KORREKTUREN) { const policy = nachTitel.get(korrektur.titel) if (!policy) { uebersprungen.push(`${korrektur.titel} – Erklärung nicht gefunden`) continue } const neu = fachrechtErsetzen(policy.legalBasis || "", korrektur.von, korrektur.nach) if (!neu) { uebersprungen.push( `${korrektur.titel} – Wortlaut weicht ab (erwartet „${korrektur.von}“), bereits geändert?` ) continue } geplant.push({ policy, korrektur, neu }) } const art9 = [] for (const titel of ART9_TITEL) { const policy = nachTitel.get(titel) if (!policy) { uebersprungen.push(`${titel} – Erklärung nicht gefunden`) continue } if ((policy.legalBasis || "").includes("Art. 9 Abs. 2")) { uebersprungen.push(`${titel} – Art. 9 bereits ergänzt`) continue } art9.push({ policy, neu: `${policy.legalBasis}\n\n${ART9_ZUSATZ}` }) } console.log(`=== Fachrechtliche Zitate (${geplant.length}) ===`) for (const { korrektur } of geplant) { console.log(`\n ${korrektur.titel}`) console.log(` vorher : ${korrektur.von}`) console.log(` nachher: ${korrektur.nach}`) console.log(` Grund : ${korrektur.grund}`) } console.log(`\n=== Besondere Datenkategorien, Art. 9 Abs. 2 lit. b (${art9.length}) ===`) art9.forEach(({ policy }) => console.log(` ${policy.title}`)) if (uebersprungen.length) { console.log(`\n=== Übersprungen (${uebersprungen.length}) ===`) uebersprungen.forEach((z) => console.log(` ${z}`)) } if (dryRun) { console.log("\n--dry-run: nichts geschrieben.") return } const schreiben = async (policy, legalBasis) => { await prisma.privacyPolicy.update({ where: { id: policy.id }, data: { legalBasis } }) const datei = path.join(JSON_DIR, `${policy.slug}.json`) try { if (fs.existsSync(datei)) { const inhalt = JSON.parse(fs.readFileSync(datei, "utf8")) inhalt.legalBasis = legalBasis fs.writeFileSync(datei, JSON.stringify(inhalt, null, 2), "utf8") } } catch { // Die Datenbank ist maßgeblich; die Ablage ist nur Nachweis. } } for (const { policy, neu } of geplant) await schreiben(policy, neu) for (const { policy, neu } of art9) await schreiben(policy, neu) console.log(`\nZitate korrigiert: ${geplant.length}`) console.log(`Art. 9 ergänzt: ${art9.length}`) } main() .catch((e) => { console.error(e) process.exitCode = 1 }) .finally(() => prisma.$disconnect())