feat: streamline occupancy check-in and imports

This commit is contained in:
2026-07-14 12:23:53 +08:00
parent 718c58589f
commit d84f37e98f
11 changed files with 322 additions and 78 deletions

View File

@@ -16,7 +16,6 @@ import { Bed } from '../entities/bed.entity';
import { Locker } from '../entities/locker.entity';
import { Deposit } from '../entities/deposit.entity';
import { Organization } from '../entities/organization.entity';
import { uuidV7 } from '../common/uuid-v7';
import { CheckInDto, CheckOutDto, TransferRoomDto } from './dto/occupancy.dto';
import { RoomsService } from '../rooms/rooms.service';
@@ -40,7 +39,6 @@ export class OccupanciesService {
.leftJoinAndSelect('o.room', 'room')
.leftJoinAndSelect('o.bed', 'bed')
.leftJoinAndSelect('o.locker', 'locker')
.leftJoinAndSelect('o.responsibleOrganization', 'responsibleOrganization')
.orderBy('o.checkInDate', 'DESC');
if (query?.roomId) qb.andWhere('o.roomId = :roomId', { roomId: query.roomId });
if (query?.studentId) qb.andWhere('o.studentId = :studentId', { studentId: query.studentId });
@@ -48,7 +46,7 @@ export class OccupanciesService {
return qb.getMany();
}
async checkIn(dto: CheckInDto) {
async checkIn(dto: CheckInDto, userId?: number) {
// 检查学生是否已有活跃入住
const existing = await this.repo.findOne({
where: { studentId: dto.studentId, checkOutDate: IsNull() },
@@ -86,7 +84,7 @@ export class OccupanciesService {
checkInDate: dto.checkInDate,
billingStartDate: dto.billingStartDate || dto.checkInDate,
stayType: dto.stayType,
responsibleOrganizationId: dto.responsibleOrganizationId ?? student.organizationId,
responsibleOrganizationId: student.organizationId,
notes: dto.notes,
bedId: dto.bedId,
lockerId: dto.lockerId,
@@ -105,6 +103,25 @@ export class OccupanciesService {
if (count + 1 >= room.capacity) {
await this.roomRepo.update(room.id, { status: 'full' });
}
if (dto.collectDeposit) {
const existingDeposit = await this.depositRepo.findOne({
where: { studentId: dto.studentId, status: 'paid' },
});
if (!existingDeposit) {
await this.depositRepo.save(
this.depositRepo.create({
studentId: dto.studentId,
amount: dto.depositAmount ?? 500,
paidDate: dto.checkInDate,
status: 'paid',
recordedBy: userId,
notes: '入住登记自动收取',
}),
);
}
}
return saved;
}
@@ -336,7 +353,6 @@ export class OccupanciesService {
ethnicity?: string;
emergencyContact?: string;
emergencyPhone?: string;
organization?: string;
supervisor?: string;
roomNumber: string;
building?: string;
@@ -365,42 +381,33 @@ export class OccupanciesService {
}
try {
// 1. 解析所属机构;未填写时默认本机构
let organization = row.organization?.trim()
? await this.organizationRepo.findOne({ where: { name: row.organization.trim() } })
: await this.organizationRepo.findOne({ where: { isHost: true, status: 'active' } });
if (!organization && row.organization?.trim()) {
organization = await this.organizationRepo.save(
this.organizationRepo.create({
publicId: uuidV7(),
code: `ORG_${Date.now()}_${i}`,
name: row.organization.trim(),
isHost: false,
status: 'active',
}),
);
}
if (!organization) throw new BadRequestException('尚未配置本机构');
// 1. 通过手机号关联学生;未找到时创建学生并归入本机构
const phone = row.phone?.trim();
if (!phone) throw new BadRequestException('手机号不能为空,无法关联学生');
let student = await this.studentRepo.findOne({ where: { name: row.name.trim() } });
let student = await this.studentRepo.findOne({ where: { phone } });
if (!student) {
const hostOrganization = await this.organizationRepo.findOne({
where: { isHost: true, status: 'active' },
});
if (!hostOrganization) throw new BadRequestException('尚未配置本机构');
student = await this.studentRepo.save(
this.studentRepo.create({
name: row.name.trim(),
phone: row.phone?.trim() || undefined,
phone,
idNumber: row.idNumber?.trim() || undefined,
gender: row.gender?.trim() || undefined,
ethnicity: row.ethnicity?.trim() || undefined,
emergencyContact: row.emergencyContact?.trim() || undefined,
emergencyPhone: row.emergencyPhone?.trim() || undefined,
organizationId: organization.id,
organizationId: hostOrganization.id,
supervisor: row.supervisor?.trim() || undefined,
}),
);
} else {
// 更新已有学生的缺失信息
const updates: any = {};
if (!student.phone && row.phone?.trim()) updates.phone = row.phone.trim();
if (!student.idNumber && row.idNumber?.trim()) updates.idNumber = row.idNumber.trim();
if (!student.gender && row.gender?.trim()) updates.gender = row.gender.trim();
if (!student.ethnicity && row.ethnicity?.trim()) updates.ethnicity = row.ethnicity.trim();
@@ -408,7 +415,6 @@ export class OccupanciesService {
updates.emergencyContact = row.emergencyContact.trim();
if (!student.emergencyPhone && row.emergencyPhone?.trim())
updates.emergencyPhone = row.emergencyPhone.trim();
if (!student.organizationId) updates.organizationId = organization.id;
if (!student.supervisor && row.supervisor?.trim())
updates.supervisor = row.supervisor.trim();
if (Object.keys(updates).length > 0) {
@@ -499,7 +505,7 @@ export class OccupanciesService {
checkInDate,
billingStartDate: row.billingStartDate?.trim() || checkInDate,
stayType: row.stayType || undefined,
responsibleOrganizationId: student.organizationId || organization.id,
responsibleOrganizationId: student.organizationId,
notes: row.notes || undefined,
bedId: bed?.id,
lockerId: locker?.id,