fix: bank sync fehler sichtbar machen
All checks were successful
Build and Push Docker Images / build-backend (push) Successful in 26s
Build and Push Docker Images / build-frontend (push) Successful in 1m15s
Build and Push Docker Images / build-website (push) Successful in 19s
Build and Push Docker Images / build-central-services-api (push) Successful in 16s
Build and Push Docker Images / build-central-services-admin (push) Successful in 17s
Build and Push Docker Images / build-docs (push) Successful in 16s

This commit is contained in:
2026-08-05 14:12:16 +02:00
parent 8aee8e7882
commit 4e533a47c9
3 changed files with 76 additions and 67 deletions

View File

@@ -1,8 +1,6 @@
// /services/bankStatementService.ts // /services/bankStatementService.ts
import axios from "axios" import axios from "axios"
import { centralServicesClient } from "../push-server.client" import { centralServicesClient } from "../push-server.client"
import dayjs from "dayjs"
import utc from "dayjs/plugin/utc.js"
import {secrets} from "../../utils/secrets" import {secrets} from "../../utils/secrets"
import {FastifyInstance} from "fastify" import {FastifyInstance} from "fastify"
@@ -15,16 +13,8 @@ import {
import { import {
eq, eq,
and, and,
isNull,
} from "drizzle-orm" } from "drizzle-orm"
dayjs.extend(utc)
interface BalanceAmount {
amount: string
currency: string
}
interface BookedTransaction { interface BookedTransaction {
bookingDate: string bookingDate: string
valueDate: string valueDate: string
@@ -49,6 +39,13 @@ interface TransactionsResponse {
} }
} }
export interface BankStatementSyncResult {
accountsFound: number
accountsSynced: number
transactionsImported: number
errors: Array<{ accountId: string; message: string }>
}
const normalizeDate = (val: any) => { const normalizeDate = (val: any) => {
if (!val) return null if (!val) return null
const d = new Date(val) const d = new Date(val)
@@ -81,7 +78,7 @@ export function bankStatementService(server: FastifyInstance) {
// ----------------------------------------------- // -----------------------------------------------
// ✔ Salden laden // ✔ Salden laden
// ----------------------------------------------- // -----------------------------------------------
const getBalanceData = async (accountId: string): Promise<any | false> => { const getBalanceData = async (accountId: string): Promise<any> => {
try { try {
if (useCentralBanking) return await centralServicesClient.getBankingBalances(accountId) if (useCentralBanking) return await centralServicesClient.getBankingBalances(accountId)
const {data} = await axios.get( const {data} = await axios.get(
@@ -109,7 +106,7 @@ export function bankStatementService(server: FastifyInstance) {
.where(eq(bankaccounts.accountId, accountId)) .where(eq(bankaccounts.accountId, accountId))
} }
return false throw err
} }
} }
@@ -135,15 +132,14 @@ export function bankStatementService(server: FastifyInstance) {
return data.transactions.booked return data.transactions.booked
} catch (err: any) { } catch (err: any) {
server.log.error(err.response?.data ?? err) server.log.error(err.response?.data ?? err)
return null throw err
} }
} }
// ----------------------------------------------- // -----------------------------------------------
// ✔ Haupt-Sync-Prozess // ✔ Haupt-Sync-Prozess
// ----------------------------------------------- // -----------------------------------------------
const syncAccounts = async (tenantId:number) => { const syncAccounts = async (tenantId:number): Promise<BankStatementSyncResult> => {
try {
console.log("Starting account sync…") console.log("Starting account sync…")
// 🟦 DB: Aktive Accounts // 🟦 DB: Aktive Accounts
@@ -152,37 +148,45 @@ export function bankStatementService(server: FastifyInstance) {
.from(bankaccounts) .from(bankaccounts)
.where(and(eq(bankaccounts.expired, false),eq(bankaccounts.tenant, tenantId))) .where(and(eq(bankaccounts.expired, false),eq(bankaccounts.tenant, tenantId)))
if (!accounts.length) return const result: BankStatementSyncResult = {
accountsFound: accounts.length,
accountsSynced: 0,
transactionsImported: 0,
errors: [],
}
if (!accounts.length) return result
const allNewTransactions: any[] = [] const allNewTransactions: any[] = []
for (const account of accounts) { for (const account of accounts) {
try {
// --------------------------- // ---------------------------
// 1. BALANCE SYNC // 1. BALANCE SYNC
// --------------------------- // ---------------------------
const balData = await getBalanceData(account.accountId) const balData = await getBalanceData(account.accountId)
if (balData === false) break
if (balData) { if (balData) {
const closing = balData.balances.find( const closing = balData.balances.find(
(i: any) => i.balanceType === "closingBooked" (i: any) => i.balanceType === "closingBooked"
) )
if (closing?.balanceAmount?.amount !== undefined) {
const bookedBal = Number(closing.balanceAmount.amount) const bookedBal = Number(closing.balanceAmount.amount)
if (Number.isFinite(bookedBal)) {
await server.db await server.db
.update(bankaccounts) .update(bankaccounts)
.set({balance: bookedBal}) .set({balance: bookedBal})
.where(eq(bankaccounts.id, account.id)) .where(eq(bankaccounts.id, account.id))
} }
}
}
// --------------------------- // ---------------------------
// 2. TRANSACTIONS // 2. TRANSACTIONS
// --------------------------- // ---------------------------
let transactions = await getTransactionData(account.accountId) let transactions = await getTransactionData(account.accountId)
if (!transactions) continue
//@ts-ignore //@ts-ignore
transactions = transactions.map((item) => ({ transactions = transactions.map((item) => ({
@@ -217,6 +221,17 @@ export function bankStatementService(server: FastifyInstance) {
) )
allNewTransactions.push(...filtered) allNewTransactions.push(...filtered)
await server.db
.update(bankaccounts)
.set({syncedAt: new Date()})
.where(eq(bankaccounts.id, account.id))
result.accountsSynced++
} catch (error: any) {
const message = error?.message || String(error)
result.errors.push({accountId: account.accountId, message})
server.log.error({err: error, accountId: account.accountId}, "Bankkonto konnte nicht synchronisiert werden")
}
} }
// --------------------------- // ---------------------------
@@ -224,38 +239,19 @@ export function bankStatementService(server: FastifyInstance) {
// --------------------------- // ---------------------------
if (allNewTransactions.length > 0) { if (allNewTransactions.length > 0) {
await server.db.insert(bankstatements).values(allNewTransactions) await server.db.insert(bankstatements).values(allNewTransactions)
result.transactionsImported = allNewTransactions.length
const affectedAccounts = [
...new Set(allNewTransactions.map((t) => t.account)),
]
const normalizeDate = (val: any) => {
if (!val) return null
const d = new Date(val)
return isNaN(d.getTime()) ? null : d
}
for (const accId of affectedAccounts) {
await server.db
.update(bankaccounts)
//@ts-ignore
.set({syncedAt: normalizeDate(dayjs())})
.where(eq(bankaccounts.id, accId))
}
} }
console.log("Bank statement sync completed.") console.log("Bank statement sync completed.")
} catch (error) { return result
console.error(error)
}
} }
return { return {
run: async (tenant) => { run: async (tenant) => {
await getToken() await getToken()
await syncAccounts(tenant) const result = await syncAccounts(tenant)
console.log("Service: Bankstatement sync finished") console.log("Service: Bankstatement sync finished")
return result
} }
} }
} }

View File

@@ -308,7 +308,16 @@ export default async function functionRoutes(server: FastifyInstance) {
}) })
server.post('/functions/services/bankstatementsync', async (req, reply) => { server.post('/functions/services/bankstatementsync', async (req, reply) => {
await server.services.bankStatements.run(req.user.tenant_id); const result = await server.services.bankStatements.run(req.user.tenant_id);
if (result.errors.length > 0) {
const firstError = result.errors[0]?.message
return reply.code(502).send({
error: 'bank_sync_failed',
message: `${result.errors.length} von ${result.accountsFound} Bankkonten konnten nicht synchronisiert werden.${firstError ? ` Ursache: ${firstError}` : ''}`,
...result,
})
}
return result
}) })
server.post('/functions/services/prepareincominginvoices', async (req, reply) => { server.post('/functions/services/prepareincominginvoices', async (req, reply) => {

View File

@@ -174,11 +174,15 @@ watch([selectedPeriod, dateRange], () => {
const syncBankStatements = async () => { const syncBankStatements = async () => {
isSyncing.value = true isSyncing.value = true
try { try {
await $api('/api/functions/services/bankstatementsync', {method: 'POST'}) const result = await $api('/api/functions/services/bankstatementsync', {method: 'POST'})
toast.add({title: 'Erfolg', description: 'Bankdaten synchronisiert.', color: 'green'}) const description = result.accountsFound === 0
? 'Es sind keine aktiven Bankkonten eingerichtet.'
: `${result.accountsSynced} Konto/Konten synchronisiert, ${result.transactionsImported} neue Umsätze importiert.`
toast.add({title: 'Bankdaten synchronisiert', description, color: 'green'})
await setupPage() await setupPage()
} catch (error) { } catch (error) {
toast.add({title: 'Fehler', description: 'Fehler beim Abruf.', color: 'red'}) const description = error?.data?.message || error?.message || 'Fehler beim Abruf.'
toast.add({title: 'Bank-Synchronisierung fehlgeschlagen', description, color: 'red'})
} finally { } finally {
isSyncing.value = false isSyncing.value = false
} }