mirror of
https://github.com/SyncrowIOT/backend.git
synced 2025-07-16 18:56:22 +00:00
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:
@ -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',
|
||||||
}
|
}
|
||||||
|
@ -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> {
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user