AB-Kern: Admin-Routen zum Senden, Erinnern, für neue Fassungen, zum Stornieren und zum Zuordnen eines Projekts

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-07 14:16:09 +00:00
1 parent 7d3114d7a3
commit e680ea3266
2 files changed
+130

No files matched your search

+48
View File
@@ -94,4 +94,52 @@ router.get('/:id/pdf', async (req, res) => {
}
})
/** Antwort der Aktionen, die eine Mail auslösen: { auftrag, mailFehler }. */
const mitMail = ({ auftrag, mailFehler }) => ({ auftrag: auftragFuerTeam(auftrag), mailFehler })
router.post('/:id/senden', async (req, res) => {
try {
await ladeMitSchalter(req.params.id)
return res.json(mitMail(await sendeAuftrag(req.params.id, { von: vonAdmin(req) })))
} catch (err) {
return sendeFehler(res, 'Auftragsbestätigung senden', err)
}
})
router.post('/:id/erinnern', async (req, res) => {
try {
await ladeMitSchalter(req.params.id)
return res.json(mitMail(await erinnereAuftrag(req.params.id)))
} catch (err) {
return sendeFehler(res, 'Auftragsbestätigung erinnern', err)
}
})
router.post('/:id/neue-fassung', async (req, res) => {
try {
await ladeMitSchalter(req.params.id)
return res.json({ auftrag: auftragFuerTeam(await neueFassung(req.params.id, { von: vonAdmin(req) })) })
} catch (err) {
return sendeFehler(res, 'Neue Fassung anlegen', err)
}
})
router.post('/:id/stornieren', async (req, res) => {
try {
await ladeMitSchalter(req.params.id)
return res.json({ auftrag: auftragFuerTeam(await storniereAuftrag(req.params.id)) })
} catch (err) {
return sendeFehler(res, 'Auftragsbestätigung stornieren', err)
}
})
router.post('/:id/projekt', async (req, res) => {
try {
await ladeMitSchalter(req.params.id)
return res.json({ auftrag: auftragFuerTeam(await ordneProjektZu(req.params.id, req.body?.projektId)) })
} catch (err) {
return sendeFehler(res, 'Projekt zuordnen', err)
}
})
export default router
+82
View File
@@ -125,3 +125,85 @@ test('Unerwartete Fehler: 500 mit fester Meldung, nichts von Appwrite gelangt an
const r = await api('GET', `/${a.id}`)
assert.deepEqual([r.status, r.body], [500, { error: 'Interner Fehler.' }])
})
test('POST senden: 200 ohne Token und Dateipfad; Kundendaten-, PDF- und Statusfehler mit festen Meldungen; Mail-Fehler als mailFehler: true', async () => {
mock.method(console, 'error', () => {})
const a = await entwurfAnlegen()
aw.seed(config.collections.customers, kundeDoc({ email: '' }))
let r = await api('POST', `/${a.id}/senden`)
assert.deepEqual([r.status, r.body], [400, { error: MELDUNG.keineMail }])
aw.seed(config.collections.customers, kundeDoc({ street: '' }))
r = await api('POST', `/${a.id}/senden`)
assert.deepEqual([r.status, r.body], [409, { error: MELDUNG.adresse }])
aw.seed(config.collections.customers, kundeDoc())
kontext = baueDeps({ pdfFehler: new Error('pdfkit') })
setzeDepsFuerTests(kontext.deps)
r = await api('POST', `/${a.id}/senden`)
assert.deepEqual([r.status, r.body], [500, { error: MELDUNG.pdf }])
kontext = baueDeps({ mailFehler: new Error('smtp') })
setzeDepsFuerTests(kontext.deps)
r = await api('POST', `/${a.id}/senden`)
assert.equal(r.status, 200)
assert.equal(r.body.mailFehler, true)
assert.deepEqual([r.body.auftrag.status, r.body.auftrag.mailStatus, r.body.auftrag.pdfVorhanden], ['versendet', 'fehler', true])
assert.match(r.body.auftrag.nummer, /^AB-\d{4}-0001$/)
for (const feld of ['tokenHash', 'pdfDatei', 'bestaetigtPdfSha256']) assert.equal(feld in r.body.auftrag, false, feld)
r = await api('POST', `/${a.id}/senden`)
assert.deepEqual([r.status, r.body], [409, { error: MELDUNG.nurEntwurfSenden }])
})
test('POST erinnern, neue-fassung, stornieren: Antworten, Folgezustände und 409 bei falschem Status', async () => {
const a = await entwurfAnlegen()
let r = await api('POST', `/${a.id}/erinnern`)
assert.deepEqual([r.status, r.body], [409, { error: MELDUNG.keineErinnerung }])
r = await api('POST', `/${a.id}/senden`)
assert.deepEqual([r.status, r.body.mailFehler, r.body.auftrag.status], [200, false, 'versendet'])
r = await api('POST', `/${a.id}/erinnern`)
assert.deepEqual([r.status, r.body.mailFehler, r.body.auftrag.status], [200, false, 'versendet'])
assert.equal(kontext.calls.mail.at(-1).erinnerung, true)
r = await api('POST', `/${a.id}/neue-fassung`)
assert.deepEqual([r.status, r.body.auftrag.status, r.body.auftrag.ersetztId], [200, 'entwurf', a.id])
r = await api('POST', `/${a.id}/neue-fassung`)
assert.deepEqual([r.status, r.body], [409, { error: MELDUNG.schonEntwurf }])
r = await api('POST', `/${a.id}/stornieren`)
assert.deepEqual([r.status, r.body.auftrag.status], [200, 'storniert'])
r = await api('POST', `/${a.id}/stornieren`)
assert.deepEqual([r.status, r.body], [409, { error: MELDUNG.keinStorno }])
})
test('POST projekt: ordnet einer AB ohne Projekt das Projekt zu (200, AuftragTeam); 400 ohne oder mit fremdem Projekt, 404 bei unbekannter AB, 409 bei vorhandenem Projekt oder falschem Status', async () => {
const a = await entwurfAnlegen({ projektId: '', titel: 'Beratung ohne Projekt' })
await api('POST', `/${a.id}/senden`)
let r = await api('POST', `/${a.id}/projekt`, {})
assert.deepEqual([r.status, r.body], [400, { error: MELDUNG.projektFehlt }])
r = await api('POST', `/${a.id}/projekt`)
assert.deepEqual([r.status, r.body], [400, { error: MELDUNG.projektFehlt }])
r = await api('POST', `/${a.id}/projekt`, { projektId: FREMD.projekt })
assert.deepEqual([r.status, r.body], [400, { error: MELDUNG.projektFremd }])
r = await api('POST', '/a_gibtsnicht/projekt', { projektId: PROJEKT_ID })
assert.deepEqual([r.status, r.body], [404, { error: MELDUNG.auftragFehlt }])
assert.equal((await getAuftrag(a.id)).projektId, '') // die abgelehnten Aufrufe haben nichts geändert
r = await api('POST', `/${a.id}/projekt`, { projektId: PROJEKT_ID })
assert.equal(r.status, 200)
assert.deepEqual([r.body.auftrag.projektId, r.body.auftrag.zeitquelle, r.body.auftrag.status], [PROJEKT_ID, [TICKET_ID], 'versendet'])
for (const feld of ['tokenHash', 'pdfDatei', 'bestaetigtPdfSha256']) assert.equal(feld in r.body.auftrag, false, feld)
r = await api('POST', `/${a.id}/projekt`, { projektId: PROJEKT_ID })
assert.deepEqual([r.status, r.body], [409, { error: MELDUNG.schonProjekt }])
const b = await entwurfAnlegen({ projektId: '', titel: 'Zweite Beratung ohne Projekt' })
await api('POST', `/${b.id}/senden`)
await api('POST', `/${b.id}/stornieren`)
r = await api('POST', `/${b.id}/projekt`, { projektId: PROJEKT_ID })
assert.deepEqual([r.status, r.body], [409, { error: MELDUNG.keinProjekt }])
})
test('Schalter aus: auch senden, erinnern, neue-fassung, stornieren und projekt antworten 404 mit der festen Meldung', async () => {
const a = await entwurfAnlegen({ projektId: '', titel: 'Beratung ohne Projekt' })
config.auftraege.mode = 'off'
for (const aktion of ['senden', 'erinnern', 'neue-fassung', 'stornieren', 'projekt']) {
const r = await api('POST', `/${a.id}/${aktion}`, aktion === 'projekt' ? { projektId: PROJEKT_ID } : undefined)
assert.deepEqual([r.status, r.body], [404, { error: SCHALTER_AUS_MELDUNG }], aktion)
}
const danach = await getAuftrag(a.id)
assert.deepEqual([danach.status, danach.projektId], ['entwurf', ''])
assert.equal(kontext.calls.pdf.length + kontext.calls.mail.length, 0)
})