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
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:
@@ -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,54 +132,61 @@ 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
|
||||||
const accounts = await server.db
|
const accounts = await server.db
|
||||||
.select()
|
.select()
|
||||||
.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: [],
|
||||||
|
}
|
||||||
|
|
||||||
const allNewTransactions: any[] = []
|
if (!accounts.length) return result
|
||||||
|
|
||||||
for (const account of accounts) {
|
const allNewTransactions: any[] = []
|
||||||
|
|
||||||
|
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"
|
||||||
)
|
)
|
||||||
|
|
||||||
const bookedBal = Number(closing.balanceAmount.amount)
|
if (closing?.balanceAmount?.amount !== undefined) {
|
||||||
|
const bookedBal = Number(closing.balanceAmount.amount)
|
||||||
await server.db
|
if (Number.isFinite(bookedBal)) {
|
||||||
.update(bankaccounts)
|
await server.db
|
||||||
.set({balance: bookedBal})
|
.update(bankaccounts)
|
||||||
.where(eq(bankaccounts.id, account.id))
|
.set({balance: bookedBal})
|
||||||
|
.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,45 +221,37 @@ 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")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------
|
|
||||||
// 3. NEW TRANSACTIONS → DB
|
|
||||||
// ---------------------------
|
|
||||||
if (allNewTransactions.length > 0) {
|
|
||||||
await server.db.insert(bankstatements).values(allNewTransactions)
|
|
||||||
|
|
||||||
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.")
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------
|
||||||
|
// 3. NEW TRANSACTIONS → DB
|
||||||
|
// ---------------------------
|
||||||
|
if (allNewTransactions.length > 0) {
|
||||||
|
await server.db.insert(bankstatements).values(allNewTransactions)
|
||||||
|
result.transactionsImported = allNewTransactions.length
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Bank statement sync completed.")
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user