Gaestetexte/Datenschutz: Storno-Regel im Archiv, PDF-Pruefsumme, PDF-Kopfpruefung
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
9a2a70b491
commit
ec320853ce
13 files changed
+124
-12
No files matched your search
@@ -0,0 +1 @@
|
||||
2f7af8a9c50a3af5ddc5083a3ddc657019ea02628a8f795c84f57ae40d20f473
|
||||
@@ -115,7 +115,7 @@ Bricht das Netz mitten in der Strecke ab, zeigt das Widget „Gerade nicht errei
|
||||
2. Person wählen („Egal“ zuerst). Nur wenn mehrere Personen infrage kommen und der Betrieb die Wahl erlaubt; bei Plätzen entfällt der Schritt.
|
||||
3. Tag (Monatsraster, nur Tage mit freien Zeiten sind wählbar) und Uhrzeit. Alle Daten und Uhrzeiten kommen vom Server in Berliner Zeit. Das Widget rechnet nie mit dem Datum oder der Zeitzone des Browsers.
|
||||
4. Angaben: Name, E-Mail, Telefon, Nachricht und Zusatzfelder nach den Einstellungen des Betriebs.
|
||||
5. Zusammenfassung mit Beschreibung der Leistung, Stornobedingungen (daneben der Link „AGB des Betriebs“, wenn im Portal eine https-Adresse hinterlegt ist), Widerrufsbelehrung (aufklappbar, bei Verbraucher-Betrieben), Datenschutz-Hinweis, den Haken (Storno, vorzeitiger Beginn, Erinnerung) und dem Knopf („Zahlungspflichtig buchen“, „Termin buchen“ oder „Termin anfragen“, kommt vom Server).
|
||||
5. Zusammenfassung mit Beschreibung der Leistung, Stornobedingungen (nur bei einer Leistung mit Preis; daneben der Link „AGB des Betriebs“, wenn im Portal eine https-Adresse hinterlegt ist), Widerrufsbelehrung (aufklappbar, bei Verbraucher-Betrieben), Datenschutz-Hinweis, den Haken (Storno nur bei einer Leistung mit Preis, vorzeitiger Beginn, Erinnerung) und dem Knopf („Zahlungspflichtig buchen“, „Termin buchen“ oder „Termin anfragen“, kommt vom Server).
|
||||
6. „Fast geschafft“: Der Gast bestätigt den Termin über den Link in der E-Mail. Die Reservierung gilt 30 Minuten.
|
||||
|
||||
Im Fuß stehen „Vertrag widerrufen“ als zweiter Knopf (kein bloßer Textlink; für Verbraucher bei jeder Leistung, ob sofort buchbar oder als Anfrage, weil auch ein angenommener Anfrage-Vertrag widerrufen werden kann), „Datenschutz“ (Link auf die Datenschutzerklärung des Betriebs) und klein „Terminbuchung über WEBklar“. Im Modus `knopf` steht der Fuß nur im geöffneten Dialog. Damit die Widerrufsfunktion (§ 356a BGB) trotzdem dauerhaft auffindbar ist, lädt das Widget dort schon beim Laden der Seite die Konfiguration (ein einziger Abruf, sonst nichts) und setzt, wenn der Betrieb ein Widerrufsrecht hat, den Knopf „Vertrag widerrufen“ als zweiten Knopf neben den Buchungsknopf. Er öffnet den Dialog gleich beim Widerrufsformular. Anrede (Sie oder Du) und Betriebsfarbe kommen aus den Einstellungen. Die rechtlich vorgeschriebenen Texte (Haken, Belehrung) stehen immer in der Sie-Form.
|
||||
|
||||
@@ -31,4 +31,4 @@ Die Texte, die ein Gast beim Buchen sieht (Hinweis am Formular, Texte der Haken,
|
||||
|
||||
„Gültig ab“ einer neuen Fassung wird beim Livegang eingetragen; ihr Zeitraum endet mit dem „gültig ab“ der nächsten.
|
||||
|
||||
Änderungen der Fassung 2026-10-05 gegenüber 2026-10-02 (nur diese beiden Stellen): Der Haken für die Erinnerung endet mit dem Satz „Diese Einwilligung kann ich jederzeit widerrufen.“ (Art. 7 Abs. 3 DSGVO); der Datenschutz-Absatz sagt bei der Abschaltung: „Wird die Online-Terminbuchung abgeschaltet, zum Beispiel weil unser Vertrag mit WEBklar endet, werden alle Termindaten 30 Tage nach dem Abschalten gelöscht.“ Buchungen, die vor dem Livegang der neuen Fassung entstanden sind, behalten im Nachweis die Fassung 2026-10-02.
|
||||
Änderungen der Fassung 2026-10-05 gegenüber 2026-10-02 (Textänderungen: nur diese beiden Stellen): Der Haken für die Erinnerung endet mit dem Satz „Diese Einwilligung kann ich jederzeit widerrufen.“ (Art. 7 Abs. 3 DSGVO); der Datenschutz-Absatz sagt bei der Abschaltung: „Wird die Online-Terminbuchung abgeschaltet, zum Beispiel weil unser Vertrag mit WEBklar endet, werden alle Termindaten 30 Tage nach dem Abschalten gelöscht.“ Außerdem ändert sich die Regel zum Haken „Stornobedingungen“, nicht sein Text: Pflicht ist er nur noch bei einer Leistung mit Preis, bei kostenlosen Leistungen fragt der Terminplaner nicht danach (die Fassung 2026-10-02 verlangte ihn bei jeder Leistung, sobald der Betrieb Stornobedingungen hat). Buchungen, die vor dem Livegang der neuen Fassung entstanden sind, behalten im Nachweis die Fassung 2026-10-02.
|
||||
@@ -26,7 +26,7 @@ Die Haken stehen immer in der Sie-Form, auch bei Betrieben, die ihre Gäste duze
|
||||
|
||||
> Ich verlange ausdrücklich, dass {{Name des Betriebs}} mit der Leistung vor Ablauf der Widerrufsfrist beginnt. Mir ist bekannt, dass mein Widerrufsrecht mit der vollständigen Erbringung der Leistung erlischt (§ 356 Abs. 5 Nr. 2 BGB). Widerrufe ich vor der vollständigen Erbringung, schulde ich einen angemessenen Betrag für die bis dahin erbrachte Leistung (§ 357a Abs. 2 BGB).
|
||||
|
||||
### Stornobedingungen (Pflicht, wenn der Betrieb Stornobedingungen hat; der Text der Bedingungen steht beim Betrieb und trägt eine eigene Version)
|
||||
### Stornobedingungen (Pflicht bei einer Leistung mit Preis, wenn der Betrieb Stornobedingungen hat; bei kostenlosen Leistungen fragt der Terminplaner nicht danach; der Text der Bedingungen steht beim Betrieb und trägt eine eigene Version)
|
||||
|
||||
> Ich habe die Stornobedingungen gelesen.
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ Wortlaut immer in der Sie-Form, auch bei Betrieben, die ihre Gäste duzen: Es si
|
||||
| Haken | Wann | Pflicht | Wortlaut |
|
||||
|---|---|---|---|
|
||||
| Vorzeitiger Beginn | Verbraucher, mit Preis: bei sofort buchbaren Leistungen, wenn der Terminbeginn nicht nach dem Ende der Widerrufsfrist liegt (14 Tage ab dem Tag nach dem Vertragsschluss, Berliner Kalender, §§ 187, 188, 193 BGB; gerechnet mit dem Zeitpunkt der Buchung plus Haltezeit; R29); bei bepreisten Anfragen immer, weil der Tag der Annahme nicht feststeht (R31) | ja | Ich verlange ausdrücklich, dass {{Firma}} mit der Leistung vor Ablauf der Widerrufsfrist beginnt. Mir ist bekannt, dass mein Widerrufsrecht mit der vollständigen Erbringung der Leistung erlischt (§ 356 Abs. 5 Nr. 2 BGB). Widerrufe ich vor der vollständigen Erbringung, schulde ich einen angemessenen Betrag für die bis dahin erbrachte Leistung (§ 357a Abs. 2 BGB). |
|
||||
| Stornobedingungen | der Betrieb hat Stornobedingungen hinterlegt | ja | Ich habe die Stornobedingungen gelesen. |
|
||||
| Stornobedingungen | der Betrieb hat Stornobedingungen hinterlegt und die gebuchte Leistung hat einen Preis (bei kostenlosen Leistungen fragt der Terminplaner nicht danach) | ja | Ich habe die Stornobedingungen gelesen. |
|
||||
| Erinnerung | der Betrieb bietet Erinnerungen an | nein | Ich möchte {{n}} Stunden vor dem Termin eine Erinnerung per E-Mail erhalten. Diese Einwilligung kann ich jederzeit widerrufen. |
|
||||
|
||||
## 3. Hinweis über dem Nachrichtenfeld
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
# die HTML-Seiten kommen vorher aus `node scripts/build-legal-html.mjs termine`. Neben jedem PDF liegt <PDF>.sha256 mit der
|
||||
# SHA-256 der HTML-Seite, aus der es gedruckt wurde (test/buildLegalHtml.test.js prueft, dass beide zusammenpassen). Ein PDF,
|
||||
# dessen Pruefsumme noch stimmt, wird nicht neu gedruckt (FORCE=1 druckt alle neu).
|
||||
# datenschutz = nur die Datenschutzerklaerung (docs/WEBklar-Datenschutzerklaerung.pdf)
|
||||
# datenschutz = nur die Datenschutzerklaerung (docs/WEBklar-Datenschutzerklaerung.pdf); daneben liegt <PDF>.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.
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
@@ -54,7 +55,15 @@ termine)
|
||||
done
|
||||
;;
|
||||
datenschutz)
|
||||
pdf public/datenschutz.html docs/WEBklar-Datenschutzerklaerung.pdf
|
||||
quelle="public/datenschutz.html"
|
||||
ziel="docs/WEBklar-Datenschutzerklaerung.pdf"
|
||||
summe="$(sha256sum "$quelle" | cut -d' ' -f1)"
|
||||
if [ -z "${FORCE:-}" ] && [ -f "$ziel" ] && [ "$(cat "$ziel.sha256" 2>/dev/null)" = "$summe" ]; then
|
||||
echo "= $ziel (unveraendert)"
|
||||
else
|
||||
pdf public/datenschutz.html docs/WEBklar-Datenschutzerklaerung.pdf
|
||||
echo "$summe" > "$ziel.sha256"
|
||||
fi
|
||||
;;
|
||||
all)
|
||||
pdf docs/leitfaden-empfehlungsgeber.html docs/WEBklar-Leitfaden-Empfehlungsgeber.pdf
|
||||
|
||||
@@ -91,7 +91,7 @@ export function baueArchiv() {
|
||||
'Die Haken stehen immer in der Sie-Form, auch bei Betrieben, die ihre Gäste duzen.',
|
||||
'### Vorzeitiger Beginn (Pflicht bei einer Leistung mit Preis für Verbraucher, wenn der Termin vor Ablauf der Widerrufsfrist beginnt, bei Terminanfragen immer)',
|
||||
zitat(ZUSTIMMUNG_VORZEITIG(PLATZHALTER_BETRIEB.name)),
|
||||
'### Stornobedingungen (Pflicht, wenn der Betrieb Stornobedingungen hat; der Text der Bedingungen steht beim Betrieb und trägt eine eigene Version)',
|
||||
'### Stornobedingungen (Pflicht bei einer Leistung mit Preis, wenn der Betrieb Stornobedingungen hat; bei kostenlosen Leistungen fragt der Terminplaner nicht danach; der Text der Bedingungen steht beim Betrieb und trägt eine eigene Version)',
|
||||
zitat(ZUSTIMMUNG_STORNO),
|
||||
'### Erinnerung (freiwillig, nie vorangekreuzt, nur wenn der Betrieb Erinnerungen anbietet)',
|
||||
zitat(ZUSTIMMUNG_ERINNERUNG(PLATZHALTER_STUNDEN)),
|
||||
|
||||
@@ -66,9 +66,13 @@ export function legalPdfFiles(satz, version) {
|
||||
)
|
||||
}
|
||||
|
||||
/** Ein Anhang ist ein PDF: nicht leer und mit "%PDF-" am Anfang. */
|
||||
const istPdf = (inhalt) => inhalt.length >= 5 && inhalt.subarray(0, 5).toString('latin1') === '%PDF-'
|
||||
|
||||
/**
|
||||
* PDFs als Mail-Anhaenge in der Reihenfolge des Satzes (termine: lb, avv, widerruf; analyse: avv, lb); wirft, wenn eine
|
||||
* Datei fehlt oder nicht lesbar ist. ohne: Dokumente, die nicht angehaengt werden (sie muessen nicht vorhanden sein).
|
||||
* Datei fehlt, nicht lesbar, leer oder kein PDF ist (nie ein leerer oder falscher Anhang). ohne: Dokumente, die nicht
|
||||
* angehaengt werden (sie muessen nicht vorhanden sein).
|
||||
*/
|
||||
export async function legalPdfAttachments(satz, version, { ohne = [] } = {}) {
|
||||
const dateien = legalPdfFiles(satz, version)
|
||||
@@ -78,7 +82,11 @@ export async function legalPdfAttachments(satz, version, { ohne = [] } = {}) {
|
||||
return Promise.all(
|
||||
Object.entries(dateien)
|
||||
.filter(([doc]) => !ohne.includes(doc))
|
||||
.map(async ([, f]) => ({ filename: f.filename, content: await readFile(f.path), contentType: 'application/pdf' }))
|
||||
.map(async ([, f]) => {
|
||||
const content = await readFile(f.path)
|
||||
if (!istPdf(content)) throw new Error(`Rechtstexte: ${f.filename} ist leer oder kein PDF`)
|
||||
return { filename: f.filename, content, contentType: 'application/pdf' }
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -386,7 +386,7 @@ export const ZUSTIMMUNG_VORZEITIG = (name) =>
|
||||
+ 'Mir ist bekannt, dass mein Widerrufsrecht mit der vollständigen Erbringung der Leistung erlischt (§ 356 Abs. 5 Nr. 2 BGB). '
|
||||
+ 'Widerrufe ich vor der vollständigen Erbringung, schulde ich einen angemessenen Betrag für die bis dahin erbrachte Leistung (§ 357a Abs. 2 BGB).'
|
||||
|
||||
/** Pflicht-Haken, wenn der Betrieb Stornobedingungen hat. */
|
||||
/** Pflicht-Haken, wenn der Betrieb Stornobedingungen hat und die gebuchte Leistung einen Preis hat (bei kostenlosen Leistungen gibt es ihn nicht). */
|
||||
export const ZUSTIMMUNG_STORNO = 'Ich habe die Stornobedingungen gelesen.'
|
||||
|
||||
/**
|
||||
|
||||
@@ -1559,6 +1559,43 @@ test('textform_pflicht_modus_on: fremdes Projekt, Kundensitzung und nicht lesbar
|
||||
assert.equal((await aktivierenF13(k, feldF13(k))).status, 200)
|
||||
})
|
||||
|
||||
test('textform_pflicht_modus_on: eine leere oder keine PDF-Datei als Unterlage ergibt 503 vor jeder Änderung (R121), danach mit gültigen Dateien 200', async (t) => {
|
||||
modusMerken(t)
|
||||
termineMit('on', [])
|
||||
const fs = await import('node:fs')
|
||||
const os = await import('node:os')
|
||||
const path = await import('node:path')
|
||||
const { legalPdfFiles } = await import('../server/services/legalDocs.js')
|
||||
const echt = legalPdfFiles('termine', config.termine.avvVersion)
|
||||
const kopien = fs.mkdtempSync(path.join(os.tmpdir(), 'f13-pdf-'))
|
||||
fs.mkdirSync(path.join(kopien, 'rechtliches'))
|
||||
const kopiere = () => {
|
||||
for (const f of Object.values(echt)) fs.copyFileSync(f.path, path.join(kopien, 'rechtliches', path.basename(f.path)))
|
||||
}
|
||||
t.after(() => {
|
||||
setLegalDirForTests(null)
|
||||
fs.rmSync(kopien, { recursive: true, force: true })
|
||||
})
|
||||
setLegalDirForTests(kopien)
|
||||
const k = legeKundeF13()
|
||||
const mailsVorher = mails.sent.length
|
||||
for (const [doc, inhalt] of [['lb', ''], ['avv', '<html>kein PDF</html>'], ['lb', '%PDF']]) {
|
||||
kopiere()
|
||||
fs.writeFileSync(path.join(kopien, 'rechtliches', path.basename(echt[doc].path)), inhalt)
|
||||
const res = await aktivierenF13(k, feldF13(k))
|
||||
assert.equal(res.status, 503, `${doc}: ${JSON.stringify(inhalt)}`)
|
||||
assert.match(res.body.error, /Rechtsunterlagen/)
|
||||
assert.equal(await sf.getFeatureDoc(k.customerId, k.projectId, 'termine'), null, 'nichts angelegt')
|
||||
assert.equal(consentsDesF13(k.projectId).length, 0, 'kein Nachweis')
|
||||
assert.equal(mails.sent.length, mailsVorher)
|
||||
}
|
||||
kopiere()
|
||||
const ok = await aktivierenF13(k, feldF13(k))
|
||||
assert.equal(ok.status, 200, JSON.stringify(ok.body))
|
||||
assert.equal(consentsDesF13(k.projectId).length, 1)
|
||||
assert.equal(k16AnF13(k.email).length, 1)
|
||||
})
|
||||
|
||||
test('get_admin_termine_feld: GET /api/team/funktionen/:kunde liefert termine { textformNoetig, avvVersion, links } je Modus', async (t) => {
|
||||
modusMerken(t)
|
||||
const k = legeKundeF13({ email: 'get-f13@example.invalid' })
|
||||
|
||||
@@ -200,6 +200,39 @@ test('featureLegalReady("termine"): je PDF stat und "%PDF-", nie den ganzen Inha
|
||||
assert.equal(await featureLegalReady('termine'), true, 'mit den ausgelieferten Dateien bereit')
|
||||
})
|
||||
|
||||
// R121: ein Anhang ist ein PDF. Eine leere oder keine PDF-Datei wird nie angehängt, sondern wirft (die Aufrufer fangen den Wurf).
|
||||
test('legalPdfAttachments: eine leere Datei oder eine Datei ohne "%PDF-" am Anfang wirft, ausgenommene Dokumente zählen nicht', async () => {
|
||||
const kopien = mkdtempSync(join(tmpdir(), 'legal-anhang-'))
|
||||
mkdirSync(join(kopien, 'rechtliches'))
|
||||
const echtePdf = Object.fromEntries(Object.entries(legalPdfFiles('termine', V)).map(([doc, f]) => [doc, f.path]))
|
||||
const pdfPfad = (doc) => join(kopien, 'rechtliches', echtePdf[doc].split('/').at(-1))
|
||||
const bereit = () => {
|
||||
for (const pfad of Object.values(echtePdf)) copyFileSync(pfad, join(kopien, 'rechtliches', pfad.split('/').at(-1)))
|
||||
}
|
||||
try {
|
||||
setLegalDirForTests(kopien)
|
||||
bereit()
|
||||
const gut = await legalPdfAttachments('termine', V)
|
||||
assert.equal(gut.length, 3)
|
||||
for (const a of gut) assert.equal(a.content.subarray(0, 5).toString('latin1'), '%PDF-')
|
||||
for (const doc of ['lb', 'avv', 'widerruf']) {
|
||||
for (const [mangel, inhaltDatei] of [['leer', ''], ['kein PDF', '<html>kein PDF</html>'], ['zu kurz', '%PDF'], ['PDF nicht am Anfang', ' %PDF-1.4\n']]) {
|
||||
writeFileSync(pdfPfad(doc), inhaltDatei)
|
||||
await assert.rejects(legalPdfAttachments('termine', V), /PDF/, `${doc}: ${mangel}`)
|
||||
await assert.rejects(featureLegalAttachments('termine'), /PDF/, `${doc}: ${mangel} (Funktion)`)
|
||||
// ein ausgenommenes Dokument wird nicht gelesen und darf kaputt sein
|
||||
const ohne = await legalPdfAttachments('termine', V, { ohne: [doc] })
|
||||
assert.equal(ohne.length, 2, `${doc}: ${mangel}, ausgenommen`)
|
||||
}
|
||||
bereit()
|
||||
assert.equal((await legalPdfAttachments('termine', V)).length, 3, `${doc}: wieder in Ordnung`)
|
||||
}
|
||||
} finally {
|
||||
setLegalDirForTests(null)
|
||||
rmSync(kopien, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
// Fassung wählbar (Task F8, Fix-Runde 1): K15 schickt die Unterlagen der Fassung, der der Kunde zugestimmt hat
|
||||
test('featureLegalAttachments / featureLegalReady / featureLegalPaths mit opts.fassung', async () => {
|
||||
assert.deepEqual(featureLegalPaths('termine'), legalPaths('termine', V))
|
||||
|
||||
+15
-1
@@ -604,11 +604,25 @@ test('wiederkehr_satz_gleich_lb: Rückkehr des Abos innerhalb von 30 Tagen steht
|
||||
}
|
||||
})
|
||||
|
||||
test('pdf_pruefsumme: neben dem PDF der Datenschutzerklärung liegt .sha256 mit der SHA-256 von public/datenschutz.html (HTML geändert ohne neues PDF fällt auf)', () => {
|
||||
const summe = read('docs/WEBklar-Datenschutzerklaerung.pdf.sha256')
|
||||
assert.match(summe, /^[0-9a-f]{64}\n$/, 'nur die Prüfsumme und ein Zeilenende')
|
||||
assert.equal(summe.trim(), sha256(DS), 'public/datenschutz.html wurde geändert, ohne das PDF neu zu drucken (scripts/build-legal-pdfs.sh datenschutz)')
|
||||
// eine Änderung am HTML ergäbe eine andere Prüfsumme
|
||||
assert.notEqual(createHash('sha256').update(`${read(DS)} `).digest('hex'), summe.trim())
|
||||
// das Skript schreibt die Prüfsumme und druckt bei passender Prüfsumme nicht neu (FORCE=1 druckt neu)
|
||||
const sh = read('scripts/build-legal-pdfs.sh')
|
||||
const modus = sh.slice(sh.indexOf('\ndatenschutz)'), sh.indexOf('\nall)'))
|
||||
assert.ok(modus.includes('sha256sum "$quelle"') && modus.includes('echo "$summe" > "$ziel.sha256"'))
|
||||
assert.ok(modus.includes('${FORCE:-}') && modus.includes('(unveraendert)'))
|
||||
assert.ok(modus.includes('pdf public/datenschutz.html docs/WEBklar-Datenschutzerklaerung.pdf'))
|
||||
})
|
||||
|
||||
test('datenschutz_pdf: das PDF der Datenschutzerklärung ist eingecheckt (> 20 KB, %PDF) und das Skript baut es im Modus datenschutz', () => {
|
||||
const pdf = readFileSync(file('docs/WEBklar-Datenschutzerklaerung.pdf'))
|
||||
assert.ok(pdf.length > 20 * 1024, `PDF zu klein: ${pdf.length} Bytes`)
|
||||
assert.equal(pdf.subarray(0, 5).toString('latin1'), '%PDF-')
|
||||
assert.ok(pdf.subarray(-2048).toString('latin1').includes('%%EOF'), 'PDF vollständig geschrieben')
|
||||
const skript = read('scripts/build-legal-pdfs.sh')
|
||||
assert.match(skript, /datenschutz\)\n\s*pdf public\/datenschutz\.html docs\/WEBklar-Datenschutzerklaerung\.pdf\n\s*;;/)
|
||||
assert.match(skript, /\ndatenschutz\)[\s\S]*\n\s+pdf public\/datenschutz\.html docs\/WEBklar-Datenschutzerklaerung\.pdf\n[\s\S]*?;;/)
|
||||
})
|
||||
@@ -135,7 +135,17 @@ const HASH_2026_10_02 = '920633e2e23c9c98aded0c1509db5cac1465058a025aeddc76a9a12
|
||||
|
||||
// SHA-256 der Archivdatei der aktuellen Fassung: ändert sich ein Gästetext, ohne dass die Fassung (RECHTSTEXTE_VERSION) ein neues Datum
|
||||
// und diese Datei eine neue Archivdatei bekommt, schlägt der Test an. Bei einer neuen Fassung: neue Datei, neues Datum, neuer Pin.
|
||||
const HASH_AKTUELL = { '2026-10-05': '6bcad4c4a02ea5e39b3ec690d40617fdfb3cb1290aadd3304f0ecabab14c4a59' }
|
||||
const HASH_AKTUELL = { '2026-10-05': '830bc7364afc852755ca66d2213866160812f35ba7221f59198acf37588a8ada' }
|
||||
|
||||
test('archiv_storno_regel: die Fassung 2026-10-05 nennt den Storno-Haken nur für Leistungen mit Preis, die eingefrorene Fassung 2026-10-02 die alte Regel', () => {
|
||||
const neu = lies(archivPfad())
|
||||
assert.ok(neu.includes('### Stornobedingungen (Pflicht bei einer Leistung mit Preis, wenn der Betrieb Stornobedingungen hat; bei kostenlosen Leistungen fragt der Terminplaner nicht danach;'))
|
||||
assert.ok(!neu.includes('### Stornobedingungen (Pflicht, wenn der Betrieb Stornobedingungen hat;'))
|
||||
const alt = lies(`${ARCHIV_DIR}/termine-gaestetexte-2026-10-02.md`)
|
||||
assert.ok(alt.includes('### Stornobedingungen (Pflicht, wenn der Betrieb Stornobedingungen hat;'), 'die Altfassung beschreibt die alte Regel')
|
||||
assert.ok(lies('docs/rechtliches/termine-hinweise.md').includes('| Stornobedingungen | der Betrieb hat Stornobedingungen hinterlegt und die gebuchte Leistung hat einen Preis'))
|
||||
assert.ok(lies(`${ARCHIV_DIR}/README.md`).includes('Pflicht ist er nur noch bei einer Leistung mit Preis'))
|
||||
})
|
||||
|
||||
test('textstand_pin: Hash der aktuellen Archivdatei ist festgenagelt (Textänderung ohne neues Datum fällt auf)', () => {
|
||||
assert.ok(Object.hasOwn(HASH_AKTUELL, RECHTSTEXTE_VERSION), `kein Hash-Pin für die Fassung ${RECHTSTEXTE_VERSION}`)
|
||||
|
||||
Reference in new issue
Block a user