chore: commit oxfmt formatting changes and verify artifacts
This commit is contained in:
@@ -1,6 +1,14 @@
|
||||
import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository, DataSource, IsNull, Between, LessThanOrEqual, MoreThanOrEqual, In } from 'typeorm';
|
||||
import {
|
||||
Repository,
|
||||
DataSource,
|
||||
IsNull,
|
||||
Between,
|
||||
LessThanOrEqual,
|
||||
MoreThanOrEqual,
|
||||
In,
|
||||
} from 'typeorm';
|
||||
import { Occupancy } from '../entities/occupancy.entity';
|
||||
import { Room } from '../entities/room.entity';
|
||||
import { Student } from '../entities/student.entity';
|
||||
@@ -19,7 +27,8 @@ export class OccupanciesService {
|
||||
) {}
|
||||
|
||||
async findAll(query?: { roomId?: number; studentId?: number; active?: boolean }) {
|
||||
const qb = this.repo.createQueryBuilder('o')
|
||||
const qb = this.repo
|
||||
.createQueryBuilder('o')
|
||||
.leftJoinAndSelect('o.student', 'student')
|
||||
.leftJoinAndSelect('o.room', 'room')
|
||||
.orderBy('o.checkInDate', 'DESC');
|
||||
@@ -31,7 +40,9 @@ export class OccupanciesService {
|
||||
|
||||
async checkIn(dto: CheckInDto) {
|
||||
// 检查学生是否已有活跃入住
|
||||
const existing = await this.repo.findOne({ where: { studentId: dto.studentId, checkOutDate: IsNull() } });
|
||||
const existing = await this.repo.findOne({
|
||||
where: { studentId: dto.studentId, checkOutDate: IsNull() },
|
||||
});
|
||||
if (existing) throw new BadRequestException('该学生已有在住记录,请先办理退宿');
|
||||
|
||||
// 检查宿舍容量
|
||||
@@ -44,7 +55,9 @@ export class OccupanciesService {
|
||||
const student = await this.studentRepo.findOne({ where: { id: dto.studentId } });
|
||||
if (!student) throw new NotFoundException('学生不存在');
|
||||
if (student.gender && room.gender && student.gender !== room.gender) {
|
||||
throw new BadRequestException(`该宿舍当前为${room.gender}生寝室,${student.gender}生无法入住`);
|
||||
throw new BadRequestException(
|
||||
`该宿舍当前为${room.gender}生寝室,${student.gender}生无法入住`,
|
||||
);
|
||||
}
|
||||
|
||||
const occ = this.repo.create({
|
||||
@@ -82,7 +95,9 @@ export class OccupanciesService {
|
||||
await this.roomRepo.update(occ.roomId, { status: 'available' });
|
||||
|
||||
// 如果房间已无在住人员,重置房间性别
|
||||
const remaining = await this.repo.count({ where: { roomId: occ.roomId, checkOutDate: IsNull() } });
|
||||
const remaining = await this.repo.count({
|
||||
where: { roomId: occ.roomId, checkOutDate: IsNull() },
|
||||
});
|
||||
if (remaining === 0) {
|
||||
await this.roomRepo.update(occ.roomId, { gender: null as any });
|
||||
}
|
||||
@@ -106,7 +121,9 @@ export class OccupanciesService {
|
||||
await runner.manager.save(oldOcc);
|
||||
await runner.manager.update(Room, oldOcc.roomId, { status: 'available' });
|
||||
// 旧房如果已无在住人员,重置性别
|
||||
const oldRemaining = await runner.manager.count(Occupancy, { where: { roomId: oldOcc.roomId, checkOutDate: IsNull() } });
|
||||
const oldRemaining = await runner.manager.count(Occupancy, {
|
||||
where: { roomId: oldOcc.roomId, checkOutDate: IsNull() },
|
||||
});
|
||||
if (oldRemaining === 0) {
|
||||
await runner.manager.update(Room, oldOcc.roomId, { gender: null as any });
|
||||
}
|
||||
@@ -114,7 +131,9 @@ export class OccupanciesService {
|
||||
// 检查新房容量
|
||||
const newRoom = await runner.manager.findOne(Room, { where: { id: dto.newRoomId } });
|
||||
if (!newRoom) throw new NotFoundException('目标宿舍不存在');
|
||||
const count = await runner.manager.count(Occupancy, { where: { roomId: dto.newRoomId, checkOutDate: IsNull() } });
|
||||
const count = await runner.manager.count(Occupancy, {
|
||||
where: { roomId: dto.newRoomId, checkOutDate: IsNull() },
|
||||
});
|
||||
if (count >= newRoom.capacity) throw new BadRequestException('目标宿舍已满');
|
||||
|
||||
// 换房性别约束检查
|
||||
@@ -160,7 +179,8 @@ export class OccupanciesService {
|
||||
|
||||
// 获取某宿舍在指定时间段内的入住记录(用于计费)
|
||||
async getRoomOccupanciesInPeriod(roomId: number, periodStart: string, periodEnd: string) {
|
||||
return this.repo.createQueryBuilder('o')
|
||||
return this.repo
|
||||
.createQueryBuilder('o')
|
||||
.leftJoinAndSelect('o.student', 'student')
|
||||
.where('o.roomId = :roomId', { roomId })
|
||||
.andWhere('o.billingStartDate <= :periodEnd', { periodEnd })
|
||||
@@ -190,19 +210,26 @@ export class OccupanciesService {
|
||||
}
|
||||
let deleted = 0;
|
||||
if (deletableIds.length > 0) {
|
||||
const result = await this.repo.createQueryBuilder()
|
||||
const result = await this.repo
|
||||
.createQueryBuilder()
|
||||
.delete()
|
||||
.where('id IN (:...ids)', { ids: deletableIds })
|
||||
.execute();
|
||||
deleted = result.affected || 0;
|
||||
}
|
||||
const message = skipped.length > 0
|
||||
? `成功删除 ${deleted} 条;${skipped.length} 条在住记录被跳过(${skipped.slice(0, 5).join('、')}${skipped.length > 5 ? '…' : ''}),请先办理退宿`
|
||||
: `批量删除成功,共 ${deleted} 条`;
|
||||
const message =
|
||||
skipped.length > 0
|
||||
? `成功删除 ${deleted} 条;${skipped.length} 条在住记录被跳过(${skipped.slice(0, 5).join('、')}${skipped.length > 5 ? '…' : ''}),请先办理退宿`
|
||||
: `批量删除成功,共 ${deleted} 条`;
|
||||
return { message, deleted, skipped: skipped.length };
|
||||
}
|
||||
|
||||
async batchCheckOut(dto: { ids: number[]; checkOutDate: string; billingEndDate?: string; checkOutReason?: string }) {
|
||||
async batchCheckOut(dto: {
|
||||
ids: number[];
|
||||
checkOutDate: string;
|
||||
billingEndDate?: string;
|
||||
checkOutReason?: string;
|
||||
}) {
|
||||
if (!dto.ids || dto.ids.length === 0) {
|
||||
throw new BadRequestException('请选择要退宿的记录');
|
||||
}
|
||||
@@ -213,9 +240,18 @@ export class OccupanciesService {
|
||||
const errors: string[] = [];
|
||||
try {
|
||||
for (const id of dto.ids) {
|
||||
const occ = await runner.manager.findOne(Occupancy, { where: { id }, relations: ['student'] });
|
||||
if (!occ) { errors.push(`记录${id}不存在`); continue; }
|
||||
if (occ.checkOutDate) { errors.push(`${occ.student?.name || id}已退宿`); continue; }
|
||||
const occ = await runner.manager.findOne(Occupancy, {
|
||||
where: { id },
|
||||
relations: ['student'],
|
||||
});
|
||||
if (!occ) {
|
||||
errors.push(`记录${id}不存在`);
|
||||
continue;
|
||||
}
|
||||
if (occ.checkOutDate) {
|
||||
errors.push(`${occ.student?.name || id}已退宿`);
|
||||
continue;
|
||||
}
|
||||
occ.checkOutDate = dto.checkOutDate;
|
||||
occ.billingEndDate = dto.billingEndDate || dto.checkOutDate;
|
||||
occ.checkOutReason = dto.checkOutReason || '';
|
||||
@@ -223,7 +259,9 @@ export class OccupanciesService {
|
||||
// 更新房间状态
|
||||
await runner.manager.update(Room, occ.roomId, { status: 'available' });
|
||||
// 如果房间已无在住人员,重置性别
|
||||
const remaining = await runner.manager.count(Occupancy, { where: { roomId: occ.roomId, checkOutDate: IsNull() } });
|
||||
const remaining = await runner.manager.count(Occupancy, {
|
||||
where: { roomId: occ.roomId, checkOutDate: IsNull() },
|
||||
});
|
||||
if (remaining === 0) {
|
||||
await runner.manager.update(Room, occ.roomId, { gender: null as any });
|
||||
}
|
||||
@@ -236,7 +274,12 @@ export class OccupanciesService {
|
||||
} finally {
|
||||
await runner.release();
|
||||
}
|
||||
return { success, failed: errors.length, message: `已成功退宿 ${success} 人${errors.length > 0 ? `,${errors.length} 条失败` : ''}`, errors: errors.length > 0 ? errors : undefined };
|
||||
return {
|
||||
success,
|
||||
failed: errors.length,
|
||||
message: `已成功退宿 ${success} 人${errors.length > 0 ? `,${errors.length} 条失败` : ''}`,
|
||||
errors: errors.length > 0 ? errors : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -244,13 +287,24 @@ export class OccupanciesService {
|
||||
* 每行数据:姓名、电话、学号、房间号、楼栋、入住日期
|
||||
* 自动创建不存在的学生和宿舍,并登记入住
|
||||
*/
|
||||
async batchImportCheckIn(rows: {
|
||||
name: string; phone?: string; idNumber?: string;
|
||||
gender?: string; ethnicity?: string; emergencyContact?: string; emergencyPhone?: string;
|
||||
organization?: string; supervisor?: string;
|
||||
roomNumber: string; building?: string;
|
||||
checkInDate: string; checkOutDate?: string;
|
||||
}[], options?: { autoDeposit?: boolean; depositAmount?: number }) {
|
||||
async batchImportCheckIn(
|
||||
rows: {
|
||||
name: string;
|
||||
phone?: string;
|
||||
idNumber?: string;
|
||||
gender?: string;
|
||||
ethnicity?: string;
|
||||
emergencyContact?: string;
|
||||
emergencyPhone?: string;
|
||||
organization?: string;
|
||||
supervisor?: string;
|
||||
roomNumber: string;
|
||||
building?: string;
|
||||
checkInDate: string;
|
||||
checkOutDate?: string;
|
||||
}[],
|
||||
options?: { autoDeposit?: boolean; depositAmount?: number },
|
||||
) {
|
||||
let imported = 0;
|
||||
let skipped = 0;
|
||||
let depositsCreated = 0;
|
||||
@@ -269,17 +323,19 @@ export class OccupanciesService {
|
||||
// 1. 查找或创建学生
|
||||
let student = await this.studentRepo.findOne({ where: { name: row.name.trim() } });
|
||||
if (!student) {
|
||||
student = await this.studentRepo.save(this.studentRepo.create({
|
||||
name: row.name.trim(),
|
||||
phone: row.phone?.trim() || undefined,
|
||||
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,
|
||||
organization: row.organization?.trim() || undefined,
|
||||
supervisor: row.supervisor?.trim() || undefined,
|
||||
}));
|
||||
student = await this.studentRepo.save(
|
||||
this.studentRepo.create({
|
||||
name: row.name.trim(),
|
||||
phone: row.phone?.trim() || undefined,
|
||||
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,
|
||||
organization: row.organization?.trim() || undefined,
|
||||
supervisor: row.supervisor?.trim() || undefined,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
// 更新已有学生的缺失信息
|
||||
const updates: any = {};
|
||||
@@ -287,10 +343,14 @@ export class OccupanciesService {
|
||||
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();
|
||||
if (!student.emergencyContact && row.emergencyContact?.trim()) updates.emergencyContact = row.emergencyContact.trim();
|
||||
if (!student.emergencyPhone && row.emergencyPhone?.trim()) updates.emergencyPhone = row.emergencyPhone.trim();
|
||||
if (!student.organization && row.organization?.trim()) updates.organization = row.organization.trim();
|
||||
if (!student.supervisor && row.supervisor?.trim()) updates.supervisor = row.supervisor.trim();
|
||||
if (!student.emergencyContact && row.emergencyContact?.trim())
|
||||
updates.emergencyContact = row.emergencyContact.trim();
|
||||
if (!student.emergencyPhone && row.emergencyPhone?.trim())
|
||||
updates.emergencyPhone = row.emergencyPhone.trim();
|
||||
if (!student.organization && row.organization?.trim())
|
||||
updates.organization = row.organization.trim();
|
||||
if (!student.supervisor && row.supervisor?.trim())
|
||||
updates.supervisor = row.supervisor.trim();
|
||||
if (Object.keys(updates).length > 0) {
|
||||
await this.studentRepo.update(student.id, updates);
|
||||
Object.assign(student, updates);
|
||||
@@ -301,19 +361,26 @@ export class OccupanciesService {
|
||||
let room = await this.roomRepo.findOne({ where: { roomNumber: row.roomNumber.trim() } });
|
||||
if (!room) {
|
||||
const parsed = RoomsService.parseRoomNumber(row.roomNumber.trim());
|
||||
room = await this.roomRepo.save(this.roomRepo.create({
|
||||
roomNumber: row.roomNumber.trim(),
|
||||
building: row.building?.trim() || parsed.building || undefined,
|
||||
floor: parsed.floor || undefined,
|
||||
capacity: parsed.capacity || 4,
|
||||
roomType: parsed.roomType || undefined,
|
||||
}));
|
||||
room = await this.roomRepo.save(
|
||||
this.roomRepo.create({
|
||||
roomNumber: row.roomNumber.trim(),
|
||||
building: row.building?.trim() || parsed.building || undefined,
|
||||
floor: parsed.floor || undefined,
|
||||
capacity: parsed.capacity || 4,
|
||||
roomType: parsed.roomType || undefined,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// 3. 检查是否已有活跃入住
|
||||
const existing = await this.repo.findOne({ where: { studentId: student.id, checkOutDate: IsNull() }, relations: ['room'] });
|
||||
const existing = await this.repo.findOne({
|
||||
where: { studentId: student.id, checkOutDate: IsNull() },
|
||||
relations: ['room'],
|
||||
});
|
||||
if (existing) {
|
||||
errors.push(`第${rowNum}行: ${row.name} 已在住(${existing.room?.roomNumber || '房间' + existing.roomId}),跳过`);
|
||||
errors.push(
|
||||
`第${rowNum}行: ${row.name} 已在住(${existing.room?.roomNumber || '房间' + existing.roomId}),跳过`,
|
||||
);
|
||||
skipped++;
|
||||
continue;
|
||||
}
|
||||
@@ -321,14 +388,18 @@ export class OccupanciesService {
|
||||
// 4. 检查宿舍容量
|
||||
const count = await this.repo.count({ where: { roomId: room.id, checkOutDate: IsNull() } });
|
||||
if (count >= room.capacity) {
|
||||
errors.push(`第${rowNum}行: 宿舍 ${row.roomNumber} 已满(${count}/${room.capacity}),跳过 ${row.name}`);
|
||||
errors.push(
|
||||
`第${rowNum}行: 宿舍 ${row.roomNumber} 已满(${count}/${room.capacity}),跳过 ${row.name}`,
|
||||
);
|
||||
skipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 5. 房间级别性别约束
|
||||
if (student.gender && room.gender && student.gender !== room.gender) {
|
||||
errors.push(`第${rowNum}行: 宿舍 ${row.roomNumber} 为${room.gender}生寝室,${row.name}(${student.gender})无法入住,跳过`);
|
||||
errors.push(
|
||||
`第${rowNum}行: 宿舍 ${row.roomNumber} 为${room.gender}生寝室,${row.name}(${student.gender})无法入住,跳过`,
|
||||
);
|
||||
skipped++;
|
||||
continue;
|
||||
}
|
||||
@@ -361,15 +432,19 @@ export class OccupanciesService {
|
||||
|
||||
// 9. 自动收取押金(仅对新入住且非历史记录的学生)
|
||||
if (options?.autoDeposit && !row.checkOutDate?.trim()) {
|
||||
const existingDeposit = await this.depositRepo.findOne({ where: { studentId: student.id, status: 'paid' } });
|
||||
const existingDeposit = await this.depositRepo.findOne({
|
||||
where: { studentId: student.id, status: 'paid' },
|
||||
});
|
||||
if (!existingDeposit) {
|
||||
await this.depositRepo.save(this.depositRepo.create({
|
||||
studentId: student.id,
|
||||
amount: options.depositAmount || 500,
|
||||
paidDate: checkInDate,
|
||||
status: 'paid',
|
||||
notes: '入住导入自动收取',
|
||||
}));
|
||||
await this.depositRepo.save(
|
||||
this.depositRepo.create({
|
||||
studentId: student.id,
|
||||
amount: options.depositAmount || 500,
|
||||
paidDate: checkInDate,
|
||||
status: 'paid',
|
||||
notes: '入住导入自动收取',
|
||||
}),
|
||||
);
|
||||
depositsCreated++;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user