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 { // 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>, ): Promise { 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 { 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(); 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(); // 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, }; }, };