Buchungskalender: Aufbewahrung – Sperre wie Ablauf, Restpunkte
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c019d3c3b0
commit
827ee54875
5 files changed
+102
-17
No files matched your search
@@ -1098,8 +1098,6 @@ export const config = {
|
||||
unverbindlichMonate: 6,
|
||||
// Offene (neue) Anfragen verfallen nach so vielen Tagen
|
||||
neuVerfallTage: 60,
|
||||
// Tage nach Abschalten der Funktion bis zur Loeschung aller Buchungsdaten (Tagesjob)
|
||||
loeschTageNachAbschalten: 90,
|
||||
// Hoechstzahl Schreib-/Loeschvorgaenge der Aufbewahrung je Tageslauf
|
||||
aufbewahrungDeckel: 500,
|
||||
// Anfragen werden so viele Jahre nach Abschluss anonymisiert; Rechnungen bleiben rechnungJahre Jahre
|
||||
|
||||
@@ -26,7 +26,7 @@ const SEITE = 100
|
||||
const ANONYM_JAHRE = 3
|
||||
const RECHNUNG_JAHRE = 8
|
||||
const MARKER = 'Buchungsdaten gelöscht am '
|
||||
const MARKER_RE = /^Buchungsdaten gelöscht am (\d{4}-\d{2}-\d{2})/
|
||||
const MARKER_RE = /Buchungsdaten gelöscht am (\d{4}-\d{2}-\d{2})/
|
||||
const ANFRAGE_ANONYM = { gast: {}, nachrichtVermieter: '', ipHash: '' }
|
||||
|
||||
const zeitMs = (v) => {
|
||||
@@ -35,7 +35,8 @@ const zeitMs = (v) => {
|
||||
}
|
||||
const jahrVon = (tag) => Number(String(tag).slice(0, 4))
|
||||
const gueltigerTag = (tag) => typeof tag === 'string' && /^\d{4}-\d{2}-\d{2}/.test(tag)
|
||||
const fehlerText = (err) => `${err?.name}${err?.status ? ` ${err.status}` : ''}: ${String(err?.message ?? '').slice(0, 160)}`
|
||||
/** Nur Name, Status und Code: die Meldung kann Personendaten enthalten und kommt nie ins Log. */
|
||||
const fehlerText = (err) => [err?.name, err?.status, err?.code ?? err?.type].filter((x) => x !== undefined && x !== null && x !== '').join(' ')
|
||||
const sammlungFehlt = (err) => err?.type === 'collection_not_found' || err?.code === 'collection_not_found'
|
||||
|
||||
/** now minus n Kalendermonate (UTC, Tag wird auf den Monatsletzten gekuerzt). */
|
||||
@@ -86,13 +87,14 @@ async function anfragenMitRechnung() {
|
||||
* Tagesarbeit der Aufbewahrung.
|
||||
* @param {Date} now
|
||||
* @param {{ deckel?: number }} [opt] hoechstens so viele Schreib-/Loeschvorgaenge (Standard 500)
|
||||
* @returns {Promise<{ anfragen: number, gaeste: number, ipHashes: number, abgelaufen: number, rechnungen: number, adressen: number, fehler: number, gedeckelt: boolean }>}
|
||||
* @returns {Promise<{ verbraucht: number (genutzter Deckel), anfragen: number, gaeste: number, ipHashes: number, abgelaufen: number, rechnungen: number, adressen: number, fehler: number, gedeckelt: boolean }>}
|
||||
* anfragen = unverbindliche Anfragen anonymisiert, gaeste = Gastdaten gebuchter Aufenthalte anonymisiert
|
||||
*/
|
||||
export async function anonymisiere(now, { deckel = config.unterkunft.aufbewahrungDeckel ?? STANDARD_DECKEL } = {}) {
|
||||
const nowMs = nowMsVon(now)
|
||||
const erg = { anfragen: 0, gaeste: 0, ipHashes: 0, abgelaufen: 0, rechnungen: 0, adressen: 0, fehler: 0, gedeckelt: false }
|
||||
const erg = { anfragen: 0, gaeste: 0, ipHashes: 0, abgelaufen: 0, rechnungen: 0, adressen: 0, fehler: 0, gedeckelt: false, verbraucht: 0 }
|
||||
let rest = Number.isInteger(deckel) && deckel > 0 ? deckel : STANDARD_DECKEL
|
||||
const start = rest
|
||||
const jetztIso = new Date(nowMs).toISOString()
|
||||
const AN = config.collections.ukAnfragen
|
||||
const c = config.unterkunft
|
||||
@@ -135,9 +137,9 @@ export async function anonymisiere(now, { deckel = config.unterkunft.aufbewahrun
|
||||
// 2. neu, nach neuVerfallTage nicht entschieden: abgelaufen (ohne Mail)
|
||||
await schritt('neu verfallen', async () => {
|
||||
const grenze = nowMs - c.neuVerfallTage * TAG_MS
|
||||
const gefunden = await sammle(AN, [Query.equal('status', 'neu'), Query.lessThan('$createdAt', new Date(grenze).toISOString())], ['status', '$createdAt'], (d) => zeitMs(d.$createdAt) < grenze, rest)
|
||||
const gefunden = await sammle(AN, [Query.equal('status', 'neu'), Query.lessThan('$createdAt', new Date(grenze).toISOString())], ['status', 'unterkunftId', '$createdAt'], (d) => zeitMs(d.$createdAt) < grenze, rest)
|
||||
await bearbeite('neu verfallen', gefunden, async (d) => {
|
||||
const neu = await withLocks(['uk-aufbewahrung:' + d.$id], async () => {
|
||||
const neu = await withLocks(['unterkunft:' + d.unterkunftId], async () => { // derselbe Schluessel wie ablauf.js (gibFrei/lehneAb/Fristen)
|
||||
const a = await ladeAnfrage(d.$id)
|
||||
if (!a || a.status !== 'neu') return null
|
||||
return aktualisiereAnfrage(a.$id, { status: 'abgelaufen', abgelaufenAt: jetztIso, ipHash: '' })
|
||||
@@ -177,7 +179,8 @@ export async function anonymisiere(now, { deckel = config.unterkunft.aufbewahrun
|
||||
rest,
|
||||
)
|
||||
await bearbeite('anonymisieren', gebucht, async (d) => {
|
||||
await aktualisiereAnfrage(d.$id, { ...ANFRAGE_ANONYM, anonymisiertAt: jetztIso })
|
||||
// ohne Rechnung gibt es keinen Beleg, der die Rechnungsadresse schuetzt: sie geht mit den Gastdaten
|
||||
await aktualisiereAnfrage(d.$id, { ...ANFRAGE_ANONYM, ...(hatRechnung(d) ? {} : { rechnungsadresse: null }), anonymisiertAt: jetztIso })
|
||||
erg.gaeste += 1
|
||||
return true
|
||||
})
|
||||
@@ -224,6 +227,7 @@ export async function anonymisiere(now, { deckel = config.unterkunft.aufbewahrun
|
||||
}
|
||||
})
|
||||
|
||||
erg.verbraucht = start - rest
|
||||
return erg
|
||||
}
|
||||
|
||||
@@ -285,6 +289,14 @@ export async function hatUkDaten(featureId) {
|
||||
return false
|
||||
}
|
||||
|
||||
/** Marker an die bisherige Notiz haengen (eine alte Marke wird ersetzt, der Rest bleibt; hoechstens 300 Zeichen, der Marker bleibt ganz). */
|
||||
function markiere(notiz, tag) {
|
||||
const marker = `${MARKER}${tag}`
|
||||
const alt = String(notiz ?? '').replace(/\s*(?:[-–|]\s*)?Buchungsdaten gelöscht am \d{4}-\d{2}-\d{2}/g, '').trim()
|
||||
if (!alt) return marker
|
||||
return `${alt.slice(0, Math.max(0, 300 - marker.length - 3))} – ${marker}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Abgeschaltete Buchungskalender (portalFeatures.state disabled, disabledAt aelter als loeschTageNachAbschalten = 90 Tage): alle uk*-Daten
|
||||
* loeschen, danach Marker in adminNote (wie beim Shop; ein spaeteres Abschalten setzt disabledAt neu und gilt wieder).
|
||||
@@ -318,7 +330,10 @@ export async function loescheAbgeschaltete(now, { deckel = STANDARD_DECKEL } = {
|
||||
if (f.siteKey) leereCache(f.siteKey)
|
||||
if (weg.vollstaendig) {
|
||||
erg.funktionen += 1
|
||||
if (await nochAus()) await updateDocument(FEATURES, f.$id, { adminNote: `${MARKER}${heute(new Date(nowMs))}`.slice(0, 300) })
|
||||
const frisch = await getDocument(FEATURES, f.$id)
|
||||
if (frisch.state === 'disabled' && zeitMs(frisch.disabledAt) < grenze) {
|
||||
await updateDocument(FEATURES, f.$id, { adminNote: markiere(frisch.adminNote, heute(new Date(nowMs))) })
|
||||
}
|
||||
} else if (!weg.abgebrochen) {
|
||||
erg.gedeckelt = true
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ async function einzeln(name, arbeit) {
|
||||
try {
|
||||
return await arbeit()
|
||||
} catch (err) {
|
||||
console.error(`[unterkunft-jobs] ${name} fehlgeschlagen: ${err?.name}: ${String(err?.message).slice(0, 200)}`)
|
||||
console.error(`[unterkunft-jobs] ${name} fehlgeschlagen: ${err?.name} ${err?.status ?? ''}`)
|
||||
return { fehler: true }
|
||||
} finally {
|
||||
laufend.delete(name)
|
||||
@@ -55,7 +55,7 @@ async function gibtDaten() {
|
||||
|
||||
/**
|
||||
* Tagesjob: Aufbewahrung (ipHash, Verfall von `neu`, Anonymisierung, Rechnungen nach 8 Jahren) und Loeschung abgeschalteter Funktionen
|
||||
* (90 Tage). Gemeinsamer Deckel config.unterkunft.aufbewahrungDeckel (500) je Lauf. Bei UNTERKUNFT_ENABLED=off nur, wenn es noch
|
||||
* (90 Tage). Gemeinsamer Deckel config.unterkunft.aufbewahrungDeckel (500) je Lauf (die Loeschung bekommt, was die Aufbewahrung uebrig laesst). Bei UNTERKUNFT_ENABLED=off nur, wenn es noch
|
||||
* Buchungsdaten gibt (die Fristen gelten auch dann). Ein Teil, der scheitert, stoppt den anderen nicht.
|
||||
*/
|
||||
export async function taeglicherJob(now = new Date()) {
|
||||
@@ -64,15 +64,17 @@ export async function taeglicherJob(now = new Date()) {
|
||||
const deckel = config.unterkunft.aufbewahrungDeckel ?? 500
|
||||
let anon = { anfragen: 0, gaeste: 0, fehler: 1 }
|
||||
let weg = { funktionen: 0, dokumente: 0, fehler: 1, gedeckelt: false }
|
||||
let rest = deckel
|
||||
try {
|
||||
anon = await anonymisiere(now, { deckel })
|
||||
rest = Math.max(0, deckel - (anon.verbraucht ?? 0))
|
||||
} catch (err) {
|
||||
console.error(`[unterkunft-jobs] taeglicherJob: Aufbewahrung fehlgeschlagen: ${err?.name}: ${String(err?.message).slice(0, 200)}`)
|
||||
console.error(`[unterkunft-jobs] taeglicherJob: Aufbewahrung fehlgeschlagen: ${err?.name} ${err?.status ?? ''}`)
|
||||
}
|
||||
try {
|
||||
weg = await loescheAbgeschaltete(now, { deckel })
|
||||
weg = await loescheAbgeschaltete(now, { deckel: rest })
|
||||
} catch (err) {
|
||||
console.error(`[unterkunft-jobs] taeglicherJob: Löschung abgeschalteter Funktionen fehlgeschlagen: ${err?.name}: ${String(err?.message).slice(0, 200)}`)
|
||||
console.error(`[unterkunft-jobs] taeglicherJob: Löschung abgeschalteter Funktionen fehlgeschlagen: ${err?.name} ${err?.status ?? ''}`)
|
||||
}
|
||||
return { aufbewahrung: anon, abgeschaltet: weg }
|
||||
})
|
||||
|
||||
Reference in new issue
Block a user