Merge pull request #249 from SyncrowIOT/origin/SP-1162-BE-Missing-Spaces-name-in-Device-Table-When-Fetching-from-Community-in-the-device-management-screen

added battery and space details
This commit is contained in:
hannathkadher
2025-02-04 23:00:18 +04:00
committed by GitHub
3 changed files with 184 additions and 39 deletions

View File

@ -18,4 +18,5 @@ export enum ProductType {
PC = 'PC', PC = 'PC',
FOUR_S = '4S', FOUR_S = '4S',
SIX_S = '6S', SIX_S = '6S',
SOS = 'SOS',
} }

View File

@ -8,6 +8,8 @@ import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter'; import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter';
import { ValidationService } from './space-validation.service'; import { ValidationService } from './space-validation.service';
import { ProductType } from '@app/common/constants/product-type.enum';
import { BatteryStatus } from '@app/common/constants/battery-status.enum';
@Injectable() @Injectable()
export class SpaceDeviceService { export class SpaceDeviceService {
@ -18,54 +20,35 @@ export class SpaceDeviceService {
async listDevicesInSpace(params: GetSpaceParam): Promise<BaseResponseDto> { async listDevicesInSpace(params: GetSpaceParam): Promise<BaseResponseDto> {
const { spaceUuid, communityUuid, projectUuid } = params; const { spaceUuid, communityUuid, projectUuid } = params;
try {
const space =
await this.validationService.validateSpaceWithinCommunityAndProject(
communityUuid,
projectUuid,
spaceUuid,
);
if (!space.devices || space.devices.length === 0) { try {
// Validate community, project, and fetch space including devices in a single query
const space = await this.validationService.fetchSpaceDevices(spaceUuid);
if (!space || !space.devices?.length) {
throw new HttpException( throw new HttpException(
'The space does not contain any devices.', 'The space does not contain any devices.',
HttpStatus.BAD_REQUEST, HttpStatus.BAD_REQUEST,
); );
} }
const safeFetch = async (device: any) => {
try {
const tuyaDetails = await this.getDeviceDetailsByDeviceIdTuya(
device.deviceTuyaUuid,
);
const tuyaDeviceStatus =
await this.tuyaService.getDevicesInstructionStatusTuya(
device.deviceTuyaUuid,
);
return {
uuid: device.uuid,
deviceTuyaUuid: device.deviceTuyaUuid,
productUuid: device.productDevice.uuid,
productType: device.productDevice.prodType,
isActive: device.isActive,
updatedAt: device.updatedAt,
deviceTag: device.tag,
subspace: device.subspace,
...tuyaDetails,
status: tuyaDeviceStatus.result[0].status,
};
} catch (error) {
console.warn(
`Skipping device with deviceTuyaUuid: ${device.deviceTuyaUuid} due to error. ${error}`,
);
return null;
}
};
const detailedDevices = await Promise.all(space.devices.map(safeFetch)); // Fetch space hierarchy **once** and reverse it to get an ordered hierarchy
const spaceHierarchy =
await this.validationService.getFullSpaceHierarchy(space);
const orderedHierarchy = spaceHierarchy.reverse();
// Fetch Tuya details for each device in parallel using Promise.allSettled
const deviceDetailsPromises = space.devices.map((device) =>
this.fetchDeviceDetails(device, orderedHierarchy),
);
const detailedDevices = (await Promise.allSettled(deviceDetailsPromises))
.filter((result) => result.status === 'fulfilled' && result.value)
.map((result) => (result as PromiseFulfilledResult<any>).value);
return new SuccessResponseDto({ return new SuccessResponseDto({
data: detailedDevices.filter(Boolean), data: detailedDevices,
message: 'Successfully retrieved list of devices', message: 'Successfully retrieved list of devices.',
}); });
} catch (error) { } catch (error) {
console.error('Error listing devices in space:', error); console.error('Error listing devices in space:', error);
@ -76,6 +59,70 @@ export class SpaceDeviceService {
} }
} }
private async fetchDeviceDetails(device: any, orderedHierarchy: any[]) {
try {
// Fetch Tuya details in parallel
const [tuyaDetails, tuyaDeviceStatusResponse] = await Promise.all([
this.getDeviceDetailsByDeviceIdTuya(device.deviceTuyaUuid),
this.tuyaService.getDevicesInstructionStatusTuya(device.deviceTuyaUuid),
]);
const tuyaStatusList =
tuyaDeviceStatusResponse?.result?.[0]?.status || [];
return {
spaces: orderedHierarchy.map((space) => ({
uuid: space.uuid,
spaceName: space.spaceName,
})),
uuid: device.uuid,
deviceTuyaUuid: device.deviceTuyaUuid,
productUuid: device.productDevice?.uuid || null,
productType: device.productDevice?.prodType || null,
isActive: device.isActive,
updatedAt: device.updatedAt,
deviceTag: device.tag,
subspace: device.subspace,
...tuyaDetails,
...(this.extractBatteryStatus(
device.productDevice?.prodType,
tuyaStatusList,
) !== null && {
battery: this.extractBatteryStatus(
device.productDevice?.prodType,
tuyaStatusList,
),
}),
status: tuyaStatusList,
};
} catch (error) {
console.warn(
`Skipping device ${device.deviceTuyaUuid} due to error: ${error.message}`,
);
return null;
}
}
private extractBatteryStatus(
deviceType: string,
tuyaStatus: any[],
): number | null {
const batteryCodes = {
[ProductType.DL]: BatteryStatus.RESIDUAL_ELECTRICITY,
[ProductType.DS]: BatteryStatus.BATTERY_PERCENTAGE,
[ProductType.WL]: BatteryStatus.BATTERY_PERCENTAGE,
[ProductType.SOS]: BatteryStatus.BATTERY_PERCENTAGE,
};
const batteryCode = batteryCodes[deviceType];
if (!batteryCode) return null;
const batteryStatus = tuyaStatus.find(
(status) => status.code === batteryCode,
);
return batteryStatus ? batteryStatus.value : null;
}
private async getDeviceDetailsByDeviceIdTuya( private async getDeviceDetailsByDeviceIdTuya(
deviceId: string, deviceId: string,
): Promise<GetDeviceDetailsInterface> { ): Promise<GetDeviceDetailsInterface> {

View File

@ -104,6 +104,26 @@ export class ValidationService {
return space; return space;
} }
async fetchSpaceDevices(spaceUuid: string): Promise<SpaceEntity> {
const space = await this.spaceRepository.findOne({
where: { uuid: spaceUuid, disabled: false },
relations: [
'devices',
'devices.productDevice',
'devices.tag',
'devices.subspace',
],
});
if (!space) {
throw new HttpException(
`Space with UUID ${spaceUuid} not found`,
HttpStatus.NOT_FOUND,
);
}
return space;
}
async validateSpaceModel(spaceModelUuid: string): Promise<SpaceModelEntity> { async validateSpaceModel(spaceModelUuid: string): Promise<SpaceModelEntity> {
const spaceModel = await this.spaceModelRepository.findOne({ const spaceModel = await this.spaceModelRepository.findOne({
where: { uuid: spaceModelUuid }, where: { uuid: spaceModelUuid },
@ -125,4 +145,81 @@ export class ValidationService {
return spaceModel; return spaceModel;
} }
async getFullSpaceHierarchy(
space: SpaceEntity,
): Promise<{ uuid: string; spaceName: string }[]> {
try {
// Fetch only the relevant spaces, starting with the target space
const targetSpace = await this.spaceRepository.findOne({
where: { uuid: space.uuid },
relations: ['parent', 'children'],
});
// Fetch only the ancestors of the target space
const ancestors = await this.fetchAncestors(targetSpace);
// Optionally, fetch descendants if required
const descendants = await this.fetchDescendants(targetSpace);
const fullHierarchy = [...ancestors, targetSpace, ...descendants].map(
(space) => ({
uuid: space.uuid,
spaceName: space.spaceName,
}),
);
return fullHierarchy;
} catch (error) {
console.error('Error fetching space hierarchy:', error.message);
throw new HttpException(
'Error fetching space hierarchy',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
private async fetchAncestors(space: SpaceEntity): Promise<SpaceEntity[]> {
const ancestors: SpaceEntity[] = [];
let currentSpace = space;
while (currentSpace && currentSpace.parent) {
// Fetch the parent space
const parent = await this.spaceRepository.findOne({
where: { uuid: currentSpace.parent.uuid },
relations: ['parent'], // To continue fetching upwards
});
if (parent) {
ancestors.push(parent);
currentSpace = parent;
} else {
currentSpace = null;
}
}
// Return the ancestors in reverse order to have the root at the start
return ancestors.reverse();
}
private async fetchDescendants(space: SpaceEntity): Promise<SpaceEntity[]> {
const descendants: SpaceEntity[] = [];
// Fetch the immediate children of the current space
const children = await this.spaceRepository.find({
where: { parent: { uuid: space.uuid } },
relations: ['children'], // To continue fetching downwards
});
for (const child of children) {
// Add the child to the descendants list
descendants.push(child);
// Recursively fetch the child's descendants
const childDescendants = await this.fetchDescendants(child);
descendants.push(...childDescendants);
}
return descendants;
}
} }