Öffnungszeiten: Mails K24–K27 heißen jetzt K27–K30, Feld k24At heißt einschaltMailAt

K24–K26 sind seit dem Webshop-Livegang (main) vergeben; der Mail-Lader sucht die Vorlage über den
Dateianfang, doppelte Nummern hätten die falsche Mail geschickt. Das Feld ist noch nicht in der
Produktion angelegt (zeiten-setup.mjs lief noch nicht), daher jetzt ein sprechender Name.
Spec und Plan behalten die alten Nummern (Hinweis oben im Dokument).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-06 06:00:17 +00:00
1 parent ea4457e6a4
commit c7cd436ec2
34 files changed
+359 -354

No files matched your search

+76 -76
View File
@@ -7,13 +7,13 @@ import { useTestEnv } from './support/testEnv.js'
import { ZEITEN_DATETIMES } from '../server/services/zeiten/schema.js'
// Abo-Abgleich von Öffnungszeiten live (Aufgabe 21, Spec 5 "Abo-Abgleich" Schritte 1 bis 4 und Z11): gleicheZeitenAboAb(now) koppelt die im
// Wartungs-Abo enthaltenen Zeiten (unlockedBy 'abo') an das Abo: Abo-Ende erkennen (aboEndeAt, K26), nach 30 Tagen abschalten (K27, Ersatz-Ticket),
// bei Rückkehr des Abos wieder einschalten (K24 "wieder", Neubau nach dem Abschalten), 30 Tage nach dem Abschalten die Daten löschen.
// Wartungs-Abo enthaltenen Zeiten (unlockedBy 'abo') an das Abo: Abo-Ende erkennen (aboEndeAt, K29), nach 30 Tagen abschalten (K30, Ersatz-Ticket),
// bei Rückkehr des Abos wieder einschalten (K27 "wieder", Neubau nach dem Abschalten), 30 Tage nach dem Abschalten die Daten löschen.
// Aufbau wie termineAboAbgleich.test.js: Appwrite-Nachbau mit Schema, Mails in der Senke, feste Zeit JETZT; Tage zählen nach Berliner Kalendertagen.
// Datumsfelder wie in Appwrite: UTC mit "+00:00", Unlesbares oder '' wäre dort ein 400 (leeren geht nur mit null)
const DATETIMES = {
...ZEITEN_DATETIMES,
portalFeatures: ['orderedAt', 'activatedAt', 'disabledAt', 'consentAt', 'aboEndeMailAt', 'aboEndeAt', 'k24At'],
portalFeatures: ['orderedAt', 'activatedAt', 'disabledAt', 'consentAt', 'aboEndeMailAt', 'aboEndeAt', 'einschaltMailAt'],
}
const aw = await startAppwriteMock({
schema: {
@@ -122,7 +122,7 @@ const EIN_PLAN = () => ({
})
/**
* Funktion "Öffnungszeiten live" von kunde für Projekt nr, wie nach dem Einschalten im Wartungs-Abo: aktiv, Ticket geschlossen, K24 gesendet,
* Funktion "Öffnungszeiten live" von kunde für Projekt nr, wie nach dem Einschalten im Wartungs-Abo: aktiv, Ticket geschlossen, K27 gesendet,
* Zeiten gespeichert (daten, ersatzHash leer). Das Projekt "Praxis Muster" und das Einbau-Ticket legt die Hilfe mit an.
*/
async function legeFunktion(nr = 1, over = {}, { kunde = KUNDE, projekt = 'Praxis Muster', zeiten = true } = {}) {
@@ -140,7 +140,7 @@ async function legeFunktion(nr = 1, over = {}, { kunde = KUNDE, projekt = 'Praxi
siteKey: `abcdefghijklmno${'pqrstuv'[nr]}`,
ticketId: `t_${nr}`,
einbau: 'angestossen',
k24At: ZUVOR,
einschaltMailAt: ZUVOR,
activatedAt: ZUVOR,
...over,
})
@@ -167,9 +167,9 @@ async function laufe(now = JETZT) {
const ERGEBNIS_LEER = { geprueft: 0, aboEnde: 0, abgeschaltet: 0, wiederEin: 0, geloescht: 0, mails: 0, fehler: 0 }
const ergebnis = (over) => ({ ...ERGEBNIS_LEER, ...over })
const K26 = /^Dein Wartungs-Abo ist beendet/
const K27 = 'Öffnungszeiten live ist abgeschaltet'
const K24 = 'Öffnungszeiten live ist eingeschaltet'
const K29 = /^Dein Wartungs-Abo ist beendet/
const K30 = 'Öffnungszeiten live ist abgeschaltet'
const K27 = 'Öffnungszeiten live ist eingeschaltet'
const mails = () => sink.sent
const mailsMit = (betreff) => mails().filter((m) => (betreff instanceof RegExp ? betreff.test(m.subject) : m.subject === betreff))
const tickets = () => [...aw.docs('workorders').values()]
@@ -203,7 +203,7 @@ async function mitEinwurf(passt, dazwischen, fn) {
}
const liste = (collection) => (url, methode) => methode === 'GET' && new RegExp(`/collections/${collection}/documents(\\?|$)`).test(url)
/** Die volle Folge bis zum Abschalten: Tag 0 Abo-Ende erkannt (K26), Tag 30 abgeschaltet (K27). */
/** Die volle Folge bis zum Abschalten: Tag 0 Abo-Ende erkannt (K29), Tag 30 abgeschaltet (K30). */
async function bisAbgeschaltet() {
await legeFunktion(1)
abgelaufen()
@@ -212,9 +212,9 @@ async function bisAbgeschaltet() {
assert.equal(funktion().state, 'disabled')
}
// ---------------------------------------------------------------- Schritt 1: Abo-Ende erkennen, K26
// ---------------------------------------------------------------- Schritt 1: Abo-Ende erkennen, K29
test('Abo endet: aboEndeAt gesetzt, K26 einmal', async () => {
test('Abo endet: aboEndeAt gesetzt, K29 einmal', async () => {
await legeFunktion(1)
abgelaufen()
@@ -223,13 +223,13 @@ test('Abo endet: aboEndeAt gesetzt, K26 einmal', async () => {
assert.equal(doc.state, 'active', 'die Funktion läuft bis zur Frist weiter')
assert.equal(doc.enabled, true)
assert.equal(ms(doc.aboEndeAt), JETZT.getTime(), 'aboEndeAt = Zeitpunkt der Erkennung')
assert.equal(ms(doc.aboEndeMailAt), JETZT.getTime(), 'Marker der K26 erst nach dem Versand')
assert.equal(ms(doc.aboEndeMailAt), JETZT.getTime(), 'Marker der K29 erst nach dem Versand')
assert.equal(doc.unlockedBy, 'abo')
assert.equal(mails().length, 1)
const mail = mails()[0]
assert.deepEqual(mail.to.map((a) => a.address), ['silke@example.com'])
assert.match(mail.subject, K26)
assert.match(mail.subject, K29)
assert.match(mail.text, /Silke Müller/)
assert.match(mail.text, /Dein Wartungs-Abo ist beendet\./, 'Variante beendet')
assert.match(mail.text, /Läuft weiter bis: 04\.11\.2026/, 'Frist = Tag der Erkennung + 30 Tage')
@@ -255,7 +255,7 @@ test('Läuft das Abo, geschieht nichts: kein Schreiben, keine Mail', async () =>
assert.equal(mails().length, 0)
})
test('K26-Variante bei Wechsel zu Hosting und bei widerrufenem Wechsel', async () => {
test('K29-Variante bei Wechsel zu Hosting und bei widerrufenem Wechsel', async () => {
const varianten = [
['Hosting-Abo', () => hosting('active'), /Dein Abo ist ein Hosting-Abo\./, /Du hast den Wechsel in das Wartungs-Abo widerrufen/],
[
@@ -274,14 +274,14 @@ test('K26-Variante bei Wechsel zu Hosting und bei widerrufenem Wechsel', async (
await legeFunktion(1)
abo()
assert.deepEqual(await laufe(), ergebnis({ geprueft: 1, aboEnde: 1, mails: 1 }), name)
assert.match(mails()[0].subject, K26, name)
assert.match(mails()[0].subject, K29, name)
assert.match(mails()[0].text, passt, name)
assert.doesNotMatch(mails()[0].text, passtNicht, name)
assert.equal(funktion().state, 'active', `${name}: die Frist läuft`)
}
})
test('K26 geht nur an die Adresse des Kundenkontos; ohne Adresse setzt der Lauf den Marker ohne Fehler', async () => {
test('K29 geht nur an die Adresse des Kundenkontos; ohne Adresse setzt der Lauf den Marker ohne Fehler', async () => {
await legeFunktion(1)
aw.seed('customers', { $id: KUNDE, name: 'Silke Müller', email: '' })
abgelaufen()
@@ -291,7 +291,7 @@ test('K26 geht nur an die Adresse des Kundenkontos; ohne Adresse setzt der Lauf
assert.deepEqual(await laufe(nach(1)), ergebnis({ geprueft: 1 }))
})
test('K26 scheitert (Mail): aboEndeAt steht, der Marker fehlt, der nächste Lauf sendet nach (einmal)', async () => {
test('K29 scheitert (Mail): aboEndeAt steht, der Marker fehlt, der nächste Lauf sendet nach (einmal)', async () => {
await legeFunktion(1)
abgelaufen()
const roh = sink.sendMail
@@ -305,16 +305,16 @@ test('K26 scheitert (Mail): aboEndeAt steht, der Marker fehlt, der nächste Lauf
sink.sendMail = roh
assert.deepEqual(await laufe(nach(1)), ergebnis({ geprueft: 1, mails: 1 }))
assert.equal(mailsMit(K26).length, 1)
assert.equal(mailsMit(K29).length, 1)
assert.equal(ms(funktion().aboEndeAt), JETZT.getTime(), 'aboEndeAt bleibt: die Frist wächst nicht')
assert.match(mails()[0].text, /Läuft weiter bis: 04\.11\.2026/, 'dieselbe Frist wie beim ersten Versuch')
assert.deepEqual(await laufe(nach(2)), ergebnis({ geprueft: 1 }))
assert.equal(mails().length, 1)
})
// ---------------------------------------------------------------- Schritt 2: nach 30 Tagen abschalten, K27, Ersatz-Ticket
// ---------------------------------------------------------------- Schritt 2: nach 30 Tagen abschalten, K30, Ersatz-Ticket
test('Tag 29: noch aktiv; Tag 30: disabled abo_ende, K27, Ersatz-Ticket', async () => {
test('Tag 29: noch aktiv; Tag 30: disabled abo_ende, K30, Ersatz-Ticket', async () => {
await legeFunktion(1)
abgelaufen()
await laufe(JETZT)
@@ -334,12 +334,12 @@ test('Tag 29: noch aktiv; Tag 30: disabled abo_ende, K27, Ersatz-Ticket', async
assert.equal(doc.disabledReason, 'abo_ende')
assert.equal(ms(doc.disabledAt), tag30.getTime())
assert.equal(ms(doc.aboEndeAt), JETZT.getTime(), 'aboEndeAt bleibt als Spur stehen')
assert.equal(ms(doc.aboEndeMailAt), tag30.getTime(), 'Marker gilt jetzt der K27')
assert.equal(ms(doc.aboEndeMailAt), tag30.getTime(), 'Marker gilt jetzt der K30')
assert.match(doc.adminNote, /System: Abgeschaltet: Wartungs-Abo beendet/)
// K27: Datum der Löschung = Abschalten + 30 Tage
assert.equal(mailsMit(K27).length, 1)
const mail = mailsMit(K27)[0]
// K30: Datum der Löschung = Abschalten + 30 Tage
assert.equal(mailsMit(K30).length, 1)
const mail = mailsMit(K30)[0]
assert.deepEqual(mail.to.map((a) => a.address), ['silke@example.com'])
assert.match(mail.text, /Eingaben gespeichert bis: 04\.12\.2026/)
assert.match(mail.text, /30 €/)
@@ -370,7 +370,7 @@ test('Frist nach Berliner Kalendertagen, nicht nach 24-Stunden-Schritten (Ende d
// 23.11., 00:01 Berliner Zeit: der Tag der Frist ist da, auch vor 23:30 (24 x 30 Stunden später wäre erst 22:30 am 23.11.)
assert.deepEqual(await laufe(new Date('2026-11-22T23:01:00.000Z')), ergebnis({ geprueft: 1, abgeschaltet: 1, mails: 1 }))
assert.equal(funktion().state, 'disabled')
assert.match(mailsMit(K27)[0].text, /Eingaben gespeichert bis: 23\.12\.2026/)
assert.match(mailsMit(K30)[0].text, /Eingaben gespeichert bis: 23\.12\.2026/)
})
test('Tag 30 mit zurückgekehrtem Abo: nicht abgeschaltet, sondern die Rückkehr', async () => {
@@ -381,10 +381,10 @@ test('Tag 30 mit zurückgekehrtem Abo: nicht abgeschaltet, sondern die Rückkehr
assert.deepEqual(await laufe(nach(30)), ergebnis({ geprueft: 1, wiederEin: 1, mails: 1 }))
assert.equal(funktion().state, 'active')
assert.equal(ersatzTickets().length, 0)
assert.equal(mailsMit(K27).length, 0)
assert.equal(mailsMit(K30).length, 0)
})
test('K27 scheitert (Mail): abgeschaltet bleibt, der Marker fehlt, der nächste Lauf sendet nach; das Ersatz-Ticket entsteht nur einmal', async () => {
test('K30 scheitert (Mail): abgeschaltet bleibt, der Marker fehlt, der nächste Lauf sendet nach; das Ersatz-Ticket entsteht nur einmal', async () => {
await legeFunktion(1)
abgelaufen()
await laufe(JETZT)
@@ -394,18 +394,18 @@ test('K27 scheitert (Mail): abgeschaltet bleibt, der Marker fehlt, der nächste
}
assert.deepEqual(await laufe(nach(30)), ergebnis({ geprueft: 1, abgeschaltet: 1, fehler: 1 }))
assert.equal(funktion().state, 'disabled')
assert.ok(!funktion().aboEndeMailAt || ms(funktion().aboEndeMailAt) < ms(funktion().disabledAt), 'der Marker der K26 gilt nicht für die K27')
assert.ok(!funktion().aboEndeMailAt || ms(funktion().aboEndeMailAt) < ms(funktion().disabledAt), 'der Marker der K29 gilt nicht für die K30')
assert.equal(ersatzTickets().length, 1)
sink.sendMail = roh
assert.deepEqual(await laufe(nach(31)), ergebnis({ geprueft: 1, mails: 1 }))
assert.equal(mailsMit(K27).length, 1)
assert.match(mailsMit(K27)[0].text, /Eingaben gespeichert bis: 04\.12\.2026/, 'Löschdatum von der echten Abschaltung, nicht von heute')
assert.equal(mailsMit(K30).length, 1)
assert.match(mailsMit(K30)[0].text, /Eingaben gespeichert bis: 04\.12\.2026/, 'Löschdatum von der echten Abschaltung, nicht von heute')
assert.equal(ersatzTickets().length, 1)
assert.deepEqual(await laufe(nach(32)), ergebnis({ geprueft: 1 }))
})
test('Ersatz-Ticket scheitert: abgeschaltet bleibt, fehler zählt, K27 und Marker fehlen; der nächste Lauf legt das Ticket an und sendet K27 genau einmal', async () => {
test('Ersatz-Ticket scheitert: abgeschaltet bleibt, fehler zählt, K30 und Marker fehlen; der nächste Lauf legt das Ticket an und sendet K30 genau einmal', async () => {
await legeFunktion(1)
abgelaufen()
await laufe(JETZT)
@@ -414,21 +414,21 @@ test('Ersatz-Ticket scheitert: abgeschaltet bleibt, fehler zählt, K27 und Marke
assert.deepEqual(await laufe(nach(30)), ergebnis({ geprueft: 1, abgeschaltet: 1, fehler: 1 }))
assert.equal(funktion().state, 'disabled')
assert.equal(ersatzTickets().length, 0)
assert.equal(mailsMit(K27).length, 0, 'K27 erst nach dem Ersatz-Ticket: so geht sie nie ein zweites Mal raus')
assert.ok(!funktion().aboEndeMailAt || ms(funktion().aboEndeMailAt) < ms(funktion().disabledAt), 'kein K27-Marker')
assert.equal(mailsMit(K30).length, 0, 'K30 erst nach dem Ersatz-Ticket: so geht sie nie ein zweites Mal raus')
assert.ok(!funktion().aboEndeMailAt || ms(funktion().aboEndeMailAt) < ms(funktion().disabledAt), 'kein K30-Marker')
// der nächste Lauf wiederholt beides (Ticket idempotent über den Hash) und sendet K27 einmal
// der nächste Lauf wiederholt beides (Ticket idempotent über den Hash) und sendet K30 einmal
assert.deepEqual(await laufe(nach(31)), ergebnis({ geprueft: 1, mails: 1 }))
assert.equal(ersatzTickets().length, 1)
assert.equal(mailsMit(K27).length, 1)
assert.match(mailsMit(K27)[0].text, /Eingaben gespeichert bis: 04\.12\.2026/, 'Löschdatum von der echten Abschaltung')
assert.ok(ms(funktion().aboEndeMailAt) >= ms(funktion().disabledAt), 'Marker gilt jetzt der K27')
assert.equal(mailsMit(K30).length, 1)
assert.match(mailsMit(K30)[0].text, /Eingaben gespeichert bis: 04\.12\.2026/, 'Löschdatum von der echten Abschaltung')
assert.ok(ms(funktion().aboEndeMailAt) >= ms(funktion().disabledAt), 'Marker gilt jetzt der K30')
assert.deepEqual(await laufe(nach(32)), ergebnis({ geprueft: 1 }))
assert.equal(ersatzTickets().length, 1)
assert.equal(mailsMit(K27).length, 1)
assert.equal(mailsMit(K30).length, 1)
})
test('Ersatz-Ticket scheitert mehrere Läufe: kein K27, jeder Lauf zählt fehler; sobald es klappt, genau eine K27', async () => {
test('Ersatz-Ticket scheitert mehrere Läufe: kein K30, jeder Lauf zählt fehler; sobald es klappt, genau eine K30', async () => {
await legeFunktion(1)
abgelaufen()
await laufe(JETZT)
@@ -437,18 +437,18 @@ test('Ersatz-Ticket scheitert mehrere Läufe: kein K27, jeder Lauf zählt fehler
await laufe(nach(30))
aw.failNext({ collection: 'workorders', method: 'POST', status: 500, message: 'Ticket kaputt' })
assert.deepEqual(await laufe(nach(31)), ergebnis({ geprueft: 1, fehler: 1 }))
assert.equal(mailsMit(K27).length, 0)
assert.equal(mailsMit(K30).length, 0)
assert.deepEqual(await laufe(nach(32)), ergebnis({ geprueft: 1, mails: 1 }))
assert.equal(mailsMit(K27).length, 1)
assert.equal(mailsMit(K30).length, 1)
assert.equal(ersatzTickets().length, 1)
})
test('Abschalten gelingt, die Folge scheitert (Marker der K26 steht noch, älter als disabledAt): der nächste Lauf holt K27 und das Ersatz-Ticket nach, einmal', async () => {
// So steht das Dokument, wenn nach disable() nichts mehr geschrieben wurde: disabled/abo_ende, der Marker ist der der K26
test('Abschalten gelingt, die Folge scheitert (Marker der K29 steht noch, älter als disabledAt): der nächste Lauf holt K30 und das Ersatz-Ticket nach, einmal', async () => {
// So steht das Dokument, wenn nach disable() nichts mehr geschrieben wurde: disabled/abo_ende, der Marker ist der der K29
await legeFunktion(1, { state: 'disabled', enabled: false, disabledReason: 'abo_ende', disabledAt: JETZT.toISOString(), aboEndeAt: ZUVOR, aboEndeMailAt: ZUVOR })
abgelaufen()
assert.deepEqual(await laufe(nach(1)), ergebnis({ geprueft: 1, mails: 1 }))
assert.equal(mailsMit(K27).length, 1)
assert.equal(mailsMit(K30).length, 1)
assert.equal(ersatzTickets().length, 1)
assert.ok(ms(funktion().aboEndeMailAt) >= ms(funktion().disabledAt))
assert.deepEqual(await laufe(nach(2)), ergebnis({ geprueft: 1 }))
@@ -456,7 +456,7 @@ test('Abschalten gelingt, die Folge scheitert (Marker der K26 steht noch, älter
assert.equal(ersatzTickets().length, 1)
})
test('Wanduhr nach dem Laufstart (disabledAt später als now): der K27-Marker liegt nie davor, K27 geht nicht täglich raus', async () => {
test('Wanduhr nach dem Laufstart (disabledAt später als now): der K30-Marker liegt nie davor, K30 geht nicht täglich raus', async () => {
await legeFunktion(1)
abgelaufen()
await laufe(JETZT)
@@ -470,12 +470,12 @@ test('Wanduhr nach dem Laufstart (disabledAt später als now): der K27-Marker li
}
assert.ok(ms(funktion().aboEndeMailAt) >= ms(funktion().disabledAt), 'Marker nicht vor disabledAt')
assert.deepEqual(await laufe(nach(31)), ergebnis({ geprueft: 1 }))
assert.equal(mailsMit(K27).length, 1)
assert.equal(mailsMit(K30).length, 1)
})
// ---------------------------------------------------------------- Schritt 3: Rückkehr des Abos
test('Rückkehr an Tag 45: zurück auf active, Neubau, K24 wieder', async () => {
test('Rückkehr an Tag 45: zurück auf active, Neubau, K27 wieder', async () => {
await bisAbgeschaltet()
mails().length = 0
laeuft()
@@ -492,9 +492,9 @@ test('Rückkehr an Tag 45: zurück auf active, Neubau, K24 wieder', async () =>
// Neubau nach dem Schreiben des Zustands und ausserhalb der Sperre
assert.deepEqual(rebuilds, [{ projektId: 'p1', state: 'active', adminFrei: true }])
// K24 mit dem Block wieder und dem Neubau-Text
assert.equal(mailsMit(K24).length, 1)
const mail = mailsMit(K24)[0]
// K27 mit dem Block wieder und dem Neubau-Text
assert.equal(mailsMit(K27).length, 1)
const mail = mailsMit(K27)[0]
assert.deepEqual(mail.to.map((a) => a.address), ['silke@example.com'])
assert.match(mail.text, /Dein Wartungs-Abo läuft wieder/)
assert.match(mail.text, /nach dem nächsten Veröffentlichen/)
@@ -505,7 +505,7 @@ test('Rückkehr an Tag 45: zurück auf active, Neubau, K24 wieder', async () =>
assert.equal(rebuilds.length, 1)
})
test('Rückkehr aus disabled: ein nie freigeschaltetes Dokument (kein activatedAt) kommt auf setup zurück; einbau manuell sagt K24 "das Team baut ein"', async () => {
test('Rückkehr aus disabled: ein nie freigeschaltetes Dokument (kein activatedAt) kommt auf setup zurück; einbau manuell sagt K27 "das Team baut ein"', async () => {
await legeFunktion(1, { state: 'setup', enabled: false, activatedAt: null })
abgelaufen()
await laufe(JETZT)
@@ -524,7 +524,7 @@ test('Rückkehr aus disabled: ein nie freigeschaltetes Dokument (kein activatedA
assert.match(mails()[0].text, /baut unser Team/)
})
test('Rückkehr vor dem Abschalten (Tag 10): aboEndeAt leer, Zustand bleibt, kein Neubau, K24 wieder ohne "Wie geht es weiter"', async () => {
test('Rückkehr vor dem Abschalten (Tag 10): aboEndeAt leer, Zustand bleibt, kein Neubau, K27 wieder ohne "Wie geht es weiter"', async () => {
for (const [name, over] of [
['aktiv', {}],
['in Einrichtung', { state: 'setup', enabled: false, activatedAt: null }],
@@ -548,15 +548,15 @@ test('Rückkehr vor dem Abschalten (Tag 10): aboEndeAt leer, Zustand bleibt, kei
assert.equal(doc.einbau, 'angestossen', name)
assert.equal(rebuilds.length, 0, `${name}: die Leisten waren nie weg, kein Neubau`)
assert.equal(mailsMit(K24).length, 1, name)
const text = mailsMit(K24)[0].text
assert.equal(mailsMit(K27).length, 1, name)
const text = mailsMit(K27)[0].text
assert.match(text, /Dein Wartungs-Abo läuft wieder/, name)
assert.doesNotMatch(text, /Wie geht es weiter/, `${name}: beide Blöcke aus, der Kasten entfällt`)
assert.doesNotMatch(text, /nach dem nächsten Veröffentlichen|baut unser Team|Ohne gespeicherte Zeiten/, name)
}
})
test('Rückkehr ohne vorherige K26 (kein Marker): die Funktion wird bereinigt, der Kunde bekommt keine Mail über etwas, das er nie erfuhr', async () => {
test('Rückkehr ohne vorherige K29 (kein Marker): die Funktion wird bereinigt, der Kunde bekommt keine Mail über etwas, das er nie erfuhr', async () => {
await legeFunktion(1, { aboEndeAt: JETZT.toISOString(), behaltenSessionId: 'cs_test_offen' })
laeuft()
assert.deepEqual(await laufe(nach(3)), ergebnis({ geprueft: 1, wiederEin: 1 }))
@@ -572,8 +572,8 @@ test('Rückkehr: das Abo kommt zwischen Vorauswahl und Sperre zurück ⇒ der fr
const erg = await mitEinwurf(liste('subscriptions'), async () => laeuft(), () => laufe(nach(30)))
assert.deepEqual(erg, ergebnis({ geprueft: 1, wiederEin: 1, mails: 1 }))
assert.equal(funktion().state, 'active')
assert.equal(mailsMit(K27).length, 0)
assert.equal(mailsMit(K24).length, 1)
assert.equal(mailsMit(K30).length, 0)
assert.equal(mailsMit(K27).length, 1)
assert.equal(ersatzTickets().length, 0)
})
@@ -592,7 +592,7 @@ test('Löschung fällig, aber die Daten sind schon weg (Lauf nach dem Löschen u
assert.deepEqual(await laufe(nach(60)), ergebnis({ geprueft: 1, geloescht: 1 }))
assert.equal(funktion().state, 'off')
assert.equal(rebuilds.length, 0)
assert.equal(mailsMit(K24).length, 0)
assert.equal(mailsMit(K27).length, 0)
})
// ---------------------------------------------------------------- Schritt 4: Löschung
@@ -615,22 +615,22 @@ test('Tag 60 nach dem Abo-Ende (30 Tage nach der Abschaltung): Daten gelöscht,
assert.equal(doc.state, 'off')
assert.equal(doc.enabled, false)
assert.deepEqual(
[doc.ticketId ?? '', doc.einbau ?? '', doc.k24At ?? null, doc.aboEndeAt ?? null, doc.behaltenSessionId ?? '', doc.activatedAt ?? null],
[doc.ticketId ?? '', doc.einbau ?? '', doc.einschaltMailAt ?? null, doc.aboEndeAt ?? null, doc.behaltenSessionId ?? '', doc.activatedAt ?? null],
['', '', null, null, '', null],
'ticketId, einbau, k24At, aboEndeAt, behaltenSessionId, activatedAt geleert (sonst käme ein späterer Lauf zu früh auf active)'
'ticketId, einbau, einschaltMailAt, aboEndeAt, behaltenSessionId, activatedAt geleert (sonst käme ein späterer Lauf zu früh auf active)'
)
assert.match(doc.adminNote, /Öffnungszeiten gelöscht, 30 Tage nach dem Abschalten/)
assert.equal(await ladeZeiten(feature.$id), null, 'das Zeiten-Dokument (samt ersatzHash) ist gelöscht')
assert.equal(aw.docs('oeffnungszeiten').size, 0)
assert.equal(aw.docs('zeitenErinnerungen').has('m1'), false, 'Erinnerungsmerker der Funktion gelöscht')
assert.equal(aw.docs('zeitenErinnerungen').has('m2'), true, 'fremde Merker bleiben')
assert.equal(mails().length, 0, 'die K27 hat die Löschung angekündigt, es gibt keine weitere Mail')
assert.equal(mails().length, 0, 'die K30 hat die Löschung angekündigt, es gibt keine weitere Mail')
// aus ist aus: der nächste Lauf liest das Dokument nicht mehr
assert.deepEqual(await laufe(nach(61)), ergebnis())
})
test('nach der Löschung: ticketId, einbau, k24At, ersatzHash leer; Neubestellung legt ein neues Ticket an', async () => {
test('nach der Löschung: ticketId, einbau, einschaltMailAt, ersatzHash leer; Neubestellung legt ein neues Ticket an', async () => {
await bisAbgeschaltet()
await laufe(nach(60))
assert.equal(funktion().state, 'off')
@@ -639,7 +639,7 @@ test('nach der Löschung: ticketId, einbau, k24At, ersatzHash leer; Neubestellun
mails().length = 0
rebuilds.length = 0
// Das Abo läuft wieder, der Kunde schaltet neu ein (Gratisweg): neues Ticket, Neubau, K24
// Das Abo läuft wieder, der Kunde schaltet neu ein (Gratisweg): neues Ticket, Neubau, K27
laeuft()
const { doc, changed } = await einschaltenZeiten({ customerId: KUNDE, projectId: 'p1', by: 'kunde', now: nach(61) })
assert.equal(changed, true)
@@ -647,8 +647,8 @@ test('nach der Löschung: ticketId, einbau, k24At, ersatzHash leer; Neubestellun
assert.ok(doc.ticketId && doc.ticketId !== 't_1', 'ein neues Einbau-Ticket')
assert.equal(tickets().length, ticketsVorher + 1)
assert.equal(doc.einbau, 'angestossen')
assert.ok(doc.k24At, 'K24 neu gesendet')
assert.equal(mailsMit(K24).length, 1)
assert.ok(doc.einschaltMailAt, 'K27 neu gesendet')
assert.equal(mailsMit(K27).length, 1)
assert.equal(rebuilds.length, 1)
})
@@ -790,14 +790,14 @@ test('ein unlesbares Datum bei einem Dokument stoppt die anderen nicht (je Dokum
const vorher = [stand(1), stand(2)]
const erg = await laufe(nach(30))
assert.deepEqual(erg, ergebnis({ geprueft: 3, aboEnde: 1, mails: 1, fehler: 2 }), 'das dritte Dokument ist trotzdem dran: Abo-Ende erkannt, K26')
assert.deepEqual(erg, ergebnis({ geprueft: 3, aboEnde: 1, mails: 1, fehler: 2 }), 'das dritte Dokument ist trotzdem dran: Abo-Ende erkannt, K29')
assert.deepEqual([stand(1), stand(2)], vorher, 'die kaputten Dokumente bleiben, wie sie sind')
assert.deepEqual(mails().map((m) => m.to[0].address), ['eva@example.com'])
assert.equal(ersatzTickets().length, 0)
assert.equal(aw.docs('oeffnungszeiten').size, 3, 'nichts gelöscht')
})
test('Kunde nicht lesbar beim Versand: fehler, das nächste Dokument ist trotzdem dran, der nächste Lauf sendet die K26 nach', async () => {
test('Kunde nicht lesbar beim Versand: fehler, das nächste Dokument ist trotzdem dran, der nächste Lauf sendet die K29 nach', async () => {
await legeFunktion(1)
await legeFunktion(2, {}, { kunde: 'c2' })
aw.seed('customers', { $id: 'c2', name: 'Karl Weber', email: 'karl@example.com' })
@@ -817,13 +817,13 @@ test('Kunde nicht lesbar beim Versand: fehler, das nächste Dokument ist trotzde
test('Mails und Neubau gehen ohne gehaltene Sperre feature-admin raus', async () => {
await legeFunktion(1)
abgelaufen()
await laufe(JETZT) // K26
await laufe(nach(30)) // K27
await laufe(JETZT) // K29
await laufe(nach(30)) // K30
laeuft()
await laufe(nach(45)) // Neubau und K24
await laufe(nach(45)) // Neubau und K27
assert.deepEqual(
sperrenBeimVersand.map((s) => [s.subject === K27 ? 'K27' : s.subject === K24 ? 'K24' : 'K26', s.admin]),
[['K26', true], ['K27', true], ['K24', true]]
sperrenBeimVersand.map((s) => [s.subject === K30 ? 'K30' : s.subject === K27 ? 'K27' : 'K29', s.admin]),
[['K29', true], ['K30', true], ['K27', true]]
)
assert.equal(rebuilds[0].adminFrei, true)
})
@@ -840,7 +840,7 @@ test('Abo-Abfragen: die Vorauswahl liest je Kunde einmal, dazu das frische Lesen
aw.resetListCalls()
assert.deepEqual(await laufe(), ergebnis({ geprueft: 3, aboEnde: 2, mails: 2 }))
assert.equal(abfragenAufAbos(), 2 + 2, 'zwei Kunden in der Vorauswahl; frisch nur für die zwei Dokumente von c1 (das von c2 braucht nichts)')
assert.equal(mailsMit(K26).length, 2)
assert.equal(mailsMit(K29).length, 2)
assert.deepEqual(mails().map((m) => m.to[0].address), ['silke@example.com', 'silke@example.com'])
assert.ok(funktion(1).aboEndeAt && funktion(2).aboEndeAt)
assert.ok(!funktion(3).aboEndeAt)
@@ -887,12 +887,12 @@ test('activate() ohne extra lässt aboEndeAt und behaltenSessionId stehen; admin
assert.deepEqual([team.aboEndeAt ?? null, team.behaltenSessionId ?? ''], [null, ''])
})
test('abgeschaltet mit abo_ende, aber ohne Marker (Lauf nach dem Abschalten unterbrochen): der nächste Lauf holt K27 und das Ersatz-Ticket nach', async () => {
test('abgeschaltet mit abo_ende, aber ohne Marker (Lauf nach dem Abschalten unterbrochen): der nächste Lauf holt K30 und das Ersatz-Ticket nach', async () => {
await legeFunktion(1, { state: 'disabled', enabled: false, disabledReason: 'abo_ende', disabledAt: JETZT.toISOString(), aboEndeAt: ZUVOR })
abgelaufen()
assert.deepEqual(await laufe(nach(1)), ergebnis({ geprueft: 1, mails: 1 }))
assert.equal(mailsMit(K27).length, 1)
assert.match(mailsMit(K27)[0].text, /Eingaben gespeichert bis: 04\.11\.2026/, 'Löschdatum = disabledAt + 30 Tage')
assert.equal(mailsMit(K30).length, 1)
assert.match(mailsMit(K30)[0].text, /Eingaben gespeichert bis: 04\.11\.2026/, 'Löschdatum = disabledAt + 30 Tage')
assert.equal(ersatzTickets().length, 1)
assert.deepEqual(await laufe(nach(2)), ergebnis({ geprueft: 1 }))
assert.equal(mails().length, 1)