Terminplaner: Aktionen des Betriebs, manuelle Termine, Sperren

Neues Modul ablaufBetrieb.js: aktion (annehmen, ablehnen, absagen, verschieben,
nichterschienen, anonymisieren), manuell, sperre, entsperre. ablauf.js exportiert
die gemeinsamen Bausteine (Pruefungen, Meldungen, mailSicher, pruefeSlot,
verschiebeKern). R31: brauchtZustimmungVorzeitig folgt der Berliner Widerrufsfrist
(withdrawalDeadline ab now + Haltezeit); bei einer Anfrage mit Preis immer.

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:43 +00:00
1 parent c4ec24448e
commit be49d38e0c
8 files changed
+1563 -79

No files matched your search

+34 -6
View File
@@ -721,28 +721,56 @@ test('istVerbraucher: nicht nur Unternehmer und keine Freizeitleistung', () => {
assert.equal(istVerbraucher({ recht: { gaesteSind: 'verbraucher', freizeit: true } }), false)
})
test('brauchtZustimmungVorzeitig: verbindlich + Preis + Verbraucher + Beginn in weniger als 14 Tagen', () => {
test('brauchtZustimmungVorzeitig: verbindlich + Preis + Verbraucher + Beginn bis zum Ende der Widerrufsfrist (Berliner Kalender, §§ 187, 188, 193 BGB)', () => {
const verbraucher = { recht: { gaesteSind: 'verbraucher', freizeit: false } }
const unternehmer = { recht: { gaesteSind: 'unternehmer', freizeit: false } }
const freizeit = { recht: { gaesteSind: 'verbraucher', freizeit: true } }
const mitPreis = { buchungsart: 'sofort', preisArt: 'fest', preisCent: 4500 }
const inTagen = (n) => new Date(NOW.getTime() + n * TAG)
// NOW = Freitag 02.10.2026, 12:00 Berlin; Vertragsschluss spaetestens nach der Haltezeit (30 Minuten): Fristende
// Freitag 16.10.2026, 23:59:59,999 Uhr Berlin = 21:59:59,999 Uhr UTC (MESZ)
const FRISTENDE = Date.parse('2026-10-16T21:59:59.999Z')
assert.equal(brauchtZustimmungVorzeitig(verbraucher, mitPreis, inTagen(3), NOW), true)
assert.equal(brauchtZustimmungVorzeitig(verbraucher, mitPreis, inTagen(20), NOW), false)
assert.equal(brauchtZustimmungVorzeitig(verbraucher, mitPreis, inTagen(14), NOW), false)
assert.equal(brauchtZustimmungVorzeitig(verbraucher, mitPreis, new Date(inTagen(14).getTime() - 1), NOW), true)
assert.equal(brauchtZustimmungVorzeitig(verbraucher, mitPreis, inTagen(0), NOW), true)
assert.equal(brauchtZustimmungVorzeitig(verbraucher, mitPreis, inTagen(20), NOW), false)
// Grenze: die letzte Millisekunde der Frist ja, die naechste nein
assert.equal(brauchtZustimmungVorzeitig(verbraucher, mitPreis, new Date(FRISTENDE), NOW), true)
assert.equal(brauchtZustimmungVorzeitig(verbraucher, mitPreis, new Date(FRISTENDE + 1), NOW), false)
// 14 mal 24 Stunden reichen nicht: 14 Tage nach der Buchung um 12:00 liegen noch in der Frist (bis 24 Uhr des Tages)
assert.equal(brauchtZustimmungVorzeitig(verbraucher, mitPreis, inTagen(14), NOW), true)
assert.equal(brauchtZustimmungVorzeitig(verbraucher, { ...mitPreis, preisArt: 'ab' }, inTagen(3), NOW), true)
// Unternehmer, Freizeit, kein Preis, Anfrage: keine Zustimmung noetig
// Fristende auf einem Wochenende ⇒ naechster Werktag: Samstag 10.10.2026 + 14 = Samstag 24.10. ⇒ Montag 26.10., 23:59:59,999 Uhr Berlin (MEZ)
const SAMSTAG = new Date('2026-10-10T10:00:00.000Z')
const wochenendeEnde = Date.parse('2026-10-26T22:59:59.999Z')
assert.equal(brauchtZustimmungVorzeitig(verbraucher, mitPreis, new Date(wochenendeEnde), SAMSTAG), true)
assert.equal(brauchtZustimmungVorzeitig(verbraucher, mitPreis, new Date(wochenendeEnde + 1), SAMSTAG), false)
// die Haltezeit zaehlt mit: wer um 23:45 Uhr bucht, schliesst den Vertrag erst nach Mitternacht (Fristende einen Tag spaeter)
// (Samstag 03.10. + 14 = Samstag 17.10. ⇒ Montag 19.10., 23:59:59,999 Uhr Berlin = 21:59:59,999 Uhr UTC)
const spaet = new Date('2026-10-02T21:45:00.000Z') // 23:45 Uhr Berlin, Freitag; + 30 Minuten = Samstag 00:15 Uhr
assert.equal(brauchtZustimmungVorzeitig(verbraucher, mitPreis, new Date(Date.parse('2026-10-16T21:59:59.999Z') + 1), spaet), true)
assert.equal(brauchtZustimmungVorzeitig(verbraucher, mitPreis, new Date(Date.parse('2026-10-19T21:59:59.999Z') + 1), spaet), false)
// Unternehmer, Freizeit, kein Preis: keine Zustimmung noetig
assert.equal(brauchtZustimmungVorzeitig(unternehmer, mitPreis, inTagen(3), NOW), false)
assert.equal(brauchtZustimmungVorzeitig(freizeit, mitPreis, inTagen(3), NOW), false)
assert.equal(brauchtZustimmungVorzeitig(verbraucher, { ...mitPreis, preisArt: 'keiner', preisCent: 0 }, inTagen(3), NOW), false)
assert.equal(brauchtZustimmungVorzeitig(verbraucher, { ...mitPreis, buchungsart: 'anfrage' }, inTagen(3), NOW), false)
// ungueltiger Beginn: lieber die Zustimmung verlangen
assert.equal(brauchtZustimmungVorzeitig(verbraucher, mitPreis, new Date('kaputt'), NOW), true)
})
test('brauchtZustimmungVorzeitig: bei einer Anfrage mit Preis fuer Verbraucher immer (der Tag der Annahme ist unbekannt)', () => {
const verbraucher = { recht: { gaesteSind: 'verbraucher', freizeit: false } }
const anfrage = { buchungsart: 'anfrage', preisArt: 'fest', preisCent: 4500 }
const inTagen = (n) => new Date(NOW.getTime() + n * TAG)
assert.equal(brauchtZustimmungVorzeitig(verbraucher, anfrage, inTagen(3), NOW), true)
assert.equal(brauchtZustimmungVorzeitig(verbraucher, anfrage, inTagen(60), NOW), true)
assert.equal(brauchtZustimmungVorzeitig(verbraucher, anfrage, new Date('kaputt'), NOW), true)
// ohne Preis, Unternehmer, Freizeit: nein
assert.equal(brauchtZustimmungVorzeitig(verbraucher, { ...anfrage, preisArt: 'keiner', preisCent: 0 }, inTagen(3), NOW), false)
assert.equal(brauchtZustimmungVorzeitig({ recht: { gaesteSind: 'unternehmer', freizeit: false } }, anfrage, inTagen(3), NOW), false)
assert.equal(brauchtZustimmungVorzeitig({ recht: { gaesteSind: 'verbraucher', freizeit: true } }, anfrage, inTagen(3), NOW), false)
})
test('preisText, preisHinweis, zahlungText', () => {
assert.equal(preisText({ preisArt: 'fest', preisCent: 4500 }), '45,00 €')
assert.equal(preisText({ preisArt: 'ab', preisCent: 4500 }), 'ab 45,00 €')