Abo-Angebote: Mail-Status in der Liste und E-Mail erneut senden
Bisher sah man nirgends, ob die Angebots-Mail rausging, Fehler landeten nicht im Log, und senden ließ sie sich nur beim Anlegen. - Angebotsliste: Kunden-Adresse unter dem Namen, im Status "E-Mail gesendet <Zeitpunkt>" bzw. "E-Mail nicht verschickt" / "Keine E-Mail-Adresse hinterlegt" - Knopf "E-Mail senden" / "Erneut senden" mit Rückfrage (Adresse, letzter Versand), solange das Angebot offen und gültig ist; bei laufendem Abo, solange die erste rabattierte Abbuchung noch bevorsteht (die Mail nennt sie). POST /api/portal-admin/offers/:id/notify setzt notifiedAt. - Server-Log: "[offers] Angebots-Mail <Ref> vom Mailserver angenommen: <Message-ID> 250 … queued as …" bzw. "… fehlgeschlagen: <Grund>" - Lehnt der Mailserver nur den Kunden ab (die BCC-Kopie ans Team geht trotzdem raus), gilt die Mail als nicht verschickt - sendCustomerMail gibt die nodemailer-Info zurück Tests: npm test (40), Server-Durchlauf gegen Appwrite-Mock, SMTP-Sink und Stripe-Double (28 Prüfungen), Oberfläche (18 Prüfungen). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
81119c19bc
commit
32e441c8bc
5 files changed
+161
-18
No files matched your search
@@ -0,0 +1,50 @@
|
||||
import { test } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { adminOfferView } from '../server/services/offers.js'
|
||||
|
||||
const DAY = 86400000
|
||||
const iso = (ms) => new Date(ms).toISOString()
|
||||
const ymd = (ms) => iso(ms).slice(0, 10)
|
||||
const now = Date.now()
|
||||
|
||||
const offer = (fields = {}) => ({
|
||||
$id: 'offer-1', $createdAt: iso(now - DAY), status: 'offen', scope: 'neu', months: 2,
|
||||
monthlyPriceCents: 2500, regularPriceCents: 5000, customerId: 'c1',
|
||||
validUntil: `${ymd(now + 10 * DAY)}T00:00:00.000Z`, notifiedAt: null, ...fields,
|
||||
})
|
||||
const customer = { name: 'Martin', email: 'martin@example.invalid' }
|
||||
|
||||
test('Offenes, gültiges Angebot: Mail kann (erneut) raus, Adresse steht in der Admin-Ansicht', () => {
|
||||
const v = adminOfferView(offer(), customer)
|
||||
assert.equal(v.mailSendable, true)
|
||||
assert.equal(v.customerEmail, 'martin@example.invalid')
|
||||
assert.equal(v.notifiedAt, null)
|
||||
assert.equal(adminOfferView(offer({ notifiedAt: iso(now) }), customer).mailSendable, true)
|
||||
})
|
||||
|
||||
test('Keine Mail bei abgelaufenem, zurückgezogenem oder eingelöstem Angebot und ohne Adresse', () => {
|
||||
assert.equal(adminOfferView(offer({ validUntil: `${ymd(now - 2 * DAY)}T00:00:00.000Z` }), customer).mailSendable, false)
|
||||
assert.equal(adminOfferView(offer({ status: 'zurueckgezogen' }), customer).mailSendable, false)
|
||||
const sub = { startedAt: iso(now - DAY), trialEnd: null }
|
||||
assert.equal(adminOfferView(offer({ status: 'aktiv', appliedAt: iso(now - DAY + 5000) }), customer, sub).mailSendable, false)
|
||||
const v = adminOfferView(offer(), { name: 'Ohne Mail', email: '' })
|
||||
assert.equal(v.mailSendable, false)
|
||||
assert.equal(v.customerEmail, '')
|
||||
})
|
||||
|
||||
test('Laufendes Abo: Mail nur, solange die erste rabattierte Abbuchung bevorsteht', () => {
|
||||
const future = adminOfferView(
|
||||
offer({ status: 'aktiv', scope: 'laufend', appliedAt: iso(now - 3600000) }),
|
||||
customer,
|
||||
{ startedAt: iso(now - 10 * DAY), trialEnd: null }
|
||||
)
|
||||
assert.equal(future.mailSendable, true)
|
||||
const past = adminOfferView(
|
||||
offer({ status: 'aktiv', scope: 'laufend', appliedAt: iso(now - 40 * DAY) }),
|
||||
customer,
|
||||
{ startedAt: iso(now - 50 * DAY), trialEnd: null }
|
||||
)
|
||||
assert.equal(past.mailSendable, false)
|
||||
// Abo-Dokument fehlt: Termin unbekannt, also keine Mail
|
||||
assert.equal(adminOfferView(offer({ status: 'aktiv', scope: 'laufend', appliedAt: iso(now) }), customer, null).mailSendable, false)
|
||||
})
|
||||
Reference in new issue
Block a user