import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { In, Repository } from 'typeorm'; import { AttendanceDevice, AttendanceDeviceStatus, Classroom } from '../entities'; import { CreateAttendanceDeviceDto, UpdateAttendanceDeviceDto } from './dto/attendance-device.dto'; @Injectable() export class AttendanceDevicesService { constructor( @InjectRepository(AttendanceDevice) private readonly repo: Repository, @InjectRepository(Classroom) private readonly classroomRepo: Repository, ) {} private normalizeSn(sn: string): string { return sn.trim(); } private async assertClassroomExists(classroomId: number): Promise { const exists = await this.classroomRepo.exist({ where: { id: classroomId } }); if (!exists) throw new BadRequestException('绑定教室不存在'); } async findAll(query?: { classroomId?: number; status?: AttendanceDeviceStatus | 'active' | 'disabled' }) { const where: Record = {}; if (query?.classroomId) where.classroomId = query.classroomId; if (query?.status) where.status = query.status; return this.repo.find({ where, relations: ['classroom'], order: { classroomId: 'ASC', deviceName: 'ASC' }, }); } async findOne(id: number) { const device = await this.repo.findOne({ where: { id }, relations: ['classroom'] }); if (!device) throw new NotFoundException('考勤机不存在'); return device; } async create(dto: CreateAttendanceDeviceDto) { const deviceSn = this.normalizeSn(dto.deviceSn); await this.assertClassroomExists(dto.classroomId); const exists = await this.repo.findOne({ where: { deviceSn } }); if (exists) throw new BadRequestException(`SN ${deviceSn} 已绑定教室`); const saved = await this.repo.save( this.repo.create({ ...dto, deviceSn, deviceName: dto.deviceName.trim(), status: dto.status ?? AttendanceDeviceStatus.ACTIVE, }), ); return this.findOne(saved.id); } async update(id: number, dto: UpdateAttendanceDeviceDto) { const device = await this.repo.findOne({ where: { id } }); if (!device) throw new NotFoundException('考勤机不存在'); const patch: Partial = { ...dto }; if (dto.classroomId != null) await this.assertClassroomExists(dto.classroomId); if (dto.deviceSn != null) { const deviceSn = this.normalizeSn(dto.deviceSn); const exists = await this.repo.findOne({ where: { deviceSn } }); if (exists && exists.id !== id) throw new BadRequestException(`SN ${deviceSn} 已绑定教室`); patch.deviceSn = deviceSn; } if (dto.deviceName != null) patch.deviceName = dto.deviceName.trim(); await this.repo.update(id, patch); return this.findOne(id); } async remove(id: number) { const device = await this.repo.findOne({ where: { id } }); if (!device) throw new NotFoundException('考勤机不存在'); if (device.status === AttendanceDeviceStatus.DISABLED) { throw new BadRequestException('考勤机已停用'); } await this.repo.update(id, { status: AttendanceDeviceStatus.DISABLED }); return { message: '已停用(绑定数据已保留)' }; } async findActiveBySn(deviceSns: string[]) { const sns = [...new Set(deviceSns.map((sn) => this.normalizeSn(sn)).filter(Boolean))]; if (sns.length === 0) return new Map(); const devices = await this.repo.find({ where: { deviceSn: In(sns), status: AttendanceDeviceStatus.ACTIVE }, relations: ['classroom'], }); return new Map(devices.map((device) => [device.deviceSn, device])); } async findActiveByClassroomIds(classroomIds: number[]) { const ids = [...new Set(classroomIds.filter((id) => Number.isFinite(id)))]; if (ids.length === 0) return new Map(); const devices = await this.repo.find({ where: { classroomId: In(ids), status: AttendanceDeviceStatus.ACTIVE }, relations: ['classroom'], order: { id: 'ASC' }, }); const result = new Map(); for (const device of devices) { if (!result.has(device.classroomId)) result.set(device.classroomId, device); } return result; } }