Rest der Route POST /api/funktionen/:key/checkout unter der Sperre feature-order:<Kunde>:<Projekt>: Zustand aus dem Register (setup/active, disabled, ordered mit offener Sitzung, "Zahlung fortsetzen"), Stripe-Sitzung mit der Zeit des Klicks, Zustimmungsnachweis (Serverfassungen und -texte) vor markOrdered und vor der URL. Standard-Haken belehrung mit BELEHRUNG_PLATZHALTER fuer den Terminplaner. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
119 lines
5.2 KiB
JavaScript
119 lines
5.2 KiB
JavaScript
import { config } from '../config.js'
|
|
import { termineFuerKunde } from './termine/schalter.js'
|
|
|
|
// Helfer fuer das Funktionen-Register config.features (News-Pinnwand, Terminplaner; Daten in server/config.js).
|
|
// Diese Datei wird von siteFeatures.js importiert und importiert selbst keine Dienste statisch (einzige Ausnahme:
|
|
// termine/schalter.js, das nur config liest): Standard-Haken laden ihre Dienste per dynamischem import()
|
|
// innerhalb der Funktion (sonst entstuenden Importzyklen).
|
|
|
|
/** Namen der Haken, die ein Registereintrag hat (optional): Bestell-, Freischalt- und Unterlagenpruefungen. */
|
|
const HAKEN_NAMEN = ['verfuegbar', 'bestellpruefung', 'unterlagenBereit', 'belehrung', 'freischaltbar']
|
|
|
|
/**
|
|
* Bestellpruefung des Terminplaners im Checkout: die offenen Punkte des Entwurfs (Einstellung und Fragebogen aus
|
|
* termineConfig; ohne Dokument beides null) nach termine/fragebogen.js. Ohne abo und mit mitAbo: false: den Abo-Punkt (h)
|
|
* wendet der Checkout nicht an, die Abo-Stufe (7) der Route liefert kein_abo | gekuendigt | nicht_aktiv.
|
|
*/
|
|
async function termineBestellpruefung({ customerId, projectId, feature }) {
|
|
const [{ getFeatureDoc }, { ladeEntwurf }, { bestellpruefung }] = await Promise.all([
|
|
import('./siteFeatures.js'),
|
|
import('./featureOrder.js'),
|
|
import('./termine/fragebogen.js'),
|
|
])
|
|
const doc = await getFeatureDoc(customerId, projectId, 'termine')
|
|
const { einstellung, fragebogen } = doc ? await ladeEntwurf(doc.$id) : { einstellung: null, fragebogen: null }
|
|
return bestellpruefung({ einstellung, fragebogen, feature, mitAbo: false })
|
|
}
|
|
|
|
/**
|
|
* Platzhalter der Widerrufsbelehrung mit Muster-Widerrufsformular, die die Bestellseite vor dem Knopf zeigt und der
|
|
* Zustimmungsnachweis speichert. Block F (Task F7) ersetzt den Haken belehrung durch widerrufsbelehrungFunktion().
|
|
*/
|
|
export const BELEHRUNG_PLATZHALTER = 'ENTWURF (Block F): Widerrufsbelehrung mit Muster-Widerrufsformular.'
|
|
|
|
/**
|
|
* Standard-Haken je Funktion (Funktionen, nicht Daten). B6 fuellt verfuegbar, bestellpruefung und unterlagenBereit
|
|
* (Standard immer bereit; F8 haengt die Rechtsunterlagen ein), B7 belehrung (Text der Belehrung, der vor dem Knopf steht
|
|
* und im Nachweis gespeichert wird) und B11 freischaltbar. News hat keine Standard-Haken, also auch keinen belehrung-Eintrag
|
|
* im Nachweis. Feste Funktionen, damit featureHaken bei jedem Aufruf dieselben liefert.
|
|
*/
|
|
const STANDARD_HAKEN = {
|
|
news: {},
|
|
termine: {
|
|
verfuegbar: (email) => termineFuerKunde(email),
|
|
bestellpruefung: termineBestellpruefung,
|
|
unterlagenBereit: async () => true,
|
|
belehrung: async () => BELEHRUNG_PLATZHALTER,
|
|
},
|
|
}
|
|
|
|
/** Registereintrag oder null; nur eigene Schluessel des Registers ('analyse', '__proto__', 'toString' => null). */
|
|
export function featureConfig(key) {
|
|
if (typeof key !== 'string' || !Object.hasOwn(config.features, key)) return null
|
|
return config.features[key]
|
|
}
|
|
|
|
/** Schluessel der Funktionen im Register, in Registerreihenfolge. */
|
|
export function featureKeys() {
|
|
return Object.keys(config.features)
|
|
}
|
|
|
|
/**
|
|
* Haken einer Funktion: { verfuegbar?, bestellpruefung?, unterlagenBereit?, belehrung?, freischaltbar? }.
|
|
* Wer config.features.<key>.<haken> als Funktion setzt (Tests), ueberschreibt den Standard.
|
|
*/
|
|
export function featureHaken(key) {
|
|
const eintrag = featureConfig(key)
|
|
if (!eintrag) return {}
|
|
const standard = Object.hasOwn(STANDARD_HAKEN, key) ? STANDARD_HAKEN[key] : {}
|
|
const haken = {}
|
|
for (const name of HAKEN_NAMEN) {
|
|
const fn = typeof eintrag[name] === 'function' ? eintrag[name] : standard[name]
|
|
if (typeof fn === 'function') haken[name] = fn
|
|
}
|
|
return haken
|
|
}
|
|
|
|
const NICHT_VERFUEGBAR = 'Diese Funktion ist noch nicht verfügbar.'
|
|
|
|
/**
|
|
* Pruefstufe 5 des Checkouts: Text, warum die Funktion diesem Kunden (noch) nicht angeboten wird, sonst null.
|
|
* Gefragt wird der Haken verfuegbar(email) (Terminplaner: Schalter TERMINE_ENABLED und Pilotliste); Funktionen ohne
|
|
* Haken (News) sind immer verfuegbar. Ein unbekannter Schluessel ist nicht verfuegbar (fail-closed).
|
|
*/
|
|
export function featureSperre(key, email) {
|
|
const eintrag = featureConfig(key)
|
|
if (!eintrag) return NICHT_VERFUEGBAR
|
|
const { verfuegbar } = featureHaken(key)
|
|
if (typeof verfuegbar !== 'function' || verfuegbar(email)) return null
|
|
return eintrag.unavailableMessage || NICHT_VERFUEGBAR
|
|
}
|
|
|
|
const fassung = (f) => (f ? { version: f.version, url: f.url ?? '' } : null)
|
|
|
|
/**
|
|
* Sicht fuer den Browser (Bestellseite): JSON-sicher, ohne Funktionen und ohne ticket/mails, eine Kopie
|
|
* (Aenderungen daran aendern das Register nicht). Unbekannter Schluessel => null.
|
|
*/
|
|
export function oeffentlicheAnsicht(key) {
|
|
const e = featureConfig(key)
|
|
if (!e) return null
|
|
return {
|
|
key: e.key,
|
|
name: e.label.name,
|
|
kurz: e.label.kurz,
|
|
priceLabel: e.priceLabel,
|
|
setupEstimate: e.setupEstimate,
|
|
consent: {
|
|
version: e.consent.version,
|
|
knopf: e.consent.knopf,
|
|
items: e.consent.items.map((item) => ({ id: item.id, text: item.text, haken: Boolean(item.haken) })),
|
|
},
|
|
leistungsbeschreibung: fassung(e.leistungsbeschreibung),
|
|
avv: fassung(e.avv),
|
|
widerruf: fassung(e.widerruf),
|
|
returnHash: e.returnHash,
|
|
requiresWartungsAbo: e.requiresWartungsAbo === true,
|
|
}
|
|
}
|