Kundenansicht P6: K02 und K03 nennen das Ende der Abo-Funktionen

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-16 16:31:06 +00:00
1 parent 9489a70f0c
commit cdc8f5c424
5 files changed
+73 -17

No files matched your search

+26 -13
View File
@@ -22,6 +22,7 @@ import {
} from '../services/planSwitch.js'
import { handleFeatureCheckoutEvent, isFeatureCheckoutEvent, listCustomerFeatureDocs, paidFeatureOrders } from '../services/siteFeatures.js'
import { featureConfig } from '../services/featureRegistry.js'
import { istAboModell } from '../services/aboModell.js'
import { PRODUCT_KEYS, contractAttachmentFor, describeItems, isTermsV4OrNewer, productLabel, productView, termsDocFor } from '../services/products.js'
import {
ACTIVE_STATUSES,
@@ -413,23 +414,35 @@ router.post('/switch', requireSession, async (req, res) => {
// ---------------------------------------------------------------------------
/**
* Hat der Kunde dieses Abos die Website-Analyse oder den Terminplaner (Funktionsdokument in portalFeatures mit featureKey analyse
* oder termine, Zustand ausser off; ein Dokument ohne Zustand gilt wie off, wie in der Ansicht)? Nur dann gilt der Hinweis in K02 und
* K03 (Block funktionen), dass beide mit dem Wartungs-Abo enden (Entscheidung 2026-10-05). Der Kunde ist der des Abos (sub.customerId,
* wie in customerNameOf); gelesen wird wie sonst auch ueber listCustomerFeatureDocs. Wirft nie: ein Lesefehler zaehlt als ja (der Kunde
* wird eher informiert als nicht), eine Logzeile, die Mail geht in jedem Fall raus.
* Welche Funktionen hat der Kunde dieses Abos? `alt`: die Website-Analyse oder der Terminplaner (Funktionsdokument in portalFeatures
* mit featureKey analyse oder termine, Zustand ausser off; ein Dokument ohne Zustand gilt wie off, wie in der Ansicht); dann gilt der
* Hinweis in K02 und K03 (Block funktionen), dass beide mit dem Wartungs-Abo enden (Entscheidung 2026-10-05). `modell`: mindestens
* ein Dokument des Abo-Modells (istAboModell) in einem Zustand ausser off; dann gilt statt dessen der Hinweis funktionen_abo
* (Kundenansicht neu, P6). Der Kunde ist der des Abos (sub.customerId, wie in customerNameOf); gelesen wird wie sonst auch ueber
* listCustomerFeatureDocs. Wirft nie: ein Lesefehler zaehlt als { alt: true, modell: false } (der Kunde wird eher informiert als
* nicht), eine Logzeile, die Mail geht in jedem Fall raus.
*/
async function hatAboFunktionen(sub) {
try {
if (!sub?.customerId) throw new Error('Abo ohne Kunden-ID')
const docs = await listCustomerFeatureDocs(sub.customerId)
return docs.some((doc) => ABO_FUNKTIONEN.includes(doc.featureKey) && (doc.state || 'off') !== 'off')
const laeuft = (doc) => (doc.state || 'off') !== 'off'
return {
alt: docs.some((doc) => ABO_FUNKTIONEN.includes(doc.featureKey) && laeuft(doc)),
modell: docs.some((doc) => istAboModell(doc) && laeuft(doc)),
}
} catch (err) {
console.error(`[abo] Funktionen des Kunden nicht lesbar, der Hinweis auf das Ende von Analyse und Terminplaner bleibt in der Kündigungsbestätigung (Abo ${sub?.$id || '-'}): ${err.message}`)
return true
return { alt: true, modell: false }
}
}
/** Blöcke funktionen (alter Hinweis) und funktionen_abo (Abo-Modell) der Kündigungsmails K02 und K03; nur beim Wartungs-Abo, sonst wird nichts gelesen. */
async function funktionenBloecke(sub) {
const { alt, modell } = isWartung(sub) ? await hatAboFunktionen(sub) : { alt: false, modell: false }
return { funktionen: alt && !modell, funktionen_abo: modell }
}
/** Anrede in Kundenmails: Name aus den Kundendaten des Portals, sonst der übergebene Ersatz. */
async function customerNameOf(sub, fallback = '') {
const customer = sub?.customerId
@@ -497,9 +510,9 @@ async function sendPortalCancellationConfirmation(sub, createdUnix) {
vertragsende: fmtDate(endsAt),
vertragsende_paragraph: endParagraphOf(sub),
},
// Hinweis auf nicht genutzte Arbeitszeit nur beim Wartungs-Abo; auf das Ende von Website-Analyse und Terminplaner nur beim
// Wartungs-Abo eines Kunden, der eine von beiden hat (hatAboFunktionen)
blocks: { wartung: isWartung(sub), funktionen: isWartung(sub) && (await hatAboFunktionen(sub)) },
// Hinweis auf nicht genutzte Arbeitszeit nur beim Wartungs-Abo; auf das Ende von Website-Analyse und Terminplaner (funktionen)
// bzw. der Funktionen des Abo-Modells (funktionen_abo) nur beim Wartungs-Abo eines Kunden, der sie hat (funktionenBloecke)
blocks: { wartung: isWartung(sub), ...(await funktionenBloecke(sub)) },
})
await updateDocument(config.collections.subscriptions, sub.$id, {
cancelRequestedAt: receivedAt.toISOString(),
@@ -976,9 +989,9 @@ router.post('/public/cancel', async (req, res) => {
abo: !storage,
// Hinweis auf nicht genutzte Arbeitszeit nur im Wartungs-Abo (Block innerhalb von abo)
wartung: !storage && isWartung(sub),
// Website-Analyse und Terminplaner enden mit dem Wartungs-Abo (Block innerhalb von abo, nie bei der Speicher-Kündigung),
// nur fuer einen Kunden, der eine von beiden hat (hatAboFunktionen)
funktionen: !storage && isWartung(sub) && (await hatAboFunktionen(sub)),
// Website-Analyse und Terminplaner (funktionen) bzw. die Funktionen des Abo-Modells (funktionen_abo) enden mit dem
// Wartungs-Abo (Bloecke innerhalb von abo, nie bei der Speicher-Kündigung), nur fuer einen Kunden, der sie hat (funktionenBloecke)
...(storage ? { funktionen: false, funktionen_abo: false } : await funktionenBloecke(sub)),
automatisch_gekuendigt: !storage && Boolean(effectiveAt) && d.kind !== 'geschaeftsaufgabe',
sonderkuendigung: !storage && Boolean(effectiveAt) && d.kind === 'geschaeftsaufgabe',
ausserordentlich: !storage && !effectiveAt && d.kind === 'ausserordentlich',