Kundenansicht P6: News-Löschschritt und Zurücksetzen nach der Löschung im Abo-Ende-Abgleich
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
92ac61dffd
commit
d8c952ac2a
2 files changed
+116
-11
No files matched your search
@@ -3,11 +3,15 @@ import { ENTHALTEN_IN, aboProdukt } from './aboFunktionen.js'
|
|||||||
import { ohneAboBisher } from './aboAufbauphase.js'
|
import { ohneAboBisher } from './aboAufbauphase.js'
|
||||||
import { istAboModell } from './aboModell.js'
|
import { istAboModell } from './aboModell.js'
|
||||||
import { meldeBeendet } from './aboFunktionenMeldung.js'
|
import { meldeBeendet } from './aboFunktionenMeldung.js'
|
||||||
import { Query, getDocument, listAllDocuments, updateDocument } from './appwriteAdmin.js'
|
import { Query, deleteDocument, getDocument, listAllDocuments, updateDocument } from './appwriteAdmin.js'
|
||||||
import { withLocks } from './keyedLock.js'
|
import { withLocks } from './keyedLock.js'
|
||||||
|
import { deleteNewsImage } from './newsImages.js'
|
||||||
|
import { listPosts } from './newsPosts.js'
|
||||||
import { activate, disable, stateFields } from './siteFeatures.js'
|
import { activate, disable, stateFields } from './siteFeatures.js'
|
||||||
import { pruefeNow } from './termine/util.js'
|
import { pruefeNow } from './termine/util.js'
|
||||||
|
import { loeschTag } from './termine/frist.js'
|
||||||
import { ladeEinstellung } from './termine/kontext.js'
|
import { ladeEinstellung } from './termine/kontext.js'
|
||||||
|
import { heute } from './termine/zeit.js'
|
||||||
import { leereCache } from './unterkunft/kontext.js'
|
import { leereCache } from './unterkunft/kontext.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -27,6 +31,14 @@ import { leereCache } from './unterkunft/kontext.js'
|
|||||||
* Rueckkehr disabled mit Grund abo_ende und das Abo deckt wieder, solange die Daten da sind (News: das Dokument ist noch disabled;
|
* Rueckkehr disabled mit Grund abo_ende und das Abo deckt wieder, solange die Daten da sind (News: das Dokument ist noch disabled;
|
||||||
* Terminplaner: die Einstellung ist gespeichert; Webshop und Buchungskalender: die Notiz nennt keine Loeschung "gelöscht am")
|
* Terminplaner: die Einstellung ist gespeichert; Webshop und Buchungskalender: die Notiz nennt keine Loeschung "gelöscht am")
|
||||||
* => wieder active (activate), ohne activatedAt wieder setup. Still, ohne Mail (K39 hat es angekuendigt).
|
* => wieder active (activate), ohne activatedAt wieder setup. Still, ohne Mail (K39 hat es angekuendigt).
|
||||||
|
* Loeschen News: disabled mit Grund abo_ende, das Abo deckt nicht und disabledAt plus 30 Tage ist erreicht (Berliner Kalendertag wie
|
||||||
|
* termine/frist.js) => alle Beitraege des Projekts samt Bild loeschen, dann das Dokument auf off (Felder ticketId, activatedAt,
|
||||||
|
* aboEndeMailAt, disabledAt, disabledReason geleert); scheitert ein Loeschen, bleibt das Dokument disabled (fehler, der naechste
|
||||||
|
* Lauf wiederholt). Deckt das Abo wieder, gilt die Rueckkehr vor der Loeschung.
|
||||||
|
* Zuruecksetzen Webshop und Buchungskalender: haben die bestehenden Jobs (shop/jobs.js, unterkunft/aufbewahrung.js) nach 90 Tagen die
|
||||||
|
* Daten geloescht und "Shopdaten gelöscht am" bzw. "Buchungsdaten gelöscht am" in adminNote vermerkt, wird das Dokument off
|
||||||
|
* (wie bei News, der Vermerk endet damit), unabhaengig vom Abo. Ohne Vermerk bleibt alles. Der Terminplaner wird von
|
||||||
|
* termine/jobs.js selbst auf off gesetzt.
|
||||||
*
|
*
|
||||||
* Regeln wie termine/aboAbgleich.js: eine Listenabfrage fuer alle Kunden; das Abo je Kunde einmal fuer die Vorauswahl lesen. Geschrieben
|
* Regeln wie termine/aboAbgleich.js: eine Listenabfrage fuer alle Kunden; das Abo je Kunde einmal fuer die Vorauswahl lesen. Geschrieben
|
||||||
* wird nur unter der Sperre feature-admin:<projectId> nach frischem Lesen von Dokument UND Abo (nie aus der Vorauswahl); die Mail und der
|
* wird nur unter der Sperre feature-admin:<projectId> nach frischem Lesen von Dokument UND Abo (nie aus der Vorauswahl); die Mail und der
|
||||||
@@ -57,12 +69,23 @@ const artVon = (abo) => (abo.produkt === 'hosting' ? 'hosting' : GRUND)
|
|||||||
/**
|
/**
|
||||||
* Sind die Daten der abgeschalteten Funktion noch da? News: das Dokument ist noch disabled (der Loeschschritt setzt es auf off);
|
* Sind die Daten der abgeschalteten Funktion noch da? News: das Dokument ist noch disabled (der Loeschschritt setzt es auf off);
|
||||||
* Terminplaner: die Einstellung ist gespeichert (ladeEinstellung wirft bei Lesefehlern, dann bleibt alles); Webshop und Buchungskalender:
|
* Terminplaner: die Einstellung ist gespeichert (ladeEinstellung wirft bei Lesefehlern, dann bleibt alles); Webshop und Buchungskalender:
|
||||||
* die Notiz nennt keine Loeschung ("Shopdaten gelöscht am", "Buchungsdaten gelöscht am").
|
* ein Vermerk der Loeschjobs in der Notiz wird vor der Rueckkehr behandelt (setzeNachLoeschungZurueck), ohne Vermerk sind die Daten da.
|
||||||
*/
|
*/
|
||||||
async function datenDa(doc) {
|
async function datenDa(doc) {
|
||||||
if (doc.featureKey === 'termine') return (await ladeEinstellung(doc.$id)) !== null
|
if (doc.featureKey === 'termine') return (await ladeEinstellung(doc.$id)) !== null
|
||||||
if (doc.featureKey === 'shop' || doc.featureKey === 'unterkunft') return !/gelöscht am/.test(String(doc.adminNote ?? ''))
|
return !hatLoeschVermerk(doc)
|
||||||
return true
|
}
|
||||||
|
|
||||||
|
const TAG_FELDER_LEER = Object.freeze({ ticketId: null, activatedAt: null, aboEndeMailAt: null, disabledAt: null, disabledReason: null })
|
||||||
|
|
||||||
|
/** Vermerk der bestehenden Loeschjobs von Webshop und Buchungskalender in der Notiz. */
|
||||||
|
const LOESCH_VERMERK = /\s*(?:[-–|]\s*)?(?:Shopdaten|Buchungsdaten) gelöscht am \d{4}-\d{2}-\d{2}/g
|
||||||
|
const hatLoeschVermerk = (doc) => (doc.featureKey === 'shop' || doc.featureKey === 'unterkunft') && /gelöscht am/.test(String(doc.adminNote ?? ''))
|
||||||
|
|
||||||
|
/** News: ist disabledAt plus die Loeschfrist (30 Tage) am Berliner Kalendertag erreicht? Ohne gueltiges disabledAt nie. */
|
||||||
|
function loeschReif(doc, now) {
|
||||||
|
const tag = loeschTag(doc.disabledAt)
|
||||||
|
return tag !== null && tag <= heute(now)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** K39 nach der Sperre; ein Misserfolg zaehlt als Fehler, der naechste Lauf wiederholt (Marker leer). */
|
/** K39 nach der Sperre; ein Misserfolg zaehlt als Fehler, der naechste Lauf wiederholt (Marker leer). */
|
||||||
@@ -131,6 +154,56 @@ async function schalteWiederEin(doc, ctx) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* News, ein Dokument: unter der Sperre frisch lesen (noch disabled/abo_ende, Frist erreicht, das Abo deckt nicht), zuerst alle Beitraege des
|
||||||
|
* Projekts samt Bild loeschen (erst das Bild, dann der Beitrag, wie retention.js), dann das Dokument auf off. Ein Fehler laesst das
|
||||||
|
* Dokument disabled (fehler, der naechste Lauf wiederholt); die uebrigen Beitraege werden trotzdem versucht.
|
||||||
|
*/
|
||||||
|
async function loescheNews(doc, ctx) {
|
||||||
|
const { erg } = ctx
|
||||||
|
await withLocks([`feature-admin:${doc.projectId}`], async () => {
|
||||||
|
const frisch = await getDocument(portalFeatures(), doc.$id)
|
||||||
|
if (!istAboModell(frisch) || frisch.featureKey !== 'news' || frisch.state !== 'disabled' || frisch.disabledReason !== GRUND) return
|
||||||
|
if (!loeschReif(frisch, ctx.now)) return
|
||||||
|
const abo = await aboProdukt(frisch.customerId, { now: ctx.now })
|
||||||
|
if (deckt(abo, frisch.featureKey)) return // das Abo ist zurueck: Rueckkehr, keine Loeschung
|
||||||
|
const { hanging, expired } = await listPosts(frisch.projectId, ctx.now)
|
||||||
|
let geloeschtOk = true
|
||||||
|
for (const post of [...hanging, ...expired]) {
|
||||||
|
try {
|
||||||
|
if (post.imageId) await deleteNewsImage(post.projectId || frisch.projectId, post.imageId) // erst das Bild, bei Fehler bleibt der Beitrag
|
||||||
|
await deleteDocument(config.collections.newsPosts, post.$id).catch((err) => {
|
||||||
|
if (err?.status !== 404) throw err
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
geloeschtOk = false
|
||||||
|
console.error(`[abo-funktionen] News-Beitrag ${post.$id} nicht gelöscht, das Dokument bleibt abgeschaltet: ${fehlerText(err)}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!geloeschtOk) {
|
||||||
|
erg.fehler += 1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await updateDocument(portalFeatures(), frisch.$id, { ...stateFields('off'), ...TAG_FELDER_LEER })
|
||||||
|
erg.geloescht += 1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Webshop und Buchungskalender, ein Dokument: die bestehenden Jobs haben die Daten geloescht (Vermerk in adminNote), unabhaengig vom Abo
|
||||||
|
* zurueck auf off. Der Vermerk endet damit, damit er nicht in eine spaetere Abschaltung desselben Dokuments hineinreicht.
|
||||||
|
*/
|
||||||
|
async function setzeNachLoeschungZurueck(doc, ctx) {
|
||||||
|
const { erg } = ctx
|
||||||
|
await withLocks([`feature-admin:${doc.projectId}`], async () => {
|
||||||
|
const frisch = await getDocument(portalFeatures(), doc.$id)
|
||||||
|
if (!istAboModell(frisch) || frisch.state !== 'disabled' || frisch.disabledReason !== GRUND || !hatLoeschVermerk(frisch)) return
|
||||||
|
const adminNote = String(frisch.adminNote ?? '').replace(LOESCH_VERMERK, '').trim()
|
||||||
|
await updateDocument(portalFeatures(), frisch.$id, { ...stateFields('off'), ...TAG_FELDER_LEER, adminNote })
|
||||||
|
erg.geloescht += 1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async function bearbeiteKunde(customerId, dokumente, ctx) {
|
async function bearbeiteKunde(customerId, dokumente, ctx) {
|
||||||
const { erg } = ctx
|
const { erg } = ctx
|
||||||
let abo
|
let abo
|
||||||
@@ -168,7 +241,9 @@ async function bearbeiteKunde(customerId, dokumente, ctx) {
|
|||||||
if (doc.unlockedBy === 'projekt' && (await inAufbauphase())) continue
|
if (doc.unlockedBy === 'projekt' && (await inAufbauphase())) continue
|
||||||
await schalteAb(doc, ctx)
|
await schalteAb(doc, ctx)
|
||||||
} else if (doc.state === 'disabled' && doc.disabledReason === GRUND) {
|
} else if (doc.state === 'disabled' && doc.disabledReason === GRUND) {
|
||||||
if (gedeckt) await schalteWiederEin(doc, ctx)
|
if (hatLoeschVermerk(doc)) await setzeNachLoeschungZurueck(doc, ctx) // Daten weg: nie Rueckkehr, off
|
||||||
|
else if (gedeckt) await schalteWiederEin(doc, ctx)
|
||||||
|
else if (doc.featureKey === 'news' && loeschReif(doc, ctx.now)) await loescheNews(doc, ctx)
|
||||||
else if (!doc.aboEndeMailAt) await holeMeldungNach(doc, ctx)
|
else if (!doc.aboEndeMailAt) await holeMeldungNach(doc, ctx)
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -182,8 +257,8 @@ async function bearbeiteKunde(customerId, dokumente, ctx) {
|
|||||||
* Taeglicher Abgleich der Funktionen des Abo-Modells mit dem Abo (siehe Kopf der Datei). Wirft nie; jedes Problem steht im Log und in `fehler`.
|
* Taeglicher Abgleich der Funktionen des Abo-Modells mit dem Abo (siehe Kopf der Datei). Wirft nie; jedes Problem steht im Log und in `fehler`.
|
||||||
* @param {Date} [now]
|
* @param {Date} [now]
|
||||||
* @returns {Promise<{ geprueft: number, abgeschaltet: number, wiederEin: number, geloescht: number, mails: number, fehler: number }>}
|
* @returns {Promise<{ geprueft: number, abgeschaltet: number, wiederEin: number, geloescht: number, mails: number, fehler: number }>}
|
||||||
* geprueft = bewertete Dokumente des Abo-Modells, deren Kunde lesbar war; abgeschaltet und wiederEin = in diesem Lauf; geloescht = Daten, die
|
* geprueft = bewertete Dokumente des Abo-Modells, deren Kunde lesbar war; abgeschaltet und wiederEin = in diesem Lauf; geloescht = Dokumente,
|
||||||
* der Loeschschritt in diesem Lauf entfernt hat (Task 9); mails = versendete K39; fehler = Kunden und Dokumente, die nicht bearbeitet werden
|
* die der Loeschschritt in diesem Lauf auf off gesetzt hat (News nach 30 Tagen, Webshop und Buchungskalender nach der Loeschung); mails = versendete K39; fehler = Kunden und Dokumente, die nicht bearbeitet werden
|
||||||
* konnten, sowie ein gescheiterter K39-Versand
|
* konnten, sowie ein gescheiterter K39-Versand
|
||||||
*/
|
*/
|
||||||
export async function gleicheAboFunktionenAb(now = new Date()) {
|
export async function gleicheAboFunktionenAb(now = new Date()) {
|
||||||
|
|||||||
@@ -14,12 +14,13 @@ const aw = await startAppwriteMock({
|
|||||||
subscriptions: schemaKeys('subscriptions'),
|
subscriptions: schemaKeys('subscriptions'),
|
||||||
contractEvents: schemaKeys('contractEvents'),
|
contractEvents: schemaKeys('contractEvents'),
|
||||||
termineConfig: schemaKeys('termineConfig'),
|
termineConfig: schemaKeys('termineConfig'),
|
||||||
|
newsPosts: schemaKeys('newsPosts'),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
const st = await startFakeStripe()
|
const st = await startFakeStripe()
|
||||||
useTestEnv({ appwriteUrl: aw.url, stripeUrl: st.url })
|
useTestEnv({ appwriteUrl: aw.url, stripeUrl: st.url })
|
||||||
const { customers, hostingDoc, seedFixtures, subscriptionDoc, wartungDoc } = await import('./support/fixtures.js')
|
const { customers, hostingDoc, seedFixtures, subscriptionDoc, wartungDoc } = await import('./support/fixtures.js')
|
||||||
const { deleteDocument } = await import('../server/services/appwriteAdmin.js')
|
const { deleteDocument, updateDocument } = await import('../server/services/appwriteAdmin.js')
|
||||||
const { setMailTransportForTests } = await import('../server/services/mailer.js')
|
const { setMailTransportForTests } = await import('../server/services/mailer.js')
|
||||||
const { datumKurz } = await import('../server/services/termine/zeit.js')
|
const { datumKurz } = await import('../server/services/termine/zeit.js')
|
||||||
const { gleicheAboFunktionenAb } = await import('../server/services/aboFunktionenAbgleich.js')
|
const { gleicheAboFunktionenAb } = await import('../server/services/aboFunktionenAbgleich.js')
|
||||||
@@ -44,7 +45,7 @@ const leere = async (...collections) => {
|
|||||||
seedFixtures(aw)
|
seedFixtures(aw)
|
||||||
aw.seed('websiteProjects', { $id: 'p1', customerId: 'c1', projectName: 'Bäckerei' })
|
aw.seed('websiteProjects', { $id: 'p1', customerId: 'c1', projectName: 'Bäckerei' })
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await leere('portalFeatures', 'subscriptions', 'contractEvents', 'termineConfig')
|
await leere('portalFeatures', 'subscriptions', 'contractEvents', 'termineConfig', 'newsPosts')
|
||||||
mails.sent.length = 0
|
mails.sent.length = 0
|
||||||
setMailTransportForTests(mails)
|
setMailTransportForTests(mails)
|
||||||
aw.seed('customers', customers.c1)
|
aw.seed('customers', customers.c1)
|
||||||
@@ -76,10 +77,10 @@ test('Rückkehr: nur solange die Daten da sind; ohne activatedAt zurück auf set
|
|||||||
assert.deepEqual([f('news').state, f('news').disabledReason, f('news').aboEndeMailAt, f('shop').state, f('unterkunft').state, f('termine').state], ['active', null, null, 'active', 'active', 'disabled'])
|
assert.deepEqual([f('news').state, f('news').disabledReason, f('news').aboEndeMailAt, f('shop').state, f('unterkunft').state, f('termine').state], ['active', null, null, 'active', 'active', 'disabled'])
|
||||||
aw.seed('termineConfig', { $id: 'f_p1_termine', einstellung: '{}', version: 1 }); await gleicheAboFunktionenAb(JETZT); assert.equal(f('termine').state, 'setup') // ohne activatedAt: setup, nicht active
|
aw.seed('termineConfig', { $id: 'f_p1_termine', einstellung: '{}', version: 1 }); await gleicheAboFunktionenAb(JETZT); assert.equal(f('termine').state, 'setup') // ohne activatedAt: setup, nicht active
|
||||||
})
|
})
|
||||||
test('Rückkehr des Webshops und des Buchungskalenders nicht nach der Löschung der Daten', async () => {
|
test('Rückkehr des Webshops und des Buchungskalenders nie nach der Löschung der Daten: das Dokument wird off (Task 9)', async () => {
|
||||||
abo(wartungDoc); const aus = { state: 'disabled', enabled: false, disabledReason: 'abo_ende', disabledAt: '2026-07-10T09:00:00.000Z', aboEndeMailAt: '2026-07-10T09:05:00.000Z' }
|
abo(wartungDoc); const aus = { state: 'disabled', enabled: false, disabledReason: 'abo_ende', disabledAt: '2026-07-10T09:00:00.000Z', aboEndeMailAt: '2026-07-10T09:05:00.000Z' }
|
||||||
seed('shop', { ...aus, adminNote: '2026-10-10T09:00:00.000Z System: Shopdaten gelöscht am 2026-10-10' }); seed('unterkunft', { ...aus, adminNote: 'Buchungsdaten gelöscht am 2026-10-10' })
|
seed('shop', { ...aus, adminNote: '2026-10-10T09:00:00.000Z System: Shopdaten gelöscht am 2026-10-10' }); seed('unterkunft', { ...aus, adminNote: 'Buchungsdaten gelöscht am 2026-10-10' })
|
||||||
const r = await gleicheAboFunktionenAb(JETZT); assert.deepEqual([r.wiederEin, f('shop').state, f('unterkunft').state], [0, 'disabled', 'disabled'])
|
const r = await gleicheAboFunktionenAb(JETZT); assert.deepEqual([r.wiederEin, r.geloescht, f('shop').state, f('unterkunft').state], [0, 2, 'off', 'off'])
|
||||||
})
|
})
|
||||||
test('Alte Dokumente bleiben unberührt; Kunde ohne Adresse setzt nur den Marker; Mailfehler: Marker leer, nächster Lauf sendet, disabledAt bleibt', async (t) => {
|
test('Alte Dokumente bleiben unberührt; Kunde ohne Adresse setzt nur den Marker; Mailfehler: Marker leer, nächster Lauf sendet, disabledAt bleibt', async (t) => {
|
||||||
t.mock.method(console, 'error', () => {}); t.mock.method(console, 'warn', () => {})
|
t.mock.method(console, 'error', () => {}); t.mock.method(console, 'warn', () => {})
|
||||||
@@ -101,3 +102,32 @@ test('Projektstart-Funktionen (projekt): ohne jedes Abo unberührt (Aufbauphase)
|
|||||||
test('abo-Dokumente kennen keine Aufbauphase: ohne jedes Abo-Dokument werden sie wie bisher abgeschaltet', async () => {
|
test('abo-Dokumente kennen keine Aufbauphase: ohne jedes Abo-Dokument werden sie wie bisher abgeschaltet', async () => {
|
||||||
seed('news'); const r = await gleicheAboFunktionenAb(JETZT); assert.deepEqual([r.abgeschaltet, f('news').state], [1, 'disabled'])
|
seed('news'); const r = await gleicheAboFunktionenAb(JETZT); assert.deepEqual([r.abgeschaltet, f('news').state], [1, 'disabled'])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Task 9: News-Löschschritt und Zurücksetzen nach der Löschung
|
||||||
|
test('News: nach 30 Tagen werden Beiträge samt Bild gelöscht und das Dokument ist off; vorher und mit laufendem Abo nicht', async () => {
|
||||||
|
abo({ ...wartungDoc, status: 'canceled' }); const aus = { state: 'disabled', enabled: false, disabledReason: 'abo_ende', aboEndeMailAt: '2026-09-01T00:00:00.000Z' }
|
||||||
|
seed('news', { ...aus, disabledAt: new Date(JETZT.getTime() - 29 * TAG).toISOString() })
|
||||||
|
aw.seed('newsPosts', { $id: 'np1', customerId: 'c1', projectId: 'p1', title: 'A', imageId: null, visibleFrom: '2026-09-01T00:00:00.000Z', visibleUntil: null }); aw.seed('newsPosts', { $id: 'np2', customerId: 'c1', projectId: 'p_anders', title: 'B', imageId: null, visibleFrom: '2026-09-01T00:00:00.000Z', visibleUntil: null })
|
||||||
|
assert.equal((await gleicheAboFunktionenAb(JETZT)).geloescht, 0); assert.equal(aw.docs('newsPosts').size, 2)
|
||||||
|
await updateDocument('portalFeatures', 'f_p1_news', { disabledAt: new Date(JETZT.getTime() - 31 * TAG).toISOString() })
|
||||||
|
assert.equal((await gleicheAboFunktionenAb(JETZT)).geloescht, 1); assert.deepEqual([...aw.docs('newsPosts').keys()], ['np2'])
|
||||||
|
assert.deepEqual([f('news').state, f('news').disabledAt, f('news').aboEndeMailAt], ['off', null, null])
|
||||||
|
seed('news', { ...aus, disabledAt: new Date(JETZT.getTime() - 40 * TAG).toISOString() }); aw.seed('subscriptions', subscriptionDoc('sub_h', wartungDoc)) // Abo kehrt zurück: Rückkehr statt Löschung
|
||||||
|
aw.seed('newsPosts', { $id: 'np3', customerId: 'c1', projectId: 'p1', title: 'C', imageId: null, visibleFrom: '2026-09-01T00:00:00.000Z', visibleUntil: null })
|
||||||
|
await gleicheAboFunktionenAb(JETZT); assert.deepEqual([f('news').state, aw.docs('newsPosts').has('np3')], ['active', true])
|
||||||
|
})
|
||||||
|
test('News: ein Fehler beim Löschen lässt das Dokument disabled und zählt fehler', async (t) => {
|
||||||
|
abo({ ...wartungDoc, status: 'canceled' }); seed('news', { state: 'disabled', enabled: false, disabledReason: 'abo_ende', aboEndeMailAt: 'x', disabledAt: new Date(JETZT.getTime() - 31 * TAG).toISOString() })
|
||||||
|
aw.seed('newsPosts', { $id: 'np1', customerId: 'c1', projectId: 'p1', title: 'A', imageId: null, visibleFrom: '2026-09-01T00:00:00.000Z', visibleUntil: null }); t.mock.method(console, 'error', () => {}); aw.failNext({ collection: 'newsPosts', method: 'DELETE', status: 500 })
|
||||||
|
const r = await gleicheAboFunktionenAb(JETZT); assert.deepEqual([r.fehler, f('news').state], [1, 'disabled']); assert.equal((await gleicheAboFunktionenAb(JETZT)).geloescht, 1)
|
||||||
|
})
|
||||||
|
test('Shop und Unterkunft: nach der Löschung durch die bestehenden Jobs wird das Dokument off (auch bei laufendem Abo); ohne Marker nichts', async () => {
|
||||||
|
abo(wartungDoc); const aus = { state: 'disabled', enabled: false, disabledReason: 'abo_ende', disabledAt: '2026-06-01T00:00:00.000Z', aboEndeMailAt: '2026-06-01T00:05:00.000Z' }
|
||||||
|
seed('shop', { ...aus, adminNote: 'Shopdaten gelöscht am 2026-09-01' }); seed('unterkunft', { ...aus, adminNote: 'Buchungsdaten gelöscht am 2026-09-01' }); seed('termine', { ...aus })
|
||||||
|
const r = await gleicheAboFunktionenAb(JETZT); assert.equal(r.geloescht, 2); assert.deepEqual([f('shop').state, f('unterkunft').state, f('shop').aboEndeMailAt], ['off', 'off', null])
|
||||||
|
assert.deepEqual([f('termine').state, f('shop').adminNote], ['disabled', '']) // Terminplaner: setzt termine/jobs.js selbst auf off; der Löschvermerk endet mit dem Zurücksetzen
|
||||||
|
})
|
||||||
|
test('News: ohne Marker und nach Ablauf der Frist wird gelöscht, ohne noch einmal K39 zu senden', async () => {
|
||||||
|
abo({ ...wartungDoc, status: 'canceled' }); seed('news', { state: 'disabled', enabled: false, disabledReason: 'abo_ende', disabledAt: new Date(JETZT.getTime() - 35 * TAG).toISOString(), aboEndeMailAt: null })
|
||||||
|
const r = await gleicheAboFunktionenAb(JETZT); assert.deepEqual([r.geloescht, r.mails, f('news').state, mails.sent.length], [1, 0, 'off', 0])
|
||||||
|
})
|
||||||
Reference in new issue
Block a user