feat: add duplicate transaction detection and display in transactions page, enhancing user experience with visual indicators for duplicates
This commit is contained in:
@@ -41,14 +41,14 @@ export const transactionService = {
|
||||
|
||||
// Create sets for fast lookup
|
||||
const existingFitIdSet = new Set(
|
||||
existingByFitId.map((t) => `${t.accountId}-${t.fitId}`),
|
||||
existingByFitId.map((t) => `${t.accountId}-${t.fitId}`)
|
||||
);
|
||||
|
||||
// Create set for duplicates by amount + date + description
|
||||
const existingCriteriaSet = new Set(
|
||||
allExistingTransactions.map(
|
||||
(t) => `${t.accountId}-${t.date}-${t.amount}-${t.description}`,
|
||||
),
|
||||
(t) => `${t.accountId}-${t.date}-${t.amount}-${t.description}`
|
||||
)
|
||||
);
|
||||
|
||||
// Filter out duplicates based on fitId OR (amount + date + description)
|
||||
@@ -85,7 +85,7 @@ export const transactionService = {
|
||||
|
||||
async update(
|
||||
id: string,
|
||||
data: Partial<Omit<Transaction, "id">>,
|
||||
data: Partial<Omit<Transaction, "id">>
|
||||
): Promise<Transaction> {
|
||||
const updated = await prisma.transaction.update({
|
||||
where: { id },
|
||||
@@ -123,14 +123,67 @@ export const transactionService = {
|
||||
await prisma.transaction.delete({
|
||||
where: { id },
|
||||
});
|
||||
} catch (error: any) {
|
||||
if (error.code === "P2025") {
|
||||
} catch (error: unknown) {
|
||||
if (
|
||||
error &&
|
||||
typeof error === "object" &&
|
||||
"code" in error &&
|
||||
error.code === "P2025"
|
||||
) {
|
||||
throw new Error(`Transaction with id ${id} not found`);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
async getDuplicateIds(): Promise<Set<string>> {
|
||||
// Get all transactions grouped by account
|
||||
const allTransactions = await prisma.transaction.findMany({
|
||||
orderBy: [
|
||||
{ accountId: "asc" },
|
||||
{ date: "asc" },
|
||||
{ createdAt: "asc" }, // Oldest first
|
||||
],
|
||||
select: {
|
||||
id: true,
|
||||
accountId: true,
|
||||
date: true,
|
||||
amount: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Group by account for efficient processing
|
||||
const transactionsByAccount = new Map<string, typeof allTransactions>();
|
||||
for (const transaction of allTransactions) {
|
||||
if (!transactionsByAccount.has(transaction.accountId)) {
|
||||
transactionsByAccount.set(transaction.accountId, []);
|
||||
}
|
||||
transactionsByAccount.get(transaction.accountId)!.push(transaction);
|
||||
}
|
||||
|
||||
const duplicateIds = new Set<string>();
|
||||
const seenKeys = new Map<string, string>(); // key -> first transaction ID
|
||||
|
||||
// For each account, find duplicates by amount + date only
|
||||
for (const [accountId, transactions] of transactionsByAccount.entries()) {
|
||||
for (const transaction of transactions) {
|
||||
const key = `${accountId}-${transaction.date}-${transaction.amount}`;
|
||||
|
||||
if (seenKeys.has(key)) {
|
||||
// This is a duplicate - mark both the first and this one
|
||||
const firstId = seenKeys.get(key)!;
|
||||
duplicateIds.add(firstId);
|
||||
duplicateIds.add(transaction.id);
|
||||
} else {
|
||||
// First occurrence
|
||||
seenKeys.set(key, transaction.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return duplicateIds;
|
||||
},
|
||||
|
||||
async deduplicate(): Promise<{
|
||||
deletedCount: number;
|
||||
duplicatesFound: number;
|
||||
|
||||
Reference in New Issue
Block a user