mirror of
https://github.com/SyncrowIOT/backend.git
synced 2025-07-16 02:36:19 +00:00
task: add create booking API
This commit is contained in:
@ -91,6 +91,20 @@ export class ControllerRoute {
|
||||
'This endpoint allows you to update existing bookable spaces by providing the required details.';
|
||||
};
|
||||
};
|
||||
static BOOKING = class {
|
||||
public static readonly ROUTE = 'bookings';
|
||||
static ACTIONS = class {
|
||||
public static readonly ADD_BOOKING_SUMMARY = 'Add new booking';
|
||||
|
||||
public static readonly ADD_BOOKING_DESCRIPTION =
|
||||
'This endpoint allows you to add new booking by providing the required details.';
|
||||
|
||||
public static readonly GET_ALL_BOOKINGS_SUMMARY = 'Get all bookings';
|
||||
|
||||
public static readonly GET_ALL_BOOKINGS_DESCRIPTION =
|
||||
'This endpoint retrieves all bookings.';
|
||||
};
|
||||
};
|
||||
static COMMUNITY = class {
|
||||
public static readonly ROUTE = '/projects/:projectUuid/communities';
|
||||
static ACTIONS = class {
|
||||
|
@ -14,6 +14,8 @@ import { createLogger } from 'winston';
|
||||
import { winstonLoggerOptions } from '../logger/services/winston.logger';
|
||||
import { AqiSpaceDailyPollutantStatsEntity } from '../modules/aqi/entities';
|
||||
import { AutomationEntity } from '../modules/automation/entities';
|
||||
import { BookableSpaceEntity } from '../modules/booking/entities/bookable-space.entity';
|
||||
import { BookingEntity } from '../modules/booking/entities/booking.entity';
|
||||
import { ClientEntity } from '../modules/client/entities';
|
||||
import { CommunityEntity } from '../modules/community/entities';
|
||||
import { DeviceStatusLogEntity } from '../modules/device-status-log/entities';
|
||||
@ -58,7 +60,6 @@ import {
|
||||
UserSpaceEntity,
|
||||
} from '../modules/user/entities';
|
||||
import { VisitorPasswordEntity } from '../modules/visitor-password/entities';
|
||||
import { BookableSpaceEntity } from '../modules/booking/entities';
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forRootAsync({
|
||||
@ -119,6 +120,7 @@ import { BookableSpaceEntity } from '../modules/booking/entities';
|
||||
AqiSpaceDailyPollutantStatsEntity,
|
||||
SpaceDailyOccupancyDurationEntity,
|
||||
BookableSpaceEntity,
|
||||
BookingEntity,
|
||||
],
|
||||
namingStrategy: new SnakeNamingStrategy(),
|
||||
synchronize: Boolean(JSON.parse(configService.get('DB_SYNC'))),
|
||||
|
@ -1,11 +1,12 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { BookableSpaceEntity } from './entities/bookable-space.entity';
|
||||
import { BookingEntity } from './entities/booking.entity';
|
||||
|
||||
@Module({
|
||||
providers: [],
|
||||
exports: [],
|
||||
controllers: [],
|
||||
imports: [TypeOrmModule.forFeature([BookableSpaceEntity])],
|
||||
imports: [TypeOrmModule.forFeature([BookableSpaceEntity, BookingEntity])],
|
||||
})
|
||||
export class BookableRepositoryModule {}
|
||||
export class BookingRepositoryModule {}
|
||||
|
44
libs/common/src/modules/booking/entities/booking.entity.ts
Normal file
44
libs/common/src/modules/booking/entities/booking.entity.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
ManyToOne,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
||||
import { SpaceEntity } from '../../space/entities/space.entity';
|
||||
import { UserEntity } from '../../user/entities';
|
||||
|
||||
@Entity('booking')
|
||||
export class BookingEntity extends AbstractEntity {
|
||||
@Column({
|
||||
type: 'uuid',
|
||||
default: () => 'gen_random_uuid()',
|
||||
nullable: false,
|
||||
})
|
||||
public uuid: string;
|
||||
|
||||
@ManyToOne(() => SpaceEntity, (space) => space.bookableConfig)
|
||||
space: SpaceEntity;
|
||||
|
||||
@ManyToOne(() => UserEntity, (user) => user.bookings)
|
||||
user: UserEntity;
|
||||
|
||||
@Column({ type: Date, nullable: false })
|
||||
date: Date;
|
||||
|
||||
@Column({ type: 'time' })
|
||||
startTime: string;
|
||||
|
||||
@Column({ type: 'time' })
|
||||
endTime: string;
|
||||
|
||||
@Column({ type: 'int', default: null })
|
||||
cost?: number;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt: Date;
|
||||
|
||||
@UpdateDateColumn()
|
||||
updatedAt: Date;
|
||||
}
|
@ -1 +0,0 @@
|
||||
export * from './bookable-space.entity';
|
@ -0,0 +1,10 @@
|
||||
import { DataSource, Repository } from 'typeorm';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { BookableSpaceEntity } from '../entities/bookable-space.entity';
|
||||
|
||||
@Injectable()
|
||||
export class BookableSpaceEntityRepository extends Repository<BookableSpaceEntity> {
|
||||
constructor(private dataSource: DataSource) {
|
||||
super(BookableSpaceEntity, dataSource.createEntityManager());
|
||||
}
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
import { DataSource, Repository } from 'typeorm';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { BookableSpaceEntity } from '../entities/bookable-space.entity';
|
||||
import { DataSource, Repository } from 'typeorm';
|
||||
import { BookingEntity } from '../entities/booking.entity';
|
||||
|
||||
@Injectable()
|
||||
export class BookableSpaceEntityRepository extends Repository<BookableSpaceEntity> {
|
||||
export class BookingEntityRepository extends Repository<BookingEntity> {
|
||||
constructor(private dataSource: DataSource) {
|
||||
super(BookableSpaceEntity, dataSource.createEntityManager());
|
||||
super(BookingEntity, dataSource.createEntityManager());
|
||||
}
|
||||
}
|
||||
|
@ -1 +0,0 @@
|
||||
export * from './booking.repository';
|
@ -8,7 +8,7 @@ import {
|
||||
} from 'typeorm';
|
||||
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
||||
import { AqiSpaceDailyPollutantStatsEntity } from '../../aqi/entities';
|
||||
import { BookableSpaceEntity } from '../../booking/entities';
|
||||
import { BookableSpaceEntity } from '../../booking/entities/bookable-space.entity';
|
||||
import { CommunityEntity } from '../../community/entities';
|
||||
import { DeviceEntity } from '../../device/entities';
|
||||
import { InviteUserSpaceEntity } from '../../Invite-user/entities';
|
||||
@ -20,6 +20,7 @@ import { UserSpaceEntity } from '../../user/entities';
|
||||
import { SpaceDto } from '../dtos';
|
||||
import { SpaceProductAllocationEntity } from './space-product-allocation.entity';
|
||||
import { SubspaceEntity } from './subspace/subspace.entity';
|
||||
import { BookingEntity } from '../../booking/entities/booking.entity';
|
||||
|
||||
@Entity({ name: 'space' })
|
||||
export class SpaceEntity extends AbstractEntity<SpaceDto> {
|
||||
@ -132,6 +133,9 @@ export class SpaceEntity extends AbstractEntity<SpaceDto> {
|
||||
@OneToOne(() => BookableSpaceEntity, (bookable) => bookable.space)
|
||||
bookableConfig: BookableSpaceEntity;
|
||||
|
||||
@OneToMany(() => BookingEntity, (booking) => booking.space)
|
||||
bookings: BookingEntity[];
|
||||
|
||||
constructor(partial: Partial<SpaceEntity>) {
|
||||
super();
|
||||
Object.assign(this, partial);
|
||||
|
@ -29,6 +29,7 @@ import {
|
||||
UserOtpDto,
|
||||
UserSpaceDto,
|
||||
} from '../dtos';
|
||||
import { BookingEntity } from '../../booking/entities/booking.entity';
|
||||
|
||||
@Entity({ name: 'user' })
|
||||
export class UserEntity extends AbstractEntity<UserDto> {
|
||||
@ -121,6 +122,9 @@ export class UserEntity extends AbstractEntity<UserDto> {
|
||||
)
|
||||
deviceUserNotification: DeviceNotificationEntity[];
|
||||
|
||||
@OneToMany(() => BookingEntity, (booking) => booking.user)
|
||||
bookings: BookingEntity[];
|
||||
|
||||
@ManyToOne(() => RegionEntity, (region) => region.users, { nullable: true })
|
||||
region: RegionEntity;
|
||||
@ManyToOne(() => TimeZoneEntity, (timezone) => timezone.users, {
|
||||
|
@ -1,17 +1,28 @@
|
||||
import { Global, Module } from '@nestjs/common';
|
||||
import { BookableSpaceController } from './controllers';
|
||||
import { BookableSpaceService } from './services';
|
||||
import { BookableSpaceEntityRepository } from '@app/common/modules/booking/repositories';
|
||||
import { BookingRepositoryModule } from '@app/common/modules/booking/booking.repository.module';
|
||||
import { BookableSpaceEntityRepository } from '@app/common/modules/booking/repositories/bookable-space.repository';
|
||||
import { BookingEntityRepository } from '@app/common/modules/booking/repositories/booking.repository';
|
||||
import { SpaceRepository } from '@app/common/modules/space';
|
||||
import { UserRepository } from '@app/common/modules/user/repositories';
|
||||
import { Global, Module } from '@nestjs/common';
|
||||
import { BookableSpaceController } from './controllers/bookable-space.controller';
|
||||
import { BookingController } from './controllers/booking.controller';
|
||||
import { BookableSpaceService } from './services/bookable-space.service';
|
||||
import { BookingService } from './services/booking.service';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
controllers: [BookableSpaceController],
|
||||
imports: [BookingRepositoryModule],
|
||||
controllers: [BookableSpaceController, BookingController],
|
||||
providers: [
|
||||
BookableSpaceService,
|
||||
BookingService,
|
||||
|
||||
BookableSpaceEntityRepository,
|
||||
BookingEntityRepository,
|
||||
|
||||
SpaceRepository,
|
||||
UserRepository,
|
||||
],
|
||||
exports: [BookableSpaceService],
|
||||
exports: [BookableSpaceService, BookingService],
|
||||
})
|
||||
export class BookingModule {}
|
||||
|
@ -18,11 +18,11 @@ import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { PageResponse } from '@app/common/dto/pagination.response.dto';
|
||||
import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
|
||||
import { plainToInstance } from 'class-transformer';
|
||||
import { CreateBookableSpaceDto } from '../dtos';
|
||||
import { BookableSpaceRequestDto } from '../dtos/bookable-space-request.dto';
|
||||
import { BookableSpaceResponseDto } from '../dtos/bookable-space-response.dto';
|
||||
import { CreateBookableSpaceDto } from '../dtos/create-bookable-space.dto';
|
||||
import { UpdateBookableSpaceDto } from '../dtos/update-bookable-space.dto';
|
||||
import { BookableSpaceService } from '../services';
|
||||
import { BookableSpaceService } from '../services/bookable-space.service';
|
||||
|
||||
@ApiTags('Booking Module')
|
||||
@Controller({
|
||||
|
76
src/booking/controllers/booking.controller.ts
Normal file
76
src/booking/controllers/booking.controller.ts
Normal file
@ -0,0 +1,76 @@
|
||||
import { ControllerRoute } from '@app/common/constants/controller-route';
|
||||
import { EnableDisableStatusEnum } from '@app/common/constants/days.enum';
|
||||
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
|
||||
import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard';
|
||||
import { Body, Controller, Post, Req, UseGuards } from '@nestjs/common';
|
||||
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
|
||||
import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
|
||||
import { CreateBookingDto } from '../dtos/create-booking.dto';
|
||||
import { BookingService } from '../services/booking.service';
|
||||
|
||||
@ApiTags('Booking Module')
|
||||
@Controller({
|
||||
version: EnableDisableStatusEnum.ENABLED,
|
||||
path: ControllerRoute.BOOKING.ROUTE,
|
||||
})
|
||||
export class BookingController {
|
||||
constructor(private readonly bookingService: BookingService) {}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Post()
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.BOOKING.ACTIONS.ADD_BOOKING_SUMMARY,
|
||||
description: ControllerRoute.BOOKING.ACTIONS.ADD_BOOKING_DESCRIPTION,
|
||||
})
|
||||
async create(
|
||||
@Body() dto: CreateBookingDto,
|
||||
@Req() req: Request,
|
||||
): Promise<BaseResponseDto> {
|
||||
const userUuid = req['user']?.uuid;
|
||||
if (!userUuid) {
|
||||
throw new Error('User UUID is required in the request');
|
||||
}
|
||||
const result = await this.bookingService.create(userUuid, dto);
|
||||
return new SuccessResponseDto({
|
||||
data: result,
|
||||
message: 'Successfully created booking',
|
||||
});
|
||||
}
|
||||
|
||||
// @ApiBearerAuth()
|
||||
// @UseGuards(JwtAuthGuard)
|
||||
// @Get()
|
||||
// @ApiOperation({
|
||||
// summary:
|
||||
// ControllerRoute.BOOKABLE_SPACES.ACTIONS.GET_ALL_BOOKABLE_SPACES_SUMMARY,
|
||||
// description:
|
||||
// ControllerRoute.BOOKABLE_SPACES.ACTIONS
|
||||
// .GET_ALL_BOOKABLE_SPACES_DESCRIPTION,
|
||||
// })
|
||||
// async findAll(
|
||||
// @Query() query: BookableSpaceRequestDto,
|
||||
// @Req() req: Request,
|
||||
// ): Promise<PageResponse<BookableSpaceResponseDto>> {
|
||||
// const project = req['user']?.project?.uuid;
|
||||
// if (!project) {
|
||||
// throw new Error('Project UUID is required in the request');
|
||||
// }
|
||||
// const { data, pagination } = await this.bookableSpaceService.findAll(
|
||||
// query,
|
||||
// project,
|
||||
// );
|
||||
// return new PageResponse<BookableSpaceResponseDto>(
|
||||
// {
|
||||
// data: data.map((space) =>
|
||||
// plainToInstance(BookableSpaceResponseDto, space, {
|
||||
// excludeExtraneousValues: true,
|
||||
// }),
|
||||
// ),
|
||||
// message: 'Successfully fetched all bookable spaces',
|
||||
// },
|
||||
// pagination,
|
||||
// );
|
||||
// }
|
||||
}
|
@ -1 +0,0 @@
|
||||
export * from './bookable-space.controller';
|
35
src/booking/dtos/create-booking.dto.ts
Normal file
35
src/booking/dtos/create-booking.dto.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsDate, IsNotEmpty, IsString, IsUUID, Matches } from 'class-validator';
|
||||
|
||||
export class CreateBookingDto {
|
||||
@ApiProperty({
|
||||
type: 'string',
|
||||
example: '4fa85f64-5717-4562-b3fc-2c963f66afa7',
|
||||
})
|
||||
@IsNotEmpty()
|
||||
@IsUUID('4', { message: 'Invalid space UUID provided' })
|
||||
spaceUuid: string;
|
||||
|
||||
@ApiProperty({
|
||||
type: Date,
|
||||
})
|
||||
@IsNotEmpty()
|
||||
@IsDate()
|
||||
date: Date;
|
||||
|
||||
@ApiProperty({ example: '09:00' })
|
||||
@IsString()
|
||||
@IsNotEmpty({ message: 'Start time cannot be empty' })
|
||||
@Matches(/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/, {
|
||||
message: 'Start time must be in HH:mm format (24-hour)',
|
||||
})
|
||||
startTime: string;
|
||||
|
||||
@ApiProperty({ example: '17:00' })
|
||||
@IsString()
|
||||
@IsNotEmpty({ message: 'End time cannot be empty' })
|
||||
@Matches(/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/, {
|
||||
message: 'End time must be in HH:mm format (24-hour)',
|
||||
})
|
||||
endTime: string;
|
||||
}
|
@ -1 +0,0 @@
|
||||
export * from './create-bookable-space.dto';
|
@ -2,7 +2,7 @@ import { BaseResponseDto } from '@app/common/dto/base.response.dto';
|
||||
import { PageResponseDto } from '@app/common/dto/pagination.response.dto';
|
||||
import { timeToMinutes } from '@app/common/helper/timeToMinutes';
|
||||
import { TypeORMCustomModel } from '@app/common/models/typeOrmCustom.model';
|
||||
import { BookableSpaceEntityRepository } from '@app/common/modules/booking/repositories';
|
||||
import { BookableSpaceEntityRepository } from '@app/common/modules/booking/repositories/bookable-space.repository';
|
||||
import { SpaceEntity } from '@app/common/modules/space/entities/space.entity';
|
||||
import { SpaceRepository } from '@app/common/modules/space/repositories/space.repository';
|
||||
import {
|
||||
@ -12,8 +12,8 @@ import {
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import { In } from 'typeorm';
|
||||
import { CreateBookableSpaceDto } from '../dtos';
|
||||
import { BookableSpaceRequestDto } from '../dtos/bookable-space-request.dto';
|
||||
import { CreateBookableSpaceDto } from '../dtos/create-bookable-space.dto';
|
||||
import { UpdateBookableSpaceDto } from '../dtos/update-bookable-space.dto';
|
||||
|
||||
@Injectable()
|
||||
|
202
src/booking/services/booking.service.ts
Normal file
202
src/booking/services/booking.service.ts
Normal file
@ -0,0 +1,202 @@
|
||||
import { DaysEnum } from '@app/common/constants/days.enum';
|
||||
import { timeToMinutes } from '@app/common/helper/timeToMinutes';
|
||||
import { BookingEntityRepository } from '@app/common/modules/booking/repositories/booking.repository';
|
||||
import { SpaceEntity } from '@app/common/modules/space/entities/space.entity';
|
||||
import { SpaceRepository } from '@app/common/modules/space/repositories/space.repository';
|
||||
import { UserRepository } from '@app/common/modules/user/repositories/user.repository';
|
||||
import {
|
||||
BadRequestException,
|
||||
ConflictException,
|
||||
ForbiddenException,
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import { CreateBookingDto } from '../dtos/create-booking.dto';
|
||||
|
||||
@Injectable()
|
||||
export class BookingService {
|
||||
constructor(
|
||||
private readonly bookingEntityRepository: BookingEntityRepository,
|
||||
private readonly spaceRepository: SpaceRepository,
|
||||
private readonly userRepository: UserRepository,
|
||||
) {}
|
||||
|
||||
async create(userUuid: string, dto: CreateBookingDto) {
|
||||
console.log(userUuid);
|
||||
const user = await this.userRepository.findOne({
|
||||
where: { uuid: userUuid },
|
||||
relations: ['userSpaces', 'userSpaces.space'],
|
||||
});
|
||||
console.log(user.userSpaces);
|
||||
if (!user.userSpaces.some(({ space }) => space.uuid === dto.spaceUuid)) {
|
||||
throw new ForbiddenException(
|
||||
`User does not have permission to book this space: ${dto.spaceUuid}`,
|
||||
);
|
||||
}
|
||||
// Validate time slots first
|
||||
this.validateTimeSlot(dto.startTime, dto.endTime);
|
||||
|
||||
// fetch spaces exist
|
||||
const space = await this.getSpaceConfigurationAndBookings(dto.spaceUuid);
|
||||
|
||||
// Validate booking availability
|
||||
this.validateBookingAvailability(space, dto);
|
||||
|
||||
// Create and save booking
|
||||
return this.createBookings(space, userUuid, dto);
|
||||
}
|
||||
|
||||
// async findAll(
|
||||
// { active, page, size, configured, search }: BookingRequestDto,
|
||||
// project: string,
|
||||
// ): Promise<{
|
||||
// data: BaseResponseDto['data'];
|
||||
// pagination: PageResponseDto;
|
||||
// }> {
|
||||
// let qb = this.spaceRepository
|
||||
// .createQueryBuilder('space')
|
||||
// .leftJoinAndSelect('space.parent', 'parentSpace')
|
||||
// .leftJoinAndSelect('space.community', 'community')
|
||||
// .where('community.project = :project', { project });
|
||||
|
||||
// if (search) {
|
||||
// qb = qb.andWhere(
|
||||
// 'space.spaceName ILIKE :search OR community.name ILIKE :search OR parentSpace.spaceName ILIKE :search',
|
||||
// { search: `%${search}%` },
|
||||
// );
|
||||
// }
|
||||
// if (configured) {
|
||||
// qb = qb
|
||||
// .leftJoinAndSelect('space.bookableConfig', 'bookableConfig')
|
||||
// .andWhere('bookableConfig.uuid IS NOT NULL');
|
||||
// if (active !== undefined) {
|
||||
// qb = qb.andWhere('bookableConfig.active = :active', { active });
|
||||
// }
|
||||
// } else {
|
||||
// qb = qb
|
||||
// .leftJoinAndSelect('space.bookableConfig', 'bookableConfig')
|
||||
// .andWhere('bookableConfig.uuid IS NULL');
|
||||
// }
|
||||
|
||||
// const customModel = TypeORMCustomModel(this.spaceRepository);
|
||||
|
||||
// const { baseResponseDto, paginationResponseDto } =
|
||||
// await customModel.findAll({ page, size, modelName: 'space' }, qb);
|
||||
// return {
|
||||
// data: baseResponseDto.data.map((space) => {
|
||||
// return {
|
||||
// ...space,
|
||||
// virtualLocation: `${space.community?.name} - ${space.parent ? space.parent?.spaceName + ' - ' : ''}${space.spaceName}`,
|
||||
// };
|
||||
// }),
|
||||
// pagination: paginationResponseDto,
|
||||
// };
|
||||
// }
|
||||
|
||||
/**
|
||||
* Fetch space by UUID and throw an error if not found or if not configured for booking
|
||||
*/
|
||||
private async getSpaceConfigurationAndBookings(
|
||||
spaceUuid: string,
|
||||
): Promise<SpaceEntity> {
|
||||
const space = await this.spaceRepository.findOne({
|
||||
where: { uuid: spaceUuid },
|
||||
relations: ['bookableConfig', 'bookings'],
|
||||
});
|
||||
|
||||
if (!space) {
|
||||
throw new NotFoundException(`Space not found: ${spaceUuid}`);
|
||||
}
|
||||
if (!space.bookableConfig) {
|
||||
throw new NotFoundException(
|
||||
`This space is not configured for booking: ${spaceUuid}`,
|
||||
);
|
||||
}
|
||||
|
||||
return space;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the slot start time is before the end time
|
||||
*/
|
||||
private validateTimeSlot(startTime: string, endTime: string): void {
|
||||
const start = timeToMinutes(startTime);
|
||||
const end = timeToMinutes(endTime);
|
||||
|
||||
if (start >= end) {
|
||||
throw new BadRequestException(
|
||||
`End time must be after start time for slot: ${startTime}-${endTime}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* check if the space is available for booking on the requested day
|
||||
* and if the requested time slot is within the available hours
|
||||
*/
|
||||
private validateBookingAvailability(
|
||||
space: SpaceEntity,
|
||||
dto: CreateBookingDto,
|
||||
): void {
|
||||
// Check if the space is available for booking on the requested day
|
||||
const availableDays = space.bookableConfig?.daysAvailable || [];
|
||||
const requestedDay = new Date(dto.date).toLocaleDateString('en-US', {
|
||||
weekday: 'short',
|
||||
}) as DaysEnum;
|
||||
|
||||
if (!availableDays.includes(requestedDay)) {
|
||||
const dayFullName = new Date(dto.date).toLocaleDateString('en-US', {
|
||||
weekday: 'long',
|
||||
});
|
||||
throw new BadRequestException(
|
||||
`Space is not available for booking on ${dayFullName}s`,
|
||||
);
|
||||
}
|
||||
|
||||
const dtoStartTimeInMinutes = timeToMinutes(dto.startTime);
|
||||
const dtoEndTimeInMinutes = timeToMinutes(dto.endTime);
|
||||
|
||||
if (
|
||||
dtoStartTimeInMinutes < timeToMinutes(space.bookableConfig.startTime) ||
|
||||
dtoEndTimeInMinutes > timeToMinutes(space.bookableConfig.endTime)
|
||||
) {
|
||||
throw new BadRequestException(
|
||||
`Booking time must be within the available hours for space: ${space.spaceName}`,
|
||||
);
|
||||
}
|
||||
|
||||
const previousBookings = space.bookings.filter(
|
||||
(booking) =>
|
||||
timeToMinutes(booking.startTime) < dtoEndTimeInMinutes &&
|
||||
timeToMinutes(booking.endTime) > dtoStartTimeInMinutes,
|
||||
);
|
||||
|
||||
if (previousBookings.length > 0) {
|
||||
// tell the user what time is unavailable
|
||||
const unavailableTimes = previousBookings.map((booking) => {
|
||||
return `${booking.startTime}-${booking.endTime}`;
|
||||
});
|
||||
throw new ConflictException(
|
||||
`Space is already booked during this times: ${unavailableTimes.join(', ')}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Create bookable space entries after all validations pass
|
||||
*/
|
||||
private async createBookings(
|
||||
space: SpaceEntity,
|
||||
user: string,
|
||||
{ spaceUuid, date, ...dto }: CreateBookingDto,
|
||||
) {
|
||||
const entry = this.bookingEntityRepository.create({
|
||||
space: { uuid: spaceUuid },
|
||||
user: { uuid: user },
|
||||
...dto,
|
||||
date: new Date(date),
|
||||
cost: space.bookableConfig?.points || null,
|
||||
});
|
||||
|
||||
return this.bookingEntityRepository.save(entry);
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
export * from './bookable-space.service';
|
Reference in New Issue
Block a user