diff --git a/apps/api/src/routes/invoices.ts b/apps/api/src/routes/invoices.ts index 5d0cfa6..ef6b96f 100644 --- a/apps/api/src/routes/invoices.ts +++ b/apps/api/src/routes/invoices.ts @@ -350,20 +350,25 @@ invoicesRouter.patch( } // Validate and persist tip splits when marking invoice as paid - if (body.status === "paid" && current.tipCents > 0) { + const tipCents = body.tipCents ?? current.tipCents; + if (body.status === "paid" && tipCents > 0) { // If incoming splits are provided in the request body, atomically replace them if (body.tipSplits !== undefined) { + if (body.tipSplits.length === 0) { + return c.json({ error: "Tip splits are required when tip amount is greater than zero" }, 400); + } const totalPct = body.tipSplits.reduce((sum, s) => sum + s.sharePct, 0); if (Math.abs(totalPct - 100) > 0.01) { return c.json({ error: "Tip split percentages must sum to 100%" }, 400); } await db.transaction(async (tx) => { await tx.delete(invoiceTipSplits).where(eq(invoiceTipSplits.invoiceId, id)); - if (body.tipSplits.length > 0) { - let remaining = current.tipCents; - const rows = body.tipSplits.map((s, i) => { - const isLast = i === body.tipSplits.length - 1; - const shareCents = isLast ? remaining : Math.round((s.sharePct / 100) * current.tipCents); + const splits = body.tipSplits!; + if (splits.length > 0) { + let remaining = tipCents; + const rows = splits.map((s, i) => { + const isLast = i === splits.length - 1; + const shareCents = isLast ? remaining : Math.round((s.sharePct / 100) * tipCents); if (!isLast) remaining -= shareCents; return { invoiceId: id, @@ -400,7 +405,10 @@ invoicesRouter.patch( } } - const update: Record = { ...body, updatedAt: new Date() }; + // Destructure tipSplits out — it belongs to a separate table, not the invoices column + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { tipSplits: _tipSplits, ...updateBody } = body as Record; + const update: Record = { ...updateBody, updatedAt: new Date() }; // Auto-set paidAt when marking as paid if (body.status === "paid" && !body.paidAt && !current.paidAt) {