Kundenansicht P6: Wechsel merkt sich die gewünschte Funktion (details.funktion, wechselFunktion)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-16 16:50:24 +00:00
1 parent db47f13510
commit d5a7312896
3 files changed
+137 -26

No files matched your search

+43 -3
View File
@@ -19,10 +19,13 @@ import {
previewSwitch,
reconcilePendingSwitch,
switchToWartung,
wechselFunktionVon,
} from '../services/planSwitch.js'
import { handleFeatureCheckoutEvent, isFeatureCheckoutEvent, listCustomerFeatureDocs, paidFeatureOrders } from '../services/siteFeatures.js'
import { assertOwnProject, handleFeatureCheckoutEvent, isFeatureCheckoutEvent, listCustomerFeatureDocs, paidFeatureOrders } from '../services/siteFeatures.js'
import { featureConfig } from '../services/featureRegistry.js'
import { istAboModell } from '../services/aboModell.js'
import { ENTHALTEN_IN } from '../services/aboFunktionen.js'
import { neueAnsicht } from '../services/kundenansicht/schalter.js'
import { PRODUCT_KEYS, contractAttachmentFor, describeItems, isTermsV4OrNewer, productLabel, productView, termsDocFor } from '../services/products.js'
import {
ACTIVE_STATUSES,
@@ -164,6 +167,21 @@ function isPilotAllowed(customer, offer = null) {
// Kunde (eingeloggt)
// ---------------------------------------------------------------------------
/**
* publicSubscription, in der neuen Ansicht mit wechselFunktion (die beim Wechsel gewuenschte Funktion, die noch einzuschalten ist, sonst null;
* planSwitch.wechselFunktionVon). In der alten Ansicht fehlt der Schluessel. Ein Lesefehler ergibt null (die Seite bleibt nutzbar).
*/
async function subscriptionMitWechselFunktion(req, customerId, sub, events) {
const view = publicSubscription(sub, events)
if (!view || !neueAnsicht(req)) return view
try {
return { ...view, wechselFunktion: await wechselFunktionVon(customerId, events.filter((e) => e.subscriptionId === sub.$id)) }
} catch (err) {
console.error('[abo] Gewünschte Funktion des Wechsels nicht lesbar:', err.message)
return { ...view, wechselFunktion: null }
}
}
/** Katalog, Abo-Status mit Speicherstand und Wechsel-Moeglichkeit, Angebote je Produkt, Stundenkonto (nur Wartung). */
router.get('/', requireSession, async (req, res) => {
try {
@@ -211,7 +229,7 @@ router.get('/', requireSession, async (req, res) => {
},
offers: { hosting: hostingOffer ? offerView(hostingOffer) : null, wartung: wartungOffer ? offerView(wartungOffer) : null },
ledger,
subscription: publicSubscription(sub, events),
subscription: await subscriptionMitWechselFunktion(req, customerId, sub, events),
})
} catch (err) {
return res.status(500).json({ error: err.message || 'Abo konnte nicht geladen werden' })
@@ -364,6 +382,26 @@ router.post('/switch/preview', requireSession, async (req, res) => {
}
})
/**
* Die beim Wechsel gewuenschte Funktion (Kundenansicht neu, P6, V11) aus dem Body: { key, projectId } oder null. Gueltig ist ein Schluessel
* aus dem Register, den nur das Wartungs-Abo enthaelt (ENTHALTEN_IN nur 'wartung': news, termine, shop, unterkunft, zeiten; nicht analyse) und
* ein Projekt des Kunden (assertOwnProject). Alles andere wird verworfen, nie abgelehnt: die Bequemlichkeit darf den bezahlten Vertrag nicht
* blockieren. Nur die beiden Schluessel key und projectId werden uebernommen.
*/
async function wechselFunktionAusBody(customerId, funktion) {
if (!funktion || typeof funktion !== 'object' || Array.isArray(funktion)) return null
const { key, projectId } = funktion
if (typeof key !== 'string' || typeof projectId !== 'string' || !projectId) return null
const enthalten = Object.hasOwn(ENTHALTEN_IN, key) ? ENTHALTEN_IN[key] : null
if (!enthalten || enthalten.length !== 1 || enthalten[0] !== 'wartung') return null
try {
await assertOwnProject(customerId, projectId)
} catch {
return null
}
return { key, projectId }
}
/**
* Bestellung { acceptTerms: true, earlyStart: true, termsVersion, prorationDate, amountDueCents } ("Zahlungspflichtig
* bestellen"); prorationDate und amountDueCents kommen aus der Vorschau.
@@ -391,12 +429,14 @@ router.post('/switch', requireSession, async (req, res) => {
}
const customerId = getSessionCustomerId(req)
const now = new Date().toISOString()
// Gewuenschte Funktion (nur neue Ansicht): geprueft oder verworfen, der Wechsel laeuft in jedem Fall
const funktion = neueAnsicht(req) ? await wechselFunktionAusBody(customerId, req.body?.funktion) : null
const { subscription, paymentUrl } = await switchToWartung(
customerId,
{ termsVersion, consentAt: now, earlyStartConsentAt: now, consentIp: req.ip, consentUserAgent: req.get('user-agent') || '' },
prorationDate,
// Fix 1 (8): der in der Bestelluebersicht gezeigte Betrag; weicht die neue Vorschau ab, 409
{ expectedAmountCents: amountDueCents }
{ expectedAmountCents: amountDueCents, funktion }
)
const events = await listContractEvents(customerId).catch((e) => {
console.error('[abo] Vertragsereignisse nicht lesbar:', e.message)