Files
fintrack/services/transaction.service.ts

189 lines
5.2 KiB
TypeScript

import { prisma } from "@/lib/prisma";
import type { Transaction } from "@/lib/types";
export interface CreateManyResult {
count: number;
transactions: Transaction[];
}
export const transactionService = {
async createMany(transactions: Transaction[]): Promise<CreateManyResult> {
// Get unique account IDs
const accountIds = [...new Set(transactions.map((t) => t.accountId))];
// Check for existing transactions by fitId
const existingByFitId = await prisma.transaction.findMany({
where: {
accountId: { in: accountIds },
fitId: { in: transactions.map((t) => t.fitId) },
},
select: {
accountId: true,
fitId: true,
date: true,
amount: true,
description: true,
},
});
// Get all existing transactions for these accounts to check duplicates by criteria
const allExistingTransactions = await prisma.transaction.findMany({
where: {
accountId: { in: accountIds },
},
select: {
accountId: true,
date: true,
amount: true,
description: true,
},
});
// Create sets for fast lookup
const existingFitIdSet = new Set(
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}`,
),
);
// Filter out duplicates based on fitId OR (amount + date + description)
const newTransactions = transactions.filter((t) => {
const fitIdKey = `${t.accountId}-${t.fitId}`;
const criteriaKey = `${t.accountId}-${t.date}-${t.amount}-${t.description}`;
return (
!existingFitIdSet.has(fitIdKey) && !existingCriteriaSet.has(criteriaKey)
);
});
if (newTransactions.length === 0) {
return { count: 0, transactions: [] };
}
const created = await prisma.transaction.createMany({
data: newTransactions.map((t) => ({
accountId: t.accountId,
date: t.date,
amount: t.amount,
description: t.description,
type: t.type,
categoryId: t.categoryId,
isReconciled: t.isReconciled,
fitId: t.fitId,
memo: t.memo,
checkNum: t.checkNum,
})),
});
return { count: created.count, transactions: newTransactions };
},
async update(
id: string,
data: Partial<Omit<Transaction, "id">>,
): Promise<Transaction> {
const updated = await prisma.transaction.update({
where: { id },
data: {
accountId: data.accountId,
date: data.date,
amount: data.amount,
description: data.description,
type: data.type,
categoryId: data.categoryId,
isReconciled: data.isReconciled,
fitId: data.fitId,
memo: data.memo,
checkNum: data.checkNum,
},
});
return {
id: updated.id,
accountId: updated.accountId,
date: updated.date,
amount: updated.amount,
description: updated.description,
type: updated.type as Transaction["type"],
categoryId: updated.categoryId,
isReconciled: updated.isReconciled,
fitId: updated.fitId,
memo: updated.memo ?? undefined,
checkNum: updated.checkNum ?? undefined,
};
},
async delete(id: string): Promise<void> {
await prisma.transaction.delete({
where: { id },
});
},
async deduplicate(): Promise<{
deletedCount: number;
duplicatesFound: number;
}> {
// Get all transactions grouped by account
const allTransactions = await prisma.transaction.findMany({
orderBy: [
{ accountId: "asc" },
{ date: "asc" },
{ id: "asc" }, // Keep the oldest transaction (first created)
],
select: {
id: true,
accountId: true,
date: true,
amount: true,
description: 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 duplicatesToDelete: string[] = [];
const seenKeys = new Set<string>();
// For each account, find duplicates
for (const [accountId, transactions] of transactionsByAccount.entries()) {
for (const transaction of transactions) {
const key = `${accountId}-${transaction.date}-${transaction.amount}-${transaction.description}`;
if (seenKeys.has(key)) {
// This is a duplicate, mark for deletion
duplicatesToDelete.push(transaction.id);
} else {
// First occurrence, keep it
seenKeys.add(key);
}
}
}
// Delete duplicates
if (duplicatesToDelete.length > 0) {
await prisma.transaction.deleteMany({
where: {
id: { in: duplicatesToDelete },
},
});
}
return {
deletedCount: duplicatesToDelete.length,
duplicatesFound: duplicatesToDelete.length,
};
},
};