Buchungskalender: Aufbewahrung und Löschfristen
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f57a3d477e
commit
c019d3c3b0
9 files changed
+719
-13
No files matched your search
@@ -4,6 +4,7 @@ import { config } from '../config.js'
|
||||
import { deleteDocument, getDocument, listDocuments, Query, updateDocument } from './appwriteAdmin.js'
|
||||
import { deleteNewsImage } from './newsImages.js'
|
||||
import { loescheShopDaten } from './shop/loeschen.js'
|
||||
import { hatUkDaten, loescheUkDaten } from './unterkunft/aufbewahrung.js'
|
||||
import { disable } from './siteFeatures.js'
|
||||
import { getStripe } from './stripeBilling.js'
|
||||
import { filesOf, STATUS } from './takeoverCredits.js'
|
||||
@@ -37,6 +38,9 @@ import { filesOf, STATUS } from './takeoverCredits.js'
|
||||
* Abschalten die Zeiten und Erinnerungsmerker des Kunden (loescheZeitenVonKunde, per dynamischem Import wie bei siteFeatures).
|
||||
* Scheitert das, werden diese Funktionen nicht abgeschaltet (solange Daten da sind, soll nichts "aus" aussehen); der Fehler steht unter
|
||||
* "Fehlgeschlagen" im Bericht.
|
||||
* - Buchungskalender: Fuer faellige Kunden loescht derselbe Lauf auch alle uk*-Daten ihrer Funktionen 'unterkunft' (plan.news[].ukIds, nur wenn es welche gibt;
|
||||
* Deckel UK_LOESCH_DECKEL je Kunde, Rest = Kunde gehalten). Die eigenen Fristen (ipHash, Anonymisierung, Rechnungen 8 Jahre, 90 Tage nach dem
|
||||
* Abschalten) laufen im Tagesjob des Buchungskalenders (unterkunft/aufbewahrung.js).
|
||||
* 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
|
||||
@@ -51,6 +55,7 @@ const FEATURES = () => config.collections.portalFeatures
|
||||
const CONSENTS = () => config.collections.featureConsents
|
||||
const SHOP_CONFIG = () => config.collections.shopConfig
|
||||
const SHOP_LOESCH_DECKEL = 2000
|
||||
const UK_LOESCH_DECKEL = 2000
|
||||
|
||||
/** Felder am Funktionsdokument, die die Zustimmung zur Bestellung festhalten (leer => nichts zu loeschen). */
|
||||
const FEATURE_CONSENT_FIELDS = ['consentIp', 'consentUserAgent', 'consentText', 'consentAt']
|
||||
@@ -154,7 +159,10 @@ export async function findDueDeletions(now = new Date(), { limit = LIST_LIMIT }
|
||||
const withFields = allFeatures.filter(hasConsentFields)
|
||||
const consents = await list(CONSENTS(), [Query.equal('customerId', customerId)])
|
||||
const shops = await shopsOf(customerId, list)
|
||||
if (!posts.length && !features.length && !consents.length && !withFields.length && !shops.length) continue
|
||||
// Buchungskalender: Funktionen 'unterkunft' mit uk*-Daten (auch abgeschaltete); ohne Collections (Setup nicht gelaufen) gibt es keine
|
||||
const ukIds = []
|
||||
for (const f of allFeatures.filter((x) => x.featureKey === 'unterkunft')) if (await hatUkDaten(f.$id)) ukIds.push(f.$id)
|
||||
if (!posts.length && !features.length && !consents.length && !withFields.length && !shops.length && !ukIds.length) continue
|
||||
news.push({
|
||||
customerId,
|
||||
posts: posts.map((p) => ({ id: p.$id, projectId: p.projectId, imageId: p.imageId || null })),
|
||||
@@ -162,6 +170,7 @@ export async function findDueDeletions(now = new Date(), { limit = LIST_LIMIT }
|
||||
consentIds: consents.map((c) => c.$id),
|
||||
featureFieldIds: withFields.map((f) => f.$id),
|
||||
...(shops.length ? { shops } : {}),
|
||||
...(ukIds.length ? { ukIds } : {}),
|
||||
zeitenIds: features.filter((f) => f.featureKey === 'zeiten').map((f) => f.$id),
|
||||
})
|
||||
}
|
||||
@@ -216,12 +225,12 @@ async function deleteQuietly(collection, id) {
|
||||
* 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, featureConsents: number, featureFields: number, shops: number, shopDocs: number,
|
||||
* zeiten: number },
|
||||
* zeiten: number, unterkuenfte: number, ukDocs: number },
|
||||
* manual: { customerId: string, subscriptionId: string, reason: string }[],
|
||||
* failed: { what: string, id: string, reason: string }[], held: string[] }>}
|
||||
*/
|
||||
export async function applyDeletions(plan) {
|
||||
const deleted = { subscriptions: 0, contractEvents: 0, takeover: 0, stripeCleared: 0, newsPosts: 0, newsImages: 0, features: 0, featureConsents: 0, featureFields: 0, shops: 0, shopDocs: 0, zeiten: 0 }
|
||||
const deleted = { subscriptions: 0, contractEvents: 0, takeover: 0, stripeCleared: 0, newsPosts: 0, newsImages: 0, features: 0, featureConsents: 0, featureFields: 0, shops: 0, shopDocs: 0, zeiten: 0, unterkuenfte: 0, ukDocs: 0 }
|
||||
const manual = []
|
||||
const failed = []
|
||||
const held = []
|
||||
@@ -286,6 +295,18 @@ export async function applyDeletions(plan) {
|
||||
for (const id of entry.consentIds || []) {
|
||||
if (await attempt('featureConsents', id, () => deleteQuietly(CONSENTS(), id))) deleted.featureConsents += 1
|
||||
}
|
||||
// Buchungskalender: alle uk*-Daten der Funktion (Anfragen, Rechnungen, Sperren, Preise, Unterkuenfte, Einstellungen); gemeinsamer Deckel je Kunde
|
||||
let ukRest = UK_LOESCH_DECKEL
|
||||
for (const featureId of entry.ukIds || []) {
|
||||
await attempt('unterkunft', featureId, async () => {
|
||||
const weg = await loescheUkDaten({ featureId, deckel: ukRest })
|
||||
ukRest -= weg.geloescht
|
||||
deleted.ukDocs += weg.geloescht
|
||||
if (!weg.vollstaendig) throw new Error('Buchungsdaten nicht vollständig gelöscht (Deckel je Lauf), der nächste Lauf macht weiter')
|
||||
deleted.unterkuenfte += 1
|
||||
return true
|
||||
})
|
||||
}
|
||||
// Webshop-Daten: Bestellungen, Artikel, Widerrufe, Rechtstexte-Archiv, Bilder, zuletzt shopConfig (Deckel je Kunde; Rest = der Kunde wird gehalten)
|
||||
let shopRest = SHOP_LOESCH_DECKEL
|
||||
for (const shop of entry.shops || []) {
|
||||
@@ -366,9 +387,10 @@ export function formatReport(plan, result = null) {
|
||||
const consents = n.consentIds?.length || 0
|
||||
const fields = n.featureFieldIds?.length || 0
|
||||
const shops = n.shops?.length || 0
|
||||
const uk = n.ukIds?.length ? `; ${n.ukIds.length} Buchungskalender samt Anfragen, Rechnungen und Unterkünften zu löschen` : ''
|
||||
const zeiten = n.zeitenIds?.length ? `; Öffnungszeiten-Daten von ${n.zeitenIds.length} Funktion(en) zu löschen` : ''
|
||||
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, ${fields} Funktionsdokument(e) mit Zustimmungsangaben zu leeren${shops ? `, ${shops} Webshop(s) samt Bestellungen, Artikeln, Widerrufen und Bildern zu löschen` : ''}${zeiten}`
|
||||
`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, ${fields} Funktionsdokument(e) mit Zustimmungsangaben zu leeren${shops ? `, ${shops} Webshop(s) samt Bestellungen, Artikeln, Widerrufen und Bildern zu löschen` : ''}${uk}${zeiten}`
|
||||
)
|
||||
}
|
||||
const del = plan.takeover.filter((t) => t.action === 'loeschen')
|
||||
@@ -391,6 +413,7 @@ export function formatReport(plan, result = null) {
|
||||
`Zustimmungsangaben an Funktionsdokumenten (IP, Browser, Text, Zeit): ${d.featureFields} geleert.`,
|
||||
...(d.shops || d.shopDocs ? [`Webshop: ${d.shops} Shop(s) gelöscht (${d.shopDocs} Dokument(e) in Bestellungen, Artikeln und Widerrufen).`] : [])
|
||||
)
|
||||
if (d.unterkuenfte || d.ukDocs) lines.push(`Buchungskalender: ${d.unterkuenfte} Funktion(en) gelöscht (${d.ukDocs} Dokument(e)).`)
|
||||
if (d.zeiten) lines.push(`Öffnungszeiten: ${d.zeiten} Datensatz/Datensätze 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