initial commit

This commit is contained in:
Matthias
2026-06-15 11:33:23 +02:00
commit fc0a6fb975
155 changed files with 24526 additions and 0 deletions

View File

@@ -0,0 +1,98 @@
import { categorize } from "./categorize";
import { resolveAssignedAndEffective, type SalaryShiftSettings } from "./month";
import { roundEur } from "./categorize";
export type ComdirectTransaction = {
bookingStatus?: string;
bookingDate?: string;
valueDate?: string;
amount?: { value?: string };
remittanceInfo?: string;
remitter?: { holderName?: string };
creditor?: { holderName?: string };
deptor?: { holderName?: string };
transactionType?: { text?: string };
reference?: string;
};
export function parseRemittanceInfo(remittanceInfo?: string): string {
if (!remittanceInfo) return "";
const blocks = remittanceInfo.split(/\s(?=\d{2})/);
return blocks
.map((block) => block.replace(/^\d{2}/, "").trim())
.filter(Boolean)
.join(" ")
.trim();
}
export type MappedComdirectRow = {
bookingDate?: string;
valueDate?: string;
description: string;
counterparty?: string;
amount: number;
vorgang?: string;
isPending: boolean;
rawText?: string;
externalRef?: string;
categoryName: string;
assignedMonth?: string;
effectiveMonth?: string;
};
export function mapComdirectTransaction(
tx: ComdirectTransaction,
ownNames: string[],
salaryShift: SalaryShiftSettings,
): MappedComdirectRow {
const isPending = tx.bookingStatus === "NOTBOOKED";
const amount = roundEur(Number(tx.amount?.value ?? 0));
const rawText = parseRemittanceInfo(tx.remittanceInfo);
const counterparty =
tx.remitter?.holderName ?? tx.creditor?.holderName ?? tx.deptor?.holderName;
const vorgang = tx.transactionType?.text;
const description = counterparty ?? rawText.slice(0, 80) ?? "Umsatz";
const categoryName = categorize(rawText, amount, vorgang ?? "", ownNames);
const { assignedMonth, effectiveMonth } = resolveAssignedAndEffective(
isPending ? undefined : tx.bookingDate,
amount,
categoryName,
salaryShift,
);
return {
bookingDate: isPending ? undefined : tx.bookingDate,
valueDate: tx.valueDate,
description,
counterparty,
amount,
vorgang,
isPending,
rawText: rawText || undefined,
externalRef: tx.reference,
categoryName,
assignedMonth,
effectiveMonth,
};
}
export async function computeDedupHash(input: {
accountId?: string;
bookingDate?: string;
amount: number;
description: string;
vorgang?: string;
}): Promise<string> {
const payload = [
input.accountId ?? "",
input.bookingDate ?? "pending",
input.amount.toFixed(2),
input.description.trim().toLowerCase(),
(input.vorgang ?? "").trim().toLowerCase(),
].join("|");
const data = new TextEncoder().encode(payload);
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
return Array.from(new Uint8Array(hashBuffer))
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
}