fix: 修复多处边界条件问题
- rooms: parseRoomNumber 未知格式返回默认 capacity=4,防止 undefined 绕过入住容量检查 - rooms: 修复 parseInt() || undefined 导致楼层 0 被吞掉 - rooms: batchImport 中 capacity 使用 ?? 代替 ||,显式 0 不被覆盖 - occupancies: 所有 capacity 比较加 ?? 0 防守兜底,fail closed - schedules: assertValidScheduleRange 增加 startTime > endTime 校验 - attendance: 时段重叠检查改为按 startTime 排序后再比较,消除漏检 - attendance: 移除 getScheduleOptionsForAttendance 中不可靠的 raw[index] fallback - expenses: 个人附加费批量导入增加 assertPositiveAmount 校验 - expenses: 水电费导入增加 periodEnd >= periodStart 校验
This commit is contained in:
@@ -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 };
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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' });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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('排课结束日期不能早于开始日期');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user