Terminplaner: Hinweistexte nach Recherche, Storno nur bei Preis

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-05 04:29:32 +00:00
1 parent 722c686690
commit 6ec8885bfd
11 files changed
+230 -21

No files matched your search

+14 -5
View File
@@ -179,6 +179,14 @@
}
// @telefon-ende
// @storno-anfang (reine Funktion, test/termineEmbedStatic.test.js fuehrt sie einzeln aus)
// Gelten die Stornobedingungen des Betriebs fuer diese Leistung? Nur bei einer Leistung mit Preis (l.preis ist der Preistext des Servers,
// bei kostenlosen Leistungen ''), wie der Server den Haken nur dort verlangt: ohne Preis gibt es weder die Box noch den Haken noch die Pruefung.
function stornoGilt (storno, l) {
return !!storno && !!l && typeof l.preis === 'string' && l.preis !== ''
}
// @storno-ende
function apiBasis (root) {
var a = String(root.getAttribute('data-api') || '').replace(/\/+$/, '')
if (a) return a
@@ -1212,9 +1220,10 @@
// Stornoregel im Klartext, daneben der Link auf die AGB des Betriebs (nur https, wenn hinterlegt)
var agbUrl = httpsUrl(c.betrieb.agbUrl)
if ((c.storno && istText(c.storno.text)) || agbUrl) {
var stornoAn = stornoGilt(c.storno, l)
if ((stornoAn && istText(c.storno.text)) || agbUrl) {
var sb = el('div', 'wkt-box')
if (c.storno && istText(c.storno.text)) {
if (stornoAn && istText(c.storno.text)) {
sb.appendChild(el('h3', '', 'Stornobedingungen'))
sb.appendChild(el('p', '', c.storno.text))
}
@@ -1256,7 +1265,7 @@
var form = el('form', 'wkt-form')
form.noValidate = true
var refs = {}
if (c.storno) form.appendChild(haken(refs, 'storno', c.texte.storno, dd.storno, true, function (v) { dd.storno = v }))
if (stornoAn) form.appendChild(haken(refs, 'storno', c.texte.storno, dd.storno, true, function (v) { dd.storno = v }))
if (S.vorzeitig) form.appendChild(haken(refs, 'vorzeitig', c.texte.vorzeitig, dd.vorzeitig, true, function (v) { dd.vorzeitig = v }))
if (c.erinnerung && c.erinnerung.anbieten === true) form.appendChild(haken(refs, 'erinnerung', c.texte.erinnerung, dd.erinnerung, false, function (v) { dd.erinnerung = v }))
form.appendChild(honigfeld())
@@ -1276,7 +1285,7 @@
e.preventDefault()
if (S.sendet) return
var f = {}
if (c.storno && !dd.storno) f.storno = T('Bitte bestätigen Sie, dass Sie die Stornobedingungen gelesen haben.', 'Bitte bestätige, dass du die Stornobedingungen gelesen hast.')
if (stornoAn && !dd.storno) f.storno = T('Bitte bestätigen Sie, dass Sie die Stornobedingungen gelesen haben.', 'Bitte bestätige, dass du die Stornobedingungen gelesen hast.')
if (S.vorzeitig && !dd.vorzeitig) f.vorzeitig = T('Bitte stimmen Sie der Leistung vor Ablauf der Widerrufsfrist ausdrücklich zu, sonst ist die Buchung nicht möglich.', 'Bitte stimme der Leistung vor Ablauf der Widerrufsfrist ausdrücklich zu, sonst ist die Buchung nicht möglich.')
fehlerBox.textContent = ''
if (Object.keys(f).length) {
@@ -1310,7 +1319,7 @@
nachricht: dd.nachricht.trim(),
zusatz: zusatz,
erinnerung: !!dd.erinnerung,
zustimmungen: { storno: !!dd.storno, vorzeitig: !!dd.vorzeitig },
zustimmungen: { storno: stornoGilt(S.cfg.storno, aktuelleLeistung()) && !!dd.storno, vorzeitig: !!dd.vorzeitig },
kontakt_url: S.honig,
formToken: S.cfg.formToken
}).then(function (d) {
+13 -6
View File
@@ -13,14 +13,17 @@ import { abschnitt, allgemeinerFehler, exportLink, feld, gesperrt, h, hakenFeld,
import { nachSpeichern, neuerSpeicher } from './termine-speichern.js'
// Rechtliche Hinweise (Ruling R52): Wortlaut nicht aendern ohne Ruling
const WIDERRUF_HINWEIS = 'Privatpersonen können eine Buchung innerhalb von 14 Tagen nach Vertragsschluss widerrufen (bei Anfragen zählt deine Annahme). '
export const WIDERRUF_HINWEIS = 'Privatpersonen können eine Buchung innerhalb von 14 Tagen nach Vertragsschluss widerrufen (bei Anfragen zählt deine Annahme). '
+ 'Das gesetzliche Widerrufsrecht geht deiner Stornoregel vor: In dieser Zeit fällt bei einem Widerruf keine Stornogebühr an, auch wenn deine Stornofrist schon abgelaufen ist. '
+ 'Liegt der Termin innerhalb dieser 14 Tage, fragt der Terminplaner deine Gäste, ob du vorher beginnen darfst. '
+ 'Hat ein Gast ausdrücklich verlangt, dass du vor Ablauf der Frist beginnst, und widerruft er danach, kann er für die bis dahin erbrachte Leistung zu einem angemessenen Betrag verpflichtet sein (Wertersatz, § 357a Abs. 2 BGB). '
+ 'Belehrung und Knopf „Vertrag widerrufen“ zeigt der Terminplaner automatisch, wenn deine Gäste Privatpersonen sind, bei sofort buchbaren Leistungen ebenso wie bei Anfragen.'
const FREIZEIT_HINWEIS = 'Nur ankreuzen, wenn deine Termine Freizeitveranstaltungen zu einem festen Termin sind (§ 312g Abs. 2 Nr. 9 BGB), zum Beispiel ein einzelner Tanzabend oder ein Escape Room. '
+ 'Friseur, Kosmetik, Massage, Tattoo, Beratung, Fotoshootings und Weiterbildungen sind in der Regel keine Freizeitleistung. '
export const FREIZEIT_HINWEIS = 'Nur ankreuzen, wenn deine Termine Freizeitveranstaltungen zu einem festen Termin sind (§ 312g Abs. 2 Nr. 9 BGB), zum Beispiel ein einzelner Tanzabend, ein Kurs oder ein Escape Room. '
+ 'Nach unserer Einschätzung gilt bei Friseur, Kosmetik, Nagel- und Fußpflege, Tattoo, Fotoshootings, Coaching und Beratung in der Regel ein Widerrufsrecht; bei Massage und Wellness ist die Einordnung unklar, im Zweifel gilt ein Widerrufsrecht. '
+ 'Kreuzt du es an, zeigt der Terminplaner keine Widerrufsbelehrung. Kreuzt du es zu Unrecht an, können deine Gäste noch bis zu 12 Monate und 14 Tage widerrufen. '
+ 'Im Zweifel nicht ankreuzen oder dich rechtlich beraten lassen.'
+ 'Das ist keine Rechtsberatung: Im Zweifel nicht ankreuzen oder dich rechtlich beraten lassen.'
// Hinweis neben dem Stornotext (nur im Portal, nicht im Gästetext): der Vorschlag der Vorlage und das Muster sind kein fertiger Rechtstext
export const STORNO_MUSTER_HINWEIS = 'Das ist ein Muster, kein Rechtstext. Bitte prüfe es und passe es an deine Bedingungen an.'
const wahl = (liste, aktuell) => liste.map(([wert, text]) => h('option', { value: wert, selected: String(aktuell) === String(wert) }, text))
const zahlenFeld = (name, label, wert, { min, max, schritt = 1, hinweis }) => {
@@ -262,9 +265,13 @@ export function storno(ctx, e, fristFeld) {
type: 'button', class: 'kb-btn-outline', 'data-aktion': 'muster-uebernehmen',
onclick: () => { textfeld.value = muster.textContent; textfeld.focus() },
}, 'Muster in das Textfeld übernehmen')
const textBlock = feld('storno.text', 'Dein Text', textfeld, s.version ? `Aktuelle Fassung: ${s.version}. Ändert sich der Text, bekommt er beim Speichern eine neue Fassung, und Gäste stimmen immer der Fassung zu, die sie gesehen haben.` : 'Beim ersten Speichern bekommt der Text eine Fassungsnummer.')
// der Hinweis „Muster“ steht unmittelbar unter dem Textfeld, vor dem Hinweis zur Fassung (Spec 7.3): der Text kann der Vorschlag der Vorlage sein
textBlock.insertBefore(h('p', { class: 'tm-hinweis tm-muster-hinweis', role: 'note', 'data-hinweis': 'storno-muster' }, STORNO_MUSTER_HINWEIS), textBlock.querySelector('.tm-hinweis'))
const el = abschnitt('Stornobedingungen',
'Gäste müssen diesen Text vor dem Buchen bestätigen. Lässt du das Feld leer, gibt es keine Stornobedingungen und keinen Haken dazu.',
feld('storno.text', 'Dein Text', textfeld, s.version ? `Aktuelle Fassung: ${s.version}. Ändert sich der Text, bekommt er beim Speichern eine neue Fassung, und Gäste stimmen immer der Fassung zu, die sie gesehen haben.` : 'Beim ersten Speichern bekommt der Text eine Fassungsnummer.'),
'Gäste müssen diesen Text vor dem Buchen bestätigen. Lässt du das Feld leer, gibt es keine Stornobedingungen und keinen Haken dazu. '
+ 'Die Stornobedingungen gelten nur für Leistungen mit Preis; bei kostenlosen Leistungen fragt der Terminplaner Gäste nicht danach.',
textBlock,
h('div', { class: 'tm-muster', role: 'group', 'aria-label': 'Mustertext' },
h('p', null, h('span', { class: 'tm-pille tm-pille-wait' }, 'Muster'), ' Ein Vorschlag, kein fertiger Rechtstext. Prüfe ihn und passe Frist, Prozentsatz und Anrede an deinen Betrieb an. Er ist in der förmlichen Anrede geschrieben. Das gesetzliche Widerrufsrecht deiner privaten Gäste bleibt unberührt.'),
muster,
+1
View File
@@ -114,6 +114,7 @@
.tm-feld [aria-invalid='true'] { border-color: #f87171; }
.tm-feld input:focus, .tm-feld select:focus, .tm-feld textarea:focus { border-color: var(--kb-purple-line); box-shadow: 0 0 0 3px var(--kb-purple-soft); }
.tm-hinweis { margin: 0; font-size: 12.5px; color: var(--kb-muted); }
.tm-feld > .tm-muster-hinweis { margin-top: 6px; font-weight: 600; }
.tm-fehler { margin: 0; font-size: 13px; color: #fca5a5; }
.tm-fehler:empty { display: none; }
.tm-zeiten { display: flex; flex-wrap: wrap; gap: 8px; }