Terminplaner: Abschluss – Server (Widerruf auch bei Anfragen, Gastdaten löschen vollständig, formMinSekunden)

- R55: Widerrufsbelehrung, Anschrift-Pflicht und Knopf "Vertrag widerrufen" gelten für jede
  Verbraucher-Buchung mit sichtbarer Leistung, auch Anfragen (zeigtWiderruf); der Nachweis
  hält die Belehrung auch bei Anfragen fest. Damit bleibt die Annahme einer Anfrage nie mehr
  still ohne G02.
- "Gastdaten löschen" leert auch absageGrund und löscht noch nicht versandte Mails der Buchung
  (neu: verwerfeWartende in der Warteschlange).
- R53: GET /:siteKey/config nennt formMinSekunden.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-03 08:17:44 +00:00
1 parent 4be886a4de
commit bd65c9dc58
11 files changed
+216 -18

No files matched your search

+55
View File
@@ -373,6 +373,61 @@ test('POST vorlage ohne Telefon und Anschrift im Konto: Entwurf mit "fehlt"; oef
assert.equal((await oeffentlich('defghijklmnopqrs')).aktiv, true)
})
test('R55: Betrieb nur mit Anfrage-Leistungen (Privatpersonen) braucht die Anschrift: PATCH 400, "fehlt", oeffentlich inaktiv; danach Belehrung im Widget und G02 mit Belehrung nach "annehmen"', async () => {
const c2 = customerCookie('c2')
const oeffentlich = (siteKey) => fetch(`${app.url}/api/public/termine/v1/${siteKey}/config`).then((r) => r.json())
const SCHLUESSEL = 'defghijklmnopqrs'
const res = await send('POST', '/api/termine/einstellungen/vorlage', { cookie: c2, body: { projectId: 'p6', vorlage: 'handwerk' } })
assert.equal(res.status, 201, JSON.stringify(res.body))
const entwurf = res.body.einstellung
assert.ok(entwurf.leistungen.every((l) => l.buchungsart === 'anfrage'), 'Vorlage: nur Anfragen')
assert.equal(entwurf.recht.gaesteSind, 'gemischt', 'Verbraucher sind dabei')
assert.deepEqual(res.body.fehlt, ['betrieb.telefon', 'betrieb.adresse'], 'auch ohne sofort-Leistung fehlt die Anschrift')
assert.deepEqual(await oeffentlich(SCHLUESSEL), { aktiv: false })
// nur das Telefon reicht nicht: PATCH nennt die Anschrift
const nurTelefon = { ...entwurf, betrieb: { ...entwurf.betrieb, telefon: '0151 2345678' } }
const abgelehnt = await send('PATCH', '/api/termine/einstellungen', { cookie: c2, body: { projectId: 'p6', version: 1, einstellung: nurTelefon } })
assert.equal(abgelehnt.status, 400)
assert.deepEqual(Object.keys(abgelehnt.body.fehler), ['betrieb.adresse'])
assert.deepEqual(await oeffentlich(SCHLUESSEL), { aktiv: false })
// mit Anschrift: speichert, Widget bekommt Belehrung (widerruf true)
const komplett = { ...nurTelefon, betrieb: { ...nurTelefon.betrieb, adresse: 'Weg 1, 12345 Ort' } }
const ok = await send('PATCH', '/api/termine/einstellungen', { cookie: c2, body: { projectId: 'p6', version: 1, einstellung: komplett } })
assert.equal(ok.status, 200, JSON.stringify(ok.body))
assert.deepEqual(ok.body.fehlt ?? [], [])
const config = await oeffentlich(SCHLUESSEL)
assert.equal(config.aktiv, true)
assert.equal(config.widerruf, true)
assert.match(config.texte.widerrufsbelehrung, /Widerrufsbelehrung/)
assert.match(config.texte.widerrufsbelehrung, /Weg 1, 12345 Ort/)
// annehmen: die Anfrage wird bestaetigt und G02 mit Belehrung geht raus (nicht still verschluckt)
const leistung = ok.body.einstellung.leistungen[0]
seedBuchung('b_handwerk', {
featureId: 'f_p6_termine',
customerId: 'c2',
projectId: 'p6',
status: 'angefragt',
leistungId: leistung.id,
leistungName: leistung.name,
ressourceId: leistung.ressourcen[0],
preisCent: 0,
preisArt: 'keiner',
buchungsart: 'anfrage',
})
const angenommen = await send('POST', '/api/termine/buchungen/b_handwerk/aktion', { cookie: c2, body: { projectId: 'p6', aktion: 'annehmen' } })
assert.equal(angenommen.status, 200, JSON.stringify(angenommen.body))
assert.equal(aw.docs('termineBookings').get('b_handwerk').status, 'bestaetigt')
await warteAufVersand()
assert.deepEqual(eintraege().map((m) => m.art), ['G02'])
assert.equal(sink.sent.length, 1)
assert.equal(sink.sent[0].to[0].address, 'max@example.com')
assert.match(sink.sent[0].text, /Widerrufsbelehrung/)
assert.match(sink.sent[0].text, /Weg 1, 12345 Ort/)
})
// ---------------------------------------------------------------- Einstellungen: PATCH
test('PATCH einstellungen: speichert, Version zaehlt hoch, Feed-Token bleiben (auch bei Wert vom Client), neue Personen bekommen einen', async () => {