// STUB (Block B) – ersetzt Task C2 (bestellpruefung); Task C1 legt in dieser Datei den Rest des Fragebogen-Dienstes an import { text } from './einstellungen.js' import { vorlagenListe } from './vorlagen.js' /** * Bestell-Fragebogen des Terminplaners (TP2, Spezifikation 2026-10-03-terminplaner-tp2-verkauf-design.md): Der Kunde * richtet den Terminplaner vor der Bestellung ein; der Entwurf steht in termineConfig.einstellung, der Fragebogen * (Branche, bestaetigte Schritte, Wuensche zum Einbau) im Attribut termineConfig.fragebogen als JSON-Text. * Dieser Teil: Schritte, Branchen, Grundform und Normalisierung. Reine Funktionen ohne Appwrite und ohne Uhr. * Fehler sind { [pfad]: text } wie bei normalisiereEinstellung (Pfad '_' = das Ganze); Texte in Du-Form. */ /** Die acht Schritte des Fragebogens in fester Reihenfolge; `bestaetigt` nennt die vom Kunden abgehakten. */ export const SCHRITTE = Object.freeze(['branche', 'wer', 'leistungen', 'zeiten', 'regeln', 'gastfelder', 'benachrichtigung', 'website']) /** * Branchen zur Auswahl: alle Vorlagen in der Reihenfolge von vorlagenListe(), danach der Gesundheitsbereich * (ohne Vorlage; er fuehrt in den Hinweis- und Pruefweg fuer Gesundheitsangaben). Schluessel und Name der letzten * Branche gelten woertlich fuer Bestellpruefung, Oberflaeche, Einbauanleitung und Ticket. */ export const BRANCHEN = Object.freeze([ ...vorlagenListe().map(({ key, name }) => Object.freeze({ key, name, vorlage: key, gesundheit: false })), Object.freeze({ key: 'gesundheit', name: 'Gesundheitsbereich', vorlage: null, gesundheit: true }), ]) /** Obergrenze fuer den JSON-Text des Fragebogens (Zeichen); die Spalte in Appwrite ist mit 20000 groesser. */ export const FRAGEBOGEN_MAX = 16000 const EINBAU_MODI = ['inline', 'knopf'] const TEXTGRENZEN = { knopfText: 40, stelle: 300, gestaltung: 1000, bemerkung: 1000 } const MEHRZEILIG = new Set(['stelle', 'gestaltung', 'bemerkung']) const istObjekt = (wert) => wert !== null && typeof wert === 'object' && !Array.isArray(wert) /** Ein leerer Fragebogen; bei jedem Aufruf ein neues Objekt. */ export function leerFragebogen() { return { schema: 1, branche: null, bestaetigt: [], bundeslandGewaehlt: false, preiseGeprueft: false, einbau: { modus: null, knopfText: '', stelle: '', gestaltung: '', bemerkung: '' }, } } function wahrheit(fehl, pfad, wert) { if (wert === undefined || wert === null) return false if (typeof wert !== 'boolean') { fehl(pfad, 'Bitte „ja“ oder „nein“ wählen.') return false } return wert } function einbauFelder(fehl, eingabe) { const quelle = eingabe === undefined || eingabe === null ? {} : eingabe if (!istObjekt(quelle)) { fehl('einbau', 'Die Angaben zum Einbau sind ungültig.') return leerFragebogen().einbau } const einbau = { modus: null } if (quelle.modus !== undefined && quelle.modus !== null) { if (EINBAU_MODI.includes(quelle.modus)) einbau.modus = quelle.modus else fehl('einbau.modus', 'Bitte wähle, ob der Terminplaner im Text oder als Knopf erscheint.') } for (const [feld, max] of Object.entries(TEXTGRENZEN)) { const wert = quelle[feld] if (wert === undefined || wert === null) einbau[feld] = '' else if (typeof wert !== 'string') { fehl(`einbau.${feld}`, 'Der Text ist ungültig.') einbau[feld] = '' } else einbau[feld] = text(wert, max, { mehrzeilig: MEHRZEILIG.has(feld) }) } return einbau } /** * Prueft und bereinigt einen Fragebogen aus Kundeneingabe oder gespeichertem JSON. * Baut ein neues Objekt Feld fuer Feld (unbekannte Schluessel und __proto__ fallen weg), kuerzt Texte, entfernt * Steuerzeichen und ordnet `bestaetigt` nach SCHRITTE ohne Doppelte und Fremde. Fehlende Felder sind leer/false. * Ergebnis: { fragebogen } oder { fehler: { [pfad]: text } }; idempotent. */ export function normalisiereFragebogen(eingabe) { if (!istObjekt(eingabe)) return { fehler: { _: 'Der Fragebogen ist ungültig.' } } const fehler = {} const fehl = (pfad, meldung) => { if (!Object.hasOwn(fehler, pfad)) fehler[pfad] = meldung } let branche = null if (eingabe.branche !== undefined && eingabe.branche !== null) { if (BRANCHEN.some((b) => b.key === eingabe.branche)) branche = eingabe.branche else fehl('branche', 'Bitte wähle eine Branche aus der Liste.') } let bestaetigt = [] if (eingabe.bestaetigt !== undefined && eingabe.bestaetigt !== null) { if (Array.isArray(eingabe.bestaetigt)) bestaetigt = SCHRITTE.filter((schritt) => eingabe.bestaetigt.includes(schritt)) else fehl('bestaetigt', 'Die bestätigten Schritte sind ungültig.') } const fragebogen = { schema: 1, branche, bestaetigt, bundeslandGewaehlt: wahrheit(fehl, 'bundeslandGewaehlt', eingabe.bundeslandGewaehlt), preiseGeprueft: wahrheit(fehl, 'preiseGeprueft', eingabe.preiseGeprueft), einbau: einbauFelder(fehl, eingabe.einbau), } return Object.keys(fehler).length ? { fehler } : { fragebogen } } /** * Bestellpruefung des Terminplaners: Liste der offenen Punkte `[{ schritt, text }]`, leer = bestellbar. * Die Signatur ist endgueltig (Block C ersetzt nur den Rumpf): `mitAbo !== false` schaltet den Abo-Punkt (h) * dazu, den nur die Vorschau (GET /api/termine/einstellungen) mit `abo` anfordert; der Checkout ruft mit * `mitAbo: false` und ohne `abo`. Der Platzhalter meldet nichts offen. */ export function bestellpruefung({ einstellung, fragebogen, feature, abo, mitAbo = true } = {}) { return [] }