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

View File

@@ -4,6 +4,8 @@ import { Occupancy } from '../entities/occupancy.entity';
import { Room } from '../entities/room.entity'; import { Room } from '../entities/room.entity';
import { Student } from '../entities/student.entity'; import { Student } from '../entities/student.entity';
import { Deposit } from '../entities/deposit.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 { OccupanciesService } from './occupancies.service';
import { OccupanciesController } from './occupancies.controller'; import { OccupanciesController } from './occupancies.controller';
import { OperationLogsModule } from '../operation-logs/operation-logs.module'; import { OperationLogsModule } from '../operation-logs/operation-logs.module';
@@ -11,7 +13,7 @@ import { NotificationsModule } from '../notifications/notifications.module';
import { CommonModule } from '../common/common.module'; import { CommonModule } from '../common/common.module';
@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], controllers: [OccupanciesController],
providers: [OccupanciesService], providers: [OccupanciesService],
exports: [OccupanciesService], exports: [OccupanciesService],

View File

@@ -12,6 +12,8 @@ import {
import { Occupancy } from '../entities/occupancy.entity'; import { Occupancy } from '../entities/occupancy.entity';
import { Room } from '../entities/room.entity'; import { Room } from '../entities/room.entity';
import { Student } from '../entities/student.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 { Deposit } from '../entities/deposit.entity';
import { CheckInDto, CheckOutDto, TransferRoomDto } from './dto/occupancy.dto'; import { CheckInDto, CheckOutDto, TransferRoomDto } from './dto/occupancy.dto';
import { RoomsService } from '../rooms/rooms.service'; import { RoomsService } from '../rooms/rooms.service';
@@ -24,6 +26,8 @@ export class OccupanciesService {
@InjectRepository(Room) private roomRepo: Repository<Room>, @InjectRepository(Room) private roomRepo: Repository<Room>,
@InjectRepository(Student) private studentRepo: Repository<Student>, @InjectRepository(Student) private studentRepo: Repository<Student>,
@InjectRepository(Deposit) private depositRepo: Repository<Deposit>, @InjectRepository(Deposit) private depositRepo: Repository<Deposit>,
@InjectRepository(Bed) private bedRepo: Repository<Bed>,
@InjectRepository(Locker) private lockerRepo: Repository<Locker>,
private dataSource: DataSource, private dataSource: DataSource,
private readonly scope: CampusScope, private readonly scope: CampusScope,
) {} ) {}
@@ -33,6 +37,8 @@ export class OccupanciesService {
.createQueryBuilder('o') .createQueryBuilder('o')
.leftJoinAndSelect('o.student', 'student') .leftJoinAndSelect('o.student', 'student')
.leftJoinAndSelect('o.room', 'room') .leftJoinAndSelect('o.room', 'room')
.leftJoinAndSelect('o.bed', 'bed')
.leftJoinAndSelect('o.locker', 'locker')
.orderBy('o.checkInDate', 'DESC'); .orderBy('o.checkInDate', 'DESC');
const scopeIds = await this.scope.getScopeDepartmentIds(); const scopeIds = await this.scope.getScopeDepartmentIds();
if (scopeIds) { 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({ const occ = this.repo.create({
studentId: dto.studentId, studentId: dto.studentId,
roomId: dto.roomId, roomId: dto.roomId,
@@ -74,10 +94,20 @@ export class OccupanciesService {
rentalType: dto.rentalType, rentalType: dto.rentalType,
tenantId: dto.tenantId, tenantId: dto.tenantId,
notes: dto.notes, notes: dto.notes,
bedId: dto.bedId,
lockerId: dto.lockerId,
}); });
occ.departmentId = room.departmentId; occ.departmentId = room.departmentId;
const saved = await this.repo.save(occ); 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) { if (student.gender && !room.gender) {
await this.roomRepo.update(room.id, { gender: student.gender }); await this.roomRepo.update(room.id, { gender: student.gender });
@@ -100,6 +130,14 @@ export class OccupanciesService {
occ.checkOutReason = dto.checkOutReason || ''; occ.checkOutReason = dto.checkOutReason || '';
await this.repo.save(occ); 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' }); await this.roomRepo.update(occ.roomId, { status: 'available' });
@@ -128,6 +166,13 @@ export class OccupanciesService {
oldOcc.billingEndDate = dto.oldBillingEndDate || dto.transferDate; oldOcc.billingEndDate = dto.oldBillingEndDate || dto.transferDate;
oldOcc.checkOutReason = dto.reason || '换房'; oldOcc.checkOutReason = dto.reason || '换房';
await runner.manager.save(oldOcc); 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' }); await runner.manager.update(Room, oldOcc.roomId, { status: 'available' });
// 旧房如果已无在住人员,重置性别 // 旧房如果已无在住人员,重置性别
const oldRemaining = await runner.manager.count(Occupancy, { const oldRemaining = await runner.manager.count(Occupancy, {
@@ -151,6 +196,18 @@ export class OccupanciesService {
throw new BadRequestException(`目标宿舍为${newRoom.gender}生寝室,无法换入`); 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 transferDate = new Date(dto.transferDate);
const nextDay = new Date(transferDate); const nextDay = new Date(transferDate);
@@ -166,9 +223,19 @@ export class OccupanciesService {
rentalType: oldOcc.rentalType, rentalType: oldOcc.rentalType,
tenantId: oldOcc.tenantId, tenantId: oldOcc.tenantId,
notes: `${oldOcc.roomId}号房换入`, notes: `${oldOcc.roomId}号房换入`,
bedId: dto.newBedId,
lockerId: dto.newLockerId,
}); });
await runner.manager.save(newOcc); 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) { if (student?.gender && !newRoom.gender) {
await runner.manager.update(Room, newRoom.id, { gender: student.gender }); await runner.manager.update(Room, newRoom.id, { gender: student.gender });