diff --git a/apps/server/src/attendance/attendance.service.ts b/apps/server/src/attendance/attendance.service.ts index 3218b81..e1d27b2 100644 --- a/apps/server/src/attendance/attendance.service.ts +++ b/apps/server/src/attendance/attendance.service.ts @@ -866,9 +866,13 @@ export class AttendanceService { }; }).sort((left, right) => left.sortOrder - right.sortOrder); - for (let index = 1; index < normalized.length; index += 1) { - const previous = normalized[index - 1]; - const current = normalized[index]; + // 按开始时间排序后再检查重叠,避免 sortOrder 与时间顺序不一致时漏检 + const sortedByTime = [...normalized].sort( + (left, right) => this.toMinutes(left.startTime) - this.toMinutes(right.startTime), + ); + for (let index = 1; index < sortedByTime.length; index += 1) { + const previous = sortedByTime[index - 1]; + const current = sortedByTime[index]; if (previous.enabled && current.enabled && this.toMinutes(current.startTime) < this.toMinutes(previous.endTime)) { throw new BadRequestException(`${previous.label} 和 ${current.label} 时间段不能重叠`); } @@ -996,10 +1000,10 @@ export class AttendanceService { ]), ); - return entities.map((schedule, index) => { + return entities.map((schedule) => { const teacher = teacherByScheduleId.get(schedule.id) ?? { - teacherName: raw[index]?.teacherName || null, - teacherUsername: raw[index]?.teacherUsername || null, + teacherName: null, + teacherUsername: null, }; return { ...schedule, ...teacher }; }); diff --git a/apps/server/src/expenses/expenses.service.ts b/apps/server/src/expenses/expenses.service.ts index d818e33..efa1e38 100644 --- a/apps/server/src/expenses/expenses.service.ts +++ b/apps/server/src/expenses/expenses.service.ts @@ -291,6 +291,11 @@ export class ExpensesService { skipped++; continue; } + if (!this.isValidDate(periodStart) || !this.isValidDate(periodEnd) || periodEnd < periodStart) { + errors.push(`第${rowNum}行: ${row.roomNumber} 账期无效(${periodStart} ~ ${periodEnd}),已跳过`); + skipped++; + continue; + } // 关键校验:电费 + 水费 都为 0 时,多半是 Excel 公式未正确计算或字段缺失, // 必须给出明确错误,避免出现"提示成功但无数据"的迷之现象。 @@ -439,6 +444,15 @@ export class ExpensesService { } } + // 校验金额 + try { + this.assertPositiveAmount(row.amount); + } catch (e: any) { + errors.push(`第${rowNum}行: ${row.studentName} ${e.message}`); + skipped++; + continue; + } + await this.personalExpRepo.save( this.personalExpRepo.create({ studentId: student.id, diff --git a/apps/server/src/occupancies/occupancies.service.ts b/apps/server/src/occupancies/occupancies.service.ts index 496aebb..5ff0881 100644 --- a/apps/server/src/occupancies/occupancies.service.ts +++ b/apps/server/src/occupancies/occupancies.service.ts @@ -82,7 +82,7 @@ export class OccupanciesService { const count = await manager.count(Occupancy, { where: { roomId: dto.roomId, checkOutDate: IsNull() }, }); - if (count >= room.capacity) throw new BadRequestException('宿舍已满'); + if (count >= (room.capacity ?? 0)) throw new BadRequestException('宿舍已满'); const student = await manager.findOne(Student, { where: { id: dto.studentId } }); if (!student) throw new NotFoundException('学生不存在'); @@ -124,8 +124,7 @@ export class OccupanciesService { ); if (dto.bedId) await manager.update(Bed, dto.bedId, { status: 'occupied' }); if (dto.lockerId) await manager.update(Locker, dto.lockerId, { status: 'occupied' }); - if (count + 1 >= room.capacity) await manager.update(Room, room.id, { status: 'full' }); - + if (count + 1 >= (room.capacity ?? 0)) await manager.update(Room, room.id, { status: 'full' }); if (dto.collectDeposit) { let deposit = await manager.findOne(Deposit, { where: { studentId: dto.studentId } }); if (deposit) { @@ -225,7 +224,7 @@ export class OccupanciesService { const count = await runner.manager.count(Occupancy, { where: { roomId: dto.newRoomId, checkOutDate: IsNull() }, }); - if (count >= newRoom.capacity) throw new BadRequestException('目标宿舍已满'); + if (count >= (newRoom.capacity ?? 0)) throw new BadRequestException('目标宿舍已满'); // 新床位校验 if (dto.newBedId) { @@ -286,7 +285,7 @@ export class OccupanciesService { await runner.manager.update(Locker, dto.newLockerId, { status: 'occupied' }); } - if (count + 1 >= newRoom.capacity) { + if (count + 1 >= (newRoom.capacity ?? 0)) { await runner.manager.update(Room, newRoom.id, { status: 'full' }); } @@ -556,9 +555,9 @@ export class OccupanciesService { // 4. 检查宿舍容量 const count = await occupancyRepo.count({ where: { roomId: room.id, checkOutDate: IsNull() } }); - if (!isHistoricalRecord && count >= room.capacity) { + if (!isHistoricalRecord && count >= (room.capacity ?? 0)) { throw new ImportRowSkipped( - `第${rowNum}行: 宿舍 ${row.roomNumber} 已满(${count}/${room.capacity}),跳过 ${row.name}`, + `第${rowNum}行: 宿舍 ${row.roomNumber} 已满(${count}/${room.capacity ?? '?'}),跳过 ${row.name}`, ); } @@ -569,9 +568,9 @@ export class OccupanciesService { bed = await bedRepo.findOne({ where: { roomId: room.id, bedNumber } }); if (!bed) { const existingBedCount = await bedRepo.count({ where: { roomId: room.id } }); - if (existingBedCount >= room.capacity) { + if (existingBedCount >= (room.capacity ?? 0)) { throw new BadRequestException( - `宿舍 ${room.roomNumber} 已有 ${existingBedCount} 张床位,不能超过额定人数 ${room.capacity}`, + `宿舍 ${room.roomNumber} 已有 ${existingBedCount} 张床位,不能超过额定人数 ${room.capacity ?? '?'}`, ); } bed = await bedRepo.save( @@ -620,7 +619,7 @@ export class OccupanciesService { if (!isHistoricalRecord) { if (bed) await bedRepo.update(bed.id, { status: 'occupied' }); if (locker) await lockerRepo.update(locker.id, { status: 'occupied' }); - if (count + 1 >= room.capacity) { + if (count + 1 >= (room.capacity ?? 0)) { await roomRepo.update(room.id, { status: 'full' }); } } diff --git a/apps/server/src/rooms/rooms.controller.ts b/apps/server/src/rooms/rooms.controller.ts index 8d10c92..88ff447 100644 --- a/apps/server/src/rooms/rooms.controller.ts +++ b/apps/server/src/rooms/rooms.controller.ts @@ -349,7 +349,7 @@ export class RoomsController { rows.push({ roomNumber: String(row.getCell(1).value || ''), building: String(row.getCell(2).value || '') || undefined, - floor: Number(row.getCell(3).value) || undefined, + floor: (n => Number.isNaN(n) ? undefined : n)(Number(row.getCell(3).value)), capacity: Number(row.getCell(4).value) || 4, roomType: String(row.getCell(5).value || '').trim() || undefined, rentalCategory, diff --git a/apps/server/src/rooms/rooms.service.ts b/apps/server/src/rooms/rooms.service.ts index ba4c63c..896848c 100644 --- a/apps/server/src/rooms/rooms.service.ts +++ b/apps/server/src/rooms/rooms.service.ts @@ -50,7 +50,8 @@ export class RoomsService { if (familyMatch) { const bldg = `${familyMatch[1]}-${familyMatch[2]}栋`; const roomPart = familyMatch[3]; - const floor = parseInt(roomPart.charAt(0), 10) || undefined; + const rawFloor = parseInt(roomPart.charAt(0), 10); + const floor = Number.isNaN(rawFloor) ? undefined : rawFloor; return { building: bldg, floor, roomType: '家庭房', capacity: 4 }; } // 标准: X-YZZ 格式 @@ -58,7 +59,8 @@ export class RoomsService { if (stdMatch) { const bldgNum = stdMatch[1]; const roomPart = stdMatch[2]; - const floor = parseInt(roomPart.charAt(0), 10) || undefined; + const rawFloor = parseInt(roomPart.charAt(0), 10); + const floor = Number.isNaN(rawFloor) ? undefined : rawFloor; const building = `${bldgNum}号楼`; let roomType = '四人间'; let capacity = 4; @@ -71,7 +73,7 @@ export class RoomsService { } return { building, floor, roomType, capacity }; } - return {}; + return { capacity: 4, roomType: '四人间' }; } async findAll(query?: { building?: string; includeArchived?: boolean }) { @@ -374,7 +376,7 @@ export class RoomsService { roomNumber: row.roomNumber.trim(), building: row.building?.trim() || parsed.building || undefined, floor: row.floor ?? parsed.floor, - capacity: row.capacity || parsed.capacity || 4, + capacity: row.capacity ?? parsed.capacity ?? 4, roomType: row.roomType || parsed.roomType || undefined, rentalCategory: row.rentalCategory || undefined, monthlyRate: row.monthlyRate ?? undefined, diff --git a/apps/server/src/schedules/schedules.service.ts b/apps/server/src/schedules/schedules.service.ts index 57217e5..d89a8ff 100644 --- a/apps/server/src/schedules/schedules.service.ts +++ b/apps/server/src/schedules/schedules.service.ts @@ -198,6 +198,9 @@ export class SchedulesService { if (startTime === endTime) { throw new BadRequestException('上课时间和下课时间不能相同'); } + if (startTime > endTime) { + throw new BadRequestException('上课时间不能晚于下课时间'); + } if (startDate > endDate) { throw new BadRequestException('排课结束日期不能早于开始日期'); }