Add endpoints and logic for fetching power clamp data by community or space

- Introduced new API endpoints to retrieve power clamp historical data based on community or space UUID.
- Updated PowerClampController to handle requests with optional parameters for community and space.
- Enhanced PowerClampService to validate input and fetch devices accordingly.
- Created ResourceParamsDto to manage request parameters.
- Updated ControllerRoute with new action summaries and descriptions.
This commit is contained in:
faris Aljohari
2025-05-07 23:09:01 +03:00
parent 5ed59e4fcc
commit 45b8cdcaae
5 changed files with 156 additions and 31 deletions

View File

@ -498,6 +498,10 @@ export class ControllerRoute {
'Get power clamp historical data'; 'Get power clamp historical data';
public static readonly GET_ENERGY_DESCRIPTION = public static readonly GET_ENERGY_DESCRIPTION =
'This endpoint retrieves the historical data of a power clamp device based on the provided parameters.'; '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 { static DEVICE = class {

View File

@ -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 { AddCommunityDto, GetCommunityParams, ProjectParam } from '../dtos';
import { UpdateCommunityNameDto } from '../dtos/update.community.dto'; import { UpdateCommunityNameDto } from '../dtos/update.community.dto';
import { BaseResponseDto } from '@app/common/dto/base.response.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 { ILike, In, Not } from 'typeorm';
import { SpaceService } from 'src/space/services'; import { SpaceService } from 'src/space/services';
import { SpaceRepository } from '@app/common/modules/space'; 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() @Injectable()
export class CommunityService { export class CommunityService {
@ -303,4 +310,58 @@ export class CommunityService {
); );
} }
} }
async getAllDevicesByCommunity(
communityUuid: string,
): Promise<DeviceEntity[]> {
// 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;
}
} }

View File

@ -4,6 +4,7 @@ import {
ApiBearerAuth, ApiBearerAuth,
ApiOperation, ApiOperation,
ApiParam, ApiParam,
ApiQuery,
} from '@nestjs/swagger'; } from '@nestjs/swagger';
import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; import { EnableDisableStatusEnum } from '@app/common/constants/days.enum';
import { ControllerRoute } from '@app/common/constants/controller-route'; import { ControllerRoute } from '@app/common/constants/controller-route';
@ -16,7 +17,7 @@ import {
import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { BaseResponseDto } from '@app/common/dto/base.response.dto';
import { import {
PowerClampParamsDto, PowerClampParamsDto,
SpaceParamsDto, ResourceParamsDto,
} from '../dto/power-clamp-params.dto'; } from '../dto/power-clamp-params.dto';
@ApiTags('Power Clamp Module') @ApiTags('Power Clamp Module')
@ -47,20 +48,32 @@ export class PowerClampController {
} }
@ApiBearerAuth() @ApiBearerAuth()
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@Get('historical/space/:spaceUuid') @Get('historical')
@ApiOperation({ @ApiOperation({
summary: ControllerRoute.PowerClamp.ACTIONS.GET_ENERGY_SUMMARY, summary:
description: ControllerRoute.PowerClamp.ACTIONS.GET_ENERGY_DESCRIPTION, 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', name: 'spaceUuid',
description: 'UUID of the Space', description: 'UUID of the Space',
required: true, required: false,
}) })
async getPowerClampBySpaceData( @ApiQuery({
@Param() params: SpaceParamsDto, name: 'communityUuid',
description: 'UUID of the Community',
required: false,
})
async getPowerClampDataBySpaceOrCommunity(
@Query() params: ResourceParamsDto,
@Query() query: GetPowerClampBySpaceDto, @Query() query: GetPowerClampBySpaceDto,
): Promise<BaseResponseDto> { ): Promise<BaseResponseDto> {
return await this.powerClampService.getPowerClampBySpaceData(params, query); return await this.powerClampService.getPowerClampDataBySpaceOrCommunity(
params,
query,
);
} }
} }

View File

@ -1,4 +1,4 @@
import { IsUUID } from 'class-validator'; import { IsNotEmpty, IsOptional, IsUUID, ValidateIf } from 'class-validator';
export class PowerClampParamsDto { export class PowerClampParamsDto {
@IsUUID('4', { message: 'Invalid UUID format' }) @IsUUID('4', { message: 'Invalid UUID format' })
@ -8,3 +8,16 @@ export class SpaceParamsDto {
@IsUUID('4', { message: 'Invalid UUID format' }) @IsUUID('4', { message: 'Invalid UUID format' })
spaceUuid: string; 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
}

View File

@ -1,4 +1,9 @@
import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import {
BadRequestException,
HttpException,
HttpStatus,
Injectable,
} from '@nestjs/common';
import { import {
GetPowerClampBySpaceDto, GetPowerClampBySpaceDto,
GetPowerClampDto, GetPowerClampDto,
@ -11,7 +16,7 @@ import {
import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
import { import {
PowerClampParamsDto, PowerClampParamsDto,
SpaceParamsDto, ResourceParamsDto,
} from '../dto/power-clamp-params.dto'; } from '../dto/power-clamp-params.dto';
import { DeviceRepository } from '@app/common/modules/device/repositories'; import { DeviceRepository } from '@app/common/modules/device/repositories';
import { SpaceDeviceService } from 'src/space/services'; 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 { SQL_PROCEDURES_PATH } from '@app/common/constants/sql-query-path';
import { filterByMonth, toMMYYYY } from '@app/common/helper/date-format'; import { filterByMonth, toMMYYYY } from '@app/common/helper/date-format';
import { ProductType } from '@app/common/constants/product-type.enum'; 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() @Injectable()
export class PowerClampService { export class PowerClampService {
@ -31,50 +38,77 @@ export class PowerClampService {
private readonly spaceDeviceService: SpaceDeviceService, private readonly spaceDeviceService: SpaceDeviceService,
private readonly sqlLoader: SqlLoaderService, private readonly sqlLoader: SqlLoaderService,
private readonly dataSource: DataSource, private readonly dataSource: DataSource,
private readonly communityService: CommunityService,
) {} ) {}
async getPowerClampBySpaceData( async getPowerClampDataBySpaceOrCommunity(
params: SpaceParamsDto, params: ResourceParamsDto,
query: GetPowerClampBySpaceDto, query: GetPowerClampBySpaceDto,
) { ): Promise<BaseResponseDto> {
const { monthDate } = query; const { monthDate } = query;
const { spaceUuid } = params; const { spaceUuid, communityUuid } = params;
try { try {
const devices = // Validate we have at least one identifier
await this.spaceDeviceService.getAllDevicesBySpace(spaceUuid); if (!spaceUuid && !communityUuid) {
console.log('devices', devices); 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 const deviceUuids = devices
.filter((device) => device.productDevice.prodType === ProductType.PC) .filter((device) => device.productDevice?.prodType === ProductType.PC)
.map((device) => device.uuid) .map((device) => device.uuid);
.join(',');
console.log('deviceUuids', deviceUuids);
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 formattedMonthDate = toMMYYYY(monthDate);
const data = await this.executeProcedure( const data = await this.executeProcedure(
'fact_daily_space_energy_consumed_procedure', '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) => ({ const formattedData = data.map((item) => ({
...item, ...item,
date: new Date(item.date).toLocaleDateString('en-CA'), // YYYY-MM-DD date: new Date(item.date).toLocaleDateString('en-CA'), // YYYY-MM-DD
})); }));
const filteredData = monthDate const resultData = monthDate
? filterByMonth(formattedData, monthDate) ? filterByMonth(formattedData, monthDate)
: formattedData; : formattedData;
return this.buildResponse( return this.buildResponse(
`Power clamp data for space ${spaceUuid} fetched successfully`, `Power clamp data fetched successfully for ${spaceUuid ? 'space' : 'community'}`,
filteredData, resultData,
); );
} catch (error) { } catch (error) {
console.error('Error fetching power clamp data', {
error,
spaceUuid,
communityUuid,
});
throw new HttpException( 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, error.status || HttpStatus.INTERNAL_SERVER_ERROR,
); );
} }