diff --git a/backend/src/modules/cron/bankstatementsync.service.ts b/backend/src/modules/cron/bankstatementsync.service.ts index cded6be..98e08ca 100644 --- a/backend/src/modules/cron/bankstatementsync.service.ts +++ b/backend/src/modules/cron/bankstatementsync.service.ts @@ -57,6 +57,16 @@ export const getBankAccountOwnerName = (account: any) => { return ownerName || null } +export const isExpiredBankingError = (error: any) => { + const values = [ + error?.response?.data?.summary, + error?.response?.data?.detail, + error?.response?.data?.message, + error?.message, + ] + return values.some((value) => typeof value === "string" && value.toLowerCase().includes("expired")) +} + export function bankStatementService(server: FastifyInstance) { let accessToken: string | null = null @@ -83,7 +93,7 @@ export function bankStatementService(server: FastifyInstance) { // ----------------------------------------------- // ✔ Salden laden // ----------------------------------------------- - const getBalanceData = async (accountId: string): Promise => { + const getBalanceData = async (accountId: string, tenantId: number): Promise => { try { if (useCentralBanking) return await centralServicesClient.getBankingBalances(accountId) const {data} = await axios.get( @@ -100,15 +110,14 @@ export function bankStatementService(server: FastifyInstance) { } catch (err: any) { server.log.error(err.response?.data ?? err) - const expired = - err.response?.data?.summary?.includes("expired") || - err.response?.data?.detail?.includes("expired") - - if (expired) { + if (isExpiredBankingError(err)) { await server.db .update(bankaccounts) .set({expired: true}) - .where(eq(bankaccounts.accountId, accountId)) + .where(and( + eq(bankaccounts.accountId, accountId), + eq(bankaccounts.tenant, tenantId), + )) } throw err @@ -203,7 +212,7 @@ export function bankStatementService(server: FastifyInstance) { // --------------------------- // 1. BALANCE SYNC // --------------------------- - const balData = await getBalanceData(account.accountId) + const balData = await getBalanceData(account.accountId, tenantId) if (balData) { const closing = balData.balances.find( diff --git a/backend/src/modules/push-server.client.ts b/backend/src/modules/push-server.client.ts index 1a0fc61..37ee7ea 100644 --- a/backend/src/modules/push-server.client.ts +++ b/backend/src/modules/push-server.client.ts @@ -86,7 +86,12 @@ async function requestPushServer(method: "GET" | "POST" | "DELETE", path: str if (!response.ok) { const message = data?.message || data?.error || `Push-Server Anfrage fehlgeschlagen (${response.status})` - throw new Error(message) + const error = Object.assign(new Error(message), { + code: data?.code || data?.error, + status: response.status, + response: { data }, + }) + throw error } return data as T diff --git a/backend/src/routes/resources/main.ts b/backend/src/routes/resources/main.ts index cf01f73..9cefe5c 100644 --- a/backend/src/routes/resources/main.ts +++ b/backend/src/routes/resources/main.ts @@ -1129,9 +1129,17 @@ export default async function resourceRoutes(server: FastifyInstance) { return reply.code(404).send({ error: "Resource not found" }) } - let data: Record = { ...body, updated_at: new Date().toISOString(), updated_by: userId } - //@ts-ignore - delete data.updatedBy; delete data.updatedAt; + let data: Record = { ...body } + delete data.updatedAt + delete data.updated_at + delete data.updatedBy + delete data.updated_by + + const updatedAt = new Date() + if (Object.prototype.hasOwnProperty.call(table, "updatedAt")) data.updatedAt = updatedAt + if (Object.prototype.hasOwnProperty.call(table, "updated_at")) data.updated_at = updatedAt + if (Object.prototype.hasOwnProperty.call(table, "updatedBy")) data.updatedBy = userId + if (Object.prototype.hasOwnProperty.call(table, "updated_by")) data.updated_by = userId if (resource === "filetags") { delete data.isSystemUsed @@ -1144,9 +1152,11 @@ export default async function resourceRoutes(server: FastifyInstance) { if (portalCustomerId) { data = { ...sanitizePortalCustomerUpdate(data), - updated_at: data.updated_at, - updated_by: data.updated_by, } + if (Object.prototype.hasOwnProperty.call(table, "updatedAt")) data.updatedAt = updatedAt + if (Object.prototype.hasOwnProperty.call(table, "updated_at")) data.updated_at = updatedAt + if (Object.prototype.hasOwnProperty.call(table, "updatedBy")) data.updatedBy = userId + if (Object.prototype.hasOwnProperty.call(table, "updated_by")) data.updated_by = userId } if (resource === "members") { @@ -1162,9 +1172,11 @@ export default async function resourceRoutes(server: FastifyInstance) { if (prepared.error) return reply.code(400).send({ error: prepared.error }) data = { ...prepared.data, - updated_at: data.updated_at, - updated_by: data.updated_by, } + if (Object.prototype.hasOwnProperty.call(table, "updatedAt")) data.updatedAt = updatedAt + if (Object.prototype.hasOwnProperty.call(table, "updated_at")) data.updated_at = updatedAt + if (Object.prototype.hasOwnProperty.call(table, "updatedBy")) data.updatedBy = userId + if (Object.prototype.hasOwnProperty.call(table, "updated_by")) data.updated_by = userId } if (resource === "costcentres") { diff --git a/backend/tests/bankStatementSyncOwnerName.test.ts b/backend/tests/bankStatementSyncOwnerName.test.ts index efc0e83..05c9417 100644 --- a/backend/tests/bankStatementSyncOwnerName.test.ts +++ b/backend/tests/bankStatementSyncOwnerName.test.ts @@ -1,7 +1,10 @@ import test from "node:test" import assert from "node:assert/strict" -import { getBankAccountOwnerName } from "../src/modules/cron/bankstatementsync.service" +import { + getBankAccountOwnerName, + isExpiredBankingError, +} from "../src/modules/cron/bankstatementsync.service" test("reads the GoCardless owner_name for a bank account", () => { assert.equal( @@ -15,3 +18,9 @@ test("does not overwrite the stored owner with an empty provider value", () => { assert.equal(getBankAccountOwnerName({}), null) assert.equal(getBankAccountOwnerName(null), null) }) + +test("recognizes expired EUA errors from direct and central banking responses", () => { + assert.equal(isExpiredBankingError({ response: { data: { detail: "EUA was valid for 90 days and it expired" } } }), true) + assert.equal(isExpiredBankingError(new Error("EUA was valid for 90 days and it expired")), true) + assert.equal(isExpiredBankingError({ response: { data: { message: "temporary provider error" } } }), false) +}) diff --git a/frontend/pages/settings/banking/index.vue b/frontend/pages/settings/banking/index.vue index 41324be..4339329 100644 --- a/frontend/pages/settings/banking/index.vue +++ b/frontend/pages/settings/banking/index.vue @@ -74,16 +74,20 @@ const addAccount = async (account) => { } const updateAccount = async (account) => { + const existingAccount = bankaccounts.value.find(i => i.iban === account.iban) + if (!existingAccount) return - let bankaccountId = bankaccounts.value.find(i => i.iban === account.iban).id - - // Fehlerbehandlung analog zu addAccount verbessert try { - const res = await useEntities("bankaccounts").update(bankaccountId, {accountId: account.id, expired: false}, true) + await useEntities("bankaccounts").update(existingAccount.id, { + accountId: account.id, + ownerName: account.owner_name, + iban: account.iban, + bankId: account.institution_id, + expired: false, + }, true) // useEntities feuert bereits einen Success-Toast - // reqData.value = null // Das würde das Modal leeren, ggf. gewünscht? Im Original war es drin. - setupPage() + await setupPage() } catch (error) { console.log(error) toast.add({title: "Es gab einen Fehler beim Aktualisieren des Accounts", color:"error"})