initial commit
This commit is contained in:
98
convex/lib/comdirectMap.ts
Normal file
98
convex/lib/comdirectMap.ts
Normal 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("");
|
||||
}
|
||||
Reference in New Issue
Block a user