Initial state for GitNexus analysis
This commit is contained in:
34
packages/features/booking-management/package.json
Normal file
34
packages/features/booking-management/package.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "@kit/booking-management",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"*": [
|
||||
"src/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"exports": {
|
||||
"./api": "./src/server/api.ts",
|
||||
"./schema/*": "./src/schema/*.ts",
|
||||
"./components": "./src/components/index.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "git clean -xdf .turbo node_modules",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@kit/next": "workspace:*",
|
||||
"@kit/shared": "workspace:*",
|
||||
"@kit/supabase": "workspace:*",
|
||||
"@kit/tsconfig": "workspace:*",
|
||||
"@kit/ui": "workspace:*",
|
||||
"@supabase/supabase-js": "catalog:",
|
||||
"@types/react": "catalog:",
|
||||
"next": "catalog:",
|
||||
"next-safe-action": "catalog:",
|
||||
"react": "catalog:",
|
||||
"zod": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export {};
|
||||
@@ -0,0 +1,40 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const BookingStatusEnum = z.enum(['pending', 'confirmed', 'checked_in', 'checked_out', 'cancelled', 'no_show']);
|
||||
|
||||
export const CreateRoomSchema = z.object({
|
||||
accountId: z.string().uuid(),
|
||||
roomNumber: z.string().min(1),
|
||||
name: z.string().optional(),
|
||||
roomType: z.string().default('standard'),
|
||||
capacity: z.number().int().min(1).default(2),
|
||||
floor: z.number().int().optional(),
|
||||
pricePerNight: z.number().min(0),
|
||||
description: z.string().optional(),
|
||||
});
|
||||
|
||||
export const CreateBookingSchema = z.object({
|
||||
accountId: z.string().uuid(),
|
||||
roomId: z.string().uuid(),
|
||||
guestId: z.string().uuid().optional(),
|
||||
checkIn: z.string(),
|
||||
checkOut: z.string(),
|
||||
adults: z.number().int().min(1).default(1),
|
||||
children: z.number().int().min(0).default(0),
|
||||
status: BookingStatusEnum.default('confirmed'),
|
||||
totalPrice: z.number().min(0).default(0),
|
||||
notes: z.string().optional(),
|
||||
});
|
||||
export type CreateBookingInput = z.infer<typeof CreateBookingSchema>;
|
||||
|
||||
export const CreateGuestSchema = z.object({
|
||||
accountId: z.string().uuid(),
|
||||
firstName: z.string().min(1),
|
||||
lastName: z.string().min(1),
|
||||
email: z.string().email().optional().or(z.literal('')),
|
||||
phone: z.string().optional(),
|
||||
street: z.string().optional(),
|
||||
postalCode: z.string().optional(),
|
||||
city: z.string().optional(),
|
||||
country: z.string().default('DE'),
|
||||
});
|
||||
88
packages/features/booking-management/src/server/api.ts
Normal file
88
packages/features/booking-management/src/server/api.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import type { Database } from '@kit/supabase/database';
|
||||
import type { SupabaseClient } from '@supabase/supabase-js';
|
||||
|
||||
import type { CreateBookingInput } from '../schema/booking.schema';
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
export function createBookingManagementApi(client: SupabaseClient<Database>) {
|
||||
const db = client;
|
||||
|
||||
return {
|
||||
// --- Rooms ---
|
||||
async listRooms(accountId: string) {
|
||||
const { data, error } = await client.from('rooms').select('*')
|
||||
.eq('account_id', accountId).eq('is_active', true).order('room_number');
|
||||
if (error) throw error;
|
||||
return data ?? [];
|
||||
},
|
||||
|
||||
async getRoom(roomId: string) {
|
||||
const { data, error } = await client.from('rooms').select('*').eq('id', roomId).single();
|
||||
if (error) throw error;
|
||||
return data;
|
||||
},
|
||||
|
||||
// --- Availability ---
|
||||
async checkAvailability(roomId: string, checkIn: string, checkOut: string) {
|
||||
const { count, error } = await client.from('bookings').select('*', { count: 'exact', head: true })
|
||||
.eq('room_id', roomId)
|
||||
.not('status', 'in', '("cancelled","no_show")')
|
||||
.lt('check_in', checkOut)
|
||||
.gt('check_out', checkIn);
|
||||
if (error) throw error;
|
||||
return (count ?? 0) === 0;
|
||||
},
|
||||
|
||||
// --- Bookings ---
|
||||
async listBookings(accountId: string, opts?: { status?: string; from?: string; to?: string; page?: number }) {
|
||||
let query = client.from('bookings').select('*', { count: 'exact' })
|
||||
.eq('account_id', accountId).order('check_in', { ascending: false });
|
||||
if (opts?.status) query = query.eq('status', opts.status);
|
||||
if (opts?.from) query = query.gte('check_in', opts.from);
|
||||
if (opts?.to) query = query.lte('check_out', opts.to);
|
||||
const page = opts?.page ?? 1;
|
||||
query = query.range((page - 1) * 25, page * 25 - 1);
|
||||
const { data, error, count } = await query;
|
||||
if (error) throw error;
|
||||
return { data: data ?? [], total: count ?? 0 };
|
||||
},
|
||||
|
||||
async createBooking(input: CreateBookingInput) {
|
||||
const available = await this.checkAvailability(input.roomId, input.checkIn, input.checkOut);
|
||||
if (!available) throw new Error('Room is not available for the selected dates');
|
||||
|
||||
const { data, error } = await client.from('bookings').insert({
|
||||
account_id: input.accountId, room_id: input.roomId, guest_id: input.guestId,
|
||||
check_in: input.checkIn, check_out: input.checkOut,
|
||||
adults: input.adults, children: input.children,
|
||||
status: input.status, total_price: input.totalPrice, notes: input.notes,
|
||||
}).select().single();
|
||||
if (error) throw error;
|
||||
return data;
|
||||
},
|
||||
|
||||
async updateBookingStatus(bookingId: string, status: string) {
|
||||
const { error } = await client.from('bookings').update({ status }).eq('id', bookingId);
|
||||
if (error) throw error;
|
||||
},
|
||||
|
||||
// --- Guests ---
|
||||
async listGuests(accountId: string, search?: string) {
|
||||
let query = client.from('guests').select('*').eq('account_id', accountId).order('last_name');
|
||||
if (search) query = query.or(`last_name.ilike.%${search}%,first_name.ilike.%${search}%,email.ilike.%${search}%`);
|
||||
const { data, error } = await query;
|
||||
if (error) throw error;
|
||||
return data ?? [];
|
||||
},
|
||||
|
||||
async createGuest(input: { accountId: string; firstName: string; lastName: string; email?: string; phone?: string; city?: string }) {
|
||||
const { data, error } = await client.from('guests').insert({
|
||||
account_id: input.accountId, first_name: input.firstName, last_name: input.lastName,
|
||||
email: input.email, phone: input.phone, city: input.city,
|
||||
}).select().single();
|
||||
if (error) throw error;
|
||||
return data;
|
||||
},
|
||||
};
|
||||
}
|
||||
6
packages/features/booking-management/tsconfig.json
Normal file
6
packages/features/booking-management/tsconfig.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "@kit/tsconfig/base.json",
|
||||
"compilerOptions": { "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json" },
|
||||
"include": ["*.ts", "*.tsx", "src"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
Reference in New Issue
Block a user