Retention: Zustimmungsnachweise der Funktionsbestellung drei Jahre nach Abo-Ende loeschen
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a629b2519b
commit
5b5b4b90eb
4 files changed
+106
-15
No files matched your search
@@ -21,6 +21,10 @@ import { filesOf, STATUS } from './takeoverCredits.js'
|
||||
* - News-Pinnwand: Wird ein Kunde geloescht (Frist wie oben), loeschen wir auch seine News-Beitraege samt Fotos
|
||||
* (newsPosts, Bilddateien) und setzen seine Funktionen (portalFeatures) auf disabled. Der Plan fuehrt sie in
|
||||
* plan.news (nur Kunden mit Beitraegen oder noch nicht abgeschalteten Funktionen), damit plan.customers unveraendert bleibt.
|
||||
* - Funktionsbestellungen: Die Zustimmungsnachweise zu bestellten Funktionen (featureConsents, Datenschutzerklaerung Abschnitt 7:
|
||||
* "Diese Angaben loeschen wir drei Jahre nach Ablauf des Jahres, in dem Ihr letztes Abo endet") loescht derselbe Lauf fuer faellige
|
||||
* Kunden mit (plan.news[].consentIds). Kunden ohne Abo, zum Beispiel nur mit News, sind nie faellig; ihre Nachweise bleiben, wie ihre
|
||||
* Beitraege, ausserhalb dieser Frist.
|
||||
* Zeitrechnung in deutscher Zeit: Silvester 23:30 in Berlin ist noch das alte Jahr.
|
||||
* Nicht abgedeckt: Vertragsereignisse ohne customerId bzw. ohne zugehoeriges Abo-Dokument (Waisen) werden
|
||||
* weder gefunden noch geloescht; sie muessen von Hand geprueft werden. Listen werden nicht paginiert: wird eine
|
||||
@@ -32,6 +36,7 @@ const EVENTS = () => config.collections.contractEvents
|
||||
const TAKEOVER = () => config.collections.takeoverCredits
|
||||
const NEWS_POSTS = () => config.collections.newsPosts
|
||||
const FEATURES = () => config.collections.portalFeatures
|
||||
const CONSENTS = () => config.collections.featureConsents
|
||||
|
||||
export const RETENTION_YEARS = 3
|
||||
export const RECEIPT_YEARS = 8
|
||||
@@ -68,6 +73,7 @@ function endOf(doc) {
|
||||
/**
|
||||
* @param {Date} [now]
|
||||
* @returns {Promise<{ customers: { customerId: string, lastEndAt: string, subscriptionIds: string[], eventIds: string[] }[],
|
||||
* news: { customerId: string, posts: { id: string, projectId: string, imageId: string | null }[], featureIds: string[], consentIds: string[] }[],
|
||||
* takeover: { id: string, reason: string, action: 'loeschen' | 'melden' }[],
|
||||
* pending: { customerId: string, subscriptionIds: string[], reason: string }[], skipped: { id: string, reason: string }[] }>}
|
||||
*/
|
||||
@@ -112,11 +118,13 @@ export async function findDueDeletions(now = new Date(), { limit = LIST_LIMIT }
|
||||
for (const { customerId } of customers) {
|
||||
const posts = await list(NEWS_POSTS(), [Query.equal('customerId', customerId)])
|
||||
const features = (await list(FEATURES(), [Query.equal('customerId', customerId)])).filter((f) => f.state !== 'disabled')
|
||||
if (!posts.length && !features.length) continue
|
||||
const consents = await list(CONSENTS(), [Query.equal('customerId', customerId)])
|
||||
if (!posts.length && !features.length && !consents.length) continue
|
||||
news.push({
|
||||
customerId,
|
||||
posts: posts.map((p) => ({ id: p.$id, projectId: p.projectId, imageId: p.imageId || null })),
|
||||
featureIds: features.map((f) => f.$id),
|
||||
consentIds: consents.map((c) => c.$id),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -165,12 +173,12 @@ async function deleteQuietly(collection, id) {
|
||||
* Appwrite-Dokumente. Lehnt Stripe ab (beendete Abos lassen sich oft nicht mehr aendern), steht das Abo in manual;
|
||||
* die Appwrite-Dokumente werden trotzdem geloescht, denn die Frist ist abgelaufen.
|
||||
* @returns {Promise<{ deleted: { subscriptions: number, contractEvents: number, takeover: number, stripeCleared: number,
|
||||
* newsPosts: number, newsImages: number, features: number },
|
||||
* newsPosts: number, newsImages: number, features: number, featureConsents: number },
|
||||
* manual: { customerId: string, subscriptionId: string, reason: string }[],
|
||||
* failed: { what: string, id: string, reason: string }[] }>}
|
||||
*/
|
||||
export async function applyDeletions(plan) {
|
||||
const deleted = { subscriptions: 0, contractEvents: 0, takeover: 0, stripeCleared: 0, newsPosts: 0, newsImages: 0, features: 0 }
|
||||
const deleted = { subscriptions: 0, contractEvents: 0, takeover: 0, stripeCleared: 0, newsPosts: 0, newsImages: 0, features: 0, featureConsents: 0 }
|
||||
const manual = []
|
||||
const failed = []
|
||||
const clear = Object.fromEntries(CONSENT_METADATA_KEYS.map((key) => [key, '']))
|
||||
@@ -225,6 +233,10 @@ export async function applyDeletions(plan) {
|
||||
})
|
||||
if (off) deleted.features += 1
|
||||
}
|
||||
// Zustimmungsnachweise der Funktionsbestellungen zuletzt: ein Fehler stoppt den Rest nicht, der naechste Lauf findet den Eintrag erneut
|
||||
for (const id of entry.consentIds || []) {
|
||||
if (await attempt('featureConsents', id, () => deleteQuietly(CONSENTS(), id))) deleted.featureConsents += 1
|
||||
}
|
||||
}
|
||||
|
||||
for (const item of (plan.takeover || []).filter((t) => t.action === 'loeschen')) {
|
||||
@@ -251,7 +263,10 @@ export function formatReport(plan, result = null) {
|
||||
}
|
||||
for (const n of plan.news || []) {
|
||||
const images = n.posts.filter((p) => p.imageId).length
|
||||
lines.push(`Kunde ${n.customerId}: ${n.posts.length} News-Beitrag/-Beiträge mit ${images} Foto(s) zu löschen, ${n.featureIds.length} Funktion(en) abzuschalten`)
|
||||
const consents = n.consentIds?.length || 0
|
||||
lines.push(
|
||||
`Kunde ${n.customerId}: ${n.posts.length} News-Beitrag/-Beiträge mit ${images} Foto(s) zu löschen, ${n.featureIds.length} Funktion(en) abzuschalten, ${consents} Zustimmungsnachweis(e) zu löschen`
|
||||
)
|
||||
}
|
||||
const del = plan.takeover.filter((t) => t.action === 'loeschen')
|
||||
const report = plan.takeover.filter((t) => t.action === 'melden')
|
||||
@@ -268,7 +283,8 @@ export function formatReport(plan, result = null) {
|
||||
lines.push(
|
||||
'',
|
||||
`Gelöscht: ${d.subscriptions} Abo(s), ${d.contractEvents} Vertragsereignis(se), ${d.takeover} Übernahme-Antrag/-Anträge; Stripe-Metadaten geleert bei ${d.stripeCleared} Abo(s).`,
|
||||
`News: ${d.newsPosts} Beitrag/Beiträge und ${d.newsImages} Foto(s) gelöscht, ${d.features} Funktion(en) abgeschaltet.`
|
||||
`News: ${d.newsPosts} Beitrag/Beiträge und ${d.newsImages} Foto(s) gelöscht, ${d.features} Funktion(en) abgeschaltet.`,
|
||||
`Zustimmungsnachweise zu Funktionsbestellungen: ${d.featureConsents} gelöscht.`
|
||||
)
|
||||
if (result.failed?.length) {
|
||||
lines.push('', 'Fehlgeschlagen (Lauf erneut starten):', ...result.failed.map((f) => ` ${f.what} ${f.id}: ${f.reason}`))
|
||||
|
||||
Reference in new issue
Block a user