mirror of
https://github.com/SyncrowIOT/backend.git
synced 2025-11-26 08:14:55 +00:00
fix commissioning
This commit is contained in:
@ -19,6 +19,8 @@ import {
|
||||
SceneRepository,
|
||||
} from '@app/common/modules/scene/repositories';
|
||||
import { AutomationRepository } from '@app/common/modules/automation/repositories';
|
||||
import { CommunityRepository } from '@app/common/modules/community/repositories';
|
||||
import { SubspaceRepository } from '@app/common/modules/space/repositories/subspace.repository';
|
||||
|
||||
@Module({
|
||||
imports: [ConfigModule, SpaceRepositoryModule],
|
||||
@ -39,6 +41,8 @@ import { AutomationRepository } from '@app/common/modules/automation/repositorie
|
||||
SceneIconRepository,
|
||||
SceneRepository,
|
||||
AutomationRepository,
|
||||
CommunityRepository,
|
||||
SubspaceRepository,
|
||||
],
|
||||
exports: [],
|
||||
})
|
||||
|
||||
@ -22,9 +22,10 @@ import { ControllerRoute } from '@app/common/constants/controller-route';
|
||||
import { EnableDisableStatusEnum } from '@app/common/constants/days.enum';
|
||||
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
|
||||
import { CommissionDeviceCsvDto } from '../dto';
|
||||
import { CommunityParam } from '@app/common/dto/community-space.param';
|
||||
import { DeviceCommissionService } from '../services';
|
||||
import { ProjectParam } from '@app/common/dto/project-param.dto';
|
||||
import { Permissions } from 'src/decorators/permissions.decorator';
|
||||
import { PermissionsGuard } from 'src/guards/permissions.guard';
|
||||
|
||||
@ApiTags('Commission Devices Module')
|
||||
@Controller({
|
||||
@ -34,6 +35,8 @@ import { ProjectParam } from '@app/common/dto/project-param.dto';
|
||||
export class DeviceCommissionController {
|
||||
constructor(private readonly commissionService: DeviceCommissionService) {}
|
||||
|
||||
@UseGuards(PermissionsGuard)
|
||||
@Permissions('COMMISSION_DEVICE')
|
||||
@ApiBearerAuth()
|
||||
@Post()
|
||||
@ApiConsumes('multipart/form-data')
|
||||
@ -65,7 +68,7 @@ export class DeviceCommissionController {
|
||||
@Param() param: ProjectParam,
|
||||
@Req() req: any,
|
||||
): Promise<BaseResponseDto> {
|
||||
await this.commissionService.processCsv(file.path);
|
||||
await this.commissionService.processCsv(param, file.path);
|
||||
return {
|
||||
message: 'CSV file received and processing started',
|
||||
success: true,
|
||||
|
||||
@ -2,44 +2,185 @@ import * as fs from 'fs';
|
||||
import * as csv from 'csv-parser';
|
||||
|
||||
import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
||||
import { DeviceService } from 'src/device/services';
|
||||
import { CommunityRepository } from '@app/common/modules/community/repositories';
|
||||
import { SpaceRepository } from '@app/common/modules/space';
|
||||
import { SubspaceRepository } from '@app/common/modules/space/repositories/subspace.repository';
|
||||
import { SubspaceEntity } from '@app/common/modules/space/entities/subspace/subspace.entity';
|
||||
import { DeviceRepository } from '@app/common/modules/device/repositories';
|
||||
import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
|
||||
import { ProjectParam } from '@app/common/dto/project-param.dto';
|
||||
import { ProjectRepository } from '@app/common/modules/project/repositiories';
|
||||
|
||||
@Injectable()
|
||||
export class DeviceCommissionService {
|
||||
constructor(
|
||||
private readonly tuyaService: TuyaService,
|
||||
private readonly deviceService: DeviceService,
|
||||
private readonly communityRepository: CommunityRepository,
|
||||
private readonly spaceRepository: SpaceRepository,
|
||||
private readonly subspaceRepository: SubspaceRepository,
|
||||
private readonly deviceRepository: DeviceRepository,
|
||||
private readonly projectRepository: ProjectRepository,
|
||||
) {}
|
||||
|
||||
async processCsv(filePath: string): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const results = [];
|
||||
async processCsv(param: ProjectParam, filePath: string) {
|
||||
const successCount = { value: 0 };
|
||||
const failureCount = { value: 0 };
|
||||
|
||||
fs.createReadStream(filePath)
|
||||
.pipe(csv())
|
||||
.on('data', async (row) => {
|
||||
console.log(`Device: ${JSON.stringify(row)}`);
|
||||
const deviceId = row.deviceId?.trim();
|
||||
const projectId = param.projectUuid;
|
||||
|
||||
if (!deviceId) {
|
||||
console.error('Missing deviceId or deviceName in row:', row);
|
||||
return;
|
||||
} else {
|
||||
const device = await this.tuyaService.getDeviceDetails(
|
||||
row.deviceId,
|
||||
);
|
||||
console.log(device);
|
||||
}
|
||||
})
|
||||
.on('end', () => {
|
||||
console.log(`Finished processing ${results.length} devices.`);
|
||||
resolve();
|
||||
})
|
||||
.on('error', (error) => {
|
||||
console.error('Error reading CSV', error);
|
||||
reject(error);
|
||||
});
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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.tags',
|
||||
'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.tags',
|
||||
'productAllocations.product',
|
||||
],
|
||||
});
|
||||
|
||||
if (!subspace) {
|
||||
console.error(`Subspace not found: ${subspaceId}`);
|
||||
failureCount.value++;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const allocations =
|
||||
subspace?.productAllocations || space.productAllocations;
|
||||
|
||||
const match = allocations
|
||||
.flatMap((pa) =>
|
||||
(pa.tags || []).map((tag) => ({ product: pa.product, tag })),
|
||||
)
|
||||
.find(({ tag }) => tag.name === tagName);
|
||||
|
||||
if (!match) {
|
||||
console.error(`No matching tag found for Device ID: ${rawDeviceId}`);
|
||||
failureCount.value++;
|
||||
return;
|
||||
}
|
||||
|
||||
if (match.product.name !== productName) {
|
||||
console.error(`Product name mismatch for Device ID: ${rawDeviceId}`);
|
||||
failureCount.value++;
|
||||
return;
|
||||
}
|
||||
|
||||
const middlewareDevice = this.deviceRepository.create({
|
||||
deviceTuyaUuid: rawDeviceId,
|
||||
isActive: true,
|
||||
spaceDevice: space,
|
||||
subspace: subspace || null,
|
||||
productDevice: match.product,
|
||||
tag: match.tag,
|
||||
});
|
||||
|
||||
await this.deviceRepository.save(middlewareDevice);
|
||||
|
||||
await this.deviceService.transferDeviceInSpacesTuya(
|
||||
rawDeviceId,
|
||||
tuyaSpaceId,
|
||||
);
|
||||
successCount.value++;
|
||||
} catch (err) {
|
||||
failureCount.value++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user