feat: integrate Bed/Locker into Occupancy check-in/out flow

This commit is contained in:
2026-07-09 12:02:43 +08:00
parent 3bed3409fe
commit 54d8d0545e
3 changed files with 84 additions and 1 deletions

View File

@@ -25,6 +25,13 @@ export class CheckInDto {
@IsOptional()
@IsInt()
tenantId?: number;
@IsOptional()
@IsInt()
bedId?: number; // 后续改 required
@IsOptional()
@IsInt()
lockerId?: number;
}
export class CheckOutDto {
@@ -51,6 +58,13 @@ export class TransferRoomDto {
@IsString()
oldBillingEndDate?: string; // 旧房计费截止日,默认=transferDate
@IsOptional()
@IsInt()
newBedId?: number;
@IsOptional()
@IsInt()
newLockerId?: number;
@IsOptional()
@IsString()
newBillingStartDate?: string; // 新房计费起始日,默认=transferDate次日

View File

@@ -4,6 +4,8 @@ import { Occupancy } from '../entities/occupancy.entity';
import { Room } from '../entities/room.entity';
import { Student } from '../entities/student.entity';
import { Deposit } from '../entities/deposit.entity';
import { Bed } from '../entities/bed.entity';
import { Locker } from '../entities/locker.entity';
import { OccupanciesService } from './occupancies.service';
import { OccupanciesController } from './occupancies.controller';
import { OperationLogsModule } from '../operation-logs/operation-logs.module';
@@ -11,7 +13,7 @@ import { NotificationsModule } from '../notifications/notifications.module';
import { CommonModule } from '../common/common.module';
@Module({
imports: [TypeOrmModule.forFeature([Occupancy, Room, Student, Deposit]), OperationLogsModule, NotificationsModule, CommonModule],
imports: [TypeOrmModule.forFeature([Occupancy, Room, Student, Deposit, Bed, Locker]), OperationLogsModule, NotificationsModule, CommonModule],
controllers: [OccupanciesController],
providers: [OccupanciesService],
exports: [OccupanciesService],

View File

@@ -12,6 +12,8 @@ import {
import { Occupancy } from '../entities/occupancy.entity';
import { Room } from '../entities/room.entity';
import { Student } from '../entities/student.entity';
import { Bed } from '../entities/bed.entity';
import { Locker } from '../entities/locker.entity';
import { Deposit } from '../entities/deposit.entity';
import { CheckInDto, CheckOutDto, TransferRoomDto } from './dto/occupancy.dto';
import { RoomsService } from '../rooms/rooms.service';
@@ -24,6 +26,8 @@ export class OccupanciesService {
@InjectRepository(Room) private roomRepo: Repository<Room>,
@InjectRepository(Student) private studentRepo: Repository<Student>,
@InjectRepository(Deposit) private depositRepo: Repository<Deposit>,
@InjectRepository(Bed) private bedRepo: Repository<Bed>,
@InjectRepository(Locker) private lockerRepo: Repository<Locker>,
private dataSource: DataSource,
private readonly scope: CampusScope,
) {}
@@ -33,6 +37,8 @@ export class OccupanciesService {
.createQueryBuilder('o')
.leftJoinAndSelect('o.student', 'student')
.leftJoinAndSelect('o.room', 'room')
.leftJoinAndSelect('o.bed', 'bed')
.leftJoinAndSelect('o.locker', 'locker')
.orderBy('o.checkInDate', 'DESC');
const scopeIds = await this.scope.getScopeDepartmentIds();
if (scopeIds) {
@@ -66,6 +72,20 @@ export class OccupanciesService {
);
}
// 床位校验
if (dto.bedId) {
const bed = await this.bedRepo.findOne({ where: { id: dto.bedId, roomId: dto.roomId } });
if (!bed) throw new BadRequestException('床位不存在或不属于该宿舍');
if (bed.status !== 'available') throw new BadRequestException('该床位已被占用或维修中');
}
// 柜子校验
if (dto.lockerId) {
const locker = await this.lockerRepo.findOne({ where: { id: dto.lockerId, roomId: dto.roomId } });
if (!locker) throw new BadRequestException('柜子不存在或不属于该宿舍');
if (locker.status !== 'available') throw new BadRequestException('该柜子已被占用或维修中');
}
const occ = this.repo.create({
studentId: dto.studentId,
roomId: dto.roomId,
@@ -74,10 +94,20 @@ export class OccupanciesService {
rentalType: dto.rentalType,
tenantId: dto.tenantId,
notes: dto.notes,
bedId: dto.bedId,
lockerId: dto.lockerId,
});
occ.departmentId = room.departmentId;
const saved = await this.repo.save(occ);
// 更新床位/柜子状态
if (dto.bedId) {
await this.bedRepo.update(dto.bedId, { status: 'occupied' });
}
if (dto.lockerId) {
await this.lockerRepo.update(dto.lockerId, { status: 'occupied' });
}
// 首位入住者确定房间性别
if (student.gender && !room.gender) {
await this.roomRepo.update(room.id, { gender: student.gender });
@@ -100,6 +130,14 @@ export class OccupanciesService {
occ.checkOutReason = dto.checkOutReason || '';
await this.repo.save(occ);
// 释放床位/柜子
if (occ.bedId) {
await this.bedRepo.update(occ.bedId, { status: 'available' });
}
if (occ.lockerId) {
await this.lockerRepo.update(occ.lockerId, { status: 'available' });
}
// 更新宿舍状态
await this.roomRepo.update(occ.roomId, { status: 'available' });
@@ -128,6 +166,13 @@ export class OccupanciesService {
oldOcc.billingEndDate = dto.oldBillingEndDate || dto.transferDate;
oldOcc.checkOutReason = dto.reason || '换房';
await runner.manager.save(oldOcc);
// 释放旧床位/柜子
if (oldOcc.bedId) {
await runner.manager.update(Bed, oldOcc.bedId, { status: 'available' });
}
if (oldOcc.lockerId) {
await runner.manager.update(Locker, oldOcc.lockerId, { status: 'available' });
}
await runner.manager.update(Room, oldOcc.roomId, { status: 'available' });
// 旧房如果已无在住人员,重置性别
const oldRemaining = await runner.manager.count(Occupancy, {
@@ -151,6 +196,18 @@ export class OccupanciesService {
throw new BadRequestException(`目标宿舍为${newRoom.gender}生寝室,无法换入`);
}
// 新床位校验
if (dto.newBedId) {
const newBed = await runner.manager.findOne(Bed, { where: { id: dto.newBedId, roomId: dto.newRoomId } });
if (!newBed) throw new BadRequestException('目标床位不存在或不属于目标宿舍');
if (newBed.status !== 'available') throw new BadRequestException('目标床位已被占用');
}
if (dto.newLockerId) {
const newLocker = await runner.manager.findOne(Locker, { where: { id: dto.newLockerId, roomId: dto.newRoomId } });
if (!newLocker) throw new BadRequestException('目标柜子不存在或不属于目标宿舍');
if (newLocker.status !== 'available') throw new BadRequestException('目标柜子已被占用');
}
// 计算新房计费起始日:默认为换房日期次日
const transferDate = new Date(dto.transferDate);
const nextDay = new Date(transferDate);
@@ -166,9 +223,19 @@ export class OccupanciesService {
rentalType: oldOcc.rentalType,
tenantId: oldOcc.tenantId,
notes: `${oldOcc.roomId}号房换入`,
bedId: dto.newBedId,
lockerId: dto.newLockerId,
});
await runner.manager.save(newOcc);
// 更新新床位/柜子状态
if (dto.newBedId) {
await runner.manager.update(Bed, dto.newBedId, { status: 'occupied' });
}
if (dto.newLockerId) {
await runner.manager.update(Locker, dto.newLockerId, { status: 'occupied' });
}
// 首位入住者确定新房性别
if (student?.gender && !newRoom.gender) {
await runner.manager.update(Room, newRoom.id, { gender: student.gender });