diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index dcc7b5e..852c4a8 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -498,6 +498,10 @@ export class ControllerRoute { 'Get power clamp historical data'; public static readonly GET_ENERGY_DESCRIPTION = 'This endpoint retrieves the historical data of a power clamp device based on the provided parameters.'; + public static readonly GET_ENERGY_BY_COMMUNITY_OR_SPACE_SUMMARY = + 'Get power clamp historical data by community or space'; + public static readonly GET_ENERGY_BY_COMMUNITY_OR_SPACE_DESCRIPTION = + 'This endpoint retrieves the historical data of power clamp devices based on the provided community or space UUID.'; }; }; static DEVICE = class { diff --git a/src/community/services/community.service.ts b/src/community/services/community.service.ts index 4debb98..cb166e7 100644 --- a/src/community/services/community.service.ts +++ b/src/community/services/community.service.ts @@ -1,4 +1,9 @@ -import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; +import { + Injectable, + HttpException, + HttpStatus, + NotFoundException, +} from '@nestjs/common'; import { AddCommunityDto, GetCommunityParams, ProjectParam } from '../dtos'; import { UpdateCommunityNameDto } from '../dtos/update.community.dto'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; @@ -16,6 +21,8 @@ import { ORPHAN_COMMUNITY_NAME } from '@app/common/constants/orphan-constant'; import { ILike, In, Not } from 'typeorm'; import { SpaceService } from 'src/space/services'; import { SpaceRepository } from '@app/common/modules/space'; +import { DeviceEntity } from '@app/common/modules/device/entities'; +import { SpaceEntity } from '@app/common/modules/space/entities/space.entity'; @Injectable() export class CommunityService { @@ -303,4 +310,58 @@ export class CommunityService { ); } } + async getAllDevicesByCommunity( + communityUuid: string, + ): Promise { + // Fetch the community and its top-level spaces + const community = await this.communityRepository.findOne({ + where: { uuid: communityUuid }, + relations: [ + 'spaces', + 'spaces.children', + 'spaces.devices', + 'spaces.devices.productDevice', + ], + }); + + if (!community) { + throw new NotFoundException('Community not found'); + } + + const allDevices: DeviceEntity[] = []; + + // Recursive fetch function for spaces + const fetchSpaceDevices = async (space: SpaceEntity) => { + if (space.devices && space.devices.length > 0) { + allDevices.push(...space.devices); + } + + if (space.children && space.children.length > 0) { + for (const childSpace of space.children) { + const fullChildSpace = await this.spaceRepository.findOne({ + where: { uuid: childSpace.uuid }, + relations: ['children', 'devices', 'devices.productDevice'], + }); + + if (fullChildSpace) { + await fetchSpaceDevices(fullChildSpace); + } + } + } + }; + + // Start recursive fetch for all top-level spaces + for (const space of community.spaces) { + const fullSpace = await this.spaceRepository.findOne({ + where: { uuid: space.uuid }, + relations: ['children', 'devices', 'devices.productDevice'], + }); + + if (fullSpace) { + await fetchSpaceDevices(fullSpace); + } + } + + return allDevices; + } } diff --git a/src/power-clamp/controllers/power-clamp.controller.ts b/src/power-clamp/controllers/power-clamp.controller.ts index 0f5dfdc..eb89d11 100644 --- a/src/power-clamp/controllers/power-clamp.controller.ts +++ b/src/power-clamp/controllers/power-clamp.controller.ts @@ -4,6 +4,7 @@ import { ApiBearerAuth, ApiOperation, ApiParam, + ApiQuery, } from '@nestjs/swagger'; import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; import { ControllerRoute } from '@app/common/constants/controller-route'; @@ -16,7 +17,7 @@ import { import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { PowerClampParamsDto, - SpaceParamsDto, + ResourceParamsDto, } from '../dto/power-clamp-params.dto'; @ApiTags('Power Clamp Module') @@ -47,20 +48,32 @@ export class PowerClampController { } @ApiBearerAuth() @UseGuards(JwtAuthGuard) - @Get('historical/space/:spaceUuid') + @Get('historical') @ApiOperation({ - summary: ControllerRoute.PowerClamp.ACTIONS.GET_ENERGY_SUMMARY, - description: ControllerRoute.PowerClamp.ACTIONS.GET_ENERGY_DESCRIPTION, + summary: + ControllerRoute.PowerClamp.ACTIONS + .GET_ENERGY_BY_COMMUNITY_OR_SPACE_SUMMARY, + description: + ControllerRoute.PowerClamp.ACTIONS + .GET_ENERGY_BY_COMMUNITY_OR_SPACE_DESCRIPTION, }) - @ApiParam({ + @ApiQuery({ name: 'spaceUuid', description: 'UUID of the Space', - required: true, + required: false, }) - async getPowerClampBySpaceData( - @Param() params: SpaceParamsDto, + @ApiQuery({ + name: 'communityUuid', + description: 'UUID of the Community', + required: false, + }) + async getPowerClampDataBySpaceOrCommunity( + @Query() params: ResourceParamsDto, @Query() query: GetPowerClampBySpaceDto, ): Promise { - return await this.powerClampService.getPowerClampBySpaceData(params, query); + return await this.powerClampService.getPowerClampDataBySpaceOrCommunity( + params, + query, + ); } } diff --git a/src/power-clamp/dto/power-clamp-params.dto.ts b/src/power-clamp/dto/power-clamp-params.dto.ts index 586eee6..7250540 100644 --- a/src/power-clamp/dto/power-clamp-params.dto.ts +++ b/src/power-clamp/dto/power-clamp-params.dto.ts @@ -1,4 +1,4 @@ -import { IsUUID } from 'class-validator'; +import { IsNotEmpty, IsOptional, IsUUID, ValidateIf } from 'class-validator'; export class PowerClampParamsDto { @IsUUID('4', { message: 'Invalid UUID format' }) @@ -8,3 +8,16 @@ export class SpaceParamsDto { @IsUUID('4', { message: 'Invalid UUID format' }) spaceUuid: string; } +export class ResourceParamsDto { + @IsUUID('4', { message: 'Invalid UUID format' }) + @IsOptional() + spaceUuid?: string; + + @IsUUID('4', { message: 'Invalid UUID format' }) + @IsOptional() + communityUuid?: string; + + @ValidateIf((o) => !o.spaceUuid && !o.communityUuid) + @IsNotEmpty({ message: 'Either spaceUuid or communityUuid must be provided' }) + requireEither?: never; // This ensures at least one of them is provided +} diff --git a/src/power-clamp/services/power-clamp.service.ts b/src/power-clamp/services/power-clamp.service.ts index b502186..24bca95 100644 --- a/src/power-clamp/services/power-clamp.service.ts +++ b/src/power-clamp/services/power-clamp.service.ts @@ -1,4 +1,9 @@ -import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { + BadRequestException, + HttpException, + HttpStatus, + Injectable, +} from '@nestjs/common'; import { GetPowerClampBySpaceDto, GetPowerClampDto, @@ -11,7 +16,7 @@ import { import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { PowerClampParamsDto, - SpaceParamsDto, + ResourceParamsDto, } from '../dto/power-clamp-params.dto'; import { DeviceRepository } from '@app/common/modules/device/repositories'; import { SpaceDeviceService } from 'src/space/services'; @@ -20,6 +25,8 @@ import { DataSource } from 'typeorm'; import { SQL_PROCEDURES_PATH } from '@app/common/constants/sql-query-path'; import { filterByMonth, toMMYYYY } from '@app/common/helper/date-format'; import { ProductType } from '@app/common/constants/product-type.enum'; +import { CommunityService } from 'src/community/services'; +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; @Injectable() export class PowerClampService { @@ -31,50 +38,77 @@ export class PowerClampService { private readonly spaceDeviceService: SpaceDeviceService, private readonly sqlLoader: SqlLoaderService, private readonly dataSource: DataSource, + private readonly communityService: CommunityService, ) {} - async getPowerClampBySpaceData( - params: SpaceParamsDto, + async getPowerClampDataBySpaceOrCommunity( + params: ResourceParamsDto, query: GetPowerClampBySpaceDto, - ) { + ): Promise { const { monthDate } = query; - const { spaceUuid } = params; + const { spaceUuid, communityUuid } = params; try { - const devices = - await this.spaceDeviceService.getAllDevicesBySpace(spaceUuid); - console.log('devices', devices); + // Validate we have at least one identifier + if (!spaceUuid && !communityUuid) { + throw new BadRequestException( + 'Either spaceUuid or communityUuid must be provided', + ); + } + // Get devices based on space or community + const devices = spaceUuid + ? await this.spaceDeviceService.getAllDevicesBySpace(spaceUuid) + : await this.communityService.getAllDevicesByCommunity(communityUuid); + + if (!devices?.length) { + return this.buildResponse( + 'No power clamp devices found for the specified criteria', + [], + ); + } + + // Filter and prepare device UUIDs const deviceUuids = devices - .filter((device) => device.productDevice.prodType === ProductType.PC) - .map((device) => device.uuid) - .join(','); - console.log('deviceUuids', deviceUuids); + .filter((device) => device.productDevice?.prodType === ProductType.PC) + .map((device) => device.uuid); + if (deviceUuids.length === 0) { + return this.buildResponse( + 'No power clamp devices (PC type) found for the specified criteria', + [], + ); + } + + // Execute procedure const formattedMonthDate = toMMYYYY(monthDate); - const data = await this.executeProcedure( 'fact_daily_space_energy_consumed_procedure', - [formattedMonthDate, deviceUuids], + [formattedMonthDate, deviceUuids.join(',')], ); - // Format date to YYYY-MM-DD + // Format and filter data const formattedData = data.map((item) => ({ ...item, date: new Date(item.date).toLocaleDateString('en-CA'), // YYYY-MM-DD })); - const filteredData = monthDate + const resultData = monthDate ? filterByMonth(formattedData, monthDate) : formattedData; return this.buildResponse( - `Power clamp data for space ${spaceUuid} fetched successfully`, - filteredData, + `Power clamp data fetched successfully for ${spaceUuid ? 'space' : 'community'}`, + resultData, ); } catch (error) { + console.error('Error fetching power clamp data', { + error, + spaceUuid, + communityUuid, + }); throw new HttpException( - error.message || 'Error fetching power clamp data by space', + error.response?.message || 'Failed to fetch power clamp data', error.status || HttpStatus.INTERNAL_SERVER_ERROR, ); }