Merge auftraege/l2-rechnungen: Rechnungen (Strang L2)

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:36 +00:00
commit e5fc5e2c74
12 files changed
+2083 -70

No files matched your search

+26 -3
View File
@@ -1,6 +1,7 @@
import { Router } from 'express'
import { requireAppwriteStaff } from '../../middleware/appwriteStaff.js'
import { withLocks } from '../../services/keyedLock.js'
import { AuftragError } from '../../services/auftraege/fehler.js'
import { EnableBankingError } from '../../services/finance/enableBanking.js'
import { completeBankAuth, startBankAuth, syncBank } from '../../services/finance/bankSync.js'
import { getConnection, listTransactions, publicConnection, saveConnection } from '../../services/finance/bankStore.js'
@@ -21,13 +22,14 @@ import {
createProjectInvoice,
deleteDraft,
listInvoices,
rechnungsVorschau,
sendInvoiceNow,
voidInvoice,
} from '../../services/finance/stripeFinance.js'
/**
* Finanzen im WOMS (nur Admins): Übersicht, Bankstatus, Abruf, Neuverbindung, Buchungen und deren Zuordnung,
* Rechnungen (lesen, anlegen, senden, stornieren, Entwurf löschen), Einstellungen und Fixkosten. Die Handler sind klein
* Rechnungen (lesen, anlegen, Vorschau, senden, stornieren, Entwurf löschen), Einstellungen und Fixkosten. Die Handler sind klein
* und rufen die Dienste in server/services/finance auf.
*/
const router = Router()
@@ -148,7 +150,7 @@ const isStripeError = (err) => typeof err?.type === 'string' && err.type.startsW
* Fehler einen status tragen: nur die beiden eigenen Fehlerarten werden weitergegeben.
*/
function sendFinanceError(res, what, err) {
if (err instanceof AssignError || err instanceof InvoiceError) {
if (err instanceof AssignError || err instanceof InvoiceError || err instanceof AuftragError) {
// Ein 502 an einer bestehenden Rechnung (nicht gesendet, nicht lesbar) hat seine Ursache bei Stripe: nur Art, Status und Code ins Protokoll
if (err.status === 502 && err.cause) logFailure(what, err.cause)
const body = { error: err.message }
@@ -208,11 +210,22 @@ router.get('/rechnungen', async (req, res) => {
})
router.post('/rechnungen', async (req, res) => {
const { customerId, items, daysUntilDue, memo, senden, idempotencyKey } = req.body || {}
const {
customerId, art, projektId, auftragId, items, arbeitsblattIds, anzahlung, zeilentexte, leistungszeitraum,
daysUntilDue, memo, senden, idempotencyKey,
} = req.body || {}
try {
// Die Typen der Felder prueft baueRechnung (400); die Route reicht sie unveraendert durch
const rechnung = await createProjectInvoice({
customerId,
art,
projektId,
auftragId,
items,
arbeitsblattIds,
anzahlung,
zeilentexte,
leistungszeitraum,
daysUntilDue: daysUntilDue ?? DEFAULT_DAYS_UNTIL_DUE,
memo,
// Nur ein echtes Ja sendet sofort: alles andere bleibt ein Entwurf
@@ -227,6 +240,16 @@ router.post('/rechnungen', async (req, res) => {
}
})
// Vorschau: dieselben Pruefungen wie das Anlegen, aber ohne Schreibzugriff
router.post('/rechnungen/vorschau', async (req, res) => {
const { customerId, art, projektId, auftragId, items, arbeitsblattIds, anzahlung, zeilentexte, leistungszeitraum } = req.body || {}
try {
return res.json(await rechnungsVorschau({ customerId, art, projektId, auftragId, items, arbeitsblattIds, anzahlung, zeilentexte, leistungszeitraum }))
} catch (err) {
return sendFinanceError(res, 'Rechnungsvorschau', err)
}
})
router.post('/rechnungen/:id/senden', async (req, res) => {
try {
return res.json({ rechnung: await sendInvoiceNow(req.params.id) })