From a6818607c87d2e37427b9b454e7efb3a84398ea4 Mon Sep 17 00:00:00 2001 From: Daniel Kirby Date: Thu, 25 Jun 2026 14:33:11 +0100 Subject: [PATCH] fix: enforce room type scheduling rules --- .../clients/[id]/bookings/+page.server.ts | 35 ++++++++++++++++++- .../clients/[id]/contracts/+page.server.ts | 14 ++++++-- .../dashboard/invoices/[id]/+page.svelte | 2 +- 3 files changed, 47 insertions(+), 4 deletions(-) diff --git a/src/routes/dashboard/clients/[id]/bookings/+page.server.ts b/src/routes/dashboard/clients/[id]/bookings/+page.server.ts index 76a75bb..1625884 100644 --- a/src/routes/dashboard/clients/[id]/bookings/+page.server.ts +++ b/src/routes/dashboard/clients/[id]/bookings/+page.server.ts @@ -9,6 +9,8 @@ import { archiveSchema } from '$lib/schemas/shared.schema'; import { bookingCreateSchema, bookingEditSchema } from '$lib/schemas/bookings.schema'; import type { Actions, PageServerLoad } from './$types'; +const bookingRoomType = 'meeting_room'; + async function loadOptions(organizationId: string) { const [clientRows, roomRows, serviceRows] = await Promise.all([ db @@ -19,7 +21,13 @@ async function loadOptions(organizationId: string) { db .select({ id: rooms.id, name: rooms.name }) .from(rooms) - .where(and(eq(rooms.organizationId, organizationId), isNull(rooms.archivedAt))) + .where( + and( + eq(rooms.organizationId, organizationId), + eq(rooms.type, bookingRoomType), + isNull(rooms.archivedAt) + ) + ) .orderBy(asc(rooms.name)), db .select({ id: services.id, name: services.name }) @@ -38,6 +46,23 @@ async function loadOptions(organizationId: string) { }; } +async function isValidBookingRoom(organizationId: string, roomId: string) { + const [room] = await db + .select({ id: rooms.id }) + .from(rooms) + .where( + and( + eq(rooms.id, roomId), + eq(rooms.organizationId, organizationId), + eq(rooms.type, bookingRoomType), + isNull(rooms.archivedAt) + ) + ) + .limit(1); + + return Boolean(room); +} + export const load: PageServerLoad = async ({ locals, params }) => { const { activeOrganizationId } = await loadOrganizationContext(locals); const records = await db @@ -93,6 +118,10 @@ export const actions: Actions = { if (!form.valid) return message(form, 'Check the highlighted fields.', { status: 400 }); + if (!(await isValidBookingRoom(activeOrganizationId, form.data.roomId))) { + return setError(form, 'roomId', 'Select a valid meeting room.'); + } + const conflict = await findBookingRoomConflict({ organizationId: activeOrganizationId, roomId: form.data.roomId, @@ -133,6 +162,10 @@ export const actions: Actions = { if (!form.valid) return message(form, 'Check the highlighted fields.', { status: 400 }); + if (!(await isValidBookingRoom(activeOrganizationId, form.data.roomId))) { + return setError(form, 'roomId', 'Select a valid meeting room.'); + } + const conflict = await findBookingRoomConflict({ organizationId: activeOrganizationId, roomId: form.data.roomId, diff --git a/src/routes/dashboard/clients/[id]/contracts/+page.server.ts b/src/routes/dashboard/clients/[id]/contracts/+page.server.ts index 1bef80e..cadb15c 100644 --- a/src/routes/dashboard/clients/[id]/contracts/+page.server.ts +++ b/src/routes/dashboard/clients/[id]/contracts/+page.server.ts @@ -13,6 +13,8 @@ import { } from '$lib/schemas/contracts.schema'; import type { Actions, PageServerLoad } from './$types'; +const contractRoomTypes = ['private_office', 'coworking_desk'] as const; + async function loadOptions(organizationId: string) { const [roomRows, serviceRows] = await Promise.all([ db @@ -26,7 +28,7 @@ async function loadOptions(organizationId: string) { .where( and( eq(rooms.organizationId, organizationId), - inArray(rooms.type, ['private_office', 'coworking_desk']), + inArray(rooms.type, contractRoomTypes), isNull(rooms.archivedAt) ) ) @@ -155,7 +157,7 @@ async function selectionError( and( inArray(rooms.id, uniqueRoomIds), eq(rooms.organizationId, organizationId), - inArray(rooms.type, ['private_office', 'coworking_desk']), + inArray(rooms.type, contractRoomTypes), isNull(rooms.archivedAt) ) ); @@ -371,6 +373,14 @@ export const actions: Actions = { .select({ roomId: contractRooms.roomId }) .from(contractRooms) .where(eq(contractRooms.contractId, form.data.id)); + const invalidSelection = await selectionError( + activeOrganizationId, + roomLinks.map((link) => link.roomId), + '' + ); + if (invalidSelection) { + return message(form, invalidSelection.message, { status: 400 }); + } const conflict = await findActiveContractRoomConflict({ organizationId: activeOrganizationId, roomIds: roomLinks.map((link) => link.roomId), diff --git a/src/routes/dashboard/invoices/[id]/+page.svelte b/src/routes/dashboard/invoices/[id]/+page.svelte index b285117..7aaf60f 100644 --- a/src/routes/dashboard/invoices/[id]/+page.svelte +++ b/src/routes/dashboard/invoices/[id]/+page.svelte @@ -215,7 +215,7 @@ {#if data.invoice.notes}

Notes

-

{data.invoice.notes}

+

{data.invoice.notes}

{/if}