Fix FinTS transaction deduplication
This commit is contained in:
65
convex/bank/fintsMap.test.ts
Normal file
65
convex/bank/fintsMap.test.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { mapFinTsTransaction } from "./fintsMap";
|
||||
|
||||
const salaryShift = {
|
||||
enabled: true,
|
||||
categoryNames: ["Gehalt & Besoldung"],
|
||||
dayThreshold: 25,
|
||||
};
|
||||
|
||||
function baseTransaction(overrides: Partial<Parameters<typeof mapFinTsTransaction>[0]> = {}) {
|
||||
return {
|
||||
valueDate: new Date(2026, 5, 22),
|
||||
entryDate: new Date(2026, 5, 22),
|
||||
amount: -12.34,
|
||||
transactionType: "NMSC",
|
||||
bankReference: "",
|
||||
customerReference: "",
|
||||
bookingText: "Kartenzahlung",
|
||||
purpose: "REWE SAGT DANKE",
|
||||
remoteName: "REWE",
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe("mapFinTsTransaction", () => {
|
||||
test("does not expose MT940 placeholder references as external references", () => {
|
||||
const placeholders = [
|
||||
"NONREF",
|
||||
"NOTPROVIDED",
|
||||
"NOT PROVIDED",
|
||||
"N/A",
|
||||
"POS 13",
|
||||
"POS-42",
|
||||
"POS_1155",
|
||||
"-",
|
||||
"",
|
||||
];
|
||||
|
||||
for (const placeholder of placeholders) {
|
||||
const mapped = mapFinTsTransaction(
|
||||
baseTransaction({
|
||||
bankReference: placeholder,
|
||||
customerReference: placeholder,
|
||||
}),
|
||||
[],
|
||||
salaryShift,
|
||||
);
|
||||
|
||||
expect(mapped.externalRef).toBeUndefined();
|
||||
}
|
||||
});
|
||||
|
||||
test("keeps real FinTS references for provider-level duplicate detection", () => {
|
||||
const mapped = mapFinTsTransaction(
|
||||
baseTransaction({
|
||||
bankReference: "5J2C21XL0470L56V/39761",
|
||||
customerReference: "NONREF",
|
||||
}),
|
||||
[],
|
||||
salaryShift,
|
||||
);
|
||||
|
||||
expect(mapped.externalRef).toBe("5J2C21XL0470L56V/39761");
|
||||
});
|
||||
});
|
||||
@@ -14,6 +14,54 @@ export type FintsStatementTransaction = {
|
||||
customerReference?: string;
|
||||
};
|
||||
|
||||
const FINTS_EXTERNAL_REF_PLACEHOLDERS = new Set([
|
||||
"0",
|
||||
"00",
|
||||
"000",
|
||||
"0000",
|
||||
"00000",
|
||||
"000000",
|
||||
"0000000",
|
||||
"00000000",
|
||||
"000000000",
|
||||
"0000000000",
|
||||
"KEINE",
|
||||
"N/A",
|
||||
"NA",
|
||||
"NONE",
|
||||
"NONREF",
|
||||
"NOREF",
|
||||
"NOTPROVIDED",
|
||||
"NULL",
|
||||
"UNBEKANNT",
|
||||
]);
|
||||
|
||||
function normalizeFinTsExternalRefValue(value?: string): string | undefined {
|
||||
const trimmed = value?.trim();
|
||||
if (!trimmed) return undefined;
|
||||
|
||||
const compact = trimmed.replace(/[\s_-]+/g, "").toUpperCase();
|
||||
if (
|
||||
!compact ||
|
||||
FINTS_EXTERNAL_REF_PLACEHOLDERS.has(compact) ||
|
||||
/^POS\d+$/.test(compact)
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
export function normalizeFinTsExternalRef(
|
||||
bankReference?: string,
|
||||
customerReference?: string,
|
||||
): string | undefined {
|
||||
return (
|
||||
normalizeFinTsExternalRefValue(bankReference) ??
|
||||
normalizeFinTsExternalRefValue(customerReference)
|
||||
);
|
||||
}
|
||||
|
||||
export function formatFinTsDate(date: Date): string {
|
||||
const y = date.getFullYear();
|
||||
const m = String(date.getMonth() + 1).padStart(2, "0");
|
||||
@@ -55,7 +103,7 @@ export function mapFinTsTransaction(
|
||||
vorgang,
|
||||
isPending: false,
|
||||
rawText: rawText || undefined,
|
||||
externalRef: tx.bankReference || tx.customerReference || undefined,
|
||||
externalRef: normalizeFinTsExternalRef(tx.bankReference, tx.customerReference),
|
||||
categoryName,
|
||||
assignedMonth,
|
||||
effectiveMonth,
|
||||
|
||||
Reference in New Issue
Block a user