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

+48
View File
@@ -1921,6 +1921,54 @@ test('Löschung nach 30 Tagen, R108: der übersprungene Reset zählt im Tagesjob
assert.equal(aw.docs('portalFeatures').get(F1).state, 'off')
})
// C2: bezahlte Einrichtung, abgeschaltet wegen des Abo-Endes vor der Einrichtung, T19 (Erstattung) noch nicht beim Team: der Reset nach
// 30 Tagen wuerde zahlungMeldung, unlockedByPurchase und ticketId loeschen und damit die Spur der geschuldeten Erstattung.
const ERSTATTUNG_OFFEN = ['', 'abo_ende', 'bereits_aktiv']
for (const meldung of ERSTATTUNG_OFFEN) {
test(`Löschung nach 30 Tagen, C2: bezahlt + abo_ende_vor_einrichtung + zahlungMeldung "${meldung}" (T19 offen) ⇒ Daten weg, Dokument bleibt disabled mit allen Markern; Warnung ohne Kennungen`, async () => {
await abgeschaltetMitBuchungen(2, { zellen: false, funktion: eingerichtet({ disabledReason: 'abo_ende_vor_einrichtung', zahlungMeldung: meldung, aboEndeMailAt: null }) })
const vorher = structuredClone(aw.docs('portalFeatures').get(F1))
assert.deepEqual(await loescheAbgeschalteteFunktionen(JETZT), { funktionen: 1, buchungen: 2, gedeckelt: 0 }, 'gelöscht wird wie bisher')
assert.equal(buchungen().length, 0)
assert.equal(aw.docs('termineConfig').get(F1), undefined, 'Einstellung gelöscht')
const doc = aw.docs('portalFeatures').get(F1)
assert.deepEqual({ ...doc, $updatedAt: undefined }, { ...vorher, $updatedAt: undefined }, 'das Dokument ist unverändert (nicht zurückgesetzt)')
assert.equal(warnLog().filter((zeile) => /offenen Zahlungsfall/.test(zeile)).length, 1)
assert.doesNotMatch(warnLog().join('\n'), /\bb_|f_p_|p_projekt|c_kunde|Müller|@/, 'keine Kennung, keine Personendaten im Log')
assert.equal(fehlerLog().length, 0, 'kein Wurf')
})
}
test('Löschung nach 30 Tagen, C2: unbezahlt (unlockedByPurchase false) oder T19 gemeldet ⇒ zurückgesetzt wie bisher', async () => {
await abgeschaltetMitBuchungen(1, { zellen: false, funktion: eingerichtet({ disabledReason: 'abo_ende_vor_einrichtung', zahlungMeldung: 'gemeldet' }) })
assert.deepEqual(await loescheAbgeschalteteFunktionen(JETZT), { funktionen: 1, buchungen: 1, gedeckelt: 0 })
assert.equal(aw.docs('portalFeatures').get(F1).state, 'off', 'T19 gemeldet')
await abgeschaltetMitBuchungen(1, { zellen: false, praefix: 'b_un', funktion: eingerichtet({ disabledReason: 'abo_ende_vor_einrichtung', zahlungMeldung: '', unlockedByPurchase: false, purchaseStatus: 'ordered', aboEndeMailAt: null }) })
assert.deepEqual(await loescheAbgeschalteteFunktionen(JETZT), { funktionen: 1, buchungen: 1, gedeckelt: 0 })
assert.equal(aw.docs('portalFeatures').get(F1).state, 'off', 'unbezahlt: es gibt nichts zu erstatten')
assert.equal(warnLog().length, 0)
})
test('Tagesjob, C2: T19 scheitert 31 Tage lang ⇒ der Reset wird nicht ausgeführt (Fehler gezählt); sobald T19 beim Team ist, setzt ein Lauf zurück und T19 ging genau einmal', async () => {
config.chatNotify.emails = ['team@example.com']
await abgeschaltetMitBuchungen(2, { zellen: false, funktion: eingerichtet({ disabledReason: 'abo_ende_vor_einrichtung', zahlungMeldung: '', aboEndeMailAt: null }) })
setMailTransportForTests({ sendMail: async () => { throw new Error('SMTP nicht erreichbar') } })
let erg = await taeglicherJob(JETZT)
assert.ok(erg.fehler >= 1, `Fehler gezählt: ${erg.fehler}`)
let doc = aw.docs('portalFeatures').get(F1)
assert.deepEqual([doc.state, doc.unlockedByPurchase, doc.zahlungMeldung, doc.ticketId, doc.disabledReason], ['disabled', true, '', 't_einrichtung', 'abo_ende_vor_einrichtung'], 'die Spur der Erstattung bleibt')
assert.equal(aw.docs('termineConfig').get(F1), undefined, 'die Daten sind trotzdem gelöscht')
setMailTransportForTests(sink)
for (const tage of [1, 2]) await taeglicherJob(plus(JETZT, tage * TAG))
doc = aw.docs('portalFeatures').get(F1)
assert.deepEqual([doc.state, doc.purchaseStatus, doc.unlockedByPurchase, doc.zahlungMeldung], ['off', 'zurueckgesetzt', false, ''], 'nach der Meldung wird zurückgesetzt')
assert.equal(sink.sent.filter((m) => /Erstattung fällig/.test(m.subject)).length, 1, 'T19 genau einmal')
})
for (const meldung of ['', 'gemeldet']) {
test(`Löschung nach 30 Tagen, R108: zahlungMeldung "${meldung}" ist kein offener Fall ⇒ zurückgesetzt wie bisher`, async () => {
await abgeschaltetMitBuchungen(1, { zellen: false, funktion: eingerichtet({ disabledReason: 'abo_ende', zahlungMeldung: meldung }) })