Files
finance-app/src/app/api/transactions/bulk/route.ts
jerick 60dabb6264 Recompute account balance after bulk transaction delete
After deleting transactions, recalculate currentBalanceCents for each
affected account so the account card and net worth dashboard stay accurate.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 16:24:59 -04:00

87 lines
2.7 KiB
TypeScript

import { NextResponse } from 'next/server'
import { z } from 'zod'
import { auth } from '@/lib/auth'
import { prisma } from '@/lib/prisma'
const bulkSchema = z.discriminatedUnion('action', [
z.object({
action: z.literal('delete'),
ids: z.array(z.string()).min(1),
}),
z.object({
action: z.literal('assignBudget'),
ids: z.array(z.string()).min(1),
budgetId: z.string().nullable(),
}),
z.object({
action: z.literal('addNotes'),
ids: z.array(z.string()).min(1),
notes: z.string().max(500),
}),
])
export async function POST(req: Request) {
const session = await auth()
if (!session?.user?.id) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
const body = await req.json()
const result = bulkSchema.safeParse(body)
if (!result.success) return NextResponse.json({ error: result.error.flatten() }, { status: 400 })
const { action, ids } = result.data
const userId = session.user.id
// Verify all transaction IDs belong to this user
const owned = await prisma.transaction.findMany({
where: { id: { in: ids }, account: { userId } },
select: { id: true, accountId: true },
})
if (owned.length !== ids.length) {
return NextResponse.json({ error: 'One or more transactions not found' }, { status: 404 })
}
if (action === 'delete') {
const accountIds = [...new Set(owned.map((t) => t.accountId))]
await prisma.transaction.deleteMany({ where: { id: { in: ids } } })
// Recompute currentBalanceCents for each affected account
for (const accountId of accountIds) {
const [balRow] = await prisma.$queryRaw<[{ balance: bigint }]>`
SELECT COALESCE(SUM(
CASE WHEN type = 'CREDIT' THEN "amountCents" ELSE -"amountCents" END
), 0)::bigint AS balance
FROM "Transaction"
WHERE "accountId" = ${accountId}
`
await prisma.account.update({
where: { id: accountId },
data: { currentBalanceCents: Number(balRow.balance) },
})
}
return NextResponse.json({ deleted: ids.length })
}
if (action === 'assignBudget') {
const { budgetId } = result.data
if (budgetId !== null) {
const budget = await prisma.budget.findFirst({ where: { id: budgetId, userId } })
if (!budget) return NextResponse.json({ error: 'Budget not found' }, { status: 404 })
}
await prisma.transaction.updateMany({
where: { id: { in: ids } },
data: { budgetId },
})
return NextResponse.json({ updated: ids.length })
}
// addNotes
const { notes } = result.data
await prisma.transaction.updateMany({
where: { id: { in: ids } },
data: { notes: notes || null },
})
return NextResponse.json({ updated: ids.length })
}