Merge auftraege/l3-ab-kern: Auftragsbestätigung Kern (Strang L3)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-07 14:20:43 +00:00
commit 095694bc44
12 files changed
+2330 -2

No files matched your search

+136 -2
View File
@@ -1,11 +1,145 @@
/**
* Gerüst von Strang L0: Auftragsbestätigungen (Admin). Mount: /api/admin/finanzen/auftraege (vor dem Finanzen-Router).
* L3 füllt: Liste, Anlegen, Ändern, Löschen, PDF, Projekt zuordnen, Senden, Erinnern, Neue Fassung, Stornieren.
* Auftragsbestätigungen (Admin), Mount: /api/admin/finanzen/auftraege (vor dem Finanzen-Router, Übersicht V5 „L3“).
* GET / (?kunde=), POST /, GET|PATCH|DELETE /:id, GET /:id/pdf, POST /:id/projekt, /:id/senden, /:id/erinnern,
* /:id/neue-fassung, /:id/stornieren. Nur dünne HTTP-Schicht: Kunde laden, Schalter prüfen, Dienst aufrufen, Fehler über
* sendeFehler. Ist der Schalter für den Kunden aus, antworten alle Routen 404 mit SCHALTER_AUS_MELDUNG.
*/
import { Router } from 'express'
import { requireAppwriteStaff } from '../../middleware/appwriteStaff.js'
import { auftraegeAktiv } from '../../services/auftraege/schalter.js'
import { SCHALTER_AUS_MELDUNG } from '../../services/auftraege/konstanten.js'
import { AuftragError, sendeFehler } from '../../services/auftraege/fehler.js'
import { auftragFuerTeam } from '../../services/auftraege/auftragSicht.js'
import { listAuftraege } from '../../services/auftraege/auftragStore.js'
import {
aendereEntwurf, erinnereAuftrag, ladeAuftrag, ladeGespeichertesPdf, ladeKunde, legeEntwurfAn, loescheEntwurf, neueFassung,
ordneProjektZu, sendeAuftrag, storniereAuftrag, vorschauPdf,
} from '../../services/auftraege/auftraege.js'
const router = Router()
router.use(requireAppwriteStaff)
async function ladeKundeMitSchalter(kundenId) {
const kunde = await ladeKunde(kundenId)
if (!auftraegeAktiv(kunde)) throw new AuftragError(404, SCHALTER_AUS_MELDUNG)
return kunde
}
async function ladeMitSchalter(id) {
const auftrag = await ladeAuftrag(id)
await ladeKundeMitSchalter(auftrag.kundenId)
return auftrag
}
const vonAdmin = (req) => req.appwriteUser?.email
router.get('/', async (req, res) => {
try {
const kundenId = req.query.kunde
if (typeof kundenId !== 'string' || kundenId === '') return res.status(400).json({ error: 'Kunde fehlt.' })
await ladeKundeMitSchalter(kundenId)
return res.json({ auftraege: (await listAuftraege({ kundenId })).map(auftragFuerTeam) })
} catch (err) {
return sendeFehler(res, 'Auftragsbestätigungen laden', err)
}
})
router.post('/', async (req, res) => {
try {
const kundenId = req.body?.kundenId
if (typeof kundenId === 'string' && kundenId !== '') await ladeKundeMitSchalter(kundenId)
return res.json({ auftrag: auftragFuerTeam(await legeEntwurfAn(req.body, { von: vonAdmin(req) })) })
} catch (err) {
return sendeFehler(res, 'Auftragsbestätigung anlegen', err)
}
})
router.get('/:id', async (req, res) => {
try {
return res.json({ auftrag: auftragFuerTeam(await ladeMitSchalter(req.params.id)) })
} catch (err) {
return sendeFehler(res, 'Auftragsbestätigung laden', err)
}
})
router.patch('/:id', async (req, res) => {
try {
await ladeMitSchalter(req.params.id)
return res.json({ auftrag: auftragFuerTeam(await aendereEntwurf(req.params.id, req.body ?? {})) })
} catch (err) {
return sendeFehler(res, 'Auftragsbestätigung ändern', err)
}
})
router.delete('/:id', async (req, res) => {
try {
await ladeMitSchalter(req.params.id)
await loescheEntwurf(req.params.id)
return res.json({ ok: true })
} catch (err) {
return sendeFehler(res, 'Auftragsbestätigung löschen', err)
}
})
router.get('/:id/pdf', async (req, res) => {
try {
const auftrag = await ladeMitSchalter(req.params.id)
const entwurf = auftrag.status === 'entwurf'
const buffer = entwurf ? await vorschauPdf(auftrag.id) : await ladeGespeichertesPdf(auftrag)
const name = entwurf ? 'Auftragsbestaetigung-Entwurf.pdf' : /^AB-\d{4}-\d{4}$/.test(auftrag.nummer) ? `${auftrag.nummer}.pdf` : 'Auftragsbestaetigung.pdf'
res.set({ 'Content-Type': 'application/pdf', 'Content-Disposition': `inline; filename="${name}"`, 'Cache-Control': 'no-store' })
return res.send(buffer)
} catch (err) {
return sendeFehler(res, 'Auftragsbestätigung-PDF laden', err)
}
})
/** 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