TP2 Abschlussprüfung: Löschung (IP/Browser auf portalFeatures, Erstattungsspur, K22-Datum)

- markOrdered speichert IP und Browser der Bestellung nicht mehr am Funktionsdokument (nur im Nachweis featureConsents, dort mit
  Löschfrist); Altwerte werden bei einer neuen Bestellung geleert
- Aufbewahrungslauf (retention): consentIp, consentUserAgent, consentText, consentAt werden an allen Funktionsdokumenten eines fälligen
  Kunden geleert, auch an abgeschalteten; Fehlschlag hält den Kunden wie die übrigen abhängigen Daten
- Einmal-Skript scripts/feature-consent-felder-leeren.mjs (Standard Probelauf, --apply leert nur, wo featureConsents die Angabe trägt)
- 30-Tage-Reset (jobs.js): auch eine bezahlte Einrichtung mit Grund abo_ende_vor_einrichtung und noch nicht gemeldetem T19 wird nicht
  zurückgesetzt, solange zahlungMeldung nicht gemeldet ist (die Spur der Erstattung bleibt)
- K22: enddatum ist das Abo-Ende (lage.endedAt), sonst der Tag des Abschaltens; loeschdatum bleibt 30 Tage nach dem Abschalten

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-05 06:26:33 +00:00
1 parent b974b3a3ec
commit 2fc48df915
13 files changed
+425 -22

No files matched your search

+66
View File
@@ -400,6 +400,72 @@ test('retention_loescht_featureConsents: Ein Fehler beim Löschen eines Nachweis
assert.deepEqual(alt.failed, [])
})
// ------------------------------------------------------- C1: IP, Browser, Text und Zeit der Bestellung an portalFeatures
const KLICK = { consentIp: '203.0.113.7', consentUserAgent: 'Mozilla/5.0 Test', consentText: 'Ich stimme zu.', consentAt: '2022-02-01T10:00:00.000Z' }
const LEER = { consentIp: '', consentUserAgent: '', consentText: '', consentAt: null }
const feld = (id) => {
const d = aw.docs('portalFeatures').get(id)
return { consentIp: d.consentIp, consentUserAgent: d.consentUserAgent, consentText: d.consentText, consentAt: d.consentAt }
}
test('C1 retention: IP, Browser, Text und Zeit werden auf ALLEN Funktionsdokumenten eines fälligen Kunden geleert, auch auf abgeschalteten; andere Kunden und Felder bleiben', async () => {
sub('cf_sub', 'cf', { status: 'canceled', endedAt: '2022-03-15T10:00:00.000Z' })
stripeSub('cf_sub', 'cf')
aw.seed('portalFeatures', { $id: 'cf_aktiv', customerId: 'cf', projectId: 'cfp1', featureKey: 'termine', state: 'active', enabled: true, siteKey: 'k1', ...KLICK })
aw.seed('portalFeatures', { $id: 'cf_aus', customerId: 'cf', projectId: 'cfp2', featureKey: 'news', state: 'disabled', enabled: false, siteKey: 'k2', disabledAt: '2022-03-15T10:00:00.000Z', ...KLICK })
aw.seed('portalFeatures', { $id: 'cf_leer', customerId: 'cf', projectId: 'cfp3', featureKey: 'news', state: 'disabled', enabled: false, siteKey: 'k3', consentIp: '', consentUserAgent: '', consentText: '', consentAt: null })
sub('cf2_sub', 'cf2', { status: 'active' })
aw.seed('portalFeatures', { $id: 'cf2_aktiv', customerId: 'cf2', projectId: 'cf2p', featureKey: 'termine', state: 'active', enabled: true, siteKey: 'k4', ...KLICK })
const plan = await findDueDeletions(JAN_2026)
const eintrag = plan.news.find((n) => n.customerId === 'cf')
assert.ok(eintrag, 'ein fälliger Kunde mit gespeicherten Bestellangaben steht im Plan')
assert.deepEqual(eintrag.featureIds, ['cf_aktiv'], 'abschalten: nur das noch nicht abgeschaltete')
assert.deepEqual([...eintrag.featureFieldIds].sort(), ['cf_aktiv', 'cf_aus'], 'leeren: alle mit Angaben, auch das abgeschaltete; das leere Dokument nicht')
assert.ok(!plan.news.some((n) => n.customerId === 'cf2'))
assert.match(formatReport(plan), /Kunde cf: .*2 Funktionsdokument\(e\) mit Zustimmungsangaben zu leeren/)
// Probelauf ändert nichts
await run({ mode: '', now: JAN_2026, log: () => {} })
assert.deepEqual(feld('cf_aus'), { ...KLICK })
const result = await applyDeletions(planVon(plan, 'cf'))
assert.deepEqual(result.failed, [])
assert.equal(result.deleted.featureFields, 2)
assert.deepEqual(feld('cf_aktiv'), LEER)
assert.deepEqual(feld('cf_aus'), LEER)
assert.equal(aw.docs('portalFeatures').get('cf_aktiv').state, 'disabled', 'abgeschaltet wie bisher')
assert.equal(aw.docs('portalFeatures').get('cf_aus').siteKey, 'k2', 'übrige Felder unberührt')
assert.deepEqual(feld('cf2_aktiv'), { ...KLICK }, 'laufender Kunde bleibt')
assert.match(formatReport(plan, result), /Zustimmungsangaben an Funktionsdokumenten \(IP, Browser, Text, Zeit\): 2 geleert\./)
// ein zweiter Lauf findet nichts mehr für diesen Kunden
assert.ok(!(await findDueDeletions(JAN_2026)).news.some((n) => n.customerId === 'cf'))
})
test('C1 retention: scheitert das Leeren, hält es den Kunden (Abo bleibt), der nächste Lauf plant es erneut; ältere Pläne ohne featureFieldIds laufen weiter', async () => {
sub('cg_sub', 'cg', { status: 'canceled', endedAt: '2022-03-15T10:00:00.000Z' })
stripeSub('cg_sub', 'cg')
aw.seed('portalFeatures', { $id: 'cg_aus', customerId: 'cg', projectId: 'cgp', featureKey: 'news', state: 'disabled', enabled: false, siteKey: 'k5', ...KLICK })
const plan = await findDueDeletions(JAN_2026)
aw.failNext({ collection: 'portalFeatures', method: 'PATCH', status: 500, message: 'kaputt' })
const result = await applyDeletions(planVon(plan, 'cg'))
assert.deepEqual(result.failed.map((f) => [f.what, f.id]), [['portalFeatures', 'cg_aus']])
assert.deepEqual(result.held, ['cg'])
assert.ok(aw.docs('subscriptions').has('cg_sub'), 'das Abo-Dokument bleibt, sonst fände der nächste Lauf den Kunden nicht')
assert.deepEqual(feld('cg_aus'), { ...KLICK })
const nochmal = await findDueDeletions(JAN_2026)
assert.deepEqual(nochmal.news.find((n) => n.customerId === 'cg').featureFieldIds, ['cg_aus'])
const zweiter = await applyDeletions(planVon(nochmal, 'cg'))
assert.deepEqual(zweiter.failed, [])
assert.deepEqual(feld('cg_aus'), LEER)
assert.ok(!aw.docs('subscriptions').has('cg_sub'))
const alt = await applyDeletions({ customers: [], takeover: [], news: [{ customerId: 'cgx', posts: [], featureIds: [], consentIds: [] }] })
assert.equal(alt.deleted.featureFields, 0)
assert.deepEqual(alt.failed, [])
})
// ---------------------------------------------------------------- Reihenfolge: abhängige Daten vor dem Abo (Fix Runde 1, R119)
/** Kunde mit beendetem Abo (2022), einem Vertragsereignis und einem Zustimmungsnachweis, ohne Beiträge und Funktionen. */