diff --git a/src/lib/server/scheduling.ts b/src/lib/server/scheduling.ts index 7b38096..4160e54 100644 --- a/src/lib/server/scheduling.ts +++ b/src/lib/server/scheduling.ts @@ -82,6 +82,54 @@ export async function findBookingRoomConflict({ return conflict ?? null; } +export async function findBookingContractRoomConflict({ + organizationId, + roomId, + startsAt, + endsAt, + status +}: { + organizationId: string; + roomId: string; + startsAt: string; + endsAt: string; + status: string; +}): Promise { + if (status === 'cancelled') return null; + + const startDate = startsAt.slice(0, 10); + const endDate = endsAt.slice(0, 10); + + const [conflict] = await db + .select({ + contractId: contracts.id, + roomId: contractRooms.roomId, + roomName: rooms.name, + startDate: contracts.startDate, + endDate: contracts.endDate + }) + .from(contractRooms) + .innerJoin(contracts, eq(contractRooms.contractId, contracts.id)) + .innerJoin(rooms, eq(contractRooms.roomId, rooms.id)) + .where( + and( + eq(contracts.organizationId, organizationId), + eq(contracts.status, 'active'), + isNull(contracts.archivedAt), + eq(contractRooms.roomId, roomId), + eq(rooms.organizationId, organizationId), + inArray(rooms.type, rentableContractRoomTypes), + isNull(rooms.archivedAt), + lte(contracts.startDate, endDate), + gte(contracts.endDate, startDate) + ) + ) + .orderBy(asc(contracts.startDate), asc(rooms.name)) + .limit(1); + + return conflict ?? null; +} + export async function findActiveContractRoomConflict({ organizationId, roomIds, diff --git a/src/routes/dashboard/clients/[id]/bookings/+page.server.ts b/src/routes/dashboard/clients/[id]/bookings/+page.server.ts index 76a75bb..bdc7ce6 100644 --- a/src/routes/dashboard/clients/[id]/bookings/+page.server.ts +++ b/src/routes/dashboard/clients/[id]/bookings/+page.server.ts @@ -4,7 +4,12 @@ import { zod4 } from 'sveltekit-superforms/adapters'; import { db } from '$lib/server/db'; import { bookings, clients, rooms, services } from '$lib/server/db/schema'; import { loadOrganizationContext } from '$lib/server/organizations'; -import { bookingConflictMessage, findBookingRoomConflict } from '$lib/server/scheduling'; +import { + bookingConflictMessage, + contractConflictMessage, + findBookingContractRoomConflict, + findBookingRoomConflict +} from '$lib/server/scheduling'; import { archiveSchema } from '$lib/schemas/shared.schema'; import { bookingCreateSchema, bookingEditSchema } from '$lib/schemas/bookings.schema'; import type { Actions, PageServerLoad } from './$types'; @@ -102,6 +107,17 @@ export const actions: Actions = { }); if (conflict) return setError(form, 'roomId', bookingConflictMessage(conflict)); + const contractConflict = await findBookingContractRoomConflict({ + organizationId: activeOrganizationId, + roomId: form.data.roomId, + startsAt: form.data.startsAt, + endsAt: form.data.endsAt, + status: form.data.status + }); + if (contractConflict) { + return setError(form, 'roomId', contractConflictMessage(contractConflict)); + } + try { await db.insert(bookings).values({ id: crypto.randomUUID(), @@ -143,6 +159,17 @@ export const actions: Actions = { }); if (conflict) return setError(form, 'roomId', bookingConflictMessage(conflict)); + const contractConflict = await findBookingContractRoomConflict({ + organizationId: activeOrganizationId, + roomId: form.data.roomId, + startsAt: form.data.startsAt, + endsAt: form.data.endsAt, + status: form.data.status + }); + if (contractConflict) { + return setError(form, 'roomId', contractConflictMessage(contractConflict)); + } + try { await db .update(bookings) 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}