Fix FinTS transaction deduplication

This commit is contained in:
Matthias
2026-06-23 12:37:41 +02:00
parent 993616c2f9
commit 6e687dc9b5
8 changed files with 451 additions and 2 deletions

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

View File

@@ -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,