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 { 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(""); }