99 lines
2.8 KiB
TypeScript
99 lines
2.8 KiB
TypeScript
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("");
|
|
}
|