merge dev to main

This commit is contained in:
faris Aljohari
2025-06-14 15:18:20 -06:00
parent 568eef8119
commit 72753b6dfb
18 changed files with 239 additions and 6 deletions

View File

@ -0,0 +1,38 @@
export function toDDMMYYYY(dateString?: string | null): string | null {
if (!dateString) return null;
// Ensure dateString is valid format YYYY-MM-DD
const regex = /^\d{4}-\d{2}-\d{2}$/;
if (!regex.test(dateString)) {
throw new Error(
`Invalid date format: ${dateString}. Expected format is YYYY-MM-DD`,
);
}
const [year, month, day] = dateString.split('-');
return `${day}-${month}-${year}`;
}
export function toMMYYYY(dateString?: string | null): string | null {
if (!dateString) return null;
// Ensure dateString is valid format YYYY-MM
const regex = /^\d{4}-\d{2}$/;
if (!regex.test(dateString)) {
throw new Error(
`Invalid date format: ${dateString}. Expected format is YYYY-MM`,
);
}
const [year, month] = dateString.split('-');
return `${month}-${year}`;
}
export function filterByMonth(data: any[], monthDate: string) {
const [year, month] = monthDate.split('-').map(Number);
return data.filter((item) => {
const itemDate = new Date(item.date);
return (
itemDate.getUTCFullYear() === year && itemDate.getUTCMonth() + 1 === month
);
});
}

View File

@ -0,0 +1 @@
export * from './presence-sensor.dto';

View File

@ -0,0 +1,27 @@
import { IsNotEmpty, IsNumber, IsString } from 'class-validator';
export class PresenceSensorDto {
@IsString()
@IsNotEmpty()
public uuid: string;
@IsString()
@IsNotEmpty()
public deviceUuid: string;
@IsString()
@IsNotEmpty()
public eventDate: string;
@IsNumber()
@IsNotEmpty()
public CountMotionDetected: number;
@IsNumber()
@IsNotEmpty()
public CountPresenceDetected: number;
@IsNumber()
@IsNotEmpty()
public CountTotalPresenceDetected: number;
}

View File

@ -0,0 +1 @@
export * from './presence-sensor.entity';

View File

@ -0,0 +1,58 @@
import { Column, Entity, ManyToOne, Unique } from 'typeorm';
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
import { PresenceSensorDto } from '../dtos';
import { DeviceEntity } from '../../device/entities/device.entity';
import { SpaceEntity } from '../../space/entities/space.entity';
@Entity({ name: 'presence-sensor-daily-device-detection' })
@Unique(['deviceUuid', 'eventDate'])
export class PresenceSensorDailyDeviceEntity extends AbstractEntity<PresenceSensorDto> {
@Column({ nullable: false })
public deviceUuid: string;
@Column({ nullable: false, type: 'date' })
public eventDate: string;
@Column({ nullable: false })
public CountMotionDetected: number;
@Column({ nullable: false })
public CountPresenceDetected: number;
@Column({ nullable: false })
public CountTotalPresenceDetected: number;
@ManyToOne(() => DeviceEntity, (device) => device.presenceSensorDaily)
device: DeviceEntity;
constructor(partial: Partial<PresenceSensorDailyDeviceEntity>) {
super();
Object.assign(this, partial);
}
}
@Entity({ name: 'presence-sensor-daily-space-detection' })
@Unique(['spaceUuid', 'eventDate'])
export class PresenceSensorDailySpaceEntity extends AbstractEntity<PresenceSensorDto> {
@Column({ nullable: false })
public spaceUuid: string;
@Column({ nullable: false, type: 'date' })
public eventDate: string;
@Column({ nullable: false })
public CountMotionDetected: number;
@Column({ nullable: false })
public CountPresenceDetected: number;
@Column({ nullable: false })
public CountTotalPresenceDetected: number;
@ManyToOne(() => SpaceEntity, (space) => space.presenceSensorDaily)
space: SpaceEntity;
constructor(partial: Partial<PresenceSensorDailySpaceEntity>) {
super();
Object.assign(this, partial);
}
}

View File

@ -0,0 +1 @@
export * from './presence-sensor.repository';

View File

@ -0,0 +1,20 @@
WITH params AS (
SELECT
TO_DATE(NULLIF($1, ''), 'MM-YYYY') AS month,
string_to_array(NULLIF($2, ''), ',') AS device_ids
)
SELECT
A.date,
SUM(A.energy_consumed_kW::numeric) AS total_energy_consumed_KW,
SUM(A.energy_consumed_A::numeric) AS total_energy_consumed_A,
SUM(A.energy_consumed_B::numeric) AS total_energy_consumed_B,
SUM(A.energy_consumed_C::numeric) AS total_energy_consumed_C
FROM public."power-clamp-energy-consumed-daily" AS A
JOIN public.device AS B
ON A.device_uuid::TEXT = B."uuid"::TEXT
JOIN params P ON TRUE
WHERE B."uuid"::TEXT = ANY(P.device_ids)
AND (P.month IS NULL OR date_trunc('month', A.date)= P.month)
GROUP BY A.date
ORDER BY A.date;

View File

@ -0,0 +1,21 @@
WITH params AS (
SELECT
TO_DATE(NULLIF($1, ''), 'DD-MM-YYYY') AS start_date,
TO_DATE(NULLIF($2, ''), 'DD-MM-YYYY') AS end_date,
string_to_array(NULLIF($3, ''), ',') AS device_ids
)
SELECT TO_CHAR(A.date, 'MM-YYYY') AS month,
SUM(A.energy_consumed_kW::numeric) AS total_energy_consumed_KW,
SUM(A.energy_consumed_A::numeric) AS total_energy_consumed_A,
SUM(A.energy_consumed_B::numeric) AS total_energy_consumed_B,
SUM(A.energy_consumed_C::numeric) AS total_energy_consumed_C
FROM public."power-clamp-energy-consumed-daily" AS A
JOIN public.device AS B
ON A.device_uuid::TEXT = B."uuid"::TEXT
JOIN params P ON TRUE
WHERE B."uuid"::TEXT = ANY(P.device_ids)
AND (P.start_date IS NULL OR A.date >= P.start_date)
AND (P.end_date IS NULL OR A.date <= P.end_date)
GROUP BY 1

View File

@ -0,0 +1,11 @@
SELECT
B.space_device_uuid AS space_id,
A."date",
SUM(A.energy_consumed_kW::numeric) AS total_energy_consumed_KW,
SUM(A.energy_consumed_A::numeric) AS total_energy_consumed_A,
SUM(A.energy_consumed_B::numeric) AS total_energy_consumed_B,
SUM(A.energy_consumed_C::numeric) AS total_energy_consumed_C
FROM public."power-clamp-energy-consumed-daily" AS A -- I want to change the source table in the future.
JOIN public.device AS B
ON A.device_uuid::TEXT = B."uuid"::TEXT
GROUP BY 1, 2;

View File

@ -1,6 +1,23 @@
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' })
powerClampUuid: string; powerClampUuid: string;
} }
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
}

View File

@ -65,7 +65,7 @@ import { AqiDataService } from '@app/common/helper/services/aqi.data.service';
imports: [ConfigModule], imports: [ConfigModule],
controllers: [PowerClampController], controllers: [PowerClampController],
providers: [ providers: [
PowerClampService, PowerClamp,
PowerClampDailyRepository, PowerClampDailyRepository,
PowerClampHourlyRepository, PowerClampHourlyRepository,
PowerClampMonthlyRepository, PowerClampMonthlyRepository,

View File

@ -1,13 +1,32 @@
import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import {
import { GetPowerClampDto } from '../dto/get-power-clamp.dto'; BadRequestException,
HttpException,
HttpStatus,
Injectable,
} from '@nestjs/common';
import {
GetPowerClampBySpaceDto,
GetPowerClampDto,
} from '../dto/get-power-clamp.dto';
import { import {
PowerClampDailyRepository, PowerClampDailyRepository,
PowerClampHourlyRepository, PowerClampHourlyRepository,
PowerClampMonthlyRepository, PowerClampMonthlyRepository,
} from '@app/common/modules/power-clamp/repositories'; } from '@app/common/modules/power-clamp/repositories';
import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
import { PowerClampParamsDto } from '../dto/power-clamp-params.dto'; import {
PowerClampParamsDto,
ResourceParamsDto,
} 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 { SqlLoaderService } from '@app/common/helper/services/sql-loader.service';
import { DataSource } from 'typeorm';
import { SQL_PROCEDURES_PATH } from '@app/common/constants/sql-query-path';
import { ProductType } from '@app/common/constants/product-type.enum';
import { CommunityService } from 'src/community/services';
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
import { filterByMonth, toMMYYYY } from '@app/common/helper/date-format';
@Injectable() @Injectable()
export class PowerClampService { export class PowerClampService {
@ -210,4 +229,17 @@ export class PowerClampService {
statusCode: HttpStatus.OK, statusCode: HttpStatus.OK,
}); });
} }
private async executeProcedure(
procedureFileName: string,
params: (string | number | null)[],
): Promise<any[]> {
const query = this.loadQuery(
'fact_space_energy_consumed',
procedureFileName,
);
return await this.dataSource.query(query, params);
}
private loadQuery(folderName: string, fileName: string): string {
return this.sqlLoader.loadQuery(folderName, fileName, SQL_PROCEDURES_PATH);
}
} }

View File

@ -1,6 +1,11 @@
import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service'; import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service';
import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import {
HttpException,
HttpStatus,
Injectable,
NotFoundException,
} from '@nestjs/common';
import { GetSpaceParam } from '../dtos'; import { GetSpaceParam } from '../dtos';
import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { BaseResponseDto } from '@app/common/dto/base.response.dto';
import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
@ -21,6 +26,7 @@ export class SpaceDeviceService {
private readonly tuyaService: TuyaService, private readonly tuyaService: TuyaService,
private readonly validationService: ValidationService, private readonly validationService: ValidationService,
private readonly deviceService: DeviceService, private readonly deviceService: DeviceService,
private readonly spaceRepository: SpaceRepository,
) {} ) {}
async listDevicesInSpace( async listDevicesInSpace(