mirror of
https://github.com/SyncrowIOT/backend.git
synced 2025-07-09 22:57:24 +00:00
Compare commits
4 Commits
18b21d697c
...
5cf45c30f4
Author | SHA1 | Date | |
---|---|---|---|
5cf45c30f4 | |||
0bb178ed10 | |||
9971fb953d | |||
7a07f39f16 |
@ -83,6 +83,12 @@ export class ControllerRoute {
|
||||
|
||||
public static readonly GET_ALL_BOOKABLE_SPACES_DESCRIPTION =
|
||||
'This endpoint retrieves all bookable spaces.';
|
||||
|
||||
public static readonly UPDATE_BOOKABLE_SPACES_SUMMARY =
|
||||
'Update existing bookable spaces';
|
||||
|
||||
public static readonly UPDATE_BOOKABLE_SPACES_DESCRIPTION =
|
||||
'This endpoint allows you to update existing bookable spaces by providing the required details.';
|
||||
};
|
||||
};
|
||||
static COMMUNITY = class {
|
||||
|
@ -40,8 +40,8 @@ export class BookableSpaceEntity extends AbstractEntity {
|
||||
@Column({ type: Boolean, default: true })
|
||||
active: boolean;
|
||||
|
||||
@Column({ type: 'int' })
|
||||
points: number;
|
||||
@Column({ type: 'int', default: null })
|
||||
points?: number;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt: Date;
|
||||
|
@ -6,7 +6,9 @@ import {
|
||||
Body,
|
||||
Controller,
|
||||
Get,
|
||||
Param,
|
||||
Post,
|
||||
Put,
|
||||
Query,
|
||||
Req,
|
||||
UseGuards,
|
||||
@ -19,6 +21,7 @@ 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 { UpdateBookableSpaceDto } from '../dtos/update-bookable-space.dto';
|
||||
import { BookableSpaceService } from '../services';
|
||||
|
||||
@ApiTags('Booking Module')
|
||||
@ -79,4 +82,25 @@ export class BookableSpaceController {
|
||||
pagination,
|
||||
);
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Put(':spaceUuid')
|
||||
@ApiOperation({
|
||||
summary:
|
||||
ControllerRoute.BOOKABLE_SPACES.ACTIONS.UPDATE_BOOKABLE_SPACES_SUMMARY,
|
||||
description:
|
||||
ControllerRoute.BOOKABLE_SPACES.ACTIONS
|
||||
.UPDATE_BOOKABLE_SPACES_DESCRIPTION,
|
||||
})
|
||||
async update(
|
||||
@Param('spaceUuid') spaceUuid: string,
|
||||
@Body() dto: UpdateBookableSpaceDto,
|
||||
): Promise<BaseResponseDto> {
|
||||
const result = await this.bookableSpaceService.update(spaceUuid, dto);
|
||||
return new SuccessResponseDto({
|
||||
data: result,
|
||||
message: 'Successfully updated bookable spaces',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { BooleanValues } from '@app/common/constants/boolean-values.enum';
|
||||
import { PaginationRequestGetListDto } from '@app/common/dto/pagination.request.dto';
|
||||
import { PaginationRequestWithSearchGetListDto } from '@app/common/dto/pagination-with-search.request.dto';
|
||||
import { ApiProperty, OmitType } from '@nestjs/swagger';
|
||||
import { Transform } from 'class-transformer';
|
||||
import { IsBoolean, IsNotEmpty, IsOptional } from 'class-validator';
|
||||
|
||||
export class BookableSpaceRequestDto extends OmitType(
|
||||
PaginationRequestGetListDto,
|
||||
PaginationRequestWithSearchGetListDto,
|
||||
['includeSpaces'],
|
||||
) {
|
||||
@ApiProperty({
|
||||
|
@ -27,9 +27,10 @@ export class BookableSpaceConfigResponseDto {
|
||||
|
||||
@ApiProperty({
|
||||
type: Number,
|
||||
nullable: true,
|
||||
})
|
||||
@Expose()
|
||||
points: number;
|
||||
points?: number;
|
||||
}
|
||||
|
||||
export class BookableSpaceResponseDto {
|
||||
|
@ -1,17 +1,17 @@
|
||||
// dtos/bookable-space.dto.ts
|
||||
import { DaysEnum } from '@app/common/constants/days.enum';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import {
|
||||
ArrayMinSize,
|
||||
IsArray,
|
||||
IsEnum,
|
||||
IsInt,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsUUID,
|
||||
IsInt,
|
||||
ArrayMinSize,
|
||||
Matches,
|
||||
Max,
|
||||
Min,
|
||||
Matches,
|
||||
} from 'class-validator';
|
||||
|
||||
export class CreateBookableSpaceDto {
|
||||
@ -54,9 +54,10 @@ export class CreateBookableSpaceDto {
|
||||
})
|
||||
endTime: string;
|
||||
|
||||
@ApiProperty({ example: 10 })
|
||||
@ApiProperty({ example: 10, required: false })
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Min(0, { message: 'Points cannot be negative' })
|
||||
@Max(1000, { message: 'Points cannot exceed 1000' })
|
||||
points: number;
|
||||
points?: number;
|
||||
}
|
||||
|
12
src/booking/dtos/update-bookable-space.dto.ts
Normal file
12
src/booking/dtos/update-bookable-space.dto.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { ApiProperty, OmitType, PartialType } from '@nestjs/swagger';
|
||||
import { IsBoolean, IsOptional } from 'class-validator';
|
||||
import { CreateBookableSpaceDto } from './create-bookable-space.dto';
|
||||
|
||||
export class UpdateBookableSpaceDto extends PartialType(
|
||||
OmitType(CreateBookableSpaceDto, ['spaceUuids']),
|
||||
) {
|
||||
@ApiProperty({ type: Boolean })
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
active?: boolean;
|
||||
}
|
@ -14,6 +14,7 @@ import {
|
||||
import { In } from 'typeorm';
|
||||
import { CreateBookableSpaceDto } from '../dtos';
|
||||
import { BookableSpaceRequestDto } from '../dtos/bookable-space-request.dto';
|
||||
import { UpdateBookableSpaceDto } from '../dtos/update-bookable-space.dto';
|
||||
|
||||
@Injectable()
|
||||
export class BookableSpaceService {
|
||||
@ -37,7 +38,7 @@ export class BookableSpaceService {
|
||||
}
|
||||
|
||||
async findAll(
|
||||
{ active, page, size, configured }: BookableSpaceRequestDto,
|
||||
{ active, page, size, configured, search }: BookableSpaceRequestDto,
|
||||
project: string,
|
||||
): Promise<{
|
||||
data: BaseResponseDto['data'];
|
||||
@ -49,6 +50,12 @@ export class BookableSpaceService {
|
||||
.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')
|
||||
@ -77,6 +84,30 @@ export class BookableSpaceService {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* todo: if updating availability, send to the ones who have access to this space
|
||||
* todo: if updating other fields, just send emails to all users who's bookings might be affected
|
||||
*/
|
||||
async update(spaceUuid: string, dto: UpdateBookableSpaceDto) {
|
||||
// fetch spaces exist
|
||||
const space = (await this.getSpacesOrFindMissing([spaceUuid]))[0];
|
||||
|
||||
if (!space.bookableConfig) {
|
||||
throw new NotFoundException(
|
||||
`Bookable configuration not found for space: ${spaceUuid}`,
|
||||
);
|
||||
}
|
||||
if (dto.startTime || dto.endTime) {
|
||||
// Validate time slots first
|
||||
this.validateTimeSlot(
|
||||
dto.startTime || space.bookableConfig.startTime,
|
||||
dto.endTime || space.bookableConfig.endTime,
|
||||
);
|
||||
}
|
||||
Object.assign(space.bookableConfig, dto);
|
||||
return this.bookableSpaceEntityRepository.save(space.bookableConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch spaces by UUIDs and throw an error if any are missing
|
||||
*/
|
||||
@ -85,6 +116,7 @@ export class BookableSpaceService {
|
||||
): Promise<SpaceEntity[]> {
|
||||
const spaces = await this.spaceRepository.find({
|
||||
where: { uuid: In(spaceUuids) },
|
||||
relations: ['bookableConfig'],
|
||||
});
|
||||
|
||||
if (spaces.length !== spaceUuids.length) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { DeviceTypeEnum } from '@app/common/constants/device-type.enum';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { Transform } from 'class-transformer';
|
||||
import {
|
||||
IsArray,
|
||||
IsEnum,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
@ -74,13 +74,34 @@ export class GetDevicesFilterDto {
|
||||
@IsEnum(DeviceTypeEnum)
|
||||
@IsOptional()
|
||||
public deviceType: DeviceTypeEnum;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'List of Space IDs to filter devices',
|
||||
required: false,
|
||||
example: ['60d21b4667d0d8992e610c85', '60d21b4967d0d8992e610c86'],
|
||||
})
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@Transform(({ value }) => {
|
||||
if (!Array.isArray(value)) {
|
||||
return [value];
|
||||
}
|
||||
return value;
|
||||
})
|
||||
@IsUUID('4', { each: true })
|
||||
public spaces?: string[];
|
||||
|
||||
@ApiProperty({
|
||||
description: 'List of Community IDs to filter devices',
|
||||
required: false,
|
||||
example: ['60d21b4667d0d8992e610c85', '60d21b4967d0d8992e610c86'],
|
||||
})
|
||||
@Transform(({ value }) => {
|
||||
if (!Array.isArray(value)) {
|
||||
return [value];
|
||||
}
|
||||
return value;
|
||||
})
|
||||
@IsOptional()
|
||||
@IsUUID('4', { each: true })
|
||||
public communities?: string[];
|
||||
}
|
||||
|
@ -100,12 +100,15 @@ export class DeviceService {
|
||||
|
||||
async getAllDevices(
|
||||
param: ProjectParam,
|
||||
{ deviceType, spaces }: GetDevicesFilterDto,
|
||||
{ deviceType, spaces, communities }: GetDevicesFilterDto,
|
||||
): Promise<BaseResponseDto> {
|
||||
try {
|
||||
await this.validateProject(param.projectUuid);
|
||||
if (deviceType === DeviceTypeEnum.DOOR_LOCK) {
|
||||
return await this.getDoorLockDevices(param.projectUuid, spaces);
|
||||
return await this.getDoorLockDevices(param.projectUuid, {
|
||||
spaces,
|
||||
communities,
|
||||
});
|
||||
} else if (!deviceType) {
|
||||
const devices = await this.deviceRepository.find({
|
||||
where: {
|
||||
@ -113,7 +116,13 @@ export class DeviceService {
|
||||
spaceDevice: {
|
||||
uuid: spaces && spaces.length ? In(spaces) : undefined,
|
||||
spaceName: Not(ORPHAN_SPACE_NAME),
|
||||
community: { project: { uuid: param.projectUuid } },
|
||||
community: {
|
||||
uuid:
|
||||
communities && communities.length
|
||||
? In(communities)
|
||||
: undefined,
|
||||
project: { uuid: param.projectUuid },
|
||||
},
|
||||
},
|
||||
},
|
||||
relations: [
|
||||
@ -708,6 +717,9 @@ export class DeviceService {
|
||||
relations: ['productDevice'],
|
||||
});
|
||||
|
||||
if (!deviceDetails) {
|
||||
throw new NotFoundException('Device not found');
|
||||
}
|
||||
let result = await this.tuyaService.getDeviceDetails(deviceId);
|
||||
|
||||
if (!result) {
|
||||
@ -1247,7 +1259,10 @@ export class DeviceService {
|
||||
await this.deviceRepository.save(updatedDevices);
|
||||
}
|
||||
|
||||
private async getDoorLockDevices(projectUuid: string, spaces?: string[]) {
|
||||
private async getDoorLockDevices(
|
||||
projectUuid: string,
|
||||
{ communities, spaces }: { spaces?: string[]; communities?: string[] },
|
||||
) {
|
||||
await this.validateProject(projectUuid);
|
||||
|
||||
const devices = await this.deviceRepository.find({
|
||||
@ -1259,6 +1274,8 @@ export class DeviceService {
|
||||
spaceName: Not(ORPHAN_SPACE_NAME),
|
||||
uuid: spaces && spaces.length ? In(spaces) : undefined,
|
||||
community: {
|
||||
uuid:
|
||||
communities && communities.length ? In(communities) : undefined,
|
||||
project: {
|
||||
uuid: projectUuid,
|
||||
},
|
||||
|
Reference in New Issue
Block a user