diff --git a/libs/common/src/firebase/devices-status/services/devices-status.service.ts b/libs/common/src/firebase/devices-status/services/devices-status.service.ts index f01b0be..18c752c 100644 --- a/libs/common/src/firebase/devices-status/services/devices-status.service.ts +++ b/libs/common/src/firebase/devices-status/services/devices-status.service.ts @@ -257,6 +257,9 @@ export class DeviceStatusFirebaseService { await this.occupancyService.updateOccupancySensorHistoricalData( addDeviceStatusDto.deviceUuid, ); + await this.occupancyService.updateOccupancySensorHistoricalDurationData( + addDeviceStatusDto.deviceUuid, + ); } } diff --git a/libs/common/src/helper/services/occupancy.service.ts b/libs/common/src/helper/services/occupancy.service.ts index 3e8fc7c..ea99b7c 100644 --- a/libs/common/src/helper/services/occupancy.service.ts +++ b/libs/common/src/helper/services/occupancy.service.ts @@ -11,7 +11,27 @@ export class OccupancyService { private readonly dataSource: DataSource, private readonly deviceRepository: DeviceRepository, ) {} + async updateOccupancySensorHistoricalDurationData( + deviceUuid: string, + ): Promise { + try { + const now = new Date(); + const dateStr = now.toLocaleDateString('en-CA'); // YYYY-MM-DD + const device = await this.deviceRepository.findOne({ + where: { uuid: deviceUuid }, + relations: ['spaceDevice'], + }); + await this.executeProcedure( + 'fact_daily_space_occupancy_duration', + 'procedure_update_daily_space_occupancy_duration', + [dateStr, device.spaceDevice?.uuid], + ); + } catch (err) { + console.error('Failed to insert or update occupancy duration data:', err); + throw err; + } + } async updateOccupancySensorHistoricalData(deviceUuid: string): Promise { try { const now = new Date(); @@ -21,10 +41,11 @@ export class OccupancyService { relations: ['spaceDevice'], }); - await this.executeProcedure('procedure_update_fact_space_occupancy', [ - dateStr, - device.spaceDevice?.uuid, - ]); + await this.executeProcedure( + 'fact_space_occupancy_count', + 'procedure_update_fact_space_occupancy', + [dateStr, device.spaceDevice?.uuid], + ); } catch (err) { console.error('Failed to insert or update occupancy data:', err); throw err; @@ -32,13 +53,11 @@ export class OccupancyService { } private async executeProcedure( + procedureFolderName: string, procedureFileName: string, params: (string | number | null)[], ): Promise { - const query = this.loadQuery( - 'fact_space_occupancy_count', - procedureFileName, - ); + const query = this.loadQuery(procedureFolderName, procedureFileName); await this.dataSource.query(query, params); console.log(`Procedure ${procedureFileName} executed successfully.`); } diff --git a/src/occupancy/controllers/occupancy.controller.ts b/src/occupancy/controllers/occupancy.controller.ts index d6aa9b1..c8ff3aa 100644 --- a/src/occupancy/controllers/occupancy.controller.ts +++ b/src/occupancy/controllers/occupancy.controller.ts @@ -9,7 +9,10 @@ import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; import { ControllerRoute } from '@app/common/constants/controller-route'; import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { OccupancyService } from '../services/occupancy.service'; -import { GetOccupancyHeatMapBySpaceDto } from '../dto/get-occupancy.dto'; +import { + GetOccupancyDurationBySpaceDto, + GetOccupancyHeatMapBySpaceDto, +} from '../dto/get-occupancy.dto'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { SpaceParamsDto } from '../dto/occupancy-params.dto'; @@ -43,4 +46,26 @@ export class OccupancyController { query, ); } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Get('duration/space/:spaceUuid') + @ApiOperation({ + summary: ControllerRoute.Occupancy.ACTIONS.GET_OCCUPANCY_HEAT_MAP_SUMMARY, + description: + ControllerRoute.Occupancy.ACTIONS.GET_OCCUPANCY_HEAT_MAP_DESCRIPTION, + }) + @ApiParam({ + name: 'spaceUuid', + description: 'UUID of the Space', + required: true, + }) + async getOccupancyDurationDataBySpace( + @Param() params: SpaceParamsDto, + @Query() query: GetOccupancyDurationBySpaceDto, + ): Promise { + return await this.occupancyService.getOccupancyDurationDataBySpace( + params, + query, + ); + } } diff --git a/src/occupancy/dto/get-occupancy.dto.ts b/src/occupancy/dto/get-occupancy.dto.ts index 26ea0a5..7e2fb38 100644 --- a/src/occupancy/dto/get-occupancy.dto.ts +++ b/src/occupancy/dto/get-occupancy.dto.ts @@ -13,3 +13,15 @@ export class GetOccupancyHeatMapBySpaceDto { }) year: string; } +export class GetOccupancyDurationBySpaceDto { + @ApiPropertyOptional({ + description: 'Month and year in format YYYY-MM', + example: '2025-03', + required: true, + }) + @Matches(/^\d{4}-(0[1-9]|1[0-2])$/, { + message: 'monthDate must be in YYYY-MM format', + }) + @IsNotEmpty() + monthDate: string; +} diff --git a/src/occupancy/services/occupancy.service.ts b/src/occupancy/services/occupancy.service.ts index 59afe18..6eec631 100644 --- a/src/occupancy/services/occupancy.service.ts +++ b/src/occupancy/services/occupancy.service.ts @@ -1,5 +1,8 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; -import { GetOccupancyHeatMapBySpaceDto } from '../dto/get-occupancy.dto'; +import { + GetOccupancyDurationBySpaceDto, + GetOccupancyHeatMapBySpaceDto, +} from '../dto/get-occupancy.dto'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { SpaceParamsDto } from '../dto/occupancy-params.dto'; import { SqlLoaderService } from '@app/common/helper/services/sql-loader.service'; @@ -14,6 +17,38 @@ export class OccupancyService { private readonly dataSource: DataSource, ) {} + async getOccupancyDurationDataBySpace( + params: SpaceParamsDto, + query: GetOccupancyDurationBySpaceDto, + ): Promise { + const { monthDate } = query; + const { spaceUuid } = params; + + try { + const data = await this.executeProcedure( + 'fact_daily_space_occupancy_duration', + 'procedure_select_daily_space_occupancy_duration', + [spaceUuid, monthDate], + ); + const formattedData = data.map((item) => ({ + ...item, + event_date: new Date(item.event_date).toLocaleDateString('en-CA'), // YYYY-MM-DD + })); + return this.buildResponse( + `Occupancy duration data fetched successfully for ${spaceUuid} space`, + formattedData, + ); + } catch (error) { + console.error('Failed to fetch occupancy duration data', { + error, + spaceUuid, + }); + throw new HttpException( + error.response?.message || 'Failed to fetch occupancy duration data', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } async getOccupancyHeatMapDataBySpace( params: SpaceParamsDto, query: GetOccupancyHeatMapBySpaceDto, @@ -23,6 +58,7 @@ export class OccupancyService { try { const data = await this.executeProcedure( + 'fact_space_occupancy_count', 'proceduce_select_fact_space_occupancy', [spaceUuid, year], ); @@ -31,7 +67,7 @@ export class OccupancyService { event_date: new Date(item.event_date).toLocaleDateString('en-CA'), // YYYY-MM-DD })); return this.buildResponse( - `Occupancy heat map data fetched successfully for ${spaceUuid ? 'space' : 'community'}`, + `Occupancy heat map data fetched successfully for ${spaceUuid} space`, formattedData, ); } catch (error) { @@ -54,13 +90,11 @@ export class OccupancyService { }); } private async executeProcedure( + procedureFolderName: string, procedureFileName: string, params: (string | number | null)[], ): Promise { - const query = this.loadQuery( - 'fact_space_occupancy_count', - procedureFileName, - ); + const query = this.loadQuery(procedureFolderName, procedureFileName); return await this.dataSource.query(query, params); } private loadQuery(folderName: string, fileName: string): string {