termineConfig bekommt das Attribut fragebogen (Text 20000, Inhalt bis FRAGEBOGEN_MAX 16000). fragebogen.js: SCHRITTE, BRANCHEN (Vorlagen plus Gesundheitsbereich), leerFragebogen und normalisiereFragebogen. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
121 lines
5.4 KiB
JavaScript
121 lines
5.4 KiB
JavaScript
// 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 []
|
||
}
|