Terminplaner: Erstattungsmeldung unabhaengig vom Abo nachholen (R107)
Der taegliche Abo-Abgleich holt T19 fuer jedes bezahlte, abgeschaltete Dokument mit Grund abo_ende_vor_einrichtung und zahlungMeldung ungleich gemeldet nach, auch wenn das Wartungs-Abo wieder laeuft oder nicht lesbar ist (der Nachlauf liest das Abo nie). Reihenfolge wie bisher: T19, dann K22 (nur wenn aboEndeMailAt leer), dann Marker. meldeAboEnde sendet bei gesetztem K22-Marker und offener Erstattung nur T19. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
13523ca5e2
commit
1c74c89feb
2 files changed
+342
-11
No files matched your search
@@ -21,6 +21,11 @@ import { datumKurz } from './zeit.js'
|
|||||||
* Schritt 2 fuer jedes abgeschaltete Dokument mit Grund 'abo_ende' oder (bezahlt) 'abo_ende_vor_einrichtung' ohne aboEndeMailAt
|
* Schritt 2 fuer jedes abgeschaltete Dokument mit Grund 'abo_ende' oder (bezahlt) 'abo_ende_vor_einrichtung' ohne aboEndeMailAt
|
||||||
* die Meldung nachholen (auch fuer die eben abgeschalteten); der Marker aboEndeMailAt wird erst nach erfolgreichem
|
* die Meldung nachholen (auch fuer die eben abgeschalteten); der Marker aboEndeMailAt wird erst nach erfolgreichem
|
||||||
* Versand gesetzt
|
* Versand gesetzt
|
||||||
|
* Schritt 2b Erstattung nachholen, unabhaengig vom Abo (R107, E5b): fuer jedes BEZAHLTE (unlockedByPurchase true), abgeschaltete
|
||||||
|
* Dokument mit Grund 'abo_ende_vor_einrichtung' und zahlungMeldung !== 'gemeldet' geht T19 an das Team, ob das Wartungs-
|
||||||
|
* Abo inzwischen wieder laeuft oder nicht lesbar ist (die Erstattung bleibt nach D3 voll geschuldet, die Rueckkehr nach
|
||||||
|
* Schritt 3 gilt dafuer nicht). Der Nachlauf liest das Abo nie. Danach, in dieser Reihenfolge und nur nach erfolgreichem
|
||||||
|
* T19: K22 (Block vor_einrichtung), falls aboEndeMailAt noch leer ist, und der Marker. Ein unbezahltes Dokument bekommt nichts.
|
||||||
* Schritt 3 Rueckkehr: ein abgeschaltetes Dokument mit Grund 'abo_ende', dessen Wartungs-Abo wieder laeuft, wird wieder eingeschaltet
|
* Schritt 3 Rueckkehr: ein abgeschaltetes Dokument mit Grund 'abo_ende', dessen Wartungs-Abo wieder laeuft, wird wieder eingeschaltet
|
||||||
* (activate: Daten und Einstellungen bleiben, Grund, Marker und disabledAt enden), solange die Einstellung noch da ist, auch
|
* (activate: Daten und Einstellungen bleiben, Grund, Marker und disabledAt enden), solange die Einstellung noch da ist, auch
|
||||||
* nach Tag 30 (der Abgleich laeuft im Tagesjob vor der Loeschung; sonst blockierten sich Loeschung und Rueckkehr).
|
* nach Tag 30 (der Abgleich laeuft im Tagesjob vor der Loeschung; sonst blockierten sich Loeschung und Rueckkehr).
|
||||||
@@ -36,6 +41,8 @@ import { datumKurz } from './zeit.js'
|
|||||||
* Abo (aboLage direkt, nie der Memo): zwischen Vorauswahl und Sperre kann das Abo zurueckkommen oder das Team abschalten.
|
* Abo (aboLage direkt, nie der Memo): zwischen Vorauswahl und Sperre kann das Abo zurueckkommen oder das Team abschalten.
|
||||||
* Die Mails (T19, K22) und die Marker folgen NACH der Sperre (meldeAboEnde), denn T19 laeuft ueber meldeErstattung mit eigener
|
* Die Mails (T19, K22) und die Marker folgen NACH der Sperre (meldeAboEnde), denn T19 laeuft ueber meldeErstattung mit eigener
|
||||||
* Sperre feature-meldung:<docId> und darf nie innerhalb einer feature-admin- oder feature-paid-Sperre gerufen werden.
|
* Sperre feature-meldung:<docId> und darf nie innerhalb einer feature-admin- oder feature-paid-Sperre gerufen werden.
|
||||||
|
* - Ausnahme von "nach frischem Lesen des Abos": der Nachlauf der Erstattung (Schritt 2b) liest nur das Dokument frisch (unter
|
||||||
|
* feature-admin:<projectId>), nie das Abo - er schaltet nichts ab und nichts ein, er meldet nur, was geschuldet ist.
|
||||||
* - Fail-closed: ist etwas nicht lesbar (Liste, Abo, Ereignisse, Dokument), wird nichts abgeschaltet. Kunde bzw. Dokument werden
|
* - Fail-closed: ist etwas nicht lesbar (Liste, Abo, Ereignisse, Dokument), wird nichts abgeschaltet. Kunde bzw. Dokument werden
|
||||||
* uebersprungen, `fehler` zaehlt, der naechste Lauf versucht es wieder. Das Log nennt weder Namen noch Adressen.
|
* uebersprungen, `fehler` zaehlt, der naechste Lauf versucht es wieder. Das Log nennt weder Namen noch Adressen.
|
||||||
* - disabledAt aendert dieser Abgleich nie nach dem Abschalten: schlaegt eine Mail fehl, wiederholt der naechste Tag den Versand mit
|
* - disabledAt aendert dieser Abgleich nie nach dem Abschalten: schlaegt eine Mail fehl, wiederholt der naechste Tag den Versand mit
|
||||||
@@ -62,6 +69,13 @@ const istBewertet = (doc) =>
|
|||||||
doc.state === 'setup' ||
|
doc.state === 'setup' ||
|
||||||
(doc.state === 'disabled' && typeof doc.disabledReason === 'string' && doc.disabledReason.startsWith('abo_ende'))
|
(doc.state === 'disabled' && typeof doc.disabledReason === 'string' && doc.disabledReason.startsWith('abo_ende'))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schritt 2b: bezahlte, wegen des Abo-Endes vor der Einrichtung abgeschaltete Einrichtung, deren Erstattung (T19) noch nicht beim Team ist.
|
||||||
|
* Das Abo spielt keine Rolle: die Erstattung bleibt geschuldet, auch wenn es zurueckkommt.
|
||||||
|
*/
|
||||||
|
const istErstattungOffen = (doc) =>
|
||||||
|
doc.state === 'disabled' && doc.disabledReason === GRUND_VOR && doc.unlockedByPurchase === true && doc.zahlungMeldung !== 'gemeldet'
|
||||||
|
|
||||||
/** Schritt 3: abgeschaltet wegen des Abo-Endes (nur 'abo_ende', nicht 'abo_ende_vor_einrichtung'). */
|
/** Schritt 3: abgeschaltet wegen des Abo-Endes (nur 'abo_ende', nicht 'abo_ende_vor_einrichtung'). */
|
||||||
const istRueckkehr = (doc) => doc.state === 'disabled' && doc.disabledReason === GRUND
|
const istRueckkehr = (doc) => doc.state === 'disabled' && doc.disabledReason === GRUND
|
||||||
|
|
||||||
@@ -77,20 +91,24 @@ const portalFeatures = () => config.collections.portalFeatures
|
|||||||
* siteFeatures das als T19 und der Abgleich sendet dann K22). Ein bezahltes Dokument meldet ZUERST die Erstattung an das Team
|
* siteFeatures das als T19 und der Abgleich sendet dann K22). Ein bezahltes Dokument meldet ZUERST die Erstattung an das Team
|
||||||
* (meldeErstattung, T19, genau einmal je Dokument, auch ohne erreichbaren Kunden), dann K22 mit Block `vor_einrichtung` (ohne
|
* (meldeErstattung, T19, genau einmal je Dokument, auch ohne erreichbaren Kunden), dann K22 mit Block `vor_einrichtung` (ohne
|
||||||
* `daten`: es gibt nichts zu loeschen). Scheitert T19 (weder gesendet noch schon gemeldet), folgen weder K22 noch Marker: der
|
* `daten`: es gibt nichts zu loeschen). Scheitert T19 (weder gesendet noch schon gemeldet), folgen weder K22 noch Marker: der
|
||||||
* naechste Tag wiederholt beides in dieser Reihenfolge.
|
* naechste Tag wiederholt beides in dieser Reihenfolge. Der Marker aboEndeMailAt gilt nur fuer K22: steht er schon, aber T19 ist
|
||||||
|
* noch nicht gemeldet (R107), geht nur T19 und K22 nicht noch einmal. `lage` braucht dieser Grund nicht (nur 'abo_ende' fuer
|
||||||
|
* den Block hosting).
|
||||||
* Der Aufrufer liest `doc` frisch und haelt KEINE feature-admin- und keine feature-paid-Sperre (meldeErstattung sperrt selbst
|
* Der Aufrufer liest `doc` frisch und haelt KEINE feature-admin- und keine feature-paid-Sperre (meldeErstattung sperrt selbst
|
||||||
* feature-meldung:<docId>). Ohne Kunde oder ohne E-Mail gibt es keine K22 zu senden: der Marker wird gesetzt, damit es kein
|
* feature-meldung:<docId>). Ohne Kunde oder ohne E-Mail gibt es keine K22 zu senden: der Marker wird gesetzt, damit es kein
|
||||||
* Wiederholen gibt. Wirft nie.
|
* Wiederholen gibt. Wirft nie.
|
||||||
* @param {object} doc portalFeatures-Dokument (disabled)
|
* @param {object} doc portalFeatures-Dokument (disabled)
|
||||||
* @param {{ lage: { art: string }, now: Date, onGesendet?: () => void }} options onGesendet wird nach jedem versendeten K22 gerufen
|
* @param {{ lage?: { art: string } | null, now: Date, onGesendet?: () => void }} options onGesendet wird nach jedem versendeten K22 gerufen
|
||||||
* @returns {Promise<boolean>} true = nichts mehr offen (Marker gesetzt oder nichts zu melden), false = Versand oder Marker
|
* @returns {Promise<boolean>} true = nichts mehr offen (Marker gesetzt oder nichts zu melden), false = Versand oder Marker
|
||||||
* gescheitert (der naechste Tag wiederholt)
|
* gescheitert (der naechste Tag wiederholt)
|
||||||
*/
|
*/
|
||||||
export async function meldeAboEnde(doc, { lage, now, onGesendet = null }) {
|
export async function meldeAboEnde(doc, { lage = null, now, onGesendet = null }) {
|
||||||
const nowMs = pruefeNow(now, 'meldeAboEnde')
|
const nowMs = pruefeNow(now, 'meldeAboEnde')
|
||||||
const vorEinrichtung = doc.disabledReason === GRUND_VOR
|
const vorEinrichtung = doc.disabledReason === GRUND_VOR
|
||||||
if ((doc.disabledReason !== GRUND && !vorEinrichtung) || doc.aboEndeMailAt) return true // mit Marker ist alles gesagt
|
if (doc.disabledReason !== GRUND && !vorEinrichtung) return true
|
||||||
if (vorEinrichtung && doc.unlockedByPurchase !== true) return true // nichts zu erstatten, nichts zu sagen: die Zahlung kommt eventuell noch
|
if (vorEinrichtung && doc.unlockedByPurchase !== true) return true // nichts zu erstatten, nichts zu sagen: die Zahlung kommt eventuell noch
|
||||||
|
const erstattungOffen = vorEinrichtung && doc.zahlungMeldung !== 'gemeldet'
|
||||||
|
if (doc.aboEndeMailAt && !erstattungOffen) return true // mit Marker ist alles gesagt
|
||||||
const setzeMarker = () => updateDocument(portalFeatures(), doc.$id, { aboEndeMailAt: new Date(nowMs).toISOString() })
|
const setzeMarker = () => updateDocument(portalFeatures(), doc.$id, { aboEndeMailAt: new Date(nowMs).toISOString() })
|
||||||
let gesendet = false
|
let gesendet = false
|
||||||
try {
|
try {
|
||||||
@@ -102,6 +120,7 @@ export async function meldeAboEnde(doc, { lage, now, onGesendet = null }) {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (doc.aboEndeMailAt) return true // K22 ging schon, hier fehlte nur T19
|
||||||
if (!customer?.email) {
|
if (!customer?.email) {
|
||||||
console.warn('[termine-abo] K22 entfällt: Kunde nicht mehr vorhanden oder ohne E-Mail-Adresse, Marker wird gesetzt')
|
console.warn('[termine-abo] K22 entfällt: Kunde nicht mehr vorhanden oder ohne E-Mail-Adresse, Marker wird gesetzt')
|
||||||
await setzeMarker()
|
await setzeMarker()
|
||||||
@@ -186,6 +205,27 @@ async function bearbeiteDokument(doc, ctx) {
|
|||||||
if (!fertig) erg.fehler += 1
|
if (!fertig) erg.fehler += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schritt 2b, ein Dokument (R107): in der Liste bezahlt, abgeschaltet mit Grund 'abo_ende_vor_einrichtung' und T19 nicht gemeldet.
|
||||||
|
* Unter der Sperre feature-admin:<projectId> frisch lesen (das Team kann inzwischen aktiviert haben, der Webhook T19 gemeldet), das
|
||||||
|
* Abo wird NICHT gelesen. Die Meldung (T19, danach K22 und Marker, meldeAboEnde) folgt nach der Sperre. Wirft nie.
|
||||||
|
* @returns {Promise<boolean>} true = erledigt oder gescheitert (fehler gezaehlt, der naechste Tag wiederholt): das Dokument ist fuer
|
||||||
|
* diesen Lauf versorgt; false = nichts zu tun (die frische Fassung ist nicht mehr offen): der Abo-Pfad darf es noch bewerten
|
||||||
|
*/
|
||||||
|
async function holeErstattungNach(doc, ctx) {
|
||||||
|
const { erg, now } = ctx
|
||||||
|
try {
|
||||||
|
const frisch = await withLocks([`feature-admin:${doc.projectId}`], () => getDocument(portalFeatures(), doc.$id))
|
||||||
|
if (!istErstattungOffen(frisch)) return false
|
||||||
|
const fertig = await meldeAboEnde(frisch, { now, onGesendet: () => (erg.mails += 1) })
|
||||||
|
if (!fertig) erg.fehler += 1
|
||||||
|
} catch (err) {
|
||||||
|
erg.fehler += 1
|
||||||
|
console.error(`[termine-abo] Erstattung für Funktion ${doc.$id} nicht gemeldet, der nächste Lauf wiederholt: ${fehlerText(err)}`)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* K23 an die E-Mail des Kundenkontos. Nur nach geschriebenem `active` gerufen, nie innerhalb einer Sperre. Scheitert der Versand,
|
* K23 an die E-Mail des Kundenkontos. Nur nach geschriebenem `active` gerufen, nie innerhalb einer Sperre. Scheitert der Versand,
|
||||||
* bleibt die Reaktivierung bestehen: das Log (ohne Adresse) und `fehler` halten es fest; der Marker aboEndeMailAt ist mit dem
|
* bleibt die Reaktivierung bestehen: das Log (ohne Adresse) und `fehler` halten es fest; der Marker aboEndeMailAt ist mit dem
|
||||||
@@ -245,17 +285,26 @@ async function bearbeiteKunde(customerId, dokumente, ctx) {
|
|||||||
const bewertet = dokumente.filter(istBewertet)
|
const bewertet = dokumente.filter(istBewertet)
|
||||||
if (bewertet.length === 0) return // nichts zu bewerten: das Abo wird nicht gelesen
|
if (bewertet.length === 0) return // nichts zu bewerten: das Abo wird nicht gelesen
|
||||||
|
|
||||||
|
// Schritt 2b (R107) VOR dem Abo: die Erstattung haengt nicht davon ab, ob das Abo lesbar ist oder wieder laeuft
|
||||||
|
const versorgt = new Set()
|
||||||
|
for (const doc of bewertet.filter(istErstattungOffen)) {
|
||||||
|
if (await holeErstattungNach(doc, ctx)) versorgt.add(doc)
|
||||||
|
}
|
||||||
|
erg.geprueft += versorgt.size
|
||||||
|
const uebrige = bewertet.filter((doc) => !versorgt.has(doc))
|
||||||
|
if (uebrige.length === 0) return // alles versorgt: das Abo wird nicht gelesen
|
||||||
|
|
||||||
let lage
|
let lage
|
||||||
try {
|
try {
|
||||||
lage = await ctx.memo.lage(customerId)
|
lage = await ctx.memo.lage(customerId)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
erg.fehler += 1
|
erg.fehler += 1
|
||||||
console.error(`[termine-abo] Abo-Lage eines Kunden nicht lesbar, ${bewertet.length} Funktion(en) übersprungen: ${fehlerText(err)}`)
|
console.error(`[termine-abo] Abo-Lage eines Kunden nicht lesbar, ${uebrige.length} Funktion(en) übersprungen: ${fehlerText(err)}`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
erg.geprueft += bewertet.length
|
erg.geprueft += uebrige.length
|
||||||
|
|
||||||
for (const doc of bewertet) {
|
for (const doc of uebrige) {
|
||||||
// laeuft das Abo, gibt es nur die Rueckkehr (Schritt 3); sonst abschalten, einrichten, nachholen (Schritte 1, 1b, 2)
|
// laeuft das Abo, gibt es nur die Rueckkehr (Schritt 3); sonst abschalten, einrichten, nachholen (Schritte 1, 1b, 2)
|
||||||
const bearbeite = lage.laeuft ? istRueckkehr(doc) && schalteWiederEin : aktionFuer(doc) && bearbeiteDokument
|
const bearbeite = lage.laeuft ? istRueckkehr(doc) && schalteWiederEin : aktionFuer(doc) && bearbeiteDokument
|
||||||
if (!bearbeite) continue
|
if (!bearbeite) continue
|
||||||
@@ -274,7 +323,7 @@ async function bearbeiteKunde(customerId, dokumente, ctx) {
|
|||||||
* @param {Date} now
|
* @param {Date} now
|
||||||
* @returns {Promise<{ geprueft: number, abgeschaltet: number, wiederEin: number, mails: number, ausgenommen: number, fehler: number }>}
|
* @returns {Promise<{ geprueft: number, abgeschaltet: number, wiederEin: number, mails: number, ausgenommen: number, fehler: number }>}
|
||||||
* geprueft = bewertete Dokumente (aktiv mit unlockedByPurchase true, ordered/setup, disabled mit Grund abo_ende*, deren Kunde
|
* geprueft = bewertete Dokumente (aktiv mit unlockedByPurchase true, ordered/setup, disabled mit Grund abo_ende*, deren Kunde
|
||||||
* lesbar war), abgeschaltet = in diesem Lauf abgeschaltet, wiederEin = in diesem Lauf wieder eingeschaltet (Schritt 3), mails =
|
* lesbar war; der Nachlauf der Erstattung, Schritt 2b, braucht das Abo nicht und zaehlt immer), abgeschaltet = in diesem Lauf abgeschaltet, wiederEin = in diesem Lauf wieder eingeschaltet (Schritt 3), mails =
|
||||||
* versendete Kundenmails K22 und K23 (T19 geht ans Team und zaehlt nicht), ausgenommen = aktive Dokumente ohne Kauf (Pilot, Kulanz),
|
* versendete Kundenmails K22 und K23 (T19 geht ans Team und zaehlt nicht), ausgenommen = aktive Dokumente ohne Kauf (Pilot, Kulanz),
|
||||||
* fehler = Kunden und Dokumente, die nicht bearbeitet werden konnten, sowie ein gescheiterter K23-Versand (das Dokument ist dann
|
* fehler = Kunden und Dokumente, die nicht bearbeitet werden konnten, sowie ein gescheiterter K23-Versand (das Dokument ist dann
|
||||||
* trotzdem wieder aktiv)
|
* trotzdem wieder aktiv)
|
||||||
|
|||||||
@@ -90,6 +90,9 @@ const funktion = (id = F1) => aw.docs('portalFeatures').get(id)
|
|||||||
const T19_BETREFF = '[Abo] Erstattung fällig: Terminplaner-Einrichtung'
|
const T19_BETREFF = '[Abo] Erstattung fällig: Terminplaner-Einrichtung'
|
||||||
const K22_BETREFF = /^Dein Terminplaner wurde beendet/
|
const K22_BETREFF = /^Dein Terminplaner wurde beendet/
|
||||||
|
|
||||||
|
/** Endzustand der Meldung zu einer bezahlten, abgeschalteten Einrichtung (E5): T19 gemeldet, K22 gesendet (Marker). Der Abgleich laesst sie in Ruhe. */
|
||||||
|
const ERSTATTET = { zahlungMeldung: 'gemeldet', aboEndeMailAt: '2026-10-04T08:00:00.000Z' }
|
||||||
|
|
||||||
/** Bezahlte Einrichtung im Aufbau (setup): Sitzung cs_test_1, Rechnungslink, offenes Ticket WO-4711 (Dokument t_<nr> in workorders). */
|
/** Bezahlte Einrichtung im Aufbau (setup): Sitzung cs_test_1, Rechnungslink, offenes Ticket WO-4711 (Dokument t_<nr> in workorders). */
|
||||||
function legeAufbau(nr = 1, over = {}) {
|
function legeAufbau(nr = 1, over = {}) {
|
||||||
aw.seed('workorders', { $id: `t_${nr}`, woid: 'WO-4711', status: 'Open', topic: 'Terminplaner einbauen – Praxis Muster' })
|
aw.seed('workorders', { $id: `t_${nr}`, woid: 'WO-4711', status: 'Open', topic: 'Terminplaner einbauen – Praxis Muster' })
|
||||||
@@ -340,7 +343,7 @@ test('Laeuft das Abo: ordered, setup und ein abgeschaltetes abo_ende_vor_einrich
|
|||||||
wartung('active')
|
wartung('active')
|
||||||
legeBestellung(1)
|
legeBestellung(1)
|
||||||
legeAufbau(2)
|
legeAufbau(2)
|
||||||
legeFunktion(3, { state: 'disabled', enabled: false, disabledReason: 'abo_ende_vor_einrichtung', disabledAt: plus(JETZT, -TAG).toISOString() })
|
legeFunktion(3, { ...ERSTATTET, state: 'disabled', enabled: false, disabledReason: 'abo_ende_vor_einrichtung', disabledAt: plus(JETZT, -TAG).toISOString() })
|
||||||
const vorher = [1, 2, 3].map((nr) => ({ ...funktion(funktionsId(nr)) }))
|
const vorher = [1, 2, 3].map((nr) => ({ ...funktion(funktionsId(nr)) }))
|
||||||
assert.deepEqual(await laufe(), ergebnis({ geprueft: 3 }))
|
assert.deepEqual(await laufe(), ergebnis({ geprueft: 3 }))
|
||||||
assert.deepEqual([1, 2, 3].map((nr) => funktion(funktionsId(nr))), vorher)
|
assert.deepEqual([1, 2, 3].map((nr) => funktion(funktionsId(nr))), vorher)
|
||||||
@@ -835,7 +838,7 @@ test('Review Focus 1, nie ohne Spur: das Ticket wird nach dem Abo-Ende geschloss
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('abo_ende_vor_einrichtung-Dokument mit Abo-Rueckkehr: bleibt wie es ist (die Rueckkehr gilt nur fuer abo_ende)', async () => {
|
test('abo_ende_vor_einrichtung-Dokument mit Abo-Rueckkehr: bleibt wie es ist (die Rueckkehr gilt nur fuer abo_ende)', async () => {
|
||||||
legeFunktion(1, { state: 'disabled', enabled: false, disabledReason: 'abo_ende_vor_einrichtung', disabledAt: plus(JETZT, -TAG).toISOString() })
|
legeFunktion(1, { ...ERSTATTET, state: 'disabled', enabled: false, disabledReason: 'abo_ende_vor_einrichtung', disabledAt: plus(JETZT, -TAG).toISOString() })
|
||||||
wartung('active')
|
wartung('active')
|
||||||
const vorher = { ...funktion() }
|
const vorher = { ...funktion() }
|
||||||
assert.deepEqual(await laufe(), ergebnis({ geprueft: 1 }))
|
assert.deepEqual(await laufe(), ergebnis({ geprueft: 1 }))
|
||||||
@@ -865,6 +868,273 @@ test('Mehrere Projekte: bezahlter Aufbau, unbezahlte Bestellung und aktiver Term
|
|||||||
assert.ok(!funktion(funktionsId(3)).aboEndeMailAt)
|
assert.ok(!funktion(funktionsId(3)).aboEndeMailAt)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------- Erstattungsmeldung (T19) unabhaengig vom Abo (R107, E5b)
|
||||||
|
|
||||||
|
/** Bezahlte Einrichtung, die das Abo-Ende schon abgeschaltet hat, deren T19 aber noch aussteht (Marker gemeldet und aboEndeMailAt leer). */
|
||||||
|
const legeErstattungOffen = (nr = 1, over = {}) =>
|
||||||
|
legeAufbau(nr, { state: 'disabled', disabledReason: 'abo_ende_vor_einrichtung', disabledAt: JETZT.toISOString(), ...over })
|
||||||
|
|
||||||
|
/** Transport, bei dem T19 scheitert (alles andere geht in die Senke). */
|
||||||
|
const t19Scheitert = () =>
|
||||||
|
setMailTransportForTests({
|
||||||
|
sendMail: async (mail) => {
|
||||||
|
if (mail.subject === T19_BETREFF) throw new Error('Verbindung abgebrochen')
|
||||||
|
return sink.sendMail(mail)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
test('R107: T19 scheitert am Tag 1 (Abo beendet), am Tag 2 laeuft das Abo wieder - Tag 2 sendet T19 genau einmal, danach K22, das Dokument bleibt abgeschaltet, Tag 3 sendet nichts', async () => {
|
||||||
|
legeAufbau(1)
|
||||||
|
t19Scheitert()
|
||||||
|
assert.deepEqual(await laufe(JETZT), ergebnis({ geprueft: 1, abgeschaltet: 1, fehler: 1 }))
|
||||||
|
let doc = funktion()
|
||||||
|
assert.equal(doc.state, 'disabled')
|
||||||
|
assert.equal(doc.disabledReason, 'abo_ende_vor_einrichtung')
|
||||||
|
assert.ok(!doc.zahlungMeldung)
|
||||||
|
assert.ok(!doc.aboEndeMailAt)
|
||||||
|
assert.equal(mails().length, 0)
|
||||||
|
const disabledAt = doc.disabledAt
|
||||||
|
|
||||||
|
// Tag 2: das Wartungs-Abo laeuft wieder, der Versand geht
|
||||||
|
setMailTransportForTests(sink)
|
||||||
|
wartung('active')
|
||||||
|
aw.resetListCalls()
|
||||||
|
const tag2 = plus(JETZT, TAG)
|
||||||
|
assert.deepEqual(await laufe(tag2), ergebnis({ geprueft: 1, mails: 1 }), 'mails zaehlt K22, nicht T19')
|
||||||
|
assert.equal(abfragenAufAbos(), 0, 'der Nachlauf liest das Abo nicht')
|
||||||
|
assert.equal(t19().length, 1, 'T19 genau einmal')
|
||||||
|
assert.equal(k22().length, 1)
|
||||||
|
assert.deepEqual(mails().map((m) => m.subject === T19_BETREFF), [true, false], 'T19 vor K22')
|
||||||
|
assert.match(k22()[0].text, /noch vor der Einrichtung Deines Terminplaners/)
|
||||||
|
assert.match(k22()[0].text, /am 05\.10\.2026 beendet/, 'Enddatum aus disabledAt, nicht vom Folgetag')
|
||||||
|
assert.match(t19()[0].text, /cs_test_1/)
|
||||||
|
assert.match(t19()[0].text, /WO-4711/)
|
||||||
|
doc = funktion()
|
||||||
|
assert.equal(doc.zahlungMeldung, 'gemeldet')
|
||||||
|
assert.equal(doc.purchaseStatus, 'paid')
|
||||||
|
assert.equal(ms(doc.aboEndeMailAt), tag2.getTime())
|
||||||
|
assert.equal(doc.state, 'disabled', 'keine Wiederkehr: sie gilt nur fuer abo_ende')
|
||||||
|
assert.equal(doc.disabledReason, 'abo_ende_vor_einrichtung')
|
||||||
|
assert.equal(doc.enabled, false)
|
||||||
|
assert.equal(doc.disabledAt, disabledAt, 'die Frist wuchs nicht')
|
||||||
|
assert.equal(aw.docs('workorders').get('t_1').status, 'Open', 'das Ticket bleibt unberuehrt')
|
||||||
|
|
||||||
|
// Tag 3: nichts mehr
|
||||||
|
const vorher = { ...doc }
|
||||||
|
assert.deepEqual(await laufe(plus(tag2, TAG)), ergebnis({ geprueft: 1 }))
|
||||||
|
assert.deepEqual(funktion(), vorher)
|
||||||
|
assert.equal(mails().length, 2)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('R107: T19 scheitert weiter, obwohl das Abo laeuft - jeden Tag fehler 1, kein K22, kein Marker; sobald der Versand geht, T19 und K22', async () => {
|
||||||
|
legeAufbau(1)
|
||||||
|
t19Scheitert()
|
||||||
|
assert.deepEqual(await laufe(JETZT), ergebnis({ geprueft: 1, abgeschaltet: 1, fehler: 1 }))
|
||||||
|
wartung('active')
|
||||||
|
assert.deepEqual(await laufe(plus(JETZT, TAG)), ergebnis({ geprueft: 1, fehler: 1 }))
|
||||||
|
assert.deepEqual(await laufe(plus(JETZT, 2 * TAG)), ergebnis({ geprueft: 1, fehler: 1 }))
|
||||||
|
assert.equal(mails().length, 0, 'auch K22 geht nicht vor T19')
|
||||||
|
assert.ok(!funktion().zahlungMeldung)
|
||||||
|
assert.ok(!funktion().aboEndeMailAt)
|
||||||
|
assert.equal(funktion().state, 'disabled')
|
||||||
|
|
||||||
|
setMailTransportForTests(sink)
|
||||||
|
assert.deepEqual(await laufe(plus(JETZT, 3 * TAG)), ergebnis({ geprueft: 1, mails: 1 }))
|
||||||
|
assert.deepEqual(mails().map((m) => m.subject === T19_BETREFF), [true, false])
|
||||||
|
assert.equal(funktion().zahlungMeldung, 'gemeldet')
|
||||||
|
assert.deepEqual(await laufe(plus(JETZT, 4 * TAG)), ergebnis({ geprueft: 1 }))
|
||||||
|
assert.equal(mails().length, 2)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('R107: das Abo ist nicht lesbar - T19 wird trotzdem nachgeholt; ein anderes Dokument desselben Kunden bleibt fail-closed (aktiv, fehler 1)', async () => {
|
||||||
|
legeErstattungOffen(1)
|
||||||
|
legeFunktion(2, {}, { projekt: 'Zweites Projekt' }) // aktiv: braucht die Abo-Lage
|
||||||
|
aw.failNext({ collection: 'subscriptions', method: 'GET', status: 500 })
|
||||||
|
assert.deepEqual(await laufe(), ergebnis({ geprueft: 1, mails: 1, fehler: 1 }))
|
||||||
|
assert.equal(t19().length, 1)
|
||||||
|
assert.equal(k22().length, 1)
|
||||||
|
assert.equal(funktion(funktionsId(1)).zahlungMeldung, 'gemeldet')
|
||||||
|
assert.ok(funktion(funktionsId(1)).aboEndeMailAt)
|
||||||
|
assert.equal(funktion(funktionsId(2)).state, 'active', 'nie abschalten, wenn etwas nicht lesbar war')
|
||||||
|
assert.equal(mails().length, 2)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('R107: der Nachlauf liest das Abo nie - ein einzelnes Dokument kostet keine Abo-Abfrage, bei zwei Dokumenten braucht nur das erledigte die Lage', async () => {
|
||||||
|
legeErstattungOffen(1)
|
||||||
|
assert.deepEqual(await laufe(), ergebnis({ geprueft: 1, mails: 1 }))
|
||||||
|
assert.equal(abfragenAufAbos(), 0)
|
||||||
|
assert.equal(t19().length, 1)
|
||||||
|
|
||||||
|
legeErstattungOffen(2)
|
||||||
|
wartung('active')
|
||||||
|
aw.resetListCalls()
|
||||||
|
assert.deepEqual(await laufe(plus(JETZT, TAG)), ergebnis({ geprueft: 2, mails: 1 }), 'das erste Dokument ist erledigt und wird nur bewertet')
|
||||||
|
assert.equal(abfragenAufAbos(), 1, 'nur das erledigte Dokument braucht die Lage (Rueckkehr-Pruefung), der Nachlauf selbst keine')
|
||||||
|
assert.equal(t19().length, 2)
|
||||||
|
})
|
||||||
|
|
||||||
|
for (const [abo, vorbereitung] of [
|
||||||
|
['ohne Abo', () => {}],
|
||||||
|
['mit laufendem Abo', () => wartung('active')],
|
||||||
|
['mit beendetem Abo', () => wartung('canceled', { endedAt: JETZT.toISOString() })],
|
||||||
|
]) {
|
||||||
|
test(`R107: unbezahltes abo_ende_vor_einrichtung-Dokument ${abo} - nie T19, nie K22, nichts geschrieben`, async () => {
|
||||||
|
legeBestellung(1, { state: 'disabled', disabledReason: 'abo_ende_vor_einrichtung', disabledAt: plus(JETZT, -TAG).toISOString() })
|
||||||
|
vorbereitung()
|
||||||
|
const vorher = { ...funktion() }
|
||||||
|
assert.deepEqual(await laufe(JETZT), ergebnis({ geprueft: 1 }))
|
||||||
|
assert.deepEqual(await laufe(plus(JETZT, TAG)), ergebnis({ geprueft: 1 }))
|
||||||
|
assert.deepEqual(funktion(), vorher)
|
||||||
|
assert.equal(mails().length, 0)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
test('R107: bereits gemeldet - kein zweites T19, auch wenn das Abo laeuft; mit gemeldetem T19 und fehlendem K22 geht nur K22 (ohne Abo), nie T19', async () => {
|
||||||
|
legeErstattungOffen(1, ERSTATTET) // T19 gemeldet, K22 ging
|
||||||
|
wartung('active')
|
||||||
|
const vorher = { ...funktion() }
|
||||||
|
assert.deepEqual(await laufe(JETZT), ergebnis({ geprueft: 1 }))
|
||||||
|
assert.deepEqual(funktion(), vorher)
|
||||||
|
assert.equal(mails().length, 0)
|
||||||
|
|
||||||
|
// T19 gemeldet, K22 noch offen, kein Abo: der bisherige Nachlauf (Schritt 2) sendet nur K22
|
||||||
|
await deleteDocument('subscriptions', 'sub_w')
|
||||||
|
aw.seed('portalFeatures', { ...funktion(), aboEndeMailAt: null })
|
||||||
|
assert.deepEqual(await laufe(plus(JETZT, TAG)), ergebnis({ geprueft: 1, mails: 1 }))
|
||||||
|
assert.equal(t19().length, 0, 'T19 war schon gemeldet')
|
||||||
|
assert.equal(k22().length, 1)
|
||||||
|
assert.equal(funktion().zahlungMeldung, 'gemeldet')
|
||||||
|
assert.ok(funktion().aboEndeMailAt)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('R107: Mischung - ein Kunde mit offener Erstattung, aktivem Terminplaner und Bestellung; das Abo laeuft: nur die Erstattung wird gemeldet, nichts wird abgeschaltet', async () => {
|
||||||
|
legeErstattungOffen(1)
|
||||||
|
legeFunktion(2, {}, { projekt: 'Projekt Alpha' })
|
||||||
|
legeBestellung(3)
|
||||||
|
wartung('active')
|
||||||
|
assert.deepEqual(await laufe(), ergebnis({ geprueft: 3, mails: 1 }))
|
||||||
|
assert.equal(t19().length, 1)
|
||||||
|
assert.equal(funktion(funktionsId(2)).state, 'active')
|
||||||
|
assert.equal(funktion(funktionsId(3)).state, 'ordered')
|
||||||
|
assert.equal(abfragenAufAbos(), 1, 'die Lage wird fuer die uebrigen Dokumente einmal gelesen')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('R107: Mischung ohne Abo - Erstattung, aktiver Terminplaner und Bestellung: je Dokument das Richtige, T19 genau einmal', async () => {
|
||||||
|
legeErstattungOffen(1)
|
||||||
|
legeFunktion(2, {}, { projekt: 'Projekt Alpha' })
|
||||||
|
legeBestellung(3)
|
||||||
|
assert.deepEqual(await laufe(), ergebnis({ geprueft: 3, abgeschaltet: 2, mails: 2 }))
|
||||||
|
assert.equal(t19().length, 1)
|
||||||
|
assert.equal(k22().length, 2, 'K22 fuer die Erstattung (vor_einrichtung) und den aktiven Terminplaner (abo_ende)')
|
||||||
|
assert.equal(funktion(funktionsId(1)).zahlungMeldung, 'gemeldet')
|
||||||
|
assert.equal(funktion(funktionsId(2)).disabledReason, 'abo_ende')
|
||||||
|
assert.equal(funktion(funktionsId(3)).disabledReason, 'abo_ende_vor_einrichtung')
|
||||||
|
assert.ok(!funktion(funktionsId(3)).aboEndeMailAt)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('R107: das Dokument wird nur einmal je Lauf bearbeitet - schlaegt K22 nach gesendetem T19 fehl, zaehlt fehler 1 (nicht 2); der naechste Tag ohne Abo sendet K22', async () => {
|
||||||
|
legeErstattungOffen(1)
|
||||||
|
setMailTransportForTests({
|
||||||
|
sendMail: async (mail) => {
|
||||||
|
if (K22_BETREFF.test(mail.subject)) throw new Error('Verbindung abgebrochen')
|
||||||
|
return sink.sendMail(mail)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
assert.deepEqual(await laufe(JETZT), ergebnis({ geprueft: 1, fehler: 1 }))
|
||||||
|
assert.equal(t19().length, 1)
|
||||||
|
assert.equal(funktion().zahlungMeldung, 'gemeldet')
|
||||||
|
assert.ok(!funktion().aboEndeMailAt)
|
||||||
|
|
||||||
|
setMailTransportForTests(sink)
|
||||||
|
assert.deepEqual(await laufe(plus(JETZT, TAG)), ergebnis({ geprueft: 1, mails: 1 }))
|
||||||
|
assert.equal(t19().length, 1, 'kein zweites T19')
|
||||||
|
assert.equal(k22().length, 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('R107 Rennen: das Team aktiviert zwischen Liste und Sperre - das frisch gelesene Dokument ist active, kein T19, keine Mail', async () => {
|
||||||
|
legeErstattungOffen(1)
|
||||||
|
const erg = await mitEinwurf(
|
||||||
|
liste('portalFeatures'),
|
||||||
|
async () => aw.seed('portalFeatures', { ...funktion(), state: 'active', enabled: true }),
|
||||||
|
() => laufe(),
|
||||||
|
)
|
||||||
|
assert.deepEqual(erg, ergebnis({ geprueft: 1 }))
|
||||||
|
assert.equal(funktion().state, 'active')
|
||||||
|
assert.equal(mails().length, 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('R107 Rennen: der Webhook meldet T19 zwischen Liste und Sperre - kein zweites T19 durch den Abgleich; ohne Abo geht K22 ueber den bisherigen Nachlauf (Schritt 2), am Folgetag nichts', async () => {
|
||||||
|
legeErstattungOffen(1)
|
||||||
|
const erg = await mitEinwurf(
|
||||||
|
liste('portalFeatures'),
|
||||||
|
async () => aw.seed('portalFeatures', { ...funktion(), zahlungMeldung: 'gemeldet' }),
|
||||||
|
() => laufe(),
|
||||||
|
)
|
||||||
|
assert.deepEqual(erg, ergebnis({ geprueft: 1, mails: 1 }))
|
||||||
|
assert.equal(t19().length, 0, 'T19 war schon gemeldet')
|
||||||
|
assert.equal(k22().length, 1)
|
||||||
|
assert.deepEqual(await laufe(plus(JETZT, TAG)), ergebnis({ geprueft: 1 }))
|
||||||
|
assert.equal(mails().length, 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('R107 Sperre: der Nachlauf liest das Dokument unter feature-admin:<projectId> (wartet auf die Sperre), sendet aber ausserhalb jeder Sperre', async () => {
|
||||||
|
legeErstattungOffen(1)
|
||||||
|
let fertig = false
|
||||||
|
let lauf
|
||||||
|
await withLocks(['feature-admin:p_projekt01'], async () => {
|
||||||
|
lauf = gleicheAboAb(JETZT).then((erg) => {
|
||||||
|
fertig = true
|
||||||
|
return erg
|
||||||
|
})
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 250))
|
||||||
|
assert.equal(fertig, false, 'der Abgleich wartet auf die Sperre')
|
||||||
|
assert.equal(mails().length, 0)
|
||||||
|
})
|
||||||
|
// Versand: waehrenddessen darf feature-admin:<projectId> nicht gehalten sein
|
||||||
|
const sperreBeimVersand = []
|
||||||
|
setMailTransportForTests({
|
||||||
|
sendMail: async (mail) => {
|
||||||
|
const frei = await Promise.race([
|
||||||
|
withLocks(['feature-admin:p_projekt01'], async () => 'frei'),
|
||||||
|
new Promise((resolve) => setTimeout(() => resolve('gesperrt'), 150)),
|
||||||
|
])
|
||||||
|
sperreBeimVersand.push([mail.subject === T19_BETREFF ? 'T19' : 'K22', frei])
|
||||||
|
return sink.sendMail(mail)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
assert.deepEqual(await lauf, ergebnis({ geprueft: 1, mails: 1 }))
|
||||||
|
assert.deepEqual(sperreBeimVersand, [['T19', 'frei'], ['K22', 'frei']])
|
||||||
|
assert.equal(funktion().zahlungMeldung, 'gemeldet')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('R107: Lesefehler beim frischen Dokument - kein Wurf, fehler 1, keine Mail; der naechste Tag holt T19 und K22 nach', async () => {
|
||||||
|
legeErstattungOffen(1)
|
||||||
|
aw.failNext({ collection: 'portalFeatures', method: 'GET', status: 500, after: 1 }) // die Liste geht durch, das frische Lesen scheitert
|
||||||
|
assert.deepEqual(await laufe(JETZT), ergebnis({ geprueft: 1, fehler: 1 }))
|
||||||
|
assert.equal(mails().length, 0)
|
||||||
|
assert.ok(!funktion().zahlungMeldung)
|
||||||
|
assert.deepEqual(await laufe(plus(JETZT, TAG)), ergebnis({ geprueft: 1, mails: 1 }))
|
||||||
|
assert.deepEqual(mails().map((m) => m.subject === T19_BETREFF), [true, false])
|
||||||
|
})
|
||||||
|
|
||||||
|
for (const [fall, vorbereitung] of [
|
||||||
|
['Kunde ohne E-Mail-Adresse', () => aw.seed('customers', { $id: KUNDE, name: 'Silke Müller', email: '' })],
|
||||||
|
['Kunde geloescht', () => deleteDocument('customers', KUNDE)],
|
||||||
|
]) {
|
||||||
|
test(`R107: ${fall} bei laufendem Abo - T19 geht trotzdem an das Team, kein K22, Marker gesetzt, am Folgetag nichts`, async () => {
|
||||||
|
legeErstattungOffen(1)
|
||||||
|
await vorbereitung()
|
||||||
|
wartung('active')
|
||||||
|
assert.deepEqual(await laufe(), ergebnis({ geprueft: 1 }))
|
||||||
|
assert.equal(t19().length, 1, 'das Geld hat eine Spur, auch ohne Kunden')
|
||||||
|
assert.equal(k22().length, 0)
|
||||||
|
assert.equal(funktion().zahlungMeldung, 'gemeldet')
|
||||||
|
assert.ok(funktion().aboEndeMailAt, 'kein Wiederholen')
|
||||||
|
assert.deepEqual(await laufe(plus(JETZT, TAG)), ergebnis({ geprueft: 1 }))
|
||||||
|
assert.equal(mails().length, 1)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------- meldeAboEnde fuer abo_ende_vor_einrichtung (der Aufruf, den E9 nutzt)
|
// ---------------------------------------------------------------- meldeAboEnde fuer abo_ende_vor_einrichtung (der Aufruf, den E9 nutzt)
|
||||||
|
|
||||||
test('meldeAboEnde direkt: bezahltes Dokument mit Grund abo_ende_vor_einrichtung - ein T19, ein K22 vor_einrichtung, Marker, true; zweiter Aufruf nichts', async () => {
|
test('meldeAboEnde direkt: bezahltes Dokument mit Grund abo_ende_vor_einrichtung - ein T19, ein K22 vor_einrichtung, Marker, true; zweiter Aufruf nichts', async () => {
|
||||||
@@ -886,6 +1156,18 @@ test('meldeAboEnde direkt: bezahltes Dokument mit Grund abo_ende_vor_einrichtung
|
|||||||
assert.equal(ms(funktion(id).aboEndeMailAt), JETZT.getTime(), 'Marker unveraendert')
|
assert.equal(ms(funktion(id).aboEndeMailAt), JETZT.getTime(), 'Marker unveraendert')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('meldeAboEnde direkt: bezahlt, K22-Marker gesetzt, aber T19 noch nicht gemeldet - nur T19 (kein zweites K22, Marker unveraendert); danach nichts mehr', async () => {
|
||||||
|
const marker = plus(JETZT, -TAG).toISOString()
|
||||||
|
const id = legeAufbau(1, { state: 'disabled', disabledReason: 'abo_ende_vor_einrichtung', disabledAt: marker, aboEndeMailAt: marker })
|
||||||
|
assert.equal(await meldeAboEnde(funktion(id), { now: JETZT }), true, 'ohne lage: bei abo_ende_vor_einrichtung wird sie nicht gebraucht')
|
||||||
|
assert.equal(t19().length, 1)
|
||||||
|
assert.equal(k22().length, 0, 'K22 ging schon')
|
||||||
|
assert.equal(funktion(id).zahlungMeldung, 'gemeldet')
|
||||||
|
assert.equal(funktion(id).aboEndeMailAt, marker, 'Marker unveraendert')
|
||||||
|
assert.equal(await meldeAboEnde(funktion(id), { now: plus(JETZT, TAG) }), true)
|
||||||
|
assert.equal(mails().length, 1)
|
||||||
|
})
|
||||||
|
|
||||||
test('meldeAboEnde direkt: unbezahltes ordered-Dokument - weder T19 noch K22 noch Marker, true, Dokument unveraendert', async () => {
|
test('meldeAboEnde direkt: unbezahltes ordered-Dokument - weder T19 noch K22 noch Marker, true, Dokument unveraendert', async () => {
|
||||||
const id = legeBestellung(1, { state: 'disabled', disabledReason: 'abo_ende_vor_einrichtung', disabledAt: JETZT.toISOString() })
|
const id = legeBestellung(1, { state: 'disabled', disabledReason: 'abo_ende_vor_einrichtung', disabledAt: JETZT.toISOString() })
|
||||||
const vorher = { ...funktion(id) }
|
const vorher = { ...funktion(id) }
|
||||||
@@ -1134,7 +1416,7 @@ test('Nicht automatisch: team, kunde_geloescht, ohne Grund und abo_ende_vor_einr
|
|||||||
legeAbgeschaltet(1, { disabledReason: 'team', aboEndeMailAt: null, disabledAt: tag })
|
legeAbgeschaltet(1, { disabledReason: 'team', aboEndeMailAt: null, disabledAt: tag })
|
||||||
legeAbgeschaltet(2, { disabledReason: 'kunde_geloescht', aboEndeMailAt: null, disabledAt: tag })
|
legeAbgeschaltet(2, { disabledReason: 'kunde_geloescht', aboEndeMailAt: null, disabledAt: tag })
|
||||||
legeAbgeschaltet(3, { disabledReason: undefined, aboEndeMailAt: null, disabledAt: tag }) // Altbestand ohne Grund
|
legeAbgeschaltet(3, { disabledReason: undefined, aboEndeMailAt: null, disabledAt: tag }) // Altbestand ohne Grund
|
||||||
legeAufbau(4, { state: 'disabled', disabledReason: 'abo_ende_vor_einrichtung', disabledAt: tag }) // bezahlt, Erstattung laeuft
|
legeAufbau(4, { ...ERSTATTET, state: 'disabled', disabledReason: 'abo_ende_vor_einrichtung', disabledAt: tag }) // bezahlt, Erstattung gemeldet, K22 ging
|
||||||
legeDaten(4)
|
legeDaten(4)
|
||||||
wartung('active')
|
wartung('active')
|
||||||
const vorher = [1, 2, 3, 4].map((nr) => ({ ...funktion(funktionsId(nr)) }))
|
const vorher = [1, 2, 3, 4].map((nr) => ({ ...funktion(funktionsId(nr)) }))
|
||||||
|
|||||||
Reference in new issue
Block a user