Art. 13 Abs. 1 lit. c DSGVO verlangt die Rechtsgrundlage der Verarbeitung.
Die Liste nannte durchgehend nur die Fachnorm ("§ 64 LBO"), also die
Aufgabe, nicht die datenschutzrechtliche Erlaubnis. Ergänzt wird je nach
Art der Verarbeitung:
91x hoheitlich Art. 6 Abs. 1 lit. e DSGVO i. V. m. § 3 LDSG SH
9x Beschaeftigung Art. 6 Abs. 1 lit. b DSGVO i. V. m. § 15 LDSG SH
6x Vertrag Art. 6 Abs. 1 lit. b DSGVO
2x Einwilligung Art. 6 Abs. 1 lit. a DSGVO, mit Hinweis auf Art. 7 Abs. 3
Der vom Amt angegebene Text bleibt unveraendert darunter stehen. Die 13
Erklaerungen ganz ohne Angabe wurden nicht angefasst: Eine fehlende
Grundlage zu erfinden ist etwas anderes, als eine vorhandene zu
vervollstaendigen.
Wiederholbar, und mit `npm run import-csv` wieder auf die Rohwerte der
Liste zurueckzusetzen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
169 lines
5.9 KiB
JavaScript
169 lines
5.9 KiB
JavaScript
/**
|
||
* Ergänzt die Rechtsgrundlage jeder Erklärung um die DSGVO-Grundlage.
|
||
*
|
||
* node scripts/enrich-legal-basis.js --dry-run
|
||
* node scripts/enrich-legal-basis.js
|
||
*
|
||
* Art. 13 Abs. 1 lit. c DSGVO verlangt „die Rechtsgrundlagen der Verarbeitung".
|
||
* Gemeint ist die Erlaubnisnorm der DSGVO selbst – die Fachnorm allein (etwa
|
||
* „§ 64 LBO") benennt nur die Aufgabe, nicht die datenschutzrechtliche
|
||
* Grundlage. Für Behörden in Schleswig-Holstein ist das:
|
||
*
|
||
* hoheitliches Handeln Art. 6 Abs. 1 lit. e DSGVO i. V. m. § 3 LDSG SH
|
||
* Beschäftigtendaten Art. 6 Abs. 1 lit. b DSGVO i. V. m. § 15 LDSG SH
|
||
* Einwilligung Art. 6 Abs. 1 lit. a DSGVO
|
||
* Vertrag Art. 6 Abs. 1 lit. b DSGVO
|
||
*
|
||
* Der vom Amt angegebene Text bleibt unverändert erhalten und wird nur
|
||
* darunter gesetzt. Erklärungen ohne jede Angabe werden NICHT ergänzt – dort
|
||
* eine Grundlage zu erfinden wäre etwas anderes als eine vorhandene zu
|
||
* vervollständigen. Sie stehen im Bericht.
|
||
*
|
||
* Der Lauf ist wiederholbar: Bereits ergänzte Texte werden übersprungen.
|
||
* Rückgängig machen lässt er sich mit `npm run import-csv`, das die
|
||
* Rohwerte der Liste wiederherstellt.
|
||
*/
|
||
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")
|
||
|
||
const GRUNDLAGEN = {
|
||
hoheitlich: {
|
||
kopf: "**Art. 6 Abs. 1 lit. e DSGVO** i. V. m. **§ 3 LDSG SH** – Verarbeitung zur Wahrnehmung einer Aufgabe, die im öffentlichen Interesse liegt oder in Ausübung öffentlicher Gewalt erfolgt.",
|
||
label: "hoheitliche Aufgabe",
|
||
},
|
||
beschaeftigung: {
|
||
kopf: "**Art. 6 Abs. 1 lit. b DSGVO** i. V. m. **§ 15 LDSG SH** – Verarbeitung für Zwecke des Beschäftigungsverhältnisses.",
|
||
label: "Beschäftigungsverhältnis",
|
||
},
|
||
einwilligung: {
|
||
kopf: "**Art. 6 Abs. 1 lit. a DSGVO** – Einwilligung der betroffenen Person. Die Einwilligung kann jederzeit mit Wirkung für die Zukunft widerrufen werden (Art. 7 Abs. 3 DSGVO).",
|
||
label: "Einwilligung",
|
||
},
|
||
vertrag: {
|
||
kopf: "**Art. 6 Abs. 1 lit. b DSGVO** – Erfüllung eines Vertrags oder Durchführung vorvertraglicher Maßnahmen.",
|
||
label: "Vertrag",
|
||
},
|
||
}
|
||
|
||
/** Liest Fachbereich und Fachdienst aus der Bezeichnung der Tätigkeit. */
|
||
function organisation(beschreibung) {
|
||
const lies = (feld) => {
|
||
const treffer = new RegExp(`^${feld}: (.+)$`, "m").exec(beschreibung || "")
|
||
return treffer ? treffer[1].trim() : ""
|
||
}
|
||
return { bereich: lies("Fachbereich"), dienst: lies("Fachdienst") }
|
||
}
|
||
|
||
/** Ordnet einer Erklärung die zutreffende DSGVO-Grundlage zu. */
|
||
function einordnen(policy) {
|
||
const norm = (policy.legalBasis || "").toLowerCase()
|
||
const { bereich, dienst } = organisation(policy.processingDescription)
|
||
|
||
// Personalangelegenheiten zuerst: Sie nennen oft BGB/TVöD und würden sonst
|
||
// als reine Vertragsverhältnisse durchgehen.
|
||
if (dienst === "Personalamt" || bereich === "LVB") return "beschaeftigung"
|
||
|
||
if (norm.includes("einwilligung")) return "einwilligung"
|
||
|
||
// Privatrechtliches Handeln der Gemeinde
|
||
if (/(^|[^a-z])(miet|pacht|kauf)vertrag/.test(norm)) return "vertrag"
|
||
if (norm.includes("§ 311b bgb")) return "vertrag"
|
||
if (norm.includes("vereinbarung der eigentümer")) return "vertrag"
|
||
|
||
return "hoheitlich"
|
||
}
|
||
|
||
async function main() {
|
||
const dryRun = process.argv.includes("--dry-run")
|
||
const policies = await prisma.privacyPolicy.findMany({ orderBy: { title: "asc" } })
|
||
|
||
const geplant = []
|
||
const ohneAngabe = []
|
||
const schonErgaenzt = []
|
||
|
||
for (const policy of policies) {
|
||
const bestand = (policy.legalBasis || "").trim()
|
||
|
||
if (!bestand) {
|
||
ohneAngabe.push(policy)
|
||
continue
|
||
}
|
||
if (bestand.includes("DSGVO")) {
|
||
schonErgaenzt.push(policy)
|
||
continue
|
||
}
|
||
|
||
const art = einordnen(policy)
|
||
const neu = `${GRUNDLAGEN[art].kopf}\n\nKonkrete Rechtsgrundlage: ${bestand}`
|
||
geplant.push({ policy, art, alt: bestand, neu })
|
||
}
|
||
|
||
const nachArt = {}
|
||
geplant.forEach((g) => {
|
||
nachArt[g.art] = nachArt[g.art] || []
|
||
nachArt[g.art].push(g)
|
||
})
|
||
|
||
console.log(`Erklärungen gesamt: ${policies.length}`)
|
||
console.log(`Zu ergänzen: ${geplant.length}`)
|
||
console.log(`Bereits ergänzt: ${schonErgaenzt.length}`)
|
||
console.log(`Ohne jede Angabe: ${ohneAngabe.length} (werden nicht angefasst)`)
|
||
console.log("")
|
||
|
||
for (const [art, liste] of Object.entries(nachArt)) {
|
||
console.log(`=== ${GRUNDLAGEN[art].label} (${liste.length}) ===`)
|
||
console.log(` ${GRUNDLAGEN[art].kopf.replace(/\*\*/g, "")}`)
|
||
if (art !== "hoheitlich") {
|
||
liste.forEach((g) => console.log(` ${g.policy.title} – „${g.alt}"`))
|
||
}
|
||
console.log("")
|
||
}
|
||
|
||
console.log(`=== Ohne Rechtsgrundlage – bleiben offen (${ohneAngabe.length}) ===`)
|
||
ohneAngabe.forEach((p) => console.log(` ${p.title}`))
|
||
console.log("")
|
||
|
||
if (dryRun) {
|
||
console.log("--dry-run: nichts geschrieben.")
|
||
if (geplant.length) {
|
||
console.log("")
|
||
console.log("Beispiel:")
|
||
console.log(" vorher : " + geplant[0].alt)
|
||
console.log(" nachher: " + geplant[0].neu.replace(/\n/g, "\n "))
|
||
}
|
||
return
|
||
}
|
||
|
||
for (const { policy, neu } of geplant) {
|
||
await prisma.privacyPolicy.update({
|
||
where: { id: policy.id },
|
||
data: { legalBasis: neu },
|
||
})
|
||
|
||
// JSON-Ablage nachziehen, soweit vorhanden und beschreibbar
|
||
const datei = path.join(JSON_DIR, `${policy.slug}.json`)
|
||
try {
|
||
if (fs.existsSync(datei)) {
|
||
const inhalt = JSON.parse(fs.readFileSync(datei, "utf8"))
|
||
inhalt.legalBasis = neu
|
||
fs.writeFileSync(datei, JSON.stringify(inhalt, null, 2), "utf8")
|
||
}
|
||
} catch {
|
||
// Die Datenbank ist maßgeblich; die Ablage ist nur Nachweis.
|
||
}
|
||
}
|
||
|
||
console.log(`Ergänzt: ${geplant.length}`)
|
||
}
|
||
|
||
main()
|
||
.catch((e) => {
|
||
console.error(e)
|
||
process.exitCode = 1
|
||
})
|
||
.finally(() => prisma.$disconnect())
|