Aufträge L5: Auftrag bestätigen (idempotent, unter Sperre, Frühbeginn nur bei Privatpersonen)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-07 14:14:08 +00:00
1 parent 19abac0373
commit 335ac60e54
2 files changed
+230

No files matched your search

+55
View File
@@ -0,0 +1,55 @@
/**
* Bestätigen einer Auftragsbestätigung (Übersicht V2/V4, Strang L5): idempotent, unter der Sperre `sperreAuftrag(id)`,
* Stand innerhalb der Sperre neu gelesen. K32 und T22 gehen erst nach der Freigabe der Sperre raus; Mailfehler stehen
* nur im Protokoll. Die erste Bestätigung zählt, jede weitere ändert nichts.
*/
import { withLocks } from '../keyedLock.js'
import { getAuftrag, updateAuftrag } from './auftragStore.js'
import { AuftragError } from './fehler.js'
import { sperreAuftrag } from './konstanten.js'
import { sendeBestaetigtMails } from './mails.js'
import { FRUEHBEGINN_FASSUNG } from './texte.js'
export const GESPERRT_MELDUNG = {
ersetzt: 'Diese Auftragsbestätigung wurde ersetzt.',
storniert: 'Diese Auftragsbestätigung wurde storniert.',
widerrufen: 'Diese Auftragsbestätigung wurde widerrufen.',
}
const NICHT_BESTAETIGBAR = 'Diese Auftragsbestätigung kann nicht bestätigt werden.'
const NICHT_GEFUNDEN = 'Auftragsbestätigung nicht gefunden.'
const GESPERRT_STATUS = ['entwurf', 'ersetzt', 'storniert', 'widerrufen']
/** 'offen' (bestätigbar), 'bestaetigt' (schon bestätigt) oder 'gesperrt' (Entwurf, ersetzt, storniert, widerrufen). */
export function bestaetigungsLage(auftrag) {
if (GESPERRT_STATUS.includes(auftrag.status)) return 'gesperrt'
return auftrag.bestaetigtAm || auftrag.status === 'bestaetigt' ? 'bestaetigt' : 'offen'
}
function pruefeZeitpunkt(jetzt) {
if (!(jetzt instanceof Date) || Number.isNaN(jetzt.getTime())) throw new TypeError('jetzt muss ein gültiges Date sein')
}
export async function bestaetigeAuftrag(auftrag, { ueber, fruehbeginn, jetzt = new Date() } = {}) {
// synchron vor der Sperre: der zuerst gestartete Aufruf bekommt die Sperre zuerst
if (ueber !== 'link' && ueber !== 'portal') throw new TypeError('bestaetigeAuftrag: ueber muss „link“ oder „portal“ sein')
pruefeZeitpunkt(jetzt)
const { aktuell, bestaetigtJetzt } = await withLocks(sperreAuftrag(auftrag.id), async () => {
const frisch = await getAuftrag(auftrag.id)
if (!frisch) throw new AuftragError(404, NICHT_GEFUNDEN)
const lage = bestaetigungsLage(frisch)
if (lage === 'gesperrt') throw new AuftragError(409, GESPERRT_MELDUNG[frisch.status] ?? NICHT_BESTAETIGBAR)
if (lage === 'bestaetigt') return { aktuell: frisch, bestaetigtJetzt: false }
const privat = frisch.kunde?.kundenart === 'privat'
const patch = {
bestaetigtAm: jetzt.toISOString(),
bestaetigtUeber: ueber,
bestaetigtPdfSha256: frisch.pdfSha256,
fruehbeginn: privat && fruehbeginn === true,
fruehbeginnFassung: privat ? FRUEHBEGINN_FASSUNG : '',
}
if (frisch.status === 'versendet') patch.status = 'bestaetigt'
return { aktuell: await updateAuftrag(frisch.id, patch), bestaetigtJetzt: true }
})
if (bestaetigtJetzt) await sendeBestaetigtMails(aktuell)
return aktuell
}