From 99d7e2bdca2a3731e7f789a434aaf65f36635583 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 16 Oct 2026 16:10:31 +0000 Subject: [PATCH] =?UTF-8?q?Rechtstexte:=20PDF-Bau=20f=C3=BCr=20Fassung=206?= =?UTF-8?q?=20(Modi=20abo6=20und=20funktionen6,=20nach=20Freigabe)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5.5 --- scripts/build-legal-pdfs.sh | 47 ++++++++++++++++++++++++++++++++++++- test/legalV6Build.test.js | 27 ++++++++++++++++++++- 2 files changed, 72 insertions(+), 2 deletions(-) diff --git a/scripts/build-legal-pdfs.sh b/scripts/build-legal-pdfs.sh index c2f0637..9995671 100755 --- a/scripts/build-legal-pdfs.sh +++ b/scripts/build-legal-pdfs.sh @@ -15,6 +15,11 @@ # auftraege = Widerrufsbelehrung der Projektauftraege (Fassung TEXTE_VERSION, Standard: TEXTE_FASSUNG aus server/services/auftraege/texte.js) und AGB # (Fassung AGB_VERSION, Standard: AGB_FASSUNG aus server/services/auftraege/konstanten.js); die HTML-Seiten kommen vorher aus # `node scripts/build-legal-html.mjs auftraege`, Pruefsumme und Neudruck wie bei termine und shop. `all` druckt auch diese beiden PDFs. +# abo6 = Abo-Bedingungen Fassung 6 (public/neu/rechtliches/abo-bedingungen.html -> WEBklar-Abo-Vertragsunterlagen.pdf daneben, .sha256 mit der SHA-256 der +# Seite, Neudruck nur bei geaenderter Seite, FORCE=1 druckt neu). Entwurf; erst nach der Freigabe drucken, vorher `free -m`, mit `nice`. `all` druckt diese Datei nicht. +# funktionen6 = die zehn Funktionsunterlagen der Fassung 6 (Leistungsbeschreibungen und AVV, Fassung FUNKTIONEN6_VERSION, Standard: FUNKTIONEN_FASSUNG_V6 aus +# server/services/products.js) aus public/neu/rechtliches/-.html nach -.pdf; die HTML-Seiten kommen vorher aus +# `node scripts/build-legal-html.mjs funktionen6`, Pruefsumme und Neudruck wie bei abo6. Entwurf; erst nach der Freigabe drucken, vorher `free -m`, mit `nice`. # datenschutz = nur die Datenschutzerklaerung (docs/WEBklar-Datenschutzerklaerung.pdf); daneben liegt .sha256 mit der SHA-256 von # public/datenschutz.html (test/legalTexts.test.js prueft sie); ein PDF mit passender Pruefsumme wird nicht neu gedruckt (FORCE=1 druckt neu) # Vorher `free -m` pruefen und mit `nice` aufrufen; jeder Chrome-Aufruf laeuft hoechstens 120 Sekunden (timeout), die PDFs nacheinander. @@ -82,6 +87,16 @@ agb_version() { echo "$v" } +# Fassung der Funktionsunterlagen 6: FUNKTIONEN6_VERSION, sonst FUNKTIONEN_FASSUNG_V6 aus server/services/products.js +funktionen6_version() { + local v="${FUNKTIONEN6_VERSION:-}" + if [ -z "$v" ]; then + v="$(timeout 60 node --max-old-space-size=256 --input-type=module -e "const { FUNKTIONEN_FASSUNG_V6 } = await import('./server/services/products.js'); console.log(FUNKTIONEN_FASSUNG_V6)")" + fi + [[ "$v" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]] || { echo "Ungueltige Fassung: $v" >&2; exit 1; } + echo "$v" +} + # Widerrufsbelehrung der Projektauftraege und AGB drucken (je PDF nur, wenn die Pruefsumme der HTML-Seite nicht mehr passt) auftraege_pdfs() { local texte agb eintrag slug fassung quelle ziel summe @@ -173,6 +188,36 @@ datenschutz) echo "$summe" > "$ziel.sha256" fi ;; +abo6) + # Entwurf Fassung 6 der Abo-Bedingungen: Seite und PDF liegen im Vorschaupfad; erst nach der Freigabe drucken + quelle="public/neu/rechtliches/abo-bedingungen.html" + ziel="public/neu/rechtliches/WEBklar-Abo-Vertragsunterlagen.pdf" + [ -f "$quelle" ] || { echo "HTML fehlt: $quelle"; exit 1; } + summe="$(sha256sum "$quelle" | cut -d' ' -f1)" + if [ -z "${FORCE:-}" ] && [ -f "$ziel" ] && [ "$(cat "$ziel.sha256" 2>/dev/null)" = "$summe" ]; then + echo "= $ziel (unveraendert)" + else + pdf "$quelle" "$ziel" + echo "$summe" > "$ziel.sha256" + fi + ;; +funktionen6) + # Entwurf Fassung 6 der Funktionsunterlagen (zehn Seiten): Leistungsbeschreibungen und AVV, nacheinander, nie parallel + fassung="$(funktionen6_version)" + for slug in leistungsbeschreibung-news leistungsbeschreibung-terminplaner avv-terminplaner leistungsbeschreibung-webshop avv-webshop \ + leistungsbeschreibung-unterkunft avv-unterkunft leistungsbeschreibung-zeiten leistungsbeschreibung-website-analyse avv-website-analyse; do + quelle="public/neu/rechtliches/$slug-$fassung.html" + ziel="public/neu/rechtliches/$slug-$fassung.pdf" + [ -f "$quelle" ] || { echo "HTML fehlt: $quelle (zuerst node scripts/build-legal-html.mjs funktionen6)"; exit 1; } + summe="$(sha256sum "$quelle" | cut -d' ' -f1)" + if [ -z "${FORCE:-}" ] && [ -f "$ziel" ] && [ "$(cat "$ziel.sha256" 2>/dev/null)" = "$summe" ]; then + echo "= $ziel (unveraendert)" + else + pdf "$quelle" "$ziel" + echo "$summe" > "$ziel.sha256" + fi + done + ;; all) pdf docs/leitfaden-empfehlungsgeber.html docs/WEBklar-Leitfaden-Empfehlungsgeber.pdf # Fassung 3 (public/WEBklar-Wartungs-Abo-Vertragsunterlagen.pdf, Archiv unter public/vertragsunterlagen/v3/) wird nicht neu erzeugt @@ -182,6 +227,6 @@ all) auftraege_pdfs ;; *) - echo "Usage: $0 [all|analyse|termine|shop|unterkunft|datenschutz]"; echo " $0 auftraege (Widerrufsbelehrung der Projektauftraege und AGB)"; exit 1 + echo "Usage: $0 [all|analyse|termine|shop|unterkunft|datenschutz]"; echo " $0 auftraege (Widerrufsbelehrung der Projektauftraege und AGB)"; echo " $0 abo6|funktionen6 (Entwurf Fassung 6: Abo-Bedingungen bzw. Funktionsunterlagen, erst nach der Freigabe)"; exit 1 ;; esac diff --git a/test/legalV6Build.test.js b/test/legalV6Build.test.js index 922986a..bd0e40b 100644 --- a/test/legalV6Build.test.js +++ b/test/legalV6Build.test.js @@ -1,8 +1,11 @@ import { test } from 'node:test' import assert from 'node:assert/strict' -import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs' +import { createHash } from 'node:crypto' +import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' +import { fileURLToPath } from 'node:url' +import { spawnSync } from 'node:child_process' import { useTestEnv } from './support/testEnv.js' // Generator der Rechtstexte: neuer Stil und Satz funktionen6 (Fassung 6, Entwurf). Ergänzt test/buildLegalHtml.test.js. @@ -10,6 +13,7 @@ import { useTestEnv } from './support/testEnv.js' useTestEnv({ appwriteUrl: 'http://127.0.0.1:1', stripeUrl: 'http://127.0.0.1:1' }) const { render, lauf, funktionen6Dokumente } = await import('../scripts/build-legal-html.mjs') +const ROOT = fileURLToPath(new URL('..', import.meta.url)) const F = '2026-10-16' const SLUGS = ['leistungsbeschreibung-news', 'leistungsbeschreibung-terminplaner', 'avv-terminplaner', 'leistungsbeschreibung-webshop', 'avv-webshop', 'leistungsbeschreibung-unterkunft', 'avv-unterkunft', 'leistungsbeschreibung-zeiten', 'leistungsbeschreibung-website-analyse', 'avv-website-analyse'] test('funktionen6Dokumente: zehn Seiten unter public/neu/rechtliches, Quellen *-v6.md, Navigation im Vorschaupfad', () => { @@ -49,3 +53,24 @@ test('lauf funktionen6: schreibt in neuen Ordner, --check, --nur, falscher Aufru assert.equal(ruf('funktionen6', '--schreiben'), 2); assert.equal(ruf('shop', '--nur=x'), 2); assert.equal(ruf('funktionen6', '--nur=gibt-es-nicht'), 2) } finally { rmSync(dir, { recursive: true, force: true }) } }) +test('build-legal-pdfs.sh: Modi abo6 und funktionen6, all und erste Usage-Zeile unverändert, nie parallel', () => { + const sh = readFileSync(join(ROOT, 'scripts/build-legal-pdfs.sh'), 'utf8') + assert.match(sh, /^abo6\)$/m); assert.match(sh, /^funktionen6\)$/m) + const abo6 = sh.slice(sh.indexOf('\nabo6)'), sh.indexOf('\nfunktionen6)')) + assert.ok(abo6.includes('public/neu/rechtliches/abo-bedingungen.html') && abo6.includes('public/neu/rechtliches/WEBklar-Abo-Vertragsunterlagen.pdf') && abo6.includes('.sha256')) + for (const s of SLUGS) assert.ok(sh.includes(s), s) + assert.ok(sh.includes('FUNKTIONEN_FASSUNG_V6') && sh.includes('zuerst node scripts/build-legal-html.mjs funktionen6')) + const all = sh.slice(sh.indexOf('\nall)'), sh.indexOf('\n*)')) + assert.ok(!all.includes('neu/rechtliches') && all.includes(' pdf public/abo-bedingungen.html public/WEBklar-Abo-Vertragsunterlagen.pdf\n'), 'all druckt die Entwürfe nicht') + assert.ok(sh.includes('Usage: $0 [all|analyse|termine|shop|unterkunft|datenschutz]') && sh.includes('abo6|funktionen6')) + assert.match(sh, /^ {2}timeout 120 "\$CHROME" /m) + assert.ok(!/ &$|^\s*wait\b|xargs -P/m.test(sh)) + assert.equal(spawnSync('bash', ['-n', join(ROOT, 'scripts/build-legal-pdfs.sh')], { encoding: 'utf8' }).status, 0) +}) +test('PDFs der Fassung 6: erst nach der Freigabe vorhanden; wenn vorhanden, PDF, Prüfsumme der Seite daneben', { skip: !existsSync(join(ROOT, 'public/neu/rechtliches/WEBklar-Abo-Vertragsunterlagen.pdf')) && 'PDFs entstehen erst nach der Freigabe (Task 11, Ablauf)' }, () => { + for (const p of ['abo-bedingungen.html', ...SLUGS.map((s) => `${s}-2026-10-16.html`)]) { + const pdf = join(ROOT, 'public/neu/rechtliches', p === 'abo-bedingungen.html' ? 'WEBklar-Abo-Vertragsunterlagen.pdf' : p.replace('.html', '.pdf')) + assert.equal(readFileSync(pdf).subarray(0, 5).toString(), '%PDF-', pdf) + assert.equal(readFileSync(`${pdf}.sha256`, 'utf8').trim(), createHash('sha256').update(readFileSync(join(ROOT, 'public/neu/rechtliches', p))).digest('hex'), p) + } +})