Funktionen: disable() idempotent, disabledReason, Schema-Skript feature-setup
disable() laeuft unter einer Sperre je Dokument und aendert ein schon
abgeschaltetes Dokument nicht mehr (disabledAt als Beginn der 30-Tage-Frist,
Grund und Notiz bleiben); nur Altbestand ohne disabledAt bekommt dieses Feld.
Neu: DISABLED_REASONS und der Parameter reason (Vorgabe team, unbekannt = 400),
disableCustomerFeatures reicht ihn durch, die Kundenloeschung nutzt
kunde_geloescht. activate() leert zusaetzlich disabledReason und aboEndeMailAt
und laesst zahlungMeldung unberuehrt.
scripts/feature-setup.mjs legt portalFeatures.disabledReason, aboEndeMailAt und
zahlungMeldung idempotent an (--check meldet Luecken mit Exit 1);
schemaKeys('portalFeatures') zieht die Schluessel mit.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
39225594e5
commit
aaed273b62
9 files changed
+472
-18
No files matched your search
@@ -286,7 +286,7 @@ export async function deleteCustomerWithPortalAccess(customerId) {
|
||||
// Fehler halten das Loeschen nicht auf (wie bei der Analyse).
|
||||
try {
|
||||
const { disableCustomerFeatures } = await import('./siteFeatures.js')
|
||||
await disableCustomerFeatures(customerId, 'termine', { adminNote: 'Kunde gelöscht' })
|
||||
await disableCustomerFeatures(customerId, 'termine', { adminNote: 'Kunde gelöscht', reason: 'kunde_geloescht' })
|
||||
} catch (err) {
|
||||
console.error(`[customerAdmin] Terminplaner von Kunde ${customerId} nicht abgeschaltet:`, err.message)
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ export const FEATURE_LABELS = {
|
||||
termine: { name: 'Terminplaner', keine: 'keinen Terminplaner' },
|
||||
}
|
||||
export const STATES = ['off', 'ordered', 'setup', 'active', 'disabled']
|
||||
/** Warum eine Funktion abgeschaltet ist (portalFeatures.disabledReason); 'team' ist der Vorgabewert (Abschalten im WOMS, Aufbewahrungsfrist). */
|
||||
export const DISABLED_REASONS = ['team', 'abo_ende', 'abo_ende_vor_einrichtung', 'kunde_geloescht']
|
||||
|
||||
export const DISABLED_MESSAGE =
|
||||
'Die News-Pinnwand wurde für diese Website abgeschaltet. Schreib uns im Chat, wenn du sie wieder möchtest.'
|
||||
@@ -295,18 +297,44 @@ export async function activate(docId, { by, adminNote } = {}) {
|
||||
return updateDocument(config.collections.portalFeatures, docId, {
|
||||
...stateFields('active'),
|
||||
activatedAt: new Date().toISOString(),
|
||||
// Eine wieder eingeschaltete Funktion darf nie in den 30-Tage-Loeschlauf des Terminplaners fallen (disabledAt-Marker)
|
||||
// Eine wieder eingeschaltete Funktion darf nie in den 30-Tage-Loeschlauf des Terminplaners fallen (disabledAt-Marker);
|
||||
// Grund und Marker der Abo-Ende-Mail gehoeren zur Abschaltung und enden mit ihr. zahlungMeldung bleibt bewusst unberuehrt:
|
||||
// ein noch offener Zahlungsfall darf durch eine Aktivierung nicht verloren gehen.
|
||||
disabledAt: null,
|
||||
disabledReason: null,
|
||||
aboEndeMailAt: null,
|
||||
...(note ? { adminNote: note } : {}),
|
||||
})
|
||||
}
|
||||
|
||||
export async function disable(docId, { by, adminNote } = {}) {
|
||||
const note = adminNote ?? byNote(by, 'Abgeschaltet')
|
||||
const doc = await updateDocument(config.collections.portalFeatures, docId, {
|
||||
...stateFields('disabled'),
|
||||
disabledAt: new Date().toISOString(),
|
||||
...(note ? { adminNote: note } : {}),
|
||||
function assertReason(reason) {
|
||||
if (!DISABLED_REASONS.includes(reason)) throw httpError(400, 'Unbekannter Abschaltgrund')
|
||||
}
|
||||
|
||||
/**
|
||||
* Funktion abschalten. Idempotent: steht das Dokument schon auf `disabled`, bleibt alles wie es ist (disabledAt als Beginn der
|
||||
* 30-Tage-Loeschfrist, Grund, Notiz) und es gibt keinen Schreibzugriff; ein Doppelklick im WOMS, die Kundenloeschung oder ein
|
||||
* zweiter Lauf verschieben die Frist also nie. Einzige Ausnahme Altbestand: fehlt einem `disabled`-Dokument disabledAt, wird nur
|
||||
* dieses Feld gesetzt (sonst fiele es nie in die Loeschung). Die Nacharbeit der Analyse (Sammeldienst) laeuft bei jedem Aufruf.
|
||||
* @param {string} docId
|
||||
* @param {{ by?: string, adminNote?: string, reason?: 'team'|'abo_ende'|'abo_ende_vor_einrichtung'|'kunde_geloescht' }} [options]
|
||||
* @throws {Error} 400 bei unbekanntem Grund (ohne Schreibzugriff), 404 bei unbekanntem Dokument
|
||||
*/
|
||||
export async function disable(docId, { by, adminNote, reason = 'team' } = {}) {
|
||||
assertReason(reason)
|
||||
const doc = await withLocks([`feature-disable:${docId}`], async () => {
|
||||
const current = await getDocument(config.collections.portalFeatures, docId)
|
||||
if (current.state === 'disabled') {
|
||||
if (current.disabledAt) return current
|
||||
return updateDocument(config.collections.portalFeatures, docId, { disabledAt: new Date().toISOString() })
|
||||
}
|
||||
const note = adminNote ?? byNote(by, 'Abgeschaltet')
|
||||
return updateDocument(config.collections.portalFeatures, docId, {
|
||||
...stateFields('disabled'),
|
||||
disabledAt: new Date().toISOString(),
|
||||
disabledReason: reason,
|
||||
...(note ? { adminNote: note } : {}),
|
||||
})
|
||||
})
|
||||
if (doc?.featureKey === 'analyse' && doc.siteKey) await stopAnalyseCollecting(doc)
|
||||
return doc
|
||||
@@ -318,8 +346,9 @@ export async function disable(docId, { by, adminNote } = {}) {
|
||||
* abgeschaltet und danach der erste Fehler weitergereicht.
|
||||
* @returns {Promise<object[]>} die abgeschalteten Dokumente
|
||||
*/
|
||||
export async function disableCustomerFeatures(customerId, key, { by, adminNote } = {}) {
|
||||
export async function disableCustomerFeatures(customerId, key, { by, adminNote, reason = 'team' } = {}) {
|
||||
assertKey(key)
|
||||
assertReason(reason)
|
||||
const docs = (await listCustomerFeatureDocs(customerId)).filter(
|
||||
(d) => d.featureKey === key && d.state !== 'off' && d.state !== 'disabled'
|
||||
)
|
||||
@@ -327,7 +356,7 @@ export async function disableCustomerFeatures(customerId, key, { by, adminNote }
|
||||
let failure = null
|
||||
for (const doc of docs) {
|
||||
try {
|
||||
disabled.push(await disable(doc.$id, { by, adminNote }))
|
||||
disabled.push(await disable(doc.$id, { by, adminNote, reason }))
|
||||
} catch (err) {
|
||||
failure ||= err
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user