Kundenansicht neu P7: Einschalten zeigt Linkzeile, Häkchen und Unterlagen der Fassung 6 aus P8
Verdrahtet P5 Task 15 und P7 Task 0 (N33): consentAbo liest Linkzeile und Hinweis aus WORTLAUT_V6, unterlagenFuer die fünf Funktionen aus FUNKTIONEN_UNTERLAGEN_V6 (Analyse bleibt bei den Alt-Unterlagen bis zur Freigabe), config.aboFunktionen.texte.hinweis entfällt, pdfAnhaenge liest PDFs der Fassung 6, sichereAdresse lässt /neu/rechtliches/ zu. Der Analyse-Kundenweg setzt wie der Team-Weg den Abo-Marker (unlockedBy und einrichtungAbrechnung abo). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
447129d4e2
commit
bfe3895a50
11 files changed
+177
-41
No files matched your search
+6
-12
@@ -844,24 +844,18 @@ export const config = {
|
||||
ustHinweis,
|
||||
// Register der kaufbaren Funktionen (siehe oben)
|
||||
features,
|
||||
// Abo-Modell der Funktionen (Kundenansicht neu, P5): Fassung der Einschalt-Erklaerung, Reihenfolge des Katalogs, Entwurfstexte
|
||||
// (Task 15 / P7 Task 0 stellt fassung und texte.beginn auf Fassung 6; config.js importiert keine Dienste) und der Katalogeintrag der
|
||||
// Analyse (sie steht nicht im Register der kaufbaren Funktionen). Die Items baut featureRegistry.consentAbo bei jedem Aufruf.
|
||||
// Abo-Modell der Funktionen (Kundenansicht neu, P5): Fassung der Einschalt-Erklaerung, Reihenfolge des Katalogs, Haekchen-Text und
|
||||
// der Katalogeintrag der Analyse (sie steht nicht im Register der kaufbaren Funktionen). fassung und texte.beginn sind Literale
|
||||
// der Fassung 6 (N33; config.js importiert keine Dienste, test/aboWortlautV6.test.js und test/knVerdrahtung.test.js pinnen die
|
||||
// Gleichheit mit FUNKTIONEN_FASSUNG_V6 und WORTLAUT_V6.beginn.einrichtung). Linkzeile und Hinweis stehen in WORTLAUT_V6
|
||||
// (products.js); die Items baut featureRegistry.consentAbo bei jedem Aufruf.
|
||||
aboFunktionen: {
|
||||
fassung: '2026-10-16',
|
||||
reihenfolge: ['news', 'termine', 'shop', 'unterkunft', 'zeiten', 'analyse'],
|
||||
texte: {
|
||||
// Haken "Beginn" (nur Privatkunden innerhalb der Widerrufsfrist); Entwurf, wortgleich zu WORTLAUT_V6.beginn.einrichtung (P8)
|
||||
// Haken "Beginn" (nur Privatkunden innerhalb der Widerrufsfrist); wortgleich zu WORTLAUT_V6.beginn.einrichtung (P8)
|
||||
beginn:
|
||||
'Bitte beginnt sofort mit der Einrichtung. Mir ist klar: Mit der vollständigen Einrichtung erlischt mein Widerrufsrecht, und wenn ich vorher widerrufe, zahle ich den bis dahin erbrachten Teil.',
|
||||
// Hinweis auf der Einschalt-Seite (ohne Haken): die Pflichten der alten Haken verantwortung, stripe und gesundheit;
|
||||
// Entwurf, wortgleich zu WORTLAUT_V6.hinweis (P8)
|
||||
hinweis: {
|
||||
shop: 'Du verkaufst in eigenem Namen und bist für deine Produkte, Preise und Rechtstexte verantwortlich. Die Zahlungen deiner Käufer laufen über dein eigenes Stripe-Konto.',
|
||||
unterkunft:
|
||||
'Du vermietest in eigenem Namen und bist für Preise, Stornobedingungen, Rechnungen und Meldepflichten verantwortlich. Online-Zahlungen deiner Gäste laufen über dein eigenes Stripe-Konto.',
|
||||
termine: 'Der Terminplaner ist nicht für den Gesundheitsbereich gedacht. Das fragen wir im Fragebogen ab.',
|
||||
},
|
||||
// Haken der Analyse (Kinder unter 16 Jahren, DSGVO Art. 8)
|
||||
zielgruppe: 'Meine Website richtet sich nicht gezielt an Kinder unter 16 Jahren.',
|
||||
analyseKnopf: 'Analyse einschalten',
|
||||
|
||||
@@ -612,10 +612,12 @@ router.post('/analyse/activate', analyseGuard, async (req, res) => {
|
||||
}
|
||||
const customerId = getSessionCustomerId(req)
|
||||
await assertOwnProject(customerId, analyseProjectId(projectId), { forNewOrder: true })
|
||||
// Nachweis der Erklaerung (featureConsents) mit der Kundenart des Dokuments, nie aus dem Body; entsteht nur bei einer neuen Aktivierung
|
||||
// Nachweis der Erklaerung (featureConsents) mit der Kundenart des Dokuments, nie aus dem Body; entsteht nur bei einer neuen Aktivierung.
|
||||
// abrechnung 'abo': dasselbe Marker-Paar am Dokument wie beim Team-Weg (admin/analyse.js), unabhaengig vom Body
|
||||
const abo = neu
|
||||
? {
|
||||
aboModell: true,
|
||||
abrechnung: 'abo',
|
||||
nachweis: async () =>
|
||||
speichereNachweisAusAnfrage(req, {
|
||||
feature: abowegAnalyse(),
|
||||
|
||||
@@ -1,17 +1,23 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { config } from '../config.js'
|
||||
import { legalPaths } from './analyseLegal.js'
|
||||
import { FUNKTIONEN_UNTERLAGEN_V6 } from './products.js'
|
||||
|
||||
/**
|
||||
* Einzige Quelle der verlinkten Unterlagen im Abo-Modell (Katalog, Einschalt-Nachweis, Analyse). Blattmodul: importiert
|
||||
* nur config und analyseLegal.js. Liest bei jedem Aufruf (Tests aendern config zur Laufzeit). Task 15 (Fassung 6) stellt
|
||||
* nur diese Quelle um; die Aufrufer bleiben unveraendert.
|
||||
* nur config, analyseLegal.js und products.js (das selbst nur config und den Schalter liest). Liest bei jedem Aufruf (Tests
|
||||
* aendern config zur Laufzeit). Verdrahtung der Fassung 6 (N33): die fuenf Funktionen des Registers lesen
|
||||
* FUNKTIONEN_UNTERLAGEN_V6; die Analyse bleibt bis zur Freigabe bei den versionierten Alt-Seiten (legalPaths).
|
||||
*/
|
||||
|
||||
const fassung = (unterlage) => (unterlage ? { version: unterlage.version, url: unterlage.url ?? '' } : null)
|
||||
|
||||
/**
|
||||
* Leistungsbeschreibung und AVV einer Funktion: { leistungsbeschreibung: { version, url } | null, avv: { version, url } | null }.
|
||||
* Funktionen aus dem Register: deren Eintraege; 'analyse': die versionierten Seiten aus legalPaths(config.analyse.avvVersion).
|
||||
* Funktionen aus dem Register: die Dokumente der Fassung 6 aus FUNKTIONEN_UNTERLAGEN_V6 (Vorschaupfad /neu/rechtliches/, ohne Preise);
|
||||
* 'analyse': die versionierten Seiten aus legalPaths(config.analyse.avvVersion) (N33: erst nach der Freigabe auf Fassung 6).
|
||||
* Ein unbekannter Schluessel ist ein Programmfehler (TypeError).
|
||||
*/
|
||||
export function unterlagenFuer(key) {
|
||||
@@ -21,8 +27,9 @@ export function unterlagenFuer(key) {
|
||||
return { leistungsbeschreibung: { version, url: pfade.lb }, avv: { version, url: pfade.avv } }
|
||||
}
|
||||
if (typeof key !== 'string' || !Object.hasOwn(config.features, key)) throw new TypeError(`Unbekannte Funktion: ${String(key)}`)
|
||||
const eintrag = config.features[key]
|
||||
return { leistungsbeschreibung: fassung(eintrag.leistungsbeschreibung), avv: fassung(eintrag.avv) }
|
||||
const unterlagen = FUNKTIONEN_UNTERLAGEN_V6[key]
|
||||
if (!unterlagen) throw new TypeError(`Keine Unterlagen der Fassung 6 fuer: ${key}`)
|
||||
return { leistungsbeschreibung: fassung(unterlagen.lb), avv: fassung(unterlagen.avv) }
|
||||
}
|
||||
|
||||
/** Unterlagen der Analyse fuer Nachweis und Mail: { avvVersion, legal: { lb, avv } } (Adressen der versionierten Seiten). */
|
||||
@@ -30,3 +37,20 @@ export function analyseUnterlagen() {
|
||||
const { leistungsbeschreibung, avv } = unterlagenFuer('analyse')
|
||||
return { avvVersion: avv.version, legal: { lb: leistungsbeschreibung.url, avv: avv.url } }
|
||||
}
|
||||
|
||||
const PUBLIC_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', '..', 'public')
|
||||
|
||||
/**
|
||||
* PDF-Anhaenge fuer Mails aus Dokumenten der Fassung 6: [{ pdfFile, anhangName }] -> [{ filename: anhangName, content, contentType }].
|
||||
* Wirft, wenn eine Datei fehlt, leer ist oder nicht mit %PDF- beginnt (nie ein leerer oder falscher Anhang). basis: Wurzel von
|
||||
* pdfFile (Vorgabe: public/).
|
||||
*/
|
||||
export async function pdfAnhaenge(dokumente, { basis = PUBLIC_DIR } = {}) {
|
||||
const anhaenge = []
|
||||
for (const dok of dokumente) {
|
||||
const content = await readFile(path.join(basis, dok.pdfFile))
|
||||
if (content.length === 0 || content.subarray(0, 5).toString('latin1') !== '%PDF-') throw new Error(`PDF fehlt oder ist ungueltig: ${dok.pdfFile}`)
|
||||
anhaenge.push({ filename: dok.anhangName, content, contentType: 'application/pdf' })
|
||||
}
|
||||
return anhaenge
|
||||
}
|
||||
@@ -4,11 +4,12 @@ import { shopFuerKunde } from './shop/schalter.js'
|
||||
import { unterkunftFuerKunde } from './unterkunft/schalter.js'
|
||||
import { zeitenFuerKunde } from './zeiten/schalter.js'
|
||||
import { analyseUnterlagen, unterlagenFuer } from './aboUnterlagen.js'
|
||||
import { WORTLAUT_V6, mitFassung } from './products.js'
|
||||
|
||||
// Helfer fuer das Funktionen-Register config.features (News-Pinnwand, Terminplaner, Webshop, Oeffnungszeiten live; Daten in server/config.js).
|
||||
// Diese Datei wird von siteFeatures.js importiert und importiert selbst keine Dienste statisch (einzige Ausnahmen:
|
||||
// termine/schalter.js, shop/schalter.js und zeiten/schalter.js, die nur config lesen, und aboUnterlagen.js, ein Blattmodul,
|
||||
// das nur config und analyseLegal.js importiert): Standard-Haken laden ihre Dienste per dynamischem import()
|
||||
// termine/schalter.js, shop/schalter.js und zeiten/schalter.js, die nur config lesen, aboUnterlagen.js, ein Blattmodul,
|
||||
// das nur config, analyseLegal.js und products.js importiert, und products.js, das nur config und den Schalter 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, dazu nachFreischaltung. */
|
||||
@@ -239,18 +240,17 @@ export function katalogFelder(key) {
|
||||
/**
|
||||
* Einschalt-Erklaerung einer Funktion im Abo-Modell: { version, knopf: 'Einschalten', items } in Anzeigereihenfolge
|
||||
* einschalten-zeile (Linkzeile mit Fassung, kein Haken), hinweis (nur shop, unterkunft, termine; kein Haken) und beginn
|
||||
* (Haken, nur fuer Privatkunden innerhalb der Widerrufsfrist). Baut die Items bei jedem Aufruf aus aboUnterlagen.js und
|
||||
* config.aboFunktionen.texte. Analyse und unbekannte Schluessel => null.
|
||||
* (Haken, nur fuer Privatkunden innerhalb der Widerrufsfrist). Baut die Items bei jedem Aufruf: Linkzeile (mit AVV oder ohne) und
|
||||
* Hinweis aus WORTLAUT_V6 (Fassung 6, P8), beginn und Fassung aus config.aboFunktionen (Literale, Gleichheit gepinnt in
|
||||
* test/aboWortlautV6.test.js und test/knVerdrahtung.test.js). Analyse und unbekannte Schluessel => null.
|
||||
*/
|
||||
export function consentAbo(key) {
|
||||
if (!featureConfig(key)) return null
|
||||
const { leistungsbeschreibung, avv } = unterlagenFuer(key)
|
||||
const { avv } = unterlagenFuer(key)
|
||||
const { fassung: version, texte } = config.aboFunktionen
|
||||
const zeile = avv
|
||||
? `Es gelten Leistungsbeschreibung und Auftragsverarbeitung (Fassung ${leistungsbeschreibung.version}).`
|
||||
: `Es gilt die Leistungsbeschreibung (Fassung ${leistungsbeschreibung.version}).`
|
||||
const zeile = mitFassung(avv ? WORTLAUT_V6.linkzeile.einschaltenMitAvv : WORTLAUT_V6.linkzeile.einschaltenOhneAvv, version)
|
||||
const items = [{ id: 'einschalten-zeile', text: zeile, haken: false }]
|
||||
if (Object.hasOwn(texte.hinweis, key)) items.push({ id: 'hinweis', text: texte.hinweis[key], haken: false })
|
||||
if (Object.hasOwn(WORTLAUT_V6.hinweis, key)) items.push({ id: 'hinweis', text: WORTLAUT_V6.hinweis[key], haken: false })
|
||||
items.push({ id: 'beginn', text: texte.beginn, haken: true, nurPrivatInFrist: true })
|
||||
return { version, knopf: 'Einschalten', items }
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user