fix(correctness): 并发/事务/实体/时区/状态一致性修复

由 OCR(open-codereview.ai,deepseek-v4-flash)审查驱动修复:
- wallets 原子扣款防 double-spend;refund 条件更新幂等;findTransactions 分页
- financial/imports/occupancies/attendance 事务与 advisory lock;重复生成/提交幂等
- 矛盾校验器、日期区间、实体双映射/DECIMAL/nullable、时区统一(china-time)
- rbac-seed 防重激活、exam 权限恢复、状态一致性、路由顺序、N+1/IN 分块等性能项

Reviewed-by: OCR (open-codereview.ai)
This commit is contained in:
2026-08-09 21:29:54 +08:00
parent 99ea931409
commit f50301148d
54 changed files with 3843 additions and 722 deletions

View File

@@ -75,7 +75,14 @@ export class OccupancyOperationsService {
await manager.save(occ);
if (occ.bedId) await manager.update(Bed, occ.bedId, { status: 'available' });
if (occ.lockerId) await manager.update(Locker, occ.lockerId, { status: 'available' });
await manager.update(Room, occ.roomId, { status: 'available' });
// 多床位房间不能因为单条退宿就置为 available
// 只有该房间不再有任何在住记录时才更新房间状态。
const remainingActive = await manager.count(Occupancy, {
where: { roomId: occ.roomId, checkOutDate: IsNull() },
});
if (remainingActive === 0) {
await manager.update(Room, occ.roomId, { status: 'available' });
}
return occ;
});
}
@@ -113,7 +120,14 @@ export class OccupancyOperationsService {
if (oldOcc.lockerId) {
await runner.manager.update(Locker, oldOcc.lockerId, { status: 'available' });
}
await runner.manager.update(Room, oldOcc.roomId, { status: 'available' });
// 多床位房间不能因为单次换房就置为 available
// 只有原房间不再有任何在住记录时才更新房间状态。
const oldRoomRemaining = await runner.manager.count(Occupancy, {
where: { roomId: oldOcc.roomId, checkOutDate: IsNull() },
});
if (oldRoomRemaining === 0) {
await runner.manager.update(Room, oldOcc.roomId, { status: 'available' });
}
// 检查新房容量
const newRoom = await withPessimisticWriteLock(
runner.manager
@@ -365,12 +379,18 @@ export class OccupancyOperationsService {
occ.billingEndDate = dto.billingEndDate || dto.checkOutDate;
occ.checkOutReason = dto.checkOutReason || '';
await runner.manager.save(occ);
// 更新房间状态
await runner.manager.update(Room, occ.roomId, { status: 'available' });
// 释放床位/柜子
if (occ.bedId) await runner.manager.update(Bed, occ.bedId, { status: 'available' });
if (occ.lockerId)
await runner.manager.update(Locker, occ.lockerId, { status: 'available' });
// 多床位房间不能因为单条退宿就置为 available
// 只有该房间不再有任何在住记录时才更新房间状态。
const remainingActive = await runner.manager.count(Occupancy, {
where: { roomId: occ.roomId, checkOutDate: IsNull() },
});
if (remainingActive === 0) {
await runner.manager.update(Room, occ.roomId, { status: 'available' });
}
success++;
}
await runner.commitTransaction();