AB-Kern: Stornieren versendeter und bestätigter Aufträge
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
cf87a7e1e0
commit
c791146af4
2 files changed
+51
No files matched your search
@@ -529,3 +529,18 @@ export async function neueFassung(id, { von } = {}) {
|
||||
return createAuftrag({ kundenId: alt.kundenId, ...speicherFelder(alt), ersetztId: alt.id, erstelltVon: String(von || '') })
|
||||
})
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// Stornieren
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
/** Storniert eine versendete oder bestätigte AB (ohne Mail); tokenHash, Nummer und PDF bleiben, der Link erklärt „storniert“. */
|
||||
export async function storniereAuftrag(id) {
|
||||
return withLocks(sperreAuftrag(id), async () => {
|
||||
const auftrag = await ladeAuftrag(id) // der Stand wird in der Sperre gelesen
|
||||
if (auftrag.status !== 'versendet' && auftrag.status !== 'bestaetigt') {
|
||||
throw new AuftragError(409, 'Nur versendete oder bestätigte Auftragsbestätigungen lassen sich stornieren.')
|
||||
}
|
||||
return updateAuftrag(id, { status: 'storniert', storniertAm: new Date().toISOString() })
|
||||
})
|
||||
}
|
||||
@@ -224,3 +224,39 @@ test('Senden: zeigt die ersetztId eines Entwurfs auf ihn selbst (beschädigte Da
|
||||
assert.deepEqual([danach.status, danach.nummer], ['entwurf', ''])
|
||||
assert.deepEqual(dateienIn(uploadDir), [])
|
||||
})
|
||||
|
||||
test('storniereAuftrag: versendet und bestätigt werden storniert (ohne Mail); Token, Nummer und PDF bleiben, damit der Link „storniert“ erklären kann', async () => {
|
||||
const { deps, calls } = baueDeps()
|
||||
const a = await gesendet(deps)
|
||||
const b = await storniereAuftrag(a.id)
|
||||
assert.equal(b.status, 'storniert')
|
||||
assert.ok(Math.abs(new Date(b.storniertAm).getTime() - Date.now()) < 60_000)
|
||||
assert.deepEqual([b.nummer, b.tokenHash, b.pdfDatei], [a.nummer, a.tokenHash, a.pdfDatei])
|
||||
assert.equal((await findAuftragByTokenHash(a.tokenHash)).id, a.id)
|
||||
assert.equal(calls.mail.length, 1) // nur die Mail des Sendens
|
||||
const c = await gesendet(deps)
|
||||
await updateAuftrag(c.id, { status: 'bestaetigt', bestaetigtAm: new Date().toISOString(), bestaetigtUeber: 'link' })
|
||||
assert.equal((await storniereAuftrag(c.id)).status, 'storniert')
|
||||
})
|
||||
|
||||
test('storniereAuftrag: sonst 409 (auch ein zweites Mal), unbekannt 404', async () => {
|
||||
const a = await gesendet(baueDeps().deps)
|
||||
for (const status of ['entwurf', 'abgerechnet', 'ersetzt', 'storniert', 'widerrufen']) {
|
||||
await updateAuftrag(a.id, { status })
|
||||
await abgelehnt(storniereAuftrag(a.id), 409, MELDUNG.keinStorno, status)
|
||||
}
|
||||
await abgelehnt(storniereAuftrag('a_gibtsnicht'), 404, MELDUNG.auftragFehlt)
|
||||
})
|
||||
|
||||
test('storniereAuftrag wartet auf die Sperre der AB (L5 hält sie beim Widerruf) und sieht danach den neuen Stand', async () => {
|
||||
const a = await gesendet(baueDeps().deps)
|
||||
const lauf = await unterFremderSperre(a.id, {
|
||||
aktion: () => storniereAuftrag(a.id),
|
||||
waehrend: async () => {
|
||||
assert.equal((await getAuftrag(a.id)).status, 'versendet') // noch nicht storniert
|
||||
await updateAuftrag(a.id, { status: 'widerrufen', widerrufenAm: new Date().toISOString() })
|
||||
},
|
||||
})
|
||||
assert.deepEqual([lauf.fehler?.status, lauf.fehler?.message], [409, MELDUNG.keinStorno])
|
||||
assert.equal((await getAuftrag(a.id)).status, 'widerrufen')
|
||||
})
|
||||
Reference in new issue
Block a user