fix commissioning

This commit is contained in:
hannathkadher
2025-04-21 10:58:14 +04:00
parent c677be400c
commit ed3c526efd
11 changed files with 214 additions and 64 deletions

View File

@ -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: [],
})

View File

@ -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,

View File

@ -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++;
}
}
}