diff --git a/src/routes/dashboard/billing/+page.server.ts b/src/routes/dashboard/billing/+page.server.ts index 3818af5..6b2a57c 100644 --- a/src/routes/dashboard/billing/+page.server.ts +++ b/src/routes/dashboard/billing/+page.server.ts @@ -1,4 +1,5 @@ import { fail } from '@sveltejs/kit'; +import type { BatchItem } from 'drizzle-orm/batch'; import { and, asc, desc, eq, gte, inArray, isNull, like, lte, ne } from 'drizzle-orm'; import { createSearchParamsSchema, validateSearchParams } from 'runed/kit'; import { db } from '$lib/server/db'; @@ -458,14 +459,17 @@ export const actions: Actions = { const invoicePrefix = invoiceNumberPrefix(invoiceDate); const latestSequence = await latestInvoiceSequence(activeOrganizationId, invoicePrefix); - await db.transaction(async (tx) => { - let invoiceOffset = 0; - for (const client of groupedClients) { - const invoiceId = crypto.randomUUID(); - const invoiceNumber = `${invoicePrefix}-${String(latestSequence + invoiceOffset + 1).padStart(3, '0')}`; - invoiceOffset += 1; + const now = new Date(); + const queries: BatchItem<'sqlite'>[] = []; + let invoiceOffset = 0; - await tx.insert(invoices).values({ + for (const client of groupedClients) { + const invoiceId = crypto.randomUUID(); + const invoiceNumber = `${invoicePrefix}-${String(latestSequence + invoiceOffset + 1).padStart(3, '0')}`; + invoiceOffset += 1; + + queries.push( + db.insert(invoices).values({ id: invoiceId, organizationId: activeOrganizationId, clientId: client.clientId, @@ -476,11 +480,13 @@ export const actions: Actions = { taxGbp: 0, totalGbp: client.totalGbp, notes: `Generated from billing run. Contract billing through ${preview.contractsCutoff}; services through ${preview.servicesCutoff}.`, - createdAt: new Date(), - updatedAt: new Date() - }); + createdAt: now, + updatedAt: now + }) + ); - await tx.insert(invoiceLines).values( + queries.push( + db.insert(invoiceLines).values( client.lines.map((line) => ({ id: crypto.randomUUID(), organizationId: activeOrganizationId, @@ -494,29 +500,35 @@ export const actions: Actions = { quantity: line.quantity, unitPriceGbp: line.unitPriceGbp, totalGbp: line.totalGbp, - createdAt: new Date(), - updatedAt: new Date() + createdAt: now, + updatedAt: now })) - ); - } + ) + ); + } - const contractBilledTo = new Map(); - for (const line of includedLines) { - if (line.sourceType !== 'contract') continue; - const current = contractBilledTo.get(line.sourceId); - if (!current || line.periodEnd > current) - contractBilledTo.set(line.sourceId, line.periodEnd); - } + const contractBilledTo = new Map(); + for (const line of includedLines) { + if (line.sourceType !== 'contract') continue; + const current = contractBilledTo.get(line.sourceId); + if (!current || line.periodEnd > current) contractBilledTo.set(line.sourceId, line.periodEnd); + } - for (const [contractId, billedTo] of contractBilledTo) { - await tx + for (const [contractId, billedTo] of contractBilledTo) { + queries.push( + db .update(contracts) - .set({ billedTo, updatedAt: new Date() }) + .set({ billedTo, updatedAt: now }) .where( and(eq(contracts.id, contractId), eq(contracts.organizationId, activeOrganizationId)) - ); - } - }); + ) + ); + } + + const [firstQuery, ...remainingQueries] = queries; + if (firstQuery) { + await db.batch([firstQuery, ...remainingQueries]); + } return { message: `Billing run complete. Created ${groupedClients.length} invoice${groupedClients.length === 1 ? '' : 's'}.`