Finanzen: Einstellungen und Fixkostenliste

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-01 15:42:06 +00:00
1 parent 6e43d54fe8
commit 34ff97e924
6 files changed
+923 -30

No files matched your search

+76 -7
View File
@@ -5,10 +5,20 @@ import { EnableBankingError } from '../../services/finance/enableBanking.js'
import { completeBankAuth, startBankAuth, syncBank } from '../../services/finance/bankSync.js'
import { getConnection, listTransactions, saveConnection } from '../../services/finance/bankStore.js'
import { AssignError, assignTransaction, unassignTransaction, withSuggestions } from '../../services/finance/assign.js'
import {
SettingsError,
createFixedCost,
deleteFixedCost,
getSettings,
listFixedCosts,
patchSettings,
updateFixedCost,
} from '../../services/finance/settings.js'
/**
* Finanzen im WOMS (nur Admins): Bankstatus, Abruf, Neuverbindung, Buchungen und deren Zuordnung. Die Handler sind klein und rufen
* die Dienste in server/services/finance auf; weitere Finanz-Routen kommen in dieselbe Datei.
* Finanzen im WOMS (nur Admins): Bankstatus, Abruf, Neuverbindung, Buchungen und deren Zuordnung, Einstellungen und
* Fixkosten. Die Handler sind klein und rufen die Dienste in server/services/finance auf; weitere Finanz-Routen kommen
* in dieselbe Datei.
*/
const router = Router()
@@ -39,8 +49,8 @@ function publicConnection(connection) {
}
}
/** Buchung ohne Appwrite-Metafelder ($createdAt, $permissions, ...); nur $id bleibt. */
const publicTransaction = (doc) => Object.fromEntries(Object.entries(doc).filter(([key]) => key === '$id' || !key.startsWith('$')))
/** Dokument ohne Appwrite-Metafelder ($createdAt, $permissions, ...); nur $id bleibt. */
const publicDoc = (doc) => Object.fromEntries(Object.entries(doc).filter(([key]) => key === '$id' || !key.startsWith('$')))
/** Fehlerart fuers Protokoll: nie die Meldung (sie koennte Kennungen der Bank oder von Appwrite enthalten). */
const logFailure = (what, err) => console.error(`[finanzen] ${what} fehlgeschlagen:`, err?.name || 'Error', err?.status ?? '', err?.code ?? '')
@@ -115,7 +125,7 @@ router.post('/bank/rueckkehr', async (req, res) => {
router.get('/buchungen', async (_req, res) => {
try {
// Vorschlaege kommen von Stripe: ein Ausfall dort (stripeFehler) macht die Liste nie kaputt
return res.json(await withSuggestions((await listTransactions()).map(publicTransaction)))
return res.json(await withSuggestions((await listTransactions()).map(publicDoc)))
} catch (err) {
logFailure('Buchungen laden', err)
return res.status(500).json({ error: 'Buchungen konnten nicht geladen werden.' })
@@ -148,7 +158,7 @@ router.post('/buchungen/:id/zuordnen', async (req, res) => {
const { invoiceId, payoutId, fixedCostId, category, note } = req.body || {}
try {
const buchung = await assignTransaction(req.params.id, { invoiceId, payoutId, fixedCostId, category, note })
return res.json({ buchung: publicTransaction(buchung) })
return res.json({ buchung: publicDoc(buchung) })
} catch (err) {
return sendAssignError(res, 'Buchung zuordnen', err)
}
@@ -156,10 +166,69 @@ router.post('/buchungen/:id/zuordnen', async (req, res) => {
router.delete('/buchungen/:id/zuordnung', async (req, res) => {
try {
return res.json({ buchung: publicTransaction(await unassignTransaction(req.params.id)) })
return res.json({ buchung: publicDoc(await unassignTransaction(req.params.id)) })
} catch (err) {
return sendAssignError(res, 'Zuordnung lösen', err)
}
})
/**
* Antwort auf einen Fehler bei Einstellungen und Fixkosten: SettingsError (400 ungueltige Eingabe, 404 unbekannte
* Position, mit anzeigbarer Meldung); alles andere (Appwrite) wird protokolliert und ist ein 500 ohne dessen Meldung.
*/
function sendSettingsError(res, what, err) {
if (err instanceof SettingsError) return res.status(err.status).json({ error: err.message })
logFailure(what, err)
return res.status(500).json({ error: 'Interner Fehler.' })
}
router.get('/einstellungen', async (_req, res) => {
try {
return res.json({ einstellungen: await getSettings() })
} catch (err) {
return sendSettingsError(res, 'Einstellungen laden', err)
}
})
router.patch('/einstellungen', async (req, res) => {
try {
return res.json({ einstellungen: await patchSettings(req.body) })
} catch (err) {
return sendSettingsError(res, 'Einstellungen speichern', err)
}
})
router.get('/fixkosten', async (_req, res) => {
try {
return res.json({ fixkosten: (await listFixedCosts()).map(publicDoc) })
} catch (err) {
return sendSettingsError(res, 'Fixkosten laden', err)
}
})
router.post('/fixkosten', async (req, res) => {
try {
return res.status(201).json({ position: publicDoc(await createFixedCost(req.body)) })
} catch (err) {
return sendSettingsError(res, 'Fixkosten anlegen', err)
}
})
router.patch('/fixkosten/:id', async (req, res) => {
try {
return res.json({ position: publicDoc(await updateFixedCost(req.params.id, req.body)) })
} catch (err) {
return sendSettingsError(res, 'Fixkosten ändern', err)
}
})
router.delete('/fixkosten/:id', async (req, res) => {
try {
await deleteFixedCost(req.params.id)
return res.json({ ok: true })
} catch (err) {
return sendSettingsError(res, 'Fixkosten löschen', err)
}
})
export default router