Files
backend/src/commission-device/services/commission-device.service.ts

227 lines
7.0 KiB
TypeScript

import * as csv from 'csv-parser';
import * as fs from 'fs';
import { ProjectParam } from '@app/common/dto/project-param.dto';
import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
import { DeviceStatusFirebaseService } from '@app/common/firebase/devices-status/services/devices-status.service';
import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service';
import { CommunityRepository } from '@app/common/modules/community/repositories';
import { DeviceRepository } from '@app/common/modules/device/repositories';
import { ProjectRepository } from '@app/common/modules/project/repositiories';
import { SpaceRepository } from '@app/common/modules/space';
import { SpaceProductAllocationEntity } from '@app/common/modules/space/entities/space-product-allocation.entity';
import { SubspaceProductAllocationEntity } from '@app/common/modules/space/entities/subspace/subspace-product-allocation.entity';
import { SubspaceEntity } from '@app/common/modules/space/entities/subspace/subspace.entity';
import { SubspaceRepository } from '@app/common/modules/space/repositories/subspace.repository';
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { DeviceService } from 'src/device/services';
@Injectable()
export class DeviceCommissionService {
constructor(
private readonly tuyaService: TuyaService,
private readonly deviceService: DeviceService,
private readonly deviceStatusFirebaseService: DeviceStatusFirebaseService,
private readonly communityRepository: CommunityRepository,
private readonly spaceRepository: SpaceRepository,
private readonly subspaceRepository: SubspaceRepository,
private readonly deviceRepository: DeviceRepository,
private readonly projectRepository: ProjectRepository,
) {}
async processCsv(param: ProjectParam, filePath: string) {
const successCount = { value: 0 };
const failureCount = { value: 0 };
const projectId = param.projectUuid;
const project = await this.projectRepository.findOne({
where: { uuid: projectId },
});
if (!project) {
throw new HttpException('Project not found', HttpStatus.NOT_FOUND);
}
const rows: any[] = [];
try {
await new Promise<void>((resolve, reject) => {
fs.createReadStream(filePath)
.pipe(csv())
.on('data', (row) => rows.push(row))
.on('end', () => resolve())
.on('error', (error) => reject(error));
});
for (const row of rows) {
await this.processCsvRow(param, row, successCount, failureCount);
}
return new SuccessResponseDto({
message: `Successfully processed CSV file`,
data: {
successCount: successCount.value,
failureCount: failureCount.value,
},
statusCode: HttpStatus.ACCEPTED,
});
} catch (error) {
throw new HttpException(
'Failed to process CSV file',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
private async processCsvRow(
param: ProjectParam,
row: any,
successCount: { value: number },
failureCount: { value: number },
) {
try {
const rawDeviceId = row['Device ID']?.trim();
const communityId = row['Community UUID']?.trim();
const spaceId = row['Space UUID']?.trim();
const subspaceId = row['Subspace UUID']?.trim();
const tagName = row['Tag']?.trim();
const productName = row['Product Name']?.trim();
const projectId = param.projectUuid;
let deviceName: string;
if (!rawDeviceId) {
console.error('Missing Device ID in row:', row);
failureCount.value++;
return;
}
const device = await this.tuyaService.getDeviceDetails(rawDeviceId);
if (!device) {
console.error(`Device not found for Device ID: ${rawDeviceId}`);
failureCount.value++;
return;
}
if (device && typeof device === 'object' && 'name' in device) {
deviceName = (device as any).name || '';
}
const community = await this.communityRepository.findOne({
where: { uuid: communityId, project: { uuid: projectId } },
});
if (!community) {
console.error(`Community not found: ${communityId}`);
failureCount.value++;
return;
}
const tuyaSpaceId = community.externalId;
const space = await this.spaceRepository.findOne({
where: { uuid: spaceId },
relations: [
'productAllocations',
'productAllocations.tag',
'productAllocations.product',
],
});
if (!space) {
console.error(`Space not found: ${spaceId}`);
failureCount.value++;
return;
}
let subspace: SubspaceEntity | null = null;
if (subspaceId?.trim()) {
subspace = await this.subspaceRepository.findOne({
where: { uuid: subspaceId },
relations: [
'productAllocations',
'productAllocations.tag',
'productAllocations.product',
],
});
if (!subspace) {
console.error(`Subspace not found: ${subspaceId}`);
failureCount.value++;
return;
}
}
const allocations =
subspace?.productAllocations || space.productAllocations;
const match = allocations
.map(
({
product,
tag,
}:
| SpaceProductAllocationEntity
| SubspaceProductAllocationEntity) => ({ product, tag }),
)
.find(
({ tag, product }) =>
tag.name === tagName && product.name === productName,
);
if (!match) {
console.error(
`No matching tag-product combination found for Device ID: ${rawDeviceId}`,
);
failureCount.value++;
return;
}
const devices = await this.deviceRepository.find({
where: {
spaceDevice: space,
},
relations: ['productDevice', 'tag'],
});
if (devices.length > 0) {
devices.forEach((device) => {
if (device.tag.uuid === match.tag.uuid) {
console.error(
`Device with same tag already exists: ${device.tag.name}`,
);
failureCount.value++;
return;
}
});
}
const middlewareDevice = this.deviceRepository.create({
deviceTuyaUuid: rawDeviceId,
isActive: true,
spaceDevice: space,
subspace: subspace || null,
productDevice: match.product,
tag: match.tag,
name: deviceName ?? '',
});
await this.deviceRepository.save(middlewareDevice);
await this.deviceService.transferDeviceInSpacesTuya(
rawDeviceId,
tuyaSpaceId,
);
await this.deviceStatusFirebaseService.addDeviceStatusByDeviceUuid(
rawDeviceId,
);
successCount.value++;
console.log(
`Device ${rawDeviceId} successfully processed and transferred to Tuya space ${tuyaSpaceId}`,
);
} catch (err) {
failureCount.value++;
}
}
}