Kundenansicht P5: Analyse im Abo-Modell (Hosting, Zielgruppe erzwungen, Nachweis, Tagesprüfung)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-16 16:14:35 +00:00
1 parent 23252df359
commit 57d5db2344
4 files changed
+271 -21

No files matched your search

+36 -11
View File
@@ -26,16 +26,17 @@ import { einschaltenDeps, einschaltenZeiten, zeitenProjektAnsicht } from '../ser
import { starteBehalten } from '../services/zeiten/behalten.js'
import { aboMemo } from '../services/aboStatus.js'
import { aboProdukt } from '../services/aboFunktionen.js'
import { analyseUnterlagen } from '../services/aboUnterlagen.js'
import { anfrageStatusJeProjekt, katalogFuerKunde } from '../services/aboKatalog.js'
import { datumDe, einschalten, EINSCHALTEN_TEXTE, pruefeAboDeckung, pruefeEinschaltZustand } from '../services/funktionEinschalten.js'
import { kundenartWirksam, NUR_BETRIEB_TEXT } from '../services/kundenart.js'
import { neueAnsicht } from '../services/kundenansicht/schalter.js'
import { aboweg, consentAbo, consentAboAktiv, featureConfig, featureHaken, featureKeys, featureSperre, freiweg, katalogFelder, oeffentlicheAnsicht } from '../services/featureRegistry.js'
import { abowegAnalyse, aboweg, consentAbo, consentAboAktiv, featureConfig, featureHaken, featureKeys, featureSperre, freiweg, katalogFelder, oeffentlicheAnsicht } from '../services/featureRegistry.js'
import { aboMeldung, bestellFehler, istFortsetzen, oeffentlicheConsent, pruefeAbo, pruefeInklusive, pruefeZustimmung } from '../services/featureOrder.js'
import {
ELIGIBILITY_MESSAGES,
activateAnalyse,
eligibilityMessage,
analyseDeps,
analyseHosts,
isDevPreview,
@@ -60,11 +61,11 @@ function fail(res, err, fallback) {
const ANALYSE_OFF = 'Website-Analyse ist noch nicht freigeschaltet'
const ANALYSE_ERROR = 'Die Website-Analyse konnte gerade nicht geändert werden. Bitte versuch es später noch einmal oder schreib uns im Chat.'
async function analyseView(customerId, projectId) {
async function analyseView(customerId, projectId, { aboModell = false } = {}) {
if (!config.analyse.enabled) return { comingSoon: true }
const [doc, eligibility, controller] = await Promise.all([
getFeatureDoc(customerId, projectId, 'analyse'),
analyseEligibility(customerId, projectId),
analyseEligibility(customerId, projectId, { aboModell }),
controllerView(customerId),
])
let devMode = false
@@ -78,9 +79,10 @@ async function analyseView(customerId, projectId) {
accentColor: doc?.accentColor || '#111111',
eligible: eligibility.ok,
reason: eligibility.reason || null,
reasonText: eligibility.reason ? ELIGIBILITY_MESSAGES[eligibility.reason] : null,
avvVersion: config.analyse.avvVersion,
legal: legalPaths(config.analyse.avvVersion),
reasonText: eligibility.reason ? eligibilityMessage(eligibility.reason, { aboModell }) : null,
// Neue Ansicht: Fassung und Adressen aus derselben Quelle wie der Nachweis (die Oberflaeche zeigt und sendet, was der Server prueft)
avvVersion: aboModell ? analyseUnterlagen().avvVersion : config.analyse.avvVersion,
legal: aboModell ? analyseUnterlagen().legal : legalPaths(config.analyse.avvVersion),
controller,
activatedAt: doc?.state === 'active' ? doc.activatedAt || null : null,
}
@@ -215,10 +217,11 @@ router.get('/', requireSession, async (req, res) => {
const projects = await listCustomerFeatures(customerId, { docs })
const zeitenAn = zeitenFuerKunde(req.session.email)
const jetzt = einschaltenDeps.jetzt()
const neu = neueAnsicht(req)
// Ein Fehler bei einem Projekt (z. B. Abo-Pruefung) darf nicht die ganze Liste kosten
await Promise.all(
projects.map(async (p) => {
p.analyse = await analyseView(customerId, p.projectId).catch((err) => {
p.analyse = await analyseView(customerId, p.projectId, { aboModell: neu }).catch((err) => {
console.error('[funktionen] Analyse-Zustand fehlgeschlagen:', err.message)
return { state: 'unknown' }
})
@@ -252,7 +255,7 @@ router.get('/', requireSession, async (req, res) => {
}
// Kundenansicht neu (V5): Abo-Modell mit Kundenart, Abo-Stand, Katalog und Anfragestatus von Webshop und Buchungskalender je Projekt.
// Ohne neue Ansicht bleibt die Antwort wie sie war. Ein Lesefehler beim Abo-Stand ist ein Fehler der Liste (nie ein erfundenes "kein Abo").
if (neueAnsicht(req)) {
if (neu) {
const [kunde, abo, katalog, anfrageStatus] = await Promise.all([
kundenDokument(customerId),
aboProdukt(customerId, { now: jetzt }),
@@ -601,14 +604,36 @@ router.post('/analyse/activate', analyseGuard, async (req, res) => {
return res.status(403).json({ error: 'In der Kundenansicht kann nichts geändert werden.' })
}
const { projectId, accentColor, consent } = req.body || {}
await assertOwnProject(getSessionCustomerId(req), analyseProjectId(projectId), { forNewOrder: true })
// Neue Ansicht (Abo-Modell): die Zielgruppen-Aussage (keine Website fuer Kinder unter 16) ist serverseitig erzwungen, nicht nur ein
// Haekchen im Browser; vor jedem Schreiben. Die alte Ansicht bleibt unveraendert.
const neu = neueAnsicht(req)
if (neu && req.body?.audienceConfirmed !== true) {
throw bestellFehler(400, 'Bitte bestätige, dass sich deine Website nicht gezielt an Kinder unter 16 Jahren richtet.', 'zielgruppe_fehlt')
}
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
const abo = neu
? {
aboModell: true,
nachweis: async () =>
speichereNachweisAusAnfrage(req, {
feature: abowegAnalyse(),
customerId,
projectId,
klickAt: einschaltenDeps.jetzt().toISOString(),
kundenart: kundenartWirksam(await kundenDokument(customerId)),
}),
}
: {}
const { doc } = await activateAnalyse({
customerId: getSessionCustomerId(req),
customerId,
projectId: analyseProjectId(projectId),
accentColor,
consent,
by: 'customer',
actor: actingPerson(req.session),
...abo,
})
res.json({ ok: true, state: doc.state })
} catch (err) {