64 lines
1.8 KiB
TypeScript
64 lines
1.8 KiB
TypeScript
import { z } from 'zod'
|
|
import { pikkuFunc } from '#pikku'
|
|
import { InvoiceAlreadyPaidError } from '../errors.js'
|
|
import { isAdminOrOwner } from '../lib/permissions.js'
|
|
|
|
export const CancelInvoiceInput = z.object({
|
|
invoiceId: z.string(),
|
|
})
|
|
|
|
export const CancelInvoiceOutput = z.object({
|
|
invoiceId: z.string(),
|
|
status: z.literal('cancelled'),
|
|
})
|
|
|
|
const uid = () =>
|
|
globalThis.crypto?.randomUUID?.() ??
|
|
`audit_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`
|
|
|
|
export const cancelInvoice = pikkuFunc({
|
|
expose: true,
|
|
description: 'Admin/owner: cancel an outstanding invoice (typo, duplicate, etc.).',
|
|
permissions: { isAdminOrOwner },
|
|
input: CancelInvoiceInput,
|
|
output: CancelInvoiceOutput,
|
|
func: async ({ kysely }, { invoiceId }, { session }) => {
|
|
const inv = await kysely
|
|
.selectFrom('invoice')
|
|
.innerJoin('booking', 'booking.bookingId', 'invoice.bookingId')
|
|
.where('invoice.invoiceId', '=', invoiceId)
|
|
.select(['invoice.invoiceId', 'invoice.status', 'booking.venueId'])
|
|
.executeTakeFirstOrThrow()
|
|
|
|
if (inv.status === 'paid') {
|
|
throw new InvoiceAlreadyPaidError()
|
|
}
|
|
if (inv.status === 'cancelled') {
|
|
return { invoiceId, status: 'cancelled' as const }
|
|
}
|
|
|
|
await kysely
|
|
.updateTable('invoice')
|
|
.set({ status: 'cancelled' })
|
|
.where('invoiceId', '=', invoiceId)
|
|
.execute()
|
|
|
|
await kysely
|
|
.insertInto('auditLog')
|
|
.values({
|
|
auditId: uid(),
|
|
venueId: inv.venueId,
|
|
userId: session.userId,
|
|
entity: 'invoice',
|
|
entityId: invoiceId,
|
|
field: 'status',
|
|
beforeValue: inv.status,
|
|
afterValue: 'cancelled',
|
|
action: 'update',
|
|
})
|
|
.execute()
|
|
|
|
return { invoiceId, status: 'cancelled' as const }
|
|
},
|
|
})
|