149 lines
5.0 KiB
TypeScript
149 lines
5.0 KiB
TypeScript
import { action } from "../_generated/server";
|
|
import { v } from "convex/values";
|
|
import { internal } from "../_generated/api";
|
|
import type { Id } from "../_generated/dataModel";
|
|
import { getAuthUserId } from "@convex-dev/auth/server";
|
|
import { getAccountBalances, getTransactions } from "./client";
|
|
import { mapComdirectTransaction } from "../lib/comdirectMap";
|
|
|
|
export const run = action({
|
|
args: {
|
|
accountId: v.optional(v.id("accounts")),
|
|
from: v.string(),
|
|
to: v.string(),
|
|
},
|
|
returns: v.object({
|
|
importedCount: v.number(),
|
|
skippedCount: v.number(),
|
|
}),
|
|
handler: async (ctx, args): Promise<{ importedCount: number; skippedCount: number }> => {
|
|
const userId = await getAuthUserId(ctx);
|
|
if (!userId) throw new Error("Nicht angemeldet");
|
|
|
|
const clientId = process.env.COMDIRECT_CLIENT_ID;
|
|
const clientSecret = process.env.COMDIRECT_CLIENT_SECRET;
|
|
if (!clientId || !clientSecret) {
|
|
throw new Error("comdirect API-Zugangsdaten nicht konfiguriert");
|
|
}
|
|
|
|
const session = await ctx.runQuery(internal.comdirect.internal.getSession, { userId });
|
|
if (!session?.accessToken || !session.secondaryActive) {
|
|
throw new Error("comdirect-Session nicht aktiv. Bitte erneut anmelden.");
|
|
}
|
|
|
|
const accessToken = session.accessToken;
|
|
const sessionUuid = session.sessionUuid;
|
|
|
|
const balances = await getAccountBalances(accessToken, sessionUuid);
|
|
const accountIdMap = new Map<string, typeof args.accountId>();
|
|
|
|
for (const item of balances.values ?? []) {
|
|
const account = item.account as {
|
|
accountId?: string;
|
|
iban?: string;
|
|
accountType?: { text?: string };
|
|
};
|
|
const accountIdExternal = account?.accountId;
|
|
if (!accountIdExternal) continue;
|
|
const balanceValue = Number((item.balance as { value?: string })?.value ?? 0);
|
|
const convexAccountId = await ctx.runMutation(
|
|
internal.comdirect.internal.upsertAccountFromComdirect,
|
|
{
|
|
userId,
|
|
externalId: accountIdExternal,
|
|
name: account.accountType?.text ?? "comdirect Konto",
|
|
iban: account.iban,
|
|
balance: balanceValue,
|
|
},
|
|
);
|
|
accountIdMap.set(accountIdExternal, convexAccountId);
|
|
}
|
|
|
|
const settings = await ctx.runQuery(internal.settings.getInternal, { userId });
|
|
const ownNames = settings?.ownNames ?? [];
|
|
const salaryShift = settings?.salaryShift ?? {
|
|
enabled: true,
|
|
categoryNames: ["Gehalt & Besoldung"],
|
|
dayThreshold: 25,
|
|
};
|
|
|
|
const rows: Array<{
|
|
accountId?: typeof args.accountId;
|
|
categoryName: string;
|
|
bookingDate?: string;
|
|
valueDate?: string;
|
|
description: string;
|
|
counterparty?: string;
|
|
amount: number;
|
|
vorgang?: string;
|
|
isPending: boolean;
|
|
rawText?: string;
|
|
assignedMonth?: string;
|
|
effectiveMonth?: string;
|
|
externalRef?: string;
|
|
}> = [];
|
|
|
|
const targetAccounts = args.accountId
|
|
? [...accountIdMap.entries()].filter(([, id]) => id === args.accountId)
|
|
: [...accountIdMap.entries()];
|
|
|
|
for (const [externalAccountId, convexAccountId] of targetAccounts) {
|
|
for (const state of ["BOOKED", "NOTBOOKED"] as const) {
|
|
let offset = 0;
|
|
let matches = 0;
|
|
do {
|
|
const result = await getTransactions(accessToken, sessionUuid, externalAccountId, {
|
|
transactionState: state,
|
|
pagingFirst: offset,
|
|
minBookingDate: args.from,
|
|
maxBookingDate: args.to,
|
|
});
|
|
matches = result.paging.matches;
|
|
for (const tx of result.values ?? []) {
|
|
const mapped = mapComdirectTransaction(
|
|
tx as Parameters<typeof mapComdirectTransaction>[0],
|
|
ownNames,
|
|
salaryShift,
|
|
);
|
|
rows.push({
|
|
accountId: convexAccountId,
|
|
categoryName: mapped.categoryName,
|
|
bookingDate: mapped.bookingDate,
|
|
valueDate: mapped.valueDate,
|
|
description: mapped.description,
|
|
counterparty: mapped.counterparty,
|
|
amount: mapped.amount,
|
|
vorgang: mapped.vorgang,
|
|
isPending: mapped.isPending,
|
|
rawText: mapped.rawText,
|
|
assignedMonth: mapped.assignedMonth,
|
|
effectiveMonth: mapped.effectiveMonth,
|
|
externalRef: mapped.externalRef,
|
|
});
|
|
}
|
|
offset += result.values?.length ?? 0;
|
|
} while (offset < matches);
|
|
}
|
|
}
|
|
|
|
const commitResult: {
|
|
importId: Id<"imports">;
|
|
importedCount: number;
|
|
skippedCount: number;
|
|
} = await ctx.runMutation(internal.imports.commitRowsInternal, {
|
|
userId,
|
|
filename: `comdirect-sync-${args.from}-${args.to}`,
|
|
source: "comdirect-api",
|
|
accountId: args.accountId,
|
|
rows,
|
|
});
|
|
|
|
await ctx.runMutation(internal.comdirect.internal.clearSession, { userId });
|
|
|
|
return {
|
|
importedCount: commitResult.importedCount,
|
|
skippedCount: commitResult.skippedCount,
|
|
};
|
|
},
|
|
});
|