Kundenansicht P5: gesammelte Widerrufsfristen (sammleFristen, GET /api/fristen)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
882438df38
commit
8e0d2b2736
4 files changed
+218
No files matched your search
@@ -0,0 +1,80 @@
|
||||
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,
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user