import { config } from '../config.js' import { getDocument } from './appwriteAdmin.js' import { listContractEvents, openWithdrawals } from './contractEvents.js' import { withdrawalDeadline } from './deadlines.js' import { FEATURE_LABELS, paidFeatureOrders } from './siteFeatures.js' import { listAuftraege } from './auftraege/auftragStore.js' import { auftraegeAktiv } from './auftraege/schalter.js' import { kundenartWirksam } from './kundenart.js' /** * Gesammelte Widerrufsfristen eines Kunden (V6, Spec 5.6): jede noch offene Frist aus Abo-Abschluss, Wechsel ins * Wartungs-Abo, Zusatzspeicher (nur Plus), bezahlten Funktionen (Altbestand mit Kauf) und Auftragsbestätigungen * (nur mit Schalter und Snapshot-Kundenart privat). Offen ist eine Frist bis einschließlich ihres Endes * (23:59:59.999 Berliner Zeit am letzten Tag). `anzeigen` ist nur für Privatkunden wahr; Betriebe haben kein * Widerrufsrecht und sehen keine Fristen (die Liste bleibt trotzdem gefüllt). Lesefehler werden weitergereicht. */ const BEZEICHNUNG = { abschluss: 'Abo-Abschluss', wechsel: 'Wechsel ins Wartungs-Abo', speicher: 'Zusatzspeicher', } const zeitpunkt = (wert) => (wert instanceof Date ? wert.getTime() : Date.parse(wert)) /** Kundendokument einmal lesen; ein unbekannter Kunde ergibt {}. */ async function leseKunde(customerId) { try { return await getDocument(config.collections.customers, customerId) } catch (err) { if (err.status === 404) return {} throw err } } /** Ende der Frist zu einem Vertragsschluss; kein lesbarer Zeitpunkt ergibt null (keine Frist, nie ein Fehler). */ function fristEnde(vertragAt) { const ms = zeitpunkt(vertragAt || '') return Number.isNaN(ms) ? null : withdrawalDeadline(new Date(ms)).endsAt } /** * @returns {Promise<{ fristen: Array<{ quelle: 'abschluss'|'wechsel'|'speicher'|'funktion'|'projekt', bezeichnung: string, bis: string, featureKey?: string, projektName?: string }>, fruehestesEnde: string|null, anzeigen: boolean }>} */ export async function sammleFristen(customerId, { now = new Date() } = {}) { const kunde = await leseKunde(customerId) const fristen = [] for (const f of openWithdrawals(await listContractEvents(customerId), now)) { fristen.push({ quelle: f.type, bezeichnung: BEZEICHNUNG[f.type], bis: new Date(zeitpunkt(f.until)).toISOString() }) } for (const order of await paidFeatureOrders(customerId)) { const ende = fristEnde(order.vertragAt) if (!ende || now.getTime() > ende.getTime()) continue fristen.push({ quelle: 'funktion', bezeichnung: FEATURE_LABELS[order.featureKey]?.name || order.featureKey, bis: ende.toISOString(), featureKey: order.featureKey, projektName: order.projekt, }) } if (auftraegeAktiv(kunde)) { for (const auftrag of await listAuftraege({ kundenId: customerId })) { if (auftrag.kunde?.kundenart !== 'privat' || !['versendet', 'bestaetigt'].includes(auftrag.status)) continue const ende = fristEnde(auftrag.gesendetAm) if (!ende || now.getTime() > ende.getTime()) continue fristen.push({ quelle: 'projekt', bezeichnung: `Auftrag ${auftrag.nummer}`, bis: ende.toISOString() }) } } fristen.sort((a, b) => Date.parse(a.bis) - Date.parse(b.bis)) return { fristen, fruehestesEnde: fristen[0]?.bis ?? null, anzeigen: kundenartWirksam(kunde) === 'privat' && fristen.length > 0, } }