Buchungskalender: Mails an Gäste, Vermieter und Team

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
knsoandClaude Opus 5.5 committed 2026-10-07 14:12:47 +00:00
1 parent 118be44847
commit e15486263f
18 files changed
+1095 -6

No files matched your search

+37 -1
View File
@@ -10,7 +10,7 @@ import { TERMINE_DATETIMES } from '../server/services/termine/schema.js'
// Produktion. Der Mailtransport ist ein Fake, der Sendeversuche zaehlt und gezielt Fehler wirft (nodemailer-Fehler
// tragen `code` und SMTP-`responseCode`). Alle Zeiten sind feste Parameter.
const aw = await startAppwriteMock({
schema: { termineOutbox: schemaKeys('termineOutbox') },
schema: { termineOutbox: schemaKeys('termineOutbox'), ukRechnungen: schemaKeys('ukRechnungen') },
datetimes: TERMINE_DATETIMES,
defaultLimit: 25,
})
@@ -1290,3 +1290,39 @@ test('einreihen: optionaler Anhang-Verweis wird gespeichert (Senden unverändert
const attribut = (await import('../server/services/termine/schema.js')).TERMINE_COLLECTIONS.termineOutbox.attributes.find((a) => a.key === 'anhang')
assert.deepEqual(attribut, { key: 'anhang', type: 'string', size: 500, required: false })
})
// Buchungskalender (Task 20): anhang {art:'ukRechnung', rechnungId} -> Rechnungs-PDF als Anhang
const RE_INHALT = { aussteller: { firma: 'Haus' }, empfaenger: { name: 'Eva' }, unterkunft: { name: 'Wohnung' }, von: '2026-11-12', bis: '2026-11-14', naechte: 2, personen: 2, posten: [{ text: 'Übernachtung', menge: 2, einzelCent: 10000, ustProzent: 0 }], summen: { bruttoCent: 20000, nettoCent: 20000, ust: [] }, steuerart: 'kleinunternehmer' }
const seedRechnung = (extra = {}) => aw.seed('ukRechnungen', { $id: 'r1', featureId: 'f_feature01', anfrageId: 'a1', art: 'rechnung', nummer: 'RE-2026-0001', datum: '2026-11-01', bruttoCent: 20000, inhalt: JSON.stringify(RE_INHALT), ...extra })
test('Anhang ukRechnung: gesendete Mail hat Rechnung-<nr>.pdf (beginnt mit %PDF); Stornorechnung heißt Stornorechnung-<nr>.pdf', async () => {
seedRechnung()
seedRechnung({ $id: 'r2', art: 'storno', nummer: 'RE-2026-0002' })
await einreihen(eintrag({ bookingId: 'b_re', anhang: JSON.stringify({ art: 'ukRechnung', rechnungId: 'r1' }) }), JETZT)
await einreihen(eintrag({ bookingId: 'b_st', anhang: JSON.stringify({ art: 'ukRechnung', rechnungId: 'r2' }) }), JETZT)
await einreihen(eintrag({ bookingId: 'b_ohne' }), JETZT)
await warteAufVersand()
const namen = tr.senke.sent.map((m) => (m.attachments ?? []).map((a) => a.filename))
assert.deepEqual(namen.map((n) => n.join()).sort(), ['', 'Rechnung-RE-2026-0001.pdf', 'Stornorechnung-RE-2026-0002.pdf'])
const mit = tr.senke.sent.find((m) => m.attachments?.length)
assert.equal(Buffer.from(mit.attachments[0].content, 'base64').subarray(0, 4).toString(), '%PDF')
assert.equal(mit.attachments[0].contentType, 'application/pdf')
})
test('Anhang nicht erzeugbar: Eintrag bleibt wartet (Wiederholung), nach 3 Fehlversuchen geht die Mail ohne Anhang raus', async () => {
const doc = await einreihen(eintrag({ bookingId: 'b_fehlt', art: 'uk-G19', anhang: JSON.stringify({ art: 'ukRechnung', rechnungId: 'gibtsnicht' }) }), JETZT)
await warteAufVersand()
let d = await lies(doc.$id)
assert.equal(d.status, 'wartet')
assert.equal(d.versuche, 1)
assert.equal(tr.aufrufe, 0)
let jetzt = JETZT
for (const warte of [2 * MIN, 6 * MIN, 31 * MIN]) {
jetzt = plus(warte, jetzt)
await sendeEintrag(doc.$id, jetzt)
}
d = await lies(doc.$id)
assert.equal(d.status, 'gesendet')
assert.equal(tr.senke.sent.length, 1)
assert.equal(tr.senke.sent[0].attachments?.length ?? 0, 0)
})